Files
microdao-daarion/tests/test_stepan_v4_farm_state.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

288 lines
12 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_v4_farm_state.py
v4 Farm State Layer:
- detect_farm_state_updates (crop, stage, issue, risk)
- update_farm_state (session merge, dedup, TTL)
- build_farm_state_prefix (format, empty cases, TTL)
- Isolation: prefix NOT added in doc/web mode
- State persists within session
"""
import time
import pytest
from crews.agromatrix_crew.farm_state import (
detect_farm_state_updates,
update_farm_state,
build_farm_state_prefix,
FARM_STATE_TTL,
)
# ─── 1. detect_farm_state_updates ────────────────────────────────────────────
class TestDetectFarmStateUpdates:
def test_crop_kukurudza(self):
u = detect_farm_state_updates("По кукурудзі що робити далі?")
assert u.get("current_crop") == "кукурудза"
def test_crop_inflection_kukurudzu(self):
u = detect_farm_state_updates("Переглянь стан кукурудзу на полі")
assert u.get("current_crop") == "кукурудза"
def test_crop_wheat(self):
u = detect_farm_state_updates("Пшениця виглядає кволою")
assert u.get("current_crop") == "пшениця"
def test_crop_sunflower(self):
u = detect_farm_state_updates("соняшник V6 посуха")
assert u.get("current_crop") == "соняшник"
def test_crop_rapeseed(self):
u = detect_farm_state_updates("ріпак — йде цвітіння")
assert u.get("current_crop") == "ріпак"
def test_stage_v6(self):
u = detect_farm_state_updates("Стадія V6, є жовтизна")
assert u.get("growth_stage") == "V6"
def test_stage_kushennya(self):
u = detect_farm_state_updates("пшениця, фаза кущення")
assert "кущення" in u.get("growth_stage", "").lower()
def test_stage_flowering(self):
u = detect_farm_state_updates("ріпак іде на цвітіння")
assert "цвітіння" in u.get("growth_stage", "").lower()
def test_issue_zhovtyzna(self):
u = detect_farm_state_updates("є жовтизна на листі")
assert "жовтизна" in u.get("recent_issue", "").lower()
def test_issue_deficit(self):
u = detect_farm_state_updates("дефіцит азоту у посівах")
assert "дефіцит" in u.get("recent_issue", "").lower()
def test_issue_shkidnyk(self):
u = detect_farm_state_updates("з'явились шкідники на кукурудзі")
assert "шкідник" in u.get("recent_issue", "").lower()
def test_risk_posuxa(self):
u = detect_farm_state_updates("на полі посуха вже 2 тижні")
assert "посуха" in u.get("risk_flags", [])
def test_risk_zamorozok(self):
u = detect_farm_state_updates("очікується заморозок вночі")
assert any("заморозок" in r for r in u.get("risk_flags", []))
def test_multiple_fields(self):
u = detect_farm_state_updates("кукурудза V6, дефіцит азоту, посуха")
assert u.get("current_crop") == "кукурудза"
assert "V6" in u.get("growth_stage", "")
assert "дефіцит" in u.get("recent_issue", "").lower()
assert any("посуха" in r for r in u.get("risk_flags", []))
def test_empty_text(self):
u = detect_farm_state_updates("")
assert u == {}
def test_no_match(self):
u = detect_farm_state_updates("привіт, як справи сьогодні?")
assert u == {}
def test_fail_safe_garbage(self):
u = detect_farm_state_updates(None) # type: ignore
assert u == {}
# ─── 2. update_farm_state ─────────────────────────────────────────────────────
class TestUpdateFarmState:
def test_creates_farm_state(self):
session = {}
update_farm_state(session, {"current_crop": "кукурудза"})
assert session["farm_state"]["current_crop"] == "кукурудза"
def test_sets_last_update_ts(self):
now = time.time()
session = {}
update_farm_state(session, {"current_crop": "соняшник"}, now_ts=now)
assert abs(session["farm_state"]["last_update_ts"] - now) < 1
def test_updates_existing(self):
session = {"farm_state": {"current_crop": "пшениця", "last_update_ts": 0.0}}
update_farm_state(session, {"current_crop": "кукурудза"})
assert session["farm_state"]["current_crop"] == "кукурудза"
def test_merges_risk_flags(self):
session = {"farm_state": {"risk_flags": ["посуха"], "last_update_ts": 0.0}}
update_farm_state(session, {"risk_flags": ["заморозок"]})
flags = session["farm_state"]["risk_flags"]
assert "посуха" in flags
assert "заморозок" in flags
def test_dedup_risk_flags(self):
session = {"farm_state": {"risk_flags": ["посуха", "спека"], "last_update_ts": 0.0}}
update_farm_state(session, {"risk_flags": ["посуха"]})
flags = session["farm_state"]["risk_flags"]
assert flags.count("посуха") == 1
def test_risk_flags_max_5(self):
session = {"farm_state": {"risk_flags": ["r1", "r2", "r3", "r4"], "last_update_ts": 0.0}}
update_farm_state(session, {"risk_flags": ["r5", "r6"]})
assert len(session["farm_state"]["risk_flags"]) <= 5
def test_empty_updates_noop(self):
session = {"farm_state": {"current_crop": "ріпак", "last_update_ts": 0.0}}
update_farm_state(session, {})
assert session["farm_state"]["current_crop"] == "ріпак"
def test_fail_safe_exception(self):
# session = None → fail-safe, no exception
update_farm_state(None, {"current_crop": "соя"}) # type: ignore
# ─── 3. build_farm_state_prefix ──────────────────────────────────────────────
class TestBuildFarmStatePrefix:
def _session_with_state(self, now: float, **kwargs) -> dict:
fs = {"last_update_ts": now}
fs.update(kwargs)
return {"farm_state": fs}
def test_empty_if_no_crop(self):
now = time.time()
s = self._session_with_state(now, growth_stage="V6")
assert build_farm_state_prefix(s) == ""
def test_basic_prefix(self):
now = time.time()
s = self._session_with_state(now, current_crop="кукурудза")
p = build_farm_state_prefix(s)
assert "кукурудза" in p
assert "[Контекст господарства]" in p
def test_includes_stage(self):
now = time.time()
s = self._session_with_state(now, current_crop="кукурудза", growth_stage="V6")
p = build_farm_state_prefix(s)
assert "V6" in p
assert "Стадія" in p
def test_includes_issue(self):
now = time.time()
s = self._session_with_state(now, current_crop="кукурудза", recent_issue="жовтизна")
p = build_farm_state_prefix(s)
assert "жовтизна" in p
assert "Проблема" in p
def test_includes_risks(self):
now = time.time()
s = self._session_with_state(now, current_crop="соняшник", risk_flags=["посуха", "спека"])
p = build_farm_state_prefix(s)
assert "посуха" in p
assert "Ризики" in p
def test_max_lines(self):
now = time.time()
s = self._session_with_state(
now,
current_crop="кукурудза",
growth_stage="V6",
recent_issue="жовтизна",
risk_flags=["посуха", "заморозок"],
)
p = build_farm_state_prefix(s)
assert len(p.splitlines()) <= 5
def test_empty_if_ttl_expired(self):
old_ts = time.time() - FARM_STATE_TTL - 10
s = self._session_with_state(old_ts, current_crop="кукурудза")
assert build_farm_state_prefix(s) == ""
def test_empty_if_no_farm_state(self):
assert build_farm_state_prefix({}) == ""
def test_fail_safe(self):
assert build_farm_state_prefix(None) == "" # type: ignore
# ─── 4. State persists within session ────────────────────────────────────────
class TestFarmStatePersistence:
def test_crop_persists_across_messages(self):
session = {}
# Перше повідомлення — встановлює crop
u1 = detect_farm_state_updates("По кукурудзі є дефіцит азоту")
update_farm_state(session, u1)
# Друге повідомлення — без crop, але є farm_state
u2 = detect_farm_state_updates("Що робити?")
update_farm_state(session, u2)
# Стан зберігся
assert session["farm_state"]["current_crop"] == "кукурудза"
def test_issue_updates_on_new_message(self):
session = {}
update_farm_state(session, {"current_crop": "пшениця", "recent_issue": "жовтизна"})
update_farm_state(session, {"recent_issue": "іржа"})
assert session["farm_state"]["recent_issue"] == "іржа"
def test_stage_updates(self):
session = {}
update_farm_state(session, {"current_crop": "кукурудза", "growth_stage": "V4"})
update_farm_state(session, {"growth_stage": "V6"})
assert session["farm_state"]["growth_stage"] == "V6"
# ─── 5. Ізоляція: prefix NOT в doc / web mode ─────────────────────────────────
def _would_inject(context_mode: str, domain: str, session: dict) -> bool:
"""Симуляція умови ін'єкції з run.py."""
if context_mode == "doc" or domain == "web":
return False
return bool(build_farm_state_prefix(session))
class TestFarmStatePrefixIsolation:
def _fresh_session(self) -> dict:
now = time.time()
session = {}
update_farm_state(session, {"current_crop": "кукурудза"}, now_ts=now)
return session
def test_not_injected_in_doc_mode(self):
s = self._fresh_session()
assert not _would_inject("doc", "doc", s)
def test_not_injected_in_web_domain(self):
s = self._fresh_session()
assert not _would_inject("general", "web", s)
def test_injected_in_general_mode(self):
s = self._fresh_session()
assert _would_inject("general", "general", s)
def test_injected_in_vision_domain(self):
s = self._fresh_session()
assert _would_inject("general", "vision", s)
def test_not_injected_if_no_crop(self):
s = {"farm_state": {"growth_stage": "V6", "last_update_ts": time.time()}}
assert not _would_inject("general", "general", s)
def test_not_injected_if_ttl_expired(self):
old_ts = time.time() - FARM_STATE_TTL - 60
s = {"farm_state": {"current_crop": "кукурудза", "last_update_ts": old_ts}}
assert not _would_inject("general", "general", s)
# ─── 6. FARM_STATE_TTL constant ──────────────────────────────────────────────
class TestConstants:
def test_farm_state_ttl_30min(self):
assert FARM_STATE_TTL == 1800.0