our own product · built and run by us
TRINTA
A public tender radar. It reads the official sources every day, reads the attached documentation, scores each tender 0 to 100 against how well it actually fits the company, and delivers what matters. This is not a case study written from the outside: TRINTA is ours, from the first line of code to the customers using it today.
The problem was never a shortage of tenders.
It was not seeing them in time. They sit scattered across hundreds of official portals, each with its own format, language and calendar. Whatever went unfound on a Monday morning went unbid.
Every country publishes where and how it likes. Some on portals that only open with JavaScript, others only as PDF.
The deadline that matters is often not in the metadata. It is buried halfway through an attached document.
The same tender arrives from three different sources, with reference systems that look nothing alike.
Searching by words is naive. "Oxygen" shows up in twenty languages, with accents, plurals and grammatical cases.
how it works
Five steps, every day.
Read
A dedicated connector per portal pulls notices from the official source. Each runs isolated, with its own retries and timeout, so a portal that goes down never stops the harvest.
Filter
The relevance engine decides what matters, across roughly 30 languages, with CPV codes and a weak tier that lets borderline cases through for the AI to judge.
Enrich
Tender documents get resolved and read. The real deadline often exists only inside a PDF, so that is where we go and get it.
Score
Deterministic triage first, AI audit second. The audit reads the documentation and writes the final score, 0 to 100.
Deliver
What matters rises to the top of the board, into the 7am email and, where it earns it, onto WhatsApp. The rest stops stealing anyone’s time.
the fleet
Over a thousand platforms, one at a time.
There is no universal scraper that solves public procurement. Every portal has its own format, so every portal has its own code. The work is not writing the first connector, it is keeping hundreds of them alive while the portals change underneath.
One connector per portal
This is not a generic scraper. Every platform has its own code that understands its format, whether that is an official API, HTML or RSS. Portals built in JavaScript get opened with a headless browser.
Failing without falling
Each source runs inside its own timeout, with retries and exponential backoff. A portal that breaks records the error and the harvest carries on. There is no single point of failure.
Knowing when a parser rots
We count the rows a parser offers, not only the ones that match. That separates a genuinely quiet portal from a broken parser returning zero in silence.
Weekly active probe
Every Monday, connectors quiet for three weeks are deliberately re-run. Two consecutive zeros mark the connector suspect and raise an alert.
the relevance engine
Searching by words is not enough.
A relevant tender can turn up in Polish, Arabic or Finnish. It can carry accents, irregular plurals, or a European code instead of words. The engine handles that before the AI is even called, because calling the AI on everything would be slow and expensive.
Accents and alphabets
Everything is normalised on both sides of the comparison: Latin, Greek, Cyrillic, Arabic harakat and Hebrew niqqud. "Oxigénio" and "OXIGENIO" are the same word, and so is Turkish "LİKİT".
Plurals derived, not hand-written
Variants are generated by rule from the stem, covering Portuguese, French, Italian and German endings. Slavic and Baltic grammatical cases are listed by hand instead, because they cannot be derived safely.
CPV codes
Strong European codes stand on their own, free of any language. Weak ones only count with a health anchor beside them, and they arrive flagged.
A safety net for the borderline
When nothing strong matches, a generic product noun plus a health anchor still lets the notice in, marked. The AI is the judge. The weak tier only guarantees arrival.
Tested against real tenders
93 regression cases, each taken from a genuine notice with its reference annotated. 75 must hit, 18 must miss. If one breaks, the build fails and nothing ships.
the score
The AI scores. The rules stop it exaggerating.
An enthusiastic model is worse than no model, because it manufactures false confidence. So the AI’s score passes through deterministic caps it cannot get around.
Two stages
First a deterministic triage on product fit, geography, value and runway. Then the AI audit, which reads the official page and up to six attached documents and writes the final score.
Brakes against optimism
The score is capped by deterministic rules the AI cannot talk its way past. No product matches caps it at 40. Only low-confidence matches, 55. Only a high-confidence match can go past 72.
Inventing is forbidden
If no document was actually read, every match the model returned is deleted and the score is capped at 25. This rule exists because of a real case where an audit mapped products onto a tender it had never read.
Audit everything, or publish nothing
If any tender is left un-audited, the process exits with an error. Since publishing comes after, an un-audited tender can never reach the site. The rule lives in code, not in procedure.
If the documentation was not read, the score does not rise. We would rather have a low honest number than a high invented one.
documents
The deadline is inside the PDF.
It took an already-closed tender sitting at the top of the board to bring this part of the system into being. The date existed, but only inside the document. Now we go and get it.
Finding the files
On TED we read the notice XML and follow the official procurement-documents reference through to the buyer’s national portal. Elsewhere we derive the file API or follow the attachments page.
Actually reading the PDF
File type is detected from the bytes, not the header, because plenty of portals serve PDFs with the wrong header. Encrypted or scanned PDFs return nothing, and nothing means "unread", never a licence to guess.
The hidden deadline
Many tenders never publish the deadline in their metadata. We extract the date from the text, with anchors in five languages that tell a submission deadline apart from a contract duration. Where there are two stages, the earlier one wins, because that is the one that shuts the door on a new bidder.
automation
It runs itself, and says when it is sick.
Four passes a day
It runs in the cloud on weekdays, with a deep pass on Mondays. It depends on no machine being switched on.
Exiting in two seconds
The first thing the job does is check whether the day has already been harvested. If so, it exits before installing anything at all. The catch-up passes only cost something when the primary one genuinely failed.
A runaway brake
If a run were about to audit more than 1,200 tenders, it fails before the first AI call. That is the guard against a mistake that multiplies cost.
Cost in layers
A cheap model triages everything; the strong model only re-audits what could be actionable. Audits go through in batches, at half price.
It tells you when it is sick
Five consecutive error days on a connector raises an email alert, without repeating the same warning twice in a day.
beyond finding
Finding is half of it. Winning is the other half.
Who wins, and for how much
We harvest contract award notices carrying a named winner and a value. Out of those come incumbents per buyer, price bands, expected bidder counts and a read on win probability.
Before the tender exists
European prior information notices are harvested as signals. And a framework contract getting old is itself a signal that a re-tender is coming.
A portrait of the buyer
Every contracting authority gets its own page, with history and the suppliers it habitually works with.
Local partners
In markets that require local representation, the platform researches candidates and checks that each company’s website actually responds. Whatever the model claims, a company that does not respond is dropped.
A bid draft
From the tender and its audit comes a compliance matrix, requirement by requirement. Anything without evidence is marked as a gap, not filled in with good intentions.



