It parses free-text Latvian public-tender requirements into structured constraints and checks them against a product catalogue with deterministic rules. It gives a per-criterion verdict, has a FastAPI review UI and exports an Excel report. 704 tests pass; in production since 2026-09-12.
Tender rows state requirements in free Latvian prose, with decimal commas, operator phrases (ne mazāk kā, no X līdz Y) and inconsistent units. Answering a tender by hand means re-reading the catalogue for every row, and a wrong "compliant" is a safety and bid-integrity risk.
tender.xlsx --> Excel loader --+
+--> extraction requirement parser, device parser,
products.yaml -> YAML loader --+ (LLM: text -> structure only) item-numbering hierarchy
|
v
normalisation units, decimal comma,
"ne mazāk kā" -> >=, "līdz" -> <=, range
| controlled vocabulary
v
matching engine deterministic, per criterion:
tolerance policy.yaml COMPLIANT | PARTIALLY_COMPLIANT
| NON_COMPLIANT | UNKNOWN
+----------------+----------------+
v v
Excel report FastAPI + Jinja2 review UI
upload, results, per-item detail,
product browser
library: uploaded tenders keyed by SHA-256 of the file bytes
verdicts are never stored; they are recomputed on every view
| date | result |
|---|---|
| 2026-08-11 | First commit and FastAPI UI. The architecture doc rejects n8n and pgvector: pre-filtering cuts 10K products to 5-50 candidates |
| 2026-08-12 | Gap analysis over 310 tender items found 487 unique unparsed segments |
| 2026-08-20 | Device matching, row routing, and a durable SHA-256-keyed procurement library |
| 2026-09-12 | Age-group matching removed live false positives where adult formulas topped paediatric and infant rows. Deployed at 03:20 Riga: 704 tests pass, 9 stored tenders intact |
| 2026-09-12 | Todo rewritten from measured results: 15 of 23 P1-P3 claims were wrong about location, size or whether to build at all |
| metric | value |
|---|---|
| tests | 704 passing on dev (3.13.2) and prod (3.12.3), 2026-09-12 |
| gap analysis | 310 tender items, 487 unparsed segments (2026-08-12) |
| catalogue | 43 nutrition variants, 136 device variants |
| bare-operator fix | covered 16 corpus rows; next target, sugar, has 39 rows |
| will not build | 7 backlog items, because each invents a threshold no tender states (2026-09-12) |
| criterion | op | value | unit | product | verdict |
|---|---|---|---|---|---|
| energy density | = | 1.5 | kcal/ml | 1.5 | COMPLIANT |
| protein | >= | 4 | g/100 ml | 6.0 | COMPLIANT |
| volume | range | 200-500 | ml | 200 | COMPLIANT |
| age group | = | adult | - | adult | COMPLIANT |
| fibre | ? | - | - | not in catalogue | UNKNOWN |
| candidate | criterion | verdict |
|---|---|---|
| Formula A 1.5 kcal | age group: adult vs infant | NON_COMPLIANT |
| Formula C infant | energy 67 <= 70 | COMPLIANT |