system-prompts-and-models-o.../dealix/auto_client_acquisition/platform_services/action_ledger.py
Dealix Builder 4e969131c7 feat(platform+intelligence): Growth Control Tower + Growth Neural Network — 20 modules + 25 endpoints + 60 tests
Platform Services Layer (10 modules) — برج التحكم بالنمو
- event_bus: 27 typed events (whatsapp/email/calendar/lead/payment/review/social/partner/sheet/crm/action)
- identity_resolution: cross-channel merge (phone+email+CRM+social) with confidence scoring
- channel_registry: 11 channels (WA, Gmail, Calendar, Moyasar, LinkedIn, X, IG, GBP, Sheets, CRM, Forms) with capabilities/risk/PDPL notes
- action_policy: 9 rules (block_cold_whatsapp, block_payment_no_confirm, block_secrets, external_send_needs_approval, calendar_insert_needs_approval, social_dm_needs_explicit, unknown_source_review, high_value_deal_review, draft_only_safe)
- tool_gateway: single execution chokepoint, env-flag-gated live actions (default OFF)
- unified_inbox: 8 card types, ≤3 buttons enforced, Arabic
- action_ledger: requested→approved→executed audit trail
- proof_ledger: leads/meetings/drafts/sends/payments/revenue/risks_blocked/time_saved per channel
- service_catalog: 12 sellable services
- router api/routers/platform_services.py — 13 endpoints under /api/v1/platform/

Intelligence Layer (10 modules) — الشبكة العصبية للنمو
- growth_brain: per-customer Brain + is_ready_for_autopilot() (≥30 signals + ≥40% accept)
- command_feed: 9 daily card types (opportunity/revenue_leak/partner_suggestion/meeting_prep/review_response/competitive_move/customer_reactivation/ai_visibility_alert/action_required)
- action_graph: 10 typed edges (signal→action→outcome) with what_works_summary
- mission_engine: 7 missions, KILL FEATURE first_10_opportunities (10 فرص في 10 دقائق)
- decision_memory: learns from accept/skip/edit/block, returns preferences (channels, tones, sectors, rejected actions, accept_rate)
- trust_score: composite 0-100 (source+opt_in+channel+content+freq+approval) → safe/needs_review/blocked
- revenue_dna: best_channel/segment/angle + common_objection + avg_cycle_days
- opportunity_simulator: 9 Saudi sectors, expected_replies/meetings/deals/pipeline_sar + risk_score
- competitive_moves: 8 move types with Arabic recommended_action_ar
- board_brief: weekly Founder Shadow Board (3 decisions + 3 opportunities + 3 risks + relationship + experiment + metric)
- router api/routers/intelligence_layer.py — 12 endpoints under /api/v1/intelligence/

Tests
- tests/unit/test_platform_services.py — 31 tests covering catalog/channels/events/policy/gateway/identity/inbox/ledger/proof
- tests/unit/test_intelligence_layer.py — 29 tests covering brain/feed/graph/missions/memory/trust/dna/simulator/competitive/brief
- 60/60 new tests pass; full suite 587 passed, 2 skipped

Docs
- docs/PLATFORM_SERVICES_STRATEGY.md (Arabic)
- docs/INTELLIGENCE_LAYER_STRATEGY.md (Arabic)
- docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md — added §32 Platform Services + §33 Intelligence Layer

Safety
- No live send by default (all WA/Gmail/Calendar/Moyasar guarded by env flags, all OFF)
- All external actions go through Tool Gateway → Action Policy → draft/approval_required
- No secrets allowed in payloads (block_secrets policy)
- PDPL-aware: cold WhatsApp without consent is hard-blocked
- Existing 477+ tests untouched (no breaking changes)

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

108 lines
3.1 KiB
Python

"""
Action Ledger — auditable record of every action lifecycle.
Stage transitions per action: requested → (approved | rejected | blocked)
→ executed → outcome.
Used for SDAIA / DPO inspections + customer's own audit trail.
"""
from __future__ import annotations
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any
VALID_STAGES: tuple[str, ...] = (
"requested", "approved", "rejected", "blocked",
"executed", "outcome_recorded",
)
@dataclass
class LedgerEntry:
"""One entry in the action ledger."""
entry_id: str
customer_id: str
action_type: str
channel: str
stage: str
actor: str = "system"
payload: dict[str, Any] = field(default_factory=dict)
reason_ar: str = ""
created_at: datetime = field(
default_factory=lambda: datetime.now(timezone.utc).replace(tzinfo=None)
)
correlation_id: str | None = None
def to_dict(self) -> dict[str, Any]:
return {
"entry_id": self.entry_id,
"customer_id": self.customer_id,
"action_type": self.action_type,
"channel": self.channel,
"stage": self.stage,
"actor": self.actor,
"payload": self.payload,
"reason_ar": self.reason_ar,
"created_at": self.created_at.isoformat(),
"correlation_id": self.correlation_id,
}
@dataclass
class ActionLedger:
"""Append-only ledger keyed by customer_id."""
entries: list[LedgerEntry] = field(default_factory=list)
def append(
self,
*,
customer_id: str,
action_type: str,
channel: str,
stage: str,
actor: str = "system",
payload: dict[str, Any] | None = None,
reason_ar: str = "",
correlation_id: str | None = None,
) -> LedgerEntry:
if stage not in VALID_STAGES:
raise ValueError(f"unknown stage: {stage}")
entry = LedgerEntry(
entry_id=f"led_{uuid.uuid4().hex[:20]}",
customer_id=customer_id,
action_type=action_type,
channel=channel,
stage=stage,
actor=actor,
payload=payload or {},
reason_ar=reason_ar,
correlation_id=correlation_id,
)
self.entries.append(entry)
return entry
def for_customer(self, customer_id: str) -> list[LedgerEntry]:
return [e for e in self.entries if e.customer_id == customer_id]
def summary(self, customer_id: str | None = None) -> dict[str, Any]:
pool = self.entries if customer_id is None else self.for_customer(customer_id)
by_stage: dict[str, int] = {}
by_channel: dict[str, int] = {}
by_action: dict[str, int] = {}
for e in pool:
by_stage[e.stage] = by_stage.get(e.stage, 0) + 1
by_channel[e.channel] = by_channel.get(e.channel, 0) + 1
by_action[e.action_type] = by_action.get(e.action_type, 0) + 1
return {
"total": len(pool),
"by_stage": by_stage,
"by_channel": by_channel,
"by_action_type": by_action,
}