Skip to content

acp-writer mock BFF: canned-data implementation of the #127 UI contract - #136

Draft
khaledsulayman wants to merge 23 commits into
samschifman:mainfrom
khaledsulayman:acp-writer-bff-mock
Draft

acp-writer mock BFF: canned-data implementation of the #127 UI contract#136
khaledsulayman wants to merge 23 commits into
samschifman:mainfrom
khaledsulayman:acp-writer-bff-mock

Conversation

@khaledsulayman

Copy link
Copy Markdown
Collaborator

Summary

A mock Backend-for-Frontend for acp-writer that implements the finalized UI contract (acp-writer/api/bff-openapi.yaml, #127) with canned, deterministically-progressing data. It lets the React UI — including the care-plan review loop — be developed, demoed, and deployed without the real pipeline (no SonataFlow / MinIO / LLM / DMN / FHIR). It mirrors the cpg-ingester BFF's optional-backend pattern and plugs into the deployment framework as the bff pod in mock mode.

Motivation: the workflow (acp-writer-workflow.yaml) has no human care-plan gate yet (that's the follow-up for a ReviewCarePlan SonataFlow state), so awaiting_careplan_review — the centerpiece of the review UI — cannot be produced by the real backend today. The mock is the only way to exercise that UI now, and it doubles as a contract-conformance harness and a working skeleton for the real (SonataFlow-backed) BFF.

Depends on #127 (the contract). This branch is stacked on bff-ui-contract; it should merge after #127. Until then this PR's diff also shows the contract commits — they'll drop off automatically once #127 lands in main.

What's included

  • services/bff_models.py — pydantic v2 models mirroring the contract (camelCase aliases); reusable by the real BFF.
  • mocks/{store,data,router}.py — in-memory store with clock-derived step progression (no background threads), the 8 UI-facing endpoints, and canned hypertension data + a small FHIR bundle.
  • services/bff.py — FastAPI app: mock mode when SONATAFLOW_URL is unset; scoped CORS (not *); /health; a marked NotImplementedError stub for the real SonataFlow branch.
  • Run lifecycle: create → steps advance to awaiting_careplan_review (full CarePlanView) → approve completes + persists a plan / request_changes loops with reviewIteration + previousFeedback; cancel; and a failed run (seed + ipsBundle.mockFail sentinel) so error UI can be exercised.
  • deploy/pods/Containerfile.bff (UBI, slim) + chart-pods bff pod (mock mode) + UI→BFF NetworkPolicy so the two-pod (bff + ui) cluster demo works.
  • Tests (28): contract conformance (model aliases, enum values, and app paths pinned to the OpenAPI), store/progression edge cases, endpoint behavior, ASGI boot, and an import-hygiene guard (the slim image must not pull langgraph/mlflow).
  • Design + plan in dev_docs/.

Flags surfaced for the team

  • The workflow lacks the ReviewCarePlan human gate (tracked as a follow-up); the mock stands in until it lands.
  • Contract nit: the StepKey.review_careplan comment still describes the removed post-write PUT /careplans/{id}/status path.

Test plan

  • pytest — 28 passing (contract, store, api, boot, architecture)
  • helm lint chart-pods — 0 failed; bff/ui NetworkPolicies render
  • podman build + container smoke — /health, /status, seed runs, create-run all serve
  • Cluster: build acp-writer-bff image, helm upgrade --install with only bff+ui, verify via oc port-forward (manual milestone)

🤖 Generated with Claude Code

Generated by Claude Code under the supervision of Khaled Sulayman

khaledsulayman and others added 23 commits August 17, 2026 18:41
First-cut, screen-driven contract for the acp-writer BFF's UI-facing surface,
to be co-owned by the BFF (backend) and React UI tracks and merged before both
split off. Single source of truth: UI generates types via openapi-typescript;
BFF validates against it — no hand-synced parallel types.

Scope: UI-facing endpoints only (runs lifecycle, review gates, care plans,
status). BFF-internal plumbing (artifact notifications, SonataFlow callbacks)
is intentionally out of scope.

Three open questions flagged inline (# OPEN:) for Jaideep/Sam: SMART token
flow, whether planningBrief is gate-only vs always-present, and confirming the
StepKey vocabulary matches the SonataFlow workflow states.

Refs samschifman#28.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ates

Verified the automated StepKey values 1:1 against the acp-writer SonataFlow
workflow (acp-writer/deploy/orchestrator/acp-writer-workflow.yaml, v0.2.0) and
documented the PascalCase-state → snake_case-key mapping inline. The BFF owns
this mapping when deriving currentStep from a live instance.

Reframed the remaining open question: the two human review gates the contract
depends on (review_brief, review_careplan) do not exist in the workflow yet —
it runs straight through with only automated async callbacks. Per spike-b/
spike-c they need to be added as Callback states emitting an awaiting_review
marker to drive the awaiting_* statuses. Flagged as a backend/workflow item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
planningBrief: confirmed available from the brief gate onward (not gate-only) —
it is written to the durable MinIO PHI artifact (planning_brief_ref) after
composition and survives the whole run, so it's readable after approval. Dropped
the OPEN hedge on RunDetail.planningBrief and noted the view-model flattens the
richer internal PlanningBrief (provenance fields) if the review UI needs them.

SMART launch: split the vague token-handling OPEN into the two coupled decisions
it actually is — (1) browser vs BFF token exchange, (2) $summary read location —
and surfaced the tension between a BFF-side FHIR read and the AGENTS.md boundary
"acp-writer does not query FHIR servers." Left open as a team boundary call.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
acp-writer is a confidential SMART client (holds a secret), so the token exchange
belongs server-side in the BFF. Reshaped POST /runs/smart-launch to take
{iss, launch, code} (+ optional PKCE codeVerifier) and made BFF-side exchange the
recommended default. Documented that the current mock-EHR IPS Viewer uses
client_credentials with the secret in the browser — a dev shortcut, not a real
SMART exchange, not to be promoted to the contract. Kept browser+PKCE (public
client re-registration) as the documented alternative.

Also removed the Person A / Person B framing from the file header and OPEN notes
in favor of "UI track" / "BFF/backend track".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removed the negotiation-draft framing and the inline # OPEN blocks so the spec
reads as the intended contract, not a working doc:
- Rewrote the header (dropped "FIRST-CUT DRAFT", review-me framing).
- Dropped the SMART-launch and StepKey OPEN comment blocks; the shapes now state
  the decisions plainly (BFF-side token exchange; human gates as first-class steps).
- Trimmed draft-y rationale (bug-ticket refs, "verified vX", flattening NOTE).
- info.version 0.1.0-draft -> 0.1.0.

Open questions and talking points now live in the PR description: SMART exchange/
$summary location, the workflow human-gate work-item, and planning-brief provenance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The contract's human gates don't match the built pipeline; annotate honestly
rather than assert them as settled:
- review_brief maps onto the AUTOMATED brief_reviewer node; no human brief review
  is specified in the design docs. Marked UNCONFIRMED.
- review_careplan is a real human step but today happens post-write (PUT
  /careplans/{id}/status on the draft), not as an in-run pause.
- The awaiting_* RunStatus states model in-run pauses the SonataFlow workflow
  does not implement.
- Care-plan approval is modeled twice (POST /runs/{id}/review/careplan vs PUT
  /careplans/{id}/status); only one should survive.

These are design-review decisions, not resolved here — surfaced inline so the
contract stops implying an execution model that isn't built.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brief review is automated, not a human step:
- Removed review_brief (StepKey), awaiting_brief_review (RunStatus), and brief
  from ReviewGate (now [careplan] only).
- /runs/{id}/review/{gate} -> /runs/{id}/review/careplan (single gate).
- planningBrief demoted to informational (composed brief for display/read-back),
  no longer a review gate; its shapes are reused by CarePlanView.

SMART token exchange and $summary read happen client-side:
- Removed POST /runs/smart-launch. The browser completes the SMART exchange and
  the Patient/$summary read, then submits the IPS via POST /runs (single entry
  point for upload and SMART). BFF never touches the SMART token or reads FHIR —
  which also resolves the AGENTS.md "acp-writer does not query FHIR servers" tension.

Still open (design review): whether care-plan review is an in-run gate
(awaiting_careplan_review + POST /runs/{id}/review/careplan) or post-write
approval (PUT /careplans/{id}/status) — the two remain redundant for now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…man#128

Care-plan provenance decision: ship the flattened BriefGoal/BriefActivity/
CarePlanView shape for the first cut. Extending it with structured provenance
(targets, dose/route/frequency, source refs, DMN audit) is deferred to samschifman#128.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Meeting decided care-plan review is an in-run gate, not post-write approval:
- Removed the redundant PUT /careplans/{id}/status approval path.
- GET /careplans/{id} is now a read-only view of a persisted (already-approved)
  plan; approval happens in-run via POST /runs/{id}/review/careplan.
- RunStatus.awaiting_careplan_review documented as a decided in-run gate needing a
  ReviewCarePlan SonataFlow Callback state (backend work-item, samschifman#129), modeled on
  the ingester's ReviewManifest/ReviewArtifacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add reviewIteration + previousFeedback to RunDetail so the UI can show which
  care-plan review round it's on and the feedback given last round (mirrors the
  ingester's map_to_run_detail).
- currentStep -> currentSteps (array) on RunSummary/RunDetail: the pipeline is not
  strictly linear (steps can run in parallel), so multiple can be active at once.
- Remove the PlanningBrief schema and RunDetail.planningBrief (brief review is not
  a human step, per discussion). Renamed the shared item shapes BriefGoal/Activity/
  Conflict -> PlanGoal/PlanActivity/PlanConflict since only CarePlanView uses them.
- (smart-launch removal he flagged was already done when SMART went client-side.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pet/mock BFF that satisfies the PR samschifman#127 UI contract with canned data so the
React UI (incl. the care-plan review loop) can be developed and demoed without
the heavy pipeline. Mirrors the cpg-ingester BFF's optional-backend/mock pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nine TDD tasks: contract models, in-memory store w/ clock-derived progression,
canned data, mock router (8 endpoints), FastAPI app, local run, Containerfile,
chart-pods wiring, cluster milestone. Model aliases pinned to the contract by a
conformance test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, import-hygiene test, careplan ordering, frozen updatedAt
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants