Skip to content

Fix care-plan request_changes loop to actually revise the plan - #164

Merged
khaledsulayman merged 2 commits into
samschifman:mainfrom
khaledsulayman:fix-careplan-feedback-regeneration
Aug 26, 2026
Merged

Fix care-plan request_changes loop to actually revise the plan#164
khaledsulayman merged 2 commits into
samschifman:mainfrom
khaledsulayman:fix-careplan-feedback-regeneration

Conversation

@khaledsulayman

Copy link
Copy Markdown
Collaborator

Fixes #163. Follow-up to #147.

Problem

After #147, requesting changes on a care plan correctly loops instead of writing — but the regenerated plan comes back byte-for-byte identical. The clinician's feedback has no effect.

Root cause

  1. Wrong loop targetrequest_changes routed to GenerateBundle, but goals/activities live in the planning brief produced by ComposePlan. GenerateBundle only serializes an existing brief; it can't change clinical content.
  2. Feedback droppedGenerateBundle only receives the automated fhir_review_feedback, never the clinician's .careplanReview.comment.

Changes

  • Workflow (acp-writer-workflow.yaml): request_changesComposePlan (not GenerateBundle); ComposePlan now receives careplan_feedback from .careplanReview.comment.
  • Compose service (llm_reasoning.py): seed the composer's brief_review_feedback from careplan_feedback via _seed_feedback(), so plan_composer addresses it on the first pass (it already renders feedback under "Reviewer Feedback (address these issues)").
  • BFF mapper (sonataflow_client.py): infer_current_state distinguishes the ComposePlan re-run from the subsequent GenerateBundle during the loop, using completed_at timestamps.

InitCarePlanReview already preserves careplanReviewCount across loops, so iteration count + previousFeedback keep working.

Testing

  • Unit tests updated/added in test_bff_review_mapping.py for the loop-detection (request_changes → ComposePlan → GenerateBundle → gate). All pass.
  • On-cluster end-to-end verification (regenerated plan actually reflects requested changes) to follow as a PR comment.

Generated by Claude Code under the supervision of Khaled Sulayman

The care-plan review request_changes loop routed back to GenerateBundle and
never passed the clinician's feedback anywhere, so round-2 plans came back
byte-for-byte identical. Goals and activities are decided in the planning
brief (ComposePlan), and GenerateBundle only serializes an existing brief —
it cannot change clinical content.

- Workflow: request_changes now loops to ComposePlan, and ComposePlan receives
  careplan_feedback from .careplanReview.comment.
- Compose service: seed the composer's brief_review_feedback from
  careplan_feedback so plan_composer addresses it on the first pass.
- BFF mapper: infer_current_state now distinguishes the ComposePlan re-run
  from the subsequent GenerateBundle during a request_changes loop.

Builds on the routing/fail-closed fix (route on .decision).

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

Copy link
Copy Markdown
Collaborator Author

✅ Verified end-to-end on-cluster (Playwright)

Deployed fix-careplan-feedback-regeneration (current main + this fix, tag 2ab3f4b) to a personal namespace and drove the full flow in the browser with patient Thomas Walker (HTN bundle), KB = AHA-HTN-2023.

The fix works. After request_changes, the workflow looped back through ComposePlan (confirmed: run status returned to running | compose_plan) and regenerated a plan that reflects the clinician feedback. Before this change, round 2 was byte-for-byte identical.

Feedback submitted: "Add a numeric BP target <130/80; patient already on amlodipine 5mg — don't start Lisinopril, optimize existing therapy; add a follow-up recheck."

Round 1 Round 2 (after feedback)
Goal "Lower blood pressure to target range" "Lower systolic blood pressure to target range"
Med activity Start Lisinopril 10mg daily Evaluate the adequacy of Amlodipine 5mg daily
Diet DASH + exercise DASH + exercise + sodium <2300 mg/day
Activities 5 4
  • The UI review panel surfaced "Previously requested: …" with the submitted comment (previousFeedback populated).
  • Approve → run completed, CarePlan written (careplanId set). Final CarePlan goal rationale: "Target Systolic blood pressure: < 130.0 mmHg" — the numeric target carried through.

Incidental (not this PR):

Generated by Claude Code under the supervision of Khaled Sulayman

…e plan

The LLM routinely emits source_cpg: null for lifestyle/general goals and
activities. source_cpg is a required string on PlanGoal/PlanActivity, so a
single null made the whole PlanningBrief fail validation and plan_composer
fell back to an empty plan (0 goals / 0 activities). This surfaced as a blank
care plan on a request_changes regeneration.

Add _sanitize_provenance() (mirrors _sanitize_conflicts) to default null/
missing source_cpg to the run's applicable CPG before validation, so the item
survives instead of dropping the entire brief. Pre-existing brittleness — can
hit any compose, not just the review loop.

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.

Care-plan request_changes loop regenerates an identical plan

2 participants