BACKGROUNDWhy procurement data is harder than downloading a tender list
A procurement process can appear in more than one OCDS release as its status changes. Descriptions are written differently, fields can be missing and the eTender portal itself is not a complete census of every procurement process. Counting raw rows as “number of tenders” would therefore be misleading.
PROBLEMThe problem I wanted to solve
I wanted a traceable pipeline that keeps the raw public record, identifies the latest state of each procurement process and classifies civil/road/asphalt opportunities without pretending the classifier is accurate before it has been tested.
TASKWhat I built
I designed the route from official OCDS release to raw snapshot, staging, schema checks, latest-state selection and explainable classification. The remaining step is to test the classifier against a human-labelled production sample before publishing market counts.
TOOLS I USED
What each tool was doing
OCDS APICollect official public procurement releases.
PostgreSQL / SQLNormalise fields and retain the latest state per OCID.
Rule-based classificationAssign explainable civil/road/asphalt labels and retain the matched terms.
Precision / recall testingRequired before classifier totals are treated as evidence.
NOW THE ANALYSIS STARTSWith the problem and data purpose clear, the next section shows the result first. The detailed steps below then show exactly how I got there.
READYOfficial OCDS API
→READYRaw snapshot
→READYSchema checks
→READYLatest OCID state
→READYClassification rules
→PENDINGPrecision / recall validation
What I foundThe pipeline is technically ready, but a pipeline is not a market finding. I will not publish road/asphalt counts until the classifier has been tested against human-labelled production records.
02 · DATA
The raw unit is an OCDS release, not a manually typed tender spreadsheet
The pipeline keeps the original release first, then extracts reporting fields. That means a classified record can still be traced back to the public procurement payload that produced it.
OCDS FIELDNORMALISED FIELDUSE
ocidocidStable procurement identifier
buyer.namebuyer_nameProcuring entity
tender.titletender_titleClassification input
tender.statustender_statusProcurement stage
tender.tenderPeriod.endDateclosing_dateTiming