system-prompts-and-models-o.../dealix/scripts/paid_beta_daily_scorecard.py
Dealix Builder 342bcf8ea5 feat(paid-beta): operational layer for first 499 SAR — playbook + workflow + board + scorecard + landing CTA
Move from GO_PRIVATE_BETA (technical readiness) to PAID_BETA_READY
(first revenue) — operational, not architectural.

Deliverables:
- docs/PAID_BETA_OPERATING_PLAYBOOK.md
  10-section Arabic playbook: gate to Paid Beta, 7-day day-by-day
  plan (Staging → Outreach → Demos → Diagnostic → Pilot Sale →
  Pilot Day1/Day2 → Proof+Upsell), weekly targets (50-70 messages /
  5-10 replies / 3-5 demos / 1+ payment), 8 hard operational rules,
  daily cadence, what NOT to add, Public Launch criteria.

- docs/FIRST_PILOT_DELIVERY_WORKFLOW.md
  48-hour Arabic Pilot delivery: T+0 intake (15 fields) → T+24
  Free Diagnostic (3 opportunities + 1 Arabic message + 1 risk + 1
  service recommendation) → T+48 Pilot 499 (10 opportunities + 7-day
  follow-up plan + Proof Pack) → T+7 final Proof Pack + 30min review +
  3 upgrade paths. Pilot success criteria + 8-row metrics table.

- docs/PRIVATE_BETA_OPERATING_BOARD.md
  15-column Sheet template (company, person, segment, source, channel,
  message_sent, reply_status, demo_booked, diagnostic_sent,
  pilot_offered, price, paid, proof_pack_sent, next_step, notes) +
  status flow + ICP distribution + 3-wave follow-up templates +
  daily routine + PDPL privacy rules + CSV header.

- landing/private-beta.html
  Pilot 499 SAR offer prominent at top (badge + hero CTA), dedicated
  3-card pricing section (Pilot 499 / Free Diagnostic / Growth OS
  Monthly 2,999), 7-day refund/case-study guarantee, mailto CTAs
  with prefilled subject + body, removed duplicate pricing block.

- scripts/paid_beta_daily_scorecard.py (274 lines)
  argparse with --messages, --replies, --demos, --pilots, --payments,
  --proof-packs, --as-of, --json. Computes reply_rate / demo_rate /
  pilot_rate / payment_rate, daily verdict (ON_TRACK / BEHIND /
  OFF_TRACK), weekly verdict (BLOCKERS / STRETCH_PENDING /
  WEEKLY_TARGETS_HIT), and rule-based next_actions in Arabic.
  Targets: 50-70 messages / 5-15 replies / 3-7 demos / 2-3 pilots /
  1-2 paid / 1+ proof pack per week.

- tests/unit/test_paid_beta_scorecard.py
  12 tests: zero-input, on-track day, tone-action trigger, payment
  → proof-pack action, full-week target hit, conversion rates,
  Arabic text rendering, JSON validity, CLI text/json modes,
  --as-of today/explicit.

Hard rules (unchanged):
- No live WhatsApp / Gmail / Calendar send without env flag + approval.
- No Moyasar API charge — manual invoice/payment-link only.
- No LinkedIn scraping / auto-DM — Lead Gen Forms + manual outreach.
- No cold WhatsApp without opt-in (PDPL hard-block).
- Every message passes safety_eval + saudi_tone_eval.
- Every action recorded in Action Ledger.

Validation:
- python -m compileall api auto_client_acquisition: clean.
- pytest tests/unit (excl. tenacity-dep tests): 950 passed, 2 skipped.
- python scripts/smoke_inprocess.py: SMOKE_INPROCESS_OK (8/8 endpoints).
- python scripts/paid_beta_daily_scorecard.py text + --json: both render
  correctly with Arabic + verdict + next_actions.
- tests/unit/test_positioning_lock.py: 10 passed (no prohibited
  phrases introduced in updated landing/private-beta.html).

Test count: 949 → 962 (+12 new, 1 prior already counted).

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

275 lines
9.5 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.

