Persist workflow artifacts and auto-run migrations
This commit is contained in:
+8
-4
@@ -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, read-only market context, and a continuous operator cockpit. This run closed F26/H27 as a real backend batch: digest preferences now persist on the server, digest snapshots archive into a history table, cockpit reads back the persisted mode/history archive, and the front-end writes preference changes back through a dedicated API instead of local-only storage. Local validation passed, remote migration `044_create_decision_assistant_digest_state.sql` was applied, 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, read-only market context, and a continuous operator cockpit. This run closed F26/W5/H27 as a real backend persistence batch: digest preferences now persist on the server, digest snapshots archive into a history table, workflow artifacts archive into their own history table, cockpit reads back both persisted digest/workflow archives, and the front-end writes digest preference changes back through a dedicated API instead of local-only storage. Local validation passed, remote migrations `044_create_decision_assistant_digest_state.sql` and `045_create_decision_assistant_workflow_history.sql` were applied, the current working tree was deployed, and remote smoke confirmed the latest Decision Assistant fields on the live host.
|
||||
|
||||
## Current Branch
|
||||
|
||||
@@ -131,6 +131,8 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_review_handoff_slice: review_entry_handoff_summary
|
||||
- latest_decision_assistant_digest_persistence_slice: digest_preferences_api_archive_history
|
||||
- latest_decision_assistant_digest_archive_slice: persisted_digest_archive_visibility
|
||||
- latest_decision_assistant_workflow_persistence_slice: workflow_history_archive_persistence
|
||||
- latest_decision_assistant_workflow_archive_slice: persisted_workflow_archive_visibility
|
||||
- latest_decision_assistant_summary_status: implemented
|
||||
- latest_decision_assistant_change_summary_status: implemented
|
||||
- latest_decision_assistant_evidence_status: implemented
|
||||
@@ -246,10 +248,12 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_review_handoff_status: implemented
|
||||
- latest_decision_assistant_digest_persistence_status: implemented
|
||||
- latest_decision_assistant_digest_archive_status: implemented
|
||||
- latest_decision_assistant_workflow_persistence_status: implemented
|
||||
- latest_decision_assistant_workflow_archive_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: digest_preferences_archive_visible
|
||||
- latest_remote_decision_assistant_review_packet_status: digest_and_workflow_archives_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
|
||||
@@ -665,12 +669,12 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- Latest E7 validation passed: focused operator workspace tests, 68 related unit/API/static tests, 713 full 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 confirmed `chart_adapter_mode=optional_reference`, `chart_primary_mode=internal_review_packet_html`, and a non-blocking external chart adapter even when other supporting context sources are stale.
|
||||
- Latest W4/D25/F25/H25 validation passed: focused operator workspace tests, 68 related unit/API/static tests, 713 full 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 confirmed `workflow_artifact_summary`, `digest_action_queue`, `learning_focus_matrix`, and `workspace_continuity_handoff` plus matching UI strings.
|
||||
- Latest C3/G20/H26 validation passed: focused operator workspace tests, 68 related unit/API/static tests, 713 full 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 confirmed `watchlist focus matrix`, `review_entry_action_queue`, `review_entry_handoff_summary`, and watchlist-focus presence inside `assistant_loop_queue`.
|
||||
- Latest F26/H27 validation passed: focused operator workspace/API tests, 69 related unit/API/static tests, 714 full unit tests, 73 script static checks, `node --check src/web/app.js`, remote migration `044_create_decision_assistant_digest_state.sql` applied successfully, deploy completed, remote ops smoke passed 9 checks, and remote field smoke confirmed `/trading/alerts/digest-preferences`, persisted `digest_preference_profile`, and `digest_history_archive`.
|
||||
- Latest F26/W5/H27 validation passed: focused operator workspace/API/deploy tests, 75 related unit/API/static tests, 715 full unit tests, 73 script static checks, `node --check src/web/app.js`, remote migrations `044_create_decision_assistant_digest_state.sql` and `045_create_decision_assistant_workflow_history.sql` applied successfully, deploy completed, remote ops smoke passed 9 checks, and remote field smoke confirmed `/trading/alerts/digest-preferences`, persisted `digest_preference_profile`, `digest_history_archive`, and `workflow_history_archive`.
|
||||
- Real-live order submission remains intentionally disabled because the OKX order adapter is still not a real-live submitter.
|
||||
|
||||
## Exact Next Step
|
||||
|
||||
F26/H27 backend digest persistence batch is closed. External chart URLs remain optional reference enhancements and are not required for the current analysis-only loop. Internal non-trading backlog still exists, but PXX remains closed; do not create P25/P26 automatically, and do not enable real trading.
|
||||
F26/W5/H27 backend persistence batch is closed. External chart URLs remain optional reference enhancements and are not required for the current analysis-only loop. Internal non-trading backlog still exists, but PXX remains closed; do not create P25/P26 automatically, and do not enable real trading.
|
||||
|
||||
## Validation Commands Used Recently
|
||||
|
||||
|
||||
@@ -161,6 +161,7 @@
|
||||
- [x] G20 review entry action queue slice
|
||||
- [x] H26 review/watchlist continuity into loop queue slice
|
||||
- [x] F26 digest preference backend persistence / history archive slice
|
||||
- [x] W5 workflow artifact archive persistence slice
|
||||
- [x] H27 persisted digest archive / cockpit sync slice
|
||||
- [x] P8 local closure complete
|
||||
- [x] P9 local closure complete
|
||||
@@ -286,6 +287,8 @@
|
||||
- [x] latest_decision_assistant_review_handoff_slice: review_entry_handoff_summary
|
||||
- [x] latest_decision_assistant_digest_persistence_slice: digest_preferences_api_archive_history
|
||||
- [x] latest_decision_assistant_digest_archive_slice: persisted_digest_archive_visibility
|
||||
- [x] latest_decision_assistant_workflow_persistence_slice: workflow_history_archive_persistence
|
||||
- [x] latest_decision_assistant_workflow_archive_slice: persisted_workflow_archive_visibility
|
||||
- [x] latest_decision_assistant_summary_status: implemented
|
||||
- [x] latest_decision_assistant_change_summary_status: implemented
|
||||
- [x] latest_decision_assistant_evidence_status: implemented
|
||||
@@ -398,10 +401,12 @@
|
||||
- [x] latest_decision_assistant_review_handoff_status: implemented
|
||||
- [x] latest_decision_assistant_digest_persistence_status: implemented
|
||||
- [x] latest_decision_assistant_digest_archive_status: implemented
|
||||
- [x] latest_decision_assistant_workflow_persistence_status: implemented
|
||||
- [x] latest_decision_assistant_workflow_archive_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: digest_preferences_archive_visible
|
||||
- [x] latest_remote_decision_assistant_review_packet_status: digest_and_workflow_archives_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
|
||||
@@ -713,8 +718,8 @@
|
||||
- [x] Deployed W4/D25/F25/H25 current tree and passed remote smoke: 11 deploy endpoints, 9 remote ops checks, and review-packet/static field smoke for `workflow_artifact_summary`, `digest_action_queue`, `learning_focus_matrix`, and `workspace_continuity_handoff`
|
||||
- [x] Completed C3/G20/H26 focused validation: operator workspace tests, 68 related unit/API/static tests, 713 full unit tests, 73 script static checks, and dashboard JS syntax
|
||||
- [x] Deployed C3/G20/H26 current tree and passed remote smoke: 11 deploy endpoints, 9 remote ops checks, and review-packet/static field smoke for `watchlist focus matrix`, `review_entry_action_queue`, `review_entry_handoff_summary`, and watchlist-focus presence inside `assistant_loop_queue`
|
||||
- [x] Completed F26/H27 focused validation: operator workspace/API tests, 69 related unit/API/static tests, 714 full unit tests, 73 script static checks, and dashboard JS syntax
|
||||
- [x] Deployed F26/H27 current tree, applied remote migration `044_create_decision_assistant_digest_state.sql`, and passed remote smoke for `/trading/alerts/digest-preferences`, persisted `digest_preference_profile`, and `digest_history_archive`
|
||||
- [x] Completed F26/W5/H27 focused validation: operator workspace/API/deploy tests, 75 related unit/API/static tests, 715 full unit tests, 73 script static checks, and dashboard JS syntax
|
||||
- [x] Deployed F26/W5/H27 current tree, applied remote migrations `044_create_decision_assistant_digest_state.sql` and `045_create_decision_assistant_workflow_history.sql`, and passed remote smoke for `/trading/alerts/digest-preferences`, persisted `digest_preference_profile`, `digest_history_archive`, and `workflow_history_archive`
|
||||
|
||||
## Blocked
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
create table if not exists decision_assistant_workflow_history (
|
||||
id bigserial primary key,
|
||||
profile_key varchar(64) not null default 'local_operator',
|
||||
instrument_id bigint not null,
|
||||
current_session varchar(32),
|
||||
next_phase varchar(32),
|
||||
next_artifact_kind varchar(64),
|
||||
snapshot_signature varchar(128) not null,
|
||||
payload jsonb not null default '{}'::jsonb,
|
||||
created_ts timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index if not exists idx_decision_assistant_workflow_history_instrument_ts
|
||||
on decision_assistant_workflow_history (instrument_id, created_ts desc);
|
||||
|
||||
create index if not exists idx_decision_assistant_workflow_history_signature
|
||||
on decision_assistant_workflow_history (instrument_id, snapshot_signature);
|
||||
@@ -123,6 +123,7 @@ def build_deploy_command(*, remote_path: str, remote_archive: str) -> str:
|
||||
f"cd {remote_path_q}",
|
||||
"((docker compose down || docker-compose down) >/dev/null 2>&1 || true)",
|
||||
"((docker compose up -d --build) || (docker-compose up -d --build))",
|
||||
"((docker compose exec -T ai-ict python scripts/apply_migrations.py) || (docker-compose exec -T ai-ict python scripts/apply_migrations.py))",
|
||||
"((docker compose ps) || (docker-compose ps))",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -167,3 +167,89 @@ class DecisionAssistantRepository:
|
||||
},
|
||||
).mappings().one()
|
||||
return dict(row)
|
||||
|
||||
def fetch_recent_workflow_history(
|
||||
self,
|
||||
*,
|
||||
instrument_id: int,
|
||||
profile_key: str = DEFAULT_PROFILE_KEY,
|
||||
limit: int = 20,
|
||||
) -> list[dict]:
|
||||
sql = text(
|
||||
"""
|
||||
select id, profile_key, instrument_id, current_session, next_phase,
|
||||
next_artifact_kind, snapshot_signature, payload, created_ts
|
||||
from decision_assistant_workflow_history
|
||||
where instrument_id = :instrument_id and profile_key = :profile_key
|
||||
order by created_ts desc, id desc
|
||||
limit :limit
|
||||
"""
|
||||
)
|
||||
with SessionLocal() as session:
|
||||
rows = session.execute(
|
||||
sql,
|
||||
{"instrument_id": instrument_id, "profile_key": profile_key, "limit": limit},
|
||||
).mappings().all()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
def fetch_latest_workflow_history(
|
||||
self,
|
||||
*,
|
||||
instrument_id: int,
|
||||
profile_key: str = DEFAULT_PROFILE_KEY,
|
||||
) -> dict | None:
|
||||
rows = self.fetch_recent_workflow_history(instrument_id=instrument_id, profile_key=profile_key, limit=1)
|
||||
return rows[0] if rows else None
|
||||
|
||||
def insert_workflow_history(
|
||||
self,
|
||||
*,
|
||||
instrument_id: int,
|
||||
profile_key: str = DEFAULT_PROFILE_KEY,
|
||||
current_session: str | None = None,
|
||||
next_phase: str | None = None,
|
||||
next_artifact_kind: str | None = None,
|
||||
snapshot_signature: str,
|
||||
payload: dict | None = None,
|
||||
created_ts=None,
|
||||
) -> dict:
|
||||
sql = text(
|
||||
"""
|
||||
insert into decision_assistant_workflow_history (
|
||||
profile_key,
|
||||
instrument_id,
|
||||
current_session,
|
||||
next_phase,
|
||||
next_artifact_kind,
|
||||
snapshot_signature,
|
||||
payload,
|
||||
created_ts
|
||||
) values (
|
||||
:profile_key,
|
||||
:instrument_id,
|
||||
:current_session,
|
||||
:next_phase,
|
||||
:next_artifact_kind,
|
||||
:snapshot_signature,
|
||||
cast(:payload as jsonb),
|
||||
coalesce(:created_ts, now())
|
||||
)
|
||||
returning id, profile_key, instrument_id, current_session, next_phase,
|
||||
next_artifact_kind, snapshot_signature, payload, created_ts
|
||||
"""
|
||||
)
|
||||
with SessionLocal.begin() as session:
|
||||
row = session.execute(
|
||||
sql,
|
||||
{
|
||||
"profile_key": profile_key,
|
||||
"instrument_id": instrument_id,
|
||||
"current_session": current_session,
|
||||
"next_phase": next_phase,
|
||||
"next_artifact_kind": next_artifact_kind,
|
||||
"snapshot_signature": snapshot_signature,
|
||||
"payload": json.dumps(payload or {}, ensure_ascii=False, default=str),
|
||||
"created_ts": created_ts,
|
||||
},
|
||||
).mappings().one()
|
||||
return dict(row)
|
||||
|
||||
@@ -178,6 +178,10 @@ class DecisionAssistantSummaryService:
|
||||
repository=self.decision_assistant_repository,
|
||||
instrument_id=instrument_id,
|
||||
)
|
||||
persisted_workflow_history = _safe_fetch_workflow_history(
|
||||
repository=self.decision_assistant_repository,
|
||||
instrument_id=instrument_id,
|
||||
)
|
||||
summary = build_decision_assistant_summary(
|
||||
instrument_id=instrument_id,
|
||||
timeframe=timeframe,
|
||||
@@ -196,25 +200,44 @@ class DecisionAssistantSummaryService:
|
||||
recent_executions=list(recent_executions or []),
|
||||
persisted_digest_preference=dict(persisted_digest_preference or {}),
|
||||
persisted_digest_history=list(persisted_digest_history or []),
|
||||
persisted_workflow_history=list(persisted_workflow_history or []),
|
||||
)
|
||||
persistence_result = _safe_persist_digest_history_snapshot(
|
||||
repository=self.decision_assistant_repository,
|
||||
instrument_id=instrument_id,
|
||||
summary=summary,
|
||||
)
|
||||
workflow_persistence_result = _safe_persist_workflow_history_snapshot(
|
||||
repository=self.decision_assistant_repository,
|
||||
instrument_id=instrument_id,
|
||||
summary=summary,
|
||||
)
|
||||
refreshed_digest_history = _safe_fetch_digest_history(
|
||||
repository=self.decision_assistant_repository,
|
||||
instrument_id=instrument_id,
|
||||
)
|
||||
refreshed_workflow_history = _safe_fetch_workflow_history(
|
||||
repository=self.decision_assistant_repository,
|
||||
instrument_id=instrument_id,
|
||||
)
|
||||
digest_history_archive = _build_decision_assistant_digest_history_archive(refreshed_digest_history)
|
||||
workflow_history_archive = _build_decision_assistant_workflow_history_archive(refreshed_workflow_history)
|
||||
summary["digest_history_archive"] = digest_history_archive
|
||||
summary["digest_persistence_status"] = persistence_result.get("status")
|
||||
summary["workflow_history_archive"] = workflow_history_archive
|
||||
summary["workflow_persistence_status"] = workflow_persistence_result.get("status")
|
||||
digest_surface = dict(summary.get("digest_surface") or {})
|
||||
digest_surface["digest_history_archive"] = digest_history_archive
|
||||
digest_surface["digest_history_archive_count"] = digest_history_archive.get("snapshot_count") or 0
|
||||
digest_surface["next_digest_history_archive_review"] = digest_history_archive.get("next_review_prompt")
|
||||
digest_surface["digest_persistence_status"] = persistence_result.get("status")
|
||||
summary["digest_surface"] = digest_surface
|
||||
workflow_artifact_summary = dict(summary.get("workflow_artifact_summary") or {})
|
||||
workflow_artifact_summary["workflow_history_archive"] = workflow_history_archive
|
||||
workflow_artifact_summary["workflow_history_archive_count"] = workflow_history_archive.get("snapshot_count") or 0
|
||||
workflow_artifact_summary["next_workflow_history_review"] = workflow_history_archive.get("next_review_prompt")
|
||||
workflow_artifact_summary["workflow_persistence_status"] = workflow_persistence_result.get("status")
|
||||
summary["workflow_artifact_summary"] = workflow_artifact_summary
|
||||
return summary
|
||||
|
||||
|
||||
@@ -295,6 +318,13 @@ def _safe_fetch_digest_history(*, repository: DecisionAssistantRepository, instr
|
||||
return []
|
||||
|
||||
|
||||
def _safe_fetch_workflow_history(*, repository: DecisionAssistantRepository, instrument_id: int, limit: int = 20) -> list[dict]:
|
||||
try:
|
||||
return list(repository.fetch_recent_workflow_history(instrument_id=instrument_id, limit=limit) or [])
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _safe_persist_digest_history_snapshot(
|
||||
*,
|
||||
repository: DecisionAssistantRepository,
|
||||
@@ -332,6 +362,43 @@ def _safe_persist_digest_history_snapshot(
|
||||
return {"status": "unavailable", "snapshot_signature": signature}
|
||||
|
||||
|
||||
def _safe_persist_workflow_history_snapshot(
|
||||
*,
|
||||
repository: DecisionAssistantRepository,
|
||||
instrument_id: int,
|
||||
summary: dict,
|
||||
) -> dict:
|
||||
workflow_artifact_summary = dict(summary.get("workflow_artifact_summary") or {})
|
||||
workflow_artifacts = list(summary.get("workflow_artifacts") or [])
|
||||
snapshot_payload = {
|
||||
"current_session": summary.get("current_session"),
|
||||
"focus_mode": summary.get("focus_mode"),
|
||||
"artifact_count": workflow_artifact_summary.get("artifact_count"),
|
||||
"next_artifact_kind": workflow_artifact_summary.get("next_artifact_kind"),
|
||||
"next_artifact_phase": workflow_artifact_summary.get("next_artifact_phase"),
|
||||
"ready_for_handoff_count": workflow_artifact_summary.get("ready_for_handoff_count"),
|
||||
"needs_update_count": workflow_artifact_summary.get("needs_update_count"),
|
||||
"pending_capture_count": workflow_artifact_summary.get("pending_capture_count"),
|
||||
"artifacts": workflow_artifacts[:3],
|
||||
}
|
||||
signature = hashlib.sha1(json.dumps(snapshot_payload, sort_keys=True, ensure_ascii=False, default=str).encode("utf-8")).hexdigest()
|
||||
try:
|
||||
latest = repository.fetch_latest_workflow_history(instrument_id=instrument_id)
|
||||
if latest and str(latest.get("snapshot_signature") or "") == signature:
|
||||
return {"status": "duplicate_skipped", "snapshot_signature": signature}
|
||||
repository.insert_workflow_history(
|
||||
instrument_id=instrument_id,
|
||||
current_session=str(summary.get("current_session") or ""),
|
||||
next_phase=str(workflow_artifact_summary.get("next_artifact_phase") or ""),
|
||||
next_artifact_kind=str(workflow_artifact_summary.get("next_artifact_kind") or ""),
|
||||
snapshot_signature=signature,
|
||||
payload=snapshot_payload,
|
||||
)
|
||||
return {"status": "persisted", "snapshot_signature": signature}
|
||||
except Exception:
|
||||
return {"status": "unavailable", "snapshot_signature": signature}
|
||||
|
||||
|
||||
def _build_research_council_summary(run: Optional[dict]) -> dict:
|
||||
payload = dict(((run or {}).get("payload") or {}).get("decision") or {})
|
||||
return {
|
||||
@@ -368,6 +435,7 @@ def build_decision_assistant_summary(
|
||||
recent_executions: list[dict],
|
||||
persisted_digest_preference: dict,
|
||||
persisted_digest_history: list[dict],
|
||||
persisted_workflow_history: list[dict],
|
||||
) -> dict:
|
||||
market_state = dict(launch_readiness.get("market_state") or {})
|
||||
session_state = dict(market_state.get("session") or {})
|
||||
@@ -597,6 +665,7 @@ def build_decision_assistant_summary(
|
||||
"digest_history_groups": digest_history_groups,
|
||||
"digest_history_browser": digest_history_browser,
|
||||
"digest_history_archive": digest_history_archive,
|
||||
"workflow_history_archive": _build_decision_assistant_workflow_history_archive(persisted_workflow_history),
|
||||
"digest_surface": digest_surface,
|
||||
"setup_learning_rollup": setup_learning_rollup,
|
||||
"decision_timeline": decision_timeline,
|
||||
@@ -3703,6 +3772,47 @@ def _build_decision_assistant_digest_history_archive(rows: list[dict]) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _build_decision_assistant_workflow_history_archive(rows: list[dict]) -> dict:
|
||||
if not rows:
|
||||
return {
|
||||
"status": "idle",
|
||||
"snapshot_count": 0,
|
||||
"latest_created_ts": None,
|
||||
"latest_next_phase": None,
|
||||
"next_review_prompt": None,
|
||||
"items": [],
|
||||
}
|
||||
items = []
|
||||
for row in rows[:12]:
|
||||
payload = dict(row.get("payload") or {})
|
||||
items.append(
|
||||
{
|
||||
"created_ts": row.get("created_ts"),
|
||||
"current_session": row.get("current_session"),
|
||||
"next_phase": row.get("next_phase"),
|
||||
"next_artifact_kind": row.get("next_artifact_kind"),
|
||||
"focus_mode": payload.get("focus_mode"),
|
||||
"artifact_count": payload.get("artifact_count"),
|
||||
"ready_for_handoff_count": payload.get("ready_for_handoff_count"),
|
||||
"needs_update_count": payload.get("needs_update_count"),
|
||||
"pending_capture_count": payload.get("pending_capture_count"),
|
||||
}
|
||||
)
|
||||
latest = dict(items[0] or {})
|
||||
return {
|
||||
"status": "active",
|
||||
"snapshot_count": len(rows),
|
||||
"latest_created_ts": latest.get("created_ts"),
|
||||
"latest_next_phase": latest.get("next_phase"),
|
||||
"next_review_prompt": (
|
||||
f"Review persisted workflow snapshot from {latest.get('created_ts')} before creating the next phase brief."
|
||||
if latest.get("created_ts")
|
||||
else None
|
||||
),
|
||||
"items": items,
|
||||
}
|
||||
|
||||
|
||||
def _digest_history_browser_hint(*, recommended_kind: str, total_count: int) -> str:
|
||||
if recommended_kind:
|
||||
return f"Open {recommended_kind} first, then review the remaining {total_count} digest history item(s) by freshness debt."
|
||||
|
||||
+28
-7
@@ -1397,13 +1397,16 @@ function renderDecisionAssistant(summary) {
|
||||
buildInfoGrid([
|
||||
['workflow artifacts', summary.workflow_artifact_summary.status || 'idle'],
|
||||
['artifact count', String(summary.workflow_artifact_summary.artifact_count ?? 0)],
|
||||
['artifacts ready', String(summary.workflow_artifact_summary.ready_for_handoff_count ?? 0)],
|
||||
['artifacts updating', String(summary.workflow_artifact_summary.needs_update_count ?? 0)],
|
||||
['artifacts pending', String(summary.workflow_artifact_summary.pending_capture_count ?? 0)],
|
||||
['next artifact', summary.workflow_artifact_summary.next_artifact_kind || 'n/a'],
|
||||
['next artifact prompt', summary.workflow_artifact_summary.next_artifact_prompt || 'n/a'],
|
||||
])
|
||||
);
|
||||
['artifacts ready', String(summary.workflow_artifact_summary.ready_for_handoff_count ?? 0)],
|
||||
['artifacts updating', String(summary.workflow_artifact_summary.needs_update_count ?? 0)],
|
||||
['artifacts pending', String(summary.workflow_artifact_summary.pending_capture_count ?? 0)],
|
||||
['next artifact', summary.workflow_artifact_summary.next_artifact_kind || 'n/a'],
|
||||
['next artifact prompt', summary.workflow_artifact_summary.next_artifact_prompt || 'n/a'],
|
||||
['workflow archive', String(summary.workflow_artifact_summary.workflow_history_archive_count ?? 0)],
|
||||
['workflow archive review', summary.workflow_artifact_summary.next_workflow_history_review || 'n/a'],
|
||||
['workflow persistence', summary.workflow_artifact_summary.workflow_persistence_status || 'n/a'],
|
||||
])
|
||||
);
|
||||
}
|
||||
const workflowGrid = createElement('div', 'cards');
|
||||
workflowDigest.forEach((item) => {
|
||||
@@ -1445,6 +1448,24 @@ function renderDecisionAssistant(summary) {
|
||||
});
|
||||
decisionAssistantPanel.appendChild(artifactGrid);
|
||||
}
|
||||
if (summary.workflow_artifact_summary?.workflow_history_archive?.items?.length) {
|
||||
const workflowArchiveGrid = createElement('div', 'cards assistant-tight-grid');
|
||||
(summary.workflow_artifact_summary.workflow_history_archive.items || []).forEach((item) => {
|
||||
workflowArchiveGrid.appendChild(
|
||||
buildItemCard(
|
||||
item.next_artifact_kind || item.next_phase || 'workflow archive',
|
||||
item.current_session || 'session',
|
||||
[
|
||||
['time', formatTimestamp(item.created_ts)],
|
||||
['focus mode', item.focus_mode || 'n/a'],
|
||||
['artifacts', String(item.artifact_count ?? 0)],
|
||||
['ready/update/pending', `${item.ready_for_handoff_count ?? 0} / ${item.needs_update_count ?? 0} / ${item.pending_capture_count ?? 0}`],
|
||||
]
|
||||
)
|
||||
);
|
||||
});
|
||||
decisionAssistantPanel.appendChild(workflowArchiveGrid);
|
||||
}
|
||||
}
|
||||
if (opportunities.length) {
|
||||
appendSectionTitle(decisionAssistantPanel, 'Opportunities');
|
||||
|
||||
@@ -35,6 +35,14 @@ class DeployWithPasswordTests(unittest.TestCase):
|
||||
self.assertIn("/trading/maintenance-summary?instrument_id=1", smoke_source)
|
||||
self.assertIn("/trading/unattended-harness?instrument_id=1", smoke_source)
|
||||
|
||||
def test_deploy_command_runs_migrations_after_compose_up(self) -> None:
|
||||
command = deploy_with_password.build_deploy_command(
|
||||
remote_path="/root/deploy/ai-ict",
|
||||
remote_archive="/root/ai-ict-deploy.tgz",
|
||||
)
|
||||
|
||||
self.assertIn("scripts/apply_migrations.py", command)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -184,6 +184,9 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("workflow_artifact_summary", summary)
|
||||
self.assertTrue(summary["workflow_artifacts"])
|
||||
self.assertIn("next_artifact_kind", summary["workflow_artifact_summary"])
|
||||
self.assertIn("workflow_history_archive", summary["workflow_artifact_summary"])
|
||||
self.assertIn("workflow_history_archive_count", summary["workflow_artifact_summary"])
|
||||
self.assertIn("workflow_persistence_status", summary["workflow_artifact_summary"])
|
||||
self.assertIn("assistant_loop_queue", summary)
|
||||
self.assertIn("assistant_loop_summary", summary)
|
||||
self.assertTrue(summary["assistant_loop_queue"])
|
||||
@@ -351,6 +354,7 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("digest_history_archive", summary["digest_surface"])
|
||||
self.assertIn("digest_history_archive_count", summary["digest_surface"])
|
||||
self.assertIn("digest_persistence_status", summary["digest_surface"])
|
||||
self.assertIn("next_digest_history_archive_review", summary["digest_surface"])
|
||||
self.assertIn("next_high_value_change", summary["digest_surface"])
|
||||
self.assertIn("digest_review_back_actions", summary["digest_surface"])
|
||||
self.assertIn("digest_review_back_action_count", summary["digest_surface"])
|
||||
@@ -896,6 +900,37 @@ class _DecisionAssistantRepo:
|
||||
"created_ts": "2026-05-12T02:07:00+00:00",
|
||||
}
|
||||
|
||||
def fetch_recent_workflow_history(self, **kwargs):
|
||||
return [
|
||||
{
|
||||
"profile_key": "local_operator",
|
||||
"instrument_id": kwargs["instrument_id"],
|
||||
"current_session": "LONDON",
|
||||
"next_phase": "intraday",
|
||||
"next_artifact_kind": "intraday_monitor_digest",
|
||||
"snapshot_signature": "unit-workflow-signature",
|
||||
"payload": {
|
||||
"focus_mode": "review_top_setup",
|
||||
"artifact_count": 3,
|
||||
"ready_for_handoff_count": 1,
|
||||
"needs_update_count": 1,
|
||||
"pending_capture_count": 1,
|
||||
},
|
||||
"created_ts": "2026-05-12T02:08:00+00:00",
|
||||
}
|
||||
]
|
||||
|
||||
def fetch_latest_workflow_history(self, **kwargs):
|
||||
return self.fetch_recent_workflow_history(**kwargs)[0]
|
||||
|
||||
def insert_workflow_history(self, **kwargs):
|
||||
return {
|
||||
"id": 10,
|
||||
"instrument_id": kwargs["instrument_id"],
|
||||
"snapshot_signature": kwargs["snapshot_signature"],
|
||||
"created_ts": "2026-05-12T02:09:00+00:00",
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user