feat: add guarded production Flow v2 authoritative simulation

This commit is contained in:
plx
2026-08-16 01:22:48 +00:00
parent fc092a96b6
commit ac385342f5
4 changed files with 379 additions and 86 deletions

View File

@@ -8,6 +8,7 @@ from __future__ import annotations
import os
import re
import subprocess
from contextlib import nullcontext
from typing import Any, Dict, List, Optional
from sqlalchemy import text
@@ -26,7 +27,7 @@ from app.canonical_operations import (
opportunity_ids_from_work_seeds,
partition_canonical_items,
)
from app.opportunity_next_action_service import get_opportunity_next_actions
from app.opportunity_next_action_service import get_opportunity_next_actions, get_opportunity_next_actions_for_mode
from app.document_reconciliation_service import active_document_link_exclusion_sql
@@ -104,7 +105,7 @@ def _norm_identity(value: Any) -> str:
def _identity_tokens(value: Any) -> set[str]:
return {
result = {
token
for token in _norm_identity(value).split()
if len(token) >= 3 and token not in _IDENTITY_STOPWORDS
@@ -302,7 +303,9 @@ def _attach_operation_urls(items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
return _sanitize_operation_identities(cleaned)
def get_operations_summary(limit: int = 24) -> Dict[str, Any]:
def get_operations_summary(
limit: int = 24, *, flow_mode: str | None = None, connection: Any = None,
) -> Dict[str, Any]:
"""Build the /operations work queue summary.
/operations is intentionally not a mini-dashboard. It returns a compact
@@ -315,7 +318,8 @@ def get_operations_summary(limit: int = 24) -> Dict[str, Any]:
# after canonicalization. This prevents duplicate rows from consuming the
# display limit while still avoiding an unbounded history scan.
candidate_limit = max(200, min(display_limit * 20, 1000))
with engine.begin() as conn:
active_flow_mode = str(flow_mode if flow_mode is not None else settings.blif_flow_v2_mode or "off").strip().lower()
with (nullcontext(connection) if connection is not None else engine.begin()) as conn:
counts = conn.execute(text("""
SELECT
(SELECT COUNT(*) FROM opportunities WHERE status = 'open')::int AS open_opportunities,
@@ -675,9 +679,9 @@ def get_operations_summary(limit: int = 24) -> Dict[str, Any]:
# In authoritative mode the factual projection, not a legacy task/stage,
# selects material processes that have current business work. Synthetic
# rows are read-model seeds only and are never persisted.
if str(settings.blif_flow_v2_mode or "off").strip().lower() == "authoritative":
if active_flow_mode == "authoritative":
seeded = {str(row.get("opportunity_id") or "") for row in seed_rows}
with engine.connect() as conn:
with (nullcontext(connection) if connection is not None else engine.connect()) as conn:
v2_seeds = conn.execute(text("""
SELECT p.opportunity_id::text, p.business_next_action, p.derived_at,
o.title, o.value_amount, o.currency, o.lifecycle_state,
@@ -713,7 +717,9 @@ def get_operations_summary(limit: int = 24) -> Dict[str, Any]:
row.update({key: timeline.get(key) for key in ("latest_public_inbound", "latest_public_outbound")})
normalized_rows = _normalise_work_item_intent(seed_rows)
opportunity_ids = opportunity_ids_from_work_seeds(normalized_rows)
decisions = get_opportunity_next_actions(opportunity_ids) if opportunity_ids else {}
decisions = (get_opportunity_next_actions_for_mode(
opportunity_ids, flow_mode=active_flow_mode, connection=connection,
) if opportunity_ids else {})
projection = canonicalize_operations(normalized_rows, decisions, evidence_rows=[dict(row) for row in evidence_rows])
canonical_items = _attach_operation_urls(list(projection["items"]))
partition = partition_canonical_items(canonical_items, display_limit=display_limit)
@@ -758,6 +764,11 @@ def get_operations_summary(limit: int = 24) -> Dict[str, Any]:
**diagnostics,
"projection_metrics": diagnostics,
}
if flow_mode is not None:
# Complete, untruncated read-model population for offline simulation;
# normal API/GET callers do not receive this audit-only field.
result["simulation_all_items"] = canonical_items
return result
def get_system_health_summary() -> Dict[str, Any]: