CareMatch — a full SaaS: marketplace core, 3 web apps, billing, ops, compliance - #1
Open
maxmurphySF wants to merge 13 commits into
Open
CareMatch — a full SaaS: marketplace core, 3 web apps, billing, ops, compliance#1maxmurphySF wants to merge 13 commits into
maxmurphySF wants to merge 13 commits into
Conversation
Bootstraps the CareMatch platform from the product & build blueprint, implementing the defensible core the blueprint calls out as the moat and as independently testable: the Matching Engine and Budget Optimizer (§4). - domain: strictly-ordered service tiers, ADL/IADL task catalog with minimum-tier mapping and safety-critical flags, core entity types, geo - matching: rules-based v1 engine — hard filters then transparent weighted-linear scoring with plain-language "why this match" reasons; weights are config, not code - optimizer: the signature "here is your care plan" feature — Full coverage / Smart mix / Essentials plans priced against real providers, with lower-tier hours billed at that tier's market rate - api: dependency-free POST /match HTTP surface with defensive input validation - sample data + console demo, 21 tests (Node built-in runner), strict tsconfig, docs (blueprint, architecture, data model) Runs on Node >=22.6 with no runtime dependencies. Deferred components (identity/verification, payments, booking, PII vault, client apps) are documented in docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
Extends the core downstream of matching, turning an optimized plan into approved, scheduled, verifiable care. - domain/booking: CarePlan, Visit, VisitEvent stream, visit-time verification types, and the transition table - booking/carePlanService: promote an optimizer option into a versioned CarePlan; senior-only approval activates it (senior sovereignty) - booking/schedulingService: expand an active plan into concrete visits, co-locating same-day/same-provider blocks and spreading weekly hours - booking/visitLifecycle: strict VisitEvent state machine; check-in is a hard gate requiring both selfie match and GPS geofence (blueprint stage 5), and a "not good" review flags a same-day follow-up - booking/repository + api/bookingService: in-memory store and orchestration wiring the tested services together - api/server: care-plan and visit REST endpoints with typed error codes (409 invalid transition, 422 failed verification); bookingValidate guards untrusted input - optimizer: plan blocks now carry their scheduled days, enabling the weekly calendar and scheduling - bookingDemo + 24 new tests (care plan, scheduling, visit lifecycle, full HTTP flow); 45 tests total, strict typecheck clean Also updates README and docs to mark booking/scheduling/visit verification as built. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
Implements the blueprint's §4.4 Funding Navigator as a content + decision -tree screener (no payer integrations), which the optimizer re-runs against an adjusted effective budget. - domain/funding: screener input, source results, indicative program parameters (VA MAPR, Medicaid HCBS limits, MA supplemental) as config, and a standing "estimate, not a determination" disclaimer - funding/fundingNavigator: rates VA Aid & Attendance, Medicaid HCBS, LTC insurance and Medicare Advantage as likely/possible/unlikely with an estimated monthly benefit and concrete next steps; only likely sources count toward the budget; withAdjustedBudget raises the ceiling - api/fundingService: assessAndReplan returns the care plans at both the out-of-pocket and funded budgets so the funding's effect is visible in real plans - api/server: POST /funding/plan endpoint; screener input validation - 10 new tests (55 total); README and architecture docs updated Example: a $1,500 firm budget that only affords "essentials" becomes a $5,600 effective budget affording "full coverage" once ~$4,100/mo of likely VA + Medicaid funding is applied. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
Turns visit lifecycle transitions into audience-targeted notifications, connecting the event stream to the family reassurance strip (§5.2) and the incident-escalation path (§5.1). - domain/notifications: Notification model with audience (senior/family/ ops), priority (info/reassurance/urgent), channels and status - notifications/notificationRules: pure mapping from a visit's latest event to notifications — en-route reassurance, the verified-arrival trust alert, checkout, cancellation and no-show; a "not good" review escalates to an urgent ops phone call; a failed identity check raises an urgent ops alert even though it is not a visit event - notifications/dispatcher: channel-agnostic sink abstraction with console, null, and recording sinks; dispatch stamps delivery status - repository: persist and list notifications per seeker - api/bookingService: fan out and persist notifications on each visit transition (and on failed verification); listNotifications - api/server: GET /seekers/:id/notifications; console sink wired for the running server - bookingDemo shows notifications firing per step; 8 new tests (63 total) - README and architecture docs updated Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
Turns the in-memory core into a running application with a real database and real authentication — no in-memory shortcuts, no runtime mock data. Database (src/db): - A small Db interface satisfied by both PGlite (real Postgres in WASM, in-process, used locally and in tests) and node-postgres in production against the same schema (DATABASE_URL). - Idempotent migrations creating users, providers, care_plans, visits and notifications; aggregate value objects stored as JSONB, ids/status/dates as indexed columns. - Async DB-backed repositories replace the in-memory store; provider data is seeded into the DB and read from it (never a runtime array). Auth (src/auth): - scrypt password hashing and HS256 JWTs, both Node built-ins (the DB driver is the only new runtime dependency). - register/login/authenticate against the users table; login is timing-safe against account enumeration. API gateway (src/api): - Every route except /health and /auth/* requires a bearer token. - Authorization enforces senior sovereignty: only the senior (or ops) approves a plan or schedules visits; only the assigned provider runs a visit's provider actions; only the senior reviews it. seekerId is taken from the token, never the body. - createApp wires DB + services; main.ts is the entrypoint (migrate + seed + serve); db:setup CLI; .env.example + src/config.ts. Tests now run the whole stack over a fresh in-memory Postgres with real auth (65 total, incl. a full authenticated booking flow and cross-request persistence). README and architecture docs updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
Encodes the sovereignty principle (blueprint §5.2): a family account has no access to a senior until the senior grants it, at a granular level. - domain/consent: ordered levels view < book < financial, with a levelPermits capability check - db: 0002_consents migration (one active grant per senior+grantee) and a consent repository (grant/upsert, revoke, findActive, lists) - auth/consentService: grant (family accounts only, not self), revoke by the granting senior, and permits() used by authorization - api: POST/GET/DELETE /consents; authorization is now consent-aware — family reads require 'view', propose/schedule require 'book', the funding navigator requires 'financial'; family can never approve a plan (senior/ops only); seekerId still comes from the token/grant, not the body - 9 new tests (73 total) covering the full grant → act → revoke flow and the sovereignty guarantees; README + architecture + data-model updated Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
Turns the backend into an actual product. An accessibility-first PWA (blueprint §5.1) is served by the same Node server at /app, driving the real authenticated API through the full senior loop. Backend: - static file handler (src/api/static.ts) serving web/ under / and /app, with content types and path-traversal guards, ahead of the API routes - GET /seekers/:id/visits so the home screen can show the next visit (repo.listVisitsForSeeker + BookingService.listSeekerVisits), reusing the consent-aware view authorization Senior app (web/, no build step, no framework, zero new runtime deps): - design system (styles.css): 20px base, >=56px targets, >=7:1 contrast, visible focus, no gesture-only/timed interactions, reduced-motion, and a persistent "Call us" button in the same corner everywhere - app.js: dependency-free router + API client (JWT) and screens — login/ register, home (next-visit card + verification badge + "my people"), one-question-per-screen booking, match card-stack, and the "here is your care plan" confirm that proposes + approves + schedules 4 weeks - the verification badge opens a "how we verify every helper" dialog — trust made visible Verified with a committed Playwright e2e (scripts/e2eSenior.ts, npm run test:e2e): register -> book -> matches -> confirm -> the visit appears on home, with zero console errors. README + architecture updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
The second client (blueprint §5.2) and the payoff for the consent model: a family member monitors a senior's care, scoped strictly to what the senior granted. Backend: - consent grant-by-email (the invite flow) and enriched GET /consents that names the counterpart (senior/grantee) for dashboards - family registration no longer needs a seniorId up front (access is granted later by the senior) - GET /seekers/:id/care-plans (listCarePlansForSeeker); /visits already added — both behind the consent-aware view check - static handler generalised to serve /family alongside /app Family dashboard (web/family/, responsive, no build step): - left-rail nav; only shows seniors who granted access, at their level - Overview reassurance strip (last visit + verified-arrival time, next visit, recent activity) — "is Mom okay and is this working" - Visits list + detail with the VisitEvent timeline, verification event, structured note and the senior's review - Care plan (the shared weekly artifact) and Budget vs. plan Senior app: a "Share with family" screen grants consent by email + level. Verified with a cross-app Playwright e2e (scripts/e2eFamily.ts, npm run test:e2e:family): family account -> senior books + shares -> family dashboard shows the visit and plan, zero console errors. One new backend test locks the invite-by-email + seeker-reads contract (74 total). README + architecture updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
The monetization core that makes this a SaaS, over a payment-gateway boundary so the in-house ledger swaps for Stripe Connect in production. - domain/billing: cents-based money, subscription plans, invoices with line items, and a take-rate settlement helper that reconciles the platform fee to the provider payouts exactly - payments/gateway: PaymentGateway interface + LedgerPaymentGateway (deterministic in-house processor; charge/refund/payout) - db 0003_billing: subscription_plans, subscriptions, invoices, invoice_line_items, payments, payouts; plans seeded (Free, Plus) - billing/billingService: subscribe (charges paid plans), idempotent invoice generation from a care plan's blocks, pay-invoice that charges the senior and settles per-provider payouts with the take-rate deducted - api: /billing routes (plans, subscription, invoices, pay, payouts) with financial-level authorization; BillingError -> 402 - 7 new tests (81 total): settlement math + full API flow (subscribe -> generate -> pay -> payout) incl. idempotency and access Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
Makes the service production-shaped, not just functional. - observability/logger: structured JSON logs (level, time, fields); every request logged with method/path/status/duration/request-id - observability/metrics: in-process counters + /metrics Prometheus text - api/rateLimit: fixed-window per-client limiter (tighter on /auth) -> 429 with Retry-After - api/httpMiddleware: security headers (CSP, nosniff, DENY, Referrer- Policy, Permissions-Policy, HSTS in prod), configurable CORS allowlist, and X-Request-Id echo; client key from XFF/socket - server: /health, /ready (DB-checked), /metrics; middleware applied to every request; graceful shutdown in main via the logger - CSP-safe: senior app skip-link moved from inline JS to CSS - CI (.github/workflows/ci.yml: typecheck + test on Node 22), Dockerfile (+ HEALTHCHECK, .dockerignore), docs/openapi.yaml (OpenAPI 3.1) - config + .env.example: CORS_ORIGINS, RATE_LIMIT_*, LOG_LEVEL - 5 new tests (86 total); both browser e2e suites still pass under CSP Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
Compliance and operations: an append-only audit trail and an ops console to run the platform. - domain/audit + 0004_audit table; auditRepository (append/list) and a best-effort AuditService that never fails the user's request - server records sensitive actions with actor + request id: register, login, consent grant/revoke, plan approve, subscribe, invoice pay, and failed visit verifications - admin/AdminService: platform metrics (users, providers, plan/visit status, GMV, platform revenue, payouts), the incident queue (bad reviews + failed identity checks), and user lookup - /admin API (ops only) + web/console ops dashboard at /console - security: ops accounts can no longer be self-registered; a bootstrap ops account is provisioned from ADMIN_* config (dev defaults provided) - 6 new tests (92 total): ops guard, no-self-register, metrics, audit trail contents, and the verification incident path Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
Rounds out the SaaS: self-service account management and the CCPA/GDPR data-subject rights. - 0005 migration: users.email_verified + auth_tokens (hashed, single-use, expiring); tokenRepository issues/consumes tokens (only the SHA-256 hash is stored, raw token travels by email) - email/EmailService over an EmailTransport boundary (console locally; swap SES/SendGrid in prod) for reset & verification links - account/AccountService: password reset (request never reveals whether an email exists; confirm rotates the password and invalidates other tokens), email verification, data export (all of a user's records), and account deletion (erases PII + health-adjacent care data, de-identifies retained invoices/payments) - routes: public /auth/password-reset/* and /auth/verify-email/confirm; authed /account/verify-email/request, GET /account/export, DELETE /account - config + .env.example: APP_URL, ADMIN_EMAIL/PASSWORD - 5 new tests (97 total): reset happy-path + no-enumeration + single-use, verification, export, and deletion; both browser e2e suites still pass Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
Makes the headline moat real: a provider's bookability and tier are derived from verification records, never self-declared. - domain/verification: 4-stage pipeline (identity, background, credential, monitoring) with deriveStatus/deriveTier and a customer-facing badge summary; visit-time confirmation stays on the visit event stream - 0006 migration: providers.status + requested_tier; verification_records - verificationRepository + VerificationService: recording a stage result re-derives status (active needs identity+background; any adverse background/monitoring result suspends) and verified_tier = min(requested tier, credential ceiling) - matching now lists only active providers, so a suspended provider drops out of results immediately - security: provider registration can only claim a pre-created, UNclaimed provider profile (no more arbitrary providerId self-claim) - endpoints: GET /providers/:id/verification (any signed-in user) and ops-only POST /admin/providers/:id/verification (stands in for a Persona/Checkr webhook) - seed gives sample providers a real, dated verification history; the senior app's "Verified helper" badge now shows that real data - 8 new tests (104 total): status/tier derivation, seeded badge, adverse -> suspended -> unmatched, ops-only recording, and claim-once semantics Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Builds CareMatch from the blueprint into a running SaaS: the verified, budget-aware in-home care marketplace, plus the company machinery around it — auth, a real database, billing, three web front-ends, an ops console, observability, CI/CD, provider verification, and data-subject compliance. No in-memory shortcuts, no runtime mock data, no vendor stubs on the critical path (external processors sit behind swappable boundaries).
Node ≥ 22.6, TypeScript run directly via type-stripping. 104 tests + two browser end-to-ends, strict typecheck, CI, Docker. 13 commits.
Product core (blueprint §3–§5)
Clients (three web apps, served by the same server)
/app) — accessibility-first (WCAG 2.2 AA floor): booking, match card-stack, "here is your care plan," and a "Verified helper" badge showing each provider's real, dated verification stages./family) — reassurance strip, visit timeline, shared care plan, budget — scoped to senior-granted consent./console) — platform metrics, incident queue, audit trail.SaaS platform
Dbinterface satisfied by PGlite (local/tests) and node-postgres (prod) against identical migrations.PaymentGatewayboundary (in-house ledger → Stripe Connect)./health/ready/metrics, graceful shutdown.docs/openapi.yaml.Verification
npm run typecheckclean (strict);npm test→ 104 pass over a real in-memory Postgres with real auth.npm run test:e2e/test:e2e:familydrive the senior flow and the senior→family handoff in a real browser under the production CSP, zero console errors.Deliberately deferred
Provider native app; live vendor webhooks for identity/verification (Persona/Checkr) and live payments (Stripe); messaging (Twilio/Sendbird); the tokenized PII vault. Each sits behind an interface this PR defines. See
docs/ARCHITECTURE.mdfor the built-vs-deferred component map.🤖 Generated with Claude Code
https://claude.ai/code/session_01QN287Pq3hEgDNJmDzKnH8d