{"id":"discovery-matching","type":"module","title":"Discovery and Matching Engine","status":"ga","audience":["bid-manager","consultant","partner"],"scope":"supplier","tier_min":"free","aliases":["1","explore","notices"],"summary":"Klarum's core AI engine. It ingests public tenders and aid-funded opportunities globally, indexes them in one canonical table, and scores every firm/tender pair with a calibrated, explained multi-signal ensemble.","keywords":["tender-search","matching","featured","notices","ensemble","pgvector","reranker","cpv","g3"],"body":{"what_it_is":"Klarum's core AI engine: it collects public tenders and aid-funded\nopportunities globally, understands them in plain language, and tells each\nfirm which are worth bidding on. Three user-facing layers plus one engine:\n\n- **Notices**: a global, searchable index of every open tender Klarum\n  ingests, with natural-language search over structured filters. Unscored;\n  the raw browsable universe. A firm's **own inbound portal submissions**\n  are also prepended to page 1, tagged with an \"Inbound\" pill, so the\n  surface a consultant triages from shows both what was scraped and what\n  was sent directly to them. They are skipped whenever a search or filter\n  is active, because a submission carries no procurement method, category\n  or notice type and so cannot satisfy one.\n- **Explore / Featured**: a personalised, ranked feed of the tenders best\n  fitting a *specific firm*, each annotated with a match score and\n  human-readable reasons.\n- **The Matching Engine**: the multi-signal ensemble underneath both,\n  turning \"a firm\" + \"a tender\" into a calibrated 0-1 fit score plus an\n  explanation, powering the \"Should we bid?\" recommendation.\n\nIn data terms: every tender lives in one canonical table,\n`canonical_opportunities` (public, no firm attached); every firm-to-tender\njudgement lives in `featured_match_results` (per-firm scored cache) and is\nlogged for learning in `match_signal_log`.\n","what_it_helps_users_do":"- **Stop trawling dozens of portals.** TED (EU), SAM.gov, UK\n  Find-a-Tender/Contracts Finder, World Bank, IATI, ProZorro, PNCP and ANAC\n  (Brazil), AusTender, EBRD, Chile Compra and more are normalised into one\n  feed.\n- **Reach beyond one jurisdiction.** Those 12+ sources resolve to **197\n  distinct ISO3 country codes** across the 191,779 rows of\n  `canonical_opportunities` (measured on Neon `dev`, 2026-07-31): **173\n  countries carry 10 or more notices**, 120 carry 100 or more, on top of the\n  EU-focused depth TED supplies. Country coverage is a property of the\n  ingested corpus, so it is a measurement and not a contract - re-measure\n  with a distinct count of country codes over the canonical table\n  before restating it publicly.\n- **Search the way they think.** A user types *\"water and sanitation\n  projects in East Africa over EUR 2M\"* and the system parses it into\n  structured filters plus a semantic query.\n- **Get a curated daily shortlist**: pre-scored per firm every morning,\n  grouped LinkedIn-style by date added.\n- **Understand *why* a tender fits**: sector overlap, geographic coverage,\n  budget fit, reference/track-record evidence, plus a bid/consider/pass\n  recommendation.\n- **Act in one click.** \"Go\" (`developFromTender`) spins a tender into a\n  workspace and auto-attaches members whose CVs match required\n  capabilities; \"No-go\" dismisses; thumbs up/down feed the learning loop.\n- **Promote anything on demand.** \"Send to Featured\" queues a single notice\n  for full real-time AI scoring even if it didn't clear the bulk gates.\n- **Save and reuse filter views**: named presets slice the feed instantly at\n  read time, with no AI re-run.\n"},"engines":[{"id":"matching-ensemble","type":"engine","title":"Matching ensemble","summary":"Blends ten independent calibrated signals into one 0-1 fit score plus an auditable per-signal breakdown; missing signals redistribute their weight rather than zeroing the score.","category":null},{"id":"nl-query-parser","type":"engine","title":"Natural-language query parser","summary":"Turns free text into structured filters plus a semantic query via a structured LLM call, with a hard 3-second timeout and a keyword-only fallback.","category":null},{"id":"tender-ingestion-pipeline","type":"engine","title":"Tender ingestion pipeline","summary":"An 8-step dlt/dbt pipeline that normalises 12+ tender sources into one canonical_opportunities row each, then scrapes, translates, embeds and indexes them.","category":null}],"integrations":[],"capabilities":[{"id":"notices-index","type":"capability","title":"Notices (global tender index)","summary":"An unscored, searchable index of every open tender Klarum ingests, served over SSE so rows paint progressively, with the org's own inbound portal submissions prepended to page 1.","status":"ga","scope":null,"keywords":["search","sse","filters","inbound-pill"],"body":null},{"id":"featured-shortlist","type":"capability","title":"Featured (per-org AI shortlist)","summary":"The pre-scored daily shortlist read from featured_match_results, refreshed by a 06:00 UTC cluster-singleton cron with an 08:00 UTC digest email to opted-in users.","status":"ga","scope":null,"keywords":["shortlist","cron","digest"],"body":null},{"id":"send-to-featured","type":"capability","title":"Send to Featured (on-demand scoring)","summary":"Queues a single notice for the full orchestrator with real-time G3 extraction, returning 202 while the UI polls a pending skeleton every five seconds.","status":"ga","scope":null,"keywords":["on-demand","g3","202"],"body":null},{"id":"first-value-warmup","type":"capability","title":"First-value warm-up","summary":"Scores the whole candidate pool once for a brand-new org as soon as onboarding confirms its profile, so its feed is populated before it first reaches the dashboard (#1933).","status":"ga","scope":null,"keywords":["onboarding","cold-start","single-flight"],"body":null},{"id":"filter-presets-read-time","type":"capability","title":"Read-time filter presets","summary":"Named presets re-slice the cached universe at the SQL layer, so switching one costs zero AI spend and returns instantly.","status":"ga","scope":null,"keywords":["presets","sql","zero-cost"],"body":null},{"id":"derived-primary-cpv","type":"capability","title":"Derived primary CPV division","summary":"Scores each notice's text against the 45 CPV division vectors and records the nearest division, used only where the source supplied no classification and only above a confidence floor.","status":"ga","scope":null,"keywords":["cpv","classification","taxonomy"],"body":null},{"id":"matching-learning-loop","type":"capability","title":"Learning loop","summary":"Every ensemble run logs to match_signal_log and user actions supply labels, so calibration and future learned weights improve from real bid behaviour.","status":"ga","scope":null,"keywords":["feedback","labels","calibration"],"body":null}]}