Files
Jyotisha/docs/superpowers/plans/2026-07-18-birth-time-evidence-rectification.md
T

15 KiB
Raw Blame History

Birth-Time Evidence Rectification Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Continue the completed birth-time questionnaire into structured life-event scoring, candidate review, and guarded user confirmation.

Architecture: Extend the pure TypeScript journey state machine first, then add one isolated Python event adjudicator behind the existing Jyotish API. The authenticated Next.js service owns persistence and confirmation; React renders only the server-returned input state.

Tech Stack: Python 3.12, pytest, Next.js 16 App Router, React 19, TypeScript 5, Zod 3, Supabase/PostgreSQL, Node test runner.

Global Constraints

  • Agent prose never determines state, candidate score, confidence, route, or application permission.
  • reported_birth_time is immutable; only a confirmed server-side result may update active_birth_time and legacy birth_time.
  • Rectification events use the free journey route and never call /api/consult.
  • Life-event dates and domains are structured; free-form text never affects scoring.
  • Low and medium results cannot apply a time. High results require an explicit confirmation event.
  • The UI must say “候选时间” or “当前排盘使用时间”, never “真实出生时间”.
  • Preserve unrelated dirty-worktree changes and do not reset the repository.

Task 1: Deterministic Journey State and Event Contracts

Files:

  • Modify: frontend/src/lib/birth-time-journey.ts
  • Test: frontend/tests/birth-time-journey.test.ts

Interfaces:

  • Produces: lifeEventSchema, candidateResultSchema, extended JourneySnapshot, withCompletedQuestionnaire(), withCandidateResult(), and withConfirmedCandidate().

  • Consumes: existing JourneySnapshot and RectificationScoring.

  • Step 1: Write failing state-transition tests

test("the final questionnaire answer requests dated life events", () => {
  const next = withRectificationScoring(initial, {
    answeredCount: 8,
    candidateClusterRankings: [{ cluster: "middle_candidate_cluster", score: 5 }],
    nextRoundQuestions: [],
  });
  assert.equal(next.input, "life_events");
  assert.equal(next.assistantIntent, "collect_dated_life_events");
  assert.equal(next.canApply, false);
});

test("only a high candidate enters confirmation", () => {
  assert.equal(withCandidateResult(eventSnapshot, mediumResult).input, "candidate_actions");
  assert.equal(withCandidateResult(eventSnapshot, highResult).input, "candidate_confirmation");
});
  • Step 2: Run the focused test and verify RED

Run: cd frontend && node --test tests/birth-time-journey.test.ts

Expected: FAIL because the new input, intent, schemas, and transition functions do not exist.

  • Step 3: Implement the minimal typed state model
export const lifeEventSchema = z.object({
  id: z.string().uuid(),
  domain: z.enum(["education", "relocation", "relationship", "career", "health_pressure"]),
  date: z.string(),
  precision: z.enum(["year", "month", "day"]),
}).strict().readonly();

export function withCandidateResult(snapshot: JourneySnapshot, result: CandidateResult): JourneySnapshot {
  switch (result.confidence) {
    case "low": return { ...snapshot, state: "rectifying", input: "life_events", canApply: false };
    case "medium": return { ...snapshot, state: "candidate", input: "candidate_actions", canApply: false };
    case "high": return { ...snapshot, state: "confirming", input: "candidate_confirmation", canApply: true };
    default: return assertNever(result.confidence);
  }
}
  • Step 4: Run the focused test and verify GREEN

Run: cd frontend && node --test tests/birth-time-journey.test.ts

Expected: all birth-time journey domain tests pass.


Task 2: Local Candidate Event Adjudicator

Files:

  • Create: scripts/active_rectification_events.py
  • Test: tests/test_active_rectification_events.py

Interfaces:

  • Produces: score_life_events(request: RectificationEventRequest) -> CandidateResult.

  • Consumes: stored birth date/range/location, structured events, domain_calculation_service, varga, jaimini, Vimshottari timeline, and Narayana Dasha.

  • Step 1: Write failing Python tests for segments, thresholds, and abstention

def test_high_confidence_requires_four_events_three_domains_and_narrow_leader() -> None:
    result = score_life_events(high_fixture())
    assert result["confidence"] == "high"
    assert result["winning_segment"]["start_time"] <= result["winning_segment"]["end_time"]
    assert result["can_apply"] is True

def test_tied_candidates_abstain() -> None:
    result = score_life_events(tied_fixture())
    assert result["confidence"] == "low"
    assert "tied_leader" in result["reasons"]
    assert result["can_apply"] is False
  • Step 2: Run the focused test and verify RED

Run: .venv/bin/python -m pytest -q tests/test_active_rectification_events.py

