# csv-roundtrip

Export a CSV, change one column by rule, prove that nothing else changed, import it.
From Distru's No Bullshit AI Course, module 05, lesson 1. Version 1.0 (2026-09-17). MIT.

Works for the three requests we get most: mass-edit sales-order status, bulk cost-type
edit, image-URL mapping. Works for anything else that is "same file back, one column different".

## Files

| file | what |
|---|---|
| `transform.py` | the script. Python 3.10+, standard library only |
| `rules.example.json` | one bulk edit written down: which column, which rows, what value |
| `sample-in.csv` | a fake 8-order export to practise on |
| `sample-out.csv` | what `transform.py` produces from the sample with the example rules |

## Run it

```bash
# 1. see what would change, write nothing
python3 transform.py --in sample-in.csv --rules rules.example.json --out changed.csv --dry-run

# 2. write the file
python3 transform.py --in sample-in.csv --rules rules.example.json --out changed.csv

# 3. prove the file is safe to import (exit code 0 = go, 1 = stop)
python3 transform.py --validate --in sample-in.csv --rules rules.example.json --out changed.csv
```

Expected output of step 1 on the sample:

```
DRY RUN: 8 rows in, 8 rows out
  3 row(s) change in column 'status'
  SO-1041: status: 'READY_TO_SHIP' -> 'DELIVERING'
  SO-1042: status: 'READY_TO_SHIP' -> 'DELIVERING'
  SO-1047: status: 'READY_TO_SHIP' -> 'DELIVERING'
OK (dry run). Re-run without --dry-run to write the file.
```

## What `--validate` refuses

- row count changed
- header changed, or a required column is missing
- the key column (`order_number`, `sku`, ...) changed on any row, is blank, or is duplicated
- any column not named in the rules changed on any row
- a value outside `allowed_values`
- the output differs from what the rules would produce (someone edited it by hand afterwards)

Any of those and it prints `STOP` and exits 1. Do not import a file that says STOP.

## Writing a rules file

`key` is the column that identifies a row. `changes` is a list; each entry names a `column`
and either `set` (one value for every matching row) or `map` (old value to new value).
`where` limits which rows a change touches; every listed column must match, exactly.
`keep_unmapped: true` leaves values that are not in the map alone and tells you which ones it saw.

Let the AI write the rules file for you: paste the header row, three sample rows and this
README, and ask for a `rules.json` that does your edit. Then run `--dry-run` and read the
diff before you believe it.

## Before you import

Open the target system's import template first. Column names have to match it exactly,
and a status has to be one the system accepts (Distru orders use `PENDING`, `PROCESSING`,
`READY_TO_SHIP`, `DELIVERING`, `DELIVERED`, `COMPLETED`, `CANCELED`). Keep the original
export; name it `original-YYYY-MM-DD.csv` and never edit it.
