Close decision assistant H15 F16 G15 D17
This commit is contained in:
+24
-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 H14/F15/G14/D16 by adding handoff lane grouping/overdue cleanup visibility, digest proof-aging escalation, template feedback trend history with negative-feedback guardrails, and setup-family promotion-readiness 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 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.
|
||||
|
||||
## Current Branch
|
||||
|
||||
@@ -84,6 +84,10 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_digest_proof_escalation_slice: proof_aging_trend_stale_escalation
|
||||
- latest_decision_assistant_template_guardrail_slice: feedback_trend_negative_guardrails
|
||||
- latest_decision_assistant_replay_promotion_slice: setup_family_watchlist_promotion_readiness
|
||||
- latest_decision_assistant_handoff_lifecycle_slice: handoff_lifecycle_resume_queue
|
||||
- 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_summary_status: implemented
|
||||
- latest_decision_assistant_change_summary_status: implemented
|
||||
- latest_decision_assistant_evidence_status: implemented
|
||||
@@ -152,10 +156,14 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_digest_proof_escalation_status: implemented
|
||||
- latest_decision_assistant_template_guardrail_status: implemented
|
||||
- latest_decision_assistant_replay_promotion_status: implemented
|
||||
- latest_decision_assistant_handoff_lifecycle_status: implemented
|
||||
- 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_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_grouping_proof_escalation_feedback_guardrails_promotion_visible
|
||||
- latest_remote_decision_assistant_review_packet_status: handoff_lifecycle_proof_followup_guardrail_resolution_promotion_queue_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
|
||||
@@ -234,6 +242,12 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
|
||||
## Completed In This Run
|
||||
|
||||
- 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`).
|
||||
- Added setup-family promotion queue / demotion history fields (`promotion_queue_status`, `promotion_queue_rank`, `promotion_next_action`, `promotion_queue_item`, `demotion_history`, and `demotion_history_count`).
|
||||
- Passed focused validation: `unittest tests.unit.test_operator_workspace_service`, `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), `scripts/run_script_static_checks.py`, and `node --check src/web/app.js`.
|
||||
- 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 handoff lifecycle/resume queue, proof follow-up queue, guardrail resolution/decay, setup-family promotion queue/demotion history, and matching `/static/app.js` UI strings.
|
||||
- Added handoff lane grouping / overdue cleanup visibility (`handoff_lane_groups`, `overdue_cleanup_count`, `next_cleanup_*`, per-marker `lane_group`, `overdue_cleanup_candidate`, and `cleanup_priority`) plus matching cockpit summaries.
|
||||
- Added digest proof-aging trend and stale-proof escalation fields (`proof_age_trend`, `proof_escalation_level`, `proof_escalation_prompt`, `proof_aging_trend_counts`, and `stale_proof_escalation_count`).
|
||||
- Added template feedback trend history and negative-feedback guardrails (`feedback_trend_history`, `negative_feedback_guardrail`, `negative_feedback_guardrail_count`, and guardrail template IDs) with cockpit visibility.
|
||||
@@ -521,7 +535,7 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
|
||||
## Exact Next Step
|
||||
|
||||
Continue the Decision Assistant dense operator workspace with the next bounded slice: H15 handoff lane lifecycle summary / operator resume queue, F16 digest proof escalation follow-up queue / resolved proof states, G15 template guardrail resolution / preferred-template decay, and D17 setup-family promotion queue / demotion history. 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: 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.
|
||||
|
||||
## Validation Commands Used Recently
|
||||
|
||||
@@ -533,6 +547,13 @@ D:\ai-exchange\.venv\Scripts\python.exe -m unittest discover -s tests/unit
|
||||
node --check src/web/app.js
|
||||
D:\ai-exchange\.venv\Scripts\python.exe scripts\deploy_with_password.py --host 192.168.3.90 --user root --port 22 --password <provided>
|
||||
D:\ai-exchange\.venv\Scripts\python.exe scripts\run_remote_ops_smoke.py --base-url http://192.168.3.90:8000 --attempts 2 --timeout-seconds 20
|
||||
D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_operator_workspace_service
|
||||
D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_operator_workspace_service tests.unit.test_api_static_server tests.unit.test_api_server
|
||||
D:\ai-exchange\.venv\Scripts\python.exe scripts\run_script_static_checks.py
|
||||
D:\ai-exchange\.venv\Scripts\python.exe -m unittest discover -s tests/unit
|
||||
node --check src/web/app.js
|
||||
D:\ai-exchange\.venv\Scripts\python.exe scripts\deploy_with_password.py --host 192.168.3.90 --user root --port 22 --password <provided>
|
||||
D:\ai-exchange\.venv\Scripts\python.exe scripts\run_remote_ops_smoke.py --base-url http://192.168.3.90:8000 --attempts 2 --timeout-seconds 20
|
||||
D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_visual_review_service tests.unit.test_run_visual_review tests.unit.test_trading_runtime_service tests.unit.test_api_and_main tests.unit.test_api_server
|
||||
D:\ai-exchange\.venv\Scripts\python.exe scripts\run_script_static_checks.py
|
||||
D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_run_until_blocked tests.unit.test_monitoring_alert_rollout tests.unit.test_runtime_script_json
|
||||
|
||||
@@ -84,6 +84,10 @@
|
||||
- [x] Decision Assistant F15 digest proof aging trend / stale proof escalation slice complete
|
||||
- [x] Decision Assistant G14 template feedback trend history / negative-feedback guardrails slice complete
|
||||
- [x] Decision Assistant D16 setup-family watchlist prioritization / promotion-readiness hints slice complete
|
||||
- [x] Decision Assistant H15 handoff lane lifecycle summary / operator resume queue slice complete
|
||||
- [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
|
||||
|
||||
## Next Up
|
||||
|
||||
@@ -99,10 +103,14 @@
|
||||
- [x] F15 deepen digest recall with proof aging trend / stale proof escalation
|
||||
- [x] G14 continue review template ergonomics with feedback trend history / negative-feedback guardrails
|
||||
- [x] D16 extend replay comparison with setup-family watchlist prioritization / promotion-readiness hints
|
||||
- [ ] H15 continue compact operator workspace polish with handoff lane lifecycle summary / operator resume queue
|
||||
- [ ] F16 deepen digest recall with proof escalation follow-up queue / resolved proof states
|
||||
- [ ] G15 continue review template ergonomics with template guardrail resolution / preferred-template decay
|
||||
- [ ] D17 extend replay comparison with setup-family promotion queue / demotion history
|
||||
- [x] H15 continue compact operator workspace polish with handoff lane lifecycle summary / operator resume queue
|
||||
- [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] P8 local closure complete
|
||||
- [x] P9 local closure complete
|
||||
- [x] P10 local closure complete
|
||||
@@ -194,6 +202,10 @@
|
||||
- [x] latest_decision_assistant_digest_proof_escalation_slice: proof_aging_trend_stale_escalation
|
||||
- [x] latest_decision_assistant_template_guardrail_slice: feedback_trend_negative_guardrails
|
||||
- [x] latest_decision_assistant_replay_promotion_slice: setup_family_watchlist_promotion_readiness
|
||||
- [x] latest_decision_assistant_handoff_lifecycle_slice: handoff_lifecycle_resume_queue
|
||||
- [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_summary_status: implemented
|
||||
- [x] latest_decision_assistant_change_summary_status: implemented
|
||||
- [x] latest_decision_assistant_evidence_status: implemented
|
||||
@@ -259,10 +271,14 @@
|
||||
- [x] latest_decision_assistant_digest_proof_escalation_status: implemented
|
||||
- [x] latest_decision_assistant_template_guardrail_status: implemented
|
||||
- [x] latest_decision_assistant_replay_promotion_status: implemented
|
||||
- [x] latest_decision_assistant_handoff_lifecycle_status: implemented
|
||||
- [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_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_grouping_proof_escalation_feedback_guardrails_promotion_visible
|
||||
- [x] latest_remote_decision_assistant_review_packet_status: handoff_lifecycle_proof_followup_guardrail_resolution_promotion_queue_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
|
||||
@@ -340,6 +356,11 @@
|
||||
|
||||
## Newly Done
|
||||
|
||||
- [x] Added handoff lifecycle / operator resume queue fields and cockpit visibility
|
||||
- [x] Added digest proof follow-up queue, resolved proof state counts, and group follow-up actions
|
||||
- [x] Added template guardrail resolution and preferred-template decay fields
|
||||
- [x] Added setup-family promotion queue and demotion history fields
|
||||
- [x] Passed focused validation, full unit discovery, script static checks, JS syntax check, deploy smoke, remote ops smoke, and remote field smoke for H15/F16/G15/D17
|
||||
- [x] Added handoff lane grouping / overdue cleanup visibility and matching cockpit summaries
|
||||
- [x] Added digest proof-aging trend, stale-proof escalation fields, and summary counts
|
||||
- [x] Added template feedback trend history and negative-feedback guardrail visibility
|
||||
|
||||
@@ -1086,6 +1086,10 @@ def _build_decision_assistant_review_entrypoints(
|
||||
item["feedback_trend_history"] = outcome.get("feedback_trend_history") or []
|
||||
item["negative_feedback_guardrail"] = outcome.get("negative_feedback_guardrail")
|
||||
item["negative_feedback_guardrail_prompt"] = outcome.get("negative_feedback_guardrail_prompt")
|
||||
item["guardrail_resolution_status"] = outcome.get("guardrail_resolution_status")
|
||||
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")
|
||||
return {
|
||||
"status": "active",
|
||||
"note_count": len(recent_notes),
|
||||
@@ -1416,6 +1420,21 @@ def _score_template_outcome(*, template_id: str, usage: dict, notes: list[dict])
|
||||
template_id=template_id,
|
||||
negative_count=negative_count,
|
||||
),
|
||||
"guardrail_resolution_status": _template_guardrail_resolution_status(negative_count=negative_count),
|
||||
"guardrail_resolution_prompt": _template_guardrail_resolution_prompt(
|
||||
template_id=template_id,
|
||||
negative_count=negative_count,
|
||||
),
|
||||
"preferred_template_decay": _template_preferred_decay_factor(
|
||||
negative_count=negative_count,
|
||||
positive_count=positive_count,
|
||||
actionable_count=action_count,
|
||||
),
|
||||
"preferred_template_decay_reason": _template_preferred_decay_reason(
|
||||
negative_count=negative_count,
|
||||
positive_count=positive_count,
|
||||
actionable_count=action_count,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -1438,6 +1457,34 @@ def _template_negative_guardrail_prompt(*, template_id: str, negative_count: int
|
||||
return f"{template_label} has no negative-feedback guardrail; continue to capture outcome notes after reuse."
|
||||
|
||||
|
||||
def _template_guardrail_resolution_status(*, negative_count: int) -> str:
|
||||
if negative_count:
|
||||
return "resolution_required"
|
||||
return "resolved_clear"
|
||||
|
||||
|
||||
def _template_guardrail_resolution_prompt(*, template_id: str, negative_count: int) -> str:
|
||||
template_label = template_id.replace("_", " ") or "template"
|
||||
if negative_count:
|
||||
return f"Capture a resolution note for {template_label} before it becomes a preferred template again."
|
||||
return f"{template_label} guardrail is resolved; no negative-feedback follow-up is pending."
|
||||
|
||||
|
||||
def _template_preferred_decay_factor(*, negative_count: int, positive_count: int, actionable_count: int) -> float:
|
||||
if negative_count <= 0:
|
||||
return 0.0
|
||||
positive_offset = min(0.4, (positive_count + actionable_count) * 0.1)
|
||||
return max(0.2, min(1.0, (negative_count * 0.35) - positive_offset))
|
||||
|
||||
|
||||
def _template_preferred_decay_reason(*, negative_count: int, positive_count: int, actionable_count: int) -> str:
|
||||
if negative_count:
|
||||
return "negative_feedback_requires_preference_decay"
|
||||
if positive_count or actionable_count:
|
||||
return "positive_feedback_keeps_preference"
|
||||
return "no_feedback_decay_signal"
|
||||
|
||||
|
||||
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":
|
||||
@@ -1508,6 +1555,17 @@ def _build_template_feedback_aggregation(
|
||||
for item in template_outcomes.values()
|
||||
if item.get("negative_feedback_guardrail") == "pause_before_reuse"
|
||||
]
|
||||
guardrail_resolution_queue = [
|
||||
{
|
||||
"template_id": str(item.get("template_id") or ""),
|
||||
"status": item.get("guardrail_resolution_status"),
|
||||
"prompt": item.get("guardrail_resolution_prompt"),
|
||||
"decay": item.get("preferred_template_decay"),
|
||||
"reason": item.get("preferred_template_decay_reason"),
|
||||
}
|
||||
for item in template_outcomes.values()
|
||||
if item.get("guardrail_resolution_status") == "resolution_required"
|
||||
]
|
||||
rated_template_count = sum(
|
||||
1
|
||||
for item in template_outcomes.values()
|
||||
@@ -1535,6 +1593,13 @@ def _build_template_feedback_aggregation(
|
||||
feedback_trend_status = "mixed_feedback"
|
||||
else:
|
||||
feedback_trend_status = "needs_more_feedback"
|
||||
recommended_guardrail = str(recommended_template_id or "") in negative_feedback_guardrail_ids
|
||||
if recommended_guardrail:
|
||||
preferred_template_decay_status = "decay_recommended_template"
|
||||
elif guardrail_resolution_queue:
|
||||
preferred_template_decay_status = "decay_guardrailed_templates"
|
||||
else:
|
||||
preferred_template_decay_status = "no_decay_needed"
|
||||
return {
|
||||
"status": "active" if template_outcomes else "idle",
|
||||
"useful_feedback_count": useful_count,
|
||||
@@ -1550,6 +1615,13 @@ def _build_template_feedback_aggregation(
|
||||
"feedback_trend_history_count": sum(len(item.get("feedback_trend_history") or []) for item in template_outcomes.values()),
|
||||
"negative_feedback_guardrail_count": len(negative_feedback_guardrail_ids),
|
||||
"negative_feedback_guardrail_template_ids": _dedupe(negative_feedback_guardrail_ids)[:5],
|
||||
"guardrail_resolution_queue": guardrail_resolution_queue[:5],
|
||||
"guardrail_resolution_count": len(guardrail_resolution_queue),
|
||||
"preferred_template_decay_status": preferred_template_decay_status,
|
||||
"preferred_template_decay_template_ids": [item.get("template_id") for item in guardrail_resolution_queue[:5]],
|
||||
"preferred_template_decay_max": max(
|
||||
[float(item.get("decay") or 0.0) for item in guardrail_resolution_queue] or [0.0]
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -1785,6 +1857,22 @@ def _build_decision_assistant_digest_history_summary(history: list[dict]) -> dic
|
||||
for group in groups
|
||||
if group.get("proof_escalation_level") in {"stale_refresh", "missing_proof"}
|
||||
]
|
||||
proof_follow_up_queue = [
|
||||
{
|
||||
"kind": str(group.get("kind") or "history"),
|
||||
"state": group.get("proof_follow_up_state"),
|
||||
"action_label": group.get("proof_follow_up_action_label"),
|
||||
"prompt": group.get("proof_escalation_prompt"),
|
||||
"evidence_anchor": group.get("refresh_evidence_anchor"),
|
||||
"resolved_state": group.get("proof_resolved_state"),
|
||||
}
|
||||
for group in groups
|
||||
if group.get("proof_follow_up_state") in {"follow_up_required", "watch"}
|
||||
]
|
||||
proof_resolution_state_counts = {}
|
||||
for group in groups:
|
||||
state = str(group.get("proof_resolved_state") or "unknown")
|
||||
proof_resolution_state_counts[state] = int(proof_resolution_state_counts.get(state) or 0) + 1
|
||||
refresh_confirmation_count = sum(
|
||||
1
|
||||
for group in groups
|
||||
@@ -1819,6 +1907,11 @@ def _build_decision_assistant_digest_history_summary(history: list[dict]) -> dic
|
||||
"stale_proof_escalation_count": len(stale_proof_escalations),
|
||||
"next_stale_proof_kind": stale_proof_escalations[0].get("kind") if stale_proof_escalations else None,
|
||||
"next_stale_proof_prompt": stale_proof_escalations[0].get("proof_escalation_prompt") if stale_proof_escalations else None,
|
||||
"proof_follow_up_queue_count": len(proof_follow_up_queue),
|
||||
"proof_follow_up_queue": proof_follow_up_queue[:5],
|
||||
"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,
|
||||
}
|
||||
|
||||
|
||||
@@ -1844,6 +1937,11 @@ def _build_decision_assistant_digest_history_groups(history: list[dict], *, reco
|
||||
age=age,
|
||||
freshness_proof_status=freshness_proof_status,
|
||||
)
|
||||
proof_follow_up_state = _digest_history_proof_follow_up_state(proof_escalation_level=proof_escalation_level)
|
||||
proof_resolved_state = _digest_history_proof_resolved_state(
|
||||
proof_escalation_level=proof_escalation_level,
|
||||
freshness_proof_status=freshness_proof_status,
|
||||
)
|
||||
refresh_confirmation_trail = _digest_history_refresh_confirmation_trail(
|
||||
kind=kind,
|
||||
items=items,
|
||||
@@ -1891,6 +1989,12 @@ def _build_decision_assistant_digest_history_groups(history: list[dict], *, reco
|
||||
proof_escalation_level=proof_escalation_level,
|
||||
evidence_anchor=str(refresh_evidence_anchor.get("anchor") or ""),
|
||||
),
|
||||
"proof_follow_up_state": proof_follow_up_state,
|
||||
"proof_follow_up_action_label": _digest_history_proof_follow_up_action_label(
|
||||
kind=kind,
|
||||
proof_follow_up_state=proof_follow_up_state,
|
||||
),
|
||||
"proof_resolved_state": proof_resolved_state,
|
||||
"refresh_confirmation_trail": refresh_confirmation_trail,
|
||||
"latest_confirmation_trail_item": refresh_confirmation_trail[0] if refresh_confirmation_trail else None,
|
||||
"items": items[:3],
|
||||
@@ -2094,6 +2198,34 @@ def _digest_history_proof_escalation_prompt(
|
||||
return f"{kind} proof trend is {proof_age_trend}; no escalation is needed."
|
||||
|
||||
|
||||
def _digest_history_proof_follow_up_state(*, proof_escalation_level: str) -> str:
|
||||
if proof_escalation_level in {"missing_proof", "stale_refresh"}:
|
||||
return "follow_up_required"
|
||||
if proof_escalation_level == "watch":
|
||||
return "watch"
|
||||
return "resolved"
|
||||
|
||||
|
||||
def _digest_history_proof_follow_up_action_label(*, kind: str, proof_follow_up_state: str) -> str:
|
||||
if proof_follow_up_state == "follow_up_required":
|
||||
return f"Resolve {kind} proof"
|
||||
if proof_follow_up_state == "watch":
|
||||
return f"Watch {kind} proof"
|
||||
return f"{kind} proof resolved"
|
||||
|
||||
|
||||
def _digest_history_proof_resolved_state(*, proof_escalation_level: str, freshness_proof_status: str) -> str:
|
||||
if proof_escalation_level == "none" and freshness_proof_status == "freshness_proven":
|
||||
return "resolved_fresh"
|
||||
if proof_escalation_level == "watch":
|
||||
return "aging_watch"
|
||||
if proof_escalation_level == "missing_proof":
|
||||
return "missing_proof"
|
||||
if proof_escalation_level == "stale_refresh":
|
||||
return "pending_refresh"
|
||||
return "unresolved"
|
||||
|
||||
|
||||
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")
|
||||
@@ -2405,6 +2537,9 @@ def _build_decision_assistant_scan_handoff_summary(cards: list[dict]) -> dict:
|
||||
"lane_group": _scan_handoff_lane_group(str(item.get("handoff_status") or "")),
|
||||
"overdue_cleanup_candidate": str(item.get("handoff_status") or "") in {"blocked_handoff", "refresh_handoff"},
|
||||
"cleanup_priority": _scan_handoff_cleanup_priority(str(item.get("handoff_status") or "")),
|
||||
"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 "")),
|
||||
}
|
||||
for item in cards
|
||||
if item.get("handoff_marker")
|
||||
@@ -2416,6 +2551,7 @@ def _build_decision_assistant_scan_handoff_summary(cards: list[dict]) -> dict:
|
||||
if bool(item.get("overdue_cleanup_candidate"))
|
||||
]
|
||||
next_cleanup_marker = dict(cleanup_markers[0] or {}) if cleanup_markers else {}
|
||||
operator_resume_queue = _build_scan_handoff_resume_queue(markers)
|
||||
stale_after_values = [
|
||||
int(item.get("stale_after_seconds") or 0)
|
||||
for item in markers
|
||||
@@ -2439,6 +2575,12 @@ def _build_decision_assistant_scan_handoff_summary(cards: list[dict]) -> dict:
|
||||
"next_cleanup_title": next_cleanup_marker.get("title"),
|
||||
"next_cleanup_prompt": next_cleanup_marker.get("handoff_prompt"),
|
||||
"handoff_lane_groups": _build_scan_handoff_lane_groups(markers),
|
||||
"handoff_lifecycle_summary": _build_scan_handoff_lifecycle_summary(markers),
|
||||
"operator_resume_queue": operator_resume_queue[:5],
|
||||
"resume_queue_count": len(operator_resume_queue),
|
||||
"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,
|
||||
"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": [
|
||||
@@ -2471,6 +2613,77 @@ def _scan_handoff_cleanup_priority(handoff_status: str) -> int:
|
||||
return 10
|
||||
|
||||
|
||||
def _scan_handoff_lifecycle_state(handoff_status: str) -> str:
|
||||
if handoff_status == "next_handoff":
|
||||
return "resume_next"
|
||||
if handoff_status == "blocked_handoff":
|
||||
return "blocked_active"
|
||||
if handoff_status == "refresh_handoff":
|
||||
return "refresh_required"
|
||||
if handoff_status == "context_ready":
|
||||
return "carry_forward"
|
||||
return "ready_to_resume"
|
||||
|
||||
|
||||
def _scan_handoff_resume_rank(handoff_status: str) -> int:
|
||||
if handoff_status == "next_handoff":
|
||||
return 1
|
||||
if handoff_status == "blocked_handoff":
|
||||
return 2
|
||||
if handoff_status == "refresh_handoff":
|
||||
return 3
|
||||
if handoff_status == "ready_handoff":
|
||||
return 4
|
||||
return 5
|
||||
|
||||
|
||||
def _scan_handoff_resume_action_label(handoff_status: str) -> str:
|
||||
if handoff_status == "next_handoff":
|
||||
return "Resume Next"
|
||||
if handoff_status == "blocked_handoff":
|
||||
return "Resume Blocker"
|
||||
if handoff_status == "refresh_handoff":
|
||||
return "Resume Refresh"
|
||||
if handoff_status == "context_ready":
|
||||
return "Carry Forward"
|
||||
return "Resume Ready"
|
||||
|
||||
|
||||
def _build_scan_handoff_resume_queue(markers: list[dict]) -> list[dict]:
|
||||
return sorted(
|
||||
[
|
||||
{
|
||||
"handoff_marker": item.get("handoff_marker"),
|
||||
"title": item.get("title"),
|
||||
"target_section": item.get("target_section"),
|
||||
"lifecycle_state": item.get("lifecycle_state"),
|
||||
"resume_action_label": item.get("resume_action_label"),
|
||||
"handoff_prompt": item.get("handoff_prompt"),
|
||||
"resume_queue_rank": item.get("resume_queue_rank"),
|
||||
}
|
||||
for item in markers
|
||||
if item.get("lifecycle_state") != "carry_forward"
|
||||
],
|
||||
key=lambda row: (int(row.get("resume_queue_rank") or 99), str(row.get("title") or "")),
|
||||
)
|
||||
|
||||
|
||||
def _build_scan_handoff_lifecycle_summary(markers: list[dict]) -> dict:
|
||||
state_counts: dict[str, int] = {}
|
||||
for item in markers:
|
||||
state = str(item.get("lifecycle_state") or "unknown")
|
||||
state_counts[state] = int(state_counts.get(state) or 0) + 1
|
||||
return {
|
||||
"state_counts": state_counts,
|
||||
"active_count": sum(
|
||||
int(state_counts.get(state) or 0)
|
||||
for state in ("resume_next", "blocked_active", "refresh_required", "ready_to_resume")
|
||||
),
|
||||
"carry_forward_count": int(state_counts.get("carry_forward") or 0),
|
||||
"resume_ready_count": int(state_counts.get("resume_next") or 0) + int(state_counts.get("ready_to_resume") or 0),
|
||||
}
|
||||
|
||||
|
||||
def _build_scan_handoff_lane_groups(markers: list[dict]) -> list[dict]:
|
||||
lanes: dict[str, list[dict]] = {}
|
||||
for marker in markers:
|
||||
@@ -2788,6 +3001,12 @@ def _build_setup_replay_broader_baseline_summary(
|
||||
"promotion_ready": False,
|
||||
"promotion_readiness_hint": f"{setup_family} needs at least 5 samples before promotion readiness can be assessed.",
|
||||
"promotion_blockers": ["insufficient_samples"],
|
||||
"promotion_queue_status": "collect_samples",
|
||||
"promotion_queue_rank": 5,
|
||||
"promotion_next_action": "Collect more replay samples before promotion review.",
|
||||
"promotion_queue_item": None,
|
||||
"demotion_history": [],
|
||||
"demotion_history_count": 0,
|
||||
"focus_recommendation": f"Collect samples before changing focus for {setup_family}.",
|
||||
"focus_reasons": ["insufficient_samples"],
|
||||
"drift_watchlist_items": ["collect_more_replay_samples"],
|
||||
@@ -2990,6 +3209,12 @@ def _build_setup_family_trend_delta(
|
||||
"promotion_ready": False,
|
||||
"promotion_readiness_hint": f"{setup_family} needs more replay samples before promotion readiness can be assessed.",
|
||||
"promotion_blockers": ["insufficient_samples"],
|
||||
"promotion_queue_status": "collect_samples",
|
||||
"promotion_queue_rank": 5,
|
||||
"promotion_next_action": "Collect more replay samples before promotion review.",
|
||||
"promotion_queue_item": None,
|
||||
"demotion_history": [],
|
||||
"demotion_history_count": 0,
|
||||
"focus_recommendation": f"Collect samples before changing focus for {setup_family}.",
|
||||
"focus_reasons": ["insufficient_samples"],
|
||||
"drift_watchlist_items": ["collect_more_replay_samples"],
|
||||
@@ -3062,6 +3287,18 @@ def _build_setup_family_trend_delta(
|
||||
drift_alert=drift_alert,
|
||||
sample_gap=sample_gap,
|
||||
)
|
||||
promotion_queue_status = _setup_family_promotion_queue_status(
|
||||
promotion_ready=promotion_ready,
|
||||
drift_alert=drift_alert,
|
||||
sample_gap=sample_gap,
|
||||
trend_status=trend_status,
|
||||
)
|
||||
demotion_history = _setup_family_demotion_history(
|
||||
drift_alert=drift_alert,
|
||||
promotion_blockers=promotion_blockers,
|
||||
focus_reasons=focus_reasons,
|
||||
trend_status=trend_status,
|
||||
)
|
||||
return {
|
||||
"status": "active",
|
||||
"trend_status": trend_status,
|
||||
@@ -3088,6 +3325,20 @@ def _build_setup_family_trend_delta(
|
||||
watchlist_status=watchlist_status,
|
||||
),
|
||||
"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(
|
||||
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,
|
||||
),
|
||||
"demotion_history": demotion_history[:5],
|
||||
"demotion_history_count": len(demotion_history),
|
||||
"focus_recommendation": focus_recommendation,
|
||||
"focus_reasons": focus_reasons[:4],
|
||||
"drift_watchlist_items": _dedupe(drift_watchlist_items)[:6],
|
||||
@@ -3121,6 +3372,111 @@ def _setup_family_promotion_readiness_hint(
|
||||
return f"{setup_family} remains in {watchlist_status or 'watch'} mode pending more replay evidence."
|
||||
|
||||
|
||||
def _setup_family_promotion_queue_status(
|
||||
*,
|
||||
promotion_ready: bool,
|
||||
drift_alert: bool,
|
||||
sample_gap: int,
|
||||
trend_status: str,
|
||||
) -> str:
|
||||
if promotion_ready:
|
||||
return "ready_for_operator_review"
|
||||
if drift_alert:
|
||||
return "demotion_review"
|
||||
if sample_gap > 0:
|
||||
return "collect_samples"
|
||||
if trend_status == "mixed_vs_family_peers":
|
||||
return "watch_before_promotion"
|
||||
return "hold"
|
||||
|
||||
|
||||
def _setup_family_promotion_queue_rank(*, promotion_queue_status: str) -> int:
|
||||
return {
|
||||
"demotion_review": 1,
|
||||
"ready_for_operator_review": 2,
|
||||
"watch_before_promotion": 3,
|
||||
"collect_samples": 4,
|
||||
"hold": 5,
|
||||
}.get(str(promotion_queue_status or "hold"), 5)
|
||||
|
||||
|
||||
def _setup_family_promotion_next_action(*, setup_family: str, promotion_queue_status: str) -> str:
|
||||
if promotion_queue_status == "ready_for_operator_review":
|
||||
return f"Review {setup_family} for analysis-lane promotion; keep execution permissions unchanged."
|
||||
if promotion_queue_status == "demotion_review":
|
||||
return f"Review whether {setup_family} should be demoted or held because drift is active."
|
||||
if promotion_queue_status == "watch_before_promotion":
|
||||
return f"Keep {setup_family} on watch until mixed peer deltas resolve."
|
||||
if promotion_queue_status == "collect_samples":
|
||||
return f"Collect more {setup_family} replay samples before promotion review."
|
||||
return f"Hold {setup_family} in the current review lane."
|
||||
|
||||
|
||||
def _setup_family_promotion_queue_item(
|
||||
*,
|
||||
setup_family: str,
|
||||
promotion_queue_status: str,
|
||||
promotion_ready: bool,
|
||||
watchlist_priority_score: int,
|
||||
) -> dict | None:
|
||||
if promotion_queue_status in {"hold"}:
|
||||
return None
|
||||
return {
|
||||
"setup_family": setup_family,
|
||||
"status": promotion_queue_status,
|
||||
"promotion_ready": promotion_ready,
|
||||
"priority_score": watchlist_priority_score,
|
||||
"action": _setup_family_promotion_next_action(
|
||||
setup_family=setup_family,
|
||||
promotion_queue_status=promotion_queue_status,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _setup_family_demotion_history(
|
||||
*,
|
||||
drift_alert: bool,
|
||||
promotion_blockers: list[str],
|
||||
focus_reasons: list[str],
|
||||
trend_status: str,
|
||||
) -> list[dict]:
|
||||
history = []
|
||||
if drift_alert:
|
||||
history.append(
|
||||
{
|
||||
"event": "drift_alert",
|
||||
"severity": "warning",
|
||||
"reason": "weaker_vs_family_peers",
|
||||
}
|
||||
)
|
||||
for blocker in promotion_blockers[:3]:
|
||||
history.append(
|
||||
{
|
||||
"event": "promotion_blocker",
|
||||
"severity": "watch",
|
||||
"reason": blocker,
|
||||
}
|
||||
)
|
||||
if trend_status == "mixed_vs_family_peers":
|
||||
history.append(
|
||||
{
|
||||
"event": "mixed_trend_watch",
|
||||
"severity": "watch",
|
||||
"reason": "mixed_family_delta",
|
||||
}
|
||||
)
|
||||
for reason in focus_reasons[:2]:
|
||||
if reason not in {item.get("reason") for item in history}:
|
||||
history.append(
|
||||
{
|
||||
"event": "focus_reason",
|
||||
"severity": "info",
|
||||
"reason": reason,
|
||||
}
|
||||
)
|
||||
return history
|
||||
|
||||
|
||||
def _rank_session_composite(rows: list[dict], *, session_code: str) -> int | None:
|
||||
if not session_code:
|
||||
return None
|
||||
|
||||
@@ -573,6 +573,11 @@ function renderDecisionAssistant(summary) {
|
||||
['handoff cleanup prompt', scanHandoffSummary.next_cleanup_prompt || 'n/a'],
|
||||
['handoff lane groups', formatList((scanHandoffSummary.handoff_lane_groups || []).map((item) => `${item.label || item.status}: ${item.count ?? 0}`))],
|
||||
['handoff lane overdue', formatList((scanHandoffSummary.handoff_lane_groups || []).filter((item) => Number(item.overdue_count || 0) > 0).map((item) => `${item.label || item.status}: ${item.overdue_count}`))],
|
||||
['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}`))],
|
||||
['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'],
|
||||
['default stale after', formatDurationSeconds(scanHandoffSummary.default_stale_after_seconds)],
|
||||
['saved handoff marker', assistantScanHandoffMarker?.marker || 'n/a'],
|
||||
@@ -699,6 +704,10 @@ function renderDecisionAssistant(summary) {
|
||||
['stale proof escalations', String(digestSurface.history_summary?.stale_proof_escalation_count ?? 0)],
|
||||
['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)],
|
||||
['resolved proofs', String(digestSurface.history_summary?.resolved_proof_count ?? 0)],
|
||||
['proof resolution states', formatJsonInline(digestSurface.history_summary?.proof_resolution_state_counts || {})],
|
||||
['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}`))],
|
||||
['refresh evidence anchors', String(digestSurface.history_summary?.refresh_evidence_anchor_count ?? 0)],
|
||||
@@ -786,6 +795,9 @@ function renderDecisionAssistant(summary) {
|
||||
['proof age trend', item.proof_age_trend || 'n/a'],
|
||||
['proof escalation', item.proof_escalation_level || 'n/a'],
|
||||
['proof escalation prompt', item.proof_escalation_prompt || 'n/a'],
|
||||
['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'],
|
||||
['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'],
|
||||
@@ -844,6 +856,10 @@ function renderDecisionAssistant(summary) {
|
||||
['feedback trend samples', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.feedback_trend_history_count ?? 0)],
|
||||
['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)],
|
||||
['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)],
|
||||
['rated templates', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.rated_template_count ?? 0)],
|
||||
['template outcome avg', formatPercent(reviewEntrypoints.template_reuse_metrics?.template_outcome_summary?.average_usefulness_score)],
|
||||
['template feedback gaps', String(reviewEntrypoints.template_reuse_metrics?.template_outcome_summary?.feedback_gap_count ?? 0)],
|
||||
@@ -899,6 +915,10 @@ function renderDecisionAssistant(summary) {
|
||||
['feedback prompt', item.feedback_prompt || 'n/a'],
|
||||
['negative guardrail', item.negative_feedback_guardrail || 'n/a'],
|
||||
['guardrail prompt', item.negative_feedback_guardrail_prompt || 'n/a'],
|
||||
['guardrail resolution', item.guardrail_resolution_status || 'n/a'],
|
||||
['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'],
|
||||
['feedback trend history', formatList((item.feedback_trend_history || []).map((row) => `${row.label || row.signal}: ${formatTimestamp(row.created_ts)}`))],
|
||||
],
|
||||
[
|
||||
@@ -1198,6 +1218,11 @@ function renderDecisionAssistant(summary) {
|
||||
['promotion readiness', String(Boolean(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_ready))],
|
||||
['promotion readiness hint', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_readiness_hint || 'n/a'],
|
||||
['promotion blockers', formatList(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_blockers || [])],
|
||||
['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'],
|
||||
['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')],
|
||||
['family sample gap', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.sample_gap ?? 'n/a')],
|
||||
['family watchlist items', formatList(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.drift_watchlist_items || [])],
|
||||
|
||||
@@ -199,6 +199,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("feedback_trend_status", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("negative_feedback_guardrail_count", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("negative_feedback_guardrail_template_ids", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("guardrail_resolution_queue", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
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.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")
|
||||
@@ -208,6 +212,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("feedback_trend_history", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("negative_feedback_guardrail", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("negative_feedback_guardrail_prompt", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("guardrail_resolution_status", summary["review_entrypoints"]["note_templates"][0])
|
||||
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.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"]))
|
||||
@@ -227,6 +235,11 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("stale_proof_escalation_count", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("next_stale_proof_kind", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("next_stale_proof_prompt", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_follow_up_queue_count", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_follow_up_queue", summary["digest_surface"]["history_summary"])
|
||||
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("refresh_prompts", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("digest_history_groups", summary)
|
||||
self.assertIn("group_status", summary["digest_history_groups"][0])
|
||||
@@ -245,6 +258,9 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("proof_age_trend", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_escalation_level", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_escalation_prompt", summary["digest_history_groups"][0])
|
||||
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("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"])
|
||||
@@ -284,6 +300,12 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("promotion_ready", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_readiness_hint", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_blockers", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_queue_status", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
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("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"])
|
||||
self.assertIn("sample_gap", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertTrue(summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"]["drift_watchlist_items"])
|
||||
@@ -316,12 +338,20 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("next_cleanup_marker", summary["scan_handoff_summary"])
|
||||
self.assertIn("next_cleanup_prompt", summary["scan_handoff_summary"])
|
||||
self.assertIn("handoff_lane_groups", summary["scan_handoff_summary"])
|
||||
self.assertIn("handoff_lifecycle_summary", summary["scan_handoff_summary"])
|
||||
self.assertIn("operator_resume_queue", summary["scan_handoff_summary"])
|
||||
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("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])
|
||||
self.assertIn("lane_group", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("overdue_cleanup_candidate", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("cleanup_priority", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
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.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