From 798bbd9a7b20b47f678a059ea2b954e9301eeb61 Mon Sep 17 00:00:00 2001 From: igor04091968 Date: Fri, 22 May 2026 15:28:43 +0300 Subject: [PATCH] feat(1c): add brief delta analysis and changes page --- clickhouse-1c/ai/company_intelligence_api.py | 197 +++++++++++++- clickhouse-1c/ai/generate_manager_brief.py | 248 ++++++++++++++++++ clickhouse-1c/ai/manager_brief_prompt.md | 1 + .../ai/test_company_intelligence_api.py | 43 +++ clickhouse-1c/ai/test_manager_brief.py | 60 +++++ 5 files changed, 548 insertions(+), 1 deletion(-) diff --git a/clickhouse-1c/ai/company_intelligence_api.py b/clickhouse-1c/ai/company_intelligence_api.py index 069e05b..a3a3c95 100644 --- a/clickhouse-1c/ai/company_intelligence_api.py +++ b/clickhouse-1c/ai/company_intelligence_api.py @@ -98,6 +98,13 @@ def load_brief_history_record(name: str) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8")) +def extract_delta(payload: dict[str, Any]) -> dict[str, Any]: + delta = payload.get("context", {}).get("delta") + if isinstance(delta, dict): + return delta + return {"available": False, "reason": "delta not present in artifact"} + + def grafana_company_dashboard_url() -> str: return os.getenv( "AW_1C_MANAGER_BRIEF_GRAFANA_URL", @@ -151,6 +158,7 @@ def render_manager_brief_html(payload: dict[str, Any]) -> str: generated_at = payload.get("generated_at", "") history_url = "/api/1/analytics-1c/manager/brief/history" history_html_url = "/manager/briefs" + delta_html_url = "/manager/changes" problematic_1d_url = "/manager/problematic?days=1" problematic_7d_url = "/manager/problematic?days=7" json_url = "/api/1/analytics-1c/manager/brief/latest" @@ -448,6 +456,7 @@ def render_manager_brief_html(payload: dict[str, Any]) -> str: JSON Markdown История brief + Что изменилось Проблемные 1д Проблемные 7д History API @@ -605,6 +614,7 @@ def render_brief_history_html(items: list[dict[str, Any]]) -> str: f"{html.escape(str(item.get('render_mode', '-')))}" f"{html.escape(str(item.get('model') or '-'))}" f"{html.escape(str(item.get('headline') or '-'))}" + f"Изменения" f"JSON" "" ) @@ -640,6 +650,7 @@ def render_brief_history_html(items: list[dict[str, Any]]) -> str:

История executive brief

@@ -652,11 +663,12 @@ def render_brief_history_html(items: list[dict[str, Any]]) -> str: Режим Модель Headline + Delta Raw - {''.join(rows) or 'История пока пуста.'} + {''.join(rows) or 'История пока пуста.'} @@ -757,6 +769,167 @@ def render_problematic_companies_html(items: list[dict[str, Any]], days: int) -> """ +def render_brief_delta_html(payload: dict[str, Any]) -> str: + delta = extract_delta(payload) + brief = payload.get("brief", {}) + if not delta.get("available"): + return f""" + +1C Brief Changes
+

Что изменилось с прошлого запуска

Delta пока недоступна: {html.escape(str(delta.get('reason', 'unknown')))}.

+

Вернуться к brief

