system-prompts-and-models-o.../dealix/auto_client_acquisition/v3/memory.py
Dealix Builder e34cc729aa feat(dealix): py3.10/3.11 compat shim + 54 unit tests for business/innovation/ai
PROBLEM
The codebase used Python 3.11+ stdlib features (`from datetime import UTC`,
`from enum import StrEnum`) in 22 files, breaking local dev on Python 3.10
(Windows users) and any pytest run that imports the affected modules.

SOLUTION
1. New `core/_py_compat.py` providing UTC + StrEnum shims that:
   - On 3.11+ re-export the stdlib names (zero overhead)
   - On 3.10 fall back to `timezone.utc` and a (str, Enum) backport

2. All 22 affected files patched to import from the shim:
   - core/utils.py, core/config/models.py
   - api/routers/admin.py
   - auto_client_acquisition/{ai/model_router, agents/{intake,icp_matcher},
     v3/{memory,agents,compliance_os,market_radar},
     personal_operator/{operator,memory,launch_report},
     innovation/{proof_ledger_repo,command_feed_live}}.py
   - autonomous_growth/agents/sector_intel.py
   - dealix/{trust/{approval,tool_verification,policy},
     observability/cost_tracker,
     contracts/{evidence_pack,event_envelope,audit_log,decision},
     classifications/__init__,
     governance/approvals}.py

3. Three new test suites for previously-untested layers (54 tests):
   - tests/unit/test_business_suite.py — gtm_plan, launch_metrics,
     market_positioning, pricing_strategy, proof_pack, unit_economics,
     verticals (28 tests covering plan recommendation, performance fee,
     ROI math, account health grading, vertical playbook structure)
   - tests/unit/test_innovation_suite.py — aeo_radar, command_feed,
     deal_rooms, experiments, growth_missions, proof_ledger, ten_in_ten
     (18 tests covering deterministic reproducibility, card type taxonomy,
     pending-approval invariant, kill-mission visibility)
   - tests/unit/test_ai_model_router.py — ModelTask + get_model_route +
     estimate_model_cost_class + requires_guardrail (8 tests covering
     enum integrity, route round-trip, guardrail bool contract)

VERIFICATION
- ast.parse green on all 22 patched files
- pytest tests/unit/ → 477 passed, 2 skipped (provider smoke needs API keys)
  on Python 3.10.12 venv with project requirements installed
- No behavior change on 3.11+: the shim re-exports stdlib symbols

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:50:04 +03:00

106 lines
3.9 KiB
Python

"""Event-sourced Revenue Memory for Dealix v3."""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from core._py_compat import UTC
from core._py_compat import StrEnum
from hashlib import sha256
from typing import Any
from uuid import uuid4
class EventType(StrEnum):
LEAD_CREATED = "lead.created"
SIGNAL_DETECTED = "signal.detected"
MESSAGE_SENT = "message.sent"
REPLY_RECEIVED = "reply.received"
MEETING_BOOKED = "meeting.booked"
DEAL_WON = "deal.won"
DEAL_LOST = "deal.lost"
COMPLIANCE_BLOCKED = "compliance.blocked"
AGENT_ACTION_EXECUTED = "agent.action_executed"
@dataclass(frozen=True)
class RevenueEvent:
event_type: EventType
customer_id: str
aggregate_id: str
payload: dict[str, Any]
actor: str = "system"
occurred_at: datetime = field(default_factory=lambda: datetime.now(UTC))
event_id: str = field(default_factory=lambda: str(uuid4()))
def integrity_hash(self) -> str:
raw = f"{self.event_id}|{self.event_type.value}|{self.customer_id}|{self.aggregate_id}|{self.occurred_at.isoformat()}|{self.payload}"
return sha256(raw.encode()).hexdigest()
def to_dict(self) -> dict[str, Any]:
return {
"event_id": self.event_id,
"event_type": self.event_type.value,
"customer_id": self.customer_id,
"aggregate_id": self.aggregate_id,
"payload": self.payload,
"actor": self.actor,
"occurred_at": self.occurred_at.isoformat(),
"integrity_hash": self.integrity_hash(),
}
class RevenueMemory:
def __init__(self) -> None:
self._events: list[RevenueEvent] = []
def append(self, event: RevenueEvent) -> RevenueEvent:
self._events.append(event)
self._events.sort(key=lambda item: item.occurred_at)
return event
def timeline(self, aggregate_id: str) -> list[dict[str, Any]]:
return [event.to_dict() for event in self._events if event.aggregate_id == aggregate_id]
def projection(self, aggregate_id: str) -> dict[str, Any]:
events = [event for event in self._events if event.aggregate_id == aggregate_id]
result: dict[str, Any] = {
"aggregate_id": aggregate_id,
"events": len(events),
"signals": 0,
"messages": 0,
"replies": 0,
"meetings": 0,
"revenue_sar": 0.0,
"blocked": False,
"stage": "unknown",
}
for event in events:
if event.event_type == EventType.SIGNAL_DETECTED:
result["signals"] += 1
elif event.event_type == EventType.MESSAGE_SENT:
result["messages"] += 1
elif event.event_type == EventType.REPLY_RECEIVED:
result["replies"] += 1
elif event.event_type == EventType.MEETING_BOOKED:
result["meetings"] += 1
result["stage"] = "meeting_booked"
elif event.event_type == EventType.DEAL_WON:
result["stage"] = "won"
result["revenue_sar"] += float(event.payload.get("amount", 0) or 0)
elif event.event_type == EventType.DEAL_LOST:
result["stage"] = "lost"
elif event.event_type == EventType.COMPLIANCE_BLOCKED:
result["blocked"] = True
result["stage"] = "blocked"
return result
def demo_memory() -> RevenueMemory:
memory = RevenueMemory()
memory.append(RevenueEvent(EventType.SIGNAL_DETECTED, "demo", "clinic_riyadh_01", {"signal": "hiring_sales"}))
memory.append(RevenueEvent(EventType.MESSAGE_SENT, "demo", "clinic_riyadh_01", {"channel": "whatsapp"}))
memory.append(RevenueEvent(EventType.REPLY_RECEIVED, "demo", "clinic_riyadh_01", {"intent": "positive"}))
memory.append(RevenueEvent(EventType.MEETING_BOOKED, "demo", "clinic_riyadh_01", {"date": "next_week"}))
return memory