bid-pool-workbenchrun.md00_intake/stub.mdThe workbench Bid pool table
(apps/web/components/dashboard/expert/bid-pool-table.tsx)
is dummy, even though the standalone /expert/bids page already renders the real bid pool via
leadService.findBidPoolLeads
(apps/web/components/expert/bid-pool-table.tsx,
note the different path). The workbench version shows richer fields the real query doesn't yet return:
urgency, a value range + valueSource (Customer budget | System estimate), timeToStart,
timeToRespond, and a fit label.
findBidPoolLeads with the expert's match fitScore + recommendation), so it stops being mock.fit label from the existing fitScore bands.timeToRespond from lead.bidDeadline.timeToStart from lead.startDate.value range + valueSource — net-new: a customer-budget signal vs a system estimate. If no
budget field exists, Define decides whether to add one or show estimate-only.urgency — derived from bid deadline proximity / fit, or a small signal on the lead./expert/bids).value/valueSource reflect a real budget when present and an estimate otherwise (clearly
labelled), never invented.findBidPoolLeads path — no second bid-pool query./expert/bids./expert/bids (already real, left
alone).valueSource label honest about it.touches: packages/services/src/db/services/leads (reuse/extend findBidPoolLeads projection),
apps/web/components/dashboard/expert/bid-pool-table.tsx.
</content>
</invoke>01_define/output/spec.mdThe expert workbench (/expert/dashboard) Bid pool table is mock — it renders static rows from
lib/mock/expert.ts even though the standalone /expert/bids page already shows the real bid
pool via leadService.findBidPoolLeads (which returns the expert's fitScore, confidence and
recommendation per match). The workbench is the expert's daily landing surface, so a fake bid pool
there undercuts trust in the whole dashboard. This advances Build the Bridge → Q2 2026 Objective 3
(Validate technical infrastructure & payout flow): experts can only act on real opportunities — see
fit, judge value, and respond before the deadline — if the workbench shows their actual open pool.
Render the workbench Bid pool from the existing findBidPoolLeads query (reuse it — no second
bid-pool query path), scoped to the signed-in expert, and replace the mock with a compact top-5 view.
The full list stays at /expert/bids.
Workbench-only derivations (all from real fields already returned, plus one net-new budget field):
fitScore bands, matching /expert/bids: >= 70 → "Strong fit",
40–69 → "Moderate fit", < 40 → "Weak fit". No score → no fit label.lead.bidDeadline (e.g. "2 days", "Today", "Overdue"); "—"
when no deadline is set.lead.startDate; "—" when unset.<= 2 days (or overdue) → "High", <= 7 days →
"Medium", else / no deadline → "Low".budgetMin / budgetMax, currency-less integers in the tenant's currency). When present, show the
formatted range labelled "Customer budget". When absent, show "—" — no invented estimate
this run (a "System estimate" source is out of scope; see below).Sort + size: top 5 by fit (highest fitScore) then nearest bidDeadline. Reuse
findBidPoolLeads (extend its projection to include the new budget fields); the workbench requests the
expert-scoped pool and applies this sort/limit — it does not introduce a parallel query.
Empty pool: render a clean empty state with a link to /expert/bids.
The new budget fields are additive and optional on the lead model; nothing currently writes them, so
existing leads render "—" until a budget is captured (capture UI is out of scope).
findBidPoolLeads, expertUserId scoped) — no mock rows.fitScore bands), value + valueSource, time-to-respond, and
time-to-start, all derived from real lead/match data.value/valueSource shows the customer budget range labelled "Customer budget" when the lead
has budgetMin/budgetMax, and "—" otherwise — never an invented number./expert/bids (linked from the workbench).findBidPoolLeads./expert/bids.bidPool rows are removed from the workbench data path (lib/mock/expert.ts /
apps/web/components/dashboard/expert/bid-pool-table.tsx).valueSource carries only "Customer budget"./expert/bids (already real — left
alone).fitScore, confidence, recommendation are consumed as-is).apps/dashboards expert page — it stays mock by convention (dashboards app is
self-contained mock data).02_build/output/notes.mdfeat: bid-pool-workbench — customer budget fields on leadfeat: bid-pool-workbench — render workbench bid pool from real datapackages/services/src/db/models/lead.ts — added optional, integer-validated budgetMin /
budgetMax to both ILeadPopulated and the schema (the net-new customer-budget signal). They are
additive and unset on existing leads.packages/services/src/db/services/leads/index.ts — no change needed. findBidPoolLeads
returns full lean docs (no .select() projection), so the new scalar fields flow through
automatically and are typed via ILeadPopulated → BidPoolLeadPopulated. The workbench reuses this
exact query — no second bid-pool path.apps/web/components/dashboard/expert/bid-pool-table.tsx — rewritten from a mock-driven client
component into a self-contained async server component (mirrors apps/web/components/expert/bid-pool-table.tsx):
resolves tenant + signed-in expert, calls findBidPoolLeads({ expertUserId }), ranks top 5 by fit
then nearest bid deadline, and renders Customer / Urgency / Value / Time to respond / Time to start
/ Fit. Pure derivation helpers: fitLabel (fitScore bands, matching /expert/bids), relativeTime
(bidDeadline / startDate → "2 days" | "Today" | "Overdue" | "—"), urgencyFromDeadline, and
customerBudget (Customer-budget range or "—", never invented). Header links to /expert/bids;
empty pool renders a clean state linking there too. Exports BidPoolTableLoading for Suspense.apps/web/app/(app)/expert/dashboard/page.tsx — dropped "use client" so the page (a server
component) can render the async bid-pool table; wrapped it in <Suspense> so the DB query doesn't
block the rest of the (instant, mock) workbench. Removed the bidPool={d.bidPool} prop.apps/web/lib/mock/expert.ts — removed the mock bidPool array, the BidItem type, and the
bidPool field from WorkbenchData (the workbench bid pool is now real).findBidPoolLeads
(expertUserId scoped) — no mock rows.value/valueSource shows "Customer budget" range when budgetMin/budgetMax set, "—"
otherwise — never an invented number (no "System estimate" source)./expert/bids
(linked from header + empty state).findBidPoolLeads./expert/bids.bidPool rows removed from the workbench data path (mock file + component).gh pr checks. No check is expected to fail.RANKING_LIMIT (50) matched leads to rank top-5 by fit, since fitScore
is attached after the Mongo query and can't be sorted in the DB. Expert bid pools are bounded
(matched leads only), so this is a safe cap; the accurate total is shown via pagination.total.budget fields are read-only this run — no capture UI (explicitly out of scope), so leads
render "—" for value until a budget is populated.03_ship/output/changelog.mdPersona: Expert
Your go live workbench now opens to your real bid pool, not a sample:
03_ship/output/investor-update.mdThe expert workbench — the first screen an expert sees — now shows their real bid pool instead of a sample row. Each opportunity is ranked by how well it fits the expert and surfaces the customer's budget (where shared), time to respond, and time to start, so experts can act on the right work immediately. This advances Build the Bridge by strengthening the bid pool and proposals stage of the service journey, and moves our Q2 objective to Validate Technical Infrastructure & Payout Flow by putting experts in front of real, actionable leads on day one.
03_ship/output/pr.mdrelativeTime mislabel: Math.ceil of a sub-day negative fraction yields -0, which slipped past the days < 0 check, so a deadline that passed <24h ago showed "Today" instead of "Overdue". Now checks the raw ms sign first. (urgency was unaffected — -0 <= 2 already resolved to High.)/expert/bids uses badge variants — same thresholds, different output, not worth a shared util yet; ranking top-5 in the component and fetching up to 50 to rank is intentional (fitScore is attached post-query, can't be Mongo-sorted) and bounded — a service-level sort/limit option is a future refinement, not in scope.lead.budgetMin/budgetMax — BidPoolLeadPopulated extends ILeadPopulated, which now carries both fields; types resolve.