Persist workflow artifacts and auto-run migrations
This commit is contained in:
+8
-4
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Current Goal
|
## 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
|
## 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_review_handoff_slice: review_entry_handoff_summary
|
||||||
- latest_decision_assistant_digest_persistence_slice: digest_preferences_api_archive_history
|
- latest_decision_assistant_digest_persistence_slice: digest_preferences_api_archive_history
|
||||||
- latest_decision_assistant_digest_archive_slice: persisted_digest_archive_visibility
|
- 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_summary_status: implemented
|
||||||
- latest_decision_assistant_change_summary_status: implemented
|
- latest_decision_assistant_change_summary_status: implemented
|
||||||
- latest_decision_assistant_evidence_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_review_handoff_status: implemented
|
||||||
- latest_decision_assistant_digest_persistence_status: implemented
|
- latest_decision_assistant_digest_persistence_status: implemented
|
||||||
- latest_decision_assistant_digest_archive_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_decision_assistant_workbench_status: visible_local_smoke
|
||||||
- latest_remote_decision_assistant_deploy_status: deployed_current_head
|
- latest_remote_decision_assistant_deploy_status: deployed_current_head
|
||||||
- latest_remote_decision_assistant_smoke_status: passed
|
- 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_p7_selector_choice: continue_periodic_analysis_decision_monitoring_with_maturity_rollup
|
||||||
- latest_p14_slice: webhook_transport_retry
|
- latest_p14_slice: webhook_transport_retry
|
||||||
- latest_p15_pxx_tasklist_status: ready
|
- 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 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 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 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.
|
- Real-live order submission remains intentionally disabled because the OKX order adapter is still not a real-live submitter.
|
||||||
|
|
||||||
## Exact Next Step
|
## 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
|
## Validation Commands Used Recently
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,7 @@
|
|||||||
- [x] G20 review entry action queue slice
|
- [x] G20 review entry action queue slice
|
||||||
- [x] H26 review/watchlist continuity into loop queue slice
|
- [x] H26 review/watchlist continuity into loop queue slice
|
||||||
- [x] F26 digest preference backend persistence / history archive 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] H27 persisted digest archive / cockpit sync slice
|
||||||
- [x] P8 local closure complete
|
- [x] P8 local closure complete
|
||||||
- [x] P9 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_review_handoff_slice: review_entry_handoff_summary
|
||||||
- [x] latest_decision_assistant_digest_persistence_slice: digest_preferences_api_archive_history
|
- [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_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_summary_status: implemented
|
||||||
- [x] latest_decision_assistant_change_summary_status: implemented
|
- [x] latest_decision_assistant_change_summary_status: implemented
|
||||||
- [x] latest_decision_assistant_evidence_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_review_handoff_status: implemented
|
||||||
- [x] latest_decision_assistant_digest_persistence_status: implemented
|
- [x] latest_decision_assistant_digest_persistence_status: implemented
|
||||||
- [x] latest_decision_assistant_digest_archive_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_decision_assistant_workbench_status: visible_local_smoke
|
||||||
- [x] latest_remote_decision_assistant_deploy_status: deployed_current_head
|
- [x] latest_remote_decision_assistant_deploy_status: deployed_current_head
|
||||||
- [x] latest_remote_decision_assistant_smoke_status: passed
|
- [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_p7_selector_choice: continue_periodic_analysis_decision_monitoring_with_maturity_rollup
|
||||||
- [x] latest_p14_slice: webhook_transport_retry
|
- [x] latest_p14_slice: webhook_transport_retry
|
||||||
- [x] latest_p15_pxx_tasklist_status: ready
|
- [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] 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] 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] 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] 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/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] 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
|
## 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}",
|
f"cd {remote_path_q}",
|
||||||
"((docker compose down || docker-compose down) >/dev/null 2>&1 || true)",
|
"((docker compose down || docker-compose down) >/dev/null 2>&1 || true)",
|
||||||
"((docker compose up -d --build) || (docker-compose up -d --build))",
|
"((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))",
|
"((docker compose ps) || (docker-compose ps))",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -167,3 +167,89 @@ class DecisionAssistantRepository:
|
|||||||
},
|
},
|
||||||
).mappings().one()
|
).mappings().one()
|
||||||
return dict(row)
|
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,
|
repository=self.decision_assistant_repository,
|
||||||
instrument_id=instrument_id,
|
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(
|
summary = build_decision_assistant_summary(
|
||||||
instrument_id=instrument_id,
|
instrument_id=instrument_id,
|
||||||
timeframe=timeframe,
|
timeframe=timeframe,
|
||||||
@@ -196,25 +200,44 @@ class DecisionAssistantSummaryService:
|
|||||||
recent_executions=list(recent_executions or []),
|
recent_executions=list(recent_executions or []),
|
||||||
persisted_digest_preference=dict(persisted_digest_preference or {}),
|
persisted_digest_preference=dict(persisted_digest_preference or {}),
|
||||||
persisted_digest_history=list(persisted_digest_history or []),
|
persisted_digest_history=list(persisted_digest_history or []),
|
||||||
|
persisted_workflow_history=list(persisted_workflow_history or []),
|
||||||
)
|
)
|
||||||
persistence_result = _safe_persist_digest_history_snapshot(
|
persistence_result = _safe_persist_digest_history_snapshot(
|
||||||
repository=self.decision_assistant_repository,
|
repository=self.decision_assistant_repository,
|
||||||
instrument_id=instrument_id,
|
instrument_id=instrument_id,
|
||||||
summary=summary,
|
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(
|
refreshed_digest_history = _safe_fetch_digest_history(
|
||||||
repository=self.decision_assistant_repository,
|
repository=self.decision_assistant_repository,
|
||||||
instrument_id=instrument_id,
|
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)
|
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_history_archive"] = digest_history_archive
|
||||||
summary["digest_persistence_status"] = persistence_result.get("status")
|
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 = dict(summary.get("digest_surface") or {})
|
||||||
digest_surface["digest_history_archive"] = digest_history_archive
|
digest_surface["digest_history_archive"] = digest_history_archive
|
||||||
digest_surface["digest_history_archive_count"] = digest_history_archive.get("snapshot_count") or 0
|
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["next_digest_history_archive_review"] = digest_history_archive.get("next_review_prompt")
|
||||||
digest_surface["digest_persistence_status"] = persistence_result.get("status")
|
digest_surface["digest_persistence_status"] = persistence_result.get("status")
|
||||||
summary["digest_surface"] = digest_surface
|
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
|
return summary
|
||||||
|
|
||||||
|
|
||||||
@@ -295,6 +318,13 @@ def _safe_fetch_digest_history(*, repository: DecisionAssistantRepository, instr
|
|||||||
return []
|
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(
|
def _safe_persist_digest_history_snapshot(
|
||||||
*,
|
*,
|
||||||
repository: DecisionAssistantRepository,
|
repository: DecisionAssistantRepository,
|
||||||
@@ -332,6 +362,43 @@ def _safe_persist_digest_history_snapshot(
|
|||||||
return {"status": "unavailable", "snapshot_signature": signature}
|
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:
|
def _build_research_council_summary(run: Optional[dict]) -> dict:
|
||||||
payload = dict(((run or {}).get("payload") or {}).get("decision") or {})
|
payload = dict(((run or {}).get("payload") or {}).get("decision") or {})
|
||||||
return {
|
return {
|
||||||
@@ -368,6 +435,7 @@ def build_decision_assistant_summary(
|
|||||||
recent_executions: list[dict],
|
recent_executions: list[dict],
|
||||||
persisted_digest_preference: dict,
|
persisted_digest_preference: dict,
|
||||||
persisted_digest_history: list[dict],
|
persisted_digest_history: list[dict],
|
||||||
|
persisted_workflow_history: list[dict],
|
||||||
) -> dict:
|
) -> dict:
|
||||||
market_state = dict(launch_readiness.get("market_state") or {})
|
market_state = dict(launch_readiness.get("market_state") or {})
|
||||||
session_state = dict(market_state.get("session") 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_groups": digest_history_groups,
|
||||||
"digest_history_browser": digest_history_browser,
|
"digest_history_browser": digest_history_browser,
|
||||||
"digest_history_archive": digest_history_archive,
|
"digest_history_archive": digest_history_archive,
|
||||||
|
"workflow_history_archive": _build_decision_assistant_workflow_history_archive(persisted_workflow_history),
|
||||||
"digest_surface": digest_surface,
|
"digest_surface": digest_surface,
|
||||||
"setup_learning_rollup": setup_learning_rollup,
|
"setup_learning_rollup": setup_learning_rollup,
|
||||||
"decision_timeline": decision_timeline,
|
"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:
|
def _digest_history_browser_hint(*, recommended_kind: str, total_count: int) -> str:
|
||||||
if recommended_kind:
|
if recommended_kind:
|
||||||
return f"Open {recommended_kind} first, then review the remaining {total_count} digest history item(s) by freshness debt."
|
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([
|
buildInfoGrid([
|
||||||
['workflow artifacts', summary.workflow_artifact_summary.status || 'idle'],
|
['workflow artifacts', summary.workflow_artifact_summary.status || 'idle'],
|
||||||
['artifact count', String(summary.workflow_artifact_summary.artifact_count ?? 0)],
|
['artifact count', String(summary.workflow_artifact_summary.artifact_count ?? 0)],
|
||||||
['artifacts ready', String(summary.workflow_artifact_summary.ready_for_handoff_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 updating', String(summary.workflow_artifact_summary.needs_update_count ?? 0)],
|
||||||
['artifacts pending', String(summary.workflow_artifact_summary.pending_capture_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', summary.workflow_artifact_summary.next_artifact_kind || 'n/a'],
|
||||||
['next artifact prompt', summary.workflow_artifact_summary.next_artifact_prompt || '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');
|
const workflowGrid = createElement('div', 'cards');
|
||||||
workflowDigest.forEach((item) => {
|
workflowDigest.forEach((item) => {
|
||||||
@@ -1445,6 +1448,24 @@ function renderDecisionAssistant(summary) {
|
|||||||
});
|
});
|
||||||
decisionAssistantPanel.appendChild(artifactGrid);
|
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) {
|
if (opportunities.length) {
|
||||||
appendSectionTitle(decisionAssistantPanel, 'Opportunities');
|
appendSectionTitle(decisionAssistantPanel, 'Opportunities');
|
||||||
|
|||||||
@@ -35,6 +35,14 @@ class DeployWithPasswordTests(unittest.TestCase):
|
|||||||
self.assertIn("/trading/maintenance-summary?instrument_id=1", smoke_source)
|
self.assertIn("/trading/maintenance-summary?instrument_id=1", smoke_source)
|
||||||
self.assertIn("/trading/unattended-harness?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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
|||||||
self.assertIn("workflow_artifact_summary", summary)
|
self.assertIn("workflow_artifact_summary", summary)
|
||||||
self.assertTrue(summary["workflow_artifacts"])
|
self.assertTrue(summary["workflow_artifacts"])
|
||||||
self.assertIn("next_artifact_kind", summary["workflow_artifact_summary"])
|
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_queue", summary)
|
||||||
self.assertIn("assistant_loop_summary", summary)
|
self.assertIn("assistant_loop_summary", summary)
|
||||||
self.assertTrue(summary["assistant_loop_queue"])
|
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", summary["digest_surface"])
|
||||||
self.assertIn("digest_history_archive_count", summary["digest_surface"])
|
self.assertIn("digest_history_archive_count", summary["digest_surface"])
|
||||||
self.assertIn("digest_persistence_status", 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("next_high_value_change", summary["digest_surface"])
|
||||||
self.assertIn("digest_review_back_actions", summary["digest_surface"])
|
self.assertIn("digest_review_back_actions", summary["digest_surface"])
|
||||||
self.assertIn("digest_review_back_action_count", 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",
|
"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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user