""" + + summary = delta.get("summary", {}) + top_changes = delta.get("top_changes", []) + new_critical = delta.get("new_critical", []) + resolved_critical = delta.get("resolved_critical", []) + entered_watchlist = delta.get("entered_watchlist", []) + left_watchlist = delta.get("left_watchlist", []) + + def delta_value(value: Any) -> str: + try: + numeric = float(value) + except (TypeError, ValueError): + return html.escape(str(value)) + prefix = "+" if numeric > 0 else "" + if numeric.is_integer(): + return prefix + str(int(numeric)) + return prefix + f"{numeric:.2f}".replace(".", ",") + + stat_cards = [ + ("Critical", delta_value(summary.get("critical_total_delta", 0))), + ("Busy", delta_value(summary.get("busy_total_delta", 0))), + ("Кейсы", delta_value(summary.get("open_cases_total_delta", 0))), + ("Detections", delta_value(summary.get("detections_total_delta", 0))), + ("Активность 30д", delta_value(summary.get("activity_30d_total_delta", 0))), + ("Прогноз 30д", delta_value(summary.get("activity_forecast_30d_total_delta", 0))), + ] + stat_html = "".join( + f"
{html.escape(label)}
{html.escape(value)}
" + for label, value in stat_cards + ) + + rows = [] + for item in top_changes: + infobase = item.get("infobase") + counterparty = item.get("company") + rows.append( + "" + f"{html.escape(str(counterparty or '-'))}" + f"{html.escape(str(item.get('change_type') or '-'))}" + f"{html.escape(str(item.get('severity_before') or '-'))} -> {html.escape(str(item.get('severity_after') or '-'))}" + f"{delta_value(item.get('score_delta', 0))}" + f"{delta_value(item.get('open_cases_delta', 0))}" + f"{delta_value(item.get('active_locks_delta', 0))}" + f"{delta_value(item.get('forecast_delta', 0))}" + f"{html.escape(str(item.get('summary') or '-'))}" + "" + ) + + def as_list(items: list[str]) -> str: + if not items: + return "
  • Нет
  • " + return "".join(f"
  • {html.escape(str(item))}
  • " for item in items) + + return f""" + + + + + + 1C Brief Changes + + + +
    +
    +

    Что изменилось с прошлого запуска

    +

    Сравнение between briefs: {html.escape(str(delta.get('previous_generated_at') or '-'))} -> {html.escape(str(delta.get('current_generated_at') or payload.get('generated_at') or '-'))}.

    + +
    + +
    +
    +

    Сводка изменений

    +
    {stat_html}
    +
    + +
    +

    Новые critical

    +
      {as_list(new_critical)}
    +
    + +
    +

    Вышли из critical

    +
      {as_list(resolved_critical)}
    +
    + +
    +

    Зашли в watchlist

    +
      {as_list(entered_watchlist)}
    +
    + +
    +

    Вышли из watchlist

    +
      {as_list(left_watchlist)}
    +
    + +
    +

    Top changes today

    + + + + + + + + + + + + + + + {''.join(rows) or ''} + +
    КомпанияТипSeverityScore ΔCases ΔLocks ΔForecast ΔКомментарий
    Нет выраженных изменений.
    +
    +
    +
    + +""" + + app = FastAPI(title="AW-rus 1C Company Intelligence API", version="1.0.0") @@ -967,6 +1140,17 @@ def manager_brief_history_record(name: str) -> dict[str, Any]: return load_brief_history_record(name) +@app.get("/api/1/analytics-1c/manager/brief/delta/latest") +def manager_brief_delta_latest() -> dict[str, Any]: + payload = load_latest_manager_brief() + delta = extract_delta(payload) + return { + "generated_at": payload.get("generated_at"), + "headline": payload.get("brief", {}).get("headline"), + "delta": delta, + } + + @app.get("/api/1/analytics-1c/companies/problematic") def problematic_companies_api( days: int = Query(default=7, ge=1, le=30), @@ -981,6 +1165,11 @@ def manager_brief_view() -> str: return render_manager_brief_html(load_latest_manager_brief()) +@app.get("/manager/changes", response_class=HTMLResponse) +def manager_brief_delta_view() -> str: + return render_brief_delta_html(load_latest_manager_brief()) + + @app.get("/manager/briefs", response_class=HTMLResponse) def manager_brief_history_view(limit: int = Query(default=40, ge=1, le=200)) -> str: return render_brief_history_html(load_brief_history_records(limit)) @@ -991,6 +1180,11 @@ def manager_brief_history_detail_view(name: str) -> str: return render_manager_brief_html(load_brief_history_record(name)) +@app.get("/manager/briefs/{name}/changes", response_class=HTMLResponse) +def manager_brief_history_delta_view(name: str) -> str: + return render_brief_delta_html(load_brief_history_record(name)) + + @app.get("/manager/problematic", response_class=HTMLResponse) def manager_problematic_companies_view( days: int = Query(default=7, ge=1, le=30), @@ -1189,6 +1383,7 @@ def render_company_detail_html(summary_payload: dict[str, Any], infobase: str |