Files
microdao-daarion/tests/test_stepan_doc_ux_v37.py
Apple 129e4ea1fc 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
2026-03-03 07:14:14 -08:00

266 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
tests/test_stepan_doc_ux_v37.py
v3.7 Doc Focus UX Polish:
- build_mode_clarifier per-domain (URL, vision, fact, neutral)
- _DOC_AWARENESS_RE pattern matching
- Context bleed guard uses clarifier (not static phrase)
- state-aware doc ack ("Працюємо зі звітом." / "По звіту дивлюсь.")
- Vision intro polish (_VISION_INTRO_RE)
- "no inertia" — general mode should not produce doc phrases
"""
import re
import pytest
from crews.agromatrix_crew.doc_focus import (
build_mode_clarifier,
_VISION_INTRO_RE,
_DOC_AWARENESS_RE,
)
# ─── 1. build_mode_clarifier (v3.7 контекстне уточнення) ─────────────────────
class TestBuildModeClarifierV37:
"""build_mode_clarifier виробляє 1 питання без "!" і без "будь ласка"."""
def test_url_question(self):
c = build_mode_clarifier("https://bayer.com/catalogue")
assert "посилання" in c.lower()
assert c.endswith("?")
def test_vision_question(self):
c = build_mode_clarifier("на листі жовті плями")
assert "фото" in c.lower() or "рослин" in c.lower()
assert c.endswith("?")
def test_fact_question(self):
c = build_mode_clarifier("що з витратами на добрива?")
assert "цифри" in c.lower() or "звіт" in c.lower()
assert c.endswith("?")
def test_neutral_question(self):
c = build_mode_clarifier("що там?")
# Neutral fallback
assert "звіт" in c.lower() or "інше" in c.lower()
assert c.endswith("?")
def test_no_bud_laska(self):
for text in ["https://x.com", "листя плями", "витрати?", "привіт"]:
c = build_mode_clarifier(text)
assert "будь ласка" not in c.lower()
assert "!" not in c
def test_exactly_one_question_mark(self):
for text in ["https://x.com", "листя плями", "витрати?", "привіт"]:
c = build_mode_clarifier(text)
assert c.count("?") == 1, f"Expected 1 '?', got '{c}'"
def test_max_length(self):
for text in ["https://x.com", "листя плями", "витрати?", "привіт"]:
c = build_mode_clarifier(text)
assert len(c) <= 60, f"Clarifier too long: '{c}'"
# ─── 2. _DOC_AWARENESS_RE — заборонені UX-фрази ─────────────────────────────
class TestDocAwarenessRegex:
"""v3.7: Регекс для виявлення "Так, пам'ятаю" / "Не бачу його" тощо."""
BANNED_PHRASES = [
"Так, пам'ятаю документ.",
"Так, пам\u2019ятаю.", # curly apostrophe
"Не бачу його перед собою.",
"Не бачу його сьогодні.",
"Мені доступний документ.",
"Мені не доступний документ.",
]
ALLOWED_PHRASES = [
"Працюємо зі звітом.",
"По звіту дивлюсь.",
"Йдеться про звіт чи про інше?",
]
def test_banned_matched(self):
for phrase in self.BANNED_PHRASES:
assert _DOC_AWARENESS_RE.search(phrase), f"Should match banned: '{phrase}'"
def test_allowed_not_matched(self):
for phrase in self.ALLOWED_PHRASES:
assert not _DOC_AWARENESS_RE.search(phrase), f"Should NOT match allowed: '{phrase}'"
# ─── 3. _VISION_INTRO_RE — "На фото видно" ───────────────────────────────────
class TestVisionIntroRegex:
def test_matches_vision_intro(self):
assert _VISION_INTRO_RE.search("На фото видно жовті плями.")
def test_no_match_other(self):
assert not _VISION_INTRO_RE.search("Схоже на хлороз листя.")
assert not _VISION_INTRO_RE.search("Ймовірно брак заліза.")
# ─── 4. Context bleed guard — використовує clarifier (не статичну фразу) ─────
def _apply_bleed_guard_v37(response: str, text: str) -> tuple[str, bool]:
"""
Симуляція v3.6/v3.7 bleed guard логіки (context_mode=general):
замінює doc-фрази на build_mode_clarifier(text).
"""
_BLEED_RE = re.compile(
r"у\s+(?:цьому|наданому|даному)\s+документі"
r"\s+(?:цьому|наданому|даному)\s+документі"
r"|у\s+(?:цьому\s+)?звіті|в\s+(?:цьому\s+)?звіті",
re.IGNORECASE | re.UNICODE,
)
if _BLEED_RE.search(response):
return build_mode_clarifier(text), True
return response, False
class TestBleedGuardUseClarifier:
def test_doc_phrase_replaced_by_clarifier(self):
resp = "У цьому документі вказано прибуток 1М грн."
out, replaced = _apply_bleed_guard_v37(resp, "що там?")
assert replaced
assert "У цьому документі" not in out
assert "?" in out
def test_doc_phrase_url_context_clarifier(self):
resp = "У звіті є дані."
out, replaced = _apply_bleed_guard_v37(resp, "https://example.com")
assert replaced
assert "посилання" in out.lower()
def test_doc_phrase_vision_context_clarifier(self):
resp = "В цьому документі щось."
out, replaced = _apply_bleed_guard_v37(resp, "на листі плями")
assert replaced
assert "фото" in out.lower() or "рослин" in out.lower()
def test_clean_response_not_touched(self):
resp = "Добрива коштують 2000 грн/га."
out, replaced = _apply_bleed_guard_v37(resp, "скільки добрив?")
assert not replaced
assert out == resp
def test_case_insensitive(self):
resp = "У ЦЬОМУ ДОКУМЕНТІ нічого немає."
out, replaced = _apply_bleed_guard_v37(resp, "що там")
assert replaced
# ─── 5. State-aware doc ack симуляція ─────────────────────────────────────────
def _simulate_doc_ack(
styled_response: str,
context_mode: str,
has_explicit_doc_token: bool,
doc_just_activated: bool,
) -> str:
"""Симуляція v3.7 state-aware doc ack prefix."""
if context_mode == "doc" and doc_just_activated:
ack = "По звіту дивлюсь." if has_explicit_doc_token else "Працюємо зі звітом."
if not styled_response.startswith(ack):
return f"{ack}\n{styled_response}"
return styled_response
class TestStateAwareDocAck:
def test_explicit_token_ack(self):
out = _simulate_doc_ack(
"Прибуток склав 500 тис грн.", "doc", True, True
)
assert out.startswith("По звіту дивлюсь.")
def test_general_doc_ack(self):
out = _simulate_doc_ack(
"Прибуток склав 500 тис грн.", "doc", False, True
)
assert out.startswith("Працюємо зі звітом.")
def test_no_ack_if_focus_was_already_active(self):
# doc_just_activated=False — фокус вже був активний
out = _simulate_doc_ack(
"Прибуток склав 500 тис грн.", "doc", True, False
)
assert not out.startswith("По звіту дивлюсь.")
assert out == "Прибуток склав 500 тис грн."
def test_no_ack_in_general_mode(self):
out = _simulate_doc_ack(
"Гаразд, зрозуміло.", "general", True, True
)
assert out == "Гаразд, зрозуміло."
def test_no_duplicate_ack(self):
ack = "Працюємо зі звітом."
out = _simulate_doc_ack(ack + "\nДані доступні.", "doc", False, True)
assert out.count(ack) == 1
# ─── 6. UX awareness phrase suppression симуляція ────────────────────────────
def _apply_awareness_guard(response: str, text: str) -> tuple[str, bool]:
"""Симуляція v3.7 awareness phrase guard."""
if _DOC_AWARENESS_RE.search(response):
replaced = re.sub(
_DOC_AWARENESS_RE,
lambda m: build_mode_clarifier(text),
response,
count=1,
)
return replaced, True
return response, False
class TestAwarenessGuard:
def test_pamyatayu_replaced(self):
resp = "Так, пам'ятаю документ який ви надіслали."
out, replaced = _apply_awareness_guard(resp, "що там?")
assert replaced
assert "пам" not in out.lower() or "пам'ятаю" not in out.lower()
def test_ne_bachu_replaced(self):
resp = "Не бачу його перед собою, надішліть ще раз."
out, replaced = _apply_awareness_guard(resp, "листя плями")
assert replaced
assert "фото" in out.lower() or "рослин" in out.lower()
def test_clean_response_unchanged(self):
resp = "Прибуток у першому кварталі склав 1.2М грн."
out, replaced = _apply_awareness_guard(resp, "прибуток?")
assert not replaced
assert out == resp
def test_only_first_occurrence_replaced(self):
"""count=1 — замінюємо тільки перше входження."""
resp = "Так, пам'ятаю. Не бачу його."
out, replaced = _apply_awareness_guard(resp, "що там?")
assert replaced
# Тільки перше замінено — друге може лишитись (count=1)
assert out.count("?") >= 1
# ─── 7. Vision intro polish ───────────────────────────────────────────────────
class TestVisionIntroPhrases:
GOOD_STARTS = [
"Схоже на хлороз листя.",
"Ймовірно дефіцит мікроелементів.",
"Виглядає як рання стадія хвороби.",
]
BAD_STARTS = [
"На фото видно жовтіння листя.",
]
def test_good_vision_intros_not_blocked(self):
for phrase in self.GOOD_STARTS:
assert not _VISION_INTRO_RE.search(phrase), f"False positive: '{phrase}'"
def test_bad_vision_intro_blocked(self):
for phrase in self.BAD_STARTS:
assert _VISION_INTRO_RE.search(phrase), f"Not blocked: '{phrase}'"