- #60 — ADR-003 (docs): Captured 2026-05-21 TWH consolidation decision in
docs/DECISIONS.md; journal entry scoped to actual shipped work and dated correctly. - #61 — workwell.os redirect (infra): Minimal nginx:1.27-alpine container issuing
301fromworkwell.os.mieweb.org→twh.os.mieweb.org. Workflow:deploy-workwell-redirect-mieweb.yml(manual dispatch). Removed misleading "reuse for API hostname" suggestion. - #62 — CQL code-filter tightening: Applied inline code-filter pattern (already in use by TB/HAZWOPER) to all 6 previously unfiltered measures (audiogram, flu, hypertension, diabetes_hba1c, obesity_bmi, cholesterol_ldl). No synthetic-data changes needed.
- #63 — CMS125v14 + CMS122v14 promoted to Active: Breast Cancer Screening (820-day mammogram window) and Diabetes HbA1c Poor Control (numeric Observation-based). Both seeded as Active v1.0;
observationValuefield added toExamConfigfor lab-value CQL. Catalog now has 10 runnable measures. - #64 — Compliance trend chart with per-bucket breakdown: Extended
ProgramTrendPointwith 5 bucket counts; added rechartsAreaChartwith per-bucket dashed Area series (% of total) + Legend replacing the hand-rolled sparkline. - #65 — Case code evidence explorer: New
GET /api/measures/versions/{id}/value-setsendpoint; case detail now shows color-coded define chips (green bool, blue date, orange positive numeric, amber Outcome Status) and a Declared value sets panel. - #66 — SQL analogy panel in CQL tab: Collapsed-by-default panel deriving illustrative SQL from
spec_jsonfields. Regex fix: compliance window parser now requires explicit "N days" pattern (not just any digit) to prevent misreading "Series of 3 doses over 6 months" as 3 days.
- 7 branches merged to main and deleted remotely + locally.
docs/MEASURES.mdcatalog summary updated (10 runnable, 47 Draft, 60 total).- CLAUDE.md Current Focus updated to reflect 10 runnable measures and new features.
- ADR-003: Captured the 2026-05-21 TWH single-instance consolidation decision in
docs/DECISIONS.mdas a numbered ADR. Quoted the JOURNAL rationale verbatim; documented that the eCQM seeding path and*_ECQMsecrets are retained as restore-later capability; noted theworkwell.osredirect as a follow-up (seeinfra/redirect/).
- Docs-only PR; no backend or frontend changes.
Brought the living docs into agreement with the current codebase and the single MIE TWH deployment, removing facts that had drifted since the 2026-05-21 focus snapshot. No code changes.
- CLAUDE.md / AGENTS.md: Frontend
Next.js 14+→Next.js 16 + React 19; AISpring AI (Anthropic)→Spring AI (OpenAI starter, spring-ai-openai-spring-boot-starter)(matchesapplication.ymlgpt-5.4-nano/gpt-4o-mini); infraFly.io + Vercel + Neon→ MIE Create-a-Container + Neon (Fly + Vercel preview decommissioned); SendGrid env var corrected toWORKWELL_EMAIL_SENDGRID_API_KEY. CLAUDE.md Current Focus re-dated 2026-06-08 (Sprint 7 closed, Sprint 8 scoped-run parity, CI 3.8× PR #57, MIE v1 deploy fix PRs #55/#56, catalog 60/49, run scopes) and gained a Build & verify section. AGENTS.md reframed from "sprint-based build phase" to post-merge polish; "active work queue" pointer updated. - README.md: Status now notes Sprint 8 parity, CI sharding, and the MIE v1 deploy migration; Production surfaces reduced to the live MIE TWH frontend/backend with an explicit note that the Vercel + Fly public-preview stack is decommissioned.
- docs/DEPLOY.md: Rewritten so MIE Create-a-Container is the sole current deployment; all Fly.io/Vercel provisioning, rollback, and troubleshooting moved into a clearly-labeled decommissioned/historical appendix. Env-var names retained; the
Wherecolumn relabeled Backend/Frontend; added the GitHub-secret → container-env mapping note and the v1 manager-API details. - docs/sprints/README.md: Rollout-status line updated to 2026-06-08; index marked historical (no active sprint queue).
- CHANGELOG.md:
[Unreleased]now records Sprint 8 parity, the CI sharding speedup, the MIE v1 deploy fix, and the deployment consolidation. - .env.example: Fly/Vercel framing replaced with MIE context; the stale
WORKWELL_CORS_ALLOWED_ORIGINSvalue (frontend-seven-eta-24.vercel.app) corrected tohttps://twh.os.mieweb.org. - Usage guides (DEMO_RUNBOOK, WALKTHROUGH_GUIDE, MCP): dead
*.vercel.app/*.fly.devhostnames swapped totwh.os.mieweb.org/twh-api.os.mieweb.org, with stack-note banners flagging that embedded example IDs predate the MIE instance.
- Measure catalog: 60 total (4 OSHA active CQL, 3 OSHA catalog, 4 HEDIS active CQL, 49 CMS eCQM Draft) — confirmed against
MeasureServiceand MEASURES.md/DEPLOY.md. - AI provider: OpenAI via
spring-ai-openai-spring-boot-starter(build.gradle.kts), modelsgpt-5.4-nano/gpt-4o-mini(application.yml). - Frontend: Next.js 16.2.4 / React 19.2.4 (
frontend/package.json). - Deployment: only
deploy-twh-mieweb.ymlis active; Fly.io + Vercel preview decommissioned (confirmed with owner).
docs/archive/**, docs/new instructions/**, docs/superpowers/**, the per-sprint SPRINT_0x_* specs, the MIE migration-process docs (DEPLOY_OS_MIEWEB.md, ECQM_TWH_DEPLOYMENT_PLAN.md), QA reports (LIVE_APP_QA_REPORT.md), and docs/POST_MERGE_STATUS.md (a dated 2026-05-11 snapshot already annotated with later resolutions). Old JOURNAL entries that mention Anthropic/Fly are point-in-time records and were left intact.
- Root cause of the ~44 min CI: the backend
./gradlew teststep dominated wall-clock (frontend ~50s, E2E manual). Per-class timing showed a few integration tests re-ran a full-population CQL evaluation (~70s) in@BeforeEach, once per test method.EvidenceAccessIntegrationTestran it 14x (~1022s); converted to one shared run via@BeforeAll+@TestInstance(PER_CLASS)— its tests are read-only on the population and filter audit by their own upload id → ~71s.CaseFlowRerunIntegrationTestran it 5x (~422s); each test targets a distinct outcome-type case with non-overlapping mutations, so one shared run suffices → ~146s.ScopedRunIntegrationTest,CaseUpsertIntegrationTest,Major1PopulationIntegrationTestleft as-is — their reruns are the behavior under test (idempotency, scoped-run parity, empty-table historical seed) and need per-test isolation.
.github/workflows/ci.yml: backend job is now an 8-way matrix; only shard 0 writes the Gradle cache; added a per-class timing diagnostic step.backend/build.gradle.kts:Test.include(Spec<FileTreeElement>)assigns each test class to a shard by stable path hash (TEST_SHARD_TOTAL/TEST_SHARD_INDEX); CI forks 4-wide with a 1.5g per-fork heap cap;GRADLE_TEST_FORKSoverride. Local runs (no shard env) unchanged.
- Wall-clock 44 min → 11m30s (~3.8x); CI green on
main. - All 239 backend tests pass; per-shard counts sum to 239 (no tests dropped).
- Remaining ceiling is
ScopedRunIntegrationTest(~635s); a single class runs in one fork, so further gains require splitting it (deferred). - Shipped in PR #57.
The MIE Create-a-Container manager API changed under us; the deploy-twh-mieweb backend-container job failed three times.
.github/scripts/deploy-mieweb-container.sh:- API base normalized to
<manager-origin>/api/v1— the origin now serves the SPA web UI,/apiserves Swagger, and the JSON REST API is at/api/v1(PR #55). - Migrated to the v1 contract (PR #56): responses are wrapped in a
{"data": ...}envelope (.data[],.data.externalDomains[]); create body usestemplate(nottemplate_name) withservicesas an array of flat objects; job polling reads.data.status(success value is"success"); create-response job id from.data.jobId; container URL from.data[].httpEntries[0].externalUrl. - Shapes verified against the live manager API and the manager's own SPA client.
- API base normalized to
- Post-merge
deploy-twh-miewebrun green end-to-end (build + deploy backend + deploy frontend). - Live:
GET https://twh-api.os.mieweb.org/actuator/health→200 {"status":"UP"}; frontend →200.
- Backend manual-run parity:
AllProgramsRunService.run(...)now keepsCASEsynchronous and routesALL_PROGRAMS,MEASURE,SITE, andEMPLOYEEthrough the async run-job path used by/api/runs/manual.AllProgramsRunService.rerunSameScope(...)now supports persistedSITEandEMPLOYEEruns by replayingrequested_scope_json.siteandrequested_scope_json.employeeExternalId.- Non-case reruns now reuse the same async contract as manual runs, so the operator-facing rerun flow is consistent across all supported non-case scopes.
- Persisted rerun-scope hydration:
RunPersistenceService.loadRerunScope(...)now restoressiteandemployeeExternalIdfromrequested_scope_json, withsitefalling back to the legacyruns.sitecolumn when present.
- Runs UI parity:
/runsmanual scope selector now exposesSITEandEMPLOYEE.- Added required free-text inputs for
siteandemployeeExternalId. - Scope filter dropdown and rerun eligibility now include
SITEandEMPLOYEE. - Scope labels now render
SiteandEmployeeconsistently in tables and details.
- Docs alignment:
README.mdanddocs/ARCHITECTURE.mdnow describe the full supported scoped-run surface.docs/POST_MERGE_STATUS.mdhistorical deferred-scope note is now explicitly marked as resolved.
frontend:npm run lintpassed with one existing warning intest/mocks/next-font.ts.frontend:npm run buildpassed.backend:.\gradlew.bat --no-daemon test --tests com.workwell.web.EvalControllerTestpassed.backend: targetedScopedRunIntegrationTestmethods passed individually for:measureScopePersistsOnlySelectedMeasureAndAuditActorsiteScopeQueuesAndPersistsOnlyRequestedSiteemployeeScopeQueuesAndPersistsOnlyRequestedEmployeesiteScopeRerunUsesPersistedRequestedSiteemployeeScopeRerunUsesPersistedRequestedEmployee
backend:ScopedRunFailureIntegrationTest.measureScopeFailurePersistsMissingDataAndPartialFailurepassed using--no-daemon --no-configuration-cachewith a uniquejava.io.tmpdirto avoid a local Gradle temp-file race in this Windows + OneDrive environment.
- Reworked
README.mdfor a production-grade repository front page:- added CI/deploy/license/runtime badges
- tightened project positioning and status summary
- refreshed stack/runtime sections
- added explicit verification command block
- added community and governance links
- Added repository community health and contribution standards:
CONTRIBUTING.mdSECURITY.mdCODE_OF_CONDUCT.mdSUPPORT.md.github/pull_request_template.md.github/ISSUE_TEMPLATE/bug_report.md.github/ISSUE_TEMPLATE/feature_request.md.github/ISSUE_TEMPLATE/config.yml(blank issues disabled; security redirect).github/CODEOWNERS
- Added project hygiene files:
CHANGELOG.md(Keep a Changelog format).editorconfig
- Refreshed package metadata for discoverability and tooling:
frontend/package.jsonupdated with canonical package name, description, repository metadata, homepage, keywordse2e/package.jsonupdated with description/repository/homepage
- Updated About description.
- Updated homepage URL to
https://twh.os.mieweb.org. - Added curated topics: TWH, occupational health, compliance, CQL/FHIR, Spring Boot, Next.js, MCP, OpenAI, and related tags.
frontend:npm run lintpassed with one existing warning intest/mocks/next-font.ts(no new lint errors introduced).
- Promoted Sprint 7.2-7.5 from the sprint feature branch chain into
mainvia merge commit95796d7. - Closed remaining Sprint 7 issues:
#48,#49,#50,#51(with completion notes linked to merged implementation). - Removed stale sprint branches locally and remotely; branch state normalized to
mainonly on both local and remote.
README.mdupdated to reflect:- TWH framing in the project summary
- primary demo surfaces (
twh.os.mieweb.org/twh-api.os.mieweb.org) - Sprint completion status through Sprint 7
docs/sprints/README.mdupdated with implementation status across Sprints 0-7.docs/sprints/SPRINT_07_overdelivery_features.mdacceptance and DoD checklists marked complete for 7.1-7.5.docs/DEPLOY.mdrefreshed for current platform reality:- stack header updated to MIE + Neon + OpenAI
- CMS catalog seed count corrected to 49
- legacy/public preview section retitled
- legacy references migrated from Anthropic secret names to OpenAI equivalents
docs/ARCHITECTURE.mdmodule boundary notes updated to explicitly include MAT export and risk outlook analytics.
- Updated repository About metadata:
- description
- website/homepage URL
- topical tags (topics) aligned with TWH, CQL/FHIR, and platform stack.
-
MAT export authorization boundary
SecurityConfignow explicitly gatesGET /api/measures/*/versions/*/export/mattoROLE_APPROVERorROLE_ADMINbefore the broad authenticated GET rule.- Prevents author/case-manager/viewer roles from downloading MAT bundles directly by URL.
-
Risk outlook missing-measure response classification
ProgramController.riskOutlook(...)now mapsIllegalArgumentExceptionfromRiskOutlookServiceto404 Not FoundviaResponseStatusException.- Keeps response semantics aligned with the rest of controller-layer not-found handling.
-
MAT export ValueSet version handling
MeasureExportServicenow preserves nullablevalue_sets.versionfrom DB and only sets FHIRValueSet.versionwhen non-blank.- Avoids serializing empty version primitives for value sets that intentionally omit version data.
ProgramControllerTest:- Added
riskOutlookReturnsNotFoundWhenMeasureIsMissing.
- Added
SecurityRoleIntegrationTest:- Added MAT export role checks:
- VIEWER forbidden
- AUTHOR forbidden
- APPROVER allowed through security layer (request reaches controller; returns 404 for unknown IDs)
- ADMIN allowed through security layer (request reaches controller; returns 404 for unknown IDs)
- Added MAT export role checks:
MeasureExportServiceTest:- Added
omitsValueSetVersionWhenStoredVersionIsBlankto assert no empty FHIR version output for blank DB values.
- Added
README.mdAPI highlights now annotate MAT export endpoint role requirements (ROLE_APPROVER/ROLE_ADMIN).
Issue 7.2 — AI Test Fixture Generator
- Backend
AiAssistServicenow supports AI fixture generation withgenerateTestFixtures(measureId, actor)and writesAI_TEST_FIXTURES_GENERATEDaudit events. - New endpoint:
POST /api/measures/{measureId}/ai/generate-test-fixturesonAiController. - Output is normalized to exactly 5 fixtures, one per required outcome (
COMPLIANT,DUE_SOON,OVERDUE,MISSING_DATA,EXCLUDED). - Deterministic fallback fixture set is returned when AI output is invalid/unavailable so authoring is never blocked.
- Frontend
TestsTabnow has Generate Fixtures + draft fixture cards and additive controls (Add to Draft,Add All to Drafts) with explanatory AI review note.
Issue 7.3 — Risk Outlook / Predictive Analytics
- Added
RiskOutlookServicewithgetOutlook(measureId, horizonDays):- Upcoming due-soon pressure from currently compliant employees nearing threshold.
- Repeat non-complier streaks (current consecutive non-compliant periods).
- Site-level current vs predicted compliance rates.
- New endpoint:
GET /api/programs/{measureId}/risk-outlook?horizonDays=30. - Programs detail page now renders a Risk Outlook panel with KPI chips, repeat non-compliers table (employee links to
/employees/[externalId]), and site heatmap table sorted by current risk.
Issue 7.4 — MAT-Compatible Export
- Added
MeasureExportService(com.workwell.fhir) to build MAT-compatible FHIR R4BundleXML containing:LibrarywithcontentType=text/cqland raw CQL bytes (HAPI serializes base64).Measurewith metadata and linked library reference.- Linked
ValueSetresources (including code concepts in compose/include blocks when available).
- New endpoint:
GET /api/measures/{measureId}/versions/{versionId}/export/mat?format=xml. - Studio Release tab now includes Export for MAT (FHIR XML) for APPROVER/ADMIN roles.
Issue 7.5 — Mobile Responsive UX
- Dashboard shell now uses
mdbreakpoint behavior for sidebar/hamburger and adds a mobile bottom tab bar (Programs, Cases, Runs, Admin). - Cases page now has explicit mobile card rows with compact employee/measure/status/chevron navigation.
- Case detail page now exposes mobile-first accordion sections (summary, actions, evidence, timeline) for 375px workflows while preserving the full desktop detail layout.
- Studio measure editor route now shows a mobile notice ("Studio requires a larger screen") and hides the heavy authoring surface on small screens.
Docs
README.mdAPI highlights updated for new Sprint 7 endpoints.
- Backend targeted tests:
.\gradlew.bat test --tests com.workwell.web.AiControllerTest --tests com.workwell.web.ProgramControllerTest --tests com.workwell.web.MeasureControllerTest→BUILD SUCCESSFUL
- Frontend:
npm run lint→ success (1 existing warning infrontend/test/mocks/next-font.ts)npm run build→ success
- Note: Full backend suite
.\gradlew.bat testexceeded local timeout windows in this run; targeted controller coverage above passed for all touched backend API surfaces.
Review comment resolved: code-reviewer flagged that AiAssistService.draftCql ordered versions by Active status before recency, meaning if a measure had an older Active version and a newer Draft version the AI prompt used stale spec_json — contradicting what Studio shows in the editor.
Fix: dropped the CASE WHEN mv.status = 'Active' THEN 0 ELSE 1 END priority from the ORDER BY; now orders purely by mv.created_at DESC so the newest version is always selected regardless of lifecycle status. One-line change, AI module tests confirmed green. Review thread resolved on GitHub.
- Commit:
e4e8501— fix(ai): select newest measure version for AI Draft CQL prompt - PR #52 ready to merge (no remaining open comments)
Backend — added AiAssistService.draftCql(measureId, oshaText, actor):
- Reads measure name + active
spec_jsonfor the given measure - Sends a CQL-specialist system prompt + user prompt containing measure name, spec JSON, and pasted OSHA text
- Strips code fences from model output
- Writes
AI_DRAFT_CQL_GENERATEDaudit event withmeasureId,model/provider,promptLength,outputLength,fallbackUsed - Deterministic fallback CQL template returned when AI call fails — TODO-annotated skeleton with
Outcome Statusdefine covering all five buckets
Endpoint — POST /api/measures/{measureId}/ai/draft-cql on AiController, accepts { oshaText } body, returns { success, cql, provider, fallbackUsed }.
Frontend — CqlTab now has an "AI Draft CQL" button next to Compile. Opens a modal with an OSHA text textarea; on submit the returned CQL is pushed into the Monaco editor and a dismissible amber banner appears above the editor. Compile state is reset so the user must compile the AI draft before approval.
Sprint 7 §7.1 differentiator — competitors don't offer CQL authoring assist. CQL is still validated by the existing compile gate before activation, so the rule that AI cannot decide compliance is preserved.
Issues filed: #47 (this), #48, #49, #50, #51 for the rest of Sprint 7.
CMS eCQM catalog: 2025 → 2026 (46 → 49 measures)
Fetched the official 2026 eligible clinician eCQM list from ecqi.healthit.gov. The 2026 performance period has 49 measures — 2 net new vs 2025 (46 carried forward minus CMS249v7 retired, plus 4 new).
Changes vs 2025 catalog:
- 4 new measures: CMS146v14 (Appropriate Testing for Pharyngitis, MIPS 066), CMS154v14 (Appropriate Treatment for URI, MIPS 065), CMS1173v1 (Diagnostic Delay of VTE in Primary Care, MIPS 514 — new CMS ID), CMS1154v1 (Screening for Abnormal Glucose Metabolism, MIPS 515 — new CMS ID)
- 1 retired: CMS249v7 (Appropriate Use of DXA Scans in Women Under 65) — removed from 2026 eligible clinician list
- 44 version-bumped: e.g., CMS128v13→CMS128v14, CMS2v14→CMS2v15, CMS951v3→CMS951v4, etc.
- New domain: Respiratory / Antimicrobial Stewardship (CMS146v14, CMS154v14)
Seed idempotency fix
Old logic matched existing measures by exact name — fragile when CMS updates measure titles between performance years (e.g., "Heart Failure (HF): ACE Inhibitor or ARB or ARNI Therapy for LVSD" → full expanded title in 2026). New logic strips the version suffix and queries policy_ref LIKE 'CMS128v%' so any version of a CMS measure maps to the same DB row. On match, UPDATE the name, policy_ref, and tags to current year's values. On no match, INSERT. This means the next TWH deploy will update all 45 existing measures in-place rather than creating 45 duplicate rows.
Workflow cleanup: deleted deploy-os-mieweb.yml
The old Deploy OS MIEWeb workflow was still triggering on every push to main, building ghcr.io/taleef7/workwell (generic non-TWH frontend) and deploying to workwell.os.mieweb.org / workwell-api.os.mieweb.org. These containers used DATABASE_URL (not DATABASE_URL_TWH) and no WORKWELL_INSTANCE — i.e., a separate, partially seeded environment. Deleted the workflow file; Taleef manually deleted the two containers from the MIE manager UI.
Container inventory post-cleanup (MIE Phoenix DC):
| Hostname | Image | Purpose | Keep |
|---|---|---|---|
twh |
workwell-twh-frontend |
Live TWH frontend | ✓ |
twh-api |
workwell-api |
Live TWH backend | ✓ |
workwell |
workwell |
Old non-TWH frontend | Deleted |
workwell-api |
workwell-api |
Old non-TWH backend | Deleted |
Only deploy-twh-mieweb.yml remains as an active workflow. Every push to main now builds and deploys exactly one environment.
MEASURES.md updated: catalog summary (58→60 total, 47→49 eCQM), domain breakdown table updated to 2026 IDs, implementation note updated. The old CMS249v7 Musculoskeletal row removed; new Respiratory domain added.
ee3dfd7— feat(catalog): upgrade CMS eCQMs to 2026 performance period (49 measures)
Sprint 7 features — ready to start. Priority order (from docs/sprints/SPRINT_07_overdelivery_features.md):
- AI Draft CQL (7.1)
- AI Test Fixture Generator (7.2)
- Risk Outlook / Predictive Analytics (7.3)
- MAT Export (7.4)
- Mobile Responsive Layout (7.5)
Doug clarified the product direction: TWH (Total Worker Health) is all-encompassing. OSHA occupational safety compliance and clinical quality (eCQMs, HEDIS wellness) are not separate products — they are two sides of the same coin and belong in one platform. The three-instance deployment model (workwell, ecqm, twh) was a development stepping stone, not the product architecture. One TWH instance covers everything.
NIOSH's TWH framework is the conceptual foundation: worker health is shaped by both workplace hazards (OSHA safety programs) and general health promotion (chronic disease, preventive care). WorkWell is the platform that manages both in one system with a shared measure catalog, shared case workflow, shared audit trail, and shared CQL evaluation engine.
Doug also asked to get all CMS eCQM IDs into the project — the 47 official CMS electronic Clinical Quality Measures from the 2025 performance period (ecqi.healthit.gov). These are the measures hospitals and clinics use for Medicare/Medicaid quality reporting. Having them in the WorkWell catalog positions the platform as a bridge between occupational health and the broader clinical quality infrastructure.
Deleted: .github/workflows/deploy-ecqm-mieweb.yml
- The separate eCQM instance (ecqm.os.mieweb.org + ecqm-api) is gone. TWH seeds everything.
Kept: .github/workflows/deploy-twh-mieweb.yml
- Now the sole deploy workflow. Triggers on every push to
main. - Builds backend (
ghcr.io/taleef7/workwell-api) and TWH-branded frontend (ghcr.io/taleef7/workwell-twh-frontend). - Sets
WORKWELL_INSTANCE=twhwhich seeds all 3 measure categories on startup.
Destroyed: Fly.io workwell-measure-studio-api
- Old secondary stack from before MIE. Stale — would diverge from main over time since Fly doesn't auto-deploy. Decommissioned via
fly apps destroy. workwell-measure-studio.vercel.appno longer has a working backend; Vercel project left dormant (free tier, harmless).
Neon: Already clean — single workwell-twh project (45.94 MB). No orphaned databases needed deletion.
Final infrastructure state:
| Service | URL | State |
|---|---|---|
| Frontend | https://twh.os.mieweb.org |
Running — latest SHA |
| Backend API | https://twh-api.os.mieweb.org |
Running — latest SHA |
| Database | Neon workwell-twh |
Active — sole Neon project |
MeasureService.java — CMS eCQM catalog seeding
Added CMS_ECQM_CATALOG — a static List<CmsEcqmRecord> of all 47 CMS eCQMs from the 2025 performance period. Each record carries: title, CMS ID (e.g., CMS128v13), MIPS Quality ID, and clinical domain tags.
Added ensureCmsEcqmCatalogSeed() — iterates the catalog, inserts each measure into measures and measure_versions if not already present. Idempotent (skips on conflict). Called from ensureInstanceSeeds() for ecqm and twh instances.
Seeding approach (no migration required):
measures.policy_refstores the CMS ID — consistent with how OSHA measures storeOSHA 29 CFR 1910.95and HEDIS measures storeHEDIS CBP / JPMC Wellness Rewardsmeasures.tagscarriesecqm,cms, plus clinical domain (mental-health,cardiovascular,diabetes,cancer-screening,pediatric,hiv,oncology, etc.)measure_versions.spec_jsonstorescmsEcqmIdandmipsQualityIdfor downstream tooling and the MAT export (Sprint 7)- Status:
Draft,compile_status: NOT_COMPILED— these are catalog entries awaiting CQL authoring
47 measures across 15 clinical domains. Full list in MeasureService.CMS_ECQM_CATALOG.
measures/page.tsx — CMS ID badge
Policy Ref column: regex /^CMS\d+/ detects CMS eCQM IDs and renders them as a blue monospace ring badge (CMS128v13 style). OSHA CFR citations and HEDIS refs remain as plain text. Makes the three measure categories visually distinct in the catalog at a glance.
docs/ARCHITECTURE.md— System overview updated to describe TWH as the product framing; deployment topology updated from Vercel+Fly to MIE Create-a-Container; infra split section updated.docs/MEASURES.md— Complete rewrite. Now documents all 58 measures across 4 categories: OSHA full CQL (4), OSHA catalog (3), HEDIS wellness full CQL (4), CMS eCQM Draft catalog (47). Includes domain breakdown table, compliance windows, CQL define logic for all runnable measures.docs/DEPLOY.md— Added MIE Create-a-Container primary deployment section with required secrets, instance seeding description, and manual re-deploy instructions.CLAUDE.md— Current Focus updated: live URL, all post-merge work itemised, measure catalog count, Sprint 7 as next work.
| # | Name | Category | CQL | Status |
|---|---|---|---|---|
| 1 | Audiogram | OSHA | Yes | Active |
| 2 | HAZWOPER Surveillance | OSHA | Yes | Active |
| 3 | TB Surveillance | OSHA | Yes | Active |
| 4 | Flu Vaccine | OSHA | Yes | Active |
| 5 | Respirator Fit Test | OSHA | No | Draft |
| 6 | Hepatitis B Vaccination Series | OSHA | Partial | Approved |
| 7 | Lead Medical Surveillance | OSHA | No | Deprecated |
| 8 | Hypertension BP Screening | HEDIS Wellness | Yes | Active |
| 9 | Diabetes HbA1c Monitoring | HEDIS Wellness | Yes | Active |
| 10 | BMI Screening & Counseling | HEDIS Wellness | Yes | Active |
| 11 | Cholesterol LDL Screening | HEDIS Wellness | Yes | Active |
| 12–58 | CMS eCQMs (47) | CMS eCQM | No | Draft |
Sprint 7 (docs/sprints/SPRINT_07_overdelivery_features.md):
- AI Draft CQL — paste OSHA text → generate CQL skeleton
- AI Test Fixture Generator — auto-generate 5 fixtures covering all outcome types
- Risk Outlook / Predictive Analytics — upcoming expirations, repeat non-compliers, site heatmap
- MAT Export — FHIR R4 XML bundle compatible with CMS Measure Authoring Tool
- Mobile Responsive Layout — bottom tab bar, card list on mobile, Studio notice
Goal: Fix AI integration "Degraded" status, add real-time run progress (spinner + live timer + auto-reload on completion), fix Traceability tab 403, fix hardcoded "Four measures" text for multi-instance deployments, and complete eCQM/TWH workflow NEXT_PUBLIC_APP_DESCRIPTION build-arg.
Branch: feat/ecqm-twh-instances
What changed:
backend/src/main/java/com/workwell/admin/IntegrationHealthService.java—checkAiHealth()was callingPOST /v1/responses(returned HTTP 400 forgpt-5.4-nano). Changed toGET /v1/modelswhich validates the API key regardless of model name. Returns healthy if 200, degraded otherwise.backend/src/main/java/com/workwell/config/SecurityConfig.java— Added explicitrequestMatchers(HttpMethod.GET, "/api/measures/*/traceability").authenticated()before the wildcard GET rule as belt-and-suspenders fix for Traceability tab 403 reports.frontend/app/page.tsx— AddedNEXT_PUBLIC_APP_DESCRIPTIONenv var constant with fallback; replaced hardcoded "Four measures, complete case management..." subtitle with{APP_DESCRIPTION}. TWH landing page will now correctly read "Eight measures (OSHA safety + wellness)..."..github/workflows/deploy-ecqm-mieweb.yml— AddedAPP_DESCRIPTIONenv var andNEXT_PUBLIC_APP_DESCRIPTIONDocker build-arg: "Four clinical quality measures, complete case management, and a full audit trail — one reviewable dashboard.".github/workflows/deploy-twh-mieweb.yml— AddedAPP_DESCRIPTIONenv var andNEXT_PUBLIC_APP_DESCRIPTIONDocker build-arg: "Eight measures (OSHA safety + wellness), complete case management, and a full audit trail — one reviewable dashboard."frontend/Dockerfile— AddedNEXT_PUBLIC_APP_DESCRIPTIONbuild arg andENVstatement (default: workwell 4-measure text) so per-instance workflows can override it at build time.frontend/app/(dashboard)/runs/page.tsx— Real-time run progress:- New state:
isRunTriggering,activeRunId,activeRunStartedAt,runElapsedSec. - Polling effect (
useEffectonactiveRunId): pollsGET /api/runs/{id}every 2 s, updates the run row in the table live, stops and auto-reloads (runs list + run detail + outcomes) when status reachesCOMPLETED|FAILED|PARTIAL_FAILURE|CANCELLED. - Timer effect (
useEffectonactiveRunStartedAt): incrementsrunElapsedSecevery second. - Run Now button: spinner + "Running…" label while
isRunTriggering; disabled during run to prevent double-submit. - Rerun Selected Scope button: same spinner treatment; disabled while a run is in progress.
- Duration column: shows live
{runElapsedSec}s ●(animated dot) for the active run row; static formatted duration for all others. - Detail panel Duration field: same live/static treatment.
- New state:
frontend/features/studio/components/TestsTab.tsx— Validate button shows spinner + "Validating…" while the/tests/validatePOST is in flight; disabled during the call.
Verification: Frontend TypeScript check clean; ESLint clean (0 errors, 0 warnings). Backend tests running.
Goal: Add ecqm.os.mieweb.org (clinical quality / wellness measures) and twh.os.mieweb.org (Total Worker Health — all 8 measures) as independent WorkWell instances. Same backend Docker image, instance-aware seeding via WORKWELL_INSTANCE env var, separate Neon databases, separate frontend Docker images with per-instance branding.
Branch: feat/ecqm-twh-instances
What changed:
backend/src/main/resources/measures/hypertension.cql— New CQL libraryHypertensionBPScreeningCQL 1.0.0. Annual BP screening (compliance window 365 days, DueSoon 336–365), wellness-enrollment/exemption value sets.backend/src/main/resources/measures/diabetes_hba1c.cql— New CQL libraryDiabetesHbA1cMonitoringCQL 1.0.0. Biannual HbA1c (compliance window 180 days, DueSoon 161–180), diabetes-program/exemption value sets.backend/src/main/resources/measures/obesity_bmi.cql— New CQL libraryObesityBMIScreeningCQL 1.0.0. Annual BMI screening (compliance window 365 days), wellness-enrollment/exemption value sets.backend/src/main/resources/measures/cholesterol_ldl.cql— New CQL libraryCholesterolLDLScreeningCQL 1.0.0. Annual LDL screening (compliance window 365 days), cholesterol-program/exemption value sets.backend/src/main/resources/application.yml— Addedworkwell.instance: ${WORKWELL_INSTANCE:workwell}property; added 4 new compliance rates (hypertension: 0.72, diabetes_hba1c: 0.68, obesity_bmi: 0.81, cholesterol_ldl: 0.74).backend/src/main/java/com/workwell/measure/MeasureService.java— Added@Value("${workwell.instance:workwell}") private String workwellInstance; addedensureInstanceSeeds()that gates OSHA seeds onworkwell|twhand wellness seeds onecqm|twh; replaced direct seed calls inlistMeasures()/getMeasure()withensureInstanceSeeds(); added 4 new seed methods (ensureHypertensionSeed,ensureDiabetesHbA1cSeed,ensureObesityBmiSeed,ensureCholesterolLdlSeed).backend/src/main/java/com/workwell/compile/CqlEvaluationService.java— Added 4 new cases tomeasureSeedSpecFor()switch (Hypertension BP Screening, Diabetes HbA1c Monitoring, BMI Screening & Counseling, Cholesterol LDL Screening). All 4 useuseImmunization=false(Procedure resources).backend/src/main/java/com/workwell/measure/ValueSetGovernanceService.java— Added 10 wellness value sets insideensureDemoValueSets()usingb0000001-...UUID range (non-colliding with existinga000...OSHA UUIDs): wellness-enrollment, wellness-exemption, bp-screening (CPT 99213), diabetes-program, diabetes-exemption, hba1c-labs (CPT 83036), bmi-screening (CPT 99401), cholesterol-program, cholesterol-exemption, ldl-labs (CPT 83721). AddedensureLink()calls for all 4 wellness measures.frontend/Dockerfile— AddedNEXT_PUBLIC_APP_NAMEandNEXT_PUBLIC_APP_TAGLINEbuild args (default to workwell values);ENVstatements bake them into each per-instance image at build time.frontend/app/layout.tsx— Root metadata usesNEXT_PUBLIC_APP_NAME/NEXT_PUBLIC_APP_TAGLINEenv vars.frontend/app/page.tsx— Landing page hero h1, header brand badge/subtitle, and footer copyright all driven byNEXT_PUBLIC_APP_NAME/NEXT_PUBLIC_APP_TAGLINEconstants derived from env vars.frontend/app/(dashboard)/layout.tsx— Sidebar and mobile header "WorkWell"/"Measure Studio" spans driven by split ofNEXT_PUBLIC_APP_NAME.frontend/app/login/page.tsx— Left-panel brand badge/subtitle driven byNEXT_PUBLIC_APP_NAMEsplit.frontend/app/sandbox/page.tsx— "WorkWell Measure Studio" label driven byNEXT_PUBLIC_APP_NAME.frontend/app/sandbox/layout.tsx— Metadata description driven byNEXT_PUBLIC_APP_NAME..github/workflows/deploy-ecqm-mieweb.yml— New workflow: builds same backend image + separateworkwell-ecqm-frontendimage (with eCQM branding build args), deploys toecqm-api/ecqmhostnames withWORKWELL_INSTANCE=ecqm, usesDATABASE_URL_ECQMandWORKWELL_AUTH_JWT_SECRET_ECQMsecrets..github/workflows/deploy-twh-mieweb.yml— New workflow: builds same backend image + separateworkwell-twh-frontendimage (with TWH branding build args), deploys totwh-api/twhhostnames withWORKWELL_INSTANCE=twh, usesDATABASE_URL_TWHandWORKWELL_AUTH_JWT_SECRET_TWHsecrets.docs/ECQM_TWH_DEPLOYMENT_PLAN.md— Full deployment plan committed for project-level visibility.
Measure assignment per instance:
| Measure | workwell | ecqm | twh |
|---|---|---|---|
| Audiogram (OSHA) | ✓ | — | ✓ |
| TB Surveillance | ✓ | — | ✓ |
| HAZWOPER Surveillance | ✓ | — | ✓ |
| Flu Vaccine | ✓ | — | ✓ |
| Hypertension Control | — | ✓ | ✓ |
| Diabetes HbA1c | — | ✓ | ✓ |
| Obesity BMI Screening | — | ✓ | ✓ |
| Cholesterol LDL | — | ✓ | ✓ |
Owner actions required (Taleef) before first deploy:
- Create two Neon projects (
workwell-ecqm,workwell-twh), copy pooled connection strings. - Add GitHub repository secrets:
DATABASE_URL_ECQM,DATABASE_URL_TWH,WORKWELL_AUTH_JWT_SECRET_ECQM,WORKWELL_AUTH_JWT_SECRET_TWH. - Make GHCR packages
workwell-ecqm-frontendandworkwell-twh-frontendpublic after first push.
Verification (local):
# ecqm instance — expect Hypertension, Diabetes, BMI, Cholesterol only
WORKWELL_INSTANCE=ecqm ./gradlew bootRun
# twh instance — expect all 8 measures
WORKWELL_INSTANCE=twh ./gradlew bootRun
# workwell instance (default) — expect 4 OSHA measures only
./gradlew bootRun2026-05-20 — UAT Sections 9-14: Add Mapping UI, Studio packet selector, Demo Reset gating (issue #30)
Goal: Fix all reported Section 9 (Terminology Mappings), Section 11 (Audit Packets in Studio Release & Approval tab), and Section 14 (Reset Demo Data prod visibility) UAT bugs from GitHub issue #30, plus correct guide inaccuracies for Sections 9–14.
Branch: fix/sprint-1-uat-sections-9-14
What changed:
frontend/app/(dashboard)/admin/page.tsx— Added "Add Mapping" form/dialog to the Local code mappings panel. The toggleable inline form posts toPOST /api/admin/terminology-mappingsand refreshes the table on success. Form validates required fields (local code/system, standard code/system) and confidence range (0.0–1.0). Also gated the Reset demo data card onprocess.env.NEXT_PUBLIC_DEMO_MODE === "true"so the card is structurally absent in production Vercel builds (production builds never setNEXT_PUBLIC_DEMO_MODE=truebecausenext.config.tsfails fast if they do).frontend/features/studio/components/ReleaseApprovalTab.tsx— Replaced the legacy direct-JSON-download button with the sharedAuditPacketExportButtonso the Studio Release & Approval tab now exposes the same JSON/HTML format selector already used on case detail and runs. The third packet entry point is now consistent with the other two.docs/WALKTHROUGH_GUIDE.md— Corrected Sections 9–14 against the current UI: Section 9 renamed panel to "Local code mappings" and documented Reviewed By / Notes columns plus the new Add Mapping inline form (and noted that Validate Mappings lives on the Source mappings panel, not the terminology panel); Section 10 documented the actual button labels and called out that audit-events CSV export lives on the Cases page (not Admin); Section 11 documents the consistent JSON/HTML format dropdown across all three entry points (case detail, run detail panel, Studio Release & Approval tab); Section 12 added explicit Claude Desktop config-file path, bearer-token JSON snippet, and JWT acquisition instructions; Section 13 added a Bug 4 cross-reference for the/loginredirect on session expiry; Section 14 documented the inline (non-modal) confirmation, the frontend visibility gate, and the seven-measure / four-lifecycle catalog left after a Demo Reset.
Verification:
frontend/npm run lint— passed (only pre-existingtest/mocks/next-font.tsanonymous-default-export warning).frontend/npm test— 40/40 passed.frontend/npx tsc --noEmit— clean (no TypeScript errors).- Playwright local end-to-end against
localhost:3000+localhost:8080:- Logged in as
admin@workwell.dev, navigated to/admin, opened the new Add Mapping form, submittedANNUAL-FIT-TEST→ SNOMED415070008, observed the new row appearing in the Local code mappings table and the form auto-closing. - With
NEXT_PUBLIC_DEMO_MODE=true: confirmed the Reset demo data card renders on/admin. - Restarted the dev server with
NEXT_PUBLIC_DEMO_MODE=false: confirmed the Reset demo data card no longer renders on/adminwhile the Admin page itself, including the Local code mappings panel and Add Mapping button, continues to render normally. - Opened the Audiogram measure in Studio → Release & Approval tab, confirmed both the header and Release & Approval tab
Export Measure Audit Packetcontrols expose JSON/HTML in the format dropdown (2 controls, both with[json, html]options).
- Logged in as
- Manual /measures inspection — confirmed the seven seeded measures across four lifecycle states (4 Active, 1 Approved, 1 Draft, 1 Deprecated) matching the WALKTHROUGH_GUIDE.md Section 14 table.
Goal: Fix all reported Section 6 (Run History), Section 7 (Studio/CQL), and Section 8 (Admin panel) UAT bugs from GitHub issue #29.
Branch: fix/sprint-1-uat-sections-6-8
What changed:
backend/src/main/java/com/workwell/run/RunPersistenceService.java— FixedfinalizeAsyncRun()duration computation: was incorrectly using the evaluationDate (a historical date) to computeduration_ms, yielding absurd values like69068s. Now fetches the actualstarted_atfrom the DB and computes real wall-clock duration. Also fixedmeasurement_period_start/measurement_period_endto correctly reflect the 1-year evaluation window (evalDate-1yr → evalDate) instead of repeatingstartedAttwice.backend/src/main/java/com/workwell/BackendApplication.java— Set JVM default timezone to UTC on startup for consistent timestamp handling.backend/src/main/java/com/workwell/measure/ValueSetGovernanceService.java— RenamedensureDemoValueSetLinks()toensureDemoValueSets()and expanded it to seed all 4 demo value sets (audiogram, TB, HAZWOPER, flu vaccine) with their correct CQL-matching canonical OIDs and local codes soresolveCheckfinds matching codes.frontend/app/(dashboard)/admin/page.tsx— Added confirmation dialog before disabling the scheduler to prevent accidental disables during a demo.frontend/features/studio/components/CqlTab.tsx— Added "New Version" button with a modal dialog for entering a change summary and cloning the current CQL into a new draft measure version.- PR review follow-up: wired the CQL-tab modal summary directly into the version-clone request so it no longer depends on asynchronous React state, added a Runs/Run Detail display guard that renders anomalous
durationMsvalues over 1 hour as-orStalled, and restored Cases search state synchronization when browser history changes thesearchURL parameter.
Verification:
backend/gradlew.bat test --tests com.workwell.export.* --tests com.workwell.web.RunControllerTest— BUILD SUCCESSFUL (21s).- Backend compiles cleanly:
gradlew compileJava— BUILD SUCCESSFUL (16s). - Playwright end-to-end: triggered async All Programs run — completed with
179sreal duration (vs old seeded60sconstant). Duration correctly reflects actual CQL evaluation time.
Goal: Fix Section 5 case-detail bugs from UAT #23: escalation confirmation, outreach delivery badge refresh, audit packet format selectors, and walkthrough-guide inaccuracies.
Branch: fix/section-5-case-detail
What changed:
backend/src/main/java/com/workwell/caseflow/CaseFlowService.java— Outreach sends now persist the actual email delivery status (SIMULATEDon the demo stack) in theOUTREACH_SENTaction payload and outreach record; latest delivery status now considers both initial outreach sends and later manual delivery updates.backend/src/test/java/com/workwell/caseflow/CaseUpsertIntegrationTest.java— Added regression coverage proving a successful outreach send immediately returnslatestOutreachDeliveryStatus=SIMULATED.frontend/app/(dashboard)/cases/[id]/page.tsx— Added an accessible confirmation dialog before escalation fires; added JSON/HTML selector for case audit packet export; addedSIMULATEDdelivery badge styling.frontend/app/(dashboard)/runs/page.tsx— Added JSON/HTML selector for run audit packet export.frontend/app/(dashboard)/studio/[id]/page.tsx— Added JSON/HTML selector for measure-version audit packet export.frontend/components/audit-packet-export-button.tsx— New reusable audit packet export control shared by case, run, and measure version packet entry points.docs/WALKTHROUGH_GUIDE.md— Corrected Section 5 and Section 11 wording to match the current UI: no separate Employee & Measure Panel heading, inline assignee field, structured evidence trail labels, appointment/resolution controls, outreach template/preview behavior, auto-updating simulated delivery badge, and packet format selectors.
Verification:
backend/gradlew.bat test --tests com.workwell.caseflow.CaseUpsertIntegrationTest— passed.backend/gradlew.bat --no-daemon test --tests com.workwell.web.CaseControllerTest --tests com.workwell.web.AuditorControllerTest— passed.frontend/corepack pnpm lint— passed with the existingtest/mocks/next-font.tsanonymous-default-export warning.frontend/corepack pnpm test— 40/40 passed.frontend/corepack pnpm build— passed.- Playwright local confirmation with mocked API data — verified escalation waits for confirmation, outreach badge updates to
Simulated, and case/run/measure packet exports honor the selectedformat=html. - Attempted full
backend/gradlew.bat test; it did not return before the 15-minute timeout, so focused backend verification above was used for this issue branch.
Goal: Redesign login page to match landing aesthetic, make the full application responsive across all device sizes, add Sign in to landing page, trim all redundant copy from public surfaces.
Branch: feat/ui-responsive-polish
What changed:
frontend/app/login/page.tsx— Full redesign. Dark left panel (Fraunces headline "Compliance ops, fully in view.", feature list with icons, sandbox shortcut card) + light right form panel. Password show/hide toggle with Eye/EyeOff icons. Mail and Lock icons in inputs. Zap icon on Fill demo credentials. "Skip login — open public sandbox" link with BadgeCheck icon. Email/password labels visible (not placeholder-only). Properh-12touch targets on all inputs. Removed all redundant explanatory text. Mobile: left panel hidden, form panel with light gradient background and compact WW logo.frontend/app/(dashboard)/layout.tsx— Full responsive overhaul. Sidebar moved tofixedoverlay on mobile (-translate-x-full→translate-x-0on open), with dark backdrop and slide-in animation. Added sidebar close button (X icon) and outside-click handler. Added icon to every nav item (BarChart3, Shield, ClipboardList, BookOpen, FileClock, Activity, Settings). User info + logout moved to sidebar footer (avatar initial + email + role + LogOut icon button). Header stripped to: hamburger (mobile only) + compact logo (mobile only) + GlobalSearch + filters. Filters moved to a dedicated scrollable bar below the header on mobile. Hamburger uses proper Menu icon from lucide.frontend/app/page.tsx— Added Sign in link/button to header nav (LogIn icon), hero CTAs, walkthrough section, and footer. Feature card copy trimmed. Hero subparagraph reduced to one tight sentence. Walkthrough section body reduced to one sentence. Removed portal pills section. Operating notes reduced to 3. Sandbox section list items now use BadgeCheck icons. Feature card "AI-assisted authoring" replaces the "Polished demo surfaces" placeholder.frontend/vitest.config.ts— Added alias fornext/font/google→test/mocks/next-font.tsso font imports don't break Vitest.frontend/test/mocks/next-font.ts— New mock file returning stable className/variable stubs for Fraunces, Geist, GeistMono, Inter.
Tests: 40/40 pass. Lint clean.
Goal: Carry forward the Codex feat/workwell-landing-sandbox handover work: polish the public landing page, improve sandbox UX, and clean up all internal-facing copy from the public surface.
Branch: feat/workwell-landing-sandbox
What changed:
-
frontend/app/page.tsx- Added a stats strip in the hero (4 compliance programs, 50+ employees, 5 outcome types, 1-click sandbox entry) to ground the product story in concrete numbers.
- Updated the badge to "PUBLIC SANDBOX · NO LOGIN REQUIRED" for clarity.
- Cleaned the hero subheading — removed "The landing page keeps the story simple…" meta-commentary; copy now describes what's actually in the product.
- Replaced the sandbox preview card heading "Built for review, not for friction." with "Open the dashboard in one click." and replaced the dark section's weak internal copy with the actual app section names (Programs & outcome trends, Case worklist & outreach, CQL Measure Studio, Audit trail & exports).
- Tightened the operating-notes pills to short, punchy form.
- Fixed the video section: removed "Why the video belongs here" + "Doug's note calls for…" references; heading is now "The full product story in under five minutes." and the body describes the actual walkthrough flow.
- Fixed video card footer copy to remove internal-facing commentary.
- Fixed a YouTube Short URL typo:
SqzDt4TBd9k→SgzDt4TBd9k(consistent with CLAUDE.md and vision doc). - Footer copy changed from "Built as a public front door for the WorkWell demo and review flow." to "WorkWell Measure Studio — compliance operations for occupational health."
-
frontend/app/sandbox/page.tsx- Redesigned as a full dark (slate-950) branded loading screen: centered WW monogram, brand label, loading status panel, animated step indicators (Connecting → Authenticating → Opening Programs dashboard), and footer links.
- Removed the three info cards that appeared while the user was waiting (redundant during a fast redirect).
- All auth logic and state management kept identical to the Codex handover; 40/40 tests still pass.
Verification:
corepack pnpm lint— cleancorepack pnpm test— 40/40 pass- Browser confirmed: landing page renders with stats strip and clean copy; sandbox auto-signs in and redirects to /programs; public routes exempt from auth refresh loop.
Goal: Prepare an additive, review-only deployment path for WorkWell Measure Studio on MIE's open source Proxmox cluster without disturbing the existing Vercel + Fly deployment.
Branch: os-mieweb-deploy
What changed:
backend/Dockerfile- Kept the repo's Gradle Kotlin DSL build instead of switching to Maven, because this project has no Maven build and the stack is fixed to Gradle.
- Builds a Spring Boot jar in a Gradle stage, copies the runnable jar into
eclipse-temurin:21-jre-alpine, exposes8080, and adds the required MIE default-port label. - Uses process env for runtime configuration and preserves
JAVA_OPTS.
frontend/Dockerfile- Added a Node 20 Alpine multi-stage build with
NEXT_PUBLIC_API_URLdefaulting tohttps://workwell-api.os.mieweb.org. - Enables standalone Next.js output and runs the generated standalone server on port
3000. - Exposes
3000and adds the required MIE default-port label.
- Added a Node 20 Alpine multi-stage build with
.github/workflows/deploy-os-mieweb.yml- Added additive GHCR build jobs for
ghcr.io/taleef7/workwell-apiandghcr.io/taleef7/workwell. - Added direct Create-a-Container REST deploy jobs for explicit
workwell-apiandworkwellhostnames, gated naturally bypushtomainor manual dispatch.
- Added additive GHCR build jobs for
.github/scripts/deploy-mieweb-container.sh- Centralized the shared MIE REST API deployment flow so backend and frontend deploys use the same site/domain lookup, replace-existing handling, job polling, and API error handling.
docs/DEPLOY_OS_MIEWEB.md- Added setup, secrets, public GHCR visibility, health verification, rollback, and pre-first-deploy clarification notes.
Needs clarification before first deploy:
mieweb/launchpad@mainappears to derive the container hostname fromowner-repo-branch, with no documented override. MIE admins should confirm how to deploy two distinct LXC containers from the same repo/branch before this workflow runs onmain.- Confirm
LAUNCHPAD_API_URLand whethersite_id: 1is the intended Phoenix DC target.
Follow-up update:
- Confirmed
https://manager.os.mieweb.org/api/openapi.jsonexposes direct Create-a-Container REST endpoints for explicithostname,services, andenvironmentVars. - Reworked
.github/workflows/deploy-os-mieweb.ymlaway frommieweb/launchpad@mainto direct REST calls so the same repo/branch can create bothworkwell-apiandworkwell. - Set site
1as the Phoenix target and removedANTHROPIC_API_KEYfrom the required MIE workflow secrets because the current backend configuration uses OpenAI. - Documented the remaining owner steps in
docs/DEPLOY_OS_MIEWEB.md: Neon JDBCDATABASE_URL,WORKWELL_AUTH_JWT_SECRET, and making GHCR packages public after first image push. - Addressed PR review follow-up: backend image default profile is now
prod,production, and shared MIE API bash moved into.github/scripts/deploy-mieweb-container.sh. - After the first
maindeploy attempt, corrected the MIE API base handling:/apiserves Swagger UI, while JSON REST endpoints are rooted athttps://manager.os.mieweb.org.
Goal: Fix the three Section 3 code bugs and correct the Section 3 walkthrough-guide inaccuracies (UAT #23, comment 4).
Branch: fix/section-3-measure-drilldown
What changed:
frontend/app/(dashboard)/programs/[measureId]/page.tsx- Bug 8: added a Run history table (from
/api/programs/{measureId}/trend, newest first) with per-run links to/runs?runId=...and a "View all runs →" link. No backend change needed (trend is already measure-scoped). - Bug 9: added a recharts donut of the latest-run outcome breakdown (Compliant/Due Soon/Overdue/Missing/Excluded) and converted the plain-text Reason mix card into proportion bars.
- Bug 8: added a Run history table (from
frontend/app/(dashboard)/runs/page.tsx- Bug 8 (cont.):
/runs?runId=now pre-selects that run in Run Detail (useSearchParams; deep-linked run preserved even when not in the current list page). - Bug 10: non-compliant outcome rows (those with a
caseId) are now clickable →/cases/[caseId](pointer + hover, keyboard accessible, inner links stopPropagation). Compliant/Excluded rows are muted and non-clickable.
- Bug 8 (cont.):
docs/WALKTHROUGH_GUIDE.md- Corrected Section 3 inaccuracies 6–11: AI Run Insight is on
/runs(auto-loads, real disclaimer wording), duration shown in seconds, real outcomes-table columns, measure labelled "Audiogram". Synced the matching Section 6 duration/label mentions. CSVdurationMscolumn name left intact (real column).
- Corrected Section 3 inaccuracies 6–11: AI Run Insight is on
Verification:
pnpm lint/pnpm build✅- Production data-shape validation via browser (changed code can't run on a preview: prod CORS allowlist excludes non-prod origins by design, and the preview is behind Vercel deployment protection).
PR #35 review follow-up (automated reviewers):
- Codex P2 + Copilot:
/runs?runId=with an invalid/stale id no longer strands the user on an error path —loadSelectedRunnow drops the URL preservation, cleans the query param (router.replace), and falls back to the newest run. - Copilot: row-level
onKeyDownon outcome rows now guardsevent.target === event.currentTarget, so Enter/Space on the nested Employee/Case links keeps their own navigation. - Copilot: reconciled the Audiogram naming inconsistency — Section 2's card list now shows the real UI labels (Audiogram / Flu Vaccine / HAZWOPER Surveillance / TB Surveillance) with the long policy titles marked documentation-only, matching the Section 3 note.
Goal: Eliminate the remaining page-refresh logout path by hardening frontend session bootstrap and login cookie persistence.
Branch: fix/section-1-refresh-reload-session
What changed:
frontend/components/auth-provider.tsx- Added a hydration-safe guard in the unauthenticated effect: if the render sees
token=nullbut a valid session still exists in localStorage, the provider now re-emits session state instead of clearing storage and forcing refresh. - This removes the race where hard reload could clear a still-valid access token and bounce users to
/login.
- Added a hydration-safe guard in the unauthenticated effect: if the render sees
frontend/app/login/page.tsx- Added
credentials: "include"toPOST /api/auth/loginso the browser persists the HttpOnly refresh cookie in cross-origin mode (vercel.appfrontend tofly.devbackend).
- Added
- Tests:
- Added regression in
frontend/components/__tests__/auth-provider.test.tsxto verify valid local session is retained without refresh/redirect. - Added
frontend/app/login/__tests__/page.test.tsxto verify login fetch includes credentials and still logs in.
- Added regression in
Verification:
corepack pnpm test✅ (37/37)corepack pnpm lint✅corepack pnpm build✅
Goal: Verify UAT #23 Section 1 (#24) and Section 2 (#25) fixes are actually complete in production.
Branch: fix/section-1-cross-site-refresh-cookie (PR #33); the Section 2 modal follow-up is fix/section-2-run-all-confirm-modal (PR #34)
Findings (end-to-end test against production, real browser):
- Section 1 / #24 — NOT fixed in production. Frontend silent-refresh code (
auth-provider.tsx) is correct and the backend/api/auth/refreshcontract matches, but the refresh cookie was issuedSameSite=Laxwith noSecure. Frontend (vercel.app) and backend (fly.dev) are different sites, so the browser never sends aSameSite=Laxcookie on the cross-sitePOST /api/auth/refreshfetch. Reproduced: clearedww_token, reloaded/programs→ redirected to/login, console shows401 @ /api/auth/refresh. - Section 2 / #25 — code correct but backend not redeployed. Bug 1/2/3/6 (frontend) are live via Vercel. Bug 5 (driver scoping) and Bug 7 (
fly.toml min_machines_running=1) are merged tomainbut the Fly backend was never redeployed (uptime ≈21h predates the 2026-05-18 18:54 fix commit). Live API still returns identical driver data for Flu/HAZWOPER/TB.
Fix applied (Section 1 cookie):
AuthController— refresh/logout cookieSameSitenow configurable (workwell.auth.cookie-same-site, defaultLax);Secureauto-forced whenSameSite=None(browser hard requirement).application.yml— addedcookie-same-site: ${WORKWELL_AUTH_COOKIE_SAME_SITE:Lax}.StartupSafetyValidator— newvalidateCookiePolicy: production-like startup now fails fast unlessSameSite=None+Secure=true(prod is cross-site by design); plus universalNone ⇒ Securerule. 5 new tests; existing tests green.- Docs:
.env.example,docs/DEPLOY.md(Fly secrets + env table) updated.
Still required (owner action — not auto-applied):
- Set Fly secrets
WORKWELL_AUTH_COOKIE_SAME_SITE=NoneandWORKWELL_AUTH_COOKIE_SECURE=true, then redeploy the Fly backend. This single redeploy also activates the merged Bug 5 and Bug 7 fixes. Frontend needs no change.
Goal: Add meaningful test coverage and make CI block merges on failures.
Branch: feat/sprint-5-tests-ci
Issue 5.2 — Backend integration tests:
- Created
CaseUpsertIntegrationTest(2 tests): verifies that re-runningAllProgramsRunServiceproduces 0 duplicate case rows and that the unique-key constraint holds across all composite keys. - Created
CaseSlaServiceTest(2 tests): backdatessla_due_dateto yesterday on a seeded open case, callsescalateBreachedCases(), asserts priority was escalated and aCASE_SLA_BREACHEDaudit event was written; second test verifies already-breached cases are not escalated again. - Both extend
AbstractIntegrationTest(existing TestContainers PostgreSQL 16 infrastructure). - Note: evidence MIME tests already covered by existing
EvidenceServiceTestandEvidenceAccessIntegrationTest(Sprint 4 work).
Issue 5.1 — Frontend unit tests:
- Installed:
vitest @vitest/coverage-v8 @testing-library/react @testing-library/jest-dom @testing-library/user-event msw jsdom @vitejs/plugin-react. - Extracted
SlaChipfrom inline cases-page logic tofrontend/components/SlaChip.tsx(also replaces the local copy in the employee profile page); addsdata-testid="sla-chip"for test targeting. - Created
vitest.config.ts,test/setup.ts,test/msw/handlers.ts,test/msw/server.ts. - Tests written (17 passing):
__tests__/components/SlaChip.test.tsx— 8 tests: null guard, Breached text, day count, color classes per urgency tier__tests__/auth/AuthProvider.test.tsx— 5 tests: renders children, null when empty localStorage, real token read, expired token guard, login stores to localStorage__tests__/lib/ApiClient.test.ts— 4 tests: Authorization header, 401 → onUnauthorized, ApiError on 404, POST JSON body + Content-Type; all API calls intercepted by MSW (no real network)
- Added
test,test:watch,test:coveragescripts topackage.json. pnpm testexits 0 ✅
Issue 5.3 — CI gates:
- Updated
.github/workflows/ci.yml: addedpnpm teststep to the frontend job (between lint and build); addedworkflow_dispatchtrigger; addeddorny/test-reporterfor backend JUnit XML results. - Backend
./gradlew testwas already in CI. Frontend build/lint already in CI. Unit tests now gate frontend job.
Issue 5.4 — Playwright E2E:
- Created
e2e/directory withpackage.json,playwright.config.ts, ande2e/tests/golden-path.spec.ts. - 4 tests: programs overview loads without 500, cases list renders rows, employee profile loads, full login→programs→cases→studio→logout flow.
- CI
e2ejob triggers only onworkflow_dispatch(manual) to avoid billing on every PR.
Verification:
corepack pnpm test✅ (17 tests, 3 files)corepack pnpm lint✅corepack pnpm build✅ (all 14 routes, including new/employees/[externalId])
Goal: Make the Admin panel demo-useful — meaningful integration health, a visible outreach delivery log (no real email on the demo stack), UI-editable notification templates, and a non-prod demo-reset tool.
Branch: feat/sprint-6-admin (worktree C:\workwell-wt\sprint-6-admin). Sprint doc V020/V021 numbering is stale; V021__add_outreach_delivery_log.sql was pre-authored by Taleef and used as-is. No migrations written/edited by the agent.
Issue 6.1 — Live integration health:
- Extended
IntegrationHealthService(not a new sync service): added@Scheduled(fixedDelay=900_000)scheduledRefresh()refreshing fhir+mcp+hris (AI is reactive).@EnableSchedulingalready present inBackendApplication. - FHIR check now instantiates
FhirContext.forR4Cached()(real CQL-engine smoke) →healthy/unhealthy. - HRIS is now a distinct first-class
simulatedstatus with message "Integration not connected — synthetic data only" (was a "healthy" stub). - Added
recordAiHealth(success, detail)lightweight status setter (no audit write — avoids per-call audit spam).AiAssistService.callWithModelFallbacknow calls it: success →healthy, both-models-failed →degradedwith root-cause reason. IntegrationHealthrecord JSON unchanged. Frontendadmin/page.tsxstatusBadgeClassextended: green=healthy, sky=simulated, amber=degraded/stale, red=unhealthy, gray=unknown. Existing per-integration "Manual Sync" button already re-fetches.
Issue 6.2 — Outreach delivery log + EmailService:
- Added
com.sendgrid:sendgrid-java:4.10.2. Createdcom.workwell.notification.EmailService+EmailDeliveryRecord. Provider switch onworkwell.email.provider(defaultsimulated); sendgrid path only active when both provider=sendgrid AND api-key set (degrades to simulated otherwise). CLAUDE.md hard rule honored:simulatedstays default; SendGrid not enabled. - Synthetic recipient: employees have no email column, so address is deterministic
<external_id>@workwell-demo.dev(obviously non-routable, stable across reruns). - Wired into
CaseFlowService.sendOutreach: renders subject/body, sends viaEmailService, inserts anoutreach_delivery_logrow, augments thecase_actionspayload withemailMessageId/deliveryProvider/emailDeliveryStatus/toAddress/sentAt.insertCaseActionnow returns the action UUID for FK linkage. - New
OutreachDeliveryLogService+GET /api/admin/outreach/delivery-log?limit=20(joins cases→measure_versions→measures for measure name). Admin UI delivery-log table with status colors. Case-detail timeline already surfaces OUTREACH_SENT payload, which now includes provider/status (no separate timeline change needed).
Issue 6.3 — Notification templates editable:
outreach_templates+ list/create/update endpoints already existed. AddedOutreachTemplateService.previewTemplate(id)+GET /api/admin/outreach-templates/{id}/previewsubstituting{employee_name}/{measure_name}/{due_date}/{assignee_name}. Admin UI section: list + inline edit (subject, body) via existing PUT + preview. "Reset to default" not shipped (optional per corrected scope; edit+preview covers the demo need).
Issue 6.4 — Demo reset:
- Created
com.workwell.admin.DemoResetService@Profile("!prod")@Transactional; truncates volatile tables in FK order with RESTRICT (also includesscheduled_appointments,outreach_records,evidence_attachments,data_readiness_snapshotswhich FK to cases/runs — required so RESTRICT does not fail), then resetsintegration_healthto unknown/null. POST /api/admin/demo-resetinjectsOptional<DemoResetService>→ 403 when absent (prod)./api/admin/**is already ROLE_ADMIN path-gated in SecurityConfig; no@EnableMethodSecuritypresent so no method-level annotation added. Admin UI two-step-confirm "Reset Demo Data" button with inline success message (no toast component in repo).
Audit caveat: demo reset truncates audit_events — in tension with the audit-integrity rule, but it is an explicitly sprint-sanctioned non-prod-only tool (@Profile("!prod"), 403 in prod).
Tests added: AdminControllerTest (preview ok/404, delivery-log, demo-reset success) + new AdminControllerDemoResetAbsentTest (403 when bean absent). Updated AiServiceIntegrationTest and AdminControllerTest constructors for new dependencies.
Verification: see end-of-entry. Docs updated same change: DATA_MODEL.md (outreach_delivery_log table), DEPLOY.md (email provider stays simulated), ARCHITECTURE.md (notification module note).
Goal: Clickable employee profiles aggregating cross-program compliance posture, functional global search, SLA countdowns on cases, and a My Cases view.
Sprint 1 merged (PR #18): Async run pipeline, scheduler, SITE/EMPLOYEE scoped runs, cases pagination — all shipped to main.
Issue 3.1 — Employee profile page (backend + frontend):
- Created
EmployeeProfileResponse.javaDTO incom.workwell.web.dtowith nested records:MeasureOutcomeSummary,OpenCaseSummary,AuditEventSummary. - Created
EmployeeProfileService.javaincom.workwell.runwithgetProfile(externalId)(4 SQL queries: employee base, latest outcome per measure viaDISTINCT ON (mv.measure_id), open cases, last 20 audit events) andsearch(q, limit)(LIKE pattern on name/externalId/role). UsesJdbcTemplate+ObjectMapperfor JSONB evidence parsing. - Created
EmployeeProfileController.javaat/api/employees/{externalId}/profileand/api/employees/search. Both@PreAuthorize("isAuthenticated()"). - Created
frontend/features/employee/hooks/useEmployeeProfile.ts— fetches the profile endpoint. - Created
frontend/features/employee/components/ComplianceSummaryBar.tsx— color-coded pill row per measure outcome. - Created
frontend/app/(dashboard)/employees/[externalId]/page.tsx— full profile page: header, compliance posture bar, open cases table (with SLA chip placeholder), measure detail accordion, recent activity timeline.
Issue 3.2 — Global search:
- Created
frontend/components/GlobalSearch.tsx— debounced (300ms) type-ahead search, calls/api/employees/search?q=..., shows name/role/site/outcome badge in dropdown, navigates to/employees/:externalId, closes on Escape or outside click. - Wired
<GlobalSearch />into the dashboard header inlayout.tsx. - Added
{ href: "/cases", label: "Cases" }nav item to the sidebar.
Issue 3.3 — SLA tracking (complete):
- Created
CaseSlaService.javaincom.workwell.caseflowwith:computeSlaDueDate(outcomeStatus): OVERDUE→14d, DUE_SOON→30d, MISSING_DATA→21d from now.@Scheduled(cron = "0 0 */6 * * *") escalateBreachedCases(): bumps priority one level, setssla_breached=TRUE, writesCASE_SLA_BREACHEDaudit event.BadSqlGrammarException(missing column) silently skipped for mixed-deployment safety; otherDataAccessExceptionlogged at ERROR.
V020__add_case_sla_due_date.sqladds columns and backfills with outcome-specific windows (OVERDUE 14d, MISSING_DATA 21d, DUE_SOON 30d via CASE expression).CaseFlowService.upsertOpenCaseinjectsCaseSlaServiceand writessla_due_dateon INSERT/reopen; preserves existingsla_due_dateandsla_breachedon update of already-open cases to prevent SLA resets by regular runs.CaseSummaryrecord andlistCases()query updated:sla_due_date,sla_breached, and computedslaRemainingDaysnow included in the cases API response.OpenCaseSummaryDTO andEmployeeProfileServiceupdated:slaBreachedflag included alongsideslaDueDate/slaRemainingDays.- Frontend:
SlaChipon employee profile page now receivesbreached={c.slaBreached}, so already-breached cases show "Breached" rather than "0d left".
Issue 3.4 — My Cases tab:
- Added "All Cases / My Cases" tab row to
cases/page.tsx. "My Cases" filters/api/cases?assignee={user.email}&view=mine. - Employee names in cases list, case detail, and runs page now link to
/employees/{employeeId}.
Branch: feat/sprint-3-employee-profile
Verification:
./gradlew.bat compileJava✅corepack pnpm lint✅corepack pnpm build✅ —/employees/[externalId]live in route table
Goal: Transform the run pipeline from synchronous-blocking to async with polling, enable the scheduler, implement SITE/EMPLOYEE scoped runs, and add cases pagination.
Sprint 2 merged: PR #17 (feat/sprint-2-demo-data-visual) merged to main. All V016–V019 migrations, trend charts, skeleton loaders, assignee personas, and reason code breakdown shipped.
Issue 1.1 + 1.4 — Async run execution & auto-refresh (backend + frontend):
- Created
AsyncConfig.java:runExecutorThreadPoolTaskExecutor (core=2, max=4, queue=20, graceful shutdown 120s). - Added
createPendingRun(),updateRunStatus(),setFailureSummary(), andfinalizeAsyncRun()toRunPersistenceService.finalizeAsyncRunreplicates the post-INSERT logic ofpersistAllProgramsRun(outcomes, case upserts, audit events, run finalization) on a pre-existing run row. - Added
createRunRecord()and@Async("runExecutor") executeRunAsync()toAllProgramsRunService. PrivateevaluateForScopeAsync()handles ALL_PROGRAMS, MEASURE, SITE, and EMPLOYEE dispatch. EvalController.POST /api/runs/manual: CASE scope stays synchronous (200 OK); all other scopes return HTTP 202 immediately with{runId, status: "REQUESTED", message}.- Frontend
programs/page.tsx: after triggering a run, storesactiveRunIdand pollsGET /api/runs/{id}every 5s. Button disabled while polling with "Running…" label. Auto-callsloadAll()on COMPLETED/PARTIAL_FAILURE. Toast on success/failure.
Issue 1.2 — Scheduler enabled:
application.yml: scheduler default changed toenabled: true, cron0 0 2 * * *(2AM UTC daily).ScheduledRunService.runScheduledAllPrograms()now uses the async path (createRunRecord+executeRunAsync) instead of blockingrunAllPrograms().
Issue 1.3 — SITE and EMPLOYEE scoped runs:
evaluateForScopeAsync()inAllProgramsRunServicehandles SITE (filters outcomes bysite) and EMPLOYEE (filters bysubjectId) scopes. Both require corresponding request fields or return 400.- Missing required field validation returns 400 via
IllegalArgumentException→ResponseStatusException.
Issue 1.5 — Cases load-more pagination:
CaseFlowService.listCases()extended withlimitandoffsetparameters; SQL query appended withLIMIT ? OFFSET ?. Existing call sites default to limit=50, offset=0.CaseController.GET /api/casesnow acceptslimit(default 25) andoffset(default 0).- Frontend
cases/page.tsx: initial load fetches 25 cases; "Load more" button appends the next 25.hasMoreflag hides the button when a page returns fewer than 25.
PR review fixes (Copilot/Codex findings on PR #18):
AllProgramsRunService: addedvalidateScopeRequest()— enforces required fields (site/employeeExternalId/measureId) before any DB write; throwsIllegalArgumentException→ 400. AddedresolveScopeId()to persistscope_idinrunsfor MEASURE-scoped runs. Added 3-argcreateRunRecordoverload soScheduledRunServicecan passtriggerType="scheduler"(previously hardcoded to "manual").RunPersistenceService.createPendingRun: extended to acceptscopeId (UUID)andevaluationDate (LocalDate)someasurement_period_start/endandscope_idare written at INSERT time rather than deferred.finalizeAsyncRun: now writesstarted_at,measurement_period_start/end, andduration_msin the final UPDATE to prevent timestamp drift in completed run records.CaseController: fixedlimitdefaultValueto"25"(was"50") to match frontendPAGE_SIZE.runs/page.tsx:ManualRunResponsefields made optional — the 202 async response only returns{runId, status, message}. Toast now usesdata.messagedirectly whenscopeLabelis absent, preventing "undefined - Run queued…".
CI speed fix:
- Created
AbstractIntegrationTest.javawith a single JVM-widestatic PostgreSQLContainerstarted via a static initialiser. All 15 integration test classes now extend it and no longer spin up their own container. Spring context caching reuses a singleApplicationContextacross the 10 plain@SpringBootTestclasses. Expected CI improvement: ~30 min → ~8 min.
Branch: feat/sprint-1-run-pipeline
Verification:
corepack pnpm lint✅corepack pnpm build✅./gradlew.bat compileTestJava✅ (review fixes + AbstractIntegrationTest compile clean)- CI run pending on PR #18 push.
Goal: Replace system-generated placeholder data with realistic personas, enrich the measure catalog, and make the trend charts interpretable.
Issue 2.1 — Measure owners and tags (V016):
- V016 migration updates owner to J. Chen (Audiogram), M. Patel (HAZWOPER), K. Williams (TB + Flu Vaccine).
- Sets
approved_by = 'Dr. R. Patel (Medical Director)'on all active measure versions. - Adds realistic tag arrays per measure (e.g.,
['surveillance','hearing','osha']). - Frontend: Tags in measures list now render as inline chip spans instead of comma-joined text.
Issue 2.2 — Additional measures catalog (V017):
- V017 migration adds Respirator Fit Test (Draft v0.9, J. Chen), Hepatitis B Vaccination Series (Approved v2.0, K. Williams), Lead Medical Surveillance (Deprecated v1.1, M. Patel).
- Catalog now shows 7 measures across all lifecycle states — matches v0 prototype richness.
- Each insert is wrapped in an idempotent DO $$ block.
Issue 2.3 — Trend charts with axes, tooltips, delta (V018 + backend + frontend):
- V018 migration seeds 5 months of MEASURE-scoped historical runs for all 4 active measures with gradual compliance decline for visual interest.
ProgramService.trend()extended with a UNION branch: outcome-based data (existing behavior) + run-level aggregate data for MEASURE-scoped runs that have no outcome rows — historical seed runs appear without needing full outcome data.- Replaced bare SVG
Sparklinewith RechartsLineChart: X-axis month labels, Y-axis percentage scale, hover tooltip with exact %, and a delta badge showing ↑/↓ % change from last run. - Added recharts 3.8.1.
Issue 2.4 — Top drivers reason code breakdown:
- Backend:
byOutcomeReasonquery now includes DUE_SOON (was OVERDUE + MISSING_DATA only);totalFlaggeddenominator updated to match. - Frontend: Rendered a new "By Reason" section below site/role drivers with color-coded chips (rose for Overdue, amber for Due Soon, slate for Missing Data) and count + percentage.
Issue 2.5 — Case assignees (V019):
- V019 migration assigns ~30% of open cases to Sarah Mitchell, ~30% to James Torres, ~40% remain unassigned.
- Idempotent: only updates rows where assignee IS NULL.
Issue 2.6 — Skeleton loading states:
- New shared
frontend/components/skeleton-loader.tsxwithSkeletonCardandSkeletonRowusing Tailwindanimate-pulse. - Programs Overview: 4 skeleton cards while loading (matches measure card shape).
- Cases list: 10 skeleton rows (8 cols) while loading.
- Runs list: 10 skeleton rows (7 cols) while loading.
Branch: feat/sprint-2-demo-data-visual
Verification:
corepack pnpm lint✅corepack pnpm build✅- Backend tests running (Testcontainers + Flyway V016–V019 applied).
Goal: Resolve the three non-blocking issues found during the automated live QA pass on the canonical Vercel deployment.
React hydration error #418 (frontend):
- Root cause:
AuthProviderinitialized session state with a lazy initializer that readslocalStorage— a mismatch because the server-side initializer returns null but the client-side re-initialization finds a stored token. - Fix: initialize with
{ token: null, user: null }always. A dedicateduseEffectreadslocalStorageafter mount and sets amountedflag. Redirect and cleanup effects gate onmountedso they wait until the localStorage check completes. - Effect: server and client initial renders match, hydration succeeds, no React error #418.
MCP health cosmetic issue (backend):
- Root cause:
IntegrationHealthService.checkMcpHealth()classified HTTP 401/403 from the SSE endpoint asdegraded. But 403 means the endpoint is reachable and correctly secured — not broken. - Fix: 401/403 responses now return
status=healthywith detail "MCP SSE reachable and secured by auth". Real failures (timeouts, 5xx, connection refused) still returndegraded.
Demo test fixtures missing (backend migration):
- Added
V015__seed_demo_test_fixtures.sqlwhich updatesspec_json->'testFixtures'for all 4 active measures. - Audiogram and HAZWOPER: 5 fixtures each (COMPLIANT, DUE_SOON, OVERDUE, MISSING_DATA, EXCLUDED).
- TB Surveillance: 5 fixtures (same coverage). Flu Vaccine: 3 fixtures (COMPLIANT, MISSING_DATA, EXCLUDED — matching current CQL outcomes).
- Migration is idempotent: only updates rows where
testFixturesis currently empty.
Verification:
corepack pnpm lint✅corepack pnpm build✅./gradlew.bat test✅ (all tests pass including V015 Flyway migration via Testcontainers)git diff --check✅
Goal: Keep the run history view readable by limiting the initial fetch, adding a progressive load-more control, and shrinking timestamp/ID copy.
Frontend run history update:
- Added
limit=20to the initial runs fetch and aLoad more runscontrol that increases the limit in 20-row increments. - Preserved the selected run when more rows are fetched so the detail pane stays stable.
- Switched the runs table to fixed layout, added a
Startedcolumn with relative timestamps, and shortened run IDs with hover titles for the full ID. - Added compact absolute timestamp helpers for hover text and detail-view timestamp formatting.
Verification:
git diff --checkcorepack pnpm lintcorepack pnpm build- Playwright browser harness on
http://localhost:3004/runswith mocked API/session: initial 20 rows,Load more runsfetched 40 rows, requests observedlimit=20thenlimit=40 - Playwright browser harness verified the first run timestamp rendered as
2h agowith the full timestamp in the hover title and stable header widths at 1280px
Goal: Stop the login entry path from triggering protected dashboard fetches so reviewers see zero console errors before they sign in.
Frontend auth/session gate:
- Added JWT expiration validation to the auth provider bootstrap so stale
ww_tokenvalues are treated as logged out before the UI renders. - Cleared any stored auth payloads that no longer represent a live session, without surfacing a visible error state.
- Routed the app root into the login flow and added a login-page redirect for already-authenticated sessions.
- Short-circuited the dashboard shell when no live session exists so the protected site and worklist fetches never run on unauthenticated entry.
Verification:
git diff --checkcorepack pnpm lintcorepack pnpm build- Playwright console on
http://localhost:3000/loginwith no session: zero errors - Playwright console on
http://localhost:3000/loginwith an expired token in localStorage: zero errors
Goal: Replace raw API/status enum strings with title-case labels across the visible frontend surfaces.
Frontend status cleanup:
- Added shared label helpers in
frontend/lib/status.ts, including enum normalization and a reusable fallback formatter. - Humanized the dashboard header role badge, programs overview role breakdown, runs filters/list/detail, case list/detail badges, measure list badges, studio measure header, and the admin integration/mapping badges.
- Updated the studio subpanels so compile status, readiness status, resolvability status, traceability severity, value-set labels, and test-fixture outcomes render as readable labels instead of all-caps enums.
Verification:
git diff --checkcorepack pnpm lintcorepack pnpm build
Goal: Make the login page look intentional and give reviewers a one-click path into the shared demo account.
Frontend branding:
- Reworked the login page into a branded split-panel auth screen with a WW monogram, product name, and tagline.
- Added a visible demo credential hint for
cm@workwell.dev / Workwell123!. - Added a
Use demo credentialsbutton that fills the login form without needing the demo mode flag. - Kept the existing login flow intact so sign-in still posts to the same auth endpoint.
Verification:
corepack pnpm lintcorepack pnpm buildSelect-Stringconfirmed the brand copy and the demo credential fill handler infrontend/app/login/page.tsx.
Goal: Hide the Admin entry from non-admin users and replace the broken admin skeleton/error state with a calm access-denied screen.
Frontend auth/UI gate:
- Conditioned the dashboard nav so the Admin link only renders for
ROLE_ADMIN. - Added a clean
Admin access requiredempty state for non-admin users on/admin. - Guarded admin page data-loading callbacks so non-admin visits do not trigger the error banner or fetch the admin data panels.
Verification:
corepack pnpm lintcorepack pnpm buildSelect-Stringconfirmed the Admin nav gate inlayout.tsxand the access-denied gate inadmin/page.tsx.
2026-05-14 — Sprint 0 Issue 0.3 global search hidden
Goal: Remove the non-functional global search bar from the dashboard header so the UI no longer advertises a broken interaction.
Frontend cleanup:
- Removed the global search form from the dashboard shell header.
- Deleted the unused local search state and submit handler from
layout.tsx. - Kept the site/date filters and account controls aligned in the header.
Verification:
corepack pnpm lintcorepack pnpm build- Confirmed
frontend/app/(dashboard)/layout.tsxno longer contains the global search placeholder or submit handler.
Goal: Prevent /programs/overview from being handled as a measure detail route and hanging on invalid API requests.
Frontend routing fix:
- Added a Next.js request-level redirect from
/programs/overviewto/programs. - Left UUID-based program detail routing unchanged so valid measure detail links continue to resolve through
/programs/[measureId]. - Confirmed the frontend source does not generate a browser link to
/programs/overview; existing/api/programs/overviewcalls are backend API calls from the real programs overview page.
Verification:
corepack pnpm lintcorepack pnpm build- Local production server check:
GET /programs/overviewreturned307withLocation=/programs.
Goal: Remove visible scaffold branding from the dashboard shell before demo review.
Frontend polish:
- Replaced the dashboard shell placeholder text with a compact WW mark and two-line
WorkWell / Measure Studioproduct identity. - Removed the truncating product-name class so the visible brand no longer renders as an ellipsis or scaffold label.
- Confirmed
MVP Dashboard Shellno longer appears in the frontend source.
Verification:
corepack pnpm lintcorepack pnpm build
Goal: Cut down backend GitHub Actions time on repeated pushes without changing product behavior.
CI optimizations:
- Added
gradle/actions/setup-gradle@v4to the backend job so Gradle wrapper, dependency, and build cache state can be reused between runs. - Enabled Gradle caching and configuration caching in
backend/gradle.properties. - Set
maxParallelForks = 2for CI test runs so Spring/Testcontainers classes can overlap a bit on the hosted runner. - Added pnpm caching for the frontend job and top-level workflow concurrency so newer pushes cancel stale in-progress runs.
Verification:
./gradlew help./gradlew build --configuration-cache --dry-run
Goal: Stabilize the deployed backend for remote MCP clients and document the required Claude Desktop configuration.
Fly config change:
backend/fly.tomlnow keepsmin_machines_running = 1so the backend stays warm for long-lived MCP SSE connections instead of scaling to zero and dropping the transport.
MCP auth note:
- Verified the remote SSE endpoint returns
200with a validROLE_ADMINJWT from/api/auth/login. mcp-remoteworks with--header Authorization:${AUTH_HEADER}and--transport sse-onlyfor this backend.
Docs updated:
docs/MCP.mdnow includes the exact Claude Desktop /mcp-remoteconfig with bearer auth.docs/DEPLOY.mdnow calls out the warm-machine requirement and the authenticated MCP connection requirement.
Goal: Extend MCP with authenticated, audited agent tools for employee compliance inspection, non-compliant case listing, and deterministic rule explanation — without AI, without bypassing security, without creating official records in preview mode.
New MCP v2 tools added (McpServerConfig.java):
get_employee— employee summary + last 5 compliance outcomes by externalId; returns EMPLOYEE_NOT_FOUND safe error if not foundcheck_compliance— latest or preview compliance status for employee/measure; both modes query persisted CQL outcomes only;complianceDecisionSourcealways"cql_outcome"; no AI consultedlist_noncompliant— open cases with DUE_SOON/OVERDUE/MISSING_DATA filter; default limit 25, max 100 enforced in SQL; INVALID_ARGUMENT returned for unknown status valuesexplain_rule— measure policy ref, eligibility, compliance window, CQL define names, value sets fromMeasureService;source: "deterministic_metadata"; no AIget_measure_traceability— delegates toMeasureTraceabilityService.generate(); returns rows + gapslist_data_quality_gaps— delegates toDataReadinessService.computeReadiness(); returns blockers + warnings + element readiness
Preserved/unchanged: get_case, list_cases, get_run_summary, list_measures, get_measure_version, list_runs, explain_outcome — all retain existing executeTool audit wrapper and safe error handling.
MCP server version: bumped 1.1.0 → 2.0.0.
Tests added (McpSecurityIntegrationTest):
- getEmployeeReturnsNotFoundForUnknownExternalId
- checkComplianceLatestModeReturnsNoOutcomeForUnknownEmployee
- checkCompliancePreviewModeDoesNotCallAi
- listNoncompliantEnforcesLimitCap (999 → capped at 100)
- listNoncompliantRejectsInvalidStatus (COMPLIANT → INVALID_ARGUMENT)
- explainRuleRequiresMeasureIdOrName
- explainRuleReturnsDeterministicMetadataWithSourceField
- mcpToolsAuditActorFromSecurityContext
McpServerConfigTest updated: added MeasureTraceabilityService and DataReadinessService mocks; version assertion updated to 2.0.0.
Docs updated:
docs/MCP.md— full v2 tool inventory table, schema examples, safe error codes, audit record format, tool posture guaranteesdocs/new instructions/README_09_MCP_V2_SAFE_AGENT_TOOLS.md— implementation progress section added
Design decision — preview mode:
check_compliance preview resolves to the same persisted data as latest, labeled source="preview". Real-time per-employee CQL re-evaluation from MCP is intentionally deferred — inline CQL eval from MCP would create unaudited transient state and adds latency. Operators needing fresh data trigger a manual run.
Goal: Stabilization and quality pass after the big merge. No new product features.
CI:
- Added
pnpm buildstep to frontend CI job (.github/workflows/ci.yml). Previously only lint ran; type errors and build failures would slip through.
Security role integration tests:
- Added
SecurityRoleIntegrationTest(backend/src/test/java/com/workwell/config/) — 14 tests exercising role boundaries end-to-end with auth enabled and a real Postgres container. - Covers: unauthenticated GET/POST fails (403), VIEWER can read but cannot mutate cases/runs/admin, AUTHOR can edit spec but cannot approve/activate, APPROVER cannot access admin endpoints or case actions, ADMIN can access admin endpoints,
/api/evalinternal-header enforcement. - Prior
MeasureControllerTest,CaseControllerTest,EvalControllerTestall useaddFilters=false— they test controller wiring only. This test fills the auth-enforcement gap.
Manual QA checklist:
- Created
docs/DEMO_QA_CHECKLIST.md— covers Author flow, Approver/Admin flow, Case Manager flow, Security checks, and MCP verification. Each step has an explicit expected outcome and pass column.
Docs status:
docs/MCP.md— verified current (merged in PR #5 and reflects actual endpoint, roles, tool list, audit behavior).docs/ARCHITECTURE.md,docs/DATA_MODEL.md,docs/README.md— all updated in the big merge; spot-checked as current.docs/TODO.md— intentionally archived todocs/archive/TODO_old_v2.md; no replacement needed (active backlog lives in the README_XX instruction series).
Remaining README_08 acceptance items:
- Actor identity tests for outreach/AI draft/rerun audit actor: partially covered (
CaseControllerTestspoofed-actor test,CaseViewAuditIntegrationTest,AdminControllerTestspoofed sync actor). Outreach and AI draft actor assertions can be strengthened in follow-up if regression is observed. - Playwright E2E tests: deferred; stack has no Playwright setup yet, README_08 marks these optional.
Five blocking fixes applied to fix/p0-secure-mcp after Codex review of PR #5:
Fix 1 — Protect evidence metadata listing (ROLE_CASE_MANAGER | ROLE_ADMIN only)
EvidenceService.list()— addedensureListAllowed()service-layer guard throwingAccessDeniedExceptionfor unauthorized roles.SecurityConfig— added explicitGET /api/cases/*/evidencerule withhasAnyAuthority("ROLE_CASE_MANAGER", "ROLE_ADMIN")before the broadGET /api/**permit-all rule.EvidenceAccessIntegrationTest— 5 new tests: CASE_MANAGER and ADMIN can list (200), AUTHOR and APPROVER cannot (403), unauthenticated gets 401/403.
Fix 2 — Apply impact preview scope filtering
MeasureImpactPreviewService.preview()—request.scope()was accepted but never applied. Now routes all outcomes throughapplyScope(outcomes, scope)before counting and building breakdowns. Adds a warning when the scope matches zero employees.
Fix 3 — Filter case impact by evaluation period
estimateCaseImpactSQL — addedAND c.evaluation_period = ?clause so existing open cases from prior evaluation periods don't inflate "would update" counts for the preview period.
Fix 4 — Return 400 for invalid evaluationDate
resolveEvaluationDate()— now throwsIllegalArgumentExceptionwith "evaluationDate" in the message instead of silently defaulting to today.MeasureController.impactPreview()— catch block distinguishes 400 (message contains "evaluationDate") from 404 (measure not found).
Fix 5 — Resolve case reruns from persisted requested_scope_json
AllProgramsRunService.loadCaseIdForRun()— reads(requested_scope_json->>'caseId')::uuidfirst; falls back to the legacylast_run_idlookup only when the JSON path is absent. Prevents rerun failure after a second rerun advanceslast_run_idpast the source run.- Fix 5b (discovered during test run): the JSON existence check used
requested_scope_json ? 'caseId'which JDBC interprets as a second parameter placeholder, causingPSQLException: No value specified for parameter 2. Replaced withjsonb_exists(requested_scope_json, 'caseId')— consistent with the pattern documented in DEPLOY.md.
Regression tests added:
MeasureImpactPreviewIntegrationTest— 9 new tests: scope filtering (site, employee, nonexistent), invalid date throws + returns 400, blank date defaults to today, case impact counts for fresh preview (uses far-future date), case impact ignores cases from a different evaluation period. Test for the period-isolation case inserts a synthetic employee row (CQL evaluator uses in-memory FHIR, not the employees table) and queriesmeasure_versionsbymeasure_idUUID rather than by name.ScopedRunIntegrationTest— newcaseRerunSameScopeSucceedsEvenAfterLastRunIdIsStale: runs CASE scope, SQL-advancescases.last_run_idto a synthetic later run, then callsrerunSameScopeon the original run ID and asserts success via JSON-based caseId resolution.
Completed:
Backend:
- Migration
V014__audit_packet_exports.sql— createsaudit_packet_exportstable (id, packet_type, entity_id, format, generated_by, generated_at, payload_hash, payload_size_bytes). Records each packet generation for accountability. - Added
AuditPacketService(com.workwell.audit) — assembles and serializes audit export packets for 3 entity types:buildCasePacket(caseId, actor, format)— case summary, employee context, measure/version, CQL decision evidence, timeline split into actions/audit events/AI assistance, appointments, attachment metadata, outreach records, disclaimers.buildRunPacket(runId, actor, format)— run metadata, scope, summary counters, run logs, audit events, disclaimers.buildMeasureVersionPacket(measureVersionId, actor, format)— measure metadata, spec, CQL text+SHA-256 hash, compile result, value sets, VS governance check, traceability matrix, data readiness, approval history, audit events, disclaimers.- Each packet serialized to JSON bytes (or HTML with a table overview + JSON appendix). SHA-256 payload hash stored in
audit_packet_exports. WritesAUDIT_PACKET_GENERATEDaudit event on every generation. - Optional services (traceability, data readiness, VS governance) wrapped in safe try-catch; failures return empty section rather than aborting the packet.
- Added
AuditorController(com.workwell.web) — 3 GET endpoints:/api/auditor/cases/{caseId}/packet,/api/auditor/runs/{runId}/packet,/api/auditor/measure-versions/{measureVersionId}/packet. Query paramformat=json|html(default json). Unsupported format → 400. Missing entity (IllegalArgumentException) → 404. Role checks viaSecurityActor.hasAnyAuthority: CASE/RUN →ROLE_CASE_MANAGER|ROLE_ADMIN; MEASURE_VERSION →ROLE_APPROVER|ROLE_ADMIN. - Tests:
AuditorControllerTest(6 tests,@WebMvcTest,@WithMockUserrole annotations): json/html/run/measure-version OK, unsupported-format 400, missing-entity 404.
Frontend:
runs/page.tsx— addedexportRunAuditPacket()helper and "Export Run Audit Packet" button in the Run Detail panel, visible when a run is selected.cases/[id]/page.tsx— addedexportCaseAuditPacket()helper and "Export Case Audit Packet" button in the page header alongside the case ID.ReleaseApprovalTab.tsx— derives current measure version ID fromversionHistory.find(v => v.version === measure.version)?.id; addedexportMeasurePacket()helper and "Export Measure Audit Packet" button above the lifecycle action buttons.
Verification:
- Backend AuditorControllerTest: 6/6 pass
- Backend full test suite: no regressions
- Frontend lint: exit 0
- Frontend build: all routes compiled, TypeScript clean
Completed:
Backend:
- Migration
V013__value_set_governance.sql— extendsvalue_setswith 7 governance columns (canonical_url,code_systems,source,status,expansion_hash,resolution_status,resolution_error). Seeds 4 demo value sets with fixed UUIDs and non-emptycodes_json(RESOLVED status). Createsterminology_mappingstable; seeds 5 demo mappings (3 APPROVED, 1 REVIEWED, 1 PROPOSED). - Added
ValueSetGovernanceService(com.workwell.measure) —resolveCheck(measureId),diff(fromId, toId),getValueSetDetail(id),listTerminologyMappings(),createTerminologyMapping(...). Lazy demo value set linking viaensureDemoValueSetLinks()called on resolve-check. CQL unattached reference detection via line-scan forvalueset "Name"declarations. - Extended
MeasureController.activationReadiness()to merge VS governance blockers into the base readiness result. AddedPOST /api/measures/{id}/value-sets/resolve-check,GET /api/value-sets/{id}/diff,GET /api/value-sets/{id}/detail. - Extended
AdminController— addedGET /api/admin/terminology-mappingsandPOST /api/admin/terminology-mappings. - Integration tests:
ValueSetGovernanceIntegrationTest(6 tests, Testcontainers, requires Docker). - Controller unit tests updated:
MeasureControllerTest(2 new tests),AdminControllerTest(1 new test).
Frontend:
- Added
ValueSetCodeEntry,ValueSetDetail,ValueSetCheckItem,ResolveCheckResponse,AffectedMeasure,ValueSetDiffResponse,TerminologyMappingtypes tofeatures/studio/types.ts. - Created
ValueSetGovernancePanel.tsx— auto-loads on mount, Re-check button, overall ALL RESOLVED / BLOCKERS FOUND badge, blockers list, warnings list, per-value-set table (name, version, resolution status badge, code count). - Embedded
ValueSetGovernancePanelinValueSetsTab(authoring view) andReleaseApprovalTab(after DataReadinessPanel). - Added Terminology Governance section to
admin/page.tsx— table of all mappings with status badge, confidence %, reviewed by, notes.
Verification:
- Frontend lint: exit 0
- Frontend build: all 12 routes compiled, TypeScript clean
- Controller unit tests: MeasureControllerTest + AdminControllerTest pass (WebMvcTest, no Docker)
- Integration tests: ValueSetGovernanceIntegrationTest (6 tests, Testcontainers with Docker Desktop)
Completed:
Backend:
- Migration
V012__data_readiness.sql— addsintegration_sources,data_element_mappings, anddata_readiness_snapshotstables; seeds 4 integration sources (hris, fhir, ai, mcp) and 15 canonical element mappings covering all 4 demo measures. - Added
DataReadinessService(com.workwell.admin) —listMappings(),validateMappings()(syncs fromintegration_health, marks STALE on degraded source),computeReadiness(UUID measureId)(per-element missingness + freshness + blocker/warning classification). - Added
GET /api/admin/data-mappingsandPOST /api/admin/data-mappings/validatetoAdminController. - Added
GET /api/measures/{id}/data-readinesstoMeasureController. - Integration tests:
DataReadinessIntegrationTest(6 tests, Testcontainers, requires Docker). - Controller unit tests updated:
AdminControllerTest(2 new tests),MeasureControllerTest(1 new test).
Frontend:
- Added
DataElementMapping,RequiredElementReadiness,DataReadinessResponsetypes tofeatures/studio/types.ts. - Created
DataReadinessPanel.tsx— loads data-readiness, shows overall status badge, blockers, warnings, per-element table (canonical, source, mapping status, freshness, missingness with sample employees), link to Admin. - Embedded
DataReadinessPanelinReleaseApprovalTababove version history. - Added Data Readiness Cockpit section to
admin/page.tsx— data element mappings table with Validate Mappings button.
Verification:
- Frontend lint: exit 0
- Frontend build: all 12 routes compiled, TypeScript clean
Completed:
Backend — Traceability:
- Added
MeasureTraceabilityService— builds a policy-to-evidence matrix from spec fields, CQL defines (parsed via regex), value sets, test fixtures, and runtime evidence keys. Generates gaps: missing policy citation, bad compile status, missing test fixtures, missing MISSING_DATA/EXCLUDED fixture coverage, unlinked value sets. - Added
GET /api/measures/{id}/traceabilityinMeasureController. - Integration tests:
MeasureTraceabilityIntegrationTest(5 tests, Testcontainers). - Controller unit tests added in
MeasureControllerTest.
Backend — Impact Preview:
- Added
MeasureImpactPreviewService— dry-run CQL evaluation; does NOT callrunPersistenceServiceorcaseFlowService. Counts outcomes, estimates case impact by querying existing open cases, builds site/role breakdown maps, writesMEASURE_IMPACT_PREVIEWEDaudit event. - Added
POST /api/measures/{id}/impact-previewinMeasureController. - Integration tests:
MeasureImpactPreviewIntegrationTest(7 tests, Testcontainers +@WithMockUser). - Note: Testcontainers integration tests require Docker Desktop; they pass in CI but are skipped when Docker is unavailable.
Frontend:
- Added
TraceabilityValueSetRef,TestFixtureRef,TraceabilityRow,TraceabilityGap,TraceabilityResponse,CaseImpact,ImpactPreviewResponsetofeatures/studio/types.ts. - Created
features/studio/components/TraceabilityTab.tsx— loads traceability matrix, renders summary card, error/warning gap panels, 7-column policy-to-evidence table, Export JSON button. - Created
features/studio/components/ImpactPreviewPanel.tsx— "Preview Activation Impact" button, outcome count cards (COMPLIANT/DUE_SOON/OVERDUE/MISSING_DATA/EXCLUDED), case impact summary, warnings panel, "preview only" disclaimer note. - Embedded
ImpactPreviewPanelinReleaseApprovalTababove the Activate Measure button (shown when measure is in Approved state). - Added "Traceability" tab to
studio/[id]/page.tsxTab union and tab bar.
Verification:
- Frontend lint: exit 0
- Frontend build:
✓ Compiled successfully, all 12 routes built MeasureControllerTest(WebMvcTest, no Docker): all 5 tests pass
Completed:
- Extracted all types into
frontend/features/studio/types.ts. - Extracted pure helper functions into
frontend/features/studio/utils.ts(parseCompileIssue,formatIssue,compileStatusClass,valueSetBadgeClass). - Created
hooks/useMeasureDetail.ts— loads measure + activation readiness + version history; returns state +loadrefresh callback. - Created
hooks/useValueSets.ts— loads global value set catalog; returnsallValueSets+load. - Created
hooks/useOshaReferences.ts— loads OSHA reference options; returnsoshaReferences+load. - Created tab components that own their own local state and take
api/measureId/callbacks as props:components/SpecTab.tsx— spec form with AI draft, owns policyRef/description/etc.components/CqlTab.tsx— Monaco editor + compile error markers.components/ValueSetsTab.tsx— attach/detach/create value sets.components/TestsTab.tsx— fixture CRUD + validate.components/ReleaseApprovalTab.tsx— readiness checklist, version history, lifecycle confirmation modals.
- Route page
studio/[id]/page.tsxreduced from 944 to ~120 lines: param parsing, hook composition, tab navigation, and shell rendering only.
Verification:
- Frontend lint:
frontend\\corepack pnpm lint-> exit 0 - Frontend build:
frontend\\corepack pnpm build->✓ Compiled successfully, all 12 routes built
Completed:
- Created
frontend/lib/api/errors.ts—ApiErrorclass with typed status helpers (isUnauthorized,isForbidden,isNotFound,isClientError,isServerError). - Created
frontend/lib/api/client.ts—ApiClientclass that readsNEXT_PUBLIC_API_BASE_URL, attachesAuthorization: Bearer <token>, handles 401 viaonUnauthorizedcallback, and throwsApiErroron non-OK responses. Methods:get,post,put,delete,postForm,downloadBlob. - Created
frontend/lib/api/hooks.ts—useApi()hook composinguseAuth()+ApiClient; recreates client only when token or logout changes. - Removed the entire
window.fetchmonkey-patchuseEffectfromfrontend/components/auth-provider.tsx. Auth-provider is now a clean context provider with no global side effects. - Migrated all 9 dashboard pages from bare
fetch()+ inlineapiBasepatterns touseApi():app/(dashboard)/layout.tsxapp/(dashboard)/measures/page.tsxapp/(dashboard)/programs/page.tsxapp/(dashboard)/programs/[measureId]/page.tsxapp/(dashboard)/runs/page.tsxapp/(dashboard)/cases/page.tsxapp/(dashboard)/cases/[id]/page.tsxapp/(dashboard)/studio/[id]/page.tsxapp/(dashboard)/admin/page.tsx
- Evidence download in
cases/[id]converted from plain<a href>to a button callingapi.downloadBlob()so the Authorization header is sent (role-protected endpoint). - Fixed two rounds of lint: re-added
// eslint-disable-next-line react-hooks/set-state-in-effectbeforevoid loadXxx()calls in effects; added missing stable setState refs touseCallbackdep arrays incases/page.tsxperreact-hooks/preserve-manual-memoization. login/page.tsxintentionally left using barefetch()— no token at login time, correct behavior.
Verification:
- Frontend lint:
frontend\\corepack pnpm lint-> exit 0 (0 errors, 0 warnings) - Frontend build:
frontend\\corepack pnpm build->✓ Compiled successfully, all 12 routes built
Completed:
- Added a typed
ManualRunRequest/RunScopeTypecontract and routed/api/runs/manualthrough the shared scoped-run executor. - Preserved
ALL_PROGRAMSbehavior, addedMEASUREscope, addedCASEscope, and made CASE reuse the structured rerun-to-verify path. - Persisted scoped-run request metadata, run lifecycle status, failure summary, and partial-failure counts in the
runstable. - Added durable run logs for requested, scope resolved, evaluation, persistence, and completion steps.
- Updated the runs/programs UI to send
scopeTypepayloads and expose a simple scoped run control surface. - Added regression tests for scoped measure runs, case reruns, unsupported scopes, and existing run-controller behavior.
Verification:
- Focused backend tests:
backend\\./gradlew.bat test --tests "com.workwell.run.ScopedRunIntegrationTest" --tests "com.workwell.web.EvalControllerTest" --tests "com.workwell.web.RunControllerTest" --tests "com.workwell.run.Major1PopulationIntegrationTest"-> PASS - Full backend test suite:
backend\\./gradlew.bat test --console=plain-> PASS - Backend build:
backend\\./gradlew.bat build --console=plain-> PASS - Frontend lint:
frontend\\corepack pnpm lint-> PASS - Frontend build:
frontend\\corepack pnpm build-> PASS
Completed:
- Confirmed MCP routes are authenticated and role-gated at
/sseand/mcp/**, withMCP_TOOL_CALLEDaudit rows using the authenticated security-context actor. - Removed the spoofable
actorquery parameter from the admin integration sync endpoint. - Removed the spoofable
resolvedByrequest-body field from manual case resolution and normalized closed-by bookkeeping to the authenticated actor. - Updated the frontend case detail resolve action to stop sending a caller-controlled actor field.
- Added regression tests for spoofed admin sync requests, spoofed case-resolution bodies, authenticated run reruns, authenticated manual run triggers, authenticated measure-status audit rows, and safe MCP invalid-argument handling.
Verification:
- Backend targeted tests:
backend\\./gradlew.bat test --tests "com.workwell.web.AdminControllerTest" --tests "com.workwell.web.CaseControllerTest" --tests "com.workwell.web.EvalControllerTest" --tests "com.workwell.web.RunControllerTest" --tests "com.workwell.measure.MeasureServiceIntegrationTest" --tests "com.workwell.mcp.McpSecurityIntegrationTest"-> PASS - Backend full suite:
backend\\./gradlew.bat test --console=plain-> PASS - Backend build:
backend\\./gradlew.bat build --console=plain-> PASS - Frontend lint:
frontend\\corepack pnpm lint-> PASS - Frontend build:
frontend\\corepack pnpm build-> PASS
Completed:
- Replaced the hardcoded CORS origin patterns with exact-origin configuration driven by
WORKWELL_CORS_ALLOWED_ORIGINS. - Added
StartupSafetyValidatorto fail startup in production-like deployments when auth is disabled, the JWT secret is weak or missing, localhost/wildcard CORS is configured, or backend demo mode is enabled without an explicit public-demo override. - Added backend tests for production-like auth disablement, wildcard and localhost CORS rejection, weak JWT secret rejection, exact-origin success, and demo-mode override behavior.
- Added frontend production-build enforcement so
NEXT_PUBLIC_DEMO_MODE=truefailsnext build.
Verification:
- Focused backend config tests:
backend\\./gradlew.bat test --tests "com.workwell.config.StartupSafetyValidatorTest" --tests "com.workwell.config.SecurityConfigCorsTest"-> PASS - Full backend suite:
backend\\./gradlew.bat test --console=plain-> PASS - Backend build:
backend\\./gradlew.bat build --console=plain-> PASS - Frontend lint:
frontend\\corepack pnpm lint-> PASS - Frontend build:
frontend\\corepack pnpm build-> PASS - Frontend negative guard check:
NEXT_PUBLIC_DEMO_MODE=true frontend\\corepack pnpm build-> FAIL as expected with the explicit unsafe-configuration error
Completed:
- Sanity-checked the rerun-to-verify path after commit
518f378and confirmed the case rerun now flows through the structured CQL evaluator instead of fabricating a COMPLIANT outcome. - Hardened evidence access so uploads and downloads are restricted to
ROLE_CASE_MANAGERandROLE_ADMIN, downloads resolve the linked case first, and download responses are audited asEVIDENCE_DOWNLOADED. - Added regression coverage for compliant, excluded, due-soon, overdue, and missing-data rerun branches plus evidence upload/download authorization, sanitization, and audit logging.
Verification:
- Focused backend slice:
backend\\./gradlew.bat test --tests "com.workwell.compile.CqlEvaluationServiceTest" --tests "com.workwell.caseflow.CaseFlowRerunIntegrationTest" --tests "com.workwell.web.EvidenceAccessIntegrationTest"-> PASS - Full backend test suite:
backend\\./gradlew.bat test-> PASS - Backend build:
backend\\./gradlew.bat build-> PASS
Completed:
- Replaced the case rerun-to-verify shortcut with a real structured CQL evaluation of the case subject using the persisted measure CQL and evaluation period.
- Preserved non-compliant reruns as open/in-progress cases and only close on structured compliant or excluded outcomes.
- Added a single-subject evaluation path to
CqlEvaluationServiceand a regression test proving it matches the batch evaluator for the same employee. - Added an integration test that seeds an open case, reruns it, and verifies the case does not fake COMPLIANT, persists the actual rerun outcome, and avoids
CASE_RESOLVEDon non-compliant reruns. - Updated the product docs to describe the real rerun-to-verify behavior.
Verification:
- Targeted backend regression tests:
backend\\./gradlew.bat test --tests "com.workwell.compile.CqlEvaluationServiceTest" --tests "com.workwell.caseflow.CaseFlowRerunIntegrationTest"-> PASS - Full backend test suite:
backend\\./gradlew.bat test-> PASS - Backend build:
backend\\./gradlew.bat build-> PASS
Completed:
- Fixed the seeded CQL evaluation path so runs use the actual
Measureobject instead of asking the CQF processor to resolve the measure back out of the in-memory repository. - Adjusted the TB and HAZWOPER recency logic to use explicit code-based procedure filtering, which keeps the demo measures compatible with the CQF in-memory evaluator.
- Added regression coverage for TB, HAZWOPER, and Flu seeded evaluation outcomes in
CqlEvaluationServiceTest. - Kept the review-driven hardening already in place across backend and frontend:
status=excludedcase filtering now works end to end- dashboard global filters preserve search/site/date query state
- login demo credentials are gated behind
NEXT_PUBLIC_DEMO_MODE - invalid date inputs now return 400 in the case/run/admin controllers
- JWT auth now fails fast if the default secret is used while auth is enabled
- evidence uploads validate file signatures instead of trusting client MIME types
- Updated
docs/MEASURES.mdwith a short implementation note for the TB/HAZWOPER CQF compatibility choice.
Verification:
- Backend full suite:
backend\\./gradlew.bat test-> PASS - Frontend lint:
corepack pnpm lint-> PASS - Frontend build:
corepack pnpm build-> PASS
Completed:
- Added
backend/src/main/resources/db/migration/V010__osha_references.sql:- creates
osha_references - adds
measure_versions.osha_reference_id - seeds 8 common occupational health citations
- backfills existing matching measure versions where policy text already matches a curated citation
- creates
- Added
GET /api/osha-referencesso the frontend can load curated OSHA policy choices. - Replaced the Studio Spec tab policy reference text input with a searchable combobox.
- Kept free-text fallback for non-OSHA references while persisting the selected
osha_reference_idthrough the measure version save/load path.
Verification:
- Backend compile + targeted measure tests:
backend\\./gradlew.bat compileJava test --tests "com.workwell.measure.MeasureServiceIntegrationTest" --tests "com.workwell.web.MeasureControllerTest"-> PASS - Frontend lint:
corepack pnpm lint-> PASS - Frontend build:
corepack pnpm build-> PASS
Completed:
- Added
@monaco-editor/reactto the frontend and replaced the Studio CQL textarea with a Monaco editor. - Kept the editor in the CQL tab controlled by the existing
cqlTextstate, so content persists across tab switches. - Enabled SQL syntax highlighting, dark theme, automatic layout, and preserved view state for a smoother authoring experience.
- Updated backend CQL compile validation messages to include line/column prefixes so frontend error markers can target the exact location.
- Parsed backend compile errors into Monaco markers, so compile failures now show red squiggles at the offending line and column.
Verification:
- Backend compile + compile-validation test:
backend\\./gradlew.bat compileJava test --tests "com.workwell.compile.CqlCompileValidationServiceTest"-> PASS - Frontend lint:
corepack pnpm lint-> PASS - Frontend build:
corepack pnpm build-> PASS
Completed:
- Added waiver persistence and exclusion context:
- Migration
backend/src/main/resources/db/migration/V009__waivers.sql waiverstable linking employee, measure, measure version, reason, grant metadata, expiry, notes, and active state
- Migration
- Added
WaiverServicefor listing, granting, and resolving active waivers for excluded cases. - Updated
CaseFlowServiceso EXCLUDED outcomes now createEXCLUDEDcases instead of disappearing from the workflow. - Added worklist and case-detail support for excluded cases:
- Excluded filter tab on
/cases - Waiver expiry / expired warning cue in case detail
- Outreach actions disabled for excluded cases
- Excluded filter tab on
Verification:
- Backend compile:
backend\\./gradlew.bat compileJava-> PASS - Backend controller tests:
backend\\./gradlew.bat test --tests "com.workwell.web.CaseControllerTest" --tests "com.workwell.web.AdminControllerTest"-> PASS - Backend integration tests:
backend\\./gradlew.bat test --tests "com.workwell.web.CaseControllerTest" --tests "com.workwell.web.AdminControllerTest" --tests "com.workwell.run.Major1PopulationIntegrationTest" --tests "com.workwell.run.CaseViewAuditIntegrationTest" --tests "com.workwell.ai.AiServiceIntegrationTest"-> PASS after Docker Desktop was started so Testcontainers could connect
- Frontend lint:
frontend\\npm run lint-> PASS - Frontend build:
frontend\\npm run build-> PASS
Completed:
- Added
CaseAccessAuditServiceto emitCASE_VIEWEDaudit events asynchronously from case detail reads. GET /api/cases/{id}now records the access event without adding it to the case timeline.- Added
AuditQueryServiceplusGET /api/admin/audit-eventsso the admin UI can filter access events apart from mutations. - Admin audit page now exposes access/mutation filters and shows
CASE_VIEWEDrows under Access Events.
Verification:
- Covered by the same backend test slice above, including
CaseControllerTest,AdminControllerTest, andCaseViewAuditIntegrationTest.
Completed:
- Added auto-queue behavior during case creation:
CaseFlowService.upsertOpenCase(...)now creates anoutreach_recordsrow for newly createdDUE_SOON,OVERDUE, andMISSING_DATAcases.- Writes
NOTIFICATION_AUTO_QUEUEDaudit events with template/outcome payload. EXCLUDEDoutcomes intentionally skip outreach creation.
- Added outreach template coverage for missing data:
- Migration
backend/src/main/resources/db/migration/V008__missing_data_follow_up_template.sql - Seeds
Missing Data Follow-Up
- Migration
- Made outreach persistence visible for manual actions too:
- manual
Send outreachnow writes anoutreach_recordsrow withauto_triggered = false - appointment reminder rows already continue to write as queued outreach records
- manual
- Added UI signal for outreach source:
- case timeline now shows
AutoandManualbadges on outreach-related rows - dashboard nav now shows a Worklist badge for open cases that still have no outreach queued
- case timeline now shows
Verification:
- Backend compile:
backend\\./gradlew.bat compileJava-> PASS - Backend web tests:
backend\\./gradlew.bat test --tests "com.workwell.web.RunControllerTest" --tests "com.workwell.web.CaseControllerTest" --tests "com.workwell.web.ProgramControllerTest"-> PASS
- Backend integration tests:
backend\\./gradlew.bat test --tests "com.workwell.run.Major1PopulationIntegrationTest.manualRunAutoQueuesOutreachForNonCompliantOutcomesAndSkipsExcluded"-> PASSbackend\\./gradlew.bat test --tests "com.workwell.run.Major1PopulationIntegrationTest.manualRunPersistsOneHundredOutcomesPerMeasureAndTbHighCompliance"-> PASS
- Frontend lint:
frontend\\npm run lint-> PASS - Frontend build:
frontend\\npm run build-> PASS
Completed:
- Added global dashboard filter context:
frontend/components/global-filter-context.tsx- Provides
siteId,from,to, and date presets (7d,30d,90d,all).
- Wired dashboard header controls in
frontend/app/(dashboard)/layout.tsx:- Site selector populated from backend sites endpoint.
- Date preset selector in top navigation.
- Navigation links preserve active
site/from/toquery values.
- Added backend filter parameters:
GET /api/runsacceptssite,from,to.GET /api/casesacceptsfrom,to(existing site filter retained).GET /api/programs+GET /api/programs/overviewacceptsite,from,to.GET /api/programs/{measureId}/trend+/top-driversacceptsite,from,to.- Added
GET /api/programs/sitesfor distinct site values.
- Updated dashboard pages to apply global filters:
/programsrequests overview/trend/top-drivers with global params./runsrequests list with global params./casesapplies global date range and global site fallback.
Verification:
- Backend compile:
backend\\./gradlew.bat compileJava-> PASS - Backend targeted web tests:
backend\\./gradlew.bat test --tests "com.workwell.web.RunControllerTest" --tests "com.workwell.web.CaseControllerTest" --tests "com.workwell.web.ProgramControllerTest"-> PASS
- Frontend lint:
frontend\\npm run lint-> PASS - Frontend build:
frontend\\npm run build-> PASS
Completed:
- Added DB migration:
backend/src/main/resources/db/migration/V007__outreach_templates.sql- Creates
outreach_templatestable and seeds four templates for outreach/reminder flows.
- Removed fragile fallback behavior:
OutreachTemplateService.listTemplates()no longer catchesDataAccessExceptionwith in-memory defaults.- Runtime now loads templates from DB persistence only.
- Added admin template CRUD endpoints:
POST /api/admin/outreach-templatesPUT /api/admin/outreach-templates/{id}
- Added template persistence methods in service:
createTemplate(...)updateTemplate(...)- Type validation for
OUTREACH,APPOINTMENT_REMINDER,ESCALATION.
- Updated admin security posture:
/api/admin/**now consistently requiresROLE_ADMIN.
Verification:
- Backend compile:
backend\\./gradlew.bat compileJava-> PASS - Backend web tests:
backend\\./gradlew.bat test --tests "com.workwell.web.AdminControllerTest" --tests "com.workwell.web.CaseControllerTest"-> PASS
Completed:
- Added Release tab + workflow surface in Studio:
- New fifth tab
Release & Approvalinfrontend/app/(dashboard)/studio/[id]/page.tsx. - Readiness checklist now visible in-tab for:
- compile status
- test fixture validation
- value set resolvability
- required spec completeness
- New fifth tab
- Added Version History panel in Studio:
- backend endpoint
GET /api/measures/{id}/versions - frontend table shows version, status, author, created date, change summary.
- backend endpoint
- Added dedicated release actions:
- backend
POST /api/measures/{id}/approve - backend
POST /api/measures/{id}/deprecate(mandatory reason) - approval writes
MEASURE_APPROVEDaudit event.
- backend
- Studio action gating and confirmations:
Approve for Releaseshown to APPROVER/ADMIN only; disabled when compile/test gates fail (tooltip shown).Activate Measureshown after Approved to APPROVER/ADMIN with confirmation.Deprecateshown only to ADMIN with mandatory reason prompt.
- Security policy alignment:
/api/measures/*/approve->ROLE_APPROVERorROLE_ADMIN/api/measures/*/deprecate->ROLE_ADMIN
Verification:
- Backend compile:
backend\\./gradlew.bat compileJava-> PASS - Backend web tests:
backend\\./gradlew.bat test --tests "com.workwell.web.*"-> PASS - Frontend lint:
frontend\\npm run lint-> PASS - Frontend build:
frontend\\npm run build-> PASS
Completed:
- Reworked
CqlEvaluationServiceto evaluate all 100 employees fromSyntheticEmployeeCatalogper measure instead of 12-15 hardcoded subsets. - Added deterministic seeded population assignment (
measure + employeeIdstable mapping) so reruns remain consistent. - Added compliance-rate configuration under
workwell.evaluation.compliance-ratesinbackend/src/main/resources/application.yml:audiogram: 0.78tb_surveillance: 0.91hazwoper: 0.65flu_vaccine: 0.84
- Updated synthetic bundle generation to use run evaluation date for exam/immunization timestamps (stable historical behavior).
- Fixed
MeasureService.listMeasures(...)PostgreSQL null-parameter query issue that blocked manual run seeding paths. - Added integration verification coverage:
Major1PopulationIntegrationTest- updated
CqlEvaluationServiceTest
Verification:
- Backend compile:
backend\\./gradlew.bat compileJava-> PASS - Targeted eval + MAJOR-1 integration tests:
backend\\./gradlew.bat test --tests "com.workwell.compile.CqlEvaluationServiceTest" --tests "com.workwell.run.Major1PopulationIntegrationTest"-> PASS
Completed:
- Added evidence schema:
- Migration
backend/src/main/resources/db/migration/V006__evidence_attachments.sql - New table
evidence_attachments
- Migration
- Implemented evidence storage/service:
EvidenceServicewith server-side filesystem storage underuploads/evidence/...- Upload validation:
- allowed: PDF, PNG, JPG/JPEG
- max size: 10 MB
- Automatic audit write:
EVIDENCE_UPLOADED
- Added backend endpoints:
POST /api/cases/{id}/evidence(multipart upload + optional description)GET /api/cases/{id}/evidence(list)GET /api/evidence/{id}/download(file streaming; image inline, PDF attachment)
- Frontend Case Detail enhancements:
- Upload Evidence section with file input and description
- Evidence list with metadata and download links
- Timeline icon mapping for evidence events
Verification:
- Backend compile:
backend\\./gradlew.bat compileJava-> PASS - Backend web tests:
backend\\./gradlew.bat test --tests \"com.workwell.web.*\"-> PASS - Frontend lint:
frontend\\npm run lint-> PASS - Frontend build:
frontend\\npm run build-> PASS
Completed:
- Added DB support for appointment and reminder records:
scheduled_appointmentsoutreach_records- Migration:
backend/src/main/resources/db/migration/V005__scheduled_appointments_and_outreach_records.sql
- Expanded unified case action endpoint to support:
type = SCHEDULE_APPOINTMENT
- Implemented appointment workflow in
CaseFlowService.scheduleAppointment(...):- Validates appointment inputs (
appointmentType,scheduledAt,location) - Persists appointment row with
PENDINGstatus - Records case action
SCHEDULE_APPOINTMENT - Auto-creates
outreach_recordsrow:type=APPOINTMENT_REMINDERstatus=QUEUEDauto_triggered=true
- Transitions case
OPEN -> IN_PROGRESS - Writes audit event
APPOINTMENT_SCHEDULED
- Validates appointment inputs (
- Added appointments query endpoint:
GET /api/cases/{id}/appointments
- Frontend Case Detail updates:
- Added
Schedule Appointmentbutton and modal with:- appointment type
- date/time
- location
- notes
- Added appointment list panel
- Added timeline icon mapping for appointment events.
- Added
Verification:
- Backend compile:
backend\\./gradlew.bat compileJava-> PASS - Backend web tests:
backend\\./gradlew.bat test --tests \"com.workwell.web.*\"-> PASS - Frontend lint:
frontend\\npm run lint-> PASS - Frontend build:
frontend\\npm run build-> PASS
Completed:
- Added manual closure API action:
POST /api/cases/{id}/actions- Payload supports
{ type: "RESOLVE", note, resolvedAt, resolvedBy }.
- Implemented manual closure service path:
CaseFlowService.resolveCase(...)- Validates state (
OPEN/IN_PROGRESSonly) and mandatory closure note - Sets case state to
CLOSED - Persists closure metadata (
closed_at,closed_reason=MANUAL_RESOLVE,closed_by) - Writes case action
RESOLVE - Writes audit event
CASE_MANUALLY_CLOSEDincluding actor + note context
- Added schema support:
- Migration
backend/src/main/resources/db/migration/V004__case_manual_closure_fields.sql - New columns on
cases:closed_reason,closed_by
- Migration
- Frontend updates:
- Case detail page now has
Mark Resolvedbutton - Modal enforces closure note before submit
- UI refreshes to closed state after success
- Metadata panel now surfaces
Closed reasonandClosed by
- Case detail page now has
- Worklist status controls updated to explicit tabs:
Open/Closed/All- Default remains
Open, so closed cases are hidden from default view.
Verification:
- Backend compile:
backend\\./gradlew.bat compileJava-> PASS - Backend web tests:
backend\\./gradlew.bat test --tests \"com.workwell.web.*\"-> PASS - Targeted AI integration test:
backend\\./gradlew.bat test --tests \"com.workwell.ai.AiServiceIntegrationTest\"-> PASS - Frontend lint:
frontend\\npm run lint-> PASS - Frontend build:
frontend\\npm run build-> PASS
Completed:
- Updated backend catalog listing to remove Active-only restriction:
MeasureService.listMeasures(...)now returns all statuses by default.- Added optional query filtering:
status:Draft | Approved | Active | Deprecatedsearch: name/tag match
- Extended catalog DTO payload with lifecycle metadata:
statusUpdatedAtstatusUpdatedBy
- Updated
GET /api/measurescontroller contract to accept?status=and?search=. - Frontend
Measurespage updates:- Added status filter pill row (
All / Draft / Approved / Active / Deprecated). - Added search box for name/tag filtering.
- Added status pill rendering for each row and status update metadata column.
- Added status filter pill row (
- Studio role visibility alignment (tied to RBAC):
New Versioncontrol is shown only toROLE_AUTHOR.Approveaction is shown only toROLE_APPROVER.
Verification:
- Backend compile:
backend\\./gradlew.bat compileJava-> PASS - Backend web tests:
backend\\./gradlew.bat test --tests \"com.workwell.web.*\"-> PASS - Frontend lint:
frontend\\npm run lint-> PASS - Frontend build:
frontend\\npm run build-> PASS
Completed:
- Added migration
backend/src/main/resources/db/migration/V003__demo_users.sqlwithdemo_usersand seeded role personas:author@workwell.dev(ROLE_AUTHOR)approver@workwell.dev(ROLE_APPROVER)cm@workwell.dev(ROLE_CASE_MANAGER)admin@workwell.dev(ROLE_ADMIN)
- Implemented JWT login flow:
POST /api/auth/login- signed HS256 JWTs with configurable TTL/secret via
workwell.auth.*properties - BCrypt password verification
- Implemented request authentication:
JwtAuthFilterparses bearer token and sets Spring Security authenticationSecurityConfigenforces role-based access policies for mutation/admin routes
- Added actor derivation from security context:
- introduced
SecurityActorhelper and wired audit-write paths to prefer authenticated email actor where available
- introduced
- Frontend auth UX:
- Added
/loginpage and in-memory session handling - Injected auth provider globally
- Dashboard header now shows logged-in user email + role badge + logout
- Added
- Added demo personas into synthetic employees catalog metadata for UI/runtime coherence.
Verification:
- Backend compile:
backend\\./gradlew.bat compileJava-> PASS - Backend web tests:
backend\\./gradlew.bat test --tests \"com.workwell.web.*\"-> PASS - Frontend lint:
frontend\\npm run lint-> PASS - Frontend build:
frontend\\npm run build-> PASS
Notes:
- For test stability with existing
@WebMvcTestslices, auth can be disabled in tests viaworkwell.auth.enabled=false(test resources only); runtime default remains enabled. - Remaining TODO items are intentionally untouched and still pending in required execution order.
Completed:
- Reconciled
docs/new_instructions.mdchecklist to zero actionable open items (55/55done). - Re-ran production
POST /api/runs/manualsuccessfully:- run
3866d69a-2519-4051-bad0-98da9ea696bf activeMeasuresExecuted=4.
- run
- Refreshed
docs/DEMO_RUNBOOK.mdpinned latest run IDs to current production values and updated MCPget_run_summarysample run ID. - Finalized advisor rehearsal evidence bundle in:
docs/evidence/2026-05-07-rehearsal/- Includes programs/measures snapshots, pinned case payload, AI explanation payload, and MCP tool transcripts (
tools/list,list_measures,get_run_summary,explain_outcome).
Outcome:
- Current branch is in advisor-ready freeze posture with production verification artifacts and runbook IDs synchronized to live state.
Completed:
- Deployed backend to Fly from current branch:
flyctl deploy --config backend/fly.toml --remote-only- release
v57onworkwell-measure-studio-api.
- Deployed frontend to Vercel from current branch:
- deployment
dpl_H88GXJKjsnvah3YaG2pH5vuVfSdj - alias confirmed at
https://frontend-seven-eta-24.vercel.app.
- deployment
- Verified
/studioroute behavior in production:GET https://frontend-seven-eta-24.vercel.app/studio->307redirect to/measures.
- Verified MCP transport endpoint is reachable:
GET https://workwell-measure-studio-api.fly.dev/sse->200.
- Verified production Flu behavior after deploy:
POST /api/runs/flu-vaccinereturned run2c9ba3b4-e8f0-4391-91ec-19f5e8ea06fawith non-zero compliant bucket.GET /api/programsnow reports Flu withtotalEvaluated=15,compliant=6,excluded=3,overdue=6,missingData=0,complianceRate=40.0.
- Re-validated explainability evidence fields on production case detail:
GET /api/cases/c0162cf4-b0bf-4410-878a-af6f1bbf9472includeswhy_flagged.last_exam_date,days_overdue,compliance_window_daysplus eligibility fields.
- Re-validated AI explain endpoint:
POST /api/cases/c0162cf4-b0bf-4410-878a-af6f1bbf9472/ai/explain->provider=openai,fallbackUsed=false.
Notes:
POST /api/runs/manualintermittently hangs from direct curl despite measure-specific run endpoints succeeding; tracked as a runtime reliability follow-up for the full Run-All demo flow.- Core freeze goals for Flu distribution and
/studiodead-end are now verified in production. - Rehearsal evidence bundle has been saved for demo reuse under
docs/evidence/2026-05-07-rehearsal/including:programs.json,measures.jsoncase_c0162cf4.json,ai_explain_c0162cf4.jsonmcp_tools_list.json,mcp_list_measures.json,mcp_get_run_summary_fba26713.json,mcp_get_run_summary_3866d69a.json,mcp_explain_outcome_32fee6f4.json
- Follow-up production run-all probe succeeded in this cycle:
POST /api/runs/manual-> run3866d69a-2519-4051-bad0-98da9ea696bfwithactiveMeasuresExecuted=4.
docs/DEMO_RUNBOOK.mdpinned run IDs were refreshed to current production values, and the MCPget_run_summarysample call now points to run3866d69a-2519-4051-bad0-98da9ea696bf.- TODO reconciliation closeout:
docs/new_instructions.mdstale unchecked items were reconciled to completed/superseded with explicit evidence references.- Remaining actionable TODO count for this instruction batch is now zero.
- MCP protocol probe details:
GET /ssereturns an endpoint event with session-scoped message path (/mcp/message?sessionId=...).- Raw curl JSON-RPC post to the message endpoint was not sufficient for a stable tool transcript capture in this shell-only flow; a proper MCP client session (SSE + message channel together) is still needed for final
explain_outcometranscript evidence. - Partial protocol evidence was captured: MCP
initializeresponse returnedserverInfo.name=workwell-mcp,serverInfo.version=1.1.0,protocolVersion=2024-11-05. - Follow-up closure: used MCP Inspector CLI directly against production SSE and captured successful tool transcripts:
tools/listreturned full registered tool set.tools/calllist_measuresreturned all 4 active measures.tools/callget_run_summaryfor runfba26713-92ff-49e3-84d0-fa8d137881f7returned structured counts and pass-rate.tools/callexplain_outcomefor case32fee6f4-6e69-4675-b44e-5f6392de7dbdreturned deterministic evidence fields with real values (last_exam_date=2025-03-13,days_overdue=55,compliance_window_days=365), nounknownplaceholders.
Completed:
- Re-ran backend test suite:
backend\\./gradlew.bat test->BUILD SUCCESSFUL(all tasks up-to-date, no new failures).
- Re-ran frontend production build:
frontend\\npm run build-> PASS (Next.js 16.2.4 build completed;/studioroute present).
- Verified local docker runtime status:
docker compose -f infra/docker-compose.yml ps->backendandpostgresbothUp.
- Verified local backend health:
GET http://localhost:8080/actuator/health->{"status":"UP"}.
- Executed fresh local all-program run:
POST http://localhost:8080/api/runs/manual-> run901100a1-95f3-4765-ac42-0ef2f74b04ac,activeMeasuresExecuted=4.
- Verified Flu outcome mix for the fresh run from outcomes CSV export:
COMPLIANT=6,EXCLUDED=3,OVERDUE=6,TOTAL=15,PASS_RATE=40%.
Notes:
- Flu pass-rate remains within the advisor target band (20%-60%) on local branch code.
- Remaining gap is deployment-time production re-validation for MCP
explain_outcomepayload fields and final rehearsal evidence capture. - Local evidence JSON check on overdue Audiogram case (
a38b94d7-8c6a-4678-b693-db31d9c5bb91) confirms concrete snake_case values inwhy_flagged:last_exam_date=2025-03-13,days_overdue=55,compliance_window_days=365,role_eligible=true,site_eligible=true,waiver_status=none.
Completed:
- Rewrote
docs/advisor_update.mdfor a full external-advisor handoff with:- implementation status snapshot,
- plan alignment against
docs/SPIKE_PLAN.md, - production/local verification signal summary,
- explicit "what is left" vs "what is done",
- risk/caveat section,
- direct advisor questions and clarification asks,
- recommended file packet list for review handoff.
- Synced tracker/context docs for consistency with current day status:
docs/TODO.mdlatest checkpoint date advanced to 2026-05-07,CLAUDE.mdcurrent focus moved from historical D3 note to stabilization/freeze focus.
Purpose:
- Ensure external advisor receives one coherent, evidence-backed package describing:
- project state,
- work completed,
- open risks,
- remaining execution steps before final demo/pilot positioning.
Executed against:
- Frontend:
https://frontend-seven-eta-24.vercel.app - Backend:
https://workwell-measure-studio-api.fly.dev
Production API checks:
GET /actuator/health->200, body{"status":"UP"}GET /api/programs->200(4 active measures returned)POST /api/runs/manual->200- Run:
5c6ebb99-9b21-46ab-9690-adca628b3044 activeMeasuresExecuted=4,measuresExecuted=[Audiogram, Flu Vaccine, HAZWOPER Surveillance, TB Surveillance]
- Run:
GET /api/cases?status=open->200(open cases present; current rows useemp-*external IDs, no legacypatient-*rows observed in payload)GET /api/exports/runs?format=csv->200,text/csvGET /api/exports/outcomes?format=csv->200,text/csvGET /api/exports/cases?format=csv&status=open->200,text/csvGET /api/audit-events/export?format=csv->200,text/csvPOST /api/measures/{measureId}/ai/draft-spec->200- measure used:
4ae5d865-3d64-4a17-905d-f1b315a037e2
- measure used:
POST /api/cases/{caseId}/ai/explain->200- case used:
c0162cf4-b0bf-4410-878a-af6f1bbf9472
- case used:
GET /api/programs/{measureId}/trend->200GET /api/programs/{measureId}/top-drivers->200GET /api/runs/{runId}/outcomes->200(run5c6ebb99-9b21-46ab-9690-adca628b3044)GET /api/admin/integrations->200POST /api/admin/integrations/ai/sync->200
Frontend route checks:
GET /programs->200GET /cases->200GET /runs->200GET /measures->200GET /admin->200GET /studio->200
Note:
HEAD https://workwell-measure-studio-api.fly.dev/ssereturned404during MCP transport probe. This endpoint had previously been expected in older notes; current runtime appears to expose MCP differently or not at/sse. Core app user flows and required API smoke checks above are passing.
Investigation:
- Verified MCP SSE endpoint is reachable over GET:
GET https://workwell-measure-studio-api.fly.dev/ssereturns200withcontent-type: text/event-stream(long-lived connection).
- Root cause for false-negative MCP health status:
- Integration health check used Java
HttpClientwithBodyHandlers.discarding()on a long-lived SSE stream, which can wait on completion and incorrectly degrade on timeout.
- Integration health check used Java
Fix implemented:
- Updated
IntegrationHealthService.checkMcpHealth()to useHttpURLConnectionGET and validate response headers/status immediately (without waiting for stream completion). - Health payload now records:
sseUrlstatusCodecontentType
Verification:
backend\\gradlew.bat test --tests "com.workwell.web.AdminControllerTest" --no-daemon-> PASS
Completed:
- Added shared frontend UI utilities:
frontend/lib/status.tsfor canonical measure lifecycle + outcome badge classes.frontend/lib/toast.tsandfrontend/components/global-toast.tsxfor a single global 2.5s toast system.
- Dashboard shell responsive + search:
- Reworked
frontend/app/(dashboard)/layout.tsxwith sticky top bar, mobile nav toggle, and global search input routing to/cases?search=....
- Reworked
- Cases list/detail polish:
- Cases page now honors query-driven search initialization and applies shared outcome badges.
- Added stronger empty-state copy.
- Case detail now emits toasts for outreach/assign/escalate/delivery/rerun actions.
- Added AI explanation loading skeleton while explain call is pending.
- Runs page polish:
- Replaced local toast stub with global toast events.
- Added no-runs empty state.
- Applied shared outcome badge colors in outcomes table.
- Programs + Measures + Studio consistency:
- Programs:
MISSING_DATAbadge now purple/violet; added empty-measures state; run-all success toast. - Measures and Studio status pills now use shared lifecycle status mapping.
- Studio compile success now emits
CQL compiled successfullytoast; local toast stub removed.
- Programs:
Verification:
frontend\\npm run lint-> PASSfrontend\\npm run build-> PASS
Completed:
- Added
backend/src/test/java/com/workwell/ai/AiServiceIntegrationTest.java:- validates draft-spec success path with AI JSON payload parsing,
- validates explain-case deterministic fallback path when AI client is unavailable,
- asserts AI audit persistence path is invoked via
JdbcTemplate.update(...).
- Added
backend/src/test/java/com/workwell/mcp/McpServerConfigTest.java:- validates MCP server wiring initializes correctly with expected server metadata (
workwell-mcp,1.1.0) and capabilities under mocked dependencies.
- validates MCP server wiring initializes correctly with expected server metadata (
Verification:
backend\\gradlew.bat test --tests "com.workwell.ai.AiServiceIntegrationTest" --tests "com.workwell.mcp.McpServerConfigTest" --no-daemon-> PASS
Completed:
- Expanded
SyntheticEmployeeCatalogfrom 50 -> 100 employees (emp-001throughemp-100). - Added edge-profile diversity:
- role-overlap labels (for example maintenance+hazwoper, nurse+clinic operations),
- additional clinic and plant cohorts,
- broader population for waiver/missing-data seeded scenarios.
- Expanded Option A seeded CQL input sets in
CqlEvaluationService:- Audiogram: 15 seeded employees (3 per each of compliant/due-soon/overdue/missing/excluded).
- TB Surveillance: 15 seeded employees (3 per each bucket).
- HAZWOPER Surveillance: 15 seeded employees (3 per each bucket), including a larger hazwoper-enrolled subset.
- Flu Vaccine: expanded seeded set and updated CQL mapping to allow
DUE_SOON/OVERDUEpaths based on most recent flu vaccine recency while preservingEXCLUDEDandMISSING_DATA.
- Updated
backend/src/main/resources/measures/flu_vaccine.cql:- added
Most Recent Flu Vaccine Date - added
Days Since Last Flu Vaccine - updated
Outcome Statusordering to emitOVERDUEandDUE_SOONwhen applicable.
- added
Verification:
backend\\gradlew.bat compileJava-> PASSbackend\\gradlew.bat test --tests \"com.workwell.compile.CqlCompileValidationServiceTest\" --tests \"com.workwell.compile.CqlEvaluationServiceTest\" --no-daemon-> PASS
Completed:
- Added
SeedHistoricalRunsService(com.workwell.run) with startup seeding guard:- if
runstable has data, no-op - if empty, seed 5 historical all-program runs at 30-day spacing
- if
- Historical run generation uses real Option A CQL evaluation payloads per active measure and then applies deterministic compliant-rate variance deltas:
-5%,-2%,0%,+3%,+5%
- Adjustment is encoded in evidence metadata (
historicalSeedAdjusted,historicalSeedOutcome) for traceability. - Seeded runs are persisted through existing
persistAllProgramsRun(...)path so audit/outcome/case pipelines stay consistent.
Verification:
backend\\gradlew.bat compileJava-> PASSbackend\\gradlew.bat test --tests \"com.workwell.compile.CqlCompileValidationServiceTest\" --tests \"com.workwell.compile.CqlEvaluationServiceTest\" --tests \"com.workwell.web.RunControllerTest\" --no-daemon-> PASS
Completed:
- Expanded
ExportControllerTest:- verifies runs/outcomes/cases CSV responses with concrete body expectations,
- verifies invalid format handling returns
400withUnsupported format. Use format=csv..
- Added new
ProgramControllerTest:- verifies
/api/programspayload shape and key fields, - verifies
/api/programs/{measureId}/trendtime-series payload, - verifies
/api/programs/{measureId}/top-driversby-site/by-role/by-outcome payloads.
- verifies
Verification:
backend\\gradlew.bat test --tests \"com.workwell.web.ExportControllerTest\" --tests \"com.workwell.web.ProgramControllerTest\" --no-daemon-> PASS
Completed:
- Rewrote
docs/AI_GUARDRAILS.mdwith implementation-accurate details fromAiAssistService:- Real prompt templates for Draft Spec, Explain Why Flagged, and Run Insight
- Model and fallback configuration (
gpt-5.4-nanoprimary,gpt-4o-minifallback, temp 0.3, max tokens 1000) - Per-surface deterministic fallback behavior
- Concrete audit payload schemas for
AI_DRAFT_SPEC_GENERATED,AI_CASE_EXPLANATION_GENERATED, andAI_RUN_INSIGHT_GENERATED - Explicit persistence boundary: AI outputs are non-canonical, CQL outcomes remain source of truth
- Rewrote
docs/MEASURES.mdwith CQL-to-outcome mapping for all four measures:- Audiogram, HAZWOPER, TB, Flu
- Define-level logic summary and final
Outcome Statusbucket mapping - Clarified canonical status derivation from
Outcome Statusdefine output
Verification:
- Confirmed AI config values from
backend/src/main/resources/application.yml. - Confirmed prompt/audit/fallback behavior from
backend/src/main/java/com/workwell/ai/AiAssistService.java. - Confirmed current CQL files from
backend/src/main/resources/measures/*.cql.
Completed:
- Rewrote
docs/ARCHITECTURE.mdto reflect current live runtime:- Vercel frontend -> Fly backend -> Neon DB topology
- Detailed package boundaries across
com.workwell.* - End-to-end flow: policy text -> spec -> CQL compile -> run -> outcomes -> cases -> actions -> audit
- Option A runtime invariants and compliance source-of-truth constraints
- Rewrote
docs/DATA_MODEL.mdwith:- Full schema coverage for active tables (
V001,V002) plus migration-safeoutreach_templatescontract - Case upsert idempotency worked example (
UNIQUE(employee_id, measure_version_id, evaluation_period)) - Detailed
evidence_jsoncontract and evaluation-error fallback payload shape - Full CSV export column contracts and case export filter contract (including
caseIds)
- Full schema coverage for active tables (
- Added
docs/DEMO_RUNBOOK.md:- Production URLs
- Pinned production case IDs including overdue Audiogram showcase case
- Click-by-click demo flow with expected outcomes and fallback paths (including AI unavailable path)
Verification:
GET https://workwell-measure-studio-api.fly.dev/api/cases?status=open-> 200- Pinned case IDs validated from live response payload at write time.
Completed:
- Cases list bulk actions:
- Added multi-select checkboxes with select-all for current filtered results.
- Added bulk toolbar for
Assign to...,Escalate selected, andExport selected. - Bulk assign/escalate executes sequential per-case API calls (
/assign,/escalate) and refreshes list on completion.
- Case search:
- Added client-side search box filtering loaded cases by employee name or employee ID.
- Selected-case CSV export:
- Extended
GET /api/exports/casesto accept optionalcaseIdsquery param (comma-separated UUIDs). - Extended
CsvExportService.exportCaseCsv(...)to filter by selected case IDs when provided.
- Extended
- Case detail evidence/timeline polish:
- Added
View Raw Evidencetoggle under Why Flagged to show/hide fullevidence_json. - Timeline now includes event icons, source tags (
auditvsaction), humanized labels, and most-recent highlight.
- Added
Verification:
backend\\gradlew.bat compileJava-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Completed:
- Implemented version cloning API and service flow:
- Backend endpoint:
POST /api/measures/{id}/versions - Requires
changeSummary - Clones latest measure version into a new
Draftversion with incremented version number (vX.Y -> vX.(Y+1)). - Copies
spec_json,cql_text, compile metadata, andmeasure_value_set_linksfrom source version. - Emits
MEASURE_VERSION_CLONEDaudit event with source/target metadata.
- Backend endpoint:
- Studio UI:
- Added change-summary input and
New Versionaction on measure detail page. - After successful clone, page reloads and surfaces the new draft context.
- Added change-summary input and
- Value set resolvability support:
- Extended
ValueSetRefpayload with resolvability metadata (status,label,note,codeCount). - Added resolvability badges on attached and attachable value-set lists.
- Added unresolved compile warnings:
Value set '{name}' ({oid}) has no codes loaded. Verify codes are available before activation.
- Extended
Constraint observed:
- Monaco editor task (
@monaco-editor/react) not executed due sprint hard rule: no new dependencies after D5.
Verification:
backend\\gradlew.bat compileJava-> PASSbackend\\gradlew.bat test --tests \"com.workwell.web.MeasureControllerTest\"-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Completed:
- Kept translator-based compile pipeline and polished compile status semantics in
MeasureService.compileCql(...):COMPILEDwhen no errors and no warningsWARNINGSwhen no errors but warnings existERRORwhen translator errors exist
- Updated activation gating behavior:
- Activation readiness now treats
COMPILEDandWARNINGSas compile-pass states. - Activation transition check now blocks only when compile status is neither
COMPILEDnorWARNINGS.
- Activation readiness now treats
- Studio CQL tab UX polish in frontend:
- Compile badge now reflects exact backend status (
COMPILED/WARNINGS/ERROR). - Warnings and errors render in separate color-coded panels.
- Added line-aware issue formatting helper so line references are surfaced more clearly to authors.
- Added warning guidance banner clarifying that warning-only compile state can still activate.
- Compile badge now reflects exact backend status (
Verification:
backend\\gradlew.bat compileJava-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Completed:
- Added DB migration for persistent integration health:
backend/src/main/resources/db/migration/V002__integration_health.sql- Creates
integration_healthtable and seeds rows forfhir,mcp,ai,hris.
- Replaced hardcoded integration-state logic with table-backed service in:
backend/src/main/java/com/workwell/admin/IntegrationHealthService.java
GET /api/admin/integrationsnow reads persisted rows (display_name,status,last_sync_at,last_sync_result,config_json).POST /api/admin/integrations/{integration}/syncnow updates persisted state and emits audit:INTEGRATION_SYNC_TRIGGEREDwith{ integrationId, result, actor, message, syncedAt }.
- Implemented manual-sync health checks:
ai: OpenAI API health ping against/v1/responseswith configured model.mcp: SSE reachability probe against configuredworkwell.mcp.sse-url(defaulthttp://127.0.0.1:8080/sse).fhirandhris: deterministic healthy manual-sync stub result with persisted timestamps.
- Updated Admin UI integration cards:
- Shows
displayNamefrom API. - Color-coded status badges (healthy/degraded-or-stale/unknown).
- Continues to show real last-sync timestamps and sync result text.
- Shows
Verification:
backend\\gradlew.bat compileJava-> PASSbackend\\gradlew.bat test --tests \"com.workwell.web.AdminControllerTest\"-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Completed:
- Kept
POST /api/cases/{caseId}/actions/outreach/deliveryand tightened service behavior to match delivery-state contract:- Enforces precondition that an
OUTREACH_SENTaction exists before accepting delivery updates. - Continues strict
deliveryStatusvalidation (QUEUED|SENT|FAILED). - Persists
OUTREACH_DELIVERY_UPDATEDcase action payload withdeliveryStatus,updatedAt,actor, and note. - Emits
CASE_OUTREACH_DELIVERY_UPDATEDaudit event with explicit payload{ caseId, deliveryStatus, updatedAt, actor }.
- Enforces precondition that an
- Tightened latest delivery-state derivation:
latestOutreachDeliveryStatusnow resolves only fromcase_actions.action_type = 'OUTREACH_DELIVERY_UPDATED'.
- Frontend case detail improvement:
- Added color-coded delivery status badge (QUEUED/SENT/FAILED/NOT_SENT).
- Added controller coverage for validation failure path:
- bad-request mapping when delivery update is attempted before outreach send.
Verification:
backend\\gradlew.bat test --tests \"com.workwell.web.CaseControllerTest\"-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Completed:
- Updated MCP tool contracts in
McpServerConfigto align with TODO requirements:list_measuresnow accepts optionalstatus(defaultActive) and returns:measureId,measureName,policyRef,version,status,compileStatus,testFixtureCount,valueSetCount,lastUpdated
get_measure_versionnow returns richer measure-version payload:specJson, truncatedcqlText(first 500 chars),compileStatus, attached value sets (name/OID/version),testFixtureCount,valueSetCount, lifecycle status.
list_runsnow accepts{ measureId?, limit? }with defaultlimit=10and returns run summaries including compliance rate and per-outcome counts.explain_outcomenow accepts{ caseId }and returns deterministic rule-based explanation derived from caseevidence_json.why_flaggedfields (no AI call).
- Confirmed
get_case,list_cases, andget_run_summarycontinue to emitMCP_TOOL_CALLEDaudit events with sanitized args. - Bumped MCP server version marker to
1.1.0.
Verification:
backend\\gradlew.bat compileJava-> PASS
Completed:
- Upgraded
ExportControllerCSV contracts:GET /api/exports/runsnow returnsruns-export.csv.GET /api/exports/casesstatus filter is optional (no forcedopendefault).
- Reworked
CsvExportServiceto use SQL-backed export queries with full column contracts:- Runs export now includes versioned scope metadata, all five outcome buckets, pass rate, and data freshness timestamp.
- Outcomes export now includes employee role/site and
why_flagged-derived evidence fields (lastExamDate,complianceWindowDays,daysOverdue,roleEligible,siteEligible,waiverStatus). - Cases export now includes role, next action, created/updated/closed timestamps, and latest outreach delivery state.
- Added export contract documentation:
docs/EXPORTS.md
- Updated TODO status for P1 CSV exports as completed.
Verification:
backend\\gradlew.bat test --tests \"com.workwell.export.CsvExportServiceTest\" --tests \"com.workwell.web.ExportControllerTest\"-> PASSbackend\\gradlew.bat test-> FAIL on Docker/Testcontainers bootstrap (DockerClientProviderStrategy) for integration tests in this local environmentfrontend npm run lint-> PASSfrontend npm run build-> PASS
Completed:
- Investigated production frontend
Failed to fetchand traced to backend instability (Fly runtime pressure and failing evaluation path). - Hardened backend runtime configuration in
backend/fly.toml:- Increased VM memory from
512mbto1gb - Increased JVM heap from
-Xmx384mto-Xmx768mwith-Xms256m
- Increased VM memory from
- Fixed AI service context fragility in tests/runtime by making
ChatClient.Builderoptional viaObjectProviderin:backend/src/main/java/com/workwell/ai/AiAssistService.java
- Fixed CQL compile validation false-negatives in:
backend/src/main/java/com/workwell/compile/CqlCompileValidationService.java- Removed hard requirement on XML writer provider during compile validation.
- Advanced Option A CQL execution wiring in:
backend/src/main/java/com/workwell/compile/CqlEvaluationService.java- Added richer generated Measure populations and robust subject result key resolution.
- Added runtime
ExpressionResultunwrapping soOutcome Statusand define results are read correctly from actual engine output.
- Added
elm-jacksonruntime support dependency:backend/build.gradle.kts
- Updated seeded CQL files for engine compatibility while preserving Option A execution path:
backend/src/main/resources/measures/audiogram.cqlbackend/src/main/resources/measures/tb_surveillance.cqlbackend/src/main/resources/measures/hazwoper.cqlbackend/src/main/resources/measures/flu_vaccine.cql
- Maintained and tightened sanity tests requested by advisor:
backend/src/test/java/com/workwell/compile/CqlEvaluationServiceTest.javabackend/src/test/java/com/workwell/compile/CqlCompileValidationServiceTest.java
Verification:
backend\\gradlew.bat test --tests "com.workwell.compile.CqlCompileValidationServiceTest" --tests "com.workwell.compile.CqlEvaluationServiceTest"-> PASSbackend\\gradlew.bat test --tests "com.workwell.compile.CqlEvaluationServiceTest"-> PASS
Notes:
- Local full-suite integration tests that require Docker/Testcontainers still depend on local Docker availability.
- Option A path now returns real CQL define-level expression results and correctly maps engine output to outcome buckets.
Completed:
- Added test-scope Spring AI OpenAI properties in:
backend/src/test/resources/application.properties
- Purpose: ensure Spring Boot test contexts in CI have deterministic OpenAI config placeholders so backend integration tests do not fail context startup when secrets are absent in test runtime.
Verification:
backend\\gradlew.bat test --tests "com.workwell.web.AiControllerTest" --tests "com.workwell.compile.CqlCompileValidationServiceTest" --tests "com.workwell.compile.CqlEvaluationServiceTest"-> PASS
Completed:
- Added rerun endpoint
POST /api/runs/{id}/reruninRunController. - Implemented
AllProgramsRunService.rerunSameScope(...):- Replays all-programs runs using the existing all-programs orchestration.
- Replays measure-scoped runs by re-evaluating the original measure version CQL and persisting a fresh run.
- Added
/runsUI action: "Rerun Selected Scope". - Added scheduler admin API:
GET /api/admin/schedulerPOST /api/admin/scheduler?enabled=true|false
- Added scheduler settings UI on
/admin:- enable/disable toggle
- cron expression display
- computed next-fire timestamp
- last scheduled run status/time
- Expanded tests:
RunControllerTestnow covers rerun endpoint.AdminControllerTestnow covers scheduler status + toggle endpoints.
Verification:
backend\\gradlew.bat test --tests "com.workwell.web.RunControllerTest" --tests "com.workwell.web.AdminControllerTest"-> PASSbackend\\gradlew.bat compileJava-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Production deploy + smoke (2026-05-06):
- Backend deployed to Fly (
https://workwell-measure-studio-api.fly.dev). - Frontend deployed to Vercel and aliased (
https://frontend-seven-eta-24.vercel.app). - Live checks:
GET /api/admin/scheduler->200POST /api/admin/scheduler?enabled=false->200POST /api/runs/{measureScopedRunId}/rerun->200(measure scope rerun succeeded)/admin->200/runs->200
- Note:
POST /api/runs/manualand rerun of all-programs-scoped runs currently return500in production (pre-existing all-programs CQL execution instability); rerun UX now prevents unsupportedcase-scope rerun attempts and still supports valid measure-scope reruns.
Completed:
- Hardened
AllProgramsRunServicewith per-measure failure isolation for all-programs and measure-scope reruns. - If a measure-level evaluation throws unexpectedly, the run now persists a deterministic
MISSING_DATAfallback outcome for that measure instead of aborting the entire run. - This preserves run continuity and aligns with the "do not let one failure abort the run" requirement.
Verification:
backend\\gradlew.bat test --tests "com.workwell.web.EvalControllerTest" --tests "com.workwell.web.RunControllerTest"-> PASSbackend\\gradlew.bat compileJava-> PASS
Production smoke (2026-05-06):
GET /actuator/health->200POST /api/runs/manual->200POST /api/runs/{allProgramsRunId}/rerun->200
Completed:
- Added backend outreach-template service and API:
GET /api/admin/outreach-templates
- Added case outreach template selection support:
POST /api/cases/{caseId}/actions/outreach?templateId=...- selected template metadata (
templateId,template,subject) now persisted incase_actions.payload_json.
- Updated case detail UI to load templates and send selected template with outreach action.
- Added migration-safe fallback behavior:
- if
outreach_templatestable is not yet present, API returns seeded default templates so workflow remains usable.
- if
Verification:
backend\\gradlew.bat test --tests "com.workwell.web.CaseControllerTest" --tests "com.workwell.web.AdminControllerTest"-> PASSbackend\\gradlew.bat compileJava-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Production deploy + smoke (2026-05-06):
GET /actuator/health->200GET /api/admin/outreach-templates->200(templatesCount=3)POST /api/cases/{caseId}/actions/outreach?templateId={templateId}->200- Follow-up
GET /api/cases/{caseId}confirmslatestOutreachDeliveryStatus=QUEUED /cases/{caseId}route ->200
Completed:
- Added backend preview endpoint:
GET /api/cases/{caseId}/actions/outreach/preview?templateId=...
- Preview response now renders selected template with case context substitutions:
employeeName,measureName,dueDate,outcomeStatus
- Added frontend preview step on case detail:
- "Preview outreach" button
- rendered subject/body preview panel
- "Send outreach" remains disabled until preview is generated
Verification:
backend\\gradlew.bat test --tests "com.workwell.web.CaseControllerTest"-> PASSbackend\\gradlew.bat compileJava-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Production deploy + smoke (2026-05-06):
GET /actuator/health->200GET /api/cases/{caseId}/actions/outreach/preview?templateId={templateId}->200- Preview payload confirms template name + rendered due date.
/cases/{caseId}route ->200
Issue observed:
- Deployed frontend showed
missing NEXT_PUBLIC_API_BASE_URLand all major actions failed with404from frontend routes. - Impacted screens: Programs run button, Runs run button, Measures create/list, Cases load, Admin scheduler toggles.
Root cause:
- Vercel project had no environment variables configured (
vercel env lsreturned none). - Frontend therefore attempted relative
/api/*calls to Vercel app origin instead of Fly backend origin.
Fix applied:
- Set Vercel production env vars:
NEXT_PUBLIC_API_BASE_URL=https://workwell-measure-studio-api.fly.devNEXT_PUBLIC_APP_NAME=WorkWell Studio
- Redeployed frontend production and refreshed alias.
- Triggered a fresh all-programs run to repopulate run/case data.
Verification:
POST /api/runs/manual->200(measures=4)GET /api/cases?status=open-> non-zero cases (openCases=35)GET /api/programs->4active programs- Frontend
/casescontent no longer includesmissing NEXT_PUBLIC_API_BASE_URLmarker.
Completed:
- Added backend endpoint
GET /api/runs/{id}/outcomesinRunController. - Added
RunPersistenceService.loadRunOutcomes(...)to join outcomes with employees/cases and project UI-ready fields:- employee name/external ID, role, site, outcome status, days-since-exam, waiver status, case ID.
- Updated
/runsdetail view to fetch and render an Outcomes table with case deep links. - Added controller test coverage for the new endpoint in
RunControllerTest.
Verification:
backend\\gradlew.bat test --tests "com.workwell.web.RunControllerTest"-> PASSbackend\\gradlew.bat compileJava-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Production deploy + smoke (2026-05-06):
- Backend deployed to Fly (
https://workwell-measure-studio-api.fly.dev). - Frontend deployed to Vercel and aliased (
https://frontend-seven-eta-24.vercel.app). - Live checks:
GET /actuator/health->200GET /api/runs?limit=1->200(runId resolved)GET /api/runs/{runId}/outcomes->200GET /runs->200
Completed:
- Backend Programs analytics endpoints:
GET /api/programsGET /api/programs/{measureId}/trendGET /api/programs/{measureId}/top-drivers- Implemented in
com.workwell.program.ProgramService+ProgramController.
- Frontend Programs overview replacement on
/programs:- KPI row, per-measure cards, compliance trend sparkline, top-drivers snippets, open-worklist link, and "Run All Measures Now" action.
- Frontend Program detail page on
/programs/{measureId}:- large compliance rate + delta, trend sparkline, drivers by site/role/reason, measure counts table, filtered worklist link.
Verification:
-
backend\\gradlew.bat compileJava-> PASS -
frontend npm run lint-> PASS -
frontend npm run build-> PASS -
Starting P0 Programs dashboard block:
- backend endpoints for
/api/programs,/api/programs/{measureId}/trend,/api/programs/{measureId}/top-drivers - frontend replacement for
/programsplaceholder and new/programs/{measureId}detail page
- backend endpoints for
-
Will update this entry with verification results after each completed batch.
- Deployed frontend to Vercel production using CLI from
frontend/. - Deployment ID:
dpl_G3LTCAgykGzNzNcBhxeqRFyJXm2e - Production URL:
https://frontend-pdi1nlhzy-taleef7s-projects.vercel.app - Alias updated:
https://frontend-seven-eta-24.vercel.app
Post-deploy route checks:
/runs-> 200/studio-> 200/cases-> 200
- Deployed backend to Fly using repo-root context with
backend/fly.tomland confirmed health checkUP. - Added model-fallback execution chain in AI service:
- primary model:
gpt-5.4-nano - fallback model:
gpt-4o-mini(only if primary fails)
- primary model:
- Added
workwell.ai.openai.fallback-modelconfig and validated compile/deploy path.
Live smoke checks on production (https://workwell-measure-studio-api.fly.dev):
POST /api/measures/{id}/ai/draft-spec->success=true,provider=openai,fallbackUsed=falsePOST /api/cases/{id}/ai/explain->provider=openai,fallbackUsed=falsePOST /api/runs/{id}/ai/insight->fallback=false, non-emptyinsights[]
This confirms production AI surfaces are now operating on real OpenAI responses (not deterministic fallback) with the configured model-priority chain.
- Added new backend endpoint for run-level AI insights:
POST /api/runs/{runId}/ai/insight- Generates 3-5 concise operational bullets via OpenAI model path (
gpt-5.4-nanoconfigured), audits asAI_RUN_INSIGHT_GENERATED, and falls back to empty insights withfallback=trueon failure.
- Updated
AiAssistServiceto include run insight generation + bullet parsing + audit payload details. - Added runs-page UI insight card:
- Dismissible panel above run detail on
/runs - Label: "AI-generated operational insight - verify before acting"
- Hidden automatically when backend returns fallback/empty insights.
- Dismissible panel above run detail on
- Expanded
AiControllerTestcoverage for the new run-insight endpoint.
Verification:
backend\\gradlew.bat compileJava-> PASSbackend\\gradlew.bat test --tests "com.workwell.web.AiControllerTest"-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
- Completed OpenAI provider-first wiring for AI surfaces with
gpt-5.4-nanomodel config in Spring AI properties. - Upgraded
AiAssistServicebehavior:- real ChatClient calls for draft spec + case explanation
- fallback-on-failure behavior preserved with deterministic responses
- draft-spec response now includes
successandfallbackcontract fields - draft-spec audit payload now records
promptLength,outputLength,model, andtokensUsedplaceholder - case explanation cache keyed by
(caseId, measureVersion)and refreshed on caseupdatedAt.
- Updated frontend integration:
- Studio AI draft now handles
success=falsefallback contract cleanly and shows a prominent review/fallback banner. - Case detail explanation panel now explicitly labels output as "Plain-language explanation (AI-assisted)".
- Studio AI draft now handles
- Updated backend test fixtures for revised draft-spec response shape.
Verification:
backend\\gradlew.bat compileJava-> PASSbackend\\gradlew.bat test --tests "com.workwell.web.AiControllerTest"-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
- Added requested sanity test classes:
backend/src/test/java/com/workwell/compile/CqlEvaluationServiceTest.javabackend/src/test/java/com/workwell/compile/CqlCompileValidationServiceTest.java
- Added a test-only failure hook in
CqlEvaluationServicefor per-employee failure isolation assertions. - Switched AI provider wiring to OpenAI starter and config:
backend/build.gradle.kts: addedorg.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-M6backend/src/main/resources/application.yml: addedspring.ai.openai.*defaults with modelgpt-5.4-nano, temperature0.3, max tokens1000.env.example: replacedANTHROPIC_API_KEYwithOPENAI_API_KEY
- Upgraded AI surface wiring toward production behavior:
AiAssistServicenow uses Spring AIChatClientfor draft spec and case explanation with deterministic fallback behavior.- Added case explanation cache keyed by
caseIdand invalidated on caseupdatedAtchanges.
Verification:
backend\\gradlew.bat compileJava-> PASSbackend\\gradlew.bat test --tests "com.workwell.web.AiControllerTest"-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS- Note: strict new compile/evaluation sanity tests currently fail against present CQL+terminology execution behavior and are retained as active guardrails for the next tightening pass.
- Replaced seeded CQL definitions with full advisor-provided logic files:
backend/src/main/resources/measures/audiogram.cqlbackend/src/main/resources/measures/tb_surveillance.cqlbackend/src/main/resources/measures/hazwoper.cqlbackend/src/main/resources/measures/flu_vaccine.cql
- Updated seed/update behavior so active measure versions are synced to these resource CQL definitions.
- Implemented com.workwell.compile.SyntheticFhirBundleBuilder to construct Patient + enrollment/waiver Condition + Procedure/Immunization resources from per-employee exam configs.
- Refactored
com.workwell.compile.CqlEvaluationServiceto:- evaluate per-employee with R4MeasureProcessor.evaluateMeasureWithCqlEngine(...)
- read CQL
expressionResultsand mapOutcome Statusdirectly to persisted outcome bucket - persist expression results into
evidence_json.expressionResults - continue run when one employee fails, marking only that employee
MISSING_DATAwithevaluationErrorpayload.
- Removed fallback-to-demo-services path from
AllProgramsRunServicefor/api/runs/manual. - Updated
RunPersistenceServicemeasure-version seeding to load per-measure CQL resources (not Audiogram-only default text).
Verification:
backend\\gradlew.bat compileJava-> PASSbackend\\gradlew.bat test --tests "com.workwell.web.EvalControllerTest" --tests "com.workwell.web.MeasureControllerTest"-> PASSbackend\\gradlew.bat test-> FAIL (environmental Docker/Testcontainers unavailable)frontend npm run lint-> PASSfrontend npm run build-> PASS
- Implemented real CQL compile validation path:
- Added com.workwell.compile.CqlCompileValidationService using CQL translator APIs (CqlTranslator) to return real compile errors/warnings.
- Replaced MeasureService.compileCql(...) string-contains placeholder check with translator-backed validation.
- Added CQF/CQL runtime dependencies in backend build:
- cqf-fhir-cr, cqf-fhir-cql, cqf-fhir-utility, model-jaxb, cql-to-elm, plus required runtime providers (moxy, hapi-fhir-caching-caffeine).
- Added initial com.workwell.compile.CqlEvaluationService for manual runs:
- Builds FHIR Library + Measure, builds synthetic patient resources from seeded run evidence, creates InMemoryFhirRepository, and calls R4MeasureProcessor.evaluateMeasureWithCqlEngine(...).
- Injected into AllProgramsRunService so /api/runs/manual now attempts the CQL-engine path first and falls back to measure demo services if evaluation is unavailable/incomplete.
Verification:
backend\\gradlew.bat compileJava-> PASSbackend\\gradlew.bat test --tests "com.workwell.web.EvalControllerTest" --tests "com.workwell.web.MeasureControllerTest"-> PASSbackend\\gradlew.bat test-> FAIL (environmental Docker/Testcontainers unavailable)frontend npm run lint-> PASSfrontend npm run build-> PASS
- Finalized repository closeout artifacts for external advisor review:
- refreshed
docs/advisor_update.mdwith full progress againstdocs/TODO.md,docs/SPIKE_PLAN.md, and archived project-plan context. - included explicit advisor clarifications/questions and requested critique focus areas.
- refreshed
- Normalized
docs/SMOKE_CHECKLIST.mdto current live API contracts:- CSV exports (
/api/exports/runs|outcomes|cases) - outreach delivery endpoint (
/api/cases/{id}/actions/outreach/delivery?deliveryStatus=...) - admin integration IDs (
fhir,mcp,ai).
- CSV exports (
- Kept remaining backend export-support changes (
RunPersistenceService+ integration test coverage) in final committed state for clean worktree.
Documentation parity updates completed:
docs/ARCHITECTURE.md- Added live modules (
ai,export,admin). - Expanded API surface to include outreach delivery updates, CSV exports, admin integrations sync, and AI endpoints.
- Updated source-of-truth references to
docs/SPIKE_PLAN.md.
- Added live modules (
docs/DATA_MODEL.md- Updated case-action lifecycle to include
OUTREACH_DELIVERY_UPDATED,ASSIGNED, andESCALATED. - Documented persisted delivery-state contract (
QUEUED|SENT|FAILED) on case actions. - Updated source-of-truth references to
docs/SPIKE_PLAN.md.
- Updated case-action lifecycle to include
docs/MEASURES.md- Added implementation-status note: four seeded measures runnable with deterministic five-outcome coverage.
docs/DEPLOY.md- Added post-deploy smoke checklist for exports/admin/outreach delivery endpoints.
- Added troubleshooting note for JDBC/Postgres JSON operator placeholder conflict.
docs/AI_GUARDRAILS.md- Added implemented AI audit events (
AI_DRAFT_SPEC_GENERATED,AI_CASE_EXPLANATION_GENERATED) and MCP per-tool audit event (MCP_TOOL_CALLED).
- Added implemented AI audit events (
docs/TODO.md- Shifted from implementation batch language to closeout/freeze posture.
- Added production closeout smoke completion checkpoint.
Verification re-run:
backend\\gradlew.bat test-> FAIL (environment-level Docker/Testcontainers availability; not a compile/runtime regression in the changed web/export/admin paths)backend\\gradlew.bat test --tests "com.workwell.web.*" --tests "com.workwell.export.*"-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
- Completed P3 notifications/admin + reporting backlog items.
Backend:
- Added explicit outreach delivery-state transitions on cases:
POST /api/cases/{caseId}/actions/outreach/delivery?deliveryStatus=QUEUED|SENT|FAILED- Persists state changes through
case_actionspayloads and emitsCASE_OUTREACH_DELIVERY_UPDATEDaudit events. - Case detail now returns
latestOutreachDeliveryStatus.
- Added admin integrations health API:
GET /api/admin/integrationsPOST /api/admin/integrations/{integration}/sync- Integrations tracked as stubs (
fhir,mcp,ai) with last successful sync derived from persisted audit events. - Manual sync writes
INTEGRATION_SYNC_TRIGGERED+INTEGRATION_SYNC_COMPLETEDaudit events.
- Added/kept CSV exports for:
- runs:
GET /api/exports/runs?format=csv - outcomes:
GET /api/exports/outcomes?format=csv&runId={optional} - cases:
GET /api/exports/cases?format=csv
- runs:
Frontend:
/adminnow shows integrations health cards and manual sync actions./cases/[id]now surfaces outreach delivery state and buttons to mark queued/sent/failed./runsnow includes export buttons for runs and outcomes CSVs./casesnow includes cases CSV export (plus existing audit CSV export).
Docs:
- Updated
README.mdAPI highlights with new admin/outreach/export routes. - Added explicit CSV column contracts in
README.md. - Updated
docs/TODO.mdto mark P3 notifications/admin/reporting items complete and move next batch to final smoke/freeze focus.
Verification checkpoints:
backend\\gradlew.bat test --tests "com.workwell.web.CaseControllerTest" --tests "com.workwell.web.AdminControllerTest" --tests "com.workwell.web.ExportControllerTest"-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Timestamp:
2026-05-05T19:10:59-04:00
What was verified live:
GET https://workwell-measure-studio-api.fly.dev/actuator/health->200GET https://frontend-seven-eta-24.vercel.app/runs->200GET https://frontend-seven-eta-24.vercel.app/cases->200GET https://frontend-seven-eta-24.vercel.app/admin->200GET https://workwell-measure-studio-api.fly.dev/api/runs?limit=1->200(runId=113bb9e9-498c-49b9-a80e-3238bf2122ed)GET https://workwell-measure-studio-api.fly.dev/api/audit-events/export?format=csv->200(text/csv)
New P3 APIs checked on production (expected after deploy):
GET /api/exports/runs?format=csv->404GET /api/exports/outcomes?format=csv&runId=...->404GET /api/exports/cases?format=csv&status=open->404POST /api/cases/{id}/actions/outreach/delivery?deliveryStatus=SENT->404GET /api/admin/integrations->404POST /api/admin/integrations/mcp/sync->404
Interpretation:
- Local implementation and tests are complete and passing, but production is still running a pre-P3 backend build.
- Next required action is backend deploy of commit
579e0b0, then rerun this exact smoke set.
Deployment actions:
- Deployed backend commit
579e0b0to Fly. - Initial rerun showed P3 exports/admin routes alive, but case detail + outreach delivery update returned
500.
Root cause:
- JDBC placeholder parsing conflict in
CaseFlowService.findLatestOutreachDeliveryStatus(...):- query used PostgreSQL JSON operator
payload_json ? 'deliveryStatus' ?was interpreted as a JDBC bind placeholder.
- query used PostgreSQL JSON operator
Fix:
- Replaced operator usage with
jsonb_exists(payload_json, 'deliveryStatus'). - Commit:
3a6eaf3(fix(caseflow): avoid jdbc placeholder conflict in delivery-status query [S6]) - Redeployed backend to Fly.
Timestamped verification (2026-05-05T19:18:14-04:00):
GET /actuator/health->200GET /api/exports/runs?format=csv->200GET /api/exports/outcomes?format=csv&runId=113bb9e9-498c-49b9-a80e-3238bf2122ed->200GET /api/exports/cases?format=csv&status=open->200GET /api/admin/integrations->200POST /api/admin/integrations/mcp/sync->200GET /api/cases/c6d79a2f-8f86-4d48-ac91-06f21d478ccb->200POST /api/cases/c6d79a2f-8f86-4d48-ac91-06f21d478ccb/actions/outreach/delivery?deliveryStatus=SENT->200- Follow-up case detail confirms
latestOutreachDeliveryStatus=SENT.
- Expanded MCP Layer 1 read surface in
backend/src/main/java/com/workwell/mcp/McpServerConfig.javaby adding:list_measuresget_measure_versionlist_runsexplain_outcome
- Kept MCP posture read-only (no write tools introduced).
- Added per-tool audit recording on every MCP tool invocation:
audit_events.event_type = MCP_TOOL_CALLED- payload includes tool name + invocation args for traceability.
Behavior details:
list_measuresreturns active catalog metadata.get_measure_versionresolves bymeasureIdormeasureNameand returns full latest measure detail payload.list_runssupports optionalstatus,scopeType,triggerType,limitfilters.explain_outcomegenerates structured-first explanation text from persistedevidence_json(includingwhy_flagged) and includes an explicit compliance disclaimer.
Local verification checkpoints:
backend\\gradlew.bat test --tests "com.workwell.web.*"-> PASSbackend\\gradlew.bat compileJava-> PASS
Notes:
- Full
backend\\gradlew.bat testremains environment-sensitive when Docker/Testcontainers are unavailable. - This slice intentionally avoided introducing MCP write capabilities per sprint guardrails.
- Ran targeted backend tests for recently touched API surfaces:
backend\\gradlew.bat test --tests "com.workwell.web.AiControllerTest" --tests "com.workwell.web.EvalControllerTest" --tests "com.workwell.web.CaseControllerTest" --tests "com.workwell.web.RunControllerTest" --tests "com.workwell.web.MeasureControllerTest"-> PASSbackend\\gradlew.bat test --tests "com.workwell.measure.AudiogramDemoServiceTest"-> PASS
- Ran frontend verification gates:
frontend npm run lint-> PASSfrontend npm run build-> PASS
- MCP transport probe from local shell:
GET http://localhost:8080/ssefailed with connection refused because no local backend instance was running during this check (expected environmental condition, not a code failure).
- Observed one transient Gradle test-results file race during parallel execution (
NoSuchFileException ... in-progress-results...bin); rerunning the web suite sequentially completed successfully.
- Fixed the reported
Failed to load measure (400)issue when opening a measure from/measures:- Root cause: client-side dynamic route parameter handling in
/studio/[id]was not robust in the current Next.js setup, causing invalid IDs to be sent to/api/measures/{id}. - Fix: switched Studio page to
useParams()+ normalizedmeasureIdusage across all API calls + guard for missing IDs.
- Root cause: client-side dynamic route parameter handling in
- Deployment + push completed:
- Commit:
015057f(feat(measure): value sets, test gates, and studio readiness polish [S2]) - Backend deployed:
https://workwell-measure-studio-api.fly.dev - Frontend deployed + aliased:
https://frontend-seven-eta-24.vercel.app - Pushed to GitHub
main.
- Commit:
- Production smoke verification (
2026-05-04T00:28:26-04:00):GET /actuator/health->UPGET /api/measures->200(measureCount=2)GET /api/measures/{id}using live id ->200(detailName=TB Surveillance,detailStatus=Active)GET /api/cases?status=open->200(openCases=23)GET https://frontend-seven-eta-24.vercel.app/measures->200GET https://frontend-seven-eta-24.vercel.app/studio/{id}->200
- Completed approval/release UX improvements in Studio:
- Added backend readiness endpoint:
GET /api/measures/{id}/activation-readiness - Added "Activation Readiness" summary panel on
/studio/[id]forApprovedmeasures. - Activation button now uses explicit readiness state and shows the first blocker inline when activation is blocked.
- Transition success toast now confirms resulting status.
- Added backend readiness endpoint:
- Completed lifecycle audit payload enrichment:
MEASURE_VERSION_STATUS_CHANGEDnow includes:compileStatusvalueSetCounttestFixtureCounttestValidationPassedactivationBlockers
- Added integration test coverage to verify richer transition audit payload fields are written.
Verification checkpoints (local):
backend\\gradlew.bat test-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
- Added shared all-program run orchestrator service:
backend/src/main/java/com/workwell/run/AllProgramsRunService.javaPOST /api/runs/manualnow delegates to this shared service.
- Added scheduled trigger service:
backend/src/main/java/com/workwell/run/ScheduledRunService.java- Cron task calls all-program run path and persists outcomes/cases/audit via existing infrastructure.
- Safe default posture: scheduler is disabled unless explicitly enabled.
- Added scheduler configuration:
workwell.scheduler.enabledfromWORKWELL_SCHEDULER_ENABLED(defaultfalse)workwell.scheduler.cronfromWORKWELL_SCHEDULER_CRON(default0 0 6 * * *)
Verification checkpoints (local):
backend\\gradlew.bat test-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Deployment + production checkpoint:
- Commit pushed:
ac0a88d(feat(run): add scheduled all-program run backbone [S3]) - Backend redeployed to Fly:
https://workwell-measure-studio-api.fly.dev - Timestamped smoke check (
2026-05-04T00:33:15-04:00):GET /actuator/health->UPGET /api/measures->200(measureCount=2)
POST /api/runs/manualwith{"scope":"All Programs"}->200(runId=bc058da6-adea-4f74-a745-9f9dd34d7a66,activeMeasuresExecuted=2)
- Backend run APIs expanded:
GET /api/runssupports filters:status,scopeType,triggerType,limitGET /api/runs/{id}/logsreturns persisted run-log entries (latest-first)- Existing
GET /api/runs/{id}retained for summary/detail
- Backend service additions:
- Added run list query with filter and limit controls
- Added run log query with limit controls
- Frontend
/runsrewritten from S0 probe page to run-ops console:- Filter bar (status/scope/trigger)
- Run history table with status/scope/duration
- Run detail panel (counts, pass rate, timings)
- Run logs panel (level/timestamp/message)
- Manual "Run Measures Now" trigger integrated with refresh and selection
- Controller test coverage added for:
- run list endpoint filters
- run detail endpoint
- run logs endpoint
Verification checkpoints (local):
backend\\gradlew.bat test-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Production deployment + hotfix checkpoint:
- Commits pushed:
ebee7db(feat(run): expand run history and logs visibility [S3])443102c(fix(run): harden run list filtering and complete run visibility [S3])
- Backend redeployed:
https://workwell-measure-studio-api.fly.dev - Frontend redeployed + aliased:
https://frontend-seven-eta-24.vercel.app - Live issue discovered and fixed immediately:
- Initial
GET /api/runsreturned500due to nullable filter SQL handling. - Fixed by switching to dynamic SQL condition construction (only bind
LOWER(?)clauses when filters are present).
- Initial
- Timestamped production smoke check (
2026-05-04T00:44:07-04:00):GET /actuator/health->UPGET /api/runs?limit=5->200(runCount=5)GET /api/runs/{id}->200(status=completed)GET /api/runs/{id}/logs?limit=5->200(logCount=1)
GET https://frontend-seven-eta-24.vercel.app/runs->200
- Added standardized freshness fields to run summary responses:
dataFreshAsOf: latestoutcomes.evaluated_attimestamp for the rundataFreshnessMinutes: age in minutes fromdataFreshAsOfto now
- Frontend
/runsdetail panel now surfaces:- "Data Freshness: X min old"
- "Data Fresh As Of: "
- Controller test fixture updated to include freshness fields in run summary payload.
Verification checkpoints (local):
backend\\gradlew.bat test-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Deployment + production checkpoint:
- Commit pushed:
ec7c794(feat(run): add data freshness indicators to run summaries [S3]) - Backend redeployed:
https://workwell-measure-studio-api.fly.dev - Frontend redeployed + aliased:
https://frontend-seven-eta-24.vercel.app - Timestamped production smoke check (
2026-05-04T00:47:59-04:00):GET /api/runs?limit=1->200
GET /api/runs/{id}-> includesdataFreshAsOfanddataFreshnessMinutes(30)GET https://frontend-seven-eta-24.vercel.app/runs->200
- Expanded backend case list filters:
- Existing:
status,measureId - Added:
priority,assignee,site
- Existing:
- Expanded frontend
/casesfilter controls:Status,Measure,Priority,Assignee,Site- Query-string filter wiring to backend API
- Added
sitefield to case summary payload and surfaced site in case cards. - Updated MCP case listing integration call-site for new case-list method signature.
Verification checkpoints (local):
backend\\gradlew.bat test-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Deployment + production checkpoint:
- Commit pushed:
f9e0ed2(feat(caseflow): expand worklist filters across api and ui [S4]) - Backend redeployed:
https://workwell-measure-studio-api.fly.dev - Frontend redeployed + aliased:
https://frontend-seven-eta-24.vercel.app - Timestamped production smoke check (
2026-05-04T01:11:34-04:00):GET /api/cases?status=open&priority=HIGH->200(highOpenCount=11)GET /api/cases?status=all&site=Clinic->200(clinicCasesCount=8)GET /api/cases?status=all&assignee=unassigned->200(unassignedCasesCount=28)GET https://frontend-seven-eta-24.vercel.app/cases->200
- Added backend case actions:
POST /api/cases/{caseId}/assign?assignee=<name>POST /api/cases/{caseId}/escalate
- Action behavior:
- Assign updates
cases.assignee, recordscase_actionsrow (ASSIGNED), emitsCASE_ASSIGNED. - Escalate sets
priority=HIGH, keepsstatus=OPEN, updates next action text, recordscase_actionsrow (ESCALATED), emitsCASE_ESCALATED.
- Assign updates
- Added frontend controls on case detail page:
- Assignee input + Assign button
- Escalate button
- Added controller tests for assign/escalate endpoints.
Verification checkpoints (local):
backend\\gradlew.bat test-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Deployment + production checkpoint:
- Commit pushed:
46849b5(feat(caseflow): add assignment and escalation actions [S4]) - Backend redeployed:
https://workwell-measure-studio-api.fly.dev - Frontend redeployed + aliased:
https://frontend-seven-eta-24.vercel.app - Timestamped production smoke check (
2026-05-04T01:48:47-04:00):GET /actuator/health->UPGET /api/cases?status=open->200(openCaseCount=27,caseId=c6d79a2f-8f86-4d48-ac91-06f21d478ccb)POST /api/cases/{caseId}/assign?assignee=QA%20Lead&actor=codex-smoke->200(status=OPEN,assignee=QA Lead)POST /api/cases/{caseId}/escalate?actor=codex-smoke->200(status=OPEN,priority=HIGH)GET https://frontend-seven-eta-24.vercel.app/cases/{caseId}->200
- Improved assignment action evidence consistency:
- Assignment payload now records real
previousAssigneeinstead of"unknown".
- Assignment payload now records real
- Improved case timeline completeness:
- Case detail timeline now merges both
audit_eventsandcase_actions, ordered chronologically. - Timeline payload entries now include
timelineSource(audit_eventorcase_action) for clearer provenance.
- Case detail timeline now merges both
- Improved case-detail evidence clarity:
- Added structured quick-read fields for
why_flaggedin UI (last exam date, window, overdue days, eligibility, waiver status). - Timeline event labels are now human-readable (for example
CASE_ESCALATED->Case Escalated).
- Added structured quick-read fields for
Verification checkpoints (local):
backend\\gradlew.bat test-> PASSfrontend npm run lint-> PASSfrontend npm run build-> PASS
Production stabilization follow-through:
- Initial deployment surfaced a regression on case detail (
GET /api/cases/{id}->500). - Root cause: timeline SQL referenced
case_actions.created_at, but schema usesperformed_at. - Additional hardening applied:
- normalized union sort-key typing (
id::text) for mixed audit/case-action streams - made timeline payload parsing resilient to non-object JSON payloads
- normalized union sort-key typing (
- Final fix commits:
88ee989(fix(caseflow): use performed_at for case action timeline [S4])- plus prior timeline hardening commits in same slice
- Timestamped production verification (
2026-05-04T02:08:47-04:00):GET /api/cases?status=open->200GET /api/cases/{id}->200(timelineCount=15,timelineSources=audit_event,case_action)GET https://frontend-seven-eta-24.vercel.app/cases/{id}->200
- Completed critical status-source cleanup:
- Removed legacy name-based filtering hacks for
AnnualAudiogramCompleted. - Enforced
measure_versions.statusas the source of truth for active measure scope. - Added explicit active-scope query in run persistence:
SELECT DISTINCT m.id, m.name, mv.id AS measure_version_id, mv.status FROM measures m JOIN measure_versions mv ON mv.measure_id = m.id WHERE mv.status = 'Active'.
- Removed legacy name-based filtering hacks for
- Added manual all-programs run endpoint:
POST /api/runs/manualwith scope"All Programs".- Endpoint now resolves active measure versions via the active-scope query and persists a run with
scope_type='all_programs'.
- Case upsert idempotency hardening:
- Replaced split insert/update logic with a single
INSERT ... ON CONFLICT (employee_id, measure_version_id, evaluation_period) DO UPDATE. - Confirmed case write path is now deterministic for reruns over the same key.
- Replaced split insert/update logic with a single
- Compliant rerun closure behavior aligned to spec:
- Chosen state:
RESOLVED(documented in code comment). - Compliant reruns now transition open cases to resolved state and emit
CASE_RESOLVED.
- Chosen state:
- Seed strategy decision for
patient-*rows:- Selected Option A.
- Removed
patient-*exclusion filter from case list path. - Added code comment documenting legacy
patient-*+emp-*rows as valid demo records.
- MCP tools wired to explicit live payload contracts:
list_casesnow returns status, priority, assignee, andmeasure_version_id.get_run_summarynow returnstotal_cases,compliant_count,non_compliant_count,pass_rate, andduration.get_casenow exposes full evidence payload plus extractedwhy_flagged.
- Evidence payload structured:
- Demo run engines now persist
why_flaggedobject with:last_exam_date,compliance_window_days,days_overdue,role_eligible,site_eligible,waiver_status(+ outcome metadata).
- Demo run engines now persist
- Audit coverage added:
MEASURE_VERSION_DRAFT_SAVEDon spec/CQL draft edits.MEASURE_VERSION_STATUS_CHANGEDon lifecycle transitions (including activation).RUN_STARTEDandRUN_COMPLETEDon run flows (measure runs + case rerun verification + all-program runs).
Verification checkpoints (local):
backend\\gradlew.bat compileJava-> PASSbackend\\gradlew.bat test --tests \"com.workwell.web.CaseControllerTest\" --tests \"com.workwell.web.EvalControllerTest\"-> PASSbackend\\gradlew.bat test-> FAIL on environment-level Docker/Testcontainers availability (DockerClientProviderStrategy), not on compile.frontend npm run lint-> PASSfrontend npm run build-> PASS
Follow-up verification after Docker restore:
backend\\gradlew.bat test-> PASS (all tests green once Docker/Testcontainers were available).- Fresh DB smoke issue found and fixed:
- Initial
/api/runs/manualon empty DB returned500(No active measures found to execute). - Fix applied in
EvalController: callmeasureService.listMeasures()before resolving active measure scope so default active seeds are present.
- Initial
- Smoke re-run against containerized backend + postgres:
POST /api/runs/manualnow succeeds on fresh DB without needing a prior/api/measurescall.- Sample result:
activeMeasuresExecuted=2,totalEvaluated=25,totalCases=14,passRate=32.0.
Git closeout:
- Grouped final changes into logical commits (backend+tests, frontend, docs) with spike-tagged commit messages.
- Verified no extra temp/runtime artifacts remained after Docker smoke runs.
- Final local checks remained green before closeout:
backend\\gradlew.bat testfrontend npm run lintfrontend npm run build
- External validation continued to report stale public responses (
3measures includingAnnualAudiogramCompleted) despite app-level filtering checks from our side. - To remove dependence on machine/region/code-path behavior, applied direct database cleanup against production data:
- Legacy measure version rows for
AnnualAudiogramCompletedset toDeprecated(no remainingActiveversions). - Legacy placeholder open cases (
employee external_id LIKE 'patient-%') set toCLOSEDwithclosed_at=NOW().
- Legacy measure version rows for
- Post-change data assertions:
active_legacy_versions=0open_legacy_cases=0
Timestamped production checkpoint (2026-05-03T20:40:00-04:00):
GET https://workwell-measure-studio-api.fly.dev/actuator/health->UPGET https://workwell-measure-studio-api.fly.dev/api/measures?cb=<timestamp>->200, returns exactly 2 active measures (TB Surveillance,Audiogram)GET https://workwell-measure-studio-api.fly.dev/api/cases?status=open&cb=<timestamp>->200,open_count=13,legacy_rows=0- Response trace sample:
fly-request-id: 01KQR6W1V49NHKNZ0HQCYYXKG4-ord
- Completed end-to-end live walkthrough aligned to
docs/DEMO_SCRIPT.mdon production backend + frontend. - Confirmed clickable frontend shell routes for demo navigation:
/measures,/studio,/runs,/cases,/programs,/worklistall return200onhttps://frontend-seven-eta-24.vercel.app.
- Case lifecycle demo loop executed live on an open Audiogram overdue case:
POST /api/cases/{caseId}/actions/outreach-> case remainedOPENPOST /api/cases/{caseId}/rerun-to-verify-> case transitionedCLOSEDwithCOMPLIANT- Case timeline tail includes
CASE_OUTREACH_SENT,CASE_RERUN_VERIFIED,CASE_CLOSED
Timestamped endpoint checklist (2026-05-03T20:00:00-04:00):
-
GET https://workwell-measure-studio-api.fly.dev/actuator/health->UP -
GET https://workwell-measure-studio-api.fly.dev/api/measures->200with 2 active measures (TB Surveillance,Audiogram) -
GET https://workwell-measure-studio-api.fly.dev/api/cases?status=open->200, nopatient-*rows -
GET https://workwell-measure-studio-api.fly.dev/api/cases?status=open&measureId=<audiogram-id>->200, clean filtered list -
POST https://workwell-measure-studio-api.fly.dev/api/runs/audiogram->200;GET /api/runs/{id}->200(totalEvaluated=15) -
POST https://workwell-measure-studio-api.fly.dev/api/runs/tb-surveillance->200; TB case detailnextActionconfirms TB-specific copy -
GET https://workwell-measure-studio-api.fly.dev/api/audit-events/export?format=csv->200 -
MCP Layer 1 validation: confirmed via Claude Code with live responses (open Audiogram cases + latest run summary)
-
Readiness decision: operational demo flow is stable and sign-off ready for D16 with bug-fix-only posture.
- Fixed TB next-action copy bug in caseflow action generation:
- TB open-case actions now use TB-specific language:
Schedule the annual TB screening before the due date.Escalate TB screening follow-up immediately.Collect the missing TB screening documentation.
- TB open-case actions now use TB-specific language:
- Clarified verification detail:
- Existing TB cases created before the fix retained old text.
- After triggering a fresh TB run in production (
runId=6793de66-b547-445e-8bcf-90fff6b621ec), TB case detail now shows corrected TB-specificnextAction.
- Removed legacy demo clutter from list surfaces:
- Measure list now excludes legacy
AnnualAudiogramCompleted. - Case list now excludes legacy placeholder employees (
patient-*) and the legacy measure line.
- Measure list now excludes legacy
- Replaced placeholder frontend routes to avoid blank-page demo risk:
/programsnow provides navigation cards to live demo surfaces (/measures,/runs)./worklistnow routes users directly to live cases via CTA (/cases).
- Production verification:
GET https://workwell-measure-studio-api.fly.dev/actuator/health->UPGET https://workwell-measure-studio-api.fly.dev/api/measures-> 2 measures (TB Surveillance,Audiogram)GET https://workwell-measure-studio-api.fly.dev/api/cases?status=open-> nopatient-*rowsGET https://workwell-measure-studio-api.fly.dev/api/cases?status=open&measureId=<tb-id>+ case detail -> TB-specificnextActionconfirmed- Frontend redeployed and aliased:
https://frontend-seven-eta-24.vercel.app
- Rewrote
docs/advisor_update.mdinto a clean, comprehensive status packet for external advisor review. - Included:
- shipped scope through Step 6,
- latest MCP validation evidence from Claude Code,
- production smoke snapshot,
- explicit agent recommendations for D16 demo-freeze strategy,
- targeted clarifying questions for advisor guidance on final sequencing and risk tolerance.
- Intent: accelerate advisor feedback loop and lock final pre-D16 execution posture without scope creep.
- Claude Code MCP validation now passes end-to-end with real data:
- Prompt equivalent: "Show me all open Audiogram cases" returned 10 open Audiogram cases.
- Prompt equivalent: "Get the summary of the latest run" returned run summary with counts:
COMPLIANT=3,DUE_SOON=3,OVERDUE=4,MISSING_DATA=3,EXCLUDED=2,totalEvaluated=15.
- This confirms stale-schema fallback works (
measureId=\"Audiogram\") and latest-run default behavior works (get_run_summarywithoutrunId). - Production smoke pass rerun after validation:
2026-05-03T02:36:00-04:00GET https://workwell-measure-studio-api.fly.dev/actuator/health->UP2026-05-03T02:36:00-04:00GET https://workwell-measure-studio-api.fly.dev/api/measures->200(Audiogram Activev1.0, TB Surveillance Activev1.3)2026-05-03T02:36:00-04:00GET https://workwell-measure-studio-api.fly.dev/api/cases?status=open->200(17 open)2026-05-03T02:36:00-04:00GET https://workwell-measure-studio-api.fly.dev/api/cases?status=open&measureId=4ae5d865-3d64-4a17-905d-f1b315a037e2->200(10 open Audiogram)2026-05-03T02:36:00-04:00POST https://workwell-measure-studio-api.fly.dev/api/runs/audiogram->200(runId=f7e73f4a-cc22-4be1-b417-9420040e0fd4)2026-05-03T02:36:00-04:00GET https://workwell-measure-studio-api.fly.dev/api/runs/f7e73f4a-cc22-4be1-b417-9420040e0fd4->200(totalEvaluated=15)2026-05-03T02:36:00-04:00POST https://workwell-measure-studio-api.fly.dev/api/runs/tb-surveillance->200(runId=5cc29869-8abf-4f66-9a09-2bdeee32751d)2026-05-03T02:36:00-04:00GET https://workwell-measure-studio-api.fly.dev/api/audit-events/export?format=csv->2002026-05-03T02:36:00-04:00GET https://workwell-measure-studio-api.fly.dev/ssewithAccept: text/event-stream->200(stream endpoint reachable)
- User validation surfaced MCP input friction:
list_casesrequiredmeasureIdUUID andget_run_summaryrequired explicitrunId, which blocked natural-language prompt execution in Claude Code. - Applied backend MCP compatibility update:
list_casesnow supports eithermeasureIdormeasureName(case-insensitive lookup through measure catalog).get_run_summarynow accepts optionalrunId; when omitted, it returns the latest persisted run.- Added
RunPersistenceService.loadLatestRun()to back the latest-run path.
- Production checkpoint:
2026-05-03T02:06:00-04:00GET https://workwell-measure-studio-api.fly.dev/actuator/health->UP2026-05-03T02:06:00-04:00GET https://workwell-measure-studio-api.fly.dev/api/measures->200
- Advisor review completed. Progress confirmed through S1 (Audiogram vertical) and early S4 backend (case lifecycle + audit chain).
- S2 (catalog/authoring) confirmed as the highest-priority remaining spike.
- Decision: rerun-to-verify remains demo-simulated for all measures through D16. Do not generalize the evaluator this sprint.
- Decision: S5 MCP scope is limited to Layer 1 only - three read-only tools (
get_case,list_cases,get_run_summary) wrapping existing API endpoints. AI explain and write tools are post-D16. - Decision: S6 video/walkthrough production is deferred until a stable live demo exists. Written demo script is sufficient for D16.
- Revised execution priority order is now recorded in
docs/SPIKE_PLAN.mdand supersedes prior task ordering.
- Updated
docs/JOURNAL.mdanddocs/SPIKE_PLAN.mdper advisor instructions before implementation changes. - Added explicit S2 thin-vertical scope note and revised priority order with deferred items.
- Production checkpoint:
2026-05-02T23:58:38-04:00GET https://workwell-measure-studio-api.fly.dev/actuator/health->UP
- Implemented backend Measure APIs:
GET /api/measuresPOST /api/measuresGET /api/measures/{id}PUT /api/measures/{id}/specPUT /api/measures/{id}/cqlPOST /api/measures/{id}/cql/compilePOST /api/measures/{id}/status
- Seeded Audiogram as catalog-visible Active
v1.0in service-level seed guard. - Implemented frontend S2 UI:
/measurestable with status pills and create flow/studio/[id]with Spec tab, CQL tab + compile gate, lifecycle action buttons- Save Draft success toast behavior on Spec save
- Local verification:
backend\\gradlew.bat test->BUILD SUCCESSFULfrontend npm run lint-> successfrontend npm run build-> success
- Deployment state:
- Frontend production deployed:
https://frontend-seven-eta-24.vercel.app - Backend deploy currently blocked on this machine because
flyctlis not installed (flyctlcommand not found).
- Frontend production deployed:
- Production checkpoint evidence:
2026-05-02T23:58:38-04:00GET https://workwell-measure-studio-api.fly.dev/actuator/health->UP2026-05-02T23:58:38-04:00GET https://workwell-measure-studio-api.fly.dev/api/measures->404(expected until backend deployment with Step 1 code)
- Backend deployed via Fly after
flyctlinstall. - Production checkpoint:
2026-05-03T00:17:01-04:00GET https://workwell-measure-studio-api.fly.dev/actuator/health->UP2026-05-03T00:19:48-04:00GET https://workwell-measure-studio-api.fly.dev/api/measures->200
- Frontend production deployed and aliased:
https://frontend-seven-eta-24.vercel.app
Audit answers:
- Which classes/methods in
AudiogramDemoServiceandRunPersistenceServicewere hardcoded to Audiogram fixtures?AudiogramDemoService.run()hardcoded Audiogram patient fixture list and Audiogram-specific measure name/version.RunPersistenceService.persistAudiogramRun(...),loadLatestAudiogramRun(),loadOutcomesForRun(...), and seed helpers (ensureMeasure*) were coupled to Audiogram types/constants and patient-id naming.
- Does
CaseFlowServicereference any Audiogram-specific types or IDs?- Before refactor: yes, method signatures used
AudiogramDemoService.AudiogramOutcome, and several message strings/templates were Audiogram-specific. - After refactor: shared case upsert path now uses generic
DemoOutcomemodel and no longer depends on Audiogram Java types/IDs.
- Before refactor: yes, method signatures used
- Can a second measure seeded run be added by implementing a new
DemoService+ registering it, without modifyingCaseFlowServiceorRunPersistenceService?- Yes.
RunPersistenceServicenow exposespersistDemoRun(DemoRunPayload)andCaseFlowServiceaccepts generic outcome models (upsertCases(...)), so a second measure service can plug into the same run/case/audit infrastructure.
- Yes.
Minimum changes applied:
- Added shared run models:
backend/src/main/java/com/workwell/run/DemoRunModels.java
- Refactored shared persistence to generic payload:
RunPersistenceService.persistDemoRun(...)added and used by existing Audiogram path.
- Refactored shared case upsert path to generic outcomes:
CaseFlowService.upsertCases(...)now accepts sharedDemoOutcome.
- Kept simulation pattern in place (no generalized evaluator introduced).
Verification + deployment checkpoint:
- Local backend verification:
backend\\gradlew.bat test->BUILD SUCCESSFUL
- Production checkpoint:
2026-05-03T00:23:51-04:00GET https://workwell-measure-studio-api.fly.dev/actuator/health->UP2026-05-03T00:23:51-04:00GET https://workwell-measure-studio-api.fly.dev/api/measures->2002026-05-03T00:23:51-04:00POST https://workwell-measure-studio-api.fly.dev/api/runs/audiogram->200
Implemented:
- Backend case filters:
GET /api/cases?status=open|closed|all(defaultopen)GET /api/cases?measureId=<measure-id>(optional, combinable with status)
- Frontend
/casesfilter controls:Statusdropdown (Open / Closed / All), default OpenMeasuredropdown (populated from active measures)- Re-fetch on filter changes
Audit chain linkage verification (Audiogram path):
- Code-path inspection confirms required run/case linkage for the demo lifecycle chain:
CASE_CREATED/CASE_UPDATEDincluderef_run_idandref_case_idCASE_OUTREACH_SENTincludesref_run_idandref_case_idCASE_RERUN_VERIFIEDincludesref_run_idandref_case_idCASE_CLOSEDincludesref_run_idandref_case_id
- No additional linkage fix was required for the specified chain.
Verification + deployment checkpoint:
- Local verification:
backend\\gradlew.bat test->BUILD SUCCESSFULfrontend npm run lint-> successfrontend npm run build-> success
- Production deploy:
- Backend deployed on Fly
- Frontend deployed and aliased to
https://frontend-seven-eta-24.vercel.app
- Production checkpoint:
2026-05-03T00:28:21-04:00GET https://workwell-measure-studio-api.fly.dev/actuator/health->UP2026-05-03T00:28:21-04:00GET https://workwell-measure-studio-api.fly.dev/api/cases?status=open->200(3 cases)2026-05-03T00:28:21-04:00GET https://workwell-measure-studio-api.fly.dev/api/cases?status=open&measureId=<active-id>->200(filter path verified)
Implemented:
- Added shared synthetic employee catalog with ~50 employees across required roles/sites:
- Roles represented:
Maintenance Tech,Nurse,Welder,Office Staff,Industrial Hygienist,Clinic Staff - Sites represented:
Plant A,Plant B,Clinic
- Roles represented:
- Extended run persistence seeding to maintain the synthetic employee roster in
employeesand upsert profile fields (name, role, site). - Expanded Audiogram simulation to a larger seeded cohort with mixed outcomes and persisted case generation through existing run/case/audit pipeline.
- Added
TBSurveillanceDemoServiceand registered:POST /api/runs/tb-surveillance
- Added TB measure seed in catalog as Active:
TB Surveillanceversionv1.3
- Aligned Audiogram demo run metadata to:
Audiogramversionv1.0
TB run distribution validation:
- Production TB run response currently returns:
outcomes=10compliant=5dueSoon=1overdue=2missingData=1excluded=1
- This satisfies the target mix for demo credibility and keeps run simulation per-measure (no generalized evaluator introduced).
Verification + deployment checkpoint:
- Local backend verification:
backend\\gradlew.bat test->BUILD SUCCESSFUL
- Production checkpoint:
2026-05-03T01:04:54-04:00GET https://workwell-measure-studio-api.fly.dev/actuator/health->UP2026-05-03T01:04:54-04:00GET https://workwell-measure-studio-api.fly.dev/api/measures-> includes ActiveAudiogramand ActiveTB Surveillance2026-05-03T01:04:54-04:00POST https://workwell-measure-studio-api.fly.dev/api/runs/tb-surveillance->200
Implemented MCP Layer 1 as read-only tools only:
get_case- Input:
caseId: string - Returns full case detail payload from existing caseflow read path.
- Input:
list_cases- Input:
status?: string(defaultopen),measureId?: string - Returns case summaries using existing filtered case listing path.
- Input:
get_run_summary- Input:
runId: string - Added supporting endpoint:
GET /api/runs/{id}for run metadata + outcome counts by status.
- Input:
Implementation notes:
- Added MCP Java SDK dependencies and Spring WebMVC SSE transport wiring.
- MCP server config:
backend/src/main/java/com/workwell/mcp/McpServerConfig.java
- New run summary endpoint:
backend/src/main/java/com/workwell/web/RunController.java
Validation status:
- Programmatic MCP transport validation completed:
GET /ssereturns MCP endpoint event with session-scoped message route.- MCP initialize and message POST handshake return success status.
- Full Claude Desktop interactive validation is pending in this environment (no direct Claude Desktop UI session available from this runtime).
Deployment checkpoint:
2026-05-03T01:19:02-04:00GET https://workwell-measure-studio-api.fly.dev/actuator/health->UP2026-05-03T01:19:02-04:00GET https://workwell-measure-studio-api.fly.dev/api/runs/{id}->2002026-05-03T01:19:02-04:00GET https://workwell-measure-studio-api.fly.dev/sse-> MCP endpoint advertised
Implemented:
- Audit trail CSV export endpoint:
GET /api/audit-events/export?format=csv- Columns:
timestamp,eventType,caseId,runId,measureName,employeeId,actor,detail
- Frontend export control:
- Added Export CSV button on
/casesto trigger browser download.
- Added Export CSV button on
- Added written demo script:
docs/DEMO_SCRIPT.md
Local verification:
backend\\gradlew.bat test->BUILD SUCCESSFULfrontend npm run lint-> successfrontend npm run build-> success
Production checkpoint:
2026-05-03T01:19:02-04:00GET https://workwell-measure-studio-api.fly.dev/actuator/health->UP2026-05-03T01:19:02-04:00GET https://workwell-measure-studio-api.fly.dev/api/audit-events/export?format=csv->200(text/csv)
Goals set
- Start S1a by replacing placeholder run flow with a real measure-specific vertical slice.
- Keep changes within backend/frontend ownership boundaries and preserve ADR-002 evidence shape.
What shipped
- Added seeded Audiogram demo evaluator service for 5 synthetic patients with outcome buckets:
COMPLIANT,DUE_SOON,OVERDUE,MISSING_DATA,EXCLUDED- File:
backend/src/main/java/com/workwell/measure/AudiogramDemoService.java
- Added S1a run endpoint:
POST /api/runs/audiogram- File:
backend/src/main/java/com/workwell/web/EvalController.java
- Added DB-backed persistence and readback for seeded runs:
runs,outcomes,audit_eventsrows are written throughRunPersistenceServiceGET /api/runs/audiogram/latestreads the latest persisted run- File:
backend/src/main/java/com/workwell/run/RunPersistenceService.java
- Added baseline authored CQL resource for Annual Audiogram:
- File:
backend/src/main/resources/measures/audiogram.cql
- File:
- Expanded dashboard run page to execute and render the S1a vertical response, including run summary and per-patient evidence payloads:
- File:
frontend/app/(dashboard)/runs/page.tsx
- File:
Verification
- Backend tests:
backend\\gradlew.bat test->BUILD SUCCESSFUL - Frontend lint:
npm run lint-> success - Frontend production build:
npm run build-> success
Notes
- This slice establishes the S1a authored-measure/run/evidence path with deterministic seeded outcomes.
- Persistence is now live for seeded Audiogram runs; case detail integration remains for next S1a steps.
Fix + redeploy
- Live
/api/runs/audiograminitially failed because the seeded missing-data patient produced anullevidence value andMap.of(...)rejected it. - Updated evidence assembly to use null-safe
LinkedHashMappayloads. - Added a direct service test for the seeded run to guard against the same regression.
- Redeployed Fly backend and verified live success:
POST https://workwell-measure-studio-api.fly.dev/api/runs/audiogram->200OPTIONS https://workwell-measure-studio-api.fly.dev/api/runs/audiogram->200- Returned summary counts:
1 / 1 / 1 / 1 / 1across compliant, due soon, overdue, missing data, excluded
Current status
- Backend and frontend both verify locally after persistence wiring.
- Ready to push the DB-backed run path live and confirm the latest-run readback in the browser.
Caseflow / Why Flagged
- Wired seeded Audiogram outcomes into the
casestable for non-compliant statuses:DUE_SOON,OVERDUE,MISSING_DATAcreate or refresh open cases.COMPLIANTandEXCLUDEDclose an existing case if one is already present.
- Added read APIs for:
GET /api/casesGET /api/cases/{id}
- Added frontend case views:
/caseslist page/cases/[id]detail page with structured evidence, metadata, and audit timeline
- Verification completed after the change:
backend\\gradlew.bat test->BUILD SUCCESSFULnpm run lint-> successnpm run build-> success
Case action + rerun-to-verify loop
- Added case action API endpoints:
POST /api/cases/{id}/actions/outreachPOST /api/cases/{id}/rerun-to-verify
- Added backend case lifecycle behavior for S4b:
- Outreach action writes
case_actionsplusCASE_OUTREACH_SENTaudit event. - Rerun-to-verify writes a case-scoped verification run, persists a compliant verification outcome, records action/audit events, and closes the case.
- Outreach action writes
- Added UI controls on
/cases/[id]:Send outreachRerun to verify- Page refreshes with updated status and audit timeline after each action.
- Verification after this slice:
backend\\gradlew.bat test->BUILD SUCCESSFULnpm run lint-> successnpm run build-> success
Deploy + live checkpoint verification
- Backend deployed to Fly using repo-root context with backend config:
flyctl deploy --config backend/fly.toml- Live URL:
https://workwell-measure-studio-api.fly.dev
- Frontend deployed to Vercel production:
- Deployment:
https://frontend-5wx93gznt-taleef7s-projects.vercel.app - Active alias observed:
https://frontend-seven-eta-24.vercel.app
- Deployment:
- Live API verification evidence:
GET /actuator/health->UPPOST /api/runs/audiogram-> returned run id79d87735-81b7-42dc-86b2-bf200a196890GET /api/cases->3casesPOST /api/cases/d99266b2-0cb8-47b9-a329-c7ccc89ea00e/actions/outreach-> next action updated to follow-up + rerun guidancePOST /api/cases/d99266b2-0cb8-47b9-a329-c7ccc89ea00e/rerun-to-verify-> case transitioned toCLOSEDwithCOMPLIANTGET /api/cases/d99266b2-0cb8-47b9-a329-c7ccc89ea00e->closedAtpresent and timeline length5
- Checkpoint readout:
- The core S4b loop (open case -> outreach action -> rerun verification -> case closure + audit chain) is now live and test-backed.
- Ready to re-evaluate completed scope against SPIKE_PLAN acceptance and pick the next highest-risk gap.
Advisor checkpoint package
- Added
docs/advisor_update.mdas a comprehensive status handoff for external advisor review. - Document includes:
- spike-by-spike Done/Partial/Missing matrix against
docs/SPIKE_PLAN.md - execution evidence from
docs/JOURNAL.mdand deploy checks - issue log, risk assessment, and recommended next execution sequence
- explicit advisor feedback prompts for scope/risk decisions
- spike-by-spike Done/Partial/Missing matrix against
Goals set
- Finalize canonical sprint docs and archive legacy planning docs.
- Prepare deploy targets (Neon, Fly.io, Vercel) without doing the D2 deployment.
- Close ADR-002 on
evidence_jsonshape to unblock S1.
What shipped today
- Archived legacy plan files under
docs/archive/, includingPROJECT_PLAN_v1.mdwith top note:- "Archived May 2, 2026. Replaced by docs/SPIKE_PLAN.md."
- Canonical sprint docs are now in place:
docs/SPIKE_PLAN.mddocs/DEPLOY.mdAGENTS.mdandCLAUDE.mdupdated to point toSPIKE_PLAN.mdas source of truth.
- Added root
.env.examplewith all deployment variables fromdocs/DEPLOY.md:DATABASE_URLDATABASE_URL_DIRECTANTHROPIC_API_KEYSPRING_PROFILES_ACTIVENEXT_PUBLIC_API_BASE_URLNEXT_PUBLIC_APP_NAME
- Added
backend/fly.tomlwith D1 baseline:- app:
workwell-measure-studio-api - region:
ord - memory:
512mb - healthcheck:
/actuator/health - JVM opts:
-Xmx384m -Xss256k
- app:
- Closed ADR-002 in
docs/DECISIONS.mdwith accepted shape:evidence_json = { expressionResults, evaluatedResource }rule_path[]derived at render time (not persisted)
Sub-spike / verification evidence
- Re-ran CQF ADR probe test in spike repo:
../workwell-spike-cqf:./gradlew.bat test --tests com.workwell.spike.DualEvaluationCostSubSpikeTest- Result:
BUILD SUCCESSFUL
- Backend tests in this repo were green in D1 verification sweep:
backend\gradlew.bat test->BUILD SUCCESSFUL
Provisioning status (end of D1)
- Fly:
- Authenticated and app created with
flyctl launch --no-deploy. - Current staged secret:
SPRING_PROFILES_ACTIVE=prod. - No app deploy performed (correct for D1).
- Authenticated and app created with
- Vercel:
- Git repository now connected (confirmed in project Git settings).
- Preview deployment failure observed on PR branch due to project root mismatch.
- Exact error: "No Next.js version detected".
- Root cause: Vercel building from repo root while Next.js app lives in
frontend/. - Required fix: set Vercel project Root Directory to
frontendand redeploy.
- Neon:
- CLI provisioning created a project defaulting to PostgreSQL 17.
- This conflicts with locked stack requirement (PostgreSQL 16).
- DB secrets pointing to PG17 were intentionally not kept as final runtime configuration.
What surprised
- Neon CLI default behavior is PG17 unless PG version is explicitly controlled through supported path.
- Vercel integration succeeded, but monorepo root detection still caused preview build failure.
- CQF processor two-step path remains the best evidence-friendly path and did not require a second full evaluation in the measured probe.
Risk status
- ADR-002 risk: closed.
- Vercel preview build risk: open until Root Directory is set to
frontend. - Database version compliance risk: open until Neon PG16 target is created/selected.
Plan for D2 (S0 walking skeleton only)
- Do not add scope beyond S0.
- Complete infra readiness first:
- Ensure Vercel Root Directory =
frontendand preview deploy succeeds. - Ensure Neon target is PostgreSQL 16.
- Set final Fly DB secrets (
DATABASE_URL,DATABASE_URL_DIRECT) from compliant PG16 Neon target. - Add
ANTHROPIC_API_KEYonly if AI surface is exercised in S0 path.
- Ensure Vercel Root Directory =
- Then execute S0 end-to-end:
- Backend
/api/evalon Fly - Frontend call from Vercel
- Health checks and demoable round-trip
- Backend
What shipped in code
- Added backend stub-auth security config to allow sprint-phase unauthenticated API access:
backend/src/main/java/com/workwell/config/SecurityConfig.java
- Added S0 walking-skeleton endpoint:
POST /api/evalinbackend/src/main/java/com/workwell/web/EvalController.java- Accepts
patientBundle+cqlLibrary, returns placeholder outcome + evidence payload shape.
- Added endpoint test:
backend/src/test/java/com/workwell/web/EvalControllerTest.java
- Replaced placeholder "Test Runs" UI with an S0 API probe page:
frontend/app/(dashboard)/runs/page.tsx- Button posts sample payload to
${NEXT_PUBLIC_API_BASE_URL}/api/evaland renders response/error.
Verification run
- Backend:
backend\\gradlew.bat test->BUILD SUCCESSFUL
- Frontend:
npm run lint-> successnpm run build-> success
Still pending outside repo code
- Vercel project setting: Root Directory must be
frontend. - Neon runtime target must be PostgreSQL 16 before final Fly DB secret wiring.
- Deployed S0 validation on live URLs (Fly
/actuator/health, Vercel/runsprobe).
Infra completion
- Neon PG16 project created and selected for runtime (
workwell-measure-studio-pg16). - Fly secrets set with JDBC-form
DATABASE_URLandDATABASE_URL_DIRECTvalues from PG16 target. - Backend deployed to Fly and verified healthy on:
https://workwell-measure-studio-api.fly.dev/actuator/health
- Vercel root directory locked to
frontendand production alias confirmed:https://workwell-measure-studio.vercel.app
What shipped after D2 prep
- Backend CORS handling enabled in spring security to allow browser preflight from Vercel frontend.
- File:
backend/src/main/java/com/workwell/config/SecurityConfig.java
- File:
- Frontend eval probe hardened by normalizing
NEXT_PUBLIC_API_BASE_URLand surfacing the full request URL on failure.- File:
frontend/app/(dashboard)/runs/page.tsx
- File:
Production verification evidence
- Preflight check from Vercel origin to Fly eval endpoint:
OPTIONS /api/eval->200,Access-Control-Allow-Originreturned correctly.
- Direct API eval check:
POST https://workwell-measure-studio-api.fly.dev/api/eval->200with expected placeholder payload.
- Browser check on production frontend:
/runs"Run Eval Probe" now renders successful JSON response (COMPLIANT placeholder outcome).
Commits applied during D2 completion
a62c4d3fix(api): allow CORS preflight for eval probe [S0]b672d8ffix(frontend): normalize API base URL for eval probe [S0]
Result
- S0 acceptance met: deployed patient/CQL eval probe round-trip works end-to-end across Vercel + Fly + Neon.
- Ready to move into D3/S1a Audiogram vertical.
CQF/FHIR de-risking and ADR-002 probes completed in ../workwell-spike-cqf with passing test evidence and documented transfer notes in docs/CQF_FHIR_CR_REFERENCE.md.
Initial planning baseline and scaffolding completed.
- MCP schema-compat deploy checkpoint:
- 2026-05-03T13:53:42.1028589-04:00 GET https://workwell-measure-studio-api.fly.dev/actuator/health -> UP