Close decision assistant H16 F17 G16 D18
This commit is contained in:
+18
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
## Current Goal
|
||||
|
||||
Automation execution-layer PXX remains closed and its known scope stays frozen. Under explicit user direction, the active product phase is a dense `Decision Assistant` operator workspace with grouped recall, broader replay baselines, note templates, quick-save shortcuts, and concrete OKX announcements/system-status read-only sources. This run closed H15/F16/G15/D17 by adding handoff lifecycle / operator resume queue fields, digest proof follow-up queue and resolved states, template guardrail resolution / preferred-template decay fields, and setup-family promotion queue / demotion history hints. Local validation passed, the current working tree was deployed, and remote smoke confirmed the latest Decision Assistant fields on the live host.
|
||||
Automation execution-layer PXX remains closed and its known scope stays frozen. Under explicit user direction, the active product phase is a dense `Decision Assistant` operator workspace with grouped recall, broader replay baselines, note templates, quick-save shortcuts, and concrete OKX announcements/system-status read-only sources. This run closed H16/F17/G16/D18 by adding handoff resume persistence / resolved-lane state, digest proof follow-up resolution history / stale queue aging, template preference recovery / guardrail audit trails, and setup-family promotion decision history / review-lane aging. Local validation passed, the current working tree was deployed, and remote smoke confirmed the latest Decision Assistant fields on the live host.
|
||||
|
||||
## Current Branch
|
||||
|
||||
@@ -88,6 +88,10 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_digest_followup_slice: proof_escalation_followup_resolved_states
|
||||
- latest_decision_assistant_template_guardrail_resolution_slice: guardrail_resolution_preferred_decay
|
||||
- latest_decision_assistant_replay_promotion_queue_slice: setup_family_promotion_queue_demotion_history
|
||||
- latest_decision_assistant_handoff_resume_persistence_slice: resume_queue_persistence_resolved_lanes
|
||||
- latest_decision_assistant_digest_resolution_history_slice: proof_followup_resolution_history_queue_aging
|
||||
- latest_decision_assistant_template_recovery_slice: preference_recovery_guardrail_audit
|
||||
- latest_decision_assistant_replay_review_lane_slice: promotion_decision_history_review_lane_aging
|
||||
- latest_decision_assistant_summary_status: implemented
|
||||
- latest_decision_assistant_change_summary_status: implemented
|
||||
- latest_decision_assistant_evidence_status: implemented
|
||||
@@ -160,10 +164,14 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_digest_followup_status: implemented
|
||||
- latest_decision_assistant_template_guardrail_resolution_status: implemented
|
||||
- latest_decision_assistant_replay_promotion_queue_status: implemented
|
||||
- latest_decision_assistant_handoff_resume_persistence_status: implemented
|
||||
- latest_decision_assistant_digest_resolution_history_status: implemented
|
||||
- latest_decision_assistant_template_recovery_status: implemented
|
||||
- latest_decision_assistant_replay_review_lane_status: implemented
|
||||
- latest_decision_assistant_workbench_status: visible_local_smoke
|
||||
- latest_remote_decision_assistant_deploy_status: deployed_current_head
|
||||
- latest_remote_decision_assistant_smoke_status: passed
|
||||
- latest_remote_decision_assistant_review_packet_status: handoff_lifecycle_proof_followup_guardrail_resolution_promotion_queue_visible
|
||||
- latest_remote_decision_assistant_review_packet_status: resume_persistence_proof_resolution_template_recovery_review_lane_visible
|
||||
- latest_p7_selector_choice: continue_periodic_analysis_decision_monitoring_with_maturity_rollup
|
||||
- latest_p14_slice: webhook_transport_retry
|
||||
- latest_p15_pxx_tasklist_status: ready
|
||||
@@ -242,6 +250,12 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
|
||||
## Completed In This Run
|
||||
|
||||
- Added handoff resume persistence / resolved-lane fields (`resume_persistence_key`, `resume_persistence_status`, `resolved_lane_state`, `resolved_lane_prompt`, `resume_queue_persistence_status`, and `resolved_lane_state_counts`) plus cockpit visibility.
|
||||
- Added digest proof follow-up resolution history / stale queue aging (`proof_follow_up_age_band`, `proof_follow_up_stale_queue`, `proof_resolution_history`, `stale_proof_follow_up_count`, and `proof_follow_up_age_counts`).
|
||||
- Added template preference recovery / guardrail audit fields (`preference_recovery_status`, `preference_recovery_prompt`, `guardrail_audit_trail`, `preference_recovery_queue`, and `guardrail_audit_trail_count`).
|
||||
- Added setup-family promotion decision history / review-lane aging (`promotion_decision_history`, `promotion_decision_history_count`, `review_lane_age_status`, `review_lane_age_score`, and `review_lane_aging_hint`).
|
||||
- Passed focused validation: `unittest tests.unit.test_operator_workspace_service`, `node --check src/web/app.js`, `unittest tests.unit.test_operator_workspace_service tests.unit.test_api_static_server tests.unit.test_api_server`, full `unittest discover -s tests/unit` (713 tests), and `scripts/run_script_static_checks.py`.
|
||||
- Deployed the current working tree to `http://192.168.3.90:8000`; deploy smoke passed 11 endpoints, `run_remote_ops_smoke.py` passed 9 checks, and remote field smoke confirmed resume persistence, proof resolution history, template recovery/audit, and review-lane aging fields.
|
||||
- Added handoff lifecycle / operator resume queue fields (`lifecycle_state`, `resume_queue_rank`, `resume_action_label`, `handoff_lifecycle_summary`, `operator_resume_queue`, and `next_resume_*`) plus cockpit resume visibility.
|
||||
- Added digest proof follow-up queue and resolved-state fields (`proof_follow_up_state`, `proof_follow_up_action_label`, `proof_resolved_state`, `proof_follow_up_queue`, `resolved_proof_count`, and `proof_resolution_state_counts`).
|
||||
- Added template guardrail resolution / preferred-template decay fields (`guardrail_resolution_status`, `guardrail_resolution_prompt`, `preferred_template_decay`, `guardrail_resolution_queue`, and `preferred_template_decay_status`).
|
||||
@@ -531,11 +545,12 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- P24 deploy passed through `scripts/deploy_with_password.py` with 11 HTTP smoke endpoints.
|
||||
- P24 remote ops smoke passed on `http://192.168.3.90:8000` with 9 passed checks, warnings=[], blockers=[].
|
||||
- PXX final validation passed: 709 unit tests, 73 script static checks, `node --check src/web/app.js`, P24 remote ops smoke passed, and closure gate passed.
|
||||
- Latest H16/F17/G16/D18 validation passed: 713 unit tests, 73 script static checks, `node --check src/web/app.js`, deploy smoke passed 11 endpoints, remote ops smoke passed 9 checks, and remote field smoke returned `status=passed`.
|
||||
- Real-live order submission remains intentionally disabled because the OKX order adapter is still not a real-live submitter.
|
||||
|
||||
## Exact Next Step
|
||||
|
||||
Continue the Decision Assistant dense operator workspace with the next bounded slice: H16 handoff resume queue persistence / resolved lane state, F17 digest proof follow-up resolution history / stale queue aging, G16 template preference recovery / guardrail audit trail, and D18 setup-family promotion decision history / review lane aging. PXX remains closed; do not create P25/P26 automatically, and do not enable real trading.
|
||||
Continue the Decision Assistant dense operator workspace with the next bounded slice: H17 handoff resume history / stale persistence cleanup, F18 digest proof resolution SLA / repeated-stale proof suppression, G17 template recovery recommendations / audit filtering, and D19 setup-family review-lane SLA / promotion backlog prioritization. PXX remains closed; do not create P25/P26 automatically, and do not enable real trading.
|
||||
|
||||
## Validation Commands Used Recently
|
||||
|
||||
|
||||
@@ -88,6 +88,10 @@
|
||||
- [x] Decision Assistant F16 digest proof escalation follow-up queue / resolved proof states slice complete
|
||||
- [x] Decision Assistant G15 template guardrail resolution / preferred-template decay slice complete
|
||||
- [x] Decision Assistant D17 setup-family promotion queue / demotion history slice complete
|
||||
- [x] Decision Assistant H16 handoff resume queue persistence / resolved lane state slice complete
|
||||
- [x] Decision Assistant F17 digest proof follow-up resolution history / stale queue aging slice complete
|
||||
- [x] Decision Assistant G16 template preference recovery / guardrail audit trail slice complete
|
||||
- [x] Decision Assistant D18 setup-family promotion decision history / review lane aging slice complete
|
||||
|
||||
## Next Up
|
||||
|
||||
@@ -107,10 +111,14 @@
|
||||
- [x] F16 deepen digest recall with proof escalation follow-up queue / resolved proof states
|
||||
- [x] G15 continue review template ergonomics with template guardrail resolution / preferred-template decay
|
||||
- [x] D17 extend replay comparison with setup-family promotion queue / demotion history
|
||||
- [ ] H16 continue compact operator workspace polish with handoff resume queue persistence / resolved lane state
|
||||
- [ ] F17 deepen digest recall with proof follow-up resolution history / stale queue aging
|
||||
- [ ] G16 continue review template ergonomics with template preference recovery / guardrail audit trail
|
||||
- [ ] D18 extend replay comparison with setup-family promotion decision history / review lane aging
|
||||
- [x] H16 continue compact operator workspace polish with handoff resume queue persistence / resolved lane state
|
||||
- [x] F17 deepen digest recall with proof follow-up resolution history / stale queue aging
|
||||
- [x] G16 continue review template ergonomics with template preference recovery / guardrail audit trail
|
||||
- [x] D18 extend replay comparison with setup-family promotion decision history / review lane aging
|
||||
- [ ] H17 continue compact operator workspace polish with handoff resume history / stale persistence cleanup
|
||||
- [ ] F18 deepen digest recall with proof resolution SLA / repeated-stale proof suppression
|
||||
- [ ] G17 continue review template ergonomics with template recovery recommendations / audit filtering
|
||||
- [ ] D19 extend replay comparison with setup-family review-lane SLA / promotion backlog prioritization
|
||||
- [x] P8 local closure complete
|
||||
- [x] P9 local closure complete
|
||||
- [x] P10 local closure complete
|
||||
@@ -206,6 +214,10 @@
|
||||
- [x] latest_decision_assistant_digest_followup_slice: proof_escalation_followup_resolved_states
|
||||
- [x] latest_decision_assistant_template_guardrail_resolution_slice: guardrail_resolution_preferred_decay
|
||||
- [x] latest_decision_assistant_replay_promotion_queue_slice: setup_family_promotion_queue_demotion_history
|
||||
- [x] latest_decision_assistant_handoff_resume_persistence_slice: resume_queue_persistence_resolved_lanes
|
||||
- [x] latest_decision_assistant_digest_resolution_history_slice: proof_followup_resolution_history_queue_aging
|
||||
- [x] latest_decision_assistant_template_recovery_slice: preference_recovery_guardrail_audit
|
||||
- [x] latest_decision_assistant_replay_review_lane_slice: promotion_decision_history_review_lane_aging
|
||||
- [x] latest_decision_assistant_summary_status: implemented
|
||||
- [x] latest_decision_assistant_change_summary_status: implemented
|
||||
- [x] latest_decision_assistant_evidence_status: implemented
|
||||
@@ -275,10 +287,14 @@
|
||||
- [x] latest_decision_assistant_digest_followup_status: implemented
|
||||
- [x] latest_decision_assistant_template_guardrail_resolution_status: implemented
|
||||
- [x] latest_decision_assistant_replay_promotion_queue_status: implemented
|
||||
- [x] latest_decision_assistant_handoff_resume_persistence_status: implemented
|
||||
- [x] latest_decision_assistant_digest_resolution_history_status: implemented
|
||||
- [x] latest_decision_assistant_template_recovery_status: implemented
|
||||
- [x] latest_decision_assistant_replay_review_lane_status: implemented
|
||||
- [x] latest_decision_assistant_workbench_status: visible_local_smoke
|
||||
- [x] latest_remote_decision_assistant_deploy_status: deployed_current_head
|
||||
- [x] latest_remote_decision_assistant_smoke_status: passed
|
||||
- [x] latest_remote_decision_assistant_review_packet_status: handoff_lifecycle_proof_followup_guardrail_resolution_promotion_queue_visible
|
||||
- [x] latest_remote_decision_assistant_review_packet_status: resume_persistence_proof_resolution_template_recovery_review_lane_visible
|
||||
- [x] latest_p7_selector_choice: continue_periodic_analysis_decision_monitoring_with_maturity_rollup
|
||||
- [x] latest_p14_slice: webhook_transport_retry
|
||||
- [x] latest_p15_pxx_tasklist_status: ready
|
||||
|
||||
@@ -1090,6 +1090,10 @@ def _build_decision_assistant_review_entrypoints(
|
||||
item["guardrail_resolution_prompt"] = outcome.get("guardrail_resolution_prompt")
|
||||
item["preferred_template_decay"] = outcome.get("preferred_template_decay")
|
||||
item["preferred_template_decay_reason"] = outcome.get("preferred_template_decay_reason")
|
||||
item["preference_recovery_status"] = outcome.get("preference_recovery_status")
|
||||
item["preference_recovery_prompt"] = outcome.get("preference_recovery_prompt")
|
||||
item["guardrail_audit_trail"] = outcome.get("guardrail_audit_trail") or []
|
||||
item["guardrail_audit_trail_count"] = outcome.get("guardrail_audit_trail_count") or 0
|
||||
return {
|
||||
"status": "active",
|
||||
"note_count": len(recent_notes),
|
||||
@@ -1401,6 +1405,16 @@ def _score_template_outcome(*, template_id: str, usage: dict, notes: list[dict])
|
||||
outcome_status=outcome_status,
|
||||
reuse_count=reuse_count,
|
||||
)
|
||||
preference_recovery_status = _template_preference_recovery_status(
|
||||
negative_count=negative_count,
|
||||
positive_count=positive_count,
|
||||
actionable_count=action_count,
|
||||
)
|
||||
guardrail_audit_trail = _template_guardrail_audit_trail(
|
||||
template_id=template_id,
|
||||
feedback_trend_history=feedback_trend_history,
|
||||
negative_count=negative_count,
|
||||
)
|
||||
return {
|
||||
"template_id": template_id,
|
||||
"reuse_count": reuse_count,
|
||||
@@ -1435,6 +1449,13 @@ def _score_template_outcome(*, template_id: str, usage: dict, notes: list[dict])
|
||||
positive_count=positive_count,
|
||||
actionable_count=action_count,
|
||||
),
|
||||
"preference_recovery_status": preference_recovery_status,
|
||||
"preference_recovery_prompt": _template_preference_recovery_prompt(
|
||||
template_id=template_id,
|
||||
preference_recovery_status=preference_recovery_status,
|
||||
),
|
||||
"guardrail_audit_trail": guardrail_audit_trail[:5],
|
||||
"guardrail_audit_trail_count": len(guardrail_audit_trail),
|
||||
}
|
||||
|
||||
|
||||
@@ -1485,6 +1506,57 @@ def _template_preferred_decay_reason(*, negative_count: int, positive_count: int
|
||||
return "no_feedback_decay_signal"
|
||||
|
||||
|
||||
def _template_preference_recovery_status(*, negative_count: int, positive_count: int, actionable_count: int) -> str:
|
||||
if negative_count:
|
||||
if positive_count + actionable_count >= negative_count:
|
||||
return "recovery_in_progress"
|
||||
return "recovery_required"
|
||||
if positive_count or actionable_count:
|
||||
return "recovered_or_stable"
|
||||
return "awaiting_feedback"
|
||||
|
||||
|
||||
def _template_preference_recovery_prompt(*, template_id: str, preference_recovery_status: str) -> str:
|
||||
template_label = template_id.replace("_", " ") or "template"
|
||||
if preference_recovery_status == "recovery_required":
|
||||
return f"Collect a recovery note before {template_label} can be preferred again."
|
||||
if preference_recovery_status == "recovery_in_progress":
|
||||
return f"Review the latest useful feedback before restoring {template_label} preference."
|
||||
if preference_recovery_status == "recovered_or_stable":
|
||||
return f"{template_label} preference is stable; keep capturing outcome evidence after reuse."
|
||||
return f"{template_label} still needs outcome feedback before preference can be scored."
|
||||
|
||||
|
||||
def _template_guardrail_audit_trail(*, template_id: str, feedback_trend_history: list[dict], negative_count: int) -> list[dict]:
|
||||
trail = []
|
||||
for index, item in enumerate(feedback_trend_history[:5], start=1):
|
||||
signal = str(item.get("signal") or "reuse_only")
|
||||
trail.append(
|
||||
{
|
||||
"template_id": template_id,
|
||||
"event": "negative_guardrail" if signal == "negative" else "feedback_signal",
|
||||
"signal": signal,
|
||||
"label": item.get("label"),
|
||||
"created_ts": item.get("created_ts"),
|
||||
"audit_index": index,
|
||||
"resolution_status": "resolution_required" if signal == "negative" else "evidence_captured",
|
||||
}
|
||||
)
|
||||
if negative_count and not any(item.get("event") == "negative_guardrail" for item in trail):
|
||||
trail.append(
|
||||
{
|
||||
"template_id": template_id,
|
||||
"event": "negative_guardrail",
|
||||
"signal": "negative",
|
||||
"label": "Negative feedback",
|
||||
"created_ts": None,
|
||||
"audit_index": len(trail) + 1,
|
||||
"resolution_status": "resolution_required",
|
||||
}
|
||||
)
|
||||
return trail
|
||||
|
||||
|
||||
def _build_template_feedback_loop(*, template_id: str, outcome_status: str, reuse_count: int) -> dict:
|
||||
template_label = template_id.replace("_", " ") or "template"
|
||||
if outcome_status == "review_before_reuse":
|
||||
@@ -1566,6 +1638,22 @@ def _build_template_feedback_aggregation(
|
||||
for item in template_outcomes.values()
|
||||
if item.get("guardrail_resolution_status") == "resolution_required"
|
||||
]
|
||||
preference_recovery_queue = [
|
||||
{
|
||||
"template_id": str(item.get("template_id") or ""),
|
||||
"status": item.get("preference_recovery_status"),
|
||||
"prompt": item.get("preference_recovery_prompt"),
|
||||
"decay": item.get("preferred_template_decay"),
|
||||
"guardrail_status": item.get("guardrail_resolution_status"),
|
||||
}
|
||||
for item in template_outcomes.values()
|
||||
if item.get("preference_recovery_status") in {"recovery_required", "recovery_in_progress", "awaiting_feedback"}
|
||||
]
|
||||
guardrail_audit_trail = [
|
||||
row
|
||||
for item in template_outcomes.values()
|
||||
for row in list(item.get("guardrail_audit_trail") or [])
|
||||
]
|
||||
rated_template_count = sum(
|
||||
1
|
||||
for item in template_outcomes.values()
|
||||
@@ -1622,6 +1710,13 @@ def _build_template_feedback_aggregation(
|
||||
"preferred_template_decay_max": max(
|
||||
[float(item.get("decay") or 0.0) for item in guardrail_resolution_queue] or [0.0]
|
||||
),
|
||||
"preference_recovery_queue": preference_recovery_queue[:5],
|
||||
"preference_recovery_count": len(preference_recovery_queue),
|
||||
"next_preference_recovery_template_id": (
|
||||
preference_recovery_queue[0].get("template_id") if preference_recovery_queue else None
|
||||
),
|
||||
"guardrail_audit_trail": guardrail_audit_trail[:5],
|
||||
"guardrail_audit_trail_count": len(guardrail_audit_trail),
|
||||
}
|
||||
|
||||
|
||||
@@ -1865,10 +1960,42 @@ def _build_decision_assistant_digest_history_summary(history: list[dict]) -> dic
|
||||
"prompt": group.get("proof_escalation_prompt"),
|
||||
"evidence_anchor": group.get("refresh_evidence_anchor"),
|
||||
"resolved_state": group.get("proof_resolved_state"),
|
||||
"age_seconds": group.get("proof_follow_up_age_seconds"),
|
||||
"age_band": group.get("proof_follow_up_age_band"),
|
||||
"stale_queue": group.get("proof_follow_up_stale_queue"),
|
||||
}
|
||||
for group in groups
|
||||
if group.get("proof_follow_up_state") in {"follow_up_required", "watch"}
|
||||
]
|
||||
stale_proof_follow_ups = [
|
||||
group
|
||||
for group in groups
|
||||
if bool(group.get("proof_follow_up_stale_queue"))
|
||||
]
|
||||
proof_follow_up_age_counts = {}
|
||||
for group in groups:
|
||||
age_band = str(group.get("proof_follow_up_age_band") or "unknown")
|
||||
proof_follow_up_age_counts[age_band] = int(proof_follow_up_age_counts.get(age_band) or 0) + 1
|
||||
proof_resolution_history = [
|
||||
row
|
||||
for group in groups
|
||||
for row in list(group.get("proof_resolution_history") or [])
|
||||
]
|
||||
proof_follow_up_with_age = [
|
||||
group
|
||||
for group in groups
|
||||
if group.get("proof_follow_up_state") in {"follow_up_required", "watch"}
|
||||
and group.get("proof_follow_up_age_seconds") is not None
|
||||
]
|
||||
oldest_proof_follow_up = dict(
|
||||
sorted(
|
||||
proof_follow_up_with_age,
|
||||
key=lambda row: int(row.get("proof_follow_up_age_seconds") or 0),
|
||||
reverse=True,
|
||||
)[0]
|
||||
if proof_follow_up_with_age
|
||||
else {}
|
||||
)
|
||||
proof_resolution_state_counts = {}
|
||||
for group in groups:
|
||||
state = str(group.get("proof_resolved_state") or "unknown")
|
||||
@@ -1912,6 +2039,12 @@ def _build_decision_assistant_digest_history_summary(history: list[dict]) -> dic
|
||||
"resolved_proof_count": proof_resolution_state_counts.get("resolved_fresh", 0),
|
||||
"proof_resolution_state_counts": proof_resolution_state_counts,
|
||||
"next_proof_follow_up_prompt": proof_follow_up_queue[0].get("prompt") if proof_follow_up_queue else None,
|
||||
"stale_proof_follow_up_count": len(stale_proof_follow_ups),
|
||||
"proof_follow_up_age_counts": proof_follow_up_age_counts,
|
||||
"proof_resolution_history": proof_resolution_history[:5],
|
||||
"proof_resolution_history_count": len(proof_resolution_history),
|
||||
"oldest_proof_follow_up_kind": oldest_proof_follow_up.get("kind"),
|
||||
"oldest_proof_follow_up_age_seconds": oldest_proof_follow_up.get("proof_follow_up_age_seconds"),
|
||||
}
|
||||
|
||||
|
||||
@@ -1949,6 +2082,21 @@ def _build_decision_assistant_digest_history_groups(history: list[dict], *, reco
|
||||
freshness_proof_status=freshness_proof_status,
|
||||
refresh_evidence_anchor=refresh_evidence_anchor,
|
||||
)
|
||||
proof_follow_up_age_band = _digest_history_proof_follow_up_age_band(
|
||||
age=age,
|
||||
proof_follow_up_state=proof_follow_up_state,
|
||||
)
|
||||
proof_follow_up_stale_queue = _digest_history_proof_follow_up_stale_queue(
|
||||
proof_follow_up_state=proof_follow_up_state,
|
||||
proof_age_trend=proof_age_trend,
|
||||
proof_follow_up_age_band=proof_follow_up_age_band,
|
||||
)
|
||||
proof_resolution_history = _digest_history_proof_resolution_history(
|
||||
kind=kind,
|
||||
proof_follow_up_state=proof_follow_up_state,
|
||||
proof_resolved_state=proof_resolved_state,
|
||||
refresh_confirmation_trail=refresh_confirmation_trail,
|
||||
)
|
||||
groups.append(
|
||||
{
|
||||
"kind": kind,
|
||||
@@ -1995,6 +2143,10 @@ def _build_decision_assistant_digest_history_groups(history: list[dict], *, reco
|
||||
proof_follow_up_state=proof_follow_up_state,
|
||||
),
|
||||
"proof_resolved_state": proof_resolved_state,
|
||||
"proof_follow_up_age_seconds": age.get("age_seconds"),
|
||||
"proof_follow_up_age_band": proof_follow_up_age_band,
|
||||
"proof_follow_up_stale_queue": proof_follow_up_stale_queue,
|
||||
"proof_resolution_history": proof_resolution_history,
|
||||
"refresh_confirmation_trail": refresh_confirmation_trail,
|
||||
"latest_confirmation_trail_item": refresh_confirmation_trail[0] if refresh_confirmation_trail else None,
|
||||
"items": items[:3],
|
||||
@@ -2226,6 +2378,60 @@ def _digest_history_proof_resolved_state(*, proof_escalation_level: str, freshne
|
||||
return "unresolved"
|
||||
|
||||
|
||||
def _digest_history_proof_follow_up_age_band(*, age: dict, proof_follow_up_state: str) -> str:
|
||||
age_band = str(age.get("age_band") or "unknown_age")
|
||||
if proof_follow_up_state == "resolved":
|
||||
return "resolved"
|
||||
if age_band in {"untimed", "unknown_age"}:
|
||||
return "missing_age_proof"
|
||||
if age_band == "stale":
|
||||
return "stale_follow_up"
|
||||
if age_band == "same_session":
|
||||
return "aging_follow_up"
|
||||
if age_band in {"fresh_now", "monitor_window"}:
|
||||
return "current_follow_up"
|
||||
return "unknown_follow_up_age"
|
||||
|
||||
|
||||
def _digest_history_proof_follow_up_stale_queue(
|
||||
*,
|
||||
proof_follow_up_state: str,
|
||||
proof_age_trend: str,
|
||||
proof_follow_up_age_band: str,
|
||||
) -> bool:
|
||||
return (
|
||||
proof_follow_up_state == "follow_up_required"
|
||||
and (
|
||||
proof_age_trend in {"stale", "missing"}
|
||||
or proof_follow_up_age_band in {"stale_follow_up", "missing_age_proof"}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _digest_history_proof_resolution_history(
|
||||
*,
|
||||
kind: str,
|
||||
proof_follow_up_state: str,
|
||||
proof_resolved_state: str,
|
||||
refresh_confirmation_trail: list[dict],
|
||||
) -> list[dict]:
|
||||
history = []
|
||||
for index, item in enumerate(refresh_confirmation_trail[:3], start=1):
|
||||
history.append(
|
||||
{
|
||||
"kind": kind,
|
||||
"event": "proof_resolution",
|
||||
"state": proof_resolved_state,
|
||||
"follow_up_state": proof_follow_up_state,
|
||||
"proof_label": item.get("proof_label"),
|
||||
"ts": item.get("ts"),
|
||||
"evidence_anchor": item.get("evidence_anchor"),
|
||||
"history_index": index,
|
||||
}
|
||||
)
|
||||
return history
|
||||
|
||||
|
||||
def _digest_history_refresh_evidence_anchor(*, kind: str, items: list[dict], latest_ts) -> dict:
|
||||
latest = dict(items[0] or {}) if items else {}
|
||||
title = str(latest.get("title") or kind or "history")
|
||||
@@ -2540,6 +2746,16 @@ def _build_decision_assistant_scan_handoff_summary(cards: list[dict]) -> dict:
|
||||
"lifecycle_state": _scan_handoff_lifecycle_state(str(item.get("handoff_status") or "")),
|
||||
"resume_queue_rank": _scan_handoff_resume_rank(str(item.get("handoff_status") or "")),
|
||||
"resume_action_label": _scan_handoff_resume_action_label(str(item.get("handoff_status") or "")),
|
||||
"resume_persistence_key": _scan_handoff_resume_persistence_key(
|
||||
persistence_key=str(item.get("handoff_persistence_key") or ""),
|
||||
handoff_marker=str(item.get("handoff_marker") or ""),
|
||||
),
|
||||
"resume_persistence_status": _scan_handoff_resume_persistence_status(str(item.get("handoff_status") or "")),
|
||||
"resolved_lane_state": _scan_handoff_resolved_lane_state(str(item.get("handoff_status") or "")),
|
||||
"resolved_lane_prompt": _scan_handoff_resolved_lane_prompt(
|
||||
title=str(item.get("title") or item.get("target_section") or "handoff"),
|
||||
handoff_status=str(item.get("handoff_status") or ""),
|
||||
),
|
||||
}
|
||||
for item in cards
|
||||
if item.get("handoff_marker")
|
||||
@@ -2552,6 +2768,15 @@ def _build_decision_assistant_scan_handoff_summary(cards: list[dict]) -> dict:
|
||||
]
|
||||
next_cleanup_marker = dict(cleanup_markers[0] or {}) if cleanup_markers else {}
|
||||
operator_resume_queue = _build_scan_handoff_resume_queue(markers)
|
||||
resolved_lane_state_counts = _build_scan_handoff_resolved_lane_state_counts(markers)
|
||||
next_unresolved_marker = next(
|
||||
(
|
||||
item
|
||||
for item in operator_resume_queue
|
||||
if item.get("resolved_lane_state") not in {"resolved_ready", "resolved_carry_forward"}
|
||||
),
|
||||
{},
|
||||
)
|
||||
stale_after_values = [
|
||||
int(item.get("stale_after_seconds") or 0)
|
||||
for item in markers
|
||||
@@ -2581,6 +2806,17 @@ def _build_decision_assistant_scan_handoff_summary(cards: list[dict]) -> dict:
|
||||
"next_resume_marker": operator_resume_queue[0].get("handoff_marker") if operator_resume_queue else None,
|
||||
"next_resume_action_label": operator_resume_queue[0].get("resume_action_label") if operator_resume_queue else None,
|
||||
"next_resume_prompt": operator_resume_queue[0].get("handoff_prompt") if operator_resume_queue else None,
|
||||
"resume_queue_persistence_status": _scan_handoff_resume_queue_persistence_status(operator_resume_queue),
|
||||
"resume_queue_persistence_key": operator_resume_queue[0].get("resume_persistence_key") if operator_resume_queue else None,
|
||||
"resolved_lane_state_counts": resolved_lane_state_counts,
|
||||
"resolved_lane_count": int(resolved_lane_state_counts.get("resolved_ready") or 0)
|
||||
+ int(resolved_lane_state_counts.get("resolved_carry_forward") or 0),
|
||||
"unresolved_lane_count": sum(
|
||||
int(count or 0)
|
||||
for state, count in resolved_lane_state_counts.items()
|
||||
if state not in {"resolved_ready", "resolved_carry_forward"}
|
||||
),
|
||||
"next_resolved_lane_prompt": next_unresolved_marker.get("resolved_lane_prompt"),
|
||||
"handoff_stale_policy": "Persisted handoffs older than their stale window should be reopened and refreshed before reuse.",
|
||||
"handoff_cleanup_recommendation": _scan_handoff_cleanup_recommendation(markers),
|
||||
"snooze_options": [
|
||||
@@ -2649,6 +2885,61 @@ def _scan_handoff_resume_action_label(handoff_status: str) -> str:
|
||||
return "Resume Ready"
|
||||
|
||||
|
||||
def _scan_handoff_resume_persistence_key(*, persistence_key: str, handoff_marker: str) -> str:
|
||||
base = str(persistence_key or handoff_marker or "handoff").strip() or "handoff"
|
||||
return f"{base}:resume_state"
|
||||
|
||||
|
||||
def _scan_handoff_resume_persistence_status(handoff_status: str) -> str:
|
||||
if handoff_status == "next_handoff":
|
||||
return "persist_next_resume"
|
||||
if handoff_status in {"blocked_handoff", "refresh_handoff", "ready_handoff"}:
|
||||
return "persist_for_resolution"
|
||||
return "no_persistence_needed"
|
||||
|
||||
|
||||
def _scan_handoff_resume_queue_persistence_status(operator_resume_queue: list[dict]) -> str:
|
||||
if not operator_resume_queue:
|
||||
return "idle"
|
||||
if any(item.get("resume_persistence_status") == "persist_next_resume" for item in operator_resume_queue):
|
||||
return "persist_next_resume"
|
||||
if any(item.get("resume_persistence_status") == "persist_for_resolution" for item in operator_resume_queue):
|
||||
return "persist_for_resolution"
|
||||
return "no_persistence_needed"
|
||||
|
||||
|
||||
def _scan_handoff_resolved_lane_state(handoff_status: str) -> str:
|
||||
if handoff_status == "context_ready":
|
||||
return "resolved_carry_forward"
|
||||
if handoff_status == "ready_handoff":
|
||||
return "resolved_ready"
|
||||
if handoff_status == "blocked_handoff":
|
||||
return "pending_blocker_resolution"
|
||||
if handoff_status == "refresh_handoff":
|
||||
return "pending_refresh_resolution"
|
||||
return "pending_resume"
|
||||
|
||||
|
||||
def _scan_handoff_resolved_lane_prompt(*, title: str, handoff_status: str) -> str:
|
||||
if handoff_status == "context_ready":
|
||||
return f"{title} is carried forward; keep the saved context available for the next cockpit pass."
|
||||
if handoff_status == "ready_handoff":
|
||||
return f"{title} is resolved enough to resume when the operator returns to this lane."
|
||||
if handoff_status == "blocked_handoff":
|
||||
return f"Resolve the blocker for {title} before the lane is treated as complete."
|
||||
if handoff_status == "refresh_handoff":
|
||||
return f"Refresh {title} before reusing the persisted handoff."
|
||||
return f"Resume {title} from the persisted handoff before opening a new lane."
|
||||
|
||||
|
||||
def _build_scan_handoff_resolved_lane_state_counts(markers: list[dict]) -> dict:
|
||||
counts: dict[str, int] = {}
|
||||
for item in markers:
|
||||
state = str(item.get("resolved_lane_state") or "pending_resume")
|
||||
counts[state] = int(counts.get(state) or 0) + 1
|
||||
return counts
|
||||
|
||||
|
||||
def _build_scan_handoff_resume_queue(markers: list[dict]) -> list[dict]:
|
||||
return sorted(
|
||||
[
|
||||
@@ -2660,6 +2951,11 @@ def _build_scan_handoff_resume_queue(markers: list[dict]) -> list[dict]:
|
||||
"resume_action_label": item.get("resume_action_label"),
|
||||
"handoff_prompt": item.get("handoff_prompt"),
|
||||
"resume_queue_rank": item.get("resume_queue_rank"),
|
||||
"persistence_key": item.get("persistence_key"),
|
||||
"resume_persistence_key": item.get("resume_persistence_key"),
|
||||
"resume_persistence_status": item.get("resume_persistence_status"),
|
||||
"resolved_lane_state": item.get("resolved_lane_state"),
|
||||
"resolved_lane_prompt": item.get("resolved_lane_prompt"),
|
||||
}
|
||||
for item in markers
|
||||
if item.get("lifecycle_state") != "carry_forward"
|
||||
@@ -3005,6 +3301,19 @@ def _build_setup_replay_broader_baseline_summary(
|
||||
"promotion_queue_rank": 5,
|
||||
"promotion_next_action": "Collect more replay samples before promotion review.",
|
||||
"promotion_queue_item": None,
|
||||
"promotion_decision_history": [
|
||||
{
|
||||
"event": "promotion_queue_decision",
|
||||
"setup_family": setup_family,
|
||||
"status": "collect_samples",
|
||||
"promotion_ready": False,
|
||||
"reason": "insufficient_samples",
|
||||
}
|
||||
],
|
||||
"promotion_decision_history_count": 1,
|
||||
"review_lane_age_status": "aging_collect_samples",
|
||||
"review_lane_age_score": 30,
|
||||
"review_lane_aging_hint": f"{setup_family} review lane is aging until enough replay samples exist.",
|
||||
"demotion_history": [],
|
||||
"demotion_history_count": 0,
|
||||
"focus_recommendation": f"Collect samples before changing focus for {setup_family}.",
|
||||
@@ -3213,6 +3522,19 @@ def _build_setup_family_trend_delta(
|
||||
"promotion_queue_rank": 5,
|
||||
"promotion_next_action": "Collect more replay samples before promotion review.",
|
||||
"promotion_queue_item": None,
|
||||
"promotion_decision_history": [
|
||||
{
|
||||
"event": "promotion_queue_decision",
|
||||
"setup_family": setup_family,
|
||||
"status": "collect_samples",
|
||||
"promotion_ready": False,
|
||||
"reason": "insufficient_samples",
|
||||
}
|
||||
],
|
||||
"promotion_decision_history_count": 1,
|
||||
"review_lane_age_status": "aging_collect_samples",
|
||||
"review_lane_age_score": 30,
|
||||
"review_lane_aging_hint": f"{setup_family} review lane is aging until enough replay samples exist.",
|
||||
"demotion_history": [],
|
||||
"demotion_history_count": 0,
|
||||
"focus_recommendation": f"Collect samples before changing focus for {setup_family}.",
|
||||
@@ -3299,6 +3621,35 @@ def _build_setup_family_trend_delta(
|
||||
focus_reasons=focus_reasons,
|
||||
trend_status=trend_status,
|
||||
)
|
||||
promotion_queue_rank = _setup_family_promotion_queue_rank(promotion_queue_status=promotion_queue_status)
|
||||
promotion_next_action = _setup_family_promotion_next_action(
|
||||
setup_family=setup_family,
|
||||
promotion_queue_status=promotion_queue_status,
|
||||
)
|
||||
promotion_queue_item = _setup_family_promotion_queue_item(
|
||||
setup_family=setup_family,
|
||||
promotion_queue_status=promotion_queue_status,
|
||||
promotion_ready=promotion_ready,
|
||||
watchlist_priority_score=watchlist_priority_score,
|
||||
)
|
||||
promotion_decision_history = _setup_family_promotion_decision_history(
|
||||
setup_family=setup_family,
|
||||
promotion_queue_status=promotion_queue_status,
|
||||
promotion_blockers=promotion_blockers,
|
||||
demotion_history=demotion_history,
|
||||
promotion_ready=promotion_ready,
|
||||
)
|
||||
review_lane_age_status = _setup_family_review_lane_age_status(
|
||||
promotion_queue_status=promotion_queue_status,
|
||||
sample_gap=sample_gap,
|
||||
drift_alert=drift_alert,
|
||||
trend_status=trend_status,
|
||||
)
|
||||
review_lane_age_score = _setup_family_review_lane_age_score(
|
||||
watchlist_priority_score=watchlist_priority_score,
|
||||
sample_gap=sample_gap,
|
||||
drift_alert=drift_alert,
|
||||
)
|
||||
return {
|
||||
"status": "active",
|
||||
"trend_status": trend_status,
|
||||
@@ -3326,16 +3677,16 @@ def _build_setup_family_trend_delta(
|
||||
),
|
||||
"promotion_blockers": promotion_blockers,
|
||||
"promotion_queue_status": promotion_queue_status,
|
||||
"promotion_queue_rank": _setup_family_promotion_queue_rank(promotion_queue_status=promotion_queue_status),
|
||||
"promotion_next_action": _setup_family_promotion_next_action(
|
||||
"promotion_queue_rank": promotion_queue_rank,
|
||||
"promotion_next_action": promotion_next_action,
|
||||
"promotion_queue_item": promotion_queue_item,
|
||||
"promotion_decision_history": promotion_decision_history[:6],
|
||||
"promotion_decision_history_count": len(promotion_decision_history),
|
||||
"review_lane_age_status": review_lane_age_status,
|
||||
"review_lane_age_score": review_lane_age_score,
|
||||
"review_lane_aging_hint": _setup_family_review_lane_aging_hint(
|
||||
setup_family=setup_family,
|
||||
promotion_queue_status=promotion_queue_status,
|
||||
),
|
||||
"promotion_queue_item": _setup_family_promotion_queue_item(
|
||||
setup_family=setup_family,
|
||||
promotion_queue_status=promotion_queue_status,
|
||||
promotion_ready=promotion_ready,
|
||||
watchlist_priority_score=watchlist_priority_score,
|
||||
review_lane_age_status=review_lane_age_status,
|
||||
),
|
||||
"demotion_history": demotion_history[:5],
|
||||
"demotion_history_count": len(demotion_history),
|
||||
@@ -3477,6 +3828,81 @@ def _setup_family_demotion_history(
|
||||
return history
|
||||
|
||||
|
||||
def _setup_family_promotion_decision_history(
|
||||
*,
|
||||
setup_family: str,
|
||||
promotion_queue_status: str,
|
||||
promotion_blockers: list[str],
|
||||
demotion_history: list[dict],
|
||||
promotion_ready: bool,
|
||||
) -> list[dict]:
|
||||
history = [
|
||||
{
|
||||
"event": "promotion_queue_decision",
|
||||
"setup_family": setup_family,
|
||||
"status": promotion_queue_status,
|
||||
"promotion_ready": promotion_ready,
|
||||
"reason": "operator_review_ready" if promotion_ready else (promotion_blockers[0] if promotion_blockers else "watch_lane"),
|
||||
}
|
||||
]
|
||||
for blocker in promotion_blockers[:3]:
|
||||
history.append(
|
||||
{
|
||||
"event": "promotion_blocker_reviewed",
|
||||
"setup_family": setup_family,
|
||||
"status": promotion_queue_status,
|
||||
"reason": blocker,
|
||||
}
|
||||
)
|
||||
for item in demotion_history[:3]:
|
||||
history.append(
|
||||
{
|
||||
"event": item.get("event") or "demotion_history",
|
||||
"setup_family": setup_family,
|
||||
"status": promotion_queue_status,
|
||||
"reason": item.get("reason") or item.get("severity") or "review_lane_history",
|
||||
}
|
||||
)
|
||||
return history
|
||||
|
||||
|
||||
def _setup_family_review_lane_age_status(
|
||||
*,
|
||||
promotion_queue_status: str,
|
||||
sample_gap: int,
|
||||
drift_alert: bool,
|
||||
trend_status: str,
|
||||
) -> str:
|
||||
if drift_alert or promotion_queue_status == "demotion_review":
|
||||
return "stale_demotion_review"
|
||||
if sample_gap > 0:
|
||||
return "aging_collect_samples"
|
||||
if promotion_queue_status == "ready_for_operator_review":
|
||||
return "ready_now"
|
||||
if trend_status == "mixed_vs_family_peers":
|
||||
return "aging_watch"
|
||||
return "watching"
|
||||
|
||||
|
||||
def _setup_family_review_lane_age_score(*, watchlist_priority_score: int, sample_gap: int, drift_alert: bool) -> int:
|
||||
score = int(watchlist_priority_score or 0) + min(20, int(sample_gap or 0) * 4)
|
||||
if drift_alert:
|
||||
score += 10
|
||||
return max(0, min(100, score))
|
||||
|
||||
|
||||
def _setup_family_review_lane_aging_hint(*, setup_family: str, review_lane_age_status: str) -> str:
|
||||
if review_lane_age_status == "ready_now":
|
||||
return f"{setup_family} is ready for analysis-lane promotion review; execution permissions stay unchanged."
|
||||
if review_lane_age_status == "stale_demotion_review":
|
||||
return f"{setup_family} needs demotion review before it remains in the primary analysis lane."
|
||||
if review_lane_age_status == "aging_collect_samples":
|
||||
return f"{setup_family} review lane is aging until enough replay samples exist."
|
||||
if review_lane_age_status == "aging_watch":
|
||||
return f"{setup_family} remains in an aging watch lane until mixed deltas resolve."
|
||||
return f"{setup_family} remains in watch mode while review-lane evidence accumulates."
|
||||
|
||||
|
||||
def _rank_session_composite(rows: list[dict], *, session_code: str) -> int | None:
|
||||
if not session_code:
|
||||
return None
|
||||
|
||||
@@ -576,6 +576,12 @@ function renderDecisionAssistant(summary) {
|
||||
['handoff lifecycle', formatJsonInline(scanHandoffSummary.handoff_lifecycle_summary?.state_counts || {})],
|
||||
['resume queue', String(scanHandoffSummary.resume_queue_count ?? 0)],
|
||||
['resume queue items', formatList((scanHandoffSummary.operator_resume_queue || []).map((item) => `${item.resume_action_label || 'Resume'}: ${item.title || item.target_section}`))],
|
||||
['resume persistence', scanHandoffSummary.resume_queue_persistence_status || 'n/a'],
|
||||
['resume persistence key', scanHandoffSummary.resume_queue_persistence_key || 'n/a'],
|
||||
['resolved lane states', formatJsonInline(scanHandoffSummary.resolved_lane_state_counts || {})],
|
||||
['resolved lanes', String(scanHandoffSummary.resolved_lane_count ?? 0)],
|
||||
['unresolved lanes', String(scanHandoffSummary.unresolved_lane_count ?? 0)],
|
||||
['next resolved lane prompt', scanHandoffSummary.next_resolved_lane_prompt || 'n/a'],
|
||||
['next resume action', scanHandoffSummary.next_resume_action_label || 'n/a'],
|
||||
['next resume prompt', scanHandoffSummary.next_resume_prompt || 'n/a'],
|
||||
['handoff cleanup recommendation', scanHandoffSummary.handoff_cleanup_recommendation || 'n/a'],
|
||||
@@ -705,8 +711,13 @@ function renderDecisionAssistant(summary) {
|
||||
['next stale proof kind', digestSurface.history_summary?.next_stale_proof_kind || 'n/a'],
|
||||
['next stale proof prompt', digestSurface.history_summary?.next_stale_proof_prompt || 'n/a'],
|
||||
['proof follow-up queue', String(digestSurface.history_summary?.proof_follow_up_queue_count ?? 0)],
|
||||
['stale proof follow-ups', String(digestSurface.history_summary?.stale_proof_follow_up_count ?? 0)],
|
||||
['proof follow-up age', formatJsonInline(digestSurface.history_summary?.proof_follow_up_age_counts || {})],
|
||||
['oldest proof follow-up', digestSurface.history_summary?.oldest_proof_follow_up_kind || 'n/a'],
|
||||
['oldest proof age', formatDurationSeconds(digestSurface.history_summary?.oldest_proof_follow_up_age_seconds)],
|
||||
['resolved proofs', String(digestSurface.history_summary?.resolved_proof_count ?? 0)],
|
||||
['proof resolution states', formatJsonInline(digestSurface.history_summary?.proof_resolution_state_counts || {})],
|
||||
['proof resolution history', formatList((digestSurface.history_summary?.proof_resolution_history || []).map((item) => `${item.kind}:${item.state}`))],
|
||||
['next proof follow-up', digestSurface.history_summary?.next_proof_follow_up_prompt || 'n/a'],
|
||||
['confirmation trail items', String(digestSurface.history_summary?.refresh_confirmation_trail_count ?? 0)],
|
||||
['confirmation trail', formatList((digestSurface.history_summary?.refresh_confirmation_trail || []).map((item) => `${item.kind}:${item.proof_label}`))],
|
||||
@@ -798,6 +809,9 @@ function renderDecisionAssistant(summary) {
|
||||
['proof follow-up', item.proof_follow_up_state || 'n/a'],
|
||||
['proof follow-up action', item.proof_follow_up_action_label || 'n/a'],
|
||||
['proof resolved state', item.proof_resolved_state || 'n/a'],
|
||||
['proof follow-up age', item.proof_follow_up_age_band || 'n/a'],
|
||||
['proof stale queue', String(Boolean(item.proof_follow_up_stale_queue))],
|
||||
['proof resolution history', formatList((item.proof_resolution_history || []).map((row) => `${row.proof_label || row.state}: ${formatTimestamp(row.ts)}`))],
|
||||
['latest proof trail', item.latest_confirmation_trail_item?.proof_label || 'n/a'],
|
||||
['proof trail items', formatList((item.refresh_confirmation_trail || []).map((row) => `${row.title || row.kind}: ${row.proof_label}`))],
|
||||
['refresh evidence', item.refresh_evidence_anchor || 'n/a'],
|
||||
@@ -857,6 +871,9 @@ function renderDecisionAssistant(summary) {
|
||||
['negative guardrails', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.negative_feedback_guardrail_count ?? 0)],
|
||||
['guardrail templates', formatList(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.negative_feedback_guardrail_template_ids || [])],
|
||||
['guardrail resolution queue', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.guardrail_resolution_count ?? 0)],
|
||||
['preference recovery queue', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preference_recovery_count ?? 0)],
|
||||
['next preference recovery', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.next_preference_recovery_template_id || 'n/a'],
|
||||
['guardrail audit trail', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.guardrail_audit_trail_count ?? 0)],
|
||||
['preferred template decay', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preferred_template_decay_status || 'n/a'],
|
||||
['preferred decay templates', formatList(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preferred_template_decay_template_ids || [])],
|
||||
['max preference decay', formatPercent(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preferred_template_decay_max)],
|
||||
@@ -919,6 +936,9 @@ function renderDecisionAssistant(summary) {
|
||||
['guardrail resolution prompt', item.guardrail_resolution_prompt || 'n/a'],
|
||||
['preference decay', formatPercent(item.preferred_template_decay)],
|
||||
['decay reason', item.preferred_template_decay_reason || 'n/a'],
|
||||
['preference recovery', item.preference_recovery_status || 'n/a'],
|
||||
['recovery prompt', item.preference_recovery_prompt || 'n/a'],
|
||||
['guardrail audit trail', formatList((item.guardrail_audit_trail || []).map((row) => `${row.label || row.signal}: ${row.resolution_status}`))],
|
||||
['feedback trend history', formatList((item.feedback_trend_history || []).map((row) => `${row.label || row.signal}: ${formatTimestamp(row.created_ts)}`))],
|
||||
],
|
||||
[
|
||||
@@ -1221,6 +1241,11 @@ function renderDecisionAssistant(summary) {
|
||||
['promotion queue status', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_queue_status || 'n/a'],
|
||||
['promotion queue rank', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_queue_rank ?? 'n/a')],
|
||||
['promotion next action', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_next_action || 'n/a'],
|
||||
['promotion decision history', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_decision_history_count ?? 0)],
|
||||
['promotion decision reasons', formatList((setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_decision_history || []).map((item) => item.reason || item.event))],
|
||||
['review lane age', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_age_status || 'n/a'],
|
||||
['review lane age score', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_age_score ?? 'n/a')],
|
||||
['review lane aging hint', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_aging_hint || 'n/a'],
|
||||
['demotion history', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.demotion_history_count ?? 0)],
|
||||
['demotion reasons', formatList((setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.demotion_history || []).map((item) => item.reason || item.event))],
|
||||
['family sample threshold', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.sample_threshold ?? 'n/a')],
|
||||
|
||||
@@ -203,6 +203,11 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("guardrail_resolution_count", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("preferred_template_decay_status", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("preferred_template_decay_template_ids", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("preference_recovery_queue", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("preference_recovery_count", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("next_preference_recovery_template_id", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("guardrail_audit_trail", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("guardrail_audit_trail_count", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertEqual(summary["review_entrypoints"]["note_templates"][0]["reuse_count"], 1)
|
||||
self.assertEqual(summary["review_entrypoints"]["note_templates"][0]["reuse_status"], "saved_reuse")
|
||||
self.assertEqual(summary["review_entrypoints"]["note_templates"][0]["outcome_status"], "useful_signal")
|
||||
@@ -216,6 +221,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("guardrail_resolution_prompt", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("preferred_template_decay", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("preferred_template_decay_reason", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("preference_recovery_status", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("preference_recovery_prompt", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("guardrail_audit_trail", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("guardrail_audit_trail_count", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertTrue(any(item["template_id"] == "next_review" for item in summary["review_entrypoints"]["note_templates"]))
|
||||
self.assertTrue(any(item["template_id"] == "queue_follow_up" for item in summary["review_entrypoints"]["note_templates"]))
|
||||
self.assertTrue(any(item["reuse_hint"] for item in summary["review_entrypoints"]["note_templates"]))
|
||||
@@ -240,6 +249,12 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("resolved_proof_count", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_resolution_state_counts", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("next_proof_follow_up_prompt", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("stale_proof_follow_up_count", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_follow_up_age_counts", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_resolution_history", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_resolution_history_count", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("oldest_proof_follow_up_kind", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("oldest_proof_follow_up_age_seconds", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("refresh_prompts", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("digest_history_groups", summary)
|
||||
self.assertIn("group_status", summary["digest_history_groups"][0])
|
||||
@@ -261,6 +276,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("proof_follow_up_state", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_follow_up_action_label", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_resolved_state", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_follow_up_age_seconds", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_follow_up_age_band", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_follow_up_stale_queue", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_resolution_history", summary["digest_history_groups"][0])
|
||||
self.assertIn("refresh_confirmation_trail", summary["digest_history_groups"][0])
|
||||
self.assertIn("latest_confirmation_trail_item", summary["digest_history_groups"][0])
|
||||
self.assertTrue(summary["digest_history_preview"])
|
||||
@@ -304,6 +323,11 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("promotion_queue_rank", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_next_action", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_queue_item", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_decision_history", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_decision_history_count", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("review_lane_age_status", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("review_lane_age_score", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("review_lane_aging_hint", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("demotion_history", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("demotion_history_count", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("sample_threshold", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
@@ -343,6 +367,12 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("resume_queue_count", summary["scan_handoff_summary"])
|
||||
self.assertIn("next_resume_marker", summary["scan_handoff_summary"])
|
||||
self.assertIn("next_resume_prompt", summary["scan_handoff_summary"])
|
||||
self.assertIn("resume_queue_persistence_status", summary["scan_handoff_summary"])
|
||||
self.assertIn("resume_queue_persistence_key", summary["scan_handoff_summary"])
|
||||
self.assertIn("resolved_lane_state_counts", summary["scan_handoff_summary"])
|
||||
self.assertIn("resolved_lane_count", summary["scan_handoff_summary"])
|
||||
self.assertIn("unresolved_lane_count", summary["scan_handoff_summary"])
|
||||
self.assertIn("next_resolved_lane_prompt", summary["scan_handoff_summary"])
|
||||
self.assertIn("handoff_cleanup_recommendation", summary["scan_handoff_summary"])
|
||||
self.assertTrue(summary["scan_handoff_summary"]["snooze_options"])
|
||||
self.assertIn("snooze_seconds", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
@@ -352,6 +382,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("lifecycle_state", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("resume_queue_rank", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("resume_action_label", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("resume_persistence_key", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("resume_persistence_status", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("resolved_lane_state", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("resolved_lane_prompt", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertTrue(summary["external_context_summary"]["operator_questions"])
|
||||
self.assertTrue(summary["external_context_summary"]["source_cards"])
|
||||
self.assertEqual(summary["external_context_summary"]["exchange_status"]["status"], "active")
|
||||
|
||||
Reference in New Issue
Block a user