181 lines
7.7 KiB
Python
181 lines
7.7 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
|
import generate_manager_brief as gmb
|
|
|
|
|
|
class ManagerBriefTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.context = {
|
|
"portfolio_summary": {
|
|
"companies_total": 41,
|
|
"critical_total": 10,
|
|
"high_total": 20,
|
|
"medium_total": 5,
|
|
"low_total": 3,
|
|
"none_total": 3,
|
|
"direct_total": 33,
|
|
"alias_total": 5,
|
|
"manual_total": 3,
|
|
"unmatched_total": 0,
|
|
"stale_7d_total": 4,
|
|
"stale_14d_total": 2,
|
|
"busy_total": 7,
|
|
"activity_30d_total": 12345.0,
|
|
"activity_forecast_30d_total": 54321.0,
|
|
"open_cases_total": 12,
|
|
"detections_total": 20,
|
|
},
|
|
"freshness": [
|
|
{"source": "documents_ts", "stale": False},
|
|
{"source": "signals_ts", "stale": True},
|
|
],
|
|
"top_risks": [
|
|
{
|
|
"counterparty": "ФЕЛИЦТ ГРУПП 2026",
|
|
"signal_severity": "critical",
|
|
"top_signal": "Есть открытые кейсы",
|
|
"open_cases_total": 2,
|
|
"detections_total": 5,
|
|
"active_locks": 3,
|
|
"amount_30d": 100.0,
|
|
"amount_forecast_30d": 300.0,
|
|
}
|
|
],
|
|
"top_forecasts": [
|
|
{
|
|
"counterparty": "АВКО 2026",
|
|
"amount_30d": 1000.0,
|
|
"amount_forecast_30d": 3000.0,
|
|
"registry_match_mode": "direct",
|
|
"signal_severity": "high",
|
|
}
|
|
],
|
|
"watchlist": [{"counterparty": "АВКО 2026"}],
|
|
}
|
|
|
|
def test_deterministic_payload_shape(self) -> None:
|
|
payload = gmb.render_deterministic_payload(self.context)
|
|
self.assertIn("headline", payload)
|
|
self.assertGreaterEqual(len(payload["summary"]), 3)
|
|
self.assertGreaterEqual(len(payload["manager_questions"]), 4)
|
|
self.assertGreaterEqual(len(payload["management_plan"]), 3)
|
|
self.assertEqual(payload["manager_questions"][0]["question"], "Что происходит сейчас?")
|
|
self.assertEqual(payload["management_plan"][0]["horizon"], "today")
|
|
self.assertEqual(payload["top_risks"][0]["company"], "ФЕЛИЦТ ГРУПП 2026")
|
|
self.assertIn("manual", " ".join(payload["caveats"]).lower())
|
|
self.assertTrue(any("прибыл" in item.lower() for item in payload["caveats"]))
|
|
|
|
def test_markdown_render(self) -> None:
|
|
payload = gmb.render_deterministic_payload(self.context)
|
|
md = gmb.render_markdown(payload, "2026-05-22T12:00:00+00:00")
|
|
self.assertIn("# Executive Brief 1C", md)
|
|
self.assertIn("## Простые ответы для руководителя", md)
|
|
self.assertIn("## План действий руководителя", md)
|
|
self.assertIn("## Компании риска", md)
|
|
self.assertIn("Есть ли риск падения прибыли в ближайшие 30 дней?", md)
|
|
self.assertIn("Что делать сегодня?", md)
|
|
self.assertIn("ФЕЛИЦТ ГРУПП 2026", md)
|
|
|
|
def test_normalize_brief_payload_adds_profit_question_and_plan(self) -> None:
|
|
payload = {
|
|
"headline": "Тест",
|
|
"summary": ["a", "b", "c"],
|
|
"manager_questions": [
|
|
{
|
|
"question": "Что происходит сейчас?",
|
|
"answer": "Есть нагрузка.",
|
|
"recommended_action": "Сфокусироваться на top-компаниях.",
|
|
}
|
|
],
|
|
"management_plan": [
|
|
{
|
|
"horizon": "today",
|
|
"focus": "Снять острый риск.",
|
|
"action": "Разобрать top-5.",
|
|
"expected_effect": "Остановить рост проблем.",
|
|
"metric": "open_cases_total",
|
|
}
|
|
],
|
|
"top_risks": [],
|
|
"top_forecasts": [],
|
|
"actions": ["Разобрать top-5."],
|
|
"caveats": ["Operational severity не равна финансам."],
|
|
}
|
|
normalized = gmb.normalize_brief_payload(payload, self.context)
|
|
self.assertTrue(any("прибыл" in item["question"].lower() for item in normalized["manager_questions"]))
|
|
self.assertEqual([item["horizon"] for item in normalized["management_plan"]], ["today", "week", "30d"])
|
|
self.assertTrue(any("прибыл" in item.lower() for item in normalized["caveats"]))
|
|
|
|
def test_compute_delta_context(self) -> None:
|
|
previous_artifact = {
|
|
"generated_at": "2026-05-22T09:00:00+00:00",
|
|
"context": {
|
|
"generated_at": "2026-05-22T09:00:00+00:00",
|
|
"portfolio_summary": {
|
|
"companies_total": 40,
|
|
"critical_total": 8,
|
|
"high_total": 18,
|
|
"busy_total": 6,
|
|
"open_cases_total": 10,
|
|
"detections_total": 18,
|
|
"activity_30d_total": 12000.0,
|
|
"activity_forecast_30d_total": 50000.0,
|
|
},
|
|
"watchlist": [{"infobase": "ИБ1", "counterparty": "СТАРАЯ КОМПАНИЯ"}],
|
|
"portfolio_snapshot": [
|
|
{
|
|
"infobase": "ИБ1",
|
|
"counterparty": "ФЕЛИЦТ ГРУПП 2026",
|
|
"normalized_counterparty": "ФЕЛИЦТ ГРУПП",
|
|
"registry_match_mode": "manual",
|
|
"signal_severity": "high",
|
|
"signal_score": 70,
|
|
"current_status": "idle",
|
|
"active_locks": 1,
|
|
"days_since_last_activity": 0,
|
|
"amount_30d": 100.0,
|
|
"amount_forecast_30d": 200.0,
|
|
"open_cases_total": 1,
|
|
"detections_total": 2,
|
|
}
|
|
],
|
|
},
|
|
}
|
|
current = dict(self.context)
|
|
current["generated_at"] = "2026-05-22T12:00:00+00:00"
|
|
current["portfolio_snapshot"] = [
|
|
{
|
|
"infobase": "ИБ1",
|
|
"counterparty": "ФЕЛИЦТ ГРУПП 2026",
|
|
"normalized_counterparty": "ФЕЛИЦТ ГРУПП",
|
|
"registry_match_mode": "manual",
|
|
"signal_severity": "critical",
|
|
"signal_score": 95,
|
|
"current_status": "busy",
|
|
"active_locks": 3,
|
|
"days_since_last_activity": 0,
|
|
"amount_30d": 100.0,
|
|
"amount_forecast_30d": 150.0,
|
|
"open_cases_total": 4,
|
|
"detections_total": 5,
|
|
}
|
|
]
|
|
delta = gmb.compute_delta_context(current, previous_artifact)
|
|
self.assertTrue(delta["available"])
|
|
self.assertEqual(delta["summary"]["critical_total_delta"], 2)
|
|
self.assertIn("ФЕЛИЦТ ГРУПП 2026", delta["new_critical"])
|
|
self.assertEqual(delta["top_changes"][0]["change_type"], "severity_up")
|
|
self.assertIn("priority_score", delta["top_changes"][0])
|
|
self.assertIn("priority_tier", delta["top_changes"][0])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|