Skip to content
JONAS STAMM
— FIELD NOTES
technical-deep-dive— September 21, 2026· 6 min read

What it took to ship BauGPT's Ausschreibungs-Radar

Most of the work in a tender radar is not the matching. It is everything around the matching that stops it from mailing the wrong company, at the wrong time...

JS
Jonas Stamm
Founder, BauGPT

Most of the work in a tender radar is not the matching. It is everything around the matching that stops it from mailing the wrong company, at the wrong time, about a tender that already closed. Last month we shipped the Ausschreibungs-Radar in BauGPT, made it a Business plan feature, and then spent the rest of the same week closing defects that the per-feature reviews had not caught. This is the build log, written for the construction buyer who wants to know what "the AI finds tenders for you" actually means under the hood.

What the radar does

The idea is simple. BauGPT watches public construction tenders, scores each against your company profile, and sends a daily digest of the matches. Every match in that mail carries a signed link that dismisses it in one click, so you can kill a bad match straight from your inbox without logging in. Inside the chat there is also a tender_fit tool: paste a tender, ask the assistant whether it fits your company, and it runs the same scoring. The whole feature is reserved for the Business plan.

That is the pitch. The rest of this post is about the part nobody puts in the pitch.

The hardening pass

Start with the deadline. Tenders publish an opening date. Before this fix, our deadline was derived from a duration field, which goes wrong the moment a tender gets extended. Now the deadline comes straight from the published opening date, and a duration is no longer trusted for it at all.

Next: corrigenda. Public tenders get corrected all the time. A corrected tender must be allowed to mail you again, because the correction might be exactly the thing that makes it relevant to you. At the same time, the profile gate that decides whether a match is worth mailing now fails closed. If it cannot decide, nothing goes out, and there is a test that proves it.

Then money. Every score is a model call, and a tender feed does not care about your budget. We put a cap on tender_fit spending and tightened which profiles a scoring run is allowed to touch. The score budget itself became atomic, scores that are not numeric get rejected instead of stored, and reference snippets that show up twice are removed before they reach the model. Matches that got deferred because the budget ran out are picked up by a later sweep and scored then, instead of being lost.

CAVEAT

No usage numbers in this post, because I do not have clean ones yet. The radar is new and only on the Business plan. When there is a real number on matches sent or tenders won, it gets its own post.

The plumbing nobody sees

Then there was the plumbing, and honestly this was most of the week. Jobs were being dispatched to a queue that did not exist, so now everything goes through the queue-assignment path and lands on a real worker. The prefilter now runs right after every ingest window instead of on its own schedule, so new tenders get screened as soon as they arrive. The sweep is now limited by relevance score, the month-based prefilter is gone from it, and we added checks that prove the counts are what we think they are. The summary log for the sweep came back, and it no longer claims starvation when there is none. Digest failures are surfaced instead of swallowed, and the placeholder for a tender with no buyer name is back. The digest itself is safe to prefetch, fresh at send time, and hardened against prompt injection through tender text.

None of these were visible in a per-task review. Each ticket looked fine on its own. The cross-cutting defects only showed up when we walked the whole flow end to end, and that pass got its own commit.

The last fix in that pass is the one I want to underline. The list of people who get notified is now tied to the company that is actually acting, full stop. Sounds obvious. It is also the kind of bug that only appears when several companies share the same instance, which is exactly the situation an enterprise rollout creates. A tender radar that mails the wrong company is worse than no radar.

"A tender radar that mails the wrong company is worse than no radar."

Evals for Baurecht

The other half of the week was evals. BauGPT answers German construction law questions, and the dangerous answers are the ones that sound right and are not. We added a dedicated eval family for a class of tricky construction-law cases, with its own rubric and a flagship case. The corpus landed with a floor of 8 cases. Not a lot. Enough to catch regressions on the answers that matter most, and the floor is there so it cannot quietly shrink.

The scoring changed as well. The Baurecht screen no longer decides pass or fail. It only points at where an answer is weak, and the verdict comes from somewhere else. That somewhere else is the LLM judge, which is now mandatory: whatever the judge decides is the result, and nothing passes without it. Before that, four of our traps were firing on answers that were actually correct, so we fixed the traps rather than the answers. That one stings a bit. An eval that punishes correct answers is worse than no eval, because you start optimising for the wrong thing without noticing.

Smaller things from the same window

Some unrelated fixes shipped alongside. The CRB import gained coverage for B records and a missing quantity-breakdown code, and a follow-up closed quantity holes and gave a clearer error for the legacy file format. The sanitiser we use on those fixtures now refuses to run at all if it cannot find a secrets source, so nothing real can leak into a test fixture. And seat limits and cooldowns in directory sync are now soft by default, because a hard limit in the middle of an enterprise rollout blocks exactly the people you just onboarded.

What to ask a vendor

If you are a construction company evaluating tender tools, ask these questions. What happens when a tender is corrected? Where does the deadline come from? What happens when the scoring budget runs out halfway through the feed? Who gets the mail when your group has several companies on the platform? If the answer is a shrug, the radar will be quiet on the day it matters. We had to answer all of those ourselves, and the answers are now in the code. Feel free to ping me if you want to see it running.

— END · Field Notes —

Keep reading

All writing →
technical-deep-dive · Oct 14, 2026
Nº 01

Citations Did Not Stop Our AI From Inventing a Case Study

Our blog pipeline once wrote an invented case study: a draft about shipping BauGPT's WhatsApp bot in 10 days, with a cost breakdown in the headline. The...

6 MIN READ
↗
technical-deep-dive · Aug 31, 2026
Nº 02

The hard part of a tender radar is not the matching

Most of the work in shipping BauGPT's Ausschreibungs-Radar had nothing to do with matching. Deciding whether a public tender fits a Bau company's profile was...

7 MIN READ
↗
technical-deep-dive · Aug 20, 2026
Nº 03

What Bau Enterprises Actually Made Us Build

Most of what we shipped on BauGPT in the last weeks had nothing to do with the model. It was folders, permissions, single sign-on, and Word files that open...

7 MIN READ
↗
— THE NEWSLETTER

One note a week.
No fluff, just what works.

AI engineering, growth hacks, and messy lessons from shipping BauGPT. Unsubscribe anytime. I'll even miss you.

FIELD NOTES · NEXT ISSUE DROPS MONDAY
↳ No spam. One note weekly.