Compares two CSV exports of the same records (old vs new, billing vs ledger, source vs warehouse) by key after normalizing headers, delimiters, currency formats, accounting negatives, dates and case. Reports added, removed and duplicate keys, key format drift, changed values per column with tolerances, possible re-keys and a control-total bridge, then writes a reconciliation report. Includes a…
--- name: csv-diff-and-reconciliation-checker description: Compares two CSV exports of the same records (old vs new, billing vs ledger, source vs warehouse) by key after normalizing headers, delimiters, currency formats, accounting negatives, dates and case. Reports added, removed and duplicate keys, key format drift, changed values per column with tolerances, possible re-keys and a control-total bridge, then writes a reconciliation report. Includes a stdlib Python checker with --self-test. --- # CSV Diff and Reconciliation Checker You help analysts, finance teams and data engineers answer "do these two files agree, and if not, exactly why?". You match rows by a business key, ignore differences that are only formatting, and turn the real differences into a short list of causes with the amounts that each one explains, so the totals tie out to the cent. ## Files in this skill - `scripts/csv_recon.py` - matches two CSVs by key (single or composite), normalizes values, lists schema differences, duplicate and blank keys, added, removed and changed rows, key format drift and possible re-keys, and builds a control-total bridge (Python 3 standard library only) - `references/matching-and-normalization.md` - choosing a key, header and value normalization, number and date formats, tolerances, and what each finding code means - `references/reconciliation-playbook.md` - common causes of breaks (timing, duplicates, sign conventions, rounding, filters, encoding) and how to prove each one - `templates/reconciliation-report.md` - the report to hand back - `examples/billing-export-sept.csv` - September invoices from a billing system (14 rows, comma-separated) - `examples/ledger-export-sept.csv` - the same month from the general ledger (15 rows, semicolons, $1,200.00 amounts, US dates, renamed and extra columns) ## Workflow ### 1. Understand the two files Ask what each file is, which system and filters produced it (date range, status, entity, currency), which side is the source of truth, what the key is, and which differences matter (amounts to the cent, statuses, dates). Ask for row counts and totals the user already expects. Look at the first lines of both files before running anything. ### 2. Run the checker ``` python3 scripts/csv_recon.py examples/billing-export-sept.csv examples/ledger-export-sept.csv --key invoice_id --map customer=customer_name --tol amount=0.01 --sum amount --ignore-case --dates invoice_date python3 scripts/csv_recon.py orders_old.csv orders_new.csv --key order_id,line_no --ignore updated_at --out-csv diff.csv python3 scripts/csv_recon.py a.csv b.csv --key id --json python3 scripts/csv_recon.py --self-test ``` Headers that differ only by case or spacing match automatically; renamed columns need `--map old=new`. Exit 0 means the files reconcile, 1 means differences, 2 means bad input (missing file or key column). ### 3. Explain every difference Work down the findings (see `references/reconciliation-playbook.md`): duplicates and blank keys first (they distort everything else), then added and removed rows (timing, filters, re-keys), then changed values grouped by column and by pattern (same delta, sign flip, digits swapped, rounding). Use the bridge: old total + added - removed + changes - duplicates = new total, with nothing unexplained. Check a sample of rows by hand in both source systems. ### 4. Deliver Fill `templates/reconciliation-report.md`: scope and inputs, result, summary counts, the bridge, each break with cause, amount, owner and fix, and what to change so next month reconciles automatically (stable keys, same filters, export formats). Attach `--out-csv` output for the people who fix the rows. ## Example result Billing vs ledger for September: 14 vs 15 rows, 13 matched (INV-1009 matched to `inv-1009` as KEY_FORMAT_DRIFT), 11 unchanged. Breaks: INV-1011 posted twice in the ledger (HIGH DUPLICATE_KEY, +880.00), INV-1004 450.00 vs 405.00 (digits swapped, -45.00), INV-1012 open in billing but Paid in the ledger, INV-1007 missing and INV-1015 added with the same customer and amount (POSSIBLE_REKEY: posted in October under a new number). $ signs, thousands separators, (150.00) negatives, US dates and status case are formatting only; the INV-1003 rounding (1033.335 vs 1033.34) is inside the 0.01 tolerance. Bridge: 9545.965 + 310.00 - 310.00 - 44.995 + 880.00 = 10380.97, unexplained 0. ## Rules - Never compare by row position; agree on a key first and check it is unique on both sides. - Separate formatting noise from real differences and say which normalizations you applied. - Every break gets a cause, an amount and an owner; "timing" is a cause only if you name the period it lands in. - Do not net differences against each other to make totals tie; list them. - Keep tolerances explicit and small (rounding only), never large enough to hide a real error. - Treat customer and personal data in the files as confidential; share only what the report needs. FILE:references/matching-and-normalization.md # Matching and normalization ## Choosing the key - Use a business key that both systems carry: invoice number, order id + line number, transaction id. Composite keys: `--key order_id,line_no`. - Check uniqueness on both sides before anything else. Duplicates mean either a real double posting or a key that is not unique (e.g. invoice_id without line number). - Avoid keys that change between systems: row numbers, surrogate database ids, names, timestamps. - If there is no shared key, build one (date + amount + reference) and expect some ambiguous matches; review them by hand. ## Header matching - Exact name first, then case/space/underscore/hyphen-insensitive (`Invoice ID` = `invoice_id`), reported as HEADER_FORMAT. - Renamed columns: `--map customer=customer_name`. Columns only on one side are reported and not compared. - A UTF-8 byte order mark (BOM) at the start of the file is removed; otherwise the first header would not match. ## Delimiters and encoding - Comma, semicolon, tab and pipe are detected. European Excel exports often use `;` with `,` as decimal separator; this checker expects `.` decimals, so convert those first (or export again with `.`). - Read files as UTF-8. If you see `é` instead of `é`, the file is Windows-1252: convert with `iconv -f cp1252 -t utf-8`. ## Value normalization (applied to every compared cell) | Raw | Treated as | |---|---| | leading/trailing spaces, repeated inner spaces | trimmed, single space | | `1,234.50`, `$1,234.50`, `1234.5`, `1234.50 USD` | number 1234.5 | | `(150.00)`, `150.00-`, `-$150.00` | number -150 | | `12.5%` | number 12.5 (not 0.125) | | empty, `null`, `None`, `N/A`, `NA`, `-` | empty | | `Paid` vs `paid` | different, unless `--ignore-case` | | `09/01/2026`, `2026/09/01`, `01.09.2026` | 2026-09-01 for columns in `--dates` (US month-first unless `--dayfirst`) | Numbers are compared as decimals, not floats, so `0.1 + 0.2` problems do not appear. Leading zeros in numeric-looking codes (`00123` vs `123`) compare equal as numbers; if that is wrong for a code column, add it to `--ignore` and compare it separately. ## Tolerances - `--tol amount=0.01` per column, or `--tol 0.01` for every numeric column. - Use the smallest unit of the currency for rounding differences; never use a tolerance to hide a known error. - Values inside the tolerance are counted as FORMAT_ONLY and still appear in the bridge as changes. ## Finding codes | Code | Severity | Meaning | |---|---|---| | DUPLICATE_KEY | HIGH | the same key appears more than once on one side; only the first row is compared | | MISSING_KEY | HIGH | rows with a blank key; they cannot be matched | | TOTAL_UNEXPLAINED | HIGH | the bridge does not tie (non-numeric cells, blank keys) | | COLUMN_ONLY_IN_OLD / NEW | MEDIUM | schema difference; map it if it was renamed | | ROWS_ADDED / ROWS_REMOVED | MEDIUM | keys on one side only | | VALUE_CHANGED | MEDIUM | per column, rows whose normalized values differ | | RAGGED_ROWS | MEDIUM | rows with more or fewer cells than the header (bad quoting, embedded delimiters) | | NON_NUMERIC | MEDIUM | cells in a --sum column that are not numbers | | HEADER_FORMAT | LOW | columns matched only after ignoring case and spacing | | KEY_FORMAT_DRIFT | LOW | keys equal after ignoring case, spaces, separators and leading zeros; compared as one row | | POSSIBLE_REKEY | LOW | a removed and an added row with the same non-key, non-date values | | FORMAT_ONLY, DELIMITER, TOTAL_DIFF | INFO | context: equal after normalization, different delimiters, totals moved | ## The control-total bridge ``` OLD total + amounts of rows only in NEW - amounts of rows only in OLD + (NEW - OLD) on matched rows - extra duplicate or blank-key rows in OLD + extra duplicate or blank-key rows in NEW = NEW total (unexplained must be 0) ``` FILE:references/reconciliation-playbook.md # Reconciliation playbook: causes of breaks and how to prove them Work in this order: duplicates and blank keys, then rows on one side only, then changed values, then totals. ## Duplicates - **Double posting** (same key, same values twice): a retry, an import run twice, a manual re-entry. Prove: identical rows, often the same created timestamp or batch id. Fix: reverse one posting; add an idempotency key to the import. - **Non-unique key** (same key, different values): the key needs another column (line number, currency, entity). Re-run with a composite key. ## Rows on one side only - **Timing / cut-off**: the record exists in both systems but in different periods (invoice dated 30 Sep, posted 1 Oct). Prove: find it in the next or previous period export. Typical sign: POSSIBLE_REKEY with a date just across the boundary. - **Different filters**: one export excludes drafts, voids, test accounts, zero amounts, another entity or currency. Prove: compare the export queries or report settings; count the excluded statuses. - **Re-keyed records**: credit note and re-issue, migrated ids, prefix changes (INV- vs IN-). Prove: same customer, amount and date with a new key. - **Failed sync**: the record never reached the target. Prove: integration logs, error queue, last successful sync time. - **Key format**: case, spaces, leading zeros, separators (KEY_FORMAT_DRIFT). Fix at the source; normalize keys in the export. ## Changed values | Pattern | Likely cause | |---|---| | delta is a multiple of 9 (450 -> 405, 1,234 -> 1,324) | transposed digits on manual entry | | exact sign flip | debit/credit convention, credit notes stored as positive with a type column | | delta equals tax or fee rate x amount | one side gross, the other net | | delta of 0.01 to 0.05 | rounding per line vs on the total; half-up vs banker's rounding | | amount / rate | currency conversion, different rate date | | date off by one day | time zone (UTC vs local) on timestamps near midnight | | status differs | sync lag, or the systems use different state machines (paid vs settled) | | text differs only in accents or odd characters | encoding (UTF-8 vs Windows-1252) | ## Totals - Tie the bridge to zero before reporting. A non-zero unexplained amount means blank keys, non-numeric cells or a column that mixes currencies. - Report totals per currency; never add USD and EUR. - Check the totals against the system's own report (trial balance, payout report), not just against the export. ## Proving a cause For each break: show the row(s) on both sides, the cause, the amount, and evidence (log line, export setting, the row in the next period). Mark each as: fix in source A, fix in source B, timing (will clear), or accepted difference. ## Preventing next month's breaks - Same filters and period definition (time zone, inclusive end date) on both exports. - Stable, unique keys carried end to end; no manual re-keying. - Export amounts as plain numbers with `.` decimals and a separate currency column; dates as ISO 8601. - Run the checker on a schedule and alert on HIGH findings or unexplained totals. FILE:templates/reconcil
출처 · prompts.chat 공개 프롬프트 모음 · 작성 Fatih Kadir Akın — CC0(저작권 포기)로 공개된 원문을 그대로 싣습니다. 한국어 정리 · 실행 결과는 아직 없습니다.