feat(platform): add new services, tools, tests and crews modules

New router intelligence modules (26 files): alert_ingest/store, audit_store,
architecture_pressure, backlog_generator/store, cost_analyzer, data_governance,
dependency_scanner, drift_analyzer, incident_* (5 files), llm_enrichment,
platform_priority_digest, provider_budget, release_check_runner, risk_* (6 files),
signature_state_store, sofiia_auto_router, tool_governance

New services:
- sofiia-console: Dockerfile, adapters/, monitor/nodes/ops/voice modules, launchd, react static
- memory-service: integration_endpoints, integrations, voice_endpoints, static UI
- aurora-service: full app suite (analysis, job_store, orchestrator, reporting, schemas, subagents)
- sofiia-supervisor: new supervisor service
- aistalk-bridge-lite: Telegram bridge lite
- calendar-service: CalDAV calendar service with reminders
- mlx-stt-service / mlx-tts-service: Apple Silicon speech services
- binance-bot-monitor: market monitor service
- node-worker: STT/TTS memory providers

New tools (9): agent_email, browser_tool, contract_tool, observability_tool,
oncall_tool, pr_reviewer_tool, repo_tool, safe_code_executor, secure_vault

New crews: agromatrix_crew (10 modules: depth_classifier, doc_facts, doc_focus,
farm_state, light_reply, llm_factory, memory_manager, proactivity, reflection_engine,
session_context, style_adapter, telemetry)

Tests: 85+ test files for all new modules
Made-with: Cursor
This commit is contained in:
Apple
2026-03-03 07:14:14 -08:00
parent e9dedffa48
commit 129e4ea1fc
241 changed files with 69349 additions and 0 deletions

View File

