One product, two documents on this page: a plain-language overview for pitching and a buildable spec for implementation. Both describe the same scope and the same launch commitments.
A web tool that lets a main contractor or GLC procurement team vet subcontractors before awarding work — replacing the paper form or one-off spreadsheet that currently gets filled in per tender and then thrown away. The buyer sets up their own scoring criteria once (financial standing, past performance, safety record, whatever matters to them); subcontractors fill in a self-service form; a reviewer scores and approves or rejects; and every past result is kept, so the next tender starts from a record instead of a blank page.
Right now this is done on paper or in a spreadsheet copied fresh for every tender. There's no running history: a subcontractor who failed on safety last year can slip through again because nobody remembers, or because the person who remembers has left. Enterprise vendor-management software exists, but it's priced and built for large corporates running thousands of vendors — nothing is sized for a contractor doing this 20–100 times a year.
| Included (P0) | Added once there are paying customers (P1) | Not yet — future (P2) |
|---|---|---|
| One fixed scorecard template the buyer fills in with their own questions; subcontractor self-submission form; single-reviewer approve/reject/needs-info workflow; subcontractor history visible on their profile | Multiple reviewers with sign-off routing; reminder emails for incomplete submissions; CSV export of a tender's full result set | Integration with the buyer's own ERP/procurement system; analytics dashboards on subcontractor performance trends across the whole portfolio; multi-criteria weighted scoring templates (versus one fixed template) |
6–7 weeks from kickoff to a pilot customer running one real tender cycle through the tool — assuming the scorecard stays to the single fixed-template scope above. This is a build estimate for one developer working solo with AI-assisted tooling, not a contractual date; it hasn't been tested against a real customer's actual tender calendar, which may force the timeline around their own schedule rather than ours.
| Priority | Feature |
|---|---|
| P0 | Fixed scorecard template per buyer, CSV subcontractor import, self-submission form (no login), single-reviewer scoring + approve/reject/needs-info, subcontractor profile history |
| P1 | Multi-reviewer sign-off routing, reminder emails for incomplete submissions, tender result CSV export |
| P2 | ERP/procurement system integration, cross-portfolio analytics, configurable weighted multi-template scoring |
flowchart LR A[Buyer signs up] --> B[Configure scorecard
template] B --> C[Open a tender,
import subcontractor list] C --> D[Subcontractors invited
by email link] D --> E[Subcontractor submits
self-service form] E --> F[Reviewer scores
submission] F -->|approved| G[Added to tender's
approved vendor list] F -->|rejected| H[Marked rejected,
reason recorded] F -->|needs info| D G --> I[Decision written to
subcontractor's permanent history] H --> I
flowchart TD
A[Buyer account created] --> B{First time?}
B -->|yes| C[Guided scorecard setup
with us, hands-on]
B -->|no, new tender| D[Reuse existing scorecard]
C --> E[CSV import of first
subcontractor list]
D --> E
E --> F[Invite links sent]
F --> G[We watch first live
cycle end to end]
flowchart TD A[Subcontractor opens
invite link] --> B[Fills scorecard
answer form] B --> C[Uploads supporting
documents] C --> D[Submits] D --> E[Reviewer dashboard:
submission appears as Pending] E --> F{Reviewer decision} F -->|Approve| G[Status: Approved] F -->|Reject| H[Status: Rejected + reason] F -->|Needs info| I[Status: Needs info —
subcontractor notified] I --> B G --> J[(Subcontractor profile:
decision + date + tender)] H --> J
flowchart LR
subgraph Procuring Company Account
AD[Procurement Admin
configures scorecard,
manages tenders, users]
RV[Reviewer
scores submissions,
approves/rejects]
end
SC[Subcontractor
no login — link-based access,
can view own submission status]
AD -- invites --> RV
AD -- opens tender for --> SC
RV -- decides on --> SC
flowchart TD A[Buyer signs annual contract] --> B[Invoice issued,
RM4,800/yr] B --> C{Paid?} C -->|yes| D[Account active
for 12 months] C -->|no, past due| E[Grace period reminder] E -->|still unpaid| F[Account read-only:
history visible, no new tenders] D --> G[Renewal reminder
60 days before expiry]
flowchart LR A[Us, the vendor] --> B[Provision new buyer account] B --> C[Assist first scorecard setup] A --> D[Monitor for failed imports /
bounced invite emails] A --> E[Handle support requests
from Procurement Admins] A --> F[Track renewal dates,
flag at-risk accounts]
A modular monolith is the right call here — the feature set (accounts, scorecards, submissions, reviews) is small enough that splitting into services would only add deployment and debugging overhead for a solo developer, with no scaling need that justifies it yet.
company_id on every tenant-owned table, enforced in the query layer (Postgres RLS if using Supabase directly, or an explicit WHERE company_id = ? guard on every query if using Drizzle without RLS) — this is the single most important thing to get right given the no-cross-tenant-visibility rulecompany_id as above
flowchart TB
subgraph Client
PA[Procurement Admin /
Reviewer dashboard]
SF[Subcontractor
submission form]
end
subgraph "Next.js app (Vercel)"
API[API routes /
server actions]
AUTH[Auth: email/password
+ signed link tokens]
end
DB[(Postgres
Neon / Supabase)]
FS[(Object storage
for documents)]
MAIL[Email provider
invites + reminders]
PA --> API
SF --> API
API --> AUTH
API --> DB
API --> FS
API --> MAIL
erDiagram
COMPANY ||--o{ USER : employs
COMPANY ||--o{ SCORECARD_TEMPLATE : owns
COMPANY ||--o{ TENDER : runs
TENDER ||--o{ SUBMISSION : receives
SCORECARD_TEMPLATE ||--o{ SUBMISSION : "scored against"
SUBCONTRACTOR ||--o{ SUBMISSION : submits
SUBMISSION ||--o| DECISION : "resolved by"
USER ||--o{ DECISION : makes
COMPANY {
uuid id PK
string name
string plan_status
}
USER {
uuid id PK
uuid company_id FK
string role
string email
}
SUBCONTRACTOR {
uuid id PK
string company_name
string registration_no
}
SCORECARD_TEMPLATE {
uuid id PK
uuid company_id FK
jsonb questions
}
TENDER {
uuid id PK
uuid company_id FK
uuid template_id FK
string name
string status
}
SUBMISSION {
uuid id PK
uuid tender_id FK
uuid subcontractor_id FK
jsonb answers
string status
timestamp submitted_at
}
DECISION {
uuid id PK
uuid submission_id FK
uuid reviewer_id FK
string outcome
string reason
timestamp decided_at
}
SUBMISSION.status is one of pending / needs_info / approved / rejected; once approved or rejected, the row is immutable — a correction creates a new SUBMISSION row linked to the same tender and subcontractorSUBCONTRACTOR is deduplicated by registration_no (the company registration number) so the same real-world entity isn't scored under two different profiles across buyerscompany_id — enforced at the query layer, not just the UI| Endpoint | Method | Auth | Notes |
|---|---|---|---|
| /api/scorecard-templates | GET, POST | Procurement Admin | Scoped to caller's company_id |
| /api/tenders | GET, POST | Procurement Admin | Create tender, attach a template |
| /api/tenders/:id/subcontractors | POST | Procurement Admin | CSV import, generates signed invite links |
| /api/submit/:token | GET, POST | Signed link token (no account) | GET renders the form, POST submits answers + uploads |
| /api/tenders/:id/submissions | GET | Reviewer | List pending submissions for review |
| /api/submissions/:id/decision | POST | Reviewer | Body: outcome, reason — writes DECISION, locks SUBMISSION |
| /api/subcontractors/:id/history | GET | Procurement Admin / Reviewer | Past decisions for this subcontractor within caller's company only |
Two distinct front ends: an internal dashboard (Procurement Admin + Reviewer) and an external, no-login submission form (Subcontractor). Design them as if they're two different products that happen to share a database.
The internal dashboard is desktop-first — procurement staff review on a desk, not a phone. The subcontractor submission form must work well on mobile, since many subcontractor-side staff will open the invite link from WhatsApp on a phone.
Company/user accounts, company-scoped auth, scorecard template CRUD (single template per company). Deliverable: a Procurement Admin can log in and define their scorecard questions. Done when: templates persist and are correctly scoped per company.
Tender creation, CSV subcontractor import, signed invite-link generation, the external no-login submission form with file upload. Deliverable: a subcontractor can receive a link and submit a complete form. Done when: a submission lands in the database correctly tied to the right tender and subcontractor.
Reviewer dashboard listing pending submissions, approve/reject/needs-info action, subcontractor profile showing cross-tender history. Deliverable: a full tender cycle runs end to end. Done when: a decision is recorded, the submission locks, and it appears in that subcontractor's history.
Fix whatever breaks when a real pilot customer runs their first live tender through it — empty/error states, email deliverability for invite links, basic billing status tracking. Deliverable: one pilot customer's real tender completed start to finish. Done when: they'd pay for a second tender cycle without us in the room.
This gets one pilot customer through one real tender, not a finished platform. Multi-reviewer workflows, reminder emails, and CSV export (P1) come after that pilot proves the core loop, not before.
| Stage | Technical Founder | Commercial Founder |
|---|---|---|
| Discovery | Confirm the single-template P0 scope is technically sound | Validate with 2–3 prospects whether one fixed scorecard template is acceptable, or whether per-tender customization is a dealbreaker |
| Planning | Lock the data model and API surface above | Confirm the RM4,800/yr annual price point survives a real pricing conversation with a prospect |
| Development | Build weeks 1–7 per the roadmap | Line up a pilot customer with a real tender cycle landing inside that build window, so there's no idle gap after launch |
| Testing | Fix issues surfaced by the pilot's real tender | Sit with the pilot's Procurement Admin and Reviewer during their first cycle, collect friction points directly |
| Launch | Handle the pilot's second tender with less hand-holding; start on P1 features the pilot actually asked for | Convert the pilot to a paid annual contract; start outreach to the second prospect using the pilot as a reference |