Ingesting the daily sales export
A shop receives one CSV a day and opens it by hand. Nine steps to automate the reading, the typing and the filtering — then read what the run tells you.
Context — a shop that closes its day in Excel
A retailer exports its orders every evening from the point-of-sale software. The file lands in a folder, someone opens it in a spreadsheet, converts the amounts, keeps the large orders and mails the result to the sales manager. Fifteen minutes a day, every day, done by hand.
Where things stand
- One file a day —
sales_2025-03-04.csv, seven orders, six columns. - Everything is text. A CSV has no types:
120.50is a string until someone converts it. - No trace. Nobody can say afterwards which file produced which figure.
- Manual filtering. The rule — keep orders of 100 and above — lives in a person's head.
The question
How do you read that file automatically, type its columns, apply the rule and write the result — without opening a spreadsheet, and in a way that runs the same tomorrow?
- Read the file where it sits, without copying or moving it
- Turn the amounts into numbers before comparing them
- Keep only the orders of 100 and above
- Write a file that can be regenerated without piling up
The solution in one line
A Hydra job: four small YAML files in one folder, one command to check them, one command to run.
Create the folder and drop the export inside it.
Steps
1. Create the folder and drop the export
- Create a folder
sales/wherever you work. - Inside it, create
data/andout/. - Put the export in
sales/data/sales_2025-03-04.csv.
mkdir -p sales/data sales/out
sales/data/sales_2025-03-04.csv and has a header line.2. Declare the source
- Create
sales/sources.yaml. - Name the entry
src_sales— you choose this name. - Set
type: csv, thenbase_pathto the folder andextract.tableto the file.
version: "1.0"
sources:
src_sales:
type: csv
connection:
base_path: "sales/data"
extract:
table: sales_2025-03-04.csv
batch_size: 10000
base_path is resolved from the folder you run the command in, not from the job folder. Run everything from the folder that contains sales/, or write an absolute path.3. Declare the destination
- Create
sales/destinations.yaml. - Name the entry
dest_large_orders. - Set
mode: replace.
version: "1.0"
destinations:
dest_large_orders:
type: csv
connection:
base_path: "sales/out"
load:
table: large_orders.csv
mode: replace
append. With replace the output stays at three rows however many times you run; with append it would grow every evening.4. Wire the two ends
- Create
sales/pipeline.yaml. - Write the two identifiers declared above — not paths.
version: "1.0" pipeline: from: src_sales to: dest_large_orders
5. Type the amounts
- Create
sales/transformations.yaml. - Write
stepsat the root, then onecastentry.
version: "1.0"
steps:
- cast:
mapping:
amount: float
hdrctl test only counts the steps of this short shape. Written under a transformations key it reports 0 step(s) valid and says nothing about it.6. Apply the business rule
- Add a second entry after the cast: keep the orders of 100 and above.
- filter:
expr: "amount >= 100"
7. Check the manifest
- Run
hdrctl testfrom the folder containingsales/.
hdrctl test sales
Sources ok sources.yaml — DSL valid ok src_sales — csv (local file) Destinations ok destinations.yaml — DSL valid ok dest_large_orders — mode replace Transformations ok transformations.yaml — 2 step(s) valid Operations: cast, filter ✅ All tests pass — ready to execute.
8. Run the job
- Run
hdrctl runon the same folder.
hdrctl run sales
✅ Pipeline completed successfully Rows read : 7 Rows written: 3
9. Read the result
- Open
sales/out/large_orders.csv.
order_id,customer,city,amount,status,order_date A-1001,Alice Martin,Paris,120.5,paid,2025-03-04 A-1003,Chloe Ruiz,Madrid,310.75,paid,2025-03-04 A-1006,Farid Ziani,Nantes,260.0,paid,2025-03-04
Expected result
- Seven rows read, three written. The four orders below 100 were dropped by the filter.
- The amounts are numbers.
120.50is written120.5and260.00becomes260.0— the trailing zero is a text artefact, not a value. - The file can be regenerated. Run it twice: still three rows.
Reading the results
Why seven and three?
The two counters bracket the job. Rows read is what the source produced, Rows written what reached the destination. The gap is the work of the steps — here a filter that kept 3 of 7, or 43 % of the orders, which is the share of the day above 100.
What would happen without the cast?
The filter would compare text to a number and the run would stop at step 1 of 2, before writing anything. Nothing would be lost — the destination is only touched once every step has passed.
What the manifest does not know
Nothing here says today's file. The name is written in extract.table, so the
job reads that one file. Making the date a parameter is the subject of the next workshop.
Did we answer the question?
| objective | result | where |
|---|---|---|
| Read without copying | yes | base_path points at the folder in place |
| Type the amounts | yes | cast, first step |
| Keep orders ≥ 100 | yes | filter, second step |
| Regenerable output | yes | mode: replace |
| Read today's file automatically | not yet | the date is written in the manifest |
Before and after
- Before — open the file, convert a column, sort, filter by hand, copy the result out. Around fifteen minutes, every evening, and the rule exists only in the head of whoever does it.
- After — one command. The rule is written in
transformations.yaml, readable by anyone, versioned with the rest, and it produces the same result every time. - What is really gained is not the fifteen minutes. It is that the rule left the spreadsheet and became text a colleague can read, review and correct.