Skip to content

Business intelligence

Ten portals, one question box, and an answer that cites its source

Romania publishes an enormous amount of business data and publishes it in pieces, one institution at a time. This platform ingests those registries, links what they say about the same company, and puts a question box over the result that answers with the source attached.

Client
LaTermen
Sector
Business intelligence
Published
Website
latermen.ro
The LaTermen home page: the question field at the centre of a graph of linked data sources

The problem

The data is public and unusable. Tenders are on one portal, company records on another, court files on a third, and the official gazette on a fourth — none of which know the others exist. Checking one supplier means ten sites, ten search boxes and a spreadsheet to hold what you found, and the answer goes stale the moment you close the tabs.

The solution

One ingestion layer over the official registries, a normalised record at the end of every one of them, and automatic links between what they separately describe: a company to its tenders, its awards, its court files and its filings. Over that, a question box, a search that runs eight corpora at once, and assistants that answer with the source they read.

The architecture

Every source implements one adapter contract — decide, fetch a page, parse items, normalise, say whether another page exists — so a scraped portal and a machine-readable feed arrive as the same record, and adding the next one is a class. Each source carries its own cadence and a health verdict, because an ingestion that nobody watches is a dataset that quietly stops. Retrieval is a contract too, with a document-level and a chunk-level driver behind it, returning the authority, the URL and the article a passage came from — provenance as a field on the result rather than an instruction in a prompt.

The outcome

Eight corpora searched in a single pass, with every answer carrying the official source it was read from.

In detail

The hard part was never the model. It is that the sources disagree about everything except the facts. One portal identifies a company by its fiscal code, another by its registration number, a third by a name typed by hand with the legal form in a different place each time. A tender is a page on one site, a structured record on another and a PDF attachment on a third. Before anything can be asked, all of it has to become one shape.

So every source implements the same contract and nothing downstream knows which kind it was. Decide whether this adapter handles the source, fetch a page, parse the items out of it, normalise them into a fixed set of fields — title, source URL, published date, deadline, value, currency, classification code — and report whether another page exists. A portal that has to be read as HTML and a feed that arrives structured produce identical records, which is what makes the next source a class rather than a project.

Ingestion that runs once is a demo. Each source carries its own expected cadence and is evaluated against it, producing a health verdict per source rather than one number for the whole system. That distinction matters more than it sounds: a platform aggregating dozens of registries will always have one that changed its markup last night, and the useful question is never “is the data fresh” but “which of these is stale, and since when”.

The links between sources are the product. A company record is not a page about a company; it is the join point for everything the other registries say about the same entity — the tenders it bid on, the contracts it won, the court files it appears in, the filings it made. That join is why the platform answers a question that no individual portal can: not “what does this registry hold about this company” but “what is true about this company”, and it is computed rather than curated.

Search runs eight corpora in one pass — companies, tenders, awards, procurement plans, EU notices, direct purchases, legal documents and court files — through a single multi-index query rather than eight sequential ones. The corpora stay separate because their fields are genuinely different, and the result set is merged after the fact with the type carried through, so a reader can see at a glance which kind of thing each answer is.

The assistants are where a platform like this usually starts lying, and the design is built around not letting it. The retriever is a contract with two drivers behind it — one comparing whole documents, one comparing passages — and what it returns is not text but a structure: the passage, the issuing authority, the URL, and where available the heading path and the article number it came from. The citation is a field on the result. An answer can therefore be checked against the thing it was drawn from, which is the only version of an AI answer that is worth anything in a professional context.

The same restraint runs through the query interpreter. A question typed in ordinary language goes to a model, which extracts slots — a sector, a county, a fiscal status — and stops there. The model never executes anything: the extracted slots go through a deterministic mapper into validated filters, and those filters run the same code path the manual builder runs. The interface then states, in one plain sentence built without a second model call, exactly which filters are about to execute, so an interpretation that got it wrong is visible and editable before it returns anything. Language is treated as an input method, not as an authority.

Stack

  • Laravel 12
  • PHP 8.2
  • Filament 3
  • Meilisearch
  • MySQL 8.4
  • Redis 7
  • Horizon

Capability

  • Custom software
  • Applied AI
  • Data platforms
Work
The LaTermen home page: the headline "Ask the business market directly", a map of sources linked around a central node, and the question field.
The public home page. The numbers on the map were hidden from the capture.
The active tenders list at phone width: filters by CPV code, work type, county and deadline, then notices with the code, the contracting authority and the deadline.
Active tenders on a phone, with public contracting authorities. Estimated values were hidden from the capture.

Contact

What do you want to build?

Discuss your project