mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-18 07:19:35 +00:00
- 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
247 lines
8.8 KiB
Python
247 lines
8.8 KiB
Python
"""Public Launch Gate — evaluates 9 deterministic criteria.
|
||
|
||
The gate is the formal transition from Paid Beta to Public Launch.
|
||
All criteria must be deterministic (no LLM, no network) so that the
|
||
verdict is reproducible in CI and in the dashboard.
|
||
|
||
Criteria (from PAID_BETA_OPERATING_PLAYBOOK §8):
|
||
1. ≥5 pilots completed (delivered Proof Pack within 7 days)
|
||
2. ≥2 paid customers (Moyasar invoice paid OR signed Growth OS)
|
||
3. 0 unsafe sends (no live action without approval in audit ledger)
|
||
4. Weekly Proof Pack cadence (≥3 weeks consecutive)
|
||
5. Support flow operational (avg first response < SLA)
|
||
6. Funnel visible (lead → demo → pilot → paid measurable)
|
||
7. ≥14 days staging stable (uptime ≥ 99% over 14 days)
|
||
8. Billing live (Moyasar webhook signed and verified)
|
||
9. Legal complete (Terms + Privacy + DPA published)
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass, field, asdict
|
||
from typing import Any, Mapping, Sequence
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class GateCriterion:
|
||
"""Definition of a single Public Launch criterion."""
|
||
key: str
|
||
name_ar: str
|
||
threshold: float | int | bool
|
||
unit: str
|
||
description_ar: str
|
||
|
||
|
||
PUBLIC_LAUNCH_CRITERIA: tuple[GateCriterion, ...] = (
|
||
GateCriterion(
|
||
key="pilots_completed",
|
||
name_ar="Pilots مكتملة",
|
||
threshold=5,
|
||
unit="count",
|
||
description_ar="عدد Pilots التي سُلّم لها Proof Pack نهائي خلال 7 أيام",
|
||
),
|
||
GateCriterion(
|
||
key="paid_customers",
|
||
name_ar="عملاء مدفوعون",
|
||
threshold=2,
|
||
unit="count",
|
||
description_ar="عملاء دفعوا فعلياً عبر Moyasar أو وقّعوا Growth OS",
|
||
),
|
||
GateCriterion(
|
||
key="unsafe_sends",
|
||
name_ar="إرسال غير آمن",
|
||
threshold=0,
|
||
unit="count",
|
||
description_ar="عدد الأفعال الـ live بدون اعتماد بشري في Action Ledger (يجب = 0)",
|
||
),
|
||
GateCriterion(
|
||
key="proof_cadence_weeks",
|
||
name_ar="استمرارية Proof Pack",
|
||
threshold=3,
|
||
unit="weeks_consecutive",
|
||
description_ar="عدد الأسابيع المتتالية التي صدر فيها Proof Pack",
|
||
),
|
||
GateCriterion(
|
||
key="support_first_response_minutes_p1",
|
||
name_ar="استجابة Support P1",
|
||
threshold=120,
|
||
unit="minutes",
|
||
description_ar="متوسط استجابة P1 ≤ 120 دقيقة (هدف SLA)",
|
||
),
|
||
GateCriterion(
|
||
key="funnel_visible",
|
||
name_ar="Funnel مرئي",
|
||
threshold=True,
|
||
unit="bool",
|
||
description_ar="lead→demo→pilot→paid قابل للقياس في Operating Board",
|
||
),
|
||
GateCriterion(
|
||
key="staging_uptime_days",
|
||
name_ar="استقرار Staging",
|
||
threshold=14,
|
||
unit="days_uptime_99",
|
||
description_ar="عدد الأيام المتتالية بـ uptime ≥ 99% على staging",
|
||
),
|
||
GateCriterion(
|
||
key="billing_webhook_verified",
|
||
name_ar="Moyasar webhook موثّق",
|
||
threshold=True,
|
||
unit="bool",
|
||
description_ar="Moyasar webhook signed وتم تحقق التوقيع",
|
||
),
|
||
GateCriterion(
|
||
key="legal_complete",
|
||
name_ar="القانوني مكتمل",
|
||
threshold=True,
|
||
unit="bool",
|
||
description_ar="Terms of Service + Privacy Policy + DPA منشورة بالعربي والإنجليزي",
|
||
),
|
||
)
|
||
|
||
|
||
@dataclass
|
||
class GateVerdict:
|
||
"""Result of evaluating Public Launch readiness."""
|
||
decision: str # "GO_PUBLIC_LAUNCH" | "NO_GO" | "BLOCKED"
|
||
score_passed: int
|
||
score_total: int
|
||
blockers: list[Mapping[str, Any]]
|
||
next_actions_ar: list[str]
|
||
criteria_results: list[Mapping[str, Any]]
|
||
summary_ar: str
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
return asdict(self)
|
||
|
||
|
||
def _check_criterion(crit: GateCriterion, value: Any) -> tuple[bool, str]:
|
||
"""Compare actual value against threshold. Return (passed, reason_ar)."""
|
||
if crit.unit == "bool":
|
||
passed = bool(value) is bool(crit.threshold)
|
||
if passed:
|
||
return True, "متحقّق"
|
||
return False, f"يجب أن يكون {crit.threshold}"
|
||
|
||
if crit.key == "unsafe_sends":
|
||
# Unique: lower is better. Threshold = 0 means must equal 0.
|
||
try:
|
||
v = int(value)
|
||
except (TypeError, ValueError):
|
||
return False, "قيمة غير صحيحة"
|
||
return (v == 0, f"وُجد {v} (يجب = 0)" if v != 0 else "0 إرسال غير آمن")
|
||
|
||
if crit.key == "support_first_response_minutes_p1":
|
||
# Lower is better. Threshold is the maximum.
|
||
try:
|
||
v = float(value)
|
||
except (TypeError, ValueError):
|
||
return False, "قيمة غير صحيحة"
|
||
return (
|
||
v <= crit.threshold,
|
||
f"{v:.0f} دقيقة (الحد الأعلى {crit.threshold})",
|
||
)
|
||
|
||
# Default: numeric, higher is better.
|
||
try:
|
||
v = float(value)
|
||
except (TypeError, ValueError):
|
||
return False, "قيمة غير صحيحة"
|
||
return (
|
||
v >= crit.threshold,
|
||
f"{int(v) if v.is_integer() else v}/{crit.threshold} {crit.unit}",
|
||
)
|
||
|
||
|
||
def _next_action_for(crit: GateCriterion, value: Any) -> str | None:
|
||
"""Generate Arabic next-action when a criterion fails."""
|
||
if crit.key == "pilots_completed":
|
||
return f"شغّل {int(crit.threshold) - int(value or 0)} Pilots إضافية مع Proof Pack مكتمل."
|
||
if crit.key == "paid_customers":
|
||
return f"اقفل {int(crit.threshold) - int(value or 0)} عميل مدفوع إضافي (Moyasar أو Growth OS)."
|
||
if crit.key == "unsafe_sends":
|
||
return "راجع Action Ledger، أوقف القناة المعنية، نفّذ post-mortem فوراً."
|
||
if crit.key == "proof_cadence_weeks":
|
||
return "أصدر Proof Pack أسبوعياً متتالياً حتى تصل 3 أسابيع متتالية."
|
||
if crit.key == "support_first_response_minutes_p1":
|
||
return "حسّن SLA — قلّل first-response P1 إلى ≤120 دقيقة."
|
||
if crit.key == "funnel_visible":
|
||
return "افتح Operating Board مع كل الأعمدة الـ15 وحدّثه يومياً."
|
||
if crit.key == "staging_uptime_days":
|
||
return f"حافظ على staging stable حتى تصل {int(crit.threshold)} يوم متتالي بـ uptime ≥99%."
|
||
if crit.key == "billing_webhook_verified":
|
||
return "فعّل Moyasar webhook signature verification + اختبر بـ test payload."
|
||
if crit.key == "legal_complete":
|
||
return "انشر Terms + Privacy + DPA باللغتين العربية والإنجليزية على الموقع."
|
||
return None
|
||
|
||
|
||
def evaluate_public_launch_gate(
|
||
state: Mapping[str, Any],
|
||
criteria: Sequence[GateCriterion] | None = None,
|
||
) -> GateVerdict:
|
||
"""Evaluate Public Launch readiness.
|
||
|
||
Args:
|
||
state: dict mapping criterion key → measured value.
|
||
criteria: optional override (defaults to PUBLIC_LAUNCH_CRITERIA).
|
||
|
||
Returns:
|
||
GateVerdict with decision, score, blockers, and Arabic next actions.
|
||
"""
|
||
crits = criteria or PUBLIC_LAUNCH_CRITERIA
|
||
results: list[dict[str, Any]] = []
|
||
blockers: list[dict[str, Any]] = []
|
||
actions: list[str] = []
|
||
passed_count = 0
|
||
|
||
for c in crits:
|
||
value = state.get(c.key)
|
||
ok, reason = _check_criterion(c, value)
|
||
result = {
|
||
"key": c.key,
|
||
"name_ar": c.name_ar,
|
||
"passed": ok,
|
||
"value": value,
|
||
"threshold": c.threshold,
|
||
"unit": c.unit,
|
||
"reason_ar": reason,
|
||
}
|
||
results.append(result)
|
||
if ok:
|
||
passed_count += 1
|
||
else:
|
||
blockers.append(result)
|
||
action = _next_action_for(c, value)
|
||
if action:
|
||
actions.append(action)
|
||
|
||
total = len(crits)
|
||
if passed_count == total:
|
||
decision = "GO_PUBLIC_LAUNCH"
|
||
summary = (
|
||
f"✅ جاهز للإطلاق العام — كل المعايير الـ{total} متحققة. "
|
||
"ابدأ خطة Public Launch من MASTER_STRATEGIC_PLAN §3 Phase 3."
|
||
)
|
||
elif any(b["key"] == "unsafe_sends" for b in blockers):
|
||
decision = "BLOCKED"
|
||
summary = (
|
||
"🛑 Hard block — إرسال غير آمن مكتشف. "
|
||
"أوقف كل live actions الآن. شغّل incident_router SEV1."
|
||
)
|
||
else:
|
||
decision = "NO_GO"
|
||
summary = (
|
||
f"⏳ NO_GO — {passed_count}/{total} متحقق. "
|
||
f"المتبقي: {', '.join(b['name_ar'] for b in blockers)}."
|
||
)
|
||
|
||
return GateVerdict(
|
||
decision=decision,
|
||
score_passed=passed_count,
|
||
score_total=total,
|
||
blockers=blockers,
|
||
next_actions_ar=actions,
|
||
criteria_results=results,
|
||
summary_ar=summary,
|
||
)
|