Concursea
A construction company bidding for public works gets a specification, a tender programme and a bill of quantities with thousands of lines, and a few weeks to turn them into a technical proposal with a credible work schedule. Concursea reads those documents, applies the company's own rules and hands the estimator a first draft to review — never a black box.
CONCURSEA · FROM TENDER DOCUMENTS TO WORK SCHEDULE
Why this exists
The technical side of a public works proposal — which activities, which crews, how many days, in what order — is rebuilt for every tender, under deadline, from documents that run to hundreds of pages. The schedule that wins or loses the bid is the part nobody has time to check, and months later nobody can say where a duration came from. Concursea exists to make that first draft fast and every number in it explainable.
1 # What was broken
2
3 - hundreds of pages per tender
4 - a BoQ with thousands of lines
5 - the schedule rebuilt by hand, every bid
6 - durations nobody can justify later
7
8 > goal: a first draft the estimator can defend
The decisions I made
One rule carries the product: AI reads, maths decides. Gemini runs only on the server and does the reading — extracting items from PDFs and spreadsheets and classifying each bill-of-quantities line by trade and by delivery method, in-house or subcontracted. Every number in the schedule is deterministic: the quantity divided by the output rate of a crew the company approved once in its own catalogue. Each extracted item keeps a link to the document it came from, and nothing reaches the proposal until the estimator approves it. Seven steps, from company profile to approval, on React and Firebase, with Playwright end-to-end tests in CI.
1 [tender docs] PDF · Excel
2 │
3 [Gemini · server only] extract · classify
4 │
5 [company catalogue] ──▶ deterministic engine
6 crews · output rates qty ÷ rate → days
7 │
8 [estimator review] ──▶ approved proposal
9
10 // trade-off: no rule → flagged, never guessed
The part I'm proud of
A duration is never invented. When a line has an approved output rate, the engine divides, rounds up and stores the formula next to the result, so the estimator can show exactly where 18 days came from. When there is no rule, or two rules disagree, the line is blocked with a finding the estimator has to resolve. A subcontracted trade asks for the duration agreed with the subcontractor instead of pretending the company's own crews apply.
1 // a duration carries its own proof
2 const perDay = rate * crewCount * shiftFactor;
3 const days = Math.max(1, Math.ceil(quantity / perDay));
4 formula = `ceil(${quantity} ${unit} / ${perDay}) = ${days}`;
5
6 // no approved rule → blocked, never guessed
7 if (resolved.status !== 'resolved')
8 return blocked('duration_rule_missing');
What changed afterwards
The landing page is live in Portuguese and English, and we are opening the first pilots with construction companies, demoed on synthetic data so no real tender leaves anyone's hands. Preparing the proposal works end to end today; exporting in the format the tender programme asks for (A3 PDF and MS Project) is in development, and the cash-flow schedule and crew capacity come next. What I'd do differently: chase a real company's crews and output rates first — the plan is only as credible as that catalogue.
1 ## Status
2
3 proposal preparation available
4 A3 PDF + MS Project in development
5 cash flow · capacity planned
6 landing (PT / EN) concursea.com
7
8 ### What I'd do differently
9 get the real catalogue before anything else