You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The finalised opentender.db (2026-07-13, 503,377 tenders across AT/CZ/DK/EE/FI/FR/LT/LV) is 5.03 GB. That makes the deployment path in the OpenTender design unusable: the adapter downloads the DB from OPENTENDER_S3_URL into /tmp on Render, and a 5 GB pull into ephemeral disk on every cold start is not viable on the free tier — quite apart from the S3 egress and the minutes added to an already-slow cold start.
So the source cannot go live until the artifact shrinks, regardless of the query work in #29.
Where the size actually is
Measured on the artifact:
~4.4 GB (≈87%) is the tenders.data column — the raw, complete OpenTender tender record stored as JSON, averaging 8.5 KB per tender.
The rest (FTS index, body_ids, tenders scalar columns) is ~0.6 GB and is the part actually doing the work.
But the adapter reads only a handful of fields out of that 8.5 KB blob:
buyers, bidders/winners, lots[], awardDecisionDate, award values
Everything else in the record — full descriptions, CPV code lists, document metadata, publication histories, corrections, raw source payloads — is carried at ~8.5 KB/tender and never read.
Proposed fix: rewrite data in place — no re-ingest
The key point: this does not require re-reading the source NDJSON archives (that ingest takes hours and the UK archive alone would make a full rebuild impractical — see the Notion ticket). A one-off migration can stream the existing DB, project each data blob down to the fields above, and write it back:
for persistent_id, data in SELECT persistent_id, data FROM tenders:
slim = project(json.loads(data)) # keep only the consumed fields
UPDATE tenders SET data = ? WHERE persistent_id = ?
then VACUUM INTO to reclaim the space (reuse finalise_db(), which already does checkpoint + integrity_check + VACUUM and returns the SHA-256).
Rough expectation: a projected record should land around 1–2 KB, so ~0.7 GB of JSON instead of 4.4 GB → a ~1–1.5 GB artifact. Still not small, but plausibly deployable, and a further cut is available by dropping titleEnglish or trimming the lots[] array to awarded lots only.
Suggested implementation
backend/scripts/slim_opentender.py — takes an existing DB, projects data, VACUUMs, prints the new SHA-256. Idempotent; safe to run on an already-slim DB (projection of a projected record is a no-op).
Add the same projection to extract_opentender.py behind a --slim flag (default on?) so future builds are born small and this never recurs.
Guard the projection with a test: for a sample tender, assert every field _tender_hit(), _walk_bodies() and map_opentender() touch survives the projection — so slimming can never silently break the BODS mapping. Ideally derive the keep-list from one place rather than duplicating it.
Re-measure and record the new size + SHA in docs/sources.md.
Caveats
Fidelity: the slim artifact is lossy by design. If the raw record is ever wanted (a future /deepen that shows the full tender, say), it won't be there. Worth a decision now: is OpenTender a signal source (who won what, and their identifiers) or an archive? The adapter as written treats it as the former.
Licence unchanged: still CC-BY-NC-SA-4.0; the NC obligations propagate through /deepen and /export regardless of size.
The problem
The finalised
opentender.db(2026-07-13, 503,377 tenders across AT/CZ/DK/EE/FI/FR/LT/LV) is 5.03 GB. That makes the deployment path in the OpenTender design unusable: the adapter downloads the DB fromOPENTENDER_S3_URLinto/tmpon Render, and a 5 GB pull into ephemeral disk on every cold start is not viable on the free tier — quite apart from the S3 egress and the minutes added to an already-slow cold start.So the source cannot go live until the artifact shrinks, regardless of the query work in #29.
Where the size actually is
Measured on the artifact:
tenders.datacolumn — the raw, complete OpenTender tender record stored as JSON, averaging 8.5 KB per tender.body_ids,tendersscalar columns) is ~0.6 GB and is the part actually doing the work.But the adapter reads only a handful of fields out of that 8.5 KB blob:
_tender_hit()persistentId/id,title/titleEnglish,country,buyers[].name,procedureType,ot.integrity,ot.transparency_walk_bodies()→_bridge_identifier()bodyIds[](type,scope,id)map_opentender()(bods/mapper.py)lots[],awardDecisionDate, award valuesEverything else in the record — full descriptions, CPV code lists, document metadata, publication histories, corrections, raw source payloads — is carried at ~8.5 KB/tender and never read.
Proposed fix: rewrite
datain place — no re-ingestThe key point: this does not require re-reading the source NDJSON archives (that ingest takes hours and the UK archive alone would make a full rebuild impractical — see the Notion ticket). A one-off migration can stream the existing DB, project each
datablob down to the fields above, and write it back:then
VACUUM INTOto reclaim the space (reusefinalise_db(), which already does checkpoint +integrity_check+ VACUUM and returns the SHA-256).Rough expectation: a projected record should land around 1–2 KB, so ~0.7 GB of JSON instead of 4.4 GB → a ~1–1.5 GB artifact. Still not small, but plausibly deployable, and a further cut is available by dropping
titleEnglishor trimming thelots[]array to awarded lots only.Suggested implementation
backend/scripts/slim_opentender.py— takes an existing DB, projectsdata, VACUUMs, prints the new SHA-256. Idempotent; safe to run on an already-slim DB (projection of a projected record is a no-op).extract_opentender.pybehind a--slimflag (default on?) so future builds are born small and this never recurs._tender_hit(),_walk_bodies()andmap_opentender()touch survives the projection — so slimming can never silently break the BODS mapping. Ideally derive the keep-list from one place rather than duplicating it.docs/sources.md.Caveats
/deepenthat shows the full tender, say), it won't be there. Worth a decision now: is OpenTender a signal source (who won what, and their identifiers) or an archive? The adapter as written treats it as the former.CC-BY-NC-SA-4.0; the NC obligations propagate through/deepenand/exportregardless of size.