#!/usr/bin/env python3
"""Paid Beta Daily Scorecard — تتبّع التقدّم اليومي نحو أول إيراد.
كل يوم في Paid Beta، شغّل:
python scripts/paid_beta_daily_scorecard.py \\
--messages 25 --replies 4 --demos 2 --pilots 1 --payments 0 --proof-packs 0
أو بصيغة JSON للأتمتة:
python scripts/paid_beta_daily_scorecard.py \\
--messages 25 --replies 4 --demos 2 --pilots 1 --payments 1 --proof-packs 0 --json
الهدف خلال 7 أيام:
70 تواصل يدوي / 15 رد / 7 ديمو / 3 pilots / 12 paid / 1 Proof Pack على الأقل.
"""
from __future__ import annotations
import argparse
import json
import sys
from dataclasses import asdict, dataclass
from datetime import date
# ----- Targets -----
WEEKLY_TARGETS = {
"messages": {"min": 50, "stretch": 70},
"replies": {"min": 5, "stretch": 15},
"demos": {"min": 3, "stretch": 7},
"pilots": {"min": 2, "stretch": 3},
"payments": {"min": 1, "stretch": 2},
"proof_packs": {"min": 1, "stretch": 1},
}
DAILY_TARGETS = {
# ÷ 7 (مدوّر للأعلى) لأي metric
"messages": 10,
"replies": 1,
"demos": 1,
"pilots": 0, # ≥1 خلال الأسبوع
"payments": 0, # ≥1 خلال الأسبوع
"proof_packs": 0, # ≥1 خلال الأسبوع
}
# ----- Computation -----
@dataclass
class Scorecard:
as_of: str
messages: int
replies: int
demos: int
pilots: int
payments: int
proof_packs: int
reply_rate: float
demo_rate: float
pilot_rate: float
payment_rate: float
daily_verdict: str
weekly_verdict: str
next_actions: list[str]
def _safe_div(a: int, b: int) -> float:
return round(a / b, 3) if b > 0 else 0.0
def _daily_verdict(metrics: dict[str, int]) -> str:
"""Compare today's metrics to daily targets."""
misses = []
for key, target in DAILY_TARGETS.items():
if target == 0:
continue
if metrics[key] < target:
misses.append(f"{key}: {metrics[key]}/{target}")
if not misses:
return "ON_TRACK"
if len(misses) == 1:
return f"BEHIND on {misses[0]}"
return f"OFF_TRACK: {', '.join(misses)}"
def _weekly_verdict(metrics: dict[str, int]) -> str:
"""Compare cumulative-week metrics to weekly targets (assumes the input is week-to-date totals)."""
blockers = []
misses = []
for key, t in WEEKLY_TARGETS.items():
v = metrics[key]
if v < t["min"]:
blockers.append(f"{key} {v}/{t['min']}")
elif v < t["stretch"]:
misses.append(f"{key} {v}/{t['stretch']}")
if not blockers and not misses:
return "WEEKLY_TARGETS_HIT"
if blockers:
return "BLOCKERS: " + ", ".join(blockers)
return "STRETCH_PENDING: " + ", ".join(misses)
def _next_actions(metrics: dict[str, int]) -> list[str]:
actions: list[str] = []
if metrics["messages"] < DAILY_TARGETS["messages"]:
deficit = DAILY_TARGETS["messages"] - metrics["messages"]
actions.append(
f"أرسل {deficit} رسالة إضافية اليوم (LinkedIn/Email/WhatsApp opt-in فقط)."
)
if metrics["messages"] >= 5 and metrics["replies"] == 0:
actions.append(
"0 ردود مع >5 رسائل — راجع نبرة الرسالة وعدّلها (saudi_tone_eval)."
)
if metrics["replies"] >= 2 and metrics["demos"] == 0:
actions.append(
"ردود إيجابية بدون ديمو — احجز ديمو 12 دقيقة لكل رد إيجابي اليوم."
)
if metrics["demos"] >= 2 and metrics["pilots"] == 0:
actions.append(
"ديمو ≥2 بدون عرض Pilot — أرسل عرض Pilot 499 + Free Diagnostic لكل ديمو."
)
if metrics["pilots"] >= 1 and metrics["payments"] == 0:
actions.append(
"Pilot معروض بدون دفع — تابع Moyasar invoice manual + رسالة متابعة دفع."
)
if metrics["payments"] >= 1 and metrics["proof_packs"] == 0:
actions.append(
"أول دفعة وصلت — ابدأ Pilot delivery + أعد Proof Pack v1 خلال 48 ساعة."
)
if not actions:
actions.append(
"اليوم ON_TRACK. حافظ على الإيقاع: 10 رسائل + 5 follow-ups + 1 ديمو."
)
return actions
def build_scorecard(
messages: int,
replies: int,
demos: int,
pilots: int,
payments: int,
proof_packs: int,
as_of: str | None = None,
) -> Scorecard:
metrics = {
"messages": messages,
"replies": replies,
"demos": demos,
"pilots": pilots,
"payments": payments,
"proof_packs": proof_packs,
}
return Scorecard(
as_of=as_of or date.today().isoformat(),
messages=messages,
replies=replies,
demos=demos,
pilots=pilots,
payments=payments,
proof_packs=proof_packs,
reply_rate=_safe_div(replies, messages),
demo_rate=_safe_div(demos, replies),
pilot_rate=_safe_div(pilots, demos),
payment_rate=_safe_div(payments, pilots),
daily_verdict=_daily_verdict(metrics),
weekly_verdict=_weekly_verdict(metrics),
next_actions=_next_actions(metrics),
)
# ----- Rendering -----
def render_text(card: Scorecard) -> str:
lines = [
"════════════════════════════════════════════════",
f" Paid Beta Daily Scorecard — {card.as_of}",
"════════════════════════════════════════════════",
"",
"اليوم:",
f" 📨 رسائل أُرسلت: {card.messages:>3} (يومي ≥10)",
f" 💬 ردود إيجابية: {card.replies:>3} (يومي ≥1)",
f" 📅 ديمو محجوز: {card.demos:>3} (يومي ≥1)",
f" 🚀 Pilots معروضة: {card.pilots:>3} (أسبوعي ≥2)",
f" 💳 دفعات وصلت: {card.payments:>3} (أسبوعي ≥1)",
f" 📦 Proof Packs مرسلة: {card.proof_packs:>3} (أسبوعي ≥1)",
"",
"Conversion Rates:",
f" reply_rate = {card.reply_rate:.1%}",
f" demo_rate = {card.demo_rate:.1%} (replies → demos)",
f" pilot_rate = {card.pilot_rate:.1%} (demos → pilots)",
f" payment_rate = {card.payment_rate:.1%} (pilots → paid)",
"",
f"Daily Verdict: {card.daily_verdict}",
f"Weekly Verdict: {card.weekly_verdict}",
"",
"Next Actions:",
]
for i, action in enumerate(card.next_actions, 1):
lines.append(f" {i}. {action}")
lines.extend([
"",
"════════════════════════════════════════════════",
"Targets: 5070 messages / 515 replies / 37 demos /",
" 23 pilots / 12 paid / 1+ proof pack الأسبوع.",
"════════════════════════════════════════════════",
])
return "\n".join(lines)
def render_json(card: Scorecard) -> str:
return json.dumps(asdict(card), ensure_ascii=False, indent=2)
# ----- CLI -----
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
p = argparse.ArgumentParser(
description="Paid Beta daily scorecard — track manual outreach progress.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" python scripts/paid_beta_daily_scorecard.py "
"--messages 25 --replies 4 --demos 2 --pilots 1 --payments 0 --proof-packs 0\n"
" python scripts/paid_beta_daily_scorecard.py "
"--messages 25 --replies 4 --demos 2 --pilots 1 --payments 1 --proof-packs 0 --json"
),
)
p.add_argument("--messages", type=int, default=0, help="رسائل أُرسلت")
p.add_argument("--replies", type=int, default=0, help="ردود إيجابية")
p.add_argument("--demos", type=int, default=0, help="ديمو محجوز")
p.add_argument("--pilots", type=int, default=0, help="Pilots معروضة")
p.add_argument("--payments", type=int, default=0, help="دفعات وصلت")
p.add_argument("--proof-packs", dest="proof_packs", type=int, default=0,
help="Proof Packs مُسلَّمة")
p.add_argument("--as-of", type=str, default=None,
help="تاريخ (YYYY-MM-DD أو 'today')")
p.add_argument("--json", action="store_true", help="إخراج JSON")
return p.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
as_of = None if (args.as_of in (None, "today")) else args.as_of
card = build_scorecard(
messages=args.messages,
replies=args.replies,
demos=args.demos,
pilots=args.pilots,
payments=args.payments,
proof_packs=args.proof_packs,
as_of=as_of,
)
output = render_json(card) if args.json else render_text(card)
try:
sys.stdout.reconfigure(encoding="utf-8")
except (AttributeError, OSError):
pass
print(output)
return 0
if __name__ == "__main__":
sys.exit(main())