fix: coherent demo data — compliance, audit trail, currency, charts#33
Conversation
The seeded demo dataset produced several numbers that read as bugs rather than a working business. This reseeds the demo data so every screen tells one coherent story, and hardens a few queries so the views can't disagree. Compliance - Documents were assigned random types across contractors, so almost no contractor held both required documents and the dashboard showed "0% Compliance Rate". Documents are now generated as a coherent per- contractor set with a deterministic ~85% compliant / ~12% expiring / ~13% missing-one-doc distribution (stable across reseeds). - getComplianceReport is scoped to active/suspended contractors: someone still in onboarding is not "non-compliant", they're just not done. W-9 consistency - contractors.repository documentStatus now treats an expired document as not-on-file, so the Overview tab can no longer say "W-9 on file" while the Documents tab shows it "Expired". Seed data is coherent on both views (compliant = far-future expiry; expiring = inside 30 days). Audit trail - audit_events was empty except for "notifications / read". A believable ~5-month activity stream is now seeded: staff logins, the full invoice lifecycle (pinned to each invoice's own timestamps so nothing is "paid" before it was "submitted"), contractor/document/offboarding/settings/ classification events, with real actors and no future-dated rows. Currency - The full seed sets the organization's default currency to USD, matching the USD-everywhere display formatting. Invoices & charts - generateInvoice anchored the lifecycle off a small offset, so recent "paid" invoices could land a future paid_at. The chain is now built with guaranteed past headroom and a net-30 due date. - Monthly revenue / contractor-growth / contractor-earnings queries only returned months that had data, making the dashboard revenue chart taper to $0. They now zero-fill a fixed N-month spine via generate_series. Tooling - Add seed:demo-accounts script and document the two-step reseed (the full seed rebuilds every org, so the idempotent demo/E2E accounts must be re-seeded immediately after).
There was a problem hiding this comment.
Code Review
This pull request significantly enhances the demo data seeding process by introducing more realistic invoice lifecycles, coherent document compliance distributions, and a comprehensive audit trail. It also improves dashboard data consistency by using SQL spines to ensure months with zero activity are represented and updates repository queries to better handle document expiration and contractor statuses. My feedback focuses on improving the robustness of the seeding logic by addressing brittle array indexing, replacing unsafe non-null assertions with explicit error handling, and ensuring data coherence in the audit logs.
| createdDaysAgo: 430 + (idx % 60), | ||
| }), | ||
| ); | ||
| docs[0]!.version = 2; |
There was a problem hiding this comment.
Accessing the document by a hardcoded index docs[0] is brittle and assumes the tax document is always the first element pushed to the array in the compliant bucket. It's safer to find the document by type or keep a reference to the object when it's created.
const taxDoc = docs.find((d) => d.documentType === taxType);
if (taxDoc) taxDoc.version = 2;| const admin = users.find((u) => u.role === 'admin')!; | ||
| const manager = users.find((u) => u.role === 'manager')!; |
There was a problem hiding this comment.
Using non-null assertions (!) on the results of find can cause the seed script to crash with a TypeError if the expected users are missing from the input array. Adding an explicit check with a descriptive error message would improve the robustness of the seeding process.
| const admin = users.find((u) => u.role === 'admin')!; | |
| const manager = users.find((u) => u.role === 'manager')!; | |
| const admin = users.find((u) => u.role === 'admin'); | |
| const manager = users.find((u) => u.role === 'manager'); | |
| if (!admin || !manager) { | |
| throw new Error('Audit seed requires both an admin and a manager user'); | |
| } |
| userId: admin.id, | ||
| entityType: 'organizations', | ||
| entityId: admin.id, | ||
| action: 'update', |
CodeQL flagged the new seed code's Math.random() as insecure randomness. It's seed-only fixture data, but route all fixture randomness through crypto.randomInt (via the existing randomPick/randomBetween helpers and a new randomBool) so static analysis is clean and the alert can't recur.
Why
A visitor judging the live demo currently hits numbers that read as bugs:
notifications / read$These were verified against the live site before this change (0% compliance, EUR currency, audit log with only "read" rows confirmed via screenshots).
What
generateContractorDocumentsbuilds a coherent per-contractor document set with a deterministic ~85/12/13 compliant/expiring/missing distribution.getComplianceReportscoped to engaged (active/suspended) contractors — onboarding contractors are not "non-compliant".contractors.repositorydocumentStatusnow treats an expired doc as not-on-file, so Overview and Documents tabs can never contradict.generateAuditEvents— believable ~5-month stream (logins, full invoice lifecycle pinned to invoice timestamps, contractor/document/offboarding/settings/classification), real actors, no future-dated rows.defaultCurrencyto USD.generateInvoicerebuilt so the submit→approve→schedule→pay chain always lands in the past (no futurepaid_at); net-30 due date.generate_series.seed:demo-accountsscript + README documents the two-step reseed.Verification
tsc,eslint, and affected unit suites (documents/contractors/organizations/audit/classification — 187 tests) green.The demo seed rebuilds every org, so after
pnpm --filter @contractor-os/api seedthe idempotentseed:demo-accountsmust be re-run (documented; required to keep the E2E live-smoke accounts).