system-prompts-and-models-o.../dealix/auto_client_acquisition/public_launch/pilot_tracker.py
Sami Assiri b88a7c845f feat(public-launch): Layer 13 gate, PDPL, brand moat, docs and tests
- Add public_launch ACA package (gate, pilot_tracker, pdpl, brand_moat)
- Register GET/POST /api/v1/public-launch/* router in api/main
- Add 18 unit tests, MASTER_STRATEGIC_PLAN, PUBLIC_LAUNCH_READINESS
- Refresh POST_MERGE_VERIFICATION snapshot; vendor patch for git apply

Verified: 797 passed (6 skipped), ROUTE_CHECK_OK, SMOKE_INPROCESS_OK
2026-05-01 23:32:36 +03:00

177 lines
6.2 KiB
Python
Raw 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.

"""Pilot Tracker — track 510 active pilots toward Public Launch gate.
Tracks each Pilot's lifecycle:
intake → diagnostic_24h → pilot_48h → proof_pack_7d → upgrade_decision
Used by the Public Launch Gate to count `pilots_completed` and
`paid_customers`. Deterministic — no I/O, no LLM.
"""
from __future__ import annotations
from dataclasses import dataclass, asdict, field
from datetime import datetime, timezone
from typing import Any, Mapping, Sequence
PILOT_STAGES = (
"intake", # T+0
"diagnostic_sent", # T+24h
"pilot_delivered", # T+48h
"proof_pack_sent", # T+7d
"upgrade_decided", # T+14d
"completed", # marked done
"stalled", # SLA breach
"lost", # no upgrade, no case study
)
UPGRADE_OUTCOMES = (
"growth_os_monthly", # 2,999/mo subscription
"partnership_growth", # 3,0007,500 SAR
"case_study", # free in exchange for case study
"second_pilot", # paid 499 again, different angle
"no_upgrade", # explicit decline
"ghost", # no response
)
@dataclass
class PilotRecord:
"""A single Pilot's data."""
pilot_id: str
company: str
sector: str
city: str
started_at: str # ISO date
stage: str
paid: bool # True if 499 SAR received OR signed Growth OS
pilot_price_sar: int
proof_pack_sent: bool
proof_pack_sent_at: str | None
upgrade_outcome: str | None
upgrade_value_sar: int = 0
notes: str = ""
def to_dict(self) -> dict[str, Any]:
return asdict(self)
@dataclass
class PilotSummary:
"""Aggregate view across all pilots."""
total_pilots: int
completed_pilots: int # reached upgrade_decided or completed
proof_packs_delivered: int
paid_pilots: int
paid_revenue_sar: int # sum of pilot prices that were paid
upgrade_revenue_sar: int # sum of upgrade_value_sar
case_studies: int # upgrade_outcome == "case_study"
growth_os_subscribers: int # upgrade_outcome == "growth_os_monthly"
stalled_pilots: int
lost_pilots: int
completion_rate: float # completed / total
paid_conversion_rate: float # paid / total
upgrade_conversion_rate: float # any upgrade / completed
by_sector: dict[str, int]
by_city: dict[str, int]
average_proof_pack_days: float
summary_ar: str
def to_dict(self) -> dict[str, Any]:
return asdict(self)
def _parse_iso(s: str) -> datetime | None:
if not s:
return None
try:
return datetime.fromisoformat(s.replace("Z", "+00:00"))
except ValueError:
return None
def pilot_tracker_summary(pilots: Sequence[PilotRecord | Mapping[str, Any]]) -> PilotSummary:
"""Compute aggregate summary across all pilots.
Accepts either PilotRecord instances or plain dicts (for API ingestion).
"""
records: list[PilotRecord] = []
for p in pilots:
if isinstance(p, PilotRecord):
records.append(p)
else:
records.append(PilotRecord(
pilot_id=str(p.get("pilot_id", "")),
company=str(p.get("company", "")),
sector=str(p.get("sector", "unknown")),
city=str(p.get("city", "unknown")),
started_at=str(p.get("started_at", "")),
stage=str(p.get("stage", "intake")),
paid=bool(p.get("paid", False)),
pilot_price_sar=int(p.get("pilot_price_sar", 499)),
proof_pack_sent=bool(p.get("proof_pack_sent", False)),
proof_pack_sent_at=p.get("proof_pack_sent_at"),
upgrade_outcome=p.get("upgrade_outcome"),
upgrade_value_sar=int(p.get("upgrade_value_sar", 0)),
notes=str(p.get("notes", "")),
))
total = len(records)
completed_stages = {"upgrade_decided", "completed"}
completed = sum(1 for r in records if r.stage in completed_stages)
proof_count = sum(1 for r in records if r.proof_pack_sent)
paid_count = sum(1 for r in records if r.paid)
paid_revenue = sum(r.pilot_price_sar for r in records if r.paid)
upgrade_revenue = sum(r.upgrade_value_sar for r in records)
case_studies = sum(1 for r in records if r.upgrade_outcome == "case_study")
gros_subs = sum(1 for r in records if r.upgrade_outcome == "growth_os_monthly")
stalled = sum(1 for r in records if r.stage == "stalled")
lost = sum(1 for r in records if r.stage == "lost")
by_sector: dict[str, int] = {}
by_city: dict[str, int] = {}
for r in records:
by_sector[r.sector] = by_sector.get(r.sector, 0) + 1
by_city[r.city] = by_city.get(r.city, 0) + 1
proof_durations: list[float] = []
for r in records:
if r.proof_pack_sent and r.proof_pack_sent_at and r.started_at:
start = _parse_iso(r.started_at)
done = _parse_iso(r.proof_pack_sent_at)
if start and done:
proof_durations.append((done - start).total_seconds() / 86400)
avg_proof_days = (
round(sum(proof_durations) / len(proof_durations), 2)
if proof_durations else 0.0
)
summary_ar = (
f"{total} Pilots إجمالاً، {completed} مكتمل، "
f"{paid_count} مدفوع، {proof_count} Proof Packs، "
f"{gros_subs} Growth OS، {case_studies} case studies."
)
return PilotSummary(
total_pilots=total,
completed_pilots=completed,
proof_packs_delivered=proof_count,
paid_pilots=paid_count,
paid_revenue_sar=paid_revenue,
upgrade_revenue_sar=upgrade_revenue,
case_studies=case_studies,
growth_os_subscribers=gros_subs,
stalled_pilots=stalled,
lost_pilots=lost,
completion_rate=round(completed / total, 3) if total else 0.0,
paid_conversion_rate=round(paid_count / total, 3) if total else 0.0,
upgrade_conversion_rate=(
round((gros_subs + case_studies) / completed, 3)
if completed else 0.0
),
by_sector=by_sector,
by_city=by_city,
average_proof_pack_days=avg_proof_days,
summary_ar=summary_ar,
)