One column, three formats
Northstar's export writes created_at in whichever format the tool that produced the row happened to use: ISO-8601 with a Z, dd/mm/yyyy hh:mm from a spreadsheet round-trip, and bare epoch seconds from a script somebody wrote in 2019.
Sorting that column without normalising it produces an order that looks plausible and is wrong — "04/02/2026 14:30" sorts before "2026-02-03T09:12:00Z" because 0 is less than 2. Nothing errors. Reports are simply wrong by a day or two, forever.
The rule for an ingestion layer: normalise at the boundary, once, and refuse what you cannot parse rather than guessing. A value you cannot read is a quarantine row, not a None that flows downstream.
Example
"2026-02-03T09:12:00Z" # ISO-8601 "04/02/2026 14:30" # day/month/year - note the ambiguity with month/day "1770203400" # epoch seconds
The middle format is ambiguous by design. Northstar is a UK operation, so 04/02 is 4 February.
Your task
Write normalise(value) returning YYYY-MM-DD for all three formats and None for anything else. Then print each ticket as <id> <date>, and finally the count of rows you could not parse.
Stuck?
Explain it
Why is returning None for an unreadable value better than guessing, even though it means the row is dropped from the report?
Where this goes
Deliverable D3 is the adapter this belongs to. Its acceptance criteria require that every row is either stored or quarantined with a reason, and that the two counts reconcile against the input on every run.
Files you can open: tickets.csv
This is a teaching runtime for a subset of Python, running in your browser. Integers are exact and the errors are written in plain English, but the standard library is a small subset and anything missing is refused by name rather than approximated. Your project runs on real Python.
Press Run to see what your program does, or Check when you think it is right. Everything runs here in your browser.