Expected: collection fails because active_rectification_events does not exist.

  • Step 3: Implement frozen request models and fixed rule tables
class LifeEvent(TypedDict):
    id: str
    domain: EventDomain
    date: str
    precision: EventPrecision

PRECISION_WEIGHTS: Final = {"day": 1.0, "month": 0.8, "year": 0.5}
DOMAIN_LAYERS: Final = {
    "education": ("D24", (4, 5, 9)),
    "relocation": ("D4", (4, 12)),
    "relationship": ("D9", (7,)),
    "career": ("D10", (10,)),
    "health_pressure": ("D30", (6, 8, 12)),
}
  • Step 4: Implement minute scanning, contiguous signature segments, dual-Dasha rule IDs, and confidence thresholds

Every candidate row records actual D1/D4/D9/D10/D24/D30 data. Adjacent equal signatures collapse into segments. Each event score emits rule IDs for Vimshottari lord/domain-house, Narayana sign/domain-house, and domain-Varga support; unavailable mandatory layers produce a low-confidence abstention.

  • Step 5: Run the focused test and verify GREEN

Run: .venv/bin/python -m pytest -q tests/test_active_rectification_events.py

Expected: all event adjudicator tests pass without network access.


Task 3: Python API Boundary

Files:

  • Modify: scripts/jyotish_api_server.py
  • Modify: tests/test_active_rectification_api.py

Interfaces:

  • Produces: POST /api/active_rectification_events.

  • Consumes: score_life_events() from Task 2.

  • Step 1: Add a failing API contract test

def test_active_rectification_events_api_scores_structured_events() -> None:
    result = _handler()._compute_active_rectification_events(valid_payload())
    assert result["success"] is True
    assert result["endpoint"] == "active_rectification_events"
    assert result["candidate_result_id"]
  • Step 2: Run and verify RED

Run: .venv/bin/python -m pytest -q tests/test_active_rectification_api.py -k events

Expected: FAIL because the handler and route are missing.

  • Step 3: Add strict payload parsing and the new handler route

The HTTP method validates date/range/location and three-to-six events before calling score_life_events; invalid variants raise BadRequest. Register the path in POST dispatch, endpoint catalog, and capability metadata.

  • Step 4: Run and verify GREEN

Run: .venv/bin/python -m pytest -q tests/test_active_rectification_api.py

Expected: all active rectification API tests pass.


Task 4: Journey Service, Engine, Store, and SQL

Files:

  • Modify: frontend/src/lib/birth-time-journey-service.ts
  • Modify: frontend/src/lib/birth-time-journey-engine.ts
  • Modify: frontend/src/lib/birth-time-journey-adapters.ts
  • Modify: frontend/src/lib/birth-time-journey-store.ts
  • Create: frontend/supabase/migrations/20260718010000_birth_time_evidence_rectification.sql
  • Modify: frontend/tests/birth-time-journey-service.test.ts
  • Modify: frontend/tests/birth-time-journey-adapters.test.ts
  • Modify: tests/test_birth_time_journey_contract.py

Interfaces:

  • Produces: engine scoreEvents, store saveCandidateResult/confirmCandidate, service submitLifeEvents/saveCandidate/confirmCandidate.

  • Consumes: Task 1 schemas and Task 3 API.

  • Step 1: Write failing service tests

test("submitting life events persists the server result", async () => {
  const result = await service.submitLifeEvents("user-1", "case-1", events);
  assert.equal(result.candidateResult?.confidence, "medium");
  assert.equal(result.snapshot.input, "candidate_actions");
});

test("confirmation rejects a stale result id", async () => {
  await assert.rejects(
    service.confirmCandidate("user-1", "case-1", "stale", "14:24"),
    StaleCandidateConfirmationError,
  );
});
  • Step 2: Run focused frontend tests and verify RED

Run: cd frontend && node --test tests/birth-time-journey-service.test.ts tests/birth-time-journey-adapters.test.ts

Expected: FAIL because the new ports and methods are missing.

  • Step 3: Implement engine adapter and service transitions

scoreEvents posts only stored assessment/range/location plus parsed events. submitLifeEvents reloads the owner-scoped case, computes the result, transitions through withCandidateResult, and persists atomically. confirmCandidate rechecks state, result ID, confidence, time, and ownership before store confirmation.

  • Step 4: Write the failing SQL contract assertions and verify RED

Run: .venv/bin/python -m pytest -q tests/test_birth_time_journey_contract.py

Expected: FAIL because event/result columns and grants are absent.

  • Step 5: Add the migration and store persistence

The migration adds life_events, candidate_result, event_scoring_version, candidate_result_id, candidate_saved_at, and confirming status. Browser grants exclude score/result/confirmation writes. The admin store updates profile active time only inside confirmCandidate.

  • Step 6: Run service, adapter, and SQL tests and verify GREEN

