Rebuild Estimates — .NET + Angular
Estimate for rewriting Dossier (server, client, root, portal) on a .NET + Angular stack, using the existing TypeScript codebase as a reference but not as a port. The receiving team owns the knowledge by re-implementing.
Source codebase, for reference
| Package | Purpose | Files | LOC |
|---|---|---|---|
core |
shared types, DB schemas, expression engine | 45 | 6,306 |
server |
Fastify API, ~45 routes, PDF, auth | 61 | 7,968 |
client |
tenant React app (port 4103) | 113 | 18,145 |
root |
admin React app (port 4104) | 59 | 12,573 |
portal |
client portal + intake widget (port 4105) | 41 | 5,391 |
| Total | 319 | ~50,400 |
Plus 7 SQL migrations, 7 Drizzle schema files, ~10 test files in core/server.
Target stack assumed
- Backend: .NET 8/9, ASP.NET Core minimal APIs (or controllers), EF Core on PostgreSQL 17, FluentValidation, JWT auth
- PDF: iText 7 or QuestPDF + PdfSharpCore (AcroForm read/fill, merge)
- Frontend: Angular 17+ (standalone components, signals), Angular Material or PrimeNG, ng-pdf-viewer or equivalent for PDF preview
- Testing: xUnit + Testcontainers (Postgres) for backend; Jest + Cypress/Playwright for frontend
- Observability: OpenTelemetry .NET SDK, Aspire dashboard (already used)
Assumptions
- 1 dev day = 8 productive hours. Estimates are mid-range; ranges given for risk.
- Team has solid .NET + Angular experience but is new to bankruptcy/court forms — domain learning is included.
- The existing TS source, schemas, forms, and
domains/artifacts are available as reference. Schemas/forms JSON are reused as-is (not rewritten). - Existing PostgreSQL schema is kept; EF Core models map to existing tables. No data migration needed for greenfield rebuild.
- The
website-publicandwebsite-privatepackages (static HTML) are out of scope — same files serve unchanged. - Marketing, design polish, accessibility audit, and security review are budgeted lightly; assume the existing UX is the spec.
Summary
| Area | Backend | Frontend | Tests | Total (days) |
|---|---|---|---|---|
| 1. Foundation & scaffolding | 8 | 4 | 3 | 15 |
| 2. Auth & multi-tenancy | 5 | 3 | 3 | 11 |
| 3. Expression engine + schema runtime | 18 | — | 9 | 27 |
| 4. Form/binding engine + resolver/validator | 12 | — | 6 | 18 |
| 5. PDF engine (extract, fill, merge, export) | 13 | — | 4 | 17 |
| 6. Server API — domain CRUD (~45 routes) | 28 | — | 8 | 36 |
| 7. Client app (tenant UI) | — | 58 | 10 | 68 |
| 8. Root app (admin UI) | — | 36 | 5 | 41 |
| 9. Portal app (intake + widget) | — | 18 | 3 | 21 |
| 10. Cross-cutting (telemetry, seed, docs) | 8 | 3 | 2 | 13 |
| Subtotal | 92 | 122 | 53 | 267 |
| Domain learning / integration buffer (~15%) | 40 | |||
| Grand total | ~307 dev days |
That's roughly 15–16 person-months. With a typical squad of 1 backend lead + 1 backend + 2 frontend = 4 devs, expect ~4 calendar months of full-time work, plus a small QA tail.
Range under realistic risk: 270 (best case) — 360 (worst case) dev days.
1. Foundation & scaffolding — 15 days
| Task | Days |
|---|---|
| .NET solution layout (API, Core, Infrastructure, Shared.Contracts), CI pipeline (GitHub Actions or Azure DevOps), Dockerfile | 3 |
| EF Core context, conventions, migrations baseline mirroring 7 existing SQL files | 5 |
Angular workspace with 3 apps (client, root, portal) + shared libs (shared-ui, shared-data, shared-engine-types) |
3 |
| Common HTTP interceptors (auth, error, retry), shared DTOs, OpenAPI client generation (NSwag or Refitter) | 2 |
| Local dev orchestration: docker-compose (Postgres + Aspire), seed scripts, README | 2 |
Risks: Multi-app Angular workspace boilerplate sometimes drags. NSwag/OpenAPI codegen friction with .NET minimal APIs.
2. Auth & multi-tenancy — 11 days
| Task | BE | FE | Tests |
|---|---|---|---|
JWT access + refresh, password hashing (BCrypt), /auth/sign-in, sign-up, sign-out, refresh, me |
4 | — | 2 |
Tenant scoping middleware (every query filtered by tenantId), require-root policy |
1 | — | 1 |
Angular AuthService, route guards, token refresh interceptor across all 3 apps |
— | 3 | — |
3. Expression engine + schema runtime — 27 days (the IP)
This is the densest, most subtle code in the codebase. Reference: packages/core/src/expressions/* (parser, evaluator, 22 Excel-style functions, type coercion) and packages/core/src/schema/* (flatten, validate).
| Task | BE | Tests |
|---|---|---|
Tokenizer + parser for expression DSL ($child.field, $.field, array [] selectors) |
5 | 2 |
| Evaluator + type coercion + null semantics | 4 | 2 |
| 22 built-in functions (logic, math, text, date, comparison) | 3 | 2 |
| Schema model (entries, groups, namespaces, imports), flatten + validate | 4 | 2 |
| Schema registry, defaults, dependency loading | 2 | 1 |
Risks: Subtle behavior in the existing parser (operator precedence, error tolerance, array semantics) is easy to get wrong. The receiving team must port the test suite faithfully or write a contract-equivalence test against the TS engine.
4. Form/binding engine + resolver/validator — 18 days
| Task | BE | Tests |
|---|---|---|
| Form composition (recursive children with keys/aliases), bindings, conditions | 4 | 2 |
| Binding resolver (key → form fields), source-of-truth merge across entries | 3 | 1 |
| Condition checker (gates fields/forms based on expressions) | 2 | 1 |
| Validator (severity-graded validations) and readiness scoring | 2 | 1 |
| Cycle detection across schema/form references | 1 | 1 |
5. PDF engine — 17 days
| Task | BE | Tests |
|---|---|---|
AcroForm field extraction (key, type, page, rect, label) — replaces pdf-fields.ts |
4 | 1 |
PDF filler (fill by key, type-aware: text, checkbox, radio, dropdown) — replaces pdf-filler.ts using iText/PdfSharp |
5 | 2 |
| Merged-PDF export and ZIP-of-PDFs export, font handling, value formatting | 3 | 1 |
| Preview rendering pipeline (server-side fill + client-side viewer) | 1 | — |
Risks: iText AGPL licensing forces commercial license or migration to QuestPDF/PdfSharpCore — confirm before starting. AcroField filling parity with pdf-lib is the highest single technical risk: appearance streams, field flattening, and checkbox/radio export values need careful parity tests against the TS output.
6. Server API (~45 routes) — 36 days
Existing routes total ~4,400 LOC. Grouped:
| Route group | BE | Tests |
|---|---|---|
| Schemas (CRUD, import, clone, entry validation, dependants) | 4 | 1 |
| Forms (CRUD, tree, child composition, field-map, usage) | 5 | 1 |
Cases base + entries, filings, contacts, tasks, notes, events, billing, attachments, history, readiness, validations, intake-invites, forms, activity |
9 | 2 |
| Top-level: contacts, events, billing, data-sources, marketplace, files (preview/export) | 4 | 1 |
Root admin: tenants, schemas, forms (incl. documents.ts 1,359 LOC — the biggest single file), data-sources |
5 | 2 |
| Portal/intake routes + intake service + rate-limit | 3 | 1 |
Risks: routes/root/documents.ts is unusually large (1,359 LOC) and likely contains form-management orchestration that maps poorly onto vanilla CRUD. Plan a deeper read before sizing.
7. Client app (tenant UI) — 68 days
18,145 LOC, ~30 pages/components. The Data tab and component library dominate.
| Area | FE | Tests |
|---|---|---|
| Layout shell: sidebar (collapsible), top bar, theme, command palette, new-case dialog | 6 | 1 |
| Dashboard (stats, quick actions, activity feed, deadlines) | 3 | — |
| Cases list + filters | 2 | — |
| Case detail shell + 11-tab transforming sidebar + nested routes | 3 | 1 |
| Data tab (split-pane: datasheet form left, live PDF preview right, validation panel, drift, pending edits, sections nav, multiple views) | 12 | 2 |
| Filings tab + new-filing modal + detail rail | 5 | 1 |
| Documents (checklist + uploads) | 2 | — |
| Tasks, Notes | 3 | — |
| Billing tab (fees + payments + time) | 3 | — |
| Case calendar | 2 | — |
| History | 1 | — |
| E-Filing pre-flight | 2 | — |
| Client portal tab (manage shareable link/permissions) | 2 | — |
| Forms library (per-case + tenant-wide) | 3 | — |
| Settings (8 sub-pages) | 4 | — |
| Help (30 articles, search, sections filter) | 2 | — |
| Contacts directory (filters, roles) | 2 | — |
| Calendar firm-wide (week/month, upcoming sidebar) | 4 | 1 |
| Billing firm-wide (receivable/collected/overdue/outstanding) | 2 | — |
| UI primitives library (~25 components: badge, button, card, dialog, dropdown, data-grid, master-detail, page-wrapper, view-toggle, dossier badges/rings/chips/popovers) | 5 | — |
| Cypress/Playwright e2e — golden paths (login, create case, fill form, file) | — | 5 |
Risks: Migrating ~25 shadcn-style React components to Angular Material/PrimeNG never lands one-to-one — expect re-design on at least 5 of them. The dossier/* design system pieces (provenance popover, ring meter, schema-key chip, status dot) are domain-specific and worth keeping visually faithful — that demands custom Angular components, not Material substitutes.
8. Root app (admin UI) — 41 days
12,573 LOC, ~30 pages, dominated by form-editing tooling.
| Area | FE | Tests |
|---|---|---|
| Layout shell, auth, breadcrumbs, master-detail layout | 3 | — |
| Dashboard + stats | 2 | — |
| Schemas: list + 7 detail pages (overview, raw, entries, imports, dependants, ui-config, layout) | 7 | 1 |
| Forms: list + 13 detail pages including bindings, field-map, fields, parents, children, schema, stats, test runner, tree visualizer, upload + PDF field overlay, validations | 14 | 2 |
| Data sources: list + mappings + overview | 3 | — |
| Tenants page | 2 | — |
Specialist components: expression-input, pdf-field-overlay, graph-canvas, tag-input, dropzone, data-import, results-grid |
5 | 1 |
| Cypress smoke flows | — | 1 |
Risks: pdf-field-overlay (drag-to-rect over PDF page) and graph-canvas (binding/dependency graph) are non-trivial and have no off-the-shelf Angular equivalent — budget protected here, may still slip.
9. Portal app (intake + widget) — 21 days
5,391 LOC. Five intake modes (wizard, grid, spread, chat, voice), embeddable widget, branded shell.
| Area | FE | Tests |
|---|---|---|
| Branded shell, theme resolution, intake landing | 3 | — |
| Wizard mode (multi-step + review) — primary mode | 4 | 1 |
| Grid mode | 2 | — |
| Spread mode (spreadsheet-like) | 2 | — |
| Chat mode (engine + conversational UI) | 3 | 1 |
| Voice mode | 2 | — |
| Embeddable widget (bubble + entry + panel; cross-origin loader) | 3 | 1 |
| Login, dashboard, complete pages | 2 | — |
| Intake session hook, fallback config/schema/token | 1 | — |
Risks: Voice mode depends on browser SpeechRecognition — Angular wrapper needs revisiting. Embeddable widget (<script> tag drop-in on a tenant's site) has packaging implications: build as a single self-contained bundle, not as part of the Angular workspace's normal build.
10. Cross-cutting — 13 days
| Task | BE | FE | Tests |
|---|---|---|---|
| OpenTelemetry .NET wiring (Fastify-equivalent auto-instrumentation), Aspire export | 3 | — | — |
| Structured logging via Serilog, correlation IDs | 1 | — | — |
| Seed scripts: 2 schemas + 69 leaf forms + 19 composite forms + 4 intake configs ingested into the new DB | 3 | — | 1 |
| Auto-history logging on case mutations (existing TS pattern) ported to EF Core interceptors | 1 | — | 1 |
| Theme sync (light/dark) across all 3 Angular apps + portal preview | — | 2 | — |
| Internal docs (deployment, migration runbook, API quickstart) | — | 1 | — |
| Bug-fix / integration buffer |
What's intentionally excluded
- Marketing site (
website-public,website-private) — static HTML, reusable as-is. - Form processing pipeline (
domain_tools/scripts, prompts) — not a customer-facing app; can stay in TS or be ported later. - Domain content — schemas, form JSONs, data-source recipes are reused, not rewritten.
- Production hardening beyond parity: load testing, hardening WAF rules, SOC2 controls, formal pen test, accessibility audit. Add ~20–30 days if any of these are in scope.
- New features: this estimate is parity-only. Anything from
docs/todo/todo.md(e-filing integration, PACER sync, payment processing, etc.) is on top.
Risk factors (in order of impact)
- PDF AcroForm parity with
pdf-lib— appearance streams, checkbox export values, font fallbacks. Mitigation: byte-level diff harness against existing engine on day 1. - Expression engine semantics — port the entire test suite from
packages/core/src/expressions/__tests__/and run the new engine against it as a contract test before writing any callers. - PDF library licensing — iText AGPL forces a commercial license or alternative. Decide before sprint 1.
routes/root/documents.ts(1,359 LOC) — single largest server file, likely orchestration-heavy. Read before sizing this sprint.- Component-library mismatch — Angular Material aesthetic differs from the Chambers/cream design language. Budget for custom components on the dossier-design pieces or accept visual drift.
- Domain learning curve — bankruptcy court forms, schedules, means test, exemptions. Pair the backend lead with the original team for the first 2 weeks; the 15% buffer assumes this happens.
How to refine this estimate
Spend 2 dev days reading these specific files before locking the number — they account for most of the remaining uncertainty:
packages/core/src/expressions/parser.ts+evaluator.ts(engine subtlety)packages/server/src/lib/pdf-filler.ts+pdf-fields.ts(PDF parity)packages/server/src/routes/root/documents.ts(1,359 LOC outlier)packages/client/src/pages/case-tabs/data/*(the most complex tab)packages/root/src/pages/form-*(13 form-admin pages)
docs/estimates.md