mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-18 07:19:35 +00:00
Builds the full Saudi Autonomous Revenue OS surface as 10 deterministic
modules + a 16-endpoint router under /api/v1/growth-operator/.
Approval-first: every outbound is draft. No live send / charge / calendar
insert from this layer.
MODULES (auto_client_acquisition/growth_operator/)
1. client_profile.py — ClientGrowthProfile + Saudi-default approval
+ compliance rules (no cold WhatsApp, blocked keywords, weekly cap,
quiet_hours_riyadh)
2. contact_importer.py — normalize_phone (Saudi E.164),
dedupe_contacts (richer-record-wins), classify_contact_source
(existing/inbound/event/referral/old_lead/cold/unknown), detect_opt_out
(Arabic + English markers), summarize_import (dashboard report)
3. contactability.py — score_contactability returns
safe/needs_review/blocked with Arabic reasons; default policy:
no cold WhatsApp without lawful basis (PDPL Art.5)
4. targeting.py — segment_contacts, rank_targets (filters unsafe),
recommend_top_10, why_now_stub (deterministic, sector-aware)
5. message_planner.py — draft_arabic_message (Saudi tone, 4-sector
opener bank, no overhyped phrases, always pending_approval),
draft_followup (4 outcome modes), draft_objection_response
(6 indexed Saudi B2B objections with score_delta + next_action)
6. partnership_planner.py — 6 partner types catalog
(agency / consultant / integrator / crm / community / influencer)
+ suggest_partner_types (size/sector aware) + draft_partner_outreach
+ partner_scorecard (platinum/gold/silver/bronze)
7. meeting_planner.py — build_meeting_agenda (15/20-30/45+ min slot
plans), build_calendar_draft (Google Calendar shape, live_inserted=False,
conferenceData for Meet, Asia/Riyadh timezone), build_post_meeting_followup
8. payment_offer.py — sar_to_halalas, build_moyasar_payment_link_draft
(full payload + in-chat message + 4-plan catalog, live_charged=False)
9. proof_pack.py — build_weekly_proof_pack with grade A+/A/B/C/D,
activity/money/quality/best-of sections, dynamic next_week_plan_ar,
markdown export
10. mission_planner.py — 6 GROWTH_MISSIONS (first_10_opportunities ⭐
kill feature, recover_stalled_deals, partnership_sprint,
safe_whatsapp_campaign, meeting_booking_sprint, list_cleanup);
list_missions() + run_mission()
ROUTER (api/routers/growth_operator.py) — 16 endpoints
POST /contacts/import-preview · POST /contactability/score
POST /targets/top-10 · POST /messages/draft · POST /messages/followup
POST /messages/objection-response · POST /partners/suggest
POST /partners/outreach · POST /partners/scorecard
POST /meetings/draft · POST /meetings/post-followup
POST /payment-offer/draft · GET /missions · POST /missions/{id}/run
GET /proof-pack/demo · POST /profile
WIRING: api/main.py adds growth_operator import + router include
(positioned after personal_operator, before public).
DOCS
- docs/ARABIC_GROWTH_OPERATOR_FULL_SPEC.md (NEW): 20-section vision +
customer-type table + upload flow + contactability rules +
WhatsApp/Gmail/Calendar/Moyasar drafts + 6 missions + 16-endpoint
catalog + competitive comparison + beta readiness checklist
TESTS — 50 passing on Python 3.10 venv
tests/unit/test_growth_operator.py covers:
- Phone normalization across 5 input formats including invalid
- Dedupe richer-record invariant
- Source classification (existing/inbound/event/cold/unknown)
- Opt-out detection (Arabic + English notes + status)
- Import summary aggregation
- Contactability: opt-out blocked, cold WhatsApp blocked,
unknown→needs_review, existing→safe, inbound→safe
- Bulk contactability summary
- Top-10 filtering (unsafe excluded), max-cap enforcement
- Segment buckets
- Arabic message: pending_approval invariant + Arabic content
+ no overhyped phrases (banned list)
- Followup approval invariant
- Objection response: known + unknown→diagnostic
- Partner suggestions size-aware (SMB→agency/consultant/community)
- Partner outreach approval invariant
- Partner unknown type returns error
- Partner scorecard tier ordering
- Meeting agenda + calendar draft (live_inserted=False) +
Asia/Riyadh timezone + post-followup pending
- Payment: halalas conversion (1 SAR=100), negative raises,
draft NEVER charges (live_charged=False), unknown plan→error
- Proof pack: grade range + structure + markdown export
- Missions: first_10_opportunities present + kill feature ID
+ run mission known/unknown
- Profile: demo specialized + partial not specialized
+ default compliance blocks 'ضمان 100' + no_cold_whatsapp_without_lawful_basis
VERIFICATION
- 527 unit tests pass (was 477; +50 growth_operator)
- 2 skipped (provider smoke needs API keys)
- AST green on all 13 new files
- Approval invariant holds across every drafting function
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
187 lines
6.8 KiB
Python
187 lines
6.8 KiB
Python
"""
|
|
Contactability — per-contact "can we contact?" decision with PDPL reasons.
|
|
|
|
Default policy: **no cold WhatsApp** without lawful basis.
|
|
PDPL Art.5 emphasizes lawful basis, consent, and purpose limitation.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from auto_client_acquisition.growth_operator.contact_importer import (
|
|
classify_contact_source,
|
|
detect_opt_out,
|
|
normalize_phone,
|
|
)
|
|
|
|
# ── Decision labels ──────────────────────────────────────────────
|
|
CONTACTABILITY_LABELS: tuple[str, ...] = (
|
|
"safe", # consent + lawful basis verified
|
|
"needs_review", # source unclear; pending operator confirmation
|
|
"blocked", # opt-out / banned / invalid / breaches policy
|
|
)
|
|
|
|
|
|
def score_contactability(
|
|
contact: dict[str, Any],
|
|
*,
|
|
channel: str = "whatsapp",
|
|
require_consent_for_cold_whatsapp: bool = True,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Decide whether this contact can be approached on this channel today.
|
|
|
|
Returns:
|
|
{
|
|
"label": "safe"|"needs_review"|"blocked",
|
|
"channel": "...",
|
|
"reasons": [...], # human-readable Arabic reasons
|
|
"next_action": "...", # what the operator should do
|
|
}
|
|
"""
|
|
reasons: list[str] = []
|
|
label: str = "safe"
|
|
|
|
# 1) Opt-out / banned wins everything
|
|
if detect_opt_out(contact):
|
|
return {
|
|
"label": "blocked",
|
|
"channel": channel,
|
|
"reasons": ["العميل سجل opt-out أو محظور — لا تواصل بأي شكل."],
|
|
"next_action": "remove_from_lists",
|
|
}
|
|
|
|
# 2) Phone validity
|
|
phone = normalize_phone(contact.get("phone"))
|
|
if channel == "whatsapp" and not phone:
|
|
return {
|
|
"label": "blocked",
|
|
"channel": channel,
|
|
"reasons": ["لا يوجد رقم صالح — WhatsApp مستحيل."],
|
|
"next_action": "remove_or_collect_phone",
|
|
}
|
|
|
|
# 3) Source classification
|
|
src = classify_contact_source(contact)
|
|
|
|
# Cold WhatsApp without consent → blocked
|
|
if channel == "whatsapp" and require_consent_for_cold_whatsapp:
|
|
if src == "cold_list":
|
|
return {
|
|
"label": "blocked",
|
|
"channel": channel,
|
|
"reasons": [
|
|
"WhatsApp البارد ممنوع بدون lawful basis (PDPL م.5).",
|
|
"السياسة: لا cold WhatsApp افتراضياً.",
|
|
],
|
|
"next_action": "switch_to_email_or_get_consent",
|
|
}
|
|
if src == "unknown":
|
|
return {
|
|
"label": "needs_review",
|
|
"channel": channel,
|
|
"reasons": [
|
|
"مصدر الرقم غير محدد — يحتاج توثيق lawful basis.",
|
|
"ارجع للمشغّل لإقرار العلاقة قبل الإرسال.",
|
|
],
|
|
"next_action": "operator_confirms_source",
|
|
}
|
|
|
|
# 4) Healthy paths
|
|
if src in ("existing_customer", "inbound_lead", "referral"):
|
|
return {
|
|
"label": "safe",
|
|
"channel": channel,
|
|
"reasons": [
|
|
f"علاقة قائمة ({src}) — أساس قانوني قائم لـ business contact.",
|
|
],
|
|
"next_action": "draft_message_with_approval",
|
|
}
|
|
if src == "old_lead":
|
|
last = contact.get("last_contacted_at")
|
|
if last:
|
|
reasons.append("lead سابق — تواصل ضمن نافذة شهور قابلة للتبرير.")
|
|
label = "safe"
|
|
else:
|
|
reasons.append("lead سابق بدون تاريخ تواصل — يحتاج warm-up قصير.")
|
|
label = "needs_review"
|
|
return {
|
|
"label": label,
|
|
"channel": channel,
|
|
"reasons": reasons,
|
|
"next_action": (
|
|
"draft_short_followup_with_approval" if label == "safe"
|
|
else "operator_confirms_continuity"
|
|
),
|
|
}
|
|
if src == "event_lead":
|
|
return {
|
|
"label": "safe",
|
|
"channel": channel,
|
|
"reasons": ["lead من فعالية مع موافقة ضمنية على المتابعة بـ 30 يوم."],
|
|
"next_action": "draft_event_followup_with_approval",
|
|
}
|
|
|
|
# 5) Email channel — more permissive (List-Unsubscribe header makes it safer)
|
|
if channel == "email":
|
|
if src == "unknown":
|
|
return {
|
|
"label": "needs_review",
|
|
"channel": "email",
|
|
"reasons": ["مصدر غير محدد — أرسل عبر إيميل مع List-Unsubscribe إن قبلت."],
|
|
"next_action": "operator_confirms_source",
|
|
}
|
|
return {
|
|
"label": "safe",
|
|
"channel": "email",
|
|
"reasons": [f"مصدر مقبول للإيميل B2B ({src})."],
|
|
"next_action": "draft_email_with_approval",
|
|
}
|
|
|
|
# Fallback (defensive)
|
|
return {
|
|
"label": "needs_review",
|
|
"channel": channel,
|
|
"reasons": ["لا تطابق سياسة معروفة — يحتاج مراجعة المشغّل."],
|
|
"next_action": "operator_review_required",
|
|
}
|
|
|
|
|
|
def contactability_summary(
|
|
contacts: list[dict[str, Any]],
|
|
*,
|
|
channel: str = "whatsapp",
|
|
) -> dict[str, Any]:
|
|
"""Bulk classification report for the upload dashboard."""
|
|
counts: dict[str, int] = {label: 0 for label in CONTACTABILITY_LABELS}
|
|
next_actions: dict[str, int] = {}
|
|
sample_blocked: list[dict[str, Any]] = []
|
|
sample_review: list[dict[str, Any]] = []
|
|
sample_safe: list[dict[str, Any]] = []
|
|
|
|
for c in contacts:
|
|
decision = score_contactability(c, channel=channel)
|
|
counts[decision["label"]] += 1
|
|
next_actions[decision["next_action"]] = next_actions.get(decision["next_action"], 0) + 1
|
|
if decision["label"] == "blocked" and len(sample_blocked) < 5:
|
|
sample_blocked.append({**c, **decision})
|
|
elif decision["label"] == "needs_review" and len(sample_review) < 5:
|
|
sample_review.append({**c, **decision})
|
|
elif decision["label"] == "safe" and len(sample_safe) < 5:
|
|
sample_safe.append({**c, **decision})
|
|
|
|
return {
|
|
"channel": channel,
|
|
"total": len(contacts),
|
|
"by_label": counts,
|
|
"by_next_action": next_actions,
|
|
"sample_safe": sample_safe,
|
|
"sample_review": sample_review,
|
|
"sample_blocked": sample_blocked,
|
|
"policy_note": (
|
|
"لا cold WhatsApp بدون lawful basis — السياسة الافتراضية. "
|
|
"العميل يقدر يعدل القاعدة لكل قائمة بعد توثيق المصدر."
|
|
),
|
|
}
|