Run: cd frontend && node --test tests/birth-time-journey-service.test.ts tests/birth-time-journey-adapters.test.ts && cd .. && .venv/bin/python -m pytest -q tests/test_birth_time_journey_contract.py

Expected: all selected tests pass.


Task 5: Route and Client Boundary

Files:

  • Modify: frontend/src/app/api/birth-time-journey/route.ts
  • Modify: frontend/src/lib/birth-time-journey-client.ts
  • Modify: frontend/tests/birth-time-journey-client.test.ts

Interfaces:

  • Produces: client calls submitBirthTimeLifeEvents, saveBirthTimeCandidate, confirmBirthTimeCandidate.

  • Consumes: Task 4 service methods.

  • Step 1: Write failing parser and request tests

test("client accepts only a guarded high-confirmation response", () => {
  const parsed = parseJourneyResponse(highConfirmationResponse);
  assert.equal(parsed.snapshot.input, "candidate_confirmation");
});

test("client rejects a rectification response that applies without confirmation state", () => {
  assert.throws(() => parseJourneyResponse(unsafeResponse));
});
  • Step 2: Run and verify RED

Run: cd frontend && node --test tests/birth-time-journey-client.test.ts

Expected: FAIL on missing response fields and client functions.

  • Step 3: Extend strict event schemas and exhaustive route dispatch

The route accepts only submit_life_events, save_candidate, and confirm_candidate shapes defined by Zod .strict(). It maps stale confirmation and insufficient evidence to 409 and keeps all server failures fail-closed.

  • Step 4: Run and verify GREEN

Run: cd frontend && node --test tests/birth-time-journey-client.test.ts

Expected: all client boundary tests pass.


Task 6: Life-Event and Candidate UI

Files:

  • Create: frontend/src/components/birth-time-life-events.tsx
  • Create: frontend/src/components/birth-time-candidate-result.tsx
  • Modify: frontend/src/components/birth-time-rectification.tsx
  • Modify: frontend/src/app/page.tsx
  • Modify: frontend/src/app/globals.css
  • Modify: frontend/DESIGN.md
  • Modify: frontend/tests/birth-time-rectification-contract.test.ts

Interfaces:

  • Produces: accessible event form and low/medium/high result actions.

  • Consumes: Task 5 client calls and parsed JourneyClientResponse.

  • Step 1: Add failing UI contract assertions

test("rectification renders the life-event step from the server input", () => {
  assert.match(component, /snapshot\.input === "life_events"/);
  assert.match(component, /BirthTimeLifeEvents/);
  assert.match(component, /BirthTimeCandidateResult/);
});
  • Step 2: Run and verify RED

Run: cd frontend && node --test tests/birth-time-rectification-contract.test.ts

Expected: FAIL because the new components and input branches are missing.

  • Step 3: Document and implement the component states

Extend the existing Birth time intake component in DESIGN.md with life-event rows, candidate action states, and confirmation copy. Implement three initial event rows, a six-row maximum, persistent labels, precision-dependent native inputs, live errors, and existing design tokens only.

  • Step 4: Wire page handlers and ready transition

page.tsx submits events, saves candidates, and confirms only through Task 5 client functions. A ready response updates profile state and proceeds to existing onboarding; no client code sets activeTime or canApply.

  • Step 5: Run and verify GREEN

Run: cd frontend && node --test tests/birth-time-rectification-contract.test.ts

Expected: the full UI contract passes.


Task 7: Final Verification and Browser QA

Files:

  • Review all files changed in Tasks 16.

Interfaces:

  • Produces: fresh automated and browser evidence for the approved design.

  • Step 1: Run focused Python and frontend tests

Run: .venv/bin/python -m pytest -q tests/test_active_rectification_events.py tests/test_active_rectification_api.py tests/test_birth_time_journey_contract.py

Run: cd frontend && npm test

Expected: zero failures.

  • Step 2: Run lint, TypeScript, and production build

Run: cd frontend && npm run lint && npx tsc --noEmit && npm run build

Expected: exit code 0 for every command.

  • Step 3: Run no-excuse checks on changed Python and TypeScript sources

Run the programming skill checkers against the changed source files and repair every new violation without refactoring unrelated legacy code.

  • Step 4: Run real-browser visual QA

Start the verified application, drive the final questionnaire answer, event validation, low/medium/high candidate cards, and confirmation fixture at 375px, 768px, and 1280px. Verify keyboard labels, live regions, overflow, motion, and that no consultation request is issued before ready.

  • Step 5: Review the final diff

Confirm every spec section has implementation evidence, reported_birth_time remains immutable, client payloads cannot inject score/confidence/application fields, and unrelated dirty files were not overwritten.