system-prompts-and-models-o.../dealix/auto_client_acquisition/personal_operator/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

130 lines
3.7 KiB
Python

"""In-memory Personal Operator store — swappable later with Supabase."""
from __future__ import annotations
import re
from dataclasses import dataclass, field
from datetime import datetime
from core._py_compat import UTC
from core._py_compat import StrEnum
from typing import Any
from uuid import uuid4
class MemoryType(StrEnum):
PROFILE = "profile"
GOAL = "goal"
PREFERENCE = "preference"
RELATIONSHIP = "relationship"
OPPORTUNITY = "opportunity"
DECISION = "decision"
MEETING = "meeting"
FOLLOWUP = "followup"
LAUNCH_NOTE = "launch_note"
PROJECT_NOTE = "project_note"
_SECRET_PATTERNS = (
re.compile(r"sk-[a-zA-Z0-9]{20,}", re.I),
re.compile(r"AIza[0-9A-Za-z\-_]{20,}"),
re.compile(r"Bearer\s+[a-zA-Z0-9\-_.]{20,}", re.I),
re.compile(r"-----BEGIN [A-Z ]+PRIVATE KEY-----"),
re.compile(r"xox[baprs]-[a-zA-Z0-9\-]{10,}", re.I),
)
@dataclass
class PersonalMemoryItem:
id: str
memory_type: MemoryType
title: str
body: str
created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
metadata: dict[str, Any] = field(default_factory=dict)
@dataclass
class PersonalOperatorMemory:
items: list[PersonalMemoryItem] = field(default_factory=list)
def add(self, item: PersonalMemoryItem) -> PersonalMemoryItem:
self.items.append(item)
return item
def looks_like_secret(text: str) -> bool:
"""Return True if text resembles API keys or private material."""
for pattern in _SECRET_PATTERNS:
if pattern.search(text):
return True
return False
def add_memory(
store: PersonalOperatorMemory,
*,
memory_type: MemoryType,
title: str,
body: str,
metadata: dict[str, Any] | None = None,
) -> dict[str, Any]:
if looks_like_secret(body) or looks_like_secret(title):
return {
"ok": False,
"error": "secret_like_content_blocked",
"message": "Do not store API keys or tokens in operator memory.",
}
item = PersonalMemoryItem(
id=f"mem_{uuid4().hex[:12]}",
memory_type=memory_type,
title=title.strip(),
body=body.strip(),
metadata=dict(metadata or {}),
)
store.add(item)
return {"ok": True, "item": _item_to_dict(item)}
def list_memories(store: PersonalOperatorMemory, *, memory_type: MemoryType | None = None) -> list[dict[str, Any]]:
items = store.items
if memory_type is not None:
items = [i for i in items if i.memory_type == memory_type]
return [_item_to_dict(i) for i in items]
def search_memories(store: PersonalOperatorMemory, query: str, limit: int = 20) -> list[dict[str, Any]]:
q = query.lower().strip()
if not q:
return []
hits: list[tuple[int, PersonalMemoryItem]] = []
for item in store.items:
hay = f"{item.title}\n{item.body}".lower()
score = sum(hay.count(term) for term in q.split() if len(term) > 1)
if score:
hits.append((score, item))
hits.sort(key=lambda x: x[0], reverse=True)
return [_item_to_dict(i) for _, i in hits[:limit]]
def summarize_memory(store: PersonalOperatorMemory) -> dict[str, Any]:
by_type: dict[str, int] = {}
for item in store.items:
k = item.memory_type.value
by_type[k] = by_type.get(k, 0) + 1
return {
"total": len(store.items),
"by_type": by_type,
"latest_titles": [i.title for i in store.items[-5:]],
}
def _item_to_dict(item: PersonalMemoryItem) -> dict[str, Any]:
return {
"id": item.id,
"memory_type": item.memory_type.value,
"title": item.title,
"body": item.body,
"created_at": item.created_at.isoformat(),
"metadata": item.metadata,
}