For Agents
Living index of themes for the EU/HU public-tender scraping project. Each H2 is a topic; bullets are wikilinks to related notes. Updated by
obsidian-documenterwhen documenting work. Read byhistorianat bootstrap. Topics kept alphabetical.
Status at a glance (Phases 2+3 built 2026-06-15)
Direction: Hungary/EKR-only + a “does Levandor qualify to bid?” feature. The old EU-wide / pricing-intelligence thesis is superseded (see Pricing Intelligence (SUPERSEDED — out of scope)). Phase 1 shipped to
master(gates green): TED removed end-to-end,tenders.sourceis'ekr'-only, defense-sector OR-gate, eligibility prose captured into jsonb columns. Phases 2 & 3 built onmaster(NOT pushed) — see qualification-phase-2-3: editablecompany_profilesingleton + Settings form (Phase 2); on-demandtender-qualifyedge fn (DEPLOYED) + queue/verdict UI cloning/disclosure(Phase 3). Code-reviewed clean, web tests + build green. ⚠️ NOT live end-to-end — 2 operator blockers:ANTHROPIC_API_KEYnot set in Supabase secrets (real assessments error) + web SPA deploy parked onwrangler login. Also:tenderstable needs a real EKR ingestion verification run. Pending: Phase 4 null-criteria PDF/TED-notice enrichment; push Phases 2+3. Design docs are in the repo:docs/superpowers/specs/2026-06-09-hungarian-tender-qualification-design.md,docs/superpowers/plans/2026-06-09-tender-hu-only-phase1.md. Build narrative: LOG.
Architecture Divergence (spec vs shipped)
As of the 2026-06-09 refocus, the
source-strategy-specis also superseded on its core thesisThe spec describes an EU-wide, 5-source, pricing-model design. The shipped-and-refocused system is Hungary/EKR-only with a qualification feature instead. Treat both spec docs as historical intent, not as-built. Current intent lives in the in-repo docs (
docs/superpowers/specs/2026-06-09-hungarian-tender-qualification-design.md,docs/superpowers/plans/2026-06-09-tender-hu-only-phase1.md).
The code diverged from the spec on every major axis. Read the specs as intent, not as-built.
Axis Spec said (source-strategy-spec / implementation-guide) SHIPPED (post 2026-06-09 refocus) Storage Standalone SQLite + Drizzle + FTS5 Supabase / Postgres (migrations 003+004+20260609192522_tender_hu_only,tenders_view, FTSsearch_vector)Runner Local process / CLI Supabase edge function tender-sync(120s budget, service-role, 7-day lookback) +cli/backfill.tsSources 5 (TED API, TED Bulk XML, EKR Eljárástár, EKR Szerződéstár, CoRe HTML) 1: EKR Eljárástár JSON only (TED removed end-to-end; tenders.sourceCHECK ='ekr')Stages normalize, dedupe, enrich-pricing, score normalize + dedupe only (pricing stages out of scope) Pricing model per-buyer/per-CPV model from awarded values none — superseded by the qualification feature
- source-strategy-spec - the strategy/thesis (undercut the AWARDED value, not the ESTIMATE); describes the 5-source, pricing-model design that was only half-built.
- implementation-guide - source/API validation (TED v3, ISO alpha-3 country codes, EKR specifics) verified 2026-04-26.
- LOG - the per-commit build narrative showing where reality landed.
Company Profile (qualification baseline)
Phase 2 — DONE (on
master, not pushed)Editable
company_profilesingleton (id integer pk default 1 check (id = 1), migration20260615093853_company_profileapplied live) + SettingsCompanyProfileForm, hooksuseCompanyProfile/useUpdateCompanyProfile(upsert{id:1}), en/hu i18n. The baseline the qualification assessment compares each tender’s eligibility criteria against.
Gotcha: focus-refetch clobbered unsaved edits → hydrate-once guard
const [hydrated,setHydrated]=useState(false); if(data&&!hydrated){setHydrated(true);setForm(data)}— initialize form state from server data exactly once so TanStack focus-refetches don’t overwrite in-progress edits.
- qualification-phase-2-3 - Phase 2 full writeup.
- LOG - Phase 2 DONE (2026-06-15 entry); original scope (2026-06-09 refocus entry).
Data Sources
EKR-only as of 2026-06-09 — TED removed
The single source is now EKR Eljárástár JSON. TED was deleted across the pipeline package, the
tender-syncedge fn, and the web UI;tenders.sourceCHECK is'ekr'-only (theted_publication_number/ted_urlfields were kept because EKR also populates them). See LOG (2026-06-09 refocus).
- LOG - DONE: TED removal + EKR-only source; new
config.tscentralizes CPV codes, tag map, and the defense buyer allowlist/keywords. - implementation-guide - historical: validated TED Search API v3 (
POST /v3/notices/search, ITERATION pagination, HUN not HU) and EKR specifics. The TED adapter is now removed; EKR specifics remain relevant. - source-strategy-spec - historical source taxonomy (TED Bulk XML, EKR Szerződéstár, CoRe scrape) — none of these are in scope under the refocus.
Defense Sector (DONE)
Shipped in Phase 1 (2026-06-09)
A defense-sector OR-gate inside the EKR source:
cpvOk || defenseOkbeforeyield, so defense-sector buyers are ingested + taggeddefense_sectoreven with non-IT / null CPV (cpvCodesrelaxed to allow empty). Buyer detection via an allowlist/keywords + accent-foldingisDefenseBuyerinconfig.ts(catches Magyar Honvédség, Védelmi Beszerzési Ügynökség).
Defense ≠ CPV-35 in EKR
EKR has no CPV-35 / military procurement codes. This gate captures defense-sector civilian buyers only — not weapons tenders. Also, the seed roots
HM EI ZrtandKatonai Nemzetbiztonsági Szolgálatare unconfirmed against live buyer names — validate on first ingestion.
- LOG - defense OR-gate +
config.tsallowlist (2026-06-09 refocus entry).
Gotchas & Drift
Upstream APIs drift and silently break ingestion
Commit
7bb267ealready had to adapt the pipeline once to TED Search API v3 and changed EKR API response shapes. A future re-run of the sync may need another drift fix. Treat any “no new tenders” symptom as a possible upstream-schema break, not an empty result.
- LOG - records the
7bb267edrift fix.
Pricing Intelligence (SUPERSEDED — out of scope)
Dropped on 2026-06-09. This is no longer the plan.
The “undercut the AWARDED contract value, not the padded ESTIMATE” thesis was superseded by the refocus to a qualification feature (see Qualification (LLM eligibility assessment)). The pieces below were never built and are now out of scope — kept here only as historical context:
EKR Szerződéstár contracts ingestion (apikey=PSZT, ~135k awarded contracts;schema/contract.tsexported-but-unused)CoRe (Nyilvános Elektronikus Szerződéstár) scrape for price benchmarkingTED Bulk XML historical backfill(TED itself is now removed)enrich-pricing stage (per-buyer/per-CPV historical-award model)score stageHUF → EUR normalization via ECB rates (grossValueEur/netValueEurfields)
- Strategy: why “undercut by 10%” is the wrong primitive - historical rationale for the dropped pricing model.
Qualification (LLM eligibility assessment — the new strategic core)
Replaces pricing intelligence as the value thesis
The new goal: “does Levandor qualify to bid on this tender?” — an on-demand LLM assessment of each tender’s eligibility criteria vs the editable company profile. Mirrors the CRM’s
/disclosureanalyzer pattern (queue + inline verdict UI).
Phase 1 capture — DONE
Free-text eligibility prose is now captured: the 6
alkalmassagiKovetelmenyReszletekfields + documents flow types → adapter → zod → upsert into newtenders.eligibility_criteria/tenders.documentsjsonb columns (migration20260609192522_tender_hu_only).
Phase 3 assessment — DONE (on
master, not pushed)On-demand
tender-qualifyedge fn (DEPLOYED) + queue/verdict UI cloning/disclosure. Tablestender_qualification_request(queue) +tender_qualification(one verdict/tender, upsert ontender_id), RLS authenticated SELECT/INSERT + service_role ALL (migration20260615163827_tender_qualification). Edge fn: verifyAuth bearer-or-secret → newest-pending → claim-race → load tender+company_profile→ empty-criteria short-circuit tounknown(no LLM) → else Claudeclaude-opus-4-8thinking:adaptive+ structured output. Frontend:lib/tender-qualification.ts, hooks (request + 8s polling w/ 15-min bound + verdict reads),TenderQualifyButton+QualificationVerdictPanel+ eligibility-prose onTenderOverviewTab, en/hu viat().
Phase 3 LLM landmines
- Structured-output schema:
VERDICT_SCHEMAmust avoidminimum/maximum/minLength/pattern/recursion or the raw fetch 400s.- Adaptive thinking: parse the
content[]block wheretype==='text', NOTcontent[0](the thinking block is placed first).
Blockers — NOT live end-to-end
ANTHROPIC_API_KEYnot set in Supabase secrets → every real LLM assessment landsstatus='error'. Fix:supabase secrets set ANTHROPIC_API_KEY=... --project-ref mkofmdtdldxgmmolxxhc(same secret backs_shared/llm.ts).- Web SPA deploy parked on
wrangler login/CLOUDFLARE_API_TOKEN→ frontend not live. Edge fn not self-verifiable (browser-driven invoke); empty-criteria short-circuit untestable (all 101 live tenders have criteria).
Eligibility criteria are free-text Hungarian prose
The captured
alkalmassagiKovetelmenyReszletekfields are unstructured Hungarian text — Phase 3’s LLM assessment consumes them against the company profile.
- qualification-phase-2-3 - Phase 3 full writeup (edge fn pipeline + landmines + blockers).
- LOG - Phase 3 DONE (2026-06-15 entry); eligibility-capture DONE Phase 1 (2026-06-09 refocus entry).
Schema & Database
- LOG - migrations
003_tender_pipeline.sql(tenders, cpv_codes) +004_tender_tracking.sql(tender_trackinglifecycle enum watching → evaluating → bidding → decided,tenders_view, FTSsearch_vector) +20260609192522_tender_hu_only(purged 42 TED rows + sync rows, rewrotesourceCHECK to'ekr'-only, addedeligibility_criteria/documentsjsonb columns, recreatedtenders_view). Applied live to project mkofmdtdldxgmmolxxhc. ⚠️tenderstable is now empty — EKR ingestion needs a verification run. Zod schemasschema/tender.ts(used) +schema/contract.ts(exported, unused — pricing dropped). - qualification-phase-2-3 - migrations
20260615093853_company_profile(singletoncompany_profile,id integer pk default 1 check (id = 1)) +20260615163827_tender_qualification(tender_qualification_requestqueue +tender_qualificationone-verdict-per-tender, RLS authenticated SELECT/INSERT + service_role ALL). Both applied live to mkofmdtdldxgmmolxxhc. ⚠️ Note: 101 live tenders all haveeligibility_criteria(0 null).
Status & Roadmap
Roadmap (Phases 2+3 built 2026-06-15)
- DONE (Phase 1, pushed to
master): TED removal + EKR-only; defense-sector OR-gate; eligibility-criteria capture; live migration20260609192522_tender_hu_only. Gates green.- DONE (Phase 2, on
master, NOT pushed): editablecompany_profilesingleton (check id=1, migration20260615093853_company_profilelive) + Settings form; hydrate-once gotcha. See qualification-phase-2-3.- DONE (Phase 3, on
master, NOT pushed): on-demandtender-qualifyedge fn (DEPLOYED,claude-opus-4-8+ structured outputs) + queue/verdict UI cloning/disclosure; migration20260615163827_tender_qualificationlive. See qualification-phase-2-3.- PENDING — Phase 4: null-criteria PDF / TED-notice enrichment.
- PENDING: push Phases 2+3.
- DROPPED: pricing intelligence (superseded).
- ⚠️ Operator blockers (not live):
ANTHROPIC_API_KEYnot set in Supabase secrets; web SPA deploy parked onwrangler login/CLOUDFLARE_API_TOKEN. Plustenderstable still needs an EKR ingestion verification run.
- LOG - DONE: discovery + tracking UI merged and routed (pre-refocus); Phase 1 refocus shipped. DROPPED: pricing intelligence.
Tracking UI (CRM)
- LOG - shipped
web/src/pages/tenders/(list +[tenderId]detail),Tender*.tsxcomponents (Table, Row, Sheet, FilterSidebar, MetricsBar, DetailHeader, Overview/Activity/Related tabs), hooksuseTenders/useTenderTracking/useTenderSync/useTenderSyncLog/useRelatedTenders, routes/tenders+/tenders/:tenderIdwired intoApp.tsx+ Sidebar.