# Statement Import specification
Pair: [user guide](../../user/accounting/statement-import.md). Entry public/import.php with app.js, app.css/platform.css/import-review.css; dispatch public/api.php. Actions: upload_csv, read_csv, normalize_rows, accounts, mappings, preview_import, statement_storage, confirm_import. See [inventory](../reference/action-inventory.md).
Tables read/write: statement_accounts, csv_statement_formats, statement_imports, statement_data; business context reads accounting_businesses; rules/classification and adapter create records documented in [Accounting](capability.md). Generic nodes/vectors are written only through the adapter/core.
## State and request contracts
GET init returns csrf, accounts, mappings and active businesses. POST upload multipart csv returns token, original name, headers, first-three sample rows, row count and already_imported_id. Session statement_upload stores token/path/hash/name. A replacement upload removes that session's previous staged file and preview.
POST preview accepts {token,account:{id,name,currency},mapping,coverage:{basis,start,end},save_mapping,mapping_save:{id,name},business_context:{business_id}}. Response includes preview_token, account_assigned, coverage, coverage_error, filename, rows, already_imported_id. Session freezes mapping/account/coverage/context, but confirm recomputes rows from staged bytes.
POST confirm accepts {preview_token,include_duplicates:[logical row numbers]}. Response includes statement_import_id, counts, account, stored_filename and coverage; same-file replay adds already_imported:true. POST delete_mapping accepts id and deletes a saved mapping definition, not imported evidence.
## CSV and normalization
Comma-delimited UTF-8 only, optional BOM, maximum 10 MiB and 5,000 logical transactions. No NULs. Quoted fields may include commas/newlines and escaped quotes. Blank records are omitted. Unexpected quotes, text after closing quote and unterminated quotes are errors. Headers are trimmed, nonempty and unique; _source_row_number is reserved.
Logical source_row_number is index+2 after omitted blank records; source_line_number separately identifies the starting physical line. These differ with blank lines and multiline fields. Original cells and raw JSON are retained; never assume logical row number is an exact editor line number.
Mapping required fields: date_column, date_format, description_columns[], money_layout signed|debit_credit, currency. Optional joiner, amount_column/debit_column/credit_column, balance_column/reference_column and positive_meaning credit|debit. Every selected column must exist. Debit and credit columns must differ. There is no automatic ambiguous-date inference.
Formats: ymd_dash Y-m-d, ymd_slash Y/m/d, mdy_slash m/d/Y, dmy_slash d/m/Y, compact Ymd, month_name M j, Y or F j, Y. Invalid parse/warnings produce Invalid date. Monetary parsing strips dollar/comma/space, accepts signs/parentheses and at most two decimals; blank is null, zero valid; oversized whole portions reject. Signed positive_meaning debit reverses signs. Separate debit/credit uses absolute values and rejects both nonzero. Missing description/amount, column count mismatch or invalid optional balance is a row error.
Row result includes source date/cells, interpreted date/description/amount/balance, raw_data/raw_json/hash, error list and duplicate flag. Reference column is validated/stored in mapping and raw evidence, not a separately normalized reference field.
## Duplicate and evidence policy
Account-scoped duplicate fingerprints remove _source_row_number, sort raw header keys and hash JSON. Already imported matching raw rows are counted as a multiset: repeated source occurrences consume known counts. This is exact source-content comparison, not fuzzy date/amount matching. New-account preview has no known duplicates.
Whole-file hash is checked across statement_imports. Same file under same account returns existing import rather than adding records. Under another account it is rejected. This same-file branch occurs before accepting include_duplicates, so it cannot be used to force a second identical file import.
## Coverage and raw preview
Earliest/latest parsed dates infer coverage across the full CSV, including skipped duplicates. Invalid dates mark incomplete with invalid_date_rows. Stated coverage validates dates/start<=end and inclusion of every parsed transaction. Preview catches coverage errors and returns rows with diagnostics instead of hiding them. Proposed filename only when account and coverage are complete and error-free.
The original date beside interpreted date and expandable source fields are diagnostic evidence. Confirmation rejects every row error, even for a duplicate that would otherwise be skipped. No silent invalid-row discard.
## Confirmation transaction and filesystem
Check preview token, required account, staged file existence and SHA-256. Acquire named database-scoped GET_LOCK for up to 10 seconds; always RELEASE_LOCK. Re-resolve existing account/currency. Re-read, normalize, mark duplicates, reject invalid rows, select nonduplicates plus explicit duplicate inclusions; reject zero accepted rows.
Start DB transaction. Create new account if needed; copy original bytes to private/storage/accounts/{account_key}/{start}_to_{end}_{sanitized-name}.csv using exclusive create and numeric suffix on collisions. Verify size/hash, chmod 0600. Optionally save mapping. Insert import context and accepted evidence rows; create revision-zero core payment vectors through ac_import_row. Evaluate rules; auto-commit only valid complete permitted results when statement manual review is off.
Commit, remove staging file and session state. On failure rollback DB; remove newly retained file when commit did not occur. Filesystem/DB are not one atomic transaction: process crashes between operations remain a recovery consideration, not an implemented recovery feature.
## Verification and exclusions
Test ambiguous dates chosen explicitly, malformed date/amount/CSV, original fields unchanged, debit/credit signs, zero, duplicate multiplicity and overrides, exact-file replay, different-account rejection, stated-period conflict, provisional coverage and confirmation block, no filename before valid coverage, concurrent imports, rollback removes retained copy, retained hash equals uploaded hash. Pending-upload recovery and abandoned staging cleanup remain undecided/unimplemented; do not add them as incidental maintenance.
## Planned extensions
The user intends additional import methods beyond CSV. This specification describes current CSV behaviour; no additional adapters are installed in this stage.