@@ -0,0 +1,211 @@
"""
Тести для Human Light Reply v2:
- greeting без crew
- "дякую" → коротка відповідь (< 40 символів)
- greeting + last_topic → відповідь містить topic
- ack/thanks → без питань
- seeded randomness → стабільна для одного user_id
- short_followup → без action verbs
"""
import sys
from pathlib import Path
root = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(root))
sys.path.insert(0, str(root / 'packages' / 'agromatrix-tools'))
from crews.agromatrix_crew.light_reply import (
build_light_reply,
classify_light_event,
_is_short_followup,
_seeded_rng,
_topic_label,
)
# ─── classify_light_event ────────────────────────────────────────────────────
def test_greeting_classified():
assert classify_light_event("привіт", None) == "greeting"
assert classify_light_event("Привіт!", None) == "greeting"
assert classify_light_event("добрий ранок", None) == "greeting"
assert classify_light_event("hello", None) == "greeting"
assert classify_light_event("вітаю", None) == "greeting"
def test_thanks_classified():
assert classify_light_event("дякую", None) == "thanks"
assert classify_light_event("Дякую!", None) == "thanks"
assert classify_light_event("спасибі", None) == "thanks"
assert classify_light_event("велике дякую", None) == "thanks"
def test_ack_classified():
assert classify_light_event("ок", None) == "ack"
assert classify_light_event("Ок.", None) == "ack"
assert classify_light_event("зрозумів", None) == "ack"
assert classify_light_event("добре", None) == "ack"
assert classify_light_event("чудово", None) == "ack"
assert classify_light_event("так", None) == "ack"
def test_short_followup_classified():
assert classify_light_event("а на завтра?", "plan_day") == "short_followup"
assert classify_light_event("а по полю 12?", "plan_day") == "short_followup"
def test_action_verb_not_followup():
# "зроби план" has action verb → not short_followup
assert classify_light_event("зроби план на завтра", "plan_day") != "short_followup"
def test_short_followup_no_topic():
# No last_topic → not short_followup
result = classify_light_event("а на завтра?", None)
assert result != "short_followup"
def test_deep_input_not_light_event():
# Long operational query → None
result = classify_light_event("сплануй тиждень по полях 1, 2, 3 для пшениці", "plan_day")
assert result is None
# ─── build_light_reply ───────────────────────────────────────────────────────
def test_greeting_no_crew_path():
"""build_light_reply повертає рядок без запуску будь-яких crew."""
profile = {"user_id": "u1", "name": None, "last_topic": None}
reply = build_light_reply("привіт", profile)
assert reply is not None
assert isinstance(reply, str)
assert len(reply) > 0
def test_greeting_with_name():
profile = {"user_id": "u1", "name": "Іван", "last_topic": None}
reply = build_light_reply("привіт", profile)
assert reply is not None
assert "Іван" in reply
def test_greeting_with_last_topic_contains_topic():
"""Якщо є last_topic — відповідь на привітання містить назву теми (case-insensitive)."""
profile = {"user_id": "u2", "name": None, "last_topic": "plan_day"}
reply = build_light_reply("привіт", profile)
assert reply is not None
label = _topic_label("plan_day")
assert label.lower() in reply.lower(), f"Expected '{label}' (case-insensitive) in reply: {reply!r}"
def test_thanks_short():
"""Відповідь на 'дякую' коротша за 40 символів."""
profile = {"user_id": "u3", "name": None, "last_topic": None}
reply = build_light_reply("дякую", profile)
assert reply is not None
assert len(reply) < 40, f"Reply too long: {reply!r}"
def test_thanks_no_question_mark():
"""Відповідь на 'дякую' не містить питання."""
profile = {"user_id": "u3", "name": None, "last_topic": None}
reply = build_light_reply("дякую", profile)
assert reply is not None
assert "?" not in reply, f"Should not ask a question: {reply!r}"
def test_ack_short():
profile = {"user_id": "u4", "name": None, "last_topic": None}
reply = build_light_reply("зрозумів", profile)
assert reply is not None
assert len(reply) < 40, f"Ack reply too long: {reply!r}"
def test_ack_no_question_mark():
profile = {"user_id": "u4", "name": None, "last_topic": None}
reply = build_light_reply("ок", profile)
assert reply is not None
assert "?" not in reply, f"Ack should not ask question: {reply!r}"
def test_short_followup_contains_topic():
profile = {"user_id": "u5", "name": None, "last_topic": "plan_vs_fact"}
reply = build_light_reply("а на завтра?", profile)
assert reply is not None
label = _topic_label("plan_vs_fact")
assert label in reply, f"Expected topic in followup reply: {reply!r}"
def test_offtopic_returns_none_or_str():
"""Для нечіткого запиту або не-light-event build_light_reply повертає None."""
profile = {"user_id": "u6", "name": None, "last_topic": None}
# "що робити з трактором" — не greeting/thanks/ack/followup
reply = build_light_reply("що робити з трактором взагалі", profile)
# Should be None (falls back to LLM) since no clear light category
assert reply is None
def test_seeded_stable_same_user():
"""Одному user_id — завжди та ж відповідь (стабільний seed)."""
profile = {"user_id": "stable_user_123", "name": None, "last_topic": None}
r1 = build_light_reply("привіт", profile)
r2 = build_light_reply("привіт", profile)
assert r1 == r2, f"Should be deterministic: {r1!r} != {r2!r}"
def test_seeded_different_users():
"""Різні user_id можуть давати різні відповіді (не обов'язково, але перевіряємо що обидва рядки)."""
p1 = {"user_id": "user_aaa", "name": None, "last_topic": None}
p2 = {"user_id": "user_zzz", "name": None, "last_topic": None}
r1 = build_light_reply("привіт", p1)
r2 = build_light_reply("привіт", p2)
# Both must be non-None strings
assert r1 is not None and r2 is not None
assert isinstance(r1, str) and isinstance(r2, str)
def test_no_chekim_dopomohty():
"""Жоден варіант не містить 'чим можу допомогти'."""
profile = {"user_id": "u7", "name": None, "last_topic": None}
for greeting in ["привіт", "добрий ранок", "hello"]:
reply = build_light_reply(greeting, profile)
if reply:
assert "чим можу допомогти" not in reply.lower(), f"Forbidden phrase in: {reply!r}"
assert "чим допомогти" not in reply.lower()
def test_no_farewell_script():
"""Відповідь на 'дякую' або 'ок' не містить шаблонних вступів."""
profile = {"user_id": "u8", "name": None, "last_topic": None}
for text in ["дякую", "спасибі", "ок", "зрозумів"]:
reply = build_light_reply(text, profile)
if reply:
for forbidden in ["звісно", "чудово!", "дозвольте", "я радий"]:
assert forbidden not in reply.lower(), f"Forbidden phrase '{forbidden}' in: {reply!r}"
# ─── _is_short_followup ──────────────────────────────────────────────────────
def test_short_followup_tomorrow():
assert _is_short_followup("а на завтра?", "plan_day") is True
def test_short_followup_with_action_verb_is_false():
assert _is_short_followup("зроби план на завтра", "plan_day") is False
def test_short_followup_no_topic_is_false():
assert _is_short_followup("а на завтра?", None) is False
def test_short_followup_long_text_is_false():
long_text = "а що буде на завтра по всіх полях господарства?"
assert _is_short_followup(long_text, "plan_day") is False
def test_seeded_rng_stable():
rng1 = _seeded_rng("user_abc")
rng2 = _seeded_rng("user_abc")
choices1 = [rng1.randint(0, 100) for _ in range(5)]
choices2 = [rng2.randint(0, 100) for _ in range(5)]
assert choices1 == choices2