platform
One engine, many clients.
The same system serves different sectors. What changes is each client’s keyword profile, and that profile is generated from the company’s own catalogue.
Multi-client by domain
The request host decides the tenant. The internal header is always rewritten by the server, so it cannot be forged from outside. If the context is not populated, the code throws rather than show the wrong data.
Isolation verified at build time
A script confirms that every route populates the tenant context. If one forgets, the build fails.
API and integrations
A public API with keys stored only as a cryptographic digest, webhooks into Slack and Teams, and an MCP server exposing nine read-only tools so AI assistants can reach the data.
the proof
EUR 700,000 in the first week.
TRINTA was born inside Ultra Controlo, a 40-year-old Portuguese medical-gas manufacturer serving more than 80 countries. In its first week it found a EUR 700,000 tender the company had never seen. It won it two weeks later. In month one the radar surfaced more than EUR 30 million in qualified opportunities.
We say this plainly: Ultra Controlo is the founder’s family company. That is where the system proved itself, on a real pipeline, before it was sold to anyone.
built with
- TypeScript
- Next.js 16
- React 19
- Tailwind v4
- Claude Sonnet 4.6
- Claude Haiku 4.5
- Playwright
- Vitest
- GitHub Actions
- Vercel
Want one of these for your operation?
TRINTA is what we build when the problem is our own. If you have a problem shaped like that, give us fifteen minutes and we will tell you honestly whether it is worth doing.