diff --git a/dealix/api/main.py b/dealix/api/main.py
index 12478690..b5ccee52 100644
--- a/dealix/api/main.py
+++ b/dealix/api/main.py
@@ -48,6 +48,9 @@ from api.routers import (
sales,
sectors,
security_curator,
+ service_excellence,
+ service_tower,
+ targeting_os,
v3,
webhooks,
)
@@ -164,6 +167,9 @@ def create_app() -> FastAPI:
app.include_router(model_router.router)
app.include_router(connector_catalog.router)
app.include_router(agent_observability.router)
+ app.include_router(targeting_os.router)
+ app.include_router(service_tower.router)
+ app.include_router(service_excellence.router)
app.include_router(public.router)
app.include_router(admin.router)
diff --git a/dealix/api/routers/service_excellence.py b/dealix/api/routers/service_excellence.py
new file mode 100644
index 00000000..45a8e8c8
--- /dev/null
+++ b/dealix/api/routers/service_excellence.py
@@ -0,0 +1,179 @@
+"""Service Excellence OS router — feature matrix + score + gates + research."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from fastapi import APIRouter, Body
+
+from auto_client_acquisition.service_excellence import (
+ build_backlog,
+ build_demo_script,
+ build_feature_matrix,
+ build_landing_page_outline,
+ build_monthly_service_review,
+ build_onboarding_checklist,
+ build_proof_pack_template_excellence,
+ build_sales_script,
+ build_service_launch_package,
+ build_service_research_brief,
+ calculate_service_excellence_score,
+ calculate_service_roi_estimate,
+ classify_features,
+ compare_against_categories,
+ convert_feedback_to_backlog,
+ generate_feature_hypotheses,
+ prioritize_backlog_items,
+ recommend_missing_features,
+ recommend_next_experiments,
+ recommend_weekly_improvements,
+ required_proof_metrics,
+ review_service_before_launch,
+ summarize_proof_ar,
+)
+from auto_client_acquisition.service_tower import ALL_SERVICES
+
+router = APIRouter(prefix="/api/v1/service-excellence", tags=["service-excellence"])
+
+
+# ── Feature matrix ───────────────────────────────────────────
+@router.get("/{service_id}/feature-matrix")
+async def feature_matrix(service_id: str) -> dict[str, Any]:
+ return build_feature_matrix(service_id)
+
+
+@router.get("/{service_id}/feature-classification")
+async def feature_classification(service_id: str) -> dict[str, Any]:
+ return classify_features(service_id)
+
+
+@router.get("/{service_id}/missing-features")
+async def missing_features(service_id: str) -> dict[str, Any]:
+ return {"recommendations": recommend_missing_features(service_id)}
+
+
+# ── Scoring ──────────────────────────────────────────────────
+@router.get("/{service_id}/score")
+async def score(service_id: str) -> dict[str, Any]:
+ return calculate_service_excellence_score(service_id)
+
+
+# ── Gates / quality review ──────────────────────────────────
+@router.get("/{service_id}/quality-review")
+async def quality_review(service_id: str) -> dict[str, Any]:
+ return review_service_before_launch(service_id)
+
+
+@router.get("/review/all")
+async def review_all() -> dict[str, Any]:
+ """Review every catalogued service."""
+ out = [review_service_before_launch(s.id) for s in ALL_SERVICES]
+ counts: dict[str, int] = {}
+ for r in out:
+ v = str(r.get("verdict", "?"))
+ counts[v] = counts.get(v, 0) + 1
+ return {"total": len(out), "by_verdict": counts, "results": out}
+
+
+# ── Proof metrics ────────────────────────────────────────────
+@router.get("/{service_id}/proof-metrics")
+async def proof_metrics(service_id: str) -> dict[str, Any]:
+ return {
+ "service_id": service_id,
+ "metrics": required_proof_metrics(service_id),
+ "template": build_proof_pack_template_excellence(service_id),
+ }
+
+
+@router.post("/{service_id}/roi-estimate")
+async def roi_estimate(
+ service_id: str,
+ metrics: dict[str, Any] = Body(default_factory=dict),
+) -> dict[str, Any]:
+ out = calculate_service_roi_estimate(service_id, metrics)
+ if "error" not in out:
+ out["proof_summary_ar"] = summarize_proof_ar(service_id, metrics)
+ return out
+
+
+# ── Competitor gap ───────────────────────────────────────────
+@router.get("/{service_id}/gap-analysis")
+async def gap_analysis(service_id: str) -> dict[str, Any]:
+ return compare_against_categories(service_id)
+
+
+# ── Research lab ─────────────────────────────────────────────
+@router.get("/{service_id}/research-brief")
+async def research_brief(service_id: str) -> dict[str, Any]:
+ return build_service_research_brief(service_id)
+
+
+@router.get("/{service_id}/feature-hypotheses")
+async def feature_hypotheses(service_id: str) -> dict[str, Any]:
+ return {"hypotheses": generate_feature_hypotheses(service_id)}
+
+
+@router.get("/{service_id}/experiments")
+async def experiments(service_id: str) -> dict[str, Any]:
+ return recommend_next_experiments(service_id)
+
+
+@router.get("/{service_id}/monthly-review")
+async def monthly_review(service_id: str) -> dict[str, Any]:
+ return build_monthly_service_review(service_id)
+
+
+# ── Backlog ──────────────────────────────────────────────────
+@router.get("/{service_id}/backlog")
+async def backlog(service_id: str) -> dict[str, Any]:
+ return build_backlog(service_id)
+
+
+@router.post("/{service_id}/backlog/from-feedback")
+async def backlog_from_feedback(
+ service_id: str,
+ feedback: list[dict[str, Any]] = Body(..., embed=True),
+) -> dict[str, Any]:
+ return {
+ "service_id": service_id,
+ "items": convert_feedback_to_backlog(feedback),
+ }
+
+
+@router.post("/{service_id}/backlog/prioritize")
+async def backlog_prioritize(
+ service_id: str,
+ items: list[dict[str, Any]] = Body(..., embed=True),
+) -> dict[str, Any]:
+ return {"items": prioritize_backlog_items(items)}
+
+
+@router.get("/{service_id}/weekly-improvements")
+async def weekly_improvements(service_id: str) -> dict[str, Any]:
+ return recommend_weekly_improvements(service_id)
+
+
+# ── Launch package ───────────────────────────────────────────
+@router.get("/{service_id}/launch-package")
+async def launch_package(service_id: str) -> dict[str, Any]:
+ return build_service_launch_package(service_id)
+
+
+@router.get("/{service_id}/landing-outline")
+async def landing_outline(service_id: str) -> dict[str, Any]:
+ return build_landing_page_outline(service_id)
+
+
+@router.get("/{service_id}/sales-script")
+async def sales_script(service_id: str) -> dict[str, Any]:
+ return build_sales_script(service_id)
+
+
+@router.get("/{service_id}/demo-script")
+async def demo_script(service_id: str) -> dict[str, Any]:
+ return build_demo_script(service_id)
+
+
+@router.get("/{service_id}/onboarding-checklist")
+async def onboarding_checklist(service_id: str) -> dict[str, Any]:
+ return build_onboarding_checklist(service_id)
diff --git a/dealix/api/routers/service_tower.py b/dealix/api/routers/service_tower.py
new file mode 100644
index 00000000..ac96d9bd
--- /dev/null
+++ b/dealix/api/routers/service_tower.py
@@ -0,0 +1,177 @@
+"""Service Tower router — كتالوج الخدمات + wizard + workflow + pricing + cards."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from fastapi import APIRouter, Body
+
+from auto_client_acquisition.service_tower import (
+ build_ceo_daily_service_brief,
+ build_client_report_outline,
+ build_deliverables,
+ build_end_of_day_service_report,
+ build_intake_questions,
+ build_internal_operator_checklist,
+ build_proof_pack_template,
+ build_risk_alert_card,
+ build_service_approval_card,
+ build_service_scorecard,
+ build_service_workflow,
+ build_upsell_message_ar,
+ calculate_monthly_offer,
+ calculate_setup_fee,
+ catalog_summary,
+ get_service,
+ list_all_services,
+ map_service_to_growth_mission,
+ map_service_to_subscription,
+ quote_service,
+ recommend_next_step,
+ recommend_plan_after_service,
+ recommend_service,
+ recommend_upgrade,
+ summarize_recommendation_ar,
+ summarize_scorecard_ar,
+ validate_service_inputs,
+)
+
+router = APIRouter(prefix="/api/v1/services", tags=["service-tower"])
+
+
+# ── Catalog ──────────────────────────────────────────────────
+@router.get("/catalog")
+async def catalog() -> dict[str, Any]:
+ return list_all_services()
+
+
+@router.get("/summary")
+async def summary() -> dict[str, Any]:
+ return catalog_summary()
+
+
+@router.post("/recommend")
+async def recommend(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ rec = recommend_service(
+ company_type=payload.get("company_type", ""),
+ goal=payload.get("goal", "fill_pipeline"),
+ has_contact_list=bool(payload.get("has_contact_list", False)),
+ channels=payload.get("channels", []),
+ budget_sar=int(payload.get("budget_sar", 1000)),
+ )
+ rec["summary_ar"] = summarize_recommendation_ar(rec)
+ return rec
+
+
+# ── Per-service ──────────────────────────────────────────────
+@router.get("/{service_id}/intake-questions")
+async def service_intake_questions(service_id: str) -> dict[str, Any]:
+ return build_intake_questions(service_id)
+
+
+@router.post("/{service_id}/start")
+async def service_start(
+ service_id: str,
+ payload: dict[str, Any] = Body(...),
+) -> dict[str, Any]:
+ validation = validate_service_inputs(service_id, payload)
+ if not validation["valid"]:
+ return {"started": False, "validation": validation}
+ workflow = build_service_workflow(service_id)
+ return {
+ "started": True,
+ "validation": validation,
+ "workflow": workflow,
+ "linked_growth_mission": map_service_to_growth_mission(service_id),
+ "approval_required": True,
+ }
+
+
+@router.get("/{service_id}/workflow")
+async def service_workflow(service_id: str) -> dict[str, Any]:
+ return build_service_workflow(service_id)
+
+
+@router.get("/{service_id}/deliverables")
+async def service_deliverables(service_id: str) -> dict[str, Any]:
+ return build_deliverables(service_id)
+
+
+@router.get("/{service_id}/proof-pack-template")
+async def service_proof_pack_template(service_id: str) -> dict[str, Any]:
+ return build_proof_pack_template(service_id)
+
+
+@router.get("/{service_id}/client-report-outline")
+async def service_client_report_outline(service_id: str) -> dict[str, Any]:
+ return build_client_report_outline(service_id)
+
+
+@router.get("/{service_id}/operator-checklist")
+async def service_operator_checklist(service_id: str) -> dict[str, Any]:
+ return build_internal_operator_checklist(service_id)
+
+
+@router.post("/{service_id}/quote")
+async def service_quote(
+ service_id: str,
+ payload: dict[str, Any] = Body(default_factory=dict),
+) -> dict[str, Any]:
+ return quote_service(
+ service_id,
+ company_size=payload.get("company_size", "small"),
+ urgency=payload.get("urgency", "normal"),
+ channels_count=int(payload.get("channels_count", 1)),
+ )
+
+
+@router.get("/{service_id}/setup-fee")
+async def service_setup_fee(service_id: str) -> dict[str, Any]:
+ return calculate_setup_fee(service_id)
+
+
+@router.get("/{service_id}/monthly-offer")
+async def service_monthly_offer(service_id: str) -> dict[str, Any]:
+ return calculate_monthly_offer(service_id)
+
+
+@router.post("/{service_id}/scorecard")
+async def service_scorecard(
+ service_id: str,
+ metrics: dict[str, Any] = Body(default_factory=dict),
+) -> dict[str, Any]:
+ return build_service_scorecard(service_id, metrics)
+
+
+@router.get("/{service_id}/upgrade-path")
+async def service_upgrade_path(service_id: str) -> dict[str, Any]:
+ return recommend_upgrade(service_id)
+
+
+@router.get("/{service_id}/post-service-plan")
+async def service_post_plan(service_id: str) -> dict[str, Any]:
+ return recommend_plan_after_service(service_id)
+
+
+# ── CEO control via WhatsApp ─────────────────────────────────
+@router.get("/ceo/daily-brief")
+async def ceo_daily_brief() -> dict[str, Any]:
+ return build_ceo_daily_service_brief()
+
+
+@router.post("/ceo/approval-card")
+async def ceo_approval_card(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return build_service_approval_card(
+ service_id=payload.get("service_id", ""),
+ action=payload.get("action", ""),
+ )
+
+
+@router.get("/ceo/risk-alert/demo")
+async def ceo_risk_alert_demo() -> dict[str, Any]:
+ return build_risk_alert_card()
+
+
+@router.get("/ceo/end-of-day/demo")
+async def ceo_end_of_day_demo() -> dict[str, Any]:
+ return build_end_of_day_service_report()
diff --git a/dealix/api/routers/targeting_os.py b/dealix/api/routers/targeting_os.py
new file mode 100644
index 00000000..11242d78
--- /dev/null
+++ b/dealix/api/routers/targeting_os.py
@@ -0,0 +1,240 @@
+"""Targeting & Acquisition OS router."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from fastapi import APIRouter, Body
+
+from auto_client_acquisition.targeting_os import (
+ analyze_uploaded_list_preview,
+ build_dealix_self_growth_plan,
+ build_daily_targeting_brief,
+ build_end_of_day_report,
+ build_followup_sequence,
+ build_free_growth_diagnostic,
+ build_lead_gen_form_plan,
+ build_outreach_plan,
+ build_self_growth_daily_brief,
+ build_weekly_learning_report,
+ calculate_channel_reputation,
+ draft_b2b_email,
+ draft_role_based_angle,
+ draft_whatsapp_message,
+ enforce_daily_limits,
+ evaluate_contactability,
+ explain_contactability_ar,
+ list_targeting_services,
+ map_buying_committee,
+ recommend_accounts,
+ recommend_dealix_targets,
+ recommend_linkedin_strategy,
+ recommend_recovery_action,
+ recommend_service_offer,
+ recommend_today_actions,
+ score_email_risk,
+ score_whatsapp_risk,
+ summarize_plan_ar,
+ summarize_reputation_ar,
+)
+from auto_client_acquisition.targeting_os.contract_drafts import (
+ draft_agency_partner_outline,
+ draft_dpa_outline,
+ draft_pilot_agreement_outline,
+ draft_referral_agreement_outline,
+)
+
+router = APIRouter(prefix="/api/v1/targeting", tags=["targeting-os"])
+
+
+# ── Accounts ─────────────────────────────────────────────────
+@router.post("/accounts/recommend")
+async def accounts_recommend(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return recommend_accounts(
+ sector=payload.get("sector", "saas"),
+ city=payload.get("city", "Riyadh"),
+ offer=payload.get("offer", ""),
+ goal=payload.get("goal", "fill_pipeline"),
+ limit=int(payload.get("limit", 10)),
+ )
+
+
+# ── Buying committee ─────────────────────────────────────────
+@router.post("/buying-committee/map")
+async def buying_committee_map(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return map_buying_committee(
+ sector=payload.get("sector", "saas"),
+ company_size=payload.get("company_size", "small"),
+ goal=payload.get("goal", "fill_pipeline"),
+ )
+
+
+# ── Contacts ─────────────────────────────────────────────────
+@router.post("/contacts/evaluate")
+async def contacts_evaluate(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ contact = payload.get("contact") or payload
+ desired = payload.get("desired_channel")
+ result = evaluate_contactability(contact, desired_channel=desired)
+ result["explanation_ar"] = explain_contactability_ar(result)
+ return result
+
+
+@router.post("/uploaded-list/analyze")
+async def uploaded_list_analyze(
+ contacts: list[dict[str, Any]] = Body(..., embed=True),
+) -> dict[str, Any]:
+ return analyze_uploaded_list_preview(contacts)
+
+
+# ── Outreach ─────────────────────────────────────────────────
+@router.post("/outreach/plan")
+async def outreach_plan(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ plan = build_outreach_plan(
+ targets=payload.get("targets", []),
+ channels=payload.get("channels"),
+ goal=payload.get("goal", "fill_pipeline"),
+ )
+ plan = enforce_daily_limits(plan)
+ plan["summary_ar"] = summarize_plan_ar(plan)
+ return plan
+
+
+# ── Daily autopilot ──────────────────────────────────────────
+@router.get("/daily-autopilot/demo")
+async def daily_autopilot_demo() -> dict[str, Any]:
+ return {
+ "brief": build_daily_targeting_brief(),
+ "today_actions": recommend_today_actions(),
+ "end_of_day_template": build_end_of_day_report(),
+ }
+
+
+# ── Self-Growth Mode ─────────────────────────────────────────
+@router.get("/self-growth/demo")
+async def self_growth_demo() -> dict[str, Any]:
+ return {
+ "plan": build_dealix_self_growth_plan(),
+ "today": build_self_growth_daily_brief(),
+ }
+
+
+@router.post("/self-growth/targets")
+async def self_growth_targets(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return recommend_dealix_targets(
+ sector_focus=payload.get("sector"),
+ city_focus=payload.get("city"),
+ limit=int(payload.get("limit", 10)),
+ )
+
+
+@router.post("/self-growth/weekly-report")
+async def self_growth_weekly(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_weekly_learning_report(payload)
+
+
+# ── Reputation guard ────────────────────────────────────────
+@router.get("/reputation/status")
+async def reputation_status() -> dict[str, Any]:
+ """Demo reputation snapshot."""
+ healthy_email = {"bounce_rate": 0.005, "complaint_rate": 0.0001,
+ "opt_out_rate": 0.01, "reply_rate": 0.04}
+ risky_wa = {"block_rate": 0.04, "report_rate": 0.005,
+ "opt_out_rate": 0.06, "reply_rate": 0.02}
+ return {
+ "email": calculate_channel_reputation(healthy_email, channel="email"),
+ "whatsapp": calculate_channel_reputation(risky_wa, channel="whatsapp"),
+ }
+
+
+@router.post("/reputation/recovery")
+async def reputation_recovery(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return recommend_recovery_action(
+ payload.get("metrics", {}),
+ channel=payload.get("channel", "email"),
+ )
+
+
+# ── LinkedIn strategy ────────────────────────────────────────
+@router.post("/linkedin/strategy")
+async def linkedin_strategy(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ strategy = recommend_linkedin_strategy(
+ segment=payload.get("segment", "B2B Saudi"),
+ goal=payload.get("goal", "fill_pipeline"),
+ )
+ if payload.get("with_lead_gen_form"):
+ strategy["lead_gen_form_plan"] = build_lead_gen_form_plan(
+ segment=payload.get("segment", "B2B Saudi"),
+ offer=payload.get("offer", "Pilot 7 days"),
+ campaign_name=payload.get("campaign_name", ""),
+ )
+ return strategy
+
+
+# ── Drafts ───────────────────────────────────────────────────
+@router.post("/drafts/email")
+async def drafts_email(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ contact = payload.get("contact", {})
+ draft = draft_b2b_email(
+ contact,
+ offer=payload.get("offer", ""),
+ why_now=payload.get("why_now", ""),
+ )
+ risk = score_email_risk(contact, draft.get("body_ar", ""))
+ return {**draft, "risk": risk}
+
+
+@router.post("/drafts/whatsapp")
+async def drafts_whatsapp(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ contact = payload.get("contact", {})
+ return draft_whatsapp_message(
+ contact,
+ offer=payload.get("offer", ""),
+ why_now=payload.get("why_now", ""),
+ )
+
+
+@router.post("/drafts/email-followup")
+async def drafts_email_followup(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return build_followup_sequence(
+ payload.get("contact", {}),
+ offer=payload.get("offer", ""),
+ )
+
+
+@router.post("/drafts/role-angle")
+async def drafts_role_angle(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return draft_role_based_angle(
+ role_key=payload.get("role_key", "founder_ceo"),
+ sector=payload.get("sector", "saas"),
+ offer=payload.get("offer", ""),
+ )
+
+
+# ── Free diagnostic ──────────────────────────────────────────
+@router.post("/free-diagnostic")
+async def free_diagnostic(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return build_free_growth_diagnostic(payload)
+
+
+# ── Services + contracts ─────────────────────────────────────
+@router.get("/services")
+async def services_list() -> dict[str, Any]:
+ return list_targeting_services()
+
+
+@router.post("/services/recommend")
+async def services_recommend(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return recommend_service_offer(
+ customer_type=payload.get("customer_type", ""),
+ goal=payload.get("goal", "fill_pipeline"),
+ )
+
+
+@router.get("/contracts/templates")
+async def contracts_templates() -> dict[str, Any]:
+ return {
+ "pilot": draft_pilot_agreement_outline(),
+ "dpa": draft_dpa_outline(),
+ "referral": draft_referral_agreement_outline(),
+ "agency_partner": draft_agency_partner_outline(),
+ }
diff --git a/dealix/auto_client_acquisition/service_excellence/__init__.py b/dealix/auto_client_acquisition/service_excellence/__init__.py
new file mode 100644
index 00000000..ca90f12b
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_excellence/__init__.py
@@ -0,0 +1,85 @@
+"""Service Excellence OS — يضمن أن كل خدمة هي الأفضل قبل الإطلاق.
+
+Feature matrix + scoring + workflow validation + competitor gap +
+proof metrics + quality review + improvement backlog + launch package.
+"""
+
+from __future__ import annotations
+
+from .competitor_gap import compare_against_categories
+from .feature_matrix import (
+ build_feature_matrix,
+ classify_features,
+ prioritize_features,
+ recommend_missing_features,
+)
+from .launch_package import (
+ build_demo_script,
+ build_landing_page_outline,
+ build_onboarding_checklist,
+ build_sales_script,
+ build_service_launch_package,
+)
+from .proof_metrics import (
+ build_proof_pack_template_excellence,
+ calculate_service_roi_estimate,
+ required_proof_metrics,
+ summarize_proof_ar,
+)
+from .quality_review import (
+ block_if_missing_approval_policy,
+ block_if_missing_proof,
+ block_if_unclear_pricing,
+ block_if_unsafe_channel,
+ review_service_before_launch,
+)
+from .research_lab import (
+ build_monthly_service_review,
+ build_service_research_brief,
+ generate_feature_hypotheses,
+ recommend_next_experiments,
+)
+from .service_improvement_backlog import (
+ build_backlog,
+ convert_feedback_to_backlog,
+ prioritize_backlog_items,
+ recommend_weekly_improvements,
+)
+from .service_scoring import (
+ calculate_service_excellence_score,
+ score_automation,
+ score_clarity,
+ score_compliance,
+ score_proof,
+ score_speed_to_value,
+ score_upsell,
+)
+
+__all__ = [
+ # competitor_gap
+ "compare_against_categories",
+ # feature_matrix
+ "build_feature_matrix", "classify_features",
+ "prioritize_features", "recommend_missing_features",
+ # launch_package
+ "build_demo_script", "build_landing_page_outline",
+ "build_onboarding_checklist", "build_sales_script",
+ "build_service_launch_package",
+ # proof_metrics
+ "build_proof_pack_template_excellence", "calculate_service_roi_estimate",
+ "required_proof_metrics", "summarize_proof_ar",
+ # quality_review
+ "block_if_missing_approval_policy", "block_if_missing_proof",
+ "block_if_unclear_pricing", "block_if_unsafe_channel",
+ "review_service_before_launch",
+ # research_lab
+ "build_monthly_service_review", "build_service_research_brief",
+ "generate_feature_hypotheses", "recommend_next_experiments",
+ # service_improvement_backlog
+ "build_backlog", "convert_feedback_to_backlog",
+ "prioritize_backlog_items", "recommend_weekly_improvements",
+ # service_scoring
+ "calculate_service_excellence_score", "score_automation",
+ "score_clarity", "score_compliance", "score_proof",
+ "score_speed_to_value", "score_upsell",
+]
diff --git a/dealix/auto_client_acquisition/service_excellence/competitor_gap.py b/dealix/auto_client_acquisition/service_excellence/competitor_gap.py
new file mode 100644
index 00000000..9e6dfb80
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_excellence/competitor_gap.py
@@ -0,0 +1,79 @@
+"""Competitor gap analysis — لا scraping، فقط مقارنة structural بفئات معروفة."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from auto_client_acquisition.service_tower import get_service
+
+# Categories Dealix competes against. Strengths/limits are public knowledge.
+COMPETITOR_CATEGORIES: dict[str, dict[str, list[str]]] = {
+ "crm": {
+ "strengths": ["تخزين بيانات", "pipeline tracking", "تكاملات واسعة"],
+ "limits": ["ينتظر إدخال يدوي", "لا يقرر ما تفعل اليوم",
+ "غير مصمم للسوق العربي"],
+ },
+ "whatsapp_tools": {
+ "strengths": ["إرسال جماعي", "templates", "broadcast"],
+ "limits": ["لا approval-first", "لا proof", "خطر PDPL"],
+ },
+ "email_assistant": {
+ "strengths": ["كتابة أسرع", "تكامل Gmail/Outlook"],
+ "limits": ["لا يحول الإيميل لـ pipeline", "لا proof", "عام غير عربي"],
+ },
+ "linkedin_tools": {
+ "strengths": ["إيجاد leads"],
+ "limits": ["كثير منها يخالف ToS", "auto-DM يوقف الحسابات",
+ "لا يحترم PDPL"],
+ },
+ "agency": {
+ "strengths": ["خبرة بشرية", "علاقات سوق"],
+ "limits": ["لا تتوسع", "غير قابلة للتكرار", "تعتمد على الفريق"],
+ },
+ "revenue_intelligence": {
+ "strengths": ["تحليل المكالمات", "deal scoring"],
+ "limits": ["تبدأ بعد الـcall", "لا يصنع pipeline من الصفر"],
+ },
+ "generic_ai_agent": {
+ "strengths": ["مرن", "يكتب أي شيء"],
+ "limits": ["بدون سياق شركة", "بدون proof", "بدون امتثال محلي"],
+ },
+}
+
+
+def compare_against_categories(service_id: str) -> dict[str, Any]:
+ """Compare a Dealix service against generic competitor categories."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+
+ dealix_advantages = [
+ "موجّه للسوق السعودي بالعربية الطبيعية.",
+ "Approval-first — لا يضرّ سمعة العميل.",
+ "Proof Pack شهري قابل للقياس.",
+ "Multi-channel orchestration بـ سياسة موحدة.",
+ "Self-improving Curator يحسّن الرسائل أسبوعياً.",
+ "PDPL-aware من اليوم الأول.",
+ ]
+
+ gaps_to_close: list[str] = []
+ if "growth_os" not in service_id:
+ gaps_to_close.append("Daily autopilot كامل (متاح في Growth OS).")
+ if service_id == "free_growth_diagnostic":
+ gaps_to_close.append("Proof Pack حقيقي بعد 30 يوم.")
+
+ do_not_copy = [
+ "auto-DM على LinkedIn (مخالف).",
+ "scraping ضد ToS.",
+ "وعود بنتائج مضمونة.",
+ "مفاتيح API غير محمية في الواجهة.",
+ ]
+
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "competitor_categories": COMPETITOR_CATEGORIES,
+ "dealix_advantages_ar": dealix_advantages,
+ "gaps_to_close_ar": gaps_to_close,
+ "do_not_copy_ar": do_not_copy,
+ }
diff --git a/dealix/auto_client_acquisition/service_excellence/feature_matrix.py b/dealix/auto_client_acquisition/service_excellence/feature_matrix.py
new file mode 100644
index 00000000..c745e144
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_excellence/feature_matrix.py
@@ -0,0 +1,120 @@
+"""Feature matrix per service — must_have / advanced / premium / future."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from auto_client_acquisition.service_tower import get_service
+
+# 12 must-have features every premium Dealix service should ship with.
+DEFAULT_MUST_HAVE: tuple[dict[str, object], ...] = (
+ {"name_ar": "Self-Serve Intake", "value_ar": "العميل يبدأ بدون مكالمة.",
+ "complexity": 2, "risk": 1, "proof_metric": "intake_completion_rate"},
+ {"name_ar": "AI Recommendation",
+ "value_ar": "النظام يوصي بالخدمة المناسبة من إجابات بسيطة.",
+ "complexity": 3, "risk": 2, "proof_metric": "wizard_acceptance_rate"},
+ {"name_ar": "Data Quality Check",
+ "value_ar": "لا يستخدم بيانات سيئة.",
+ "complexity": 3, "risk": 4, "proof_metric": "data_quality_score"},
+ {"name_ar": "Contactability / Risk Gate",
+ "value_ar": "يمنع التواصل الخطر تلقائياً.",
+ "complexity": 4, "risk": 8, "proof_metric": "risks_blocked"},
+ {"name_ar": "Channel Strategy",
+ "value_ar": "يختار القناة الأفضل لكل contact.",
+ "complexity": 4, "risk": 5, "proof_metric": "channel_success_rate"},
+ {"name_ar": "Arabic Contextual Drafting",
+ "value_ar": "رسائل سعودية، ليست ترجمة.",
+ "complexity": 5, "risk": 3, "proof_metric": "saudi_tone_score"},
+ {"name_ar": "Approval Cards",
+ "value_ar": "CEO/Growth Manager يوافق من واتساب.",
+ "complexity": 3, "risk": 2, "proof_metric": "approval_rate"},
+ {"name_ar": "Execution Mode",
+ "value_ar": "draft/export/approved فقط — لا live بدون env flag.",
+ "complexity": 3, "risk": 9, "proof_metric": "live_send_violations"},
+ {"name_ar": "Proof Pack",
+ "value_ar": "تقرير قيمة محسوب.",
+ "complexity": 4, "risk": 1, "proof_metric": "proof_pack_delivered"},
+ {"name_ar": "Learning Loop",
+ "value_ar": "يتعلم من Accept/Skip/Edit.",
+ "complexity": 5, "risk": 2, "proof_metric": "accept_rate_30d"},
+ {"name_ar": "Upsell Path",
+ "value_ar": "يقود للخدمة الأعلى.",
+ "complexity": 2, "risk": 1, "proof_metric": "upsell_conversion_rate"},
+ {"name_ar": "Service Score",
+ "value_ar": "يقيس نجاح الخدمة نفسها.",
+ "complexity": 3, "risk": 1, "proof_metric": "service_excellence_score"},
+)
+
+# Service-specific premium features.
+_PREMIUM_BY_SERVICE: dict[str, list[dict[str, object]]] = {
+ "growth_os_monthly": [
+ {"name_ar": "Daily Autopilot", "value_ar": "تشغيل ذاتي يومي.",
+ "complexity": 6, "risk": 4, "proof_metric": "daily_decisions_made"},
+ {"name_ar": "Revenue Leak Detector",
+ "value_ar": "كشف التسريبات تلقائياً.",
+ "complexity": 5, "risk": 2, "proof_metric": "leaks_detected"},
+ {"name_ar": "Founder Shadow Board",
+ "value_ar": "موجز أسبوعي مركّب.",
+ "complexity": 4, "risk": 1, "proof_metric": "weekly_briefs_delivered"},
+ ],
+ "agency_partner_program": [
+ {"name_ar": "Co-Branded Proof Pack", "value_ar": "Proof بعلامة الوكالة.",
+ "complexity": 4, "risk": 2, "proof_metric": "co_branded_proofs"},
+ {"name_ar": "Revenue Share Dashboard",
+ "value_ar": "لوحة مشاركة الإيرادات.",
+ "complexity": 5, "risk": 3, "proof_metric": "agency_revenue_sar"},
+ ],
+}
+
+
+def build_feature_matrix(service_id: str) -> dict[str, Any]:
+ """Build the full feature matrix for a service."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ must_have = [dict(f) for f in DEFAULT_MUST_HAVE]
+ premium = list(_PREMIUM_BY_SERVICE.get(service_id, []))
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "must_have": must_have,
+ "advanced": premium,
+ "premium": premium,
+ "future": [],
+ "total_features": len(must_have) + len(premium),
+ }
+
+
+def classify_features(service_id: str) -> dict[str, list[str]]:
+ """Classify a service's features into tiers."""
+ matrix = build_feature_matrix(service_id)
+ if "error" in matrix:
+ return {}
+ return {
+ "must_have": [str(f["name_ar"]) for f in matrix["must_have"]],
+ "advanced": [str(f["name_ar"]) for f in matrix["advanced"]],
+ "premium": [str(f["name_ar"]) for f in matrix["premium"]],
+ }
+
+
+def recommend_missing_features(service_id: str) -> list[dict[str, Any]]:
+ """Recommend features the service may be missing."""
+ matrix = build_feature_matrix(service_id)
+ if "error" in matrix:
+ return []
+ # If the service has fewer than 12 must-haves, suggest the rest.
+ if len(matrix["must_have"]) >= 12:
+ return []
+ return [{"name_ar": "Add to advanced tier",
+ "rationale_ar": "خدمة قوية تستفيد من ميزات advanced."}]
+
+
+def prioritize_features(features: list[dict[str, Any]]) -> list[dict[str, Any]]:
+ """Order features by (lower complexity, lower risk, higher impact)."""
+ return sorted(
+ features,
+ key=lambda f: (
+ int(f.get("complexity", 9)),
+ int(f.get("risk", 9)),
+ ),
+ )
diff --git a/dealix/auto_client_acquisition/service_excellence/launch_package.py b/dealix/auto_client_acquisition/service_excellence/launch_package.py
new file mode 100644
index 00000000..3d728fb5
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_excellence/launch_package.py
@@ -0,0 +1,125 @@
+"""Launch package — لكل خدمة: landing page outline + sales script + demo + onboarding."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from auto_client_acquisition.service_tower import get_service
+
+
+def build_landing_page_outline(service_id: str) -> dict[str, Any]:
+ """Outline of a landing page for the service (Arabic, RTL)."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ return {
+ "service_id": service_id,
+ "title_ar": s.name_ar,
+ "sections_ar": [
+ "Hero: العرض في جملة + CTA",
+ "وعد المنتج: ماذا سيحصل العميل عليه؟",
+ "كيف تعمل الخدمة (3 خطوات)",
+ "Deliverables — قائمة بالمخرجات",
+ "Pricing — السعر بوضوح",
+ "Proof — ما الذي نقيسه",
+ "Safety — لا live send، Approval-first",
+ "Trust — للوكالات / B2B سعودي",
+ "FAQ",
+ "CTA النهائي",
+ ],
+ "cta_ar": "ابدأ الآن" if s.pricing_max_sar > 0 else "احجز التشخيص المجاني",
+ "must_include_ar": [
+ "Approval-first.",
+ "لا cold WhatsApp.",
+ "PDPL-aware.",
+ "لا وعود بنتائج مضمونة.",
+ ],
+ }
+
+
+def build_sales_script(service_id: str) -> dict[str, Any]:
+ """Sales script (Arabic) — discovery → pitch → close."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ return {
+ "service_id": service_id,
+ "discovery_questions_ar": [
+ "وش أكبر تحدي نمو لديكم اليوم؟",
+ "كيف تستهدفون اليوم؟ ما الذي يعمل؟",
+ "ما الذي يأخذ وقتاً يومياً ولا يثبت قيمة؟",
+ "هل عندكم قائمة عملاء قدامى لم تتم متابعتهم؟",
+ "من يوافق على الرسائل قبل الإرسال؟",
+ ],
+ "pitch_ar": (
+ f"بناءً على ما شاركته، {s.name_ar} مناسبة لكم. "
+ f"خلال {('7 أيام' if s.pricing_model == 'sprint' else 'الشهر الأول')}، "
+ f"سنطلع لكم: {', '.join(s.deliverables_ar)}."
+ ),
+ "objection_handling_ar": {
+ "price": "نقدم Free Diagnostic أولاً — تشوفون النتائج قبل الدفع.",
+ "timing": "Pilot 7 أيام لا يحتاج التزام طويل — جرّبوه ثم قرروا.",
+ "trust": "Approval-first: لا نرسل أي شيء بدون موافقتكم.",
+ "complexity": "نتولى الإعداد كاملاً في 3 أيام عمل.",
+ },
+ "close_ar": (
+ "إذا الفكرة منطقية، أحدد لكم Pilot يبدأ يوم الأحد. "
+ "أرسل لي تأكيد + اسم منسّق Approvals."
+ ),
+ }
+
+
+def build_demo_script(service_id: str) -> dict[str, Any]:
+ """12-minute Arabic demo script."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ return {
+ "service_id": service_id,
+ "duration_minutes": 12,
+ "minute_by_minute_ar": [
+ "0–2: الفكرة الكبرى — Dealix ليس CRM ولا أداة واتساب.",
+ f"2–4: عرض {s.name_ar} — Daily Brief / Command Feed.",
+ "4–6: مثال حي — 10 فرص في 10 دقائق.",
+ "6–8: Trust Score + Simulator + Proof Pack.",
+ "8–10: الأمان والتكاملات (security + connectors).",
+ "10–12: العرض والـ CTA.",
+ ],
+ "do_not_do_ar": [
+ "لا تكشف API keys على الشاشة.",
+ "لا تشغّل live WhatsApp في الـdemo.",
+ "لا تعد بأرقام لم تُحقَّق.",
+ ],
+ }
+
+
+def build_onboarding_checklist(service_id: str) -> dict[str, Any]:
+ """Onboarding checklist for the customer (first 5 days)."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "first_5_days_ar": [
+ "يوم 1: kick-off + جمع الـ intake + توقيع DPA draft.",
+ "يوم 2: ربط القنوات الآمنة (Gmail drafts / Sheets / website forms).",
+ "يوم 3: توليد أول Proof Pack template + تدريب على Approval Center.",
+ "يوم 4: إطلاق أول mission (10 فرص في 10 دقائق).",
+ "يوم 5: مراجعة النتائج + تخطيط الأسبوع الثاني.",
+ ],
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def build_service_launch_package(service_id: str) -> dict[str, Any]:
+ """Full launch package = landing + sales + demo + onboarding."""
+ return {
+ "service_id": service_id,
+ "landing": build_landing_page_outline(service_id),
+ "sales_script": build_sales_script(service_id),
+ "demo_script": build_demo_script(service_id),
+ "onboarding": build_onboarding_checklist(service_id),
+ "approval_required": True,
+ }
diff --git a/dealix/auto_client_acquisition/service_excellence/proof_metrics.py b/dealix/auto_client_acquisition/service_excellence/proof_metrics.py
new file mode 100644
index 00000000..d1aa459b
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_excellence/proof_metrics.py
@@ -0,0 +1,72 @@
+"""Proof metrics — كل خدمة لازم تثبت العائد بأرقام محددة."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from auto_client_acquisition.service_tower import get_service
+
+
+def required_proof_metrics(service_id: str) -> list[str]:
+ """Return the proof metrics every run of the service must produce."""
+ s = get_service(service_id)
+ if s is None:
+ return []
+ return list(s.proof_metrics)
+
+
+def build_proof_pack_template_excellence(service_id: str) -> dict[str, Any]:
+ """Build a polished Proof Pack template for an excellence-tier service."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "executive_summary_ar": (
+ "ملخص تنفيذي من 10 أسطر يعرض النتائج، الأثر المالي، "
+ "والمخاطر التي تم منعها."
+ ),
+ "metrics": list(s.proof_metrics),
+ "report_format": ["pdf", "json", "whatsapp_summary"],
+ "signature_required": True,
+ "approval_required": True,
+ }
+
+
+def calculate_service_roi_estimate(
+ service_id: str,
+ metrics: dict[str, Any],
+) -> dict[str, Any]:
+ """Estimate ROI = pipeline_influenced / service_price."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+
+ price = max(1, float(metrics.get("price_paid_sar", s.pricing_min_sar or 1)))
+ pipeline = float(metrics.get("pipeline_sar", 0))
+ closed_won = float(metrics.get("closed_won_sar", 0))
+
+ roi_pipeline_x = round(pipeline / price, 2)
+ roi_closed_x = round(closed_won / price, 2)
+
+ return {
+ "service_id": service_id,
+ "price_paid_sar": price,
+ "pipeline_sar": pipeline,
+ "closed_won_sar": closed_won,
+ "roi_pipeline_x": roi_pipeline_x,
+ "roi_closed_x": roi_closed_x,
+ "summary_ar": (
+ f"كل ريال أنفقه العميل على {s.name_ar} أنتج "
+ f"{roi_pipeline_x}× pipeline و {roi_closed_x}× closed-won."
+ ),
+ }
+
+
+def summarize_proof_ar(service_id: str, metrics: dict[str, Any]) -> str:
+ """Build a one-paragraph Arabic proof summary."""
+ roi = calculate_service_roi_estimate(service_id, metrics)
+ if "error" in roi:
+ return roi["error"]
+ return roi["summary_ar"]
diff --git a/dealix/auto_client_acquisition/service_excellence/quality_review.py b/dealix/auto_client_acquisition/service_excellence/quality_review.py
new file mode 100644
index 00000000..7de12194
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_excellence/quality_review.py
@@ -0,0 +1,82 @@
+"""Quality review — يمنع الخدمات الضعيفة من الإطلاق."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from auto_client_acquisition.service_tower import get_service
+
+from .service_scoring import calculate_service_excellence_score
+
+
+def block_if_missing_proof(service_id: str) -> dict[str, Any]:
+ s = get_service(service_id)
+ if s is None:
+ return {"blocked": True, "reason_ar": f"خدمة غير معروفة: {service_id}"}
+ if not s.proof_metrics:
+ return {"blocked": True, "reason_ar": "لا توجد proof metrics."}
+ return {"blocked": False}
+
+
+def block_if_missing_approval_policy(service_id: str) -> dict[str, Any]:
+ s = get_service(service_id)
+ if s is None:
+ return {"blocked": True, "reason_ar": f"خدمة غير معروفة: {service_id}"}
+ if not s.approval_policy:
+ return {"blocked": True, "reason_ar": "سياسة الاعتماد غير محددة."}
+ return {"blocked": False}
+
+
+def block_if_unclear_pricing(service_id: str) -> dict[str, Any]:
+ s = get_service(service_id)
+ if s is None:
+ return {"blocked": True, "reason_ar": f"خدمة غير معروفة: {service_id}"}
+ if s.pricing_max_sar < 0:
+ return {"blocked": True, "reason_ar": "تسعير غير صحيح."}
+ if s.pricing_max_sar > 0 and s.pricing_max_sar < s.pricing_min_sar:
+ return {"blocked": True, "reason_ar": "نطاق التسعير غير منطقي."}
+ return {"blocked": False}
+
+
+def block_if_unsafe_channel(service_id: str) -> dict[str, Any]:
+ """Block if a service depends on an unsafe channel (e.g., scraping)."""
+ s = get_service(service_id)
+ if s is None:
+ return {"blocked": True, "reason_ar": f"خدمة غير معروفة: {service_id}"}
+ unsafe = {"scraping", "auto_dm", "auto_connect", "browser_extension"}
+ for ch in s.required_integrations:
+ if ch.lower() in unsafe:
+ return {"blocked": True,
+ "reason_ar": f"تكامل غير آمن: {ch}."}
+ return {"blocked": False}
+
+
+def review_service_before_launch(service_id: str) -> dict[str, Any]:
+ """Run all gates + scoring before allowing a service to ship."""
+ gates = {
+ "proof": block_if_missing_proof(service_id),
+ "approval": block_if_missing_approval_policy(service_id),
+ "pricing": block_if_unclear_pricing(service_id),
+ "channels": block_if_unsafe_channel(service_id),
+ }
+ blocked = [k for k, v in gates.items() if v.get("blocked")]
+ score = calculate_service_excellence_score(service_id)
+
+ if blocked:
+ verdict = "blocked_at_gate"
+ elif score.get("status") == "launch_ready":
+ verdict = "launch_ready"
+ elif score.get("status") == "beta_only":
+ verdict = "beta_only"
+ else:
+ verdict = "needs_work"
+
+ return {
+ "service_id": service_id,
+ "verdict": verdict,
+ "score": score,
+ "gates": gates,
+ "blocked_reasons_ar": [
+ gates[k].get("reason_ar", "") for k in blocked
+ ],
+ }
diff --git a/dealix/auto_client_acquisition/service_excellence/research_lab.py b/dealix/auto_client_acquisition/service_excellence/research_lab.py
new file mode 100644
index 00000000..8754f482
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_excellence/research_lab.py
@@ -0,0 +1,109 @@
+"""Service Research Lab — تحسين شهري لكل خدمة (deterministic)."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from auto_client_acquisition.service_tower import get_service
+
+from .competitor_gap import compare_against_categories
+from .service_scoring import calculate_service_excellence_score
+
+
+def build_service_research_brief(service_id: str) -> dict[str, Any]:
+ """Research brief: questions to answer about a service this month."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "questions_to_answer_ar": [
+ "من أكثر فئة عميل اشترت هذه الخدمة آخر 30 يوم؟",
+ "ما متوسط الـ time-to-value الفعلي؟",
+ "ما أعلى اعتراض ظهر في الـonboarding؟",
+ "ما أكثر deliverable يطلبه العميل بالاسم؟",
+ "ما أضعف proof_metric لم يُحقَّق هذا الشهر؟",
+ "ما أكثر سعر يقبله العميل بدون تردد؟",
+ ],
+ "data_sources_ar": [
+ "Action Ledger.",
+ "Proof Ledger.",
+ "Approval Center.",
+ "Decision Memory.",
+ "Customer feedback.",
+ ],
+ "approval_required": True,
+ }
+
+
+def generate_feature_hypotheses(service_id: str) -> list[dict[str, Any]]:
+ """Generate hypotheses for feature additions/improvements."""
+ s = get_service(service_id)
+ if s is None:
+ return []
+ base = [
+ {
+ "hypothesis_ar": "إضافة exit survey بعد كل deliverable يرفع NPS بـ20%.",
+ "effort": "low", "impact": "medium",
+ },
+ {
+ "hypothesis_ar": "اقتراح 3 رسائل بدل 1 في الـapproval card يرفع approval rate 30%.",
+ "effort": "medium", "impact": "high",
+ },
+ {
+ "hypothesis_ar": "إضافة Saudi-tone-score مرئية في الواجهة يقلل الرسائل المرفوضة 40%.",
+ "effort": "medium", "impact": "high",
+ },
+ {
+ "hypothesis_ar": "ربط Proof Pack بـ Moyasar invoice draft يرفع conversion 25%.",
+ "effort": "medium", "impact": "high",
+ },
+ ]
+ if s.pricing_model == "monthly":
+ base.append({
+ "hypothesis_ar": "تقرير شهري بصيغة فيديو 60 ثانية يرفع retention 15%.",
+ "effort": "high", "impact": "medium",
+ })
+ return base
+
+
+def recommend_next_experiments(service_id: str) -> dict[str, Any]:
+ """Recommend the next 3 experiments to run on a service."""
+ hypotheses = generate_feature_hypotheses(service_id)
+ # Pick top-3 by impact desc, effort asc.
+ impact_rank = {"high": 0, "medium": 1, "low": 2}
+ effort_rank = {"low": 0, "medium": 1, "high": 2}
+ sorted_h = sorted(
+ hypotheses,
+ key=lambda h: (impact_rank.get(str(h.get("impact")), 9),
+ effort_rank.get(str(h.get("effort")), 9)),
+ )
+ return {
+ "service_id": service_id,
+ "experiments": sorted_h[:3],
+ "approval_required": True,
+ }
+
+
+def build_monthly_service_review(service_id: str) -> dict[str, Any]:
+ """Build a structured monthly review of a service's performance."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ score = calculate_service_excellence_score(service_id)
+ gaps = compare_against_categories(service_id)
+ experiments = recommend_next_experiments(service_id)
+
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "current_excellence_score": score,
+ "competitor_gap_summary": {
+ "advantages": gaps.get("dealix_advantages_ar", []),
+ "gaps_to_close": gaps.get("gaps_to_close_ar", []),
+ },
+ "next_experiments": experiments.get("experiments", []),
+ "research_brief": build_service_research_brief(service_id),
+ "approval_required": True,
+ }
diff --git a/dealix/auto_client_acquisition/service_excellence/service_improvement_backlog.py b/dealix/auto_client_acquisition/service_excellence/service_improvement_backlog.py
new file mode 100644
index 00000000..89e71563
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_excellence/service_improvement_backlog.py
@@ -0,0 +1,67 @@
+"""Improvement backlog — يحوّل الفيدباك إلى bands prioritized."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def build_backlog(service_id: str) -> dict[str, Any]:
+ """Build an empty backlog skeleton for a service."""
+ return {
+ "service_id": service_id,
+ "items": [],
+ "policies_ar": [
+ "كل بند يتضمن: title_ar, impact, effort, owner.",
+ "بند بدون proof_metric يُرفض.",
+ "بند يخالف PDPL/ToS يُرفض فوراً.",
+ ],
+ }
+
+
+def prioritize_backlog_items(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
+ """Sort backlog items by impact desc, effort asc."""
+ impact_rank = {"high": 0, "medium": 1, "low": 2}
+ effort_rank = {"low": 0, "medium": 1, "high": 2}
+ return sorted(
+ items,
+ key=lambda i: (
+ impact_rank.get(str(i.get("impact", "low")), 9),
+ effort_rank.get(str(i.get("effort", "high")), 9),
+ ),
+ )
+
+
+def convert_feedback_to_backlog(
+ feedback: list[dict[str, Any]],
+) -> list[dict[str, Any]]:
+ """Convert customer feedback items into prioritized backlog items."""
+ out: list[dict[str, Any]] = []
+ for f in feedback or []:
+ text = str(f.get("text", "")).strip()
+ if not text:
+ continue
+ # Heuristic prioritization (deterministic).
+ sentiment = f.get("sentiment", "neutral")
+ impact = "high" if sentiment == "negative" else "medium"
+ effort = "medium"
+ out.append({
+ "title_ar": text[:120],
+ "impact": impact,
+ "effort": effort,
+ "source": f.get("source", "feedback"),
+ "owner": f.get("owner", "service_lead"),
+ })
+ return prioritize_backlog_items(out)
+
+
+def recommend_weekly_improvements(service_id: str) -> dict[str, Any]:
+ """Recommend 3 weekly improvements for a service."""
+ return {
+ "service_id": service_id,
+ "weekly_plan_ar": [
+ "حسّن الرسالة الأولى — اختبر زاوية جديدة لقطاع واحد.",
+ "أضف proof_metric حقيقي لو يوجد فجوة.",
+ "نظّف backlog: ادمج أو احذف بنود متشابهة.",
+ ],
+ "approval_required": True,
+ }
diff --git a/dealix/auto_client_acquisition/service_excellence/service_scoring.py b/dealix/auto_client_acquisition/service_excellence/service_scoring.py
new file mode 100644
index 00000000..98401c7a
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_excellence/service_scoring.py
@@ -0,0 +1,151 @@
+"""Service Excellence scoring — every service must score ≥80 to ship."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from auto_client_acquisition.service_tower import Service, get_service
+
+
+def score_clarity(service: Service | dict[str, Any]) -> int:
+ """0..10. هل العميل يفهم ما الذي سيحصل عليه؟"""
+ if isinstance(service, dict):
+ outcome = service.get("outcome_ar", "")
+ deliverables = service.get("deliverables_ar", [])
+ else:
+ outcome = service.outcome_ar
+ deliverables = list(service.deliverables_ar)
+ score = 5
+ if len(outcome or "") >= 30:
+ score += 3
+ if len(deliverables) >= 3:
+ score += 2
+ return min(10, score)
+
+
+def score_speed_to_value(service: Service | dict[str, Any]) -> int:
+ """0..10. هل النتيجة خلال 7 أيام؟"""
+ if isinstance(service, dict):
+ model = service.get("pricing_model", "")
+ else:
+ model = service.pricing_model
+ if model == "sprint":
+ return 10
+ if model == "monthly":
+ return 6
+ return 8 # one_time
+
+
+def score_automation(service: Service | dict[str, Any]) -> int:
+ """0..10. هل قابلة للأتمتة؟"""
+ if isinstance(service, dict):
+ steps = service.get("workflow_steps", [])
+ else:
+ steps = list(service.workflow_steps)
+ auto_steps = sum(1 for s in steps
+ if s in {"intake", "data_check", "targeting",
+ "contactability", "strategy", "drafting",
+ "tracking", "proof", "upsell"})
+ return min(10, auto_steps)
+
+
+def score_compliance(service: Service | dict[str, Any]) -> int:
+ """0..10. هل فيها opt-in/approval/audit؟"""
+ if isinstance(service, dict):
+ policy = service.get("approval_policy", "")
+ else:
+ policy = service.approval_policy
+ if "approval_required" in policy:
+ return 10
+ if "draft_only" in policy:
+ return 9
+ if policy:
+ return 6
+ return 3
+
+
+def score_proof(service: Service | dict[str, Any]) -> int:
+ """0..10. هل لها proof metrics؟"""
+ if isinstance(service, dict):
+ metrics = service.get("proof_metrics", [])
+ else:
+ metrics = list(service.proof_metrics)
+ return min(10, len(metrics) * 3)
+
+
+def score_upsell(service: Service | dict[str, Any]) -> int:
+ """0..10. هل لها upgrade path؟"""
+ if isinstance(service, dict):
+ upgrade = service.get("upgrade_path", [])
+ else:
+ upgrade = list(service.upgrade_path)
+ return 10 if upgrade else 5
+
+
+def calculate_service_excellence_score(
+ service: Service | dict[str, Any] | str,
+) -> dict[str, Any]:
+ """Compute the full excellence score (0..100) + verdict."""
+ if isinstance(service, str):
+ s = get_service(service)
+ if s is None:
+ return {"error": f"unknown service: {service}"}
+ service_obj: Service | dict[str, Any] = s
+ else:
+ service_obj = service
+
+ clarity = score_clarity(service_obj)
+ speed = score_speed_to_value(service_obj)
+ automation = score_automation(service_obj)
+ compliance = score_compliance(service_obj)
+ proof = score_proof(service_obj)
+ upsell = score_upsell(service_obj)
+
+ # Each dimension max=10; we have 6 dimensions → max=60.
+ # Add 4 baseline dimensions (uniqueness, scalability, ops, proof_data)
+ # at fixed values for now (can become real signals later).
+ uniqueness = 8 # deterministic — Dealix is Saudi-first
+ scalability = 8 # multi-sector ready
+ ops_daily = 7 # daily autopilot integration
+ proof_data = min(10, proof + 2)
+
+ total = (clarity + speed + automation + compliance
+ + proof + upsell + uniqueness + scalability
+ + ops_daily + proof_data)
+ total = max(0, min(100, total))
+
+ if total >= 80:
+ status = "launch_ready"
+ elif total >= 60:
+ status = "beta_only"
+ else:
+ status = "needs_work"
+
+ reasons: list[str] = []
+ fixes: list[str] = []
+ if compliance < 8:
+ reasons.append("سياسة الاعتماد غير واضحة.")
+ fixes.append("اضبط approval_policy على 'approval_required' أو 'draft_only'.")
+ if proof < 6:
+ reasons.append("Proof metrics قليلة.")
+ fixes.append("أضف ≥3 proof metrics محددة.")
+ if not upsell:
+ reasons.append("لا يوجد upgrade path.")
+ fixes.append("اربط الخدمة بخدمة أعلى عبر upgrade_path.")
+
+ return {
+ "service_id": (
+ service_obj.get("id") if isinstance(service_obj, dict) else service_obj.id
+ ),
+ "total_score": total,
+ "dimensions": {
+ "clarity": clarity, "speed_to_value": speed,
+ "automation": automation, "compliance": compliance,
+ "proof": proof, "upsell": upsell,
+ "uniqueness": uniqueness, "scalability": scalability,
+ "ops_daily": ops_daily, "proof_data": proof_data,
+ },
+ "status": status,
+ "reasons_ar": reasons,
+ "required_fixes_ar": fixes,
+ }
diff --git a/dealix/auto_client_acquisition/service_tower/__init__.py b/dealix/auto_client_acquisition/service_tower/__init__.py
new file mode 100644
index 00000000..3c2117bc
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_tower/__init__.py
@@ -0,0 +1,82 @@
+"""Service Tower — كل قدرات Dealix كخدمات قابلة للبيع والتشغيل الذاتي.
+
+العميل يختار هدفه → النظام يوصي بالخدمة → يجمع البيانات → يقيّم المخاطر →
+يكتب الخطة → يطلب الموافقات → يشغّل القنوات → يطلع Proof Pack.
+"""
+
+from __future__ import annotations
+
+from .deliverables import (
+ build_client_report_outline,
+ build_deliverables,
+ build_internal_operator_checklist,
+ build_proof_pack_template,
+)
+from .mission_templates import (
+ build_service_workflow,
+ get_default_mission_steps,
+ map_service_to_growth_mission,
+)
+from .pricing_engine import (
+ calculate_monthly_offer,
+ calculate_setup_fee,
+ quote_service,
+ recommend_plan_after_service,
+)
+from .service_catalog import (
+ ALL_SERVICES,
+ Service,
+ catalog_summary,
+ get_service,
+ list_all_services,
+)
+from .service_scorecard import (
+ build_service_scorecard,
+ calculate_service_success_score,
+ recommend_next_step,
+ summarize_scorecard_ar,
+)
+from .service_wizard import (
+ build_intake_questions,
+ recommend_service,
+ summarize_recommendation_ar,
+ validate_service_inputs,
+)
+from .upgrade_paths import (
+ build_upsell_message_ar,
+ map_service_to_subscription,
+ recommend_upgrade,
+)
+from .whatsapp_ceo_control import (
+ build_ceo_daily_service_brief,
+ build_end_of_day_service_report,
+ build_risk_alert_card,
+ build_service_approval_card,
+)
+
+__all__ = [
+ # service_catalog
+ "ALL_SERVICES", "Service", "catalog_summary",
+ "get_service", "list_all_services",
+ # service_wizard
+ "build_intake_questions", "recommend_service",
+ "summarize_recommendation_ar", "validate_service_inputs",
+ # mission_templates
+ "build_service_workflow", "get_default_mission_steps",
+ "map_service_to_growth_mission",
+ # pricing_engine
+ "calculate_monthly_offer", "calculate_setup_fee",
+ "quote_service", "recommend_plan_after_service",
+ # deliverables
+ "build_client_report_outline", "build_deliverables",
+ "build_internal_operator_checklist", "build_proof_pack_template",
+ # service_scorecard
+ "build_service_scorecard", "calculate_service_success_score",
+ "recommend_next_step", "summarize_scorecard_ar",
+ # whatsapp_ceo_control
+ "build_ceo_daily_service_brief", "build_end_of_day_service_report",
+ "build_risk_alert_card", "build_service_approval_card",
+ # upgrade_paths
+ "build_upsell_message_ar", "map_service_to_subscription",
+ "recommend_upgrade",
+]
diff --git a/dealix/auto_client_acquisition/service_tower/deliverables.py b/dealix/auto_client_acquisition/service_tower/deliverables.py
new file mode 100644
index 00000000..afe6cdb4
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_tower/deliverables.py
@@ -0,0 +1,91 @@
+"""Deliverables + Proof Pack templates per service."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .service_catalog import get_service
+
+
+def build_deliverables(service_id: str) -> dict[str, Any]:
+ """Return the deliverables list for a service."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "deliverables_ar": list(s.deliverables_ar),
+ "approval_required": True,
+ }
+
+
+def build_proof_pack_template(service_id: str) -> dict[str, Any]:
+ """Build a proof-pack template for a service."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "metrics_to_track": list(s.proof_metrics),
+ "report_sections_ar": [
+ "ملخص الفترة",
+ "ما تم إنجازه (ledger entries)",
+ "النتائج بالأرقام (الـ proof_metrics)",
+ "المخاطر التي تم منعها",
+ "تجربة الأسبوع/الشهر القادم",
+ "التوصية بالخطوة التالية",
+ ],
+ "delivery_format": ["pdf", "json", "whatsapp_summary"],
+ "approval_required": True,
+ }
+
+
+def build_client_report_outline(service_id: str) -> dict[str, Any]:
+ """Outline of the client-facing report for a service."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ return {
+ "service_id": service_id,
+ "title_ar": f"تقرير {s.name_ar}",
+ "sections_ar": [
+ "ملخص تنفيذي (10 أسطر)",
+ "السياق والأهداف",
+ "ما عمله Dealix",
+ "النتائج (الأرقام مقابل الأهداف)",
+ "أبرز الاعتراضات والـsignals",
+ "المخاطر التي تم منعها",
+ "Proof — ledger events",
+ "التوصية بالخطوة التالية",
+ ],
+ "approval_required": True,
+ }
+
+
+def build_internal_operator_checklist(service_id: str) -> dict[str, Any]:
+ """Internal operator checklist (for the team running the service)."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "checklist_ar": [
+ "مراجعة الـ intake واكتمال الحقول.",
+ "تشغيل targeting + contactability.",
+ "صياغة الـ drafts الأولى.",
+ "إرسال للـ approval center.",
+ "تنفيذ بعد الاعتماد فقط.",
+ "تتبع النتائج في الـ Action Ledger.",
+ "بناء Proof Pack.",
+ "اقتراح الترقية للعميل.",
+ ],
+ "do_not_do_ar": [
+ "لا live send بدون env flag + اعتماد.",
+ "لا إرسال على cold list.",
+ "لا charge بدون تأكيد.",
+ "لا تخزين أسرار في الـ payload.",
+ ],
+ }
diff --git a/dealix/auto_client_acquisition/service_tower/mission_templates.py b/dealix/auto_client_acquisition/service_tower/mission_templates.py
new file mode 100644
index 00000000..e03d26eb
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_tower/mission_templates.py
@@ -0,0 +1,94 @@
+"""Mission templates — يحوّل الخدمة إلى workflow قابل للتشغيل."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .service_catalog import get_service
+
+# Map service → growth mission ID (in intelligence_layer.mission_engine).
+_SERVICE_TO_MISSION: dict[str, str] = {
+ "free_growth_diagnostic": "first_10_opportunities",
+ "list_intelligence": "first_10_opportunities",
+ "first_10_opportunities_sprint": "first_10_opportunities",
+ "self_growth_operator": "first_10_opportunities",
+ "growth_os_monthly": "first_10_opportunities",
+ "email_revenue_rescue": "revenue_leak_rescue",
+ "meeting_booking_sprint": "meeting_booking_sprint",
+ "partner_sprint": "partnership_sprint",
+ "agency_partner_program": "partnership_sprint",
+ "whatsapp_compliance_setup": "first_10_opportunities",
+ "linkedin_lead_gen_setup": "first_10_opportunities",
+ "executive_growth_brief": "first_10_opportunities",
+}
+
+
+def get_default_mission_steps(service_id: str) -> list[dict[str, Any]]:
+ """Return default workflow steps for a service."""
+ s = get_service(service_id)
+ if s is None:
+ return []
+ steps: list[dict[str, Any]] = []
+ for i, name in enumerate(s.workflow_steps):
+ steps.append({
+ "order": i + 1,
+ "step_id": name,
+ "label_ar": _STEP_LABELS_AR.get(name, name),
+ "approval_required": name in {
+ "approval", "execution_or_export", "drafting",
+ },
+ "live_action": False,
+ })
+ return steps
+
+
+_STEP_LABELS_AR: dict[str, str] = {
+ "intake": "جمع المدخلات",
+ "data_check": "فحص جودة البيانات",
+ "targeting": "تحديد الأهداف",
+ "contactability": "تقييم إمكانية التواصل",
+ "strategy": "استراتيجية القناة",
+ "drafting": "صياغة المسودات",
+ "approval": "اعتماد بشري",
+ "execution_or_export": "تنفيذ/تصدير",
+ "tracking": "متابعة النتائج",
+ "proof": "Proof Pack",
+ "upsell": "ترقية الخدمة",
+ "agency_onboarding": "إعداد الوكالة",
+ "client_diagnostic": "تشخيص عميل الوكالة",
+ "proposal": "عرض",
+ "pilot": "Pilot",
+ "proof_pack": "Proof Pack",
+ "revenue_share": "Revenue Share",
+ "aggregate": "تجميع الإشارات",
+ "prioritize": "ترتيب الأولويات",
+ "deliver": "تسليم الموجز",
+}
+
+
+def build_service_workflow(service_id: str) -> dict[str, Any]:
+ """Build the full Arabic workflow for a service."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+
+ steps = get_default_mission_steps(service_id)
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "workflow_steps": steps,
+ "deliverables_ar": list(s.deliverables_ar),
+ "approval_policy": s.approval_policy,
+ "live_send_allowed": False,
+ "estimated_completion_days": (
+ 7 if s.pricing_model == "sprint"
+ else 30 if s.pricing_model == "monthly"
+ else 1
+ ),
+ "linked_growth_mission": _SERVICE_TO_MISSION.get(service_id),
+ }
+
+
+def map_service_to_growth_mission(service_id: str) -> str | None:
+ """Return the growth-mission ID linked to a service (or None)."""
+ return _SERVICE_TO_MISSION.get(service_id)
diff --git a/dealix/auto_client_acquisition/service_tower/pricing_engine.py b/dealix/auto_client_acquisition/service_tower/pricing_engine.py
new file mode 100644
index 00000000..50772a00
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_tower/pricing_engine.py
@@ -0,0 +1,118 @@
+"""Pricing engine — quotes + setup + monthly + post-service plan."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .service_catalog import get_service
+
+
+def quote_service(
+ service_id: str,
+ *,
+ company_size: str = "small",
+ urgency: str = "normal",
+ channels_count: int = 1,
+) -> dict[str, Any]:
+ """Quote a service with company-size + urgency + channels multipliers."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+
+ p_min = float(s.pricing_min_sar)
+ p_max = float(s.pricing_max_sar)
+ if p_min == 0 and p_max == 0:
+ return {
+ "service_id": service_id,
+ "is_free": True,
+ "estimated_min_sar": 0,
+ "estimated_max_sar": 0,
+ "currency": "SAR",
+ "notes_ar": "خدمة مجانية. تتطلب اعتماد قبل التسليم.",
+ }
+
+ size_mult = {"micro": 0.8, "small": 1.0, "medium": 1.3, "large": 1.7}.get(
+ company_size, 1.0,
+ )
+ urgency_mult = {"normal": 1.0, "rush": 1.3, "asap": 1.5}.get(urgency, 1.0)
+ ch_mult = 1.0 + max(0, channels_count - 1) * 0.15
+
+ return {
+ "service_id": service_id,
+ "estimated_min_sar": round(p_min * size_mult * urgency_mult * ch_mult),
+ "estimated_max_sar": round(p_max * size_mult * urgency_mult * ch_mult),
+ "currency": "SAR",
+ "factors": {
+ "company_size": company_size,
+ "urgency": urgency,
+ "channels_count": channels_count,
+ },
+ "pricing_model": s.pricing_model,
+ }
+
+
+def calculate_setup_fee(service_id: str) -> dict[str, Any]:
+ """Suggest a setup fee for monthly services."""
+ s = get_service(service_id)
+ if s is None or s.pricing_model != "monthly":
+ return {"setup_fee_sar": 0, "currency": "SAR"}
+ base = s.pricing_min_sar
+ return {
+ "setup_fee_sar": int(base * 1.0), # ~one month equivalent
+ "includes_ar": [
+ "ربط القنوات (واتساب/إيميل/تقويم)",
+ "استيراد القوائم وتصنيف المصادر",
+ "تدريب الفريق على Approval Center",
+ "بناء أول Proof Pack",
+ ],
+ "currency": "SAR",
+ }
+
+
+def calculate_monthly_offer(service_id: str) -> dict[str, Any]:
+ """Return monthly-pricing detail (for monthly services only)."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ if s.pricing_model != "monthly":
+ return {
+ "service_id": service_id,
+ "is_monthly": False,
+ "notes_ar": "هذه الخدمة ليست شهرية.",
+ }
+ return {
+ "service_id": service_id,
+ "is_monthly": True,
+ "monthly_sar": s.pricing_min_sar,
+ "annual_discount_pct": 15,
+ "annual_total_sar": int(s.pricing_min_sar * 12 * 0.85),
+ "currency": "SAR",
+ }
+
+
+def recommend_plan_after_service(
+ service_id: str,
+ *,
+ outcome: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+ """After a service runs, recommend an upgrade plan."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ outcome = outcome or {}
+
+ upgrade_targets = list(s.upgrade_path) or ["growth_os_monthly"]
+ next_id = upgrade_targets[0]
+ next_s = get_service(next_id)
+
+ return {
+ "from_service": service_id,
+ "recommended_upgrade": next_id,
+ "name_ar": next_s.name_ar if next_s else next_id,
+ "monthly_sar": next_s.pricing_min_sar if next_s else 0,
+ "reason_ar": (
+ f"بعد إثبات قيمة {s.name_ar}، الخطوة الطبيعية هي "
+ f"الاستمرار مع {next_s.name_ar if next_s else next_id} "
+ "للحصول على نتائج شهرية مستمرة."
+ ),
+ }
diff --git a/dealix/auto_client_acquisition/service_tower/service_catalog.py b/dealix/auto_client_acquisition/service_tower/service_catalog.py
new file mode 100644
index 00000000..a5013bfb
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_tower/service_catalog.py
@@ -0,0 +1,347 @@
+"""The full Dealix service catalog — 12 productized services."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+
+
+@dataclass(frozen=True)
+class Service:
+ """A single sellable, productized service."""
+ id: str
+ name_ar: str
+ target_customer_ar: str
+ outcome_ar: str
+ inputs_required: tuple[str, ...]
+ workflow_steps: tuple[str, ...]
+ deliverables_ar: tuple[str, ...]
+ pricing_min_sar: int
+ pricing_max_sar: int
+ pricing_model: str # "one_time" | "monthly" | "sprint"
+ risk_level: str # "low" | "medium" | "high"
+ required_integrations: tuple[str, ...]
+ approval_policy: str # short label
+ proof_metrics: tuple[str, ...]
+ upgrade_path: tuple[str, ...] = field(default_factory=tuple)
+
+ def to_dict(self) -> dict[str, object]:
+ return {
+ "id": self.id, "name_ar": self.name_ar,
+ "target_customer_ar": self.target_customer_ar,
+ "outcome_ar": self.outcome_ar,
+ "inputs_required": list(self.inputs_required),
+ "workflow_steps": list(self.workflow_steps),
+ "deliverables_ar": list(self.deliverables_ar),
+ "pricing_min_sar": self.pricing_min_sar,
+ "pricing_max_sar": self.pricing_max_sar,
+ "pricing_model": self.pricing_model,
+ "risk_level": self.risk_level,
+ "required_integrations": list(self.required_integrations),
+ "approval_policy": self.approval_policy,
+ "proof_metrics": list(self.proof_metrics),
+ "upgrade_path": list(self.upgrade_path),
+ }
+
+
+_DEFAULT_WORKFLOW: tuple[str, ...] = (
+ "intake", "data_check", "targeting", "contactability",
+ "strategy", "drafting", "approval",
+ "execution_or_export", "tracking", "proof", "upsell",
+)
+
+
+ALL_SERVICES: tuple[Service, ...] = (
+ Service(
+ id="free_growth_diagnostic",
+ name_ar="تشخيص نمو مجاني",
+ target_customer_ar="أي شركة B2B تريد عينة قبل Pilot",
+ outcome_ar="3 فرص + رسالة + تقرير مخاطر + خطة Pilot — خلال 24 ساعة عمل",
+ inputs_required=("sector", "city", "offer", "goal"),
+ workflow_steps=_DEFAULT_WORKFLOW,
+ deliverables_ar=(
+ "3 فرص B2B مع why-now",
+ "رسالة عربية مخصصة",
+ "تقرير مخاطر",
+ "خطة Pilot مقترحة",
+ ),
+ pricing_min_sar=0, pricing_max_sar=0,
+ pricing_model="one_time",
+ risk_level="low",
+ required_integrations=(),
+ approval_policy="approval_required_for_share",
+ proof_metrics=("diagnostic_to_paid_conversion",),
+ upgrade_path=("first_10_opportunities_sprint", "growth_os_monthly"),
+ ),
+ Service(
+ id="list_intelligence",
+ name_ar="تحليل القوائم (List Intelligence)",
+ target_customer_ar="شركات لديها قوائم أرقام/إيميلات/عملاء قدامى",
+ outcome_ar="تنظيف + تصنيف + أفضل 50 target + رسائل + خطة 7 أيام",
+ inputs_required=("uploaded_csv", "channels_available"),
+ workflow_steps=_DEFAULT_WORKFLOW,
+ deliverables_ar=(
+ "قائمة منظفة + dedupe",
+ "تصنيف safe / needs_review / blocked",
+ "أفضل 50 target",
+ "رسائل عربية",
+ "تقرير مخاطر",
+ ),
+ pricing_min_sar=499, pricing_max_sar=1500,
+ pricing_model="one_time",
+ risk_level="medium",
+ required_integrations=("google_sheets",),
+ approval_policy="draft_only",
+ proof_metrics=("contacts_classified", "safe_targets_found", "risks_blocked"),
+ upgrade_path=("growth_os_monthly",),
+ ),
+ Service(
+ id="first_10_opportunities_sprint",
+ name_ar="10 فرص في 10 دقائق (Sprint)",
+ target_customer_ar="شركة B2B تحتاج فرصاً مؤهلة بسرعة",
+ outcome_ar="10 فرص + رسائل + خطة متابعة + Proof Pack — خلال 7 أيام",
+ inputs_required=("sector", "city", "offer", "goal"),
+ workflow_steps=_DEFAULT_WORKFLOW,
+ deliverables_ar=(
+ "10 فرص B2B مع why-now",
+ "10 رسائل عربية",
+ "خطة متابعة 7 أيام",
+ "Proof Pack تفصيلي",
+ ),
+ pricing_min_sar=499, pricing_max_sar=1500,
+ pricing_model="sprint",
+ risk_level="low",
+ required_integrations=(),
+ approval_policy="draft_only",
+ proof_metrics=("opportunities_count", "approval_rate",
+ "positive_replies", "meetings_drafted"),
+ upgrade_path=("growth_os_monthly", "self_growth_operator"),
+ ),
+ Service(
+ id="self_growth_operator",
+ name_ar="مدير نمو شخصي (Self-Growth Operator)",
+ target_customer_ar="مؤسسون / مستشارون / وكالات صغيرة",
+ outcome_ar="Daily brief + drafts + متابعة + تقارير أسبوعية",
+ inputs_required=("company_profile", "goals"),
+ workflow_steps=_DEFAULT_WORKFLOW,
+ deliverables_ar=(
+ "Daily brief عربي",
+ "5 cards/day للقرارات",
+ "Drafts + approvals",
+ "Weekly learning report",
+ ),
+ pricing_min_sar=999, pricing_max_sar=999,
+ pricing_model="monthly",
+ risk_level="low",
+ required_integrations=("gmail", "google_calendar"),
+ approval_policy="approval_required",
+ proof_metrics=("decisions_per_day", "drafts_approved",
+ "meetings_drafted", "pipeline_sar"),
+ upgrade_path=("growth_os_monthly",),
+ ),
+ Service(
+ id="growth_os_monthly",
+ name_ar="Growth OS — اشتراك شهري",
+ target_customer_ar="شركات B2B صغيرة-متوسطة",
+ outcome_ar="منصة كاملة: قنوات، command feed، proof pack، فريق",
+ inputs_required=("company_profile", "channels", "team_size"),
+ workflow_steps=_DEFAULT_WORKFLOW,
+ deliverables_ar=(
+ "ربط القنوات",
+ "Daily autopilot",
+ "Approvals مركزية",
+ "Proof Pack شهري",
+ "Revenue leak detector",
+ ),
+ pricing_min_sar=2999, pricing_max_sar=2999,
+ pricing_model="monthly",
+ risk_level="medium",
+ required_integrations=("gmail", "google_calendar", "moyasar",
+ "google_sheets"),
+ approval_policy="approval_required",
+ proof_metrics=("monthly_pipeline_sar", "monthly_meetings",
+ "monthly_revenue_influenced", "monthly_risks_blocked"),
+ upgrade_path=("agency_partner_program",),
+ ),
+ Service(
+ id="email_revenue_rescue",
+ name_ar="استعادة الإيرادات من الإيميل",
+ target_customer_ar="شركات إيميل الشركة فيه فرص ضائعة",
+ outcome_ar="استخراج فرص ضائعة + drafts + meetings + missed revenue report",
+ inputs_required=("gmail_label", "ICP"),
+ workflow_steps=_DEFAULT_WORKFLOW,
+ deliverables_ar=(
+ "Scan الـ inbox/labels",
+ "Drafts للردود المتأخرة",
+ "Meeting drafts",
+ "Missed revenue report",
+ ),
+ pricing_min_sar=1500, pricing_max_sar=5000,
+ pricing_model="one_time",
+ risk_level="high",
+ required_integrations=("gmail",),
+ approval_policy="approval_required",
+ proof_metrics=("opportunities_found", "drafts_created",
+ "meetings_drafted", "missed_revenue_sar"),
+ upgrade_path=("growth_os_monthly",),
+ ),
+ Service(
+ id="meeting_booking_sprint",
+ name_ar="سبرنت حجز الاجتماعات",
+ target_customer_ar="شركات لديها prospects ولا تحوّلهم لاجتماعات",
+ outcome_ar="invitations + meeting drafts + briefs + follow-ups",
+ inputs_required=("prospect_list", "calendar_link"),
+ workflow_steps=_DEFAULT_WORKFLOW,
+ deliverables_ar=(
+ "دعوات اجتماع",
+ "Pre-meeting brief",
+ "Calendar drafts",
+ "Post-meeting follow-up",
+ ),
+ pricing_min_sar=1500, pricing_max_sar=5000,
+ pricing_model="sprint",
+ risk_level="medium",
+ required_integrations=("google_calendar", "gmail"),
+ approval_policy="approval_required",
+ proof_metrics=("meetings_drafted", "meetings_confirmed",
+ "meetings_completed"),
+ upgrade_path=("growth_os_monthly",),
+ ),
+ Service(
+ id="partner_sprint",
+ name_ar="سبرنت شراكات",
+ target_customer_ar="شركات تحتاج نمو عبر الشركاء والوكالات",
+ outcome_ar="20 شريك محتمل + 10 رسائل + 5 اجتماعات + scorecard",
+ inputs_required=("sector", "partner_goal"),
+ workflow_steps=_DEFAULT_WORKFLOW,
+ deliverables_ar=(
+ "قائمة شركاء محتملين",
+ "Scorecard لكل شريك",
+ "Outreach drafts",
+ "Meeting plan",
+ "Referral agreement draft",
+ ),
+ pricing_min_sar=3000, pricing_max_sar=7500,
+ pricing_model="sprint",
+ risk_level="medium",
+ required_integrations=("gmail",),
+ approval_policy="approval_required",
+ proof_metrics=("partners_identified", "partner_meetings",
+ "referral_revenue_sar"),
+ upgrade_path=("agency_partner_program",),
+ ),
+ Service(
+ id="agency_partner_program",
+ name_ar="برنامج وكالة شريكة",
+ target_customer_ar="وكالات تسويق/مبيعات/CRM",
+ outcome_ar="بيع Dealix لعملاء الوكالة مع co-branding + revenue share",
+ inputs_required=("agency_profile", "client_count"),
+ workflow_steps=("agency_onboarding", "client_diagnostic",
+ "proposal", "pilot", "proof_pack", "revenue_share"),
+ deliverables_ar=(
+ "Agency onboarding",
+ "Client diagnostics",
+ "Co-branded proof packs",
+ "Revenue share dashboard",
+ ),
+ pricing_min_sar=10000, pricing_max_sar=50000,
+ pricing_model="one_time",
+ risk_level="medium",
+ required_integrations=("gmail", "google_calendar", "moyasar"),
+ approval_policy="approval_required",
+ proof_metrics=("clients_added", "agency_revenue_sar",
+ "co_branded_proofs"),
+ ),
+ Service(
+ id="whatsapp_compliance_setup",
+ name_ar="إعداد امتثال واتساب",
+ target_customer_ar="شركات تستخدم واتساب بشكل عشوائي",
+ outcome_ar="audit + opt-in templates + approval workflow + ledger",
+ inputs_required=("contact_list", "current_practice"),
+ workflow_steps=_DEFAULT_WORKFLOW,
+ deliverables_ar=(
+ "تصنيف القوائم",
+ "Opt-in templates",
+ "Approval cards",
+ "Opt-out ledger",
+ "Safety report",
+ ),
+ pricing_min_sar=1500, pricing_max_sar=4000,
+ pricing_model="one_time",
+ risk_level="high",
+ required_integrations=("whatsapp_cloud",),
+ approval_policy="draft_only",
+ proof_metrics=("contacts_classified", "opt_ins_collected",
+ "risks_blocked"),
+ upgrade_path=("growth_os_monthly",),
+ ),
+ Service(
+ id="linkedin_lead_gen_setup",
+ name_ar="إعداد LinkedIn Lead Gen",
+ target_customer_ar="شركات B2B تحتاج decision makers",
+ outcome_ar="حملة Lead Gen Form + audiences + ربط CRM + content angle",
+ inputs_required=("ICP", "offer", "ad_budget"),
+ workflow_steps=_DEFAULT_WORKFLOW,
+ deliverables_ar=(
+ "Audience plan",
+ "Lead magnet",
+ "Lead Gen Form",
+ "Hidden fields setup",
+ "Dealix intake",
+ "Follow-up drafts",
+ ),
+ pricing_min_sar=2000, pricing_max_sar=7500,
+ pricing_model="one_time",
+ risk_level="medium",
+ required_integrations=("linkedin_lead_forms",),
+ approval_policy="approval_required",
+ proof_metrics=("leads_captured", "qualified_leads",
+ "meetings_booked"),
+ upgrade_path=("growth_os_monthly",),
+ ),
+ Service(
+ id="executive_growth_brief",
+ name_ar="موجز نمو تنفيذي (Executive Brief)",
+ target_customer_ar="CEO / Growth Manager / Sales Manager",
+ outcome_ar="3 قرارات + 3 فرص + 3 مخاطر + Pipeline + اجتماعات اليوم",
+ inputs_required=("company_profile",),
+ workflow_steps=("intake", "aggregate", "prioritize", "deliver"),
+ deliverables_ar=(
+ "Daily brief عبر واتساب/Email",
+ "Approval cards (≤3 buttons)",
+ "Risk alerts",
+ "Weekly Founder Shadow Board",
+ ),
+ pricing_min_sar=499, pricing_max_sar=999,
+ pricing_model="monthly",
+ risk_level="low",
+ required_integrations=(),
+ approval_policy="approval_required",
+ proof_metrics=("decisions_made", "alerts_actioned"),
+ upgrade_path=("growth_os_monthly",),
+ ),
+)
+
+
+def get_service(service_id: str) -> Service | None:
+ return next((s for s in ALL_SERVICES if s.id == service_id), None)
+
+
+def list_all_services() -> dict[str, object]:
+ return {
+ "total": len(ALL_SERVICES),
+ "services": [s.to_dict() for s in ALL_SERVICES],
+ }
+
+
+def catalog_summary() -> dict[str, object]:
+ by_pricing: dict[str, int] = {}
+ by_risk: dict[str, int] = {}
+ for s in ALL_SERVICES:
+ by_pricing[s.pricing_model] = by_pricing.get(s.pricing_model, 0) + 1
+ by_risk[s.risk_level] = by_risk.get(s.risk_level, 0) + 1
+ return {
+ "total": len(ALL_SERVICES),
+ "by_pricing_model": by_pricing,
+ "by_risk_level": by_risk,
+ "free_offers": [s.id for s in ALL_SERVICES if s.pricing_max_sar == 0],
+ }
diff --git a/dealix/auto_client_acquisition/service_tower/service_scorecard.py b/dealix/auto_client_acquisition/service_tower/service_scorecard.py
new file mode 100644
index 00000000..e6a94c2b
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_tower/service_scorecard.py
@@ -0,0 +1,105 @@
+"""Service scorecard — يقيس نجاح كل خدمة بعد تشغيلها."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .service_catalog import get_service
+
+
+def calculate_service_success_score(
+ service_id: str, metrics: dict[str, Any],
+) -> dict[str, Any]:
+ """Score a service run 0..100 + verdict."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+
+ score = 0
+
+ # Generic outcomes that map to most services.
+ drafts_approved = int(metrics.get("drafts_approved", 0))
+ positive_replies = int(metrics.get("positive_replies", 0))
+ meetings = int(metrics.get("meetings", 0))
+ pipeline_sar = float(metrics.get("pipeline_sar", 0))
+ risks_blocked = int(metrics.get("risks_blocked", 0))
+ customer_satisfaction = int(metrics.get("customer_satisfaction", 0)) # 0..10
+
+ score += min(15, drafts_approved * 3)
+ score += min(20, positive_replies * 5)
+ score += min(20, meetings * 8)
+ score += min(20, int(pipeline_sar / 5_000))
+ score += min(10, risks_blocked * 2)
+ score += min(15, customer_satisfaction * 1)
+
+ score = max(0, min(100, score))
+
+ if score >= 70:
+ verdict = "strong_outcome"
+ elif score >= 40:
+ verdict = "decent_outcome"
+ else:
+ verdict = "needs_iteration"
+
+ return {
+ "service_id": service_id,
+ "score": score,
+ "verdict": verdict,
+ "captured_metrics": metrics,
+ }
+
+
+def recommend_next_step(metrics: dict[str, Any]) -> dict[str, Any]:
+ """Recommend the next step for a customer based on outcome metrics."""
+ pipeline_sar = float(metrics.get("pipeline_sar", 0))
+ meetings = int(metrics.get("meetings", 0))
+ csat = int(metrics.get("customer_satisfaction", 0))
+
+ if csat >= 8 and (pipeline_sar >= 25_000 or meetings >= 2):
+ return {
+ "action": "upsell_to_growth_os",
+ "label_ar": "اعرض Growth OS الشهري — العميل راضٍ والنتائج قوية.",
+ }
+ if pipeline_sar < 5_000 and meetings == 0:
+ return {
+ "action": "iterate_offer_or_segment",
+ "label_ar": "غيّر زاوية العرض أو القطاع — النتائج ضعيفة.",
+ }
+ return {
+ "action": "extend_pilot",
+ "label_ar": "مدّد الـ Pilot لأسبوعين أو جرّب قناة إضافية.",
+ }
+
+
+def build_service_scorecard(
+ service_id: str, metrics: dict[str, Any],
+) -> dict[str, Any]:
+ """Build a full Arabic scorecard for a service run."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ score_obj = calculate_service_success_score(service_id, metrics)
+ next_step = recommend_next_step(metrics)
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "score": score_obj.get("score"),
+ "verdict": score_obj.get("verdict"),
+ "metrics": metrics,
+ "next_step": next_step,
+ "summary_ar": summarize_scorecard_ar({
+ "service_id": service_id,
+ **score_obj, "next_step": next_step,
+ }),
+ }
+
+
+def summarize_scorecard_ar(scorecard: dict[str, Any]) -> str:
+ s = get_service(scorecard.get("service_id", ""))
+ name = s.name_ar if s else scorecard.get("service_id", "?")
+ score = scorecard.get("score", 0)
+ verdict = scorecard.get("verdict", "?")
+ next_step = (scorecard.get("next_step") or {}).get("label_ar", "")
+ return (
+ f"{name}: درجة {score} ({verdict}). الخطوة التالية: {next_step}"
+ )
diff --git a/dealix/auto_client_acquisition/service_tower/service_wizard.py b/dealix/auto_client_acquisition/service_tower/service_wizard.py
new file mode 100644
index 00000000..88d4fadd
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_tower/service_wizard.py
@@ -0,0 +1,137 @@
+"""Service wizard — يوصي بالخدمة المناسبة من إجابات بسيطة."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .service_catalog import ALL_SERVICES, get_service
+
+
+def recommend_service(
+ *,
+ company_type: str = "",
+ goal: str = "fill_pipeline",
+ has_contact_list: bool = False,
+ channels: list[str] | None = None,
+ budget_sar: int = 1000,
+) -> dict[str, Any]:
+ """
+ Recommend the best-fit service based on inputs. Deterministic.
+ """
+ channels = channels or []
+ company_type_lc = (company_type or "").lower()
+
+ chosen_id: str
+ reason: str
+
+ # Highest priority first.
+ if "agency" in company_type_lc or "وكالة" in company_type:
+ chosen_id = "agency_partner_program" if budget_sar >= 10_000 else "partner_sprint"
+ reason = "وكالة → برنامج شريك أو سبرنت شراكات."
+ elif has_contact_list:
+ chosen_id = "list_intelligence"
+ reason = "العميل لديه قائمة → ابدأ بـ List Intelligence."
+ elif "founder" in company_type_lc or "مؤسس" in company_type:
+ chosen_id = "self_growth_operator"
+ reason = "مؤسس بدون فريق نمو → Self-Growth Operator."
+ elif "executive" in company_type_lc or "ceo" in company_type_lc:
+ chosen_id = "executive_growth_brief"
+ reason = "CEO/تنفيذي → موجز نمو يومي."
+ elif "whatsapp" in company_type_lc or "واتساب" in company_type:
+ chosen_id = "whatsapp_compliance_setup"
+ reason = "حالة واتساب عشوائية → امتثال أولاً."
+ elif goal == "rescue_lost_revenue":
+ chosen_id = "email_revenue_rescue"
+ reason = "الهدف استعادة إيراد ضائع → Email Revenue Rescue."
+ elif goal == "book_meetings":
+ chosen_id = "meeting_booking_sprint"
+ reason = "الهدف اجتماعات → Meeting Booking Sprint."
+ elif goal == "expand_partners":
+ chosen_id = "partner_sprint"
+ reason = "الهدف شراكات → Partner Sprint."
+ elif budget_sar >= 2999:
+ chosen_id = "growth_os_monthly"
+ reason = "الميزانية شهرية → Growth OS."
+ else:
+ chosen_id = "first_10_opportunities_sprint"
+ reason = "الافتراضي: ابدأ بـ 10 فرص في 10 دقائق."
+
+ service = get_service(chosen_id)
+ return {
+ "recommended_service_id": chosen_id,
+ "service": service.to_dict() if service else None,
+ "reason_ar": reason,
+ "next_step_ar": (
+ "املأ نموذج الـ intake، وسنبدأ خلال 24 ساعة عمل."
+ ),
+ }
+
+
+def build_intake_questions(service_id: str) -> dict[str, Any]:
+ """Return intake questions for a service. Empty if service unknown."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}", "questions": []}
+
+ base_q = [
+ {"key": "company_name", "label_ar": "اسم الشركة", "required": True},
+ {"key": "sector", "label_ar": "القطاع", "required": True},
+ {"key": "city", "label_ar": "المدينة", "required": True},
+ {"key": "decision_maker_name", "label_ar": "اسم صانع القرار", "required": True},
+ {"key": "decision_maker_role", "label_ar": "المسمى الوظيفي", "required": True},
+ ]
+ extra = []
+ if "uploaded_csv" in s.inputs_required:
+ extra.append({"key": "uploaded_csv", "label_ar": "ملف CSV", "required": True})
+ if "offer" in s.inputs_required:
+ extra.append({"key": "offer", "label_ar": "وصف العرض", "required": True})
+ if "goal" in s.inputs_required:
+ extra.append({"key": "goal", "label_ar": "الهدف الأساسي", "required": True})
+ if "channels_available" in s.inputs_required:
+ extra.append({"key": "channels", "label_ar": "القنوات المتاحة", "required": False})
+
+ return {
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "questions": base_q + extra,
+ "approval_required": True,
+ }
+
+
+def validate_service_inputs(
+ service_id: str, payload: dict[str, Any],
+) -> dict[str, Any]:
+ """Validate intake payload against service requirements."""
+ s = get_service(service_id)
+ if s is None:
+ return {"valid": False, "errors_ar": [f"خدمة غير معروفة: {service_id}"]}
+
+ errors: list[str] = []
+ for required in s.inputs_required:
+ if required in ("uploaded_csv", "offer", "goal", "channels_available",
+ "ICP", "calendar_link", "company_profile",
+ "current_practice", "ad_budget", "client_count",
+ "partner_goal", "team_size", "channels", "agency_profile",
+ "prospect_list", "gmail_label", "contact_list",
+ "goals", "sector", "city"):
+ if not payload.get(required):
+ errors.append(f"الحقل ناقص: {required}")
+
+ return {
+ "valid": not errors,
+ "errors_ar": errors,
+ "service_id": service_id,
+ }
+
+
+def summarize_recommendation_ar(result: dict[str, Any]) -> str:
+ """Build a one-paragraph Arabic recommendation summary."""
+ sid = result.get("recommended_service_id", "?")
+ reason = result.get("reason_ar", "")
+ svc = result.get("service") or {}
+ name = svc.get("name_ar", sid)
+ outcome = svc.get("outcome_ar", "")
+ return (
+ f"الخدمة المقترحة: {name}. السبب: {reason} "
+ f"المخرجات: {outcome}"
+ )
diff --git a/dealix/auto_client_acquisition/service_tower/upgrade_paths.py b/dealix/auto_client_acquisition/service_tower/upgrade_paths.py
new file mode 100644
index 00000000..14db9253
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_tower/upgrade_paths.py
@@ -0,0 +1,59 @@
+"""Upgrade paths — يوصي بالخدمة التالية بعد كل خدمة."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .service_catalog import get_service
+
+
+def recommend_upgrade(
+ service_id: str,
+ *,
+ results: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+ """Recommend the next service for a customer to buy."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+
+ upgrade_targets = list(s.upgrade_path) or ["growth_os_monthly"]
+ next_id = upgrade_targets[0]
+ next_s = get_service(next_id)
+
+ return {
+ "from_service": service_id,
+ "from_service_name_ar": s.name_ar,
+ "recommended_service_id": next_id,
+ "recommended_service_name_ar": next_s.name_ar if next_s else next_id,
+ "monthly_sar": next_s.pricing_min_sar if next_s else 0,
+ "reason_ar": (
+ f"بعد {s.name_ar}، الترقية الطبيعية هي "
+ f"{next_s.name_ar if next_s else next_id} للحفاظ على الاستمرارية."
+ ),
+ }
+
+
+def build_upsell_message_ar(
+ service_id: str,
+ next_offer: str,
+) -> str:
+ """Build a one-paragraph Arabic upsell message."""
+ s = get_service(service_id)
+ next_s = get_service(next_offer)
+ if not s or not next_s:
+ return "بعد إثبات النتائج، نوصي بالترقية للخدمة التالية."
+ return (
+ f"شاكر لك على تجربة {s.name_ar}. "
+ f"بناءً على النتائج، الترقية المنطقية هي {next_s.name_ar} "
+ "للاستمرار في النمو شهرياً مع نفس مستوى الـ Proof Pack. "
+ "أرسل لي تأكيد ونبدأ الأسبوع القادم."
+ )
+
+
+def map_service_to_subscription(service_id: str) -> str:
+ """Map any service to its eventual subscription."""
+ s = get_service(service_id)
+ if s is None:
+ return "growth_os_monthly"
+ return s.upgrade_path[0] if s.upgrade_path else "growth_os_monthly"
diff --git a/dealix/auto_client_acquisition/service_tower/whatsapp_ceo_control.py b/dealix/auto_client_acquisition/service_tower/whatsapp_ceo_control.py
new file mode 100644
index 00000000..c62a4221
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_tower/whatsapp_ceo_control.py
@@ -0,0 +1,88 @@
+"""WhatsApp CEO Control — كل القرارات بكروت عربية ≤3 أزرار."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .service_catalog import get_service
+
+
+def build_ceo_daily_service_brief() -> dict[str, Any]:
+ """The daily service brief sent to the CEO via WhatsApp/Email."""
+ return {
+ "type": "ceo_daily_service_brief",
+ "title_ar": "موجز الخدمات اليومي",
+ "summary_ar": [
+ "3 خدمات نشطة اليوم.",
+ "5 رسائل drafts تنتظر اعتمادك.",
+ "2 Free Diagnostic مكتمل وينتظر التسليم.",
+ "1 شريك وكالة جاهز للعرض.",
+ "0 مخاطر سمعة (الحالة صحية).",
+ ],
+ "buttons_ar": ["اعرض المسودات", "موافقة جماعية", "لاحقاً"],
+ "approval_required": True,
+ }
+
+
+def build_service_approval_card(
+ service_id: str, action: str,
+) -> dict[str, Any]:
+ """Approval card for a single service action (draft send / publish / charge)."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+ label_ar_by_action = {
+ "send_email": "إرسال إيميل",
+ "send_whatsapp": "إرسال واتساب",
+ "insert_calendar": "إدراج موعد",
+ "create_payment_link": "إنشاء رابط دفع",
+ "publish_review_reply": "نشر رد تقييم",
+ "share_diagnostic": "مشاركة Free Diagnostic",
+ }
+ return {
+ "type": "service_approval",
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "action": action,
+ "title_ar": f"اعتماد: {label_ar_by_action.get(action, action)}",
+ "summary_ar": f"يتم تنفيذ هذا الفعل ضمن خدمة {s.name_ar}.",
+ "risk_level": s.risk_level,
+ "buttons_ar": ["اعتمد", "عدّل", "ارفض"],
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def build_risk_alert_card() -> dict[str, Any]:
+ """A risk alert card surfaced to the CEO."""
+ return {
+ "type": "risk_alert",
+ "title_ar": "تنبيه مخاطر",
+ "summary_ar": (
+ "ارتفاع نسبة الـ bounce على الإيميل تجاوز الحد الآمن. "
+ "اقتراح: إيقاف الحملات الجديدة 14 يوماً + تنظيف القائمة."
+ ),
+ "risk_level": "high",
+ "buttons_ar": ["أوقف القناة", "خفّض الحجم", "تجاهل"],
+ "approval_required": True,
+ }
+
+
+def build_end_of_day_service_report() -> dict[str, Any]:
+ """End-of-day report on services run today."""
+ return {
+ "type": "end_of_day_service_report",
+ "title_ar": "تقرير نهاية اليوم — الخدمات",
+ "summary_ar": [
+ "خدمات منفذة اليوم: 3.",
+ "Drafts معتمدة: 6.",
+ "ردود إيجابية: 2.",
+ "اجتماعات مجدولة: 1.",
+ "Pipeline متأثر: 24,000 ريال.",
+ "مخاطر تم منعها: 8.",
+ ],
+ "next_day_focus_ar": (
+ "غداً: تابع الردود الإيجابية، اعتمد رسائل Partner Sprint، "
+ "سلّم 2 Free Diagnostic للعملاء الجدد."
+ ),
+ }
diff --git a/dealix/auto_client_acquisition/targeting_os/__init__.py b/dealix/auto_client_acquisition/targeting_os/__init__.py
new file mode 100644
index 00000000..9bac23d3
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/__init__.py
@@ -0,0 +1,177 @@
+"""Targeting & Acquisition OS — يستهدف بذكاء، يقيّم المخاطر، يقترح القنوات.
+
+Account-first targeting (شركات قبل أشخاص) + buying-committee mapping +
+contactability gate + multi-channel strategy + reputation guard +
+daily autopilot + self-growth mode + free diagnostic + contract drafts.
+
+كل شيء deterministic، عربي، draft/approval-first، لا scraping ولا cold WA.
+"""
+
+from __future__ import annotations
+
+from .account_finder import (
+ AccountSignal,
+ explain_why_now,
+ rank_accounts,
+ recommend_account_source_strategy,
+ recommend_accounts,
+ score_account_fit,
+)
+from .acquisition_scorecard import (
+ build_acquisition_scorecard,
+ calculate_meetings_booked,
+ calculate_pipeline_created,
+ calculate_productivity_score,
+ calculate_risks_blocked,
+)
+from .buyer_role_mapper import (
+ ALL_BUYER_ROLES,
+ draft_role_based_angle,
+ map_buying_committee,
+ recommend_decision_maker_roles,
+ recommend_influencer_roles,
+)
+from .contact_source_policy import (
+ ALL_SOURCES,
+ allowed_channels_for_source,
+ classify_source,
+ required_review_level,
+ retention_recommendation,
+ source_risk_score,
+)
+from .contactability_matrix import (
+ ACTION_MODES,
+ BLOCK_REASONS,
+ allowed_action_modes,
+ block_reason_codes,
+ evaluate_contactability,
+ explain_contactability_ar,
+)
+from .contract_drafts import (
+ draft_agency_partner_outline,
+ draft_dpa_outline,
+ draft_pilot_agreement_outline,
+ draft_referral_agreement_outline,
+ draft_scope_of_work,
+)
+from .daily_autopilot import (
+ build_daily_targeting_brief,
+ build_end_of_day_report,
+ prioritize_cards,
+ recommend_today_actions,
+)
+from .email_strategy import (
+ build_followup_sequence,
+ draft_b2b_email,
+ include_unsubscribe_footer,
+ recommend_pacing,
+ score_email_risk,
+)
+from .free_diagnostic import (
+ analyze_uploaded_list_preview,
+ build_free_growth_diagnostic,
+ build_mini_proof_plan,
+ recommend_paid_pilot_offer,
+)
+from .linkedin_strategy import (
+ build_lead_gen_form_plan,
+ build_manual_research_task,
+ build_safe_connection_message,
+ linkedin_do_not_do,
+ recommend_linkedin_strategy,
+)
+from .outreach_scheduler import (
+ build_outreach_plan,
+ enforce_daily_limits,
+ schedule_followups,
+ stop_on_opt_out,
+ summarize_plan_ar,
+)
+from .reputation_guard import (
+ calculate_channel_reputation,
+ recommend_recovery_action,
+ risk_thresholds,
+ should_pause_channel,
+ summarize_reputation_ar,
+)
+from .self_growth_mode import (
+ build_dealix_self_growth_plan,
+ build_free_service_offer,
+ build_self_growth_daily_brief,
+ build_weekly_learning_report,
+ recommend_dealix_targets,
+)
+from .service_offers import (
+ build_offer_card,
+ estimate_service_price,
+ list_targeting_services,
+ recommend_service_offer,
+)
+from .social_strategy import (
+ build_social_listening_plan,
+ draft_public_reply,
+ recommend_social_sources,
+ social_do_not_do,
+)
+from .whatsapp_strategy import (
+ build_opt_in_request_template,
+ draft_whatsapp_message,
+ requires_opt_in,
+ score_whatsapp_risk,
+ whatsapp_do_not_do,
+)
+
+__all__ = [
+ # account_finder
+ "AccountSignal", "explain_why_now", "rank_accounts",
+ "recommend_account_source_strategy", "recommend_accounts", "score_account_fit",
+ # acquisition_scorecard
+ "build_acquisition_scorecard", "calculate_meetings_booked",
+ "calculate_pipeline_created", "calculate_productivity_score",
+ "calculate_risks_blocked",
+ # buyer_role_mapper
+ "ALL_BUYER_ROLES", "draft_role_based_angle", "map_buying_committee",
+ "recommend_decision_maker_roles", "recommend_influencer_roles",
+ # contact_source_policy
+ "ALL_SOURCES", "allowed_channels_for_source", "classify_source",
+ "required_review_level", "retention_recommendation", "source_risk_score",
+ # contactability_matrix
+ "ACTION_MODES", "BLOCK_REASONS", "allowed_action_modes",
+ "block_reason_codes", "evaluate_contactability", "explain_contactability_ar",
+ # contract_drafts
+ "draft_agency_partner_outline", "draft_dpa_outline",
+ "draft_pilot_agreement_outline", "draft_referral_agreement_outline",
+ "draft_scope_of_work",
+ # daily_autopilot
+ "build_daily_targeting_brief", "build_end_of_day_report",
+ "prioritize_cards", "recommend_today_actions",
+ # email_strategy
+ "build_followup_sequence", "draft_b2b_email",
+ "include_unsubscribe_footer", "recommend_pacing", "score_email_risk",
+ # free_diagnostic
+ "analyze_uploaded_list_preview", "build_free_growth_diagnostic",
+ "build_mini_proof_plan", "recommend_paid_pilot_offer",
+ # linkedin_strategy
+ "build_lead_gen_form_plan", "build_manual_research_task",
+ "build_safe_connection_message", "linkedin_do_not_do",
+ "recommend_linkedin_strategy",
+ # outreach_scheduler
+ "build_outreach_plan", "enforce_daily_limits",
+ "schedule_followups", "stop_on_opt_out", "summarize_plan_ar",
+ # reputation_guard
+ "calculate_channel_reputation", "recommend_recovery_action",
+ "risk_thresholds", "should_pause_channel", "summarize_reputation_ar",
+ # self_growth_mode
+ "build_dealix_self_growth_plan", "build_free_service_offer",
+ "build_self_growth_daily_brief", "build_weekly_learning_report",
+ "recommend_dealix_targets",
+ # service_offers
+ "build_offer_card", "estimate_service_price",
+ "list_targeting_services", "recommend_service_offer",
+ # social_strategy
+ "build_social_listening_plan", "draft_public_reply",
+ "recommend_social_sources", "social_do_not_do",
+ # whatsapp_strategy
+ "build_opt_in_request_template", "draft_whatsapp_message",
+ "requires_opt_in", "score_whatsapp_risk", "whatsapp_do_not_do",
+]
diff --git a/dealix/auto_client_acquisition/targeting_os/account_finder.py b/dealix/auto_client_acquisition/targeting_os/account_finder.py
new file mode 100644
index 00000000..761c6e67
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/account_finder.py
@@ -0,0 +1,215 @@
+"""Account-first targeting — يبحث عن الشركات المناسبة قبل الأشخاص."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import Any
+
+# Signals that indicate a company is "in market" right now.
+ACCOUNT_SIGNALS_AR: dict[str, str] = {
+ "hiring_sales": "توظيف مبيعات",
+ "new_branch": "فرع جديد",
+ "website_updated": "تحديث الموقع",
+ "active_ads": "إعلانات نشطة",
+ "event_participation": "مشاركة في فعاليات",
+ "google_reviews": "تقييمات Google نشطة",
+ "booking_link": "صفحة حجز/طلب",
+ "crm_visible": "بيانات CRM متوفرة",
+ "growing_team": "نمو الفريق",
+ "partner_potential": "إمكانية شراكة",
+ "expansion_news": "أخبار توسع",
+ "leadership_change": "تغيير قيادي",
+}
+
+
+@dataclass(frozen=True)
+class AccountSignal:
+ """A single buying-readiness signal on a company."""
+ key: str
+ label_ar: str
+ weight: int # 1..10
+ why_ar: str
+
+ def to_dict(self) -> dict[str, object]:
+ return {
+ "key": self.key, "label_ar": self.label_ar,
+ "weight": self.weight, "why_ar": self.why_ar,
+ }
+
+
+# Default signal weights — can be overridden per sector.
+_DEFAULT_WEIGHTS: dict[str, int] = {
+ "hiring_sales": 9,
+ "new_branch": 8,
+ "expansion_news": 9,
+ "active_ads": 7,
+ "growing_team": 7,
+ "leadership_change": 8,
+ "booking_link": 5,
+ "website_updated": 4,
+ "google_reviews": 5,
+ "crm_visible": 3,
+ "event_participation": 6,
+ "partner_potential": 6,
+}
+
+
+def _signal_objs(signals: dict[str, bool] | list[str]) -> list[AccountSignal]:
+ out: list[AccountSignal] = []
+ if isinstance(signals, list):
+ signals = {s: True for s in signals}
+ for key, val in signals.items():
+ if not val or key not in ACCOUNT_SIGNALS_AR:
+ continue
+ out.append(AccountSignal(
+ key=key,
+ label_ar=ACCOUNT_SIGNALS_AR[key],
+ weight=_DEFAULT_WEIGHTS.get(key, 3),
+ why_ar=f"إشارة: {ACCOUNT_SIGNALS_AR[key]}",
+ ))
+ return out
+
+
+def score_account_fit(account: dict[str, Any]) -> dict[str, Any]:
+ """Score an account 0..100 based on its signals + sector+size match."""
+ signals = _signal_objs(account.get("signals", {}))
+ base = sum(s.weight for s in signals)
+ score = min(100, base * 4) # ~25 weight points = max 100
+ if account.get("sector_match"):
+ score = min(100, score + 10)
+ if account.get("city_match"):
+ score = min(100, score + 5)
+
+ if score >= 70:
+ tier = "hot"
+ elif score >= 40:
+ tier = "warm"
+ elif score >= 15:
+ tier = "watching"
+ else:
+ tier = "cold"
+
+ return {
+ "score": score,
+ "tier": tier,
+ "signals": [s.to_dict() for s in signals],
+ "signal_count": len(signals),
+ }
+
+
+def explain_why_now(account: dict[str, Any]) -> str:
+ """Build an Arabic 'why now' line from an account's signals."""
+ signals = _signal_objs(account.get("signals", {}))
+ if not signals:
+ return "لا توجد إشارات شراء واضحة الآن — متابعة دورية مقترحة."
+ top = sorted(signals, key=lambda s: -s.weight)[:2]
+ labels = " + ".join(s.label_ar for s in top)
+ company = account.get("name") or "الشركة"
+ return f"{company} تظهر إشارات: {labels}. نافذة فرصة مناسبة الآن."
+
+
+def recommend_account_source_strategy(account: dict[str, Any]) -> dict[str, Any]:
+ """Recommend safe sources for reaching this account's decision-makers."""
+ has_crm = bool(account.get("crm_visible"))
+ has_ads = bool(account.get("active_ads"))
+ has_events = bool(account.get("event_participation"))
+
+ primary = []
+ if has_crm:
+ primary.append("crm_customer")
+ primary.append("website_form")
+ primary.append("linkedin_lead_form")
+
+ if has_ads:
+ primary.append("ads_retargeting")
+ if has_events:
+ primary.append("event_lead")
+
+ return {
+ "primary_sources": primary,
+ "blocked_sources": ["scraped_email", "scraped_phone", "purchased_list"],
+ "notes_ar": (
+ "ابدأ بمصادر مصرّح بها: قوائم العميل، Lead Gen Forms، "
+ "نماذج الموقع، شركاء، أحداث. لا scraping ولا قوائم مشتراة."
+ ),
+ }
+
+
+def recommend_accounts(
+ sector: str,
+ city: str,
+ *,
+ offer: str = "",
+ goal: str = "fill_pipeline",
+ limit: int = 10,
+ seed_signals: list[str] | None = None,
+) -> dict[str, Any]:
+ """
+ Generate a deterministic list of recommended target accounts.
+
+ This is a structural template — production reads from real data sources
+ (Google Maps, CRM, web forms, etc). The output shape stays identical.
+ """
+ seed_signals = seed_signals or [
+ "hiring_sales", "new_branch", "active_ads",
+ "growing_team", "booking_link", "google_reviews",
+ ]
+ sector_label_ar = {
+ "training": "التدريب", "saas": "البرمجيات", "real_estate": "العقار",
+ "retail": "التجزئة", "healthcare": "الرعاية الصحية",
+ "logistics": "اللوجستيات", "fintech": "الفنتك",
+ "agency": "الوكالات", "education": "التعليم",
+ }.get(sector.lower(), sector)
+
+ accounts: list[dict[str, Any]] = []
+ n = max(1, min(limit, 25))
+ for i in range(n):
+ # Spread signals across accounts deterministically.
+ my_signals = {seed_signals[(i + j) % len(seed_signals)]: True
+ for j in range(2 + (i % 3))}
+ acct = {
+ "name": f"شركة {sector_label_ar} #{i + 1} في {city}",
+ "sector": sector,
+ "city": city,
+ "signals": my_signals,
+ "sector_match": True,
+ "city_match": True,
+ }
+ scored = score_account_fit(acct)
+ sources = recommend_account_source_strategy(acct)
+ acct.update({
+ "fit_score": scored["score"],
+ "tier": scored["tier"],
+ "why_now_ar": explain_why_now(acct),
+ "primary_sources": sources["primary_sources"],
+ "best_angle_ar": (
+ f"عرض Pilot 7 أيام لاستخراج 10 فرص في قطاع {sector_label_ar}."
+ if not offer else
+ f"العرض المقترح: {offer}."
+ ),
+ "recommended_channel": (
+ "email_first"
+ if "crm_visible" in my_signals
+ else "linkedin_lead_form_first"
+ ),
+ "risk_level": "low" if scored["score"] >= 50 else "medium",
+ })
+ accounts.append(acct)
+
+ accounts = rank_accounts(accounts)
+ return {
+ "sector": sector, "city": city, "goal": goal, "offer": offer,
+ "total": len(accounts),
+ "accounts": accounts,
+ "do_not_do_ar": [
+ "لا scraping للبيانات.",
+ "لا cold WhatsApp.",
+ "لا auto-DM على LinkedIn.",
+ "لا charge بدون موافقة.",
+ ],
+ }
+
+
+def rank_accounts(accounts: list[dict[str, Any]]) -> list[dict[str, Any]]:
+ """Sort accounts by fit_score desc; stable for ties."""
+ return sorted(accounts, key=lambda a: -int(a.get("fit_score", 0)))
diff --git a/dealix/auto_client_acquisition/targeting_os/acquisition_scorecard.py b/dealix/auto_client_acquisition/targeting_os/acquisition_scorecard.py
new file mode 100644
index 00000000..a0a3df7f
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/acquisition_scorecard.py
@@ -0,0 +1,86 @@
+"""Acquisition scorecard — يقيس النتائج بشكل deterministic."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def calculate_pipeline_created(opportunities: list[dict[str, Any]]) -> dict[str, Any]:
+ """Sum expected_value_sar across opportunities."""
+ total = sum(float(o.get("expected_value_sar", 0)) for o in opportunities or [])
+ return {
+ "opportunities_count": len(opportunities or []),
+ "pipeline_sar": round(total, 2),
+ }
+
+
+def calculate_meetings_booked(events: list[dict[str, Any]]) -> dict[str, Any]:
+ """Count meetings by status."""
+ drafted = sum(1 for e in events or [] if e.get("status") == "drafted")
+ confirmed = sum(1 for e in events or [] if e.get("status") == "confirmed")
+ completed = sum(1 for e in events or [] if e.get("status") == "completed")
+ return {
+ "drafted": drafted, "confirmed": confirmed, "completed": completed,
+ "total": drafted + confirmed + completed,
+ }
+
+
+def calculate_risks_blocked(actions: list[dict[str, Any]]) -> dict[str, Any]:
+ """Count actions that were blocked by policy/contactability."""
+ blocked = [a for a in actions or [] if a.get("status") == "blocked"]
+ by_reason: dict[str, int] = {}
+ for a in blocked:
+ reason = a.get("block_reason", "unknown")
+ by_reason[reason] = by_reason.get(reason, 0) + 1
+ return {"total": len(blocked), "by_reason": by_reason}
+
+
+def calculate_productivity_score(metrics: dict[str, Any]) -> dict[str, Any]:
+ """Compute a productivity score 0..100 from key acquisition metrics."""
+ accounts = int(metrics.get("accounts_researched", 0))
+ drafts = int(metrics.get("drafts_created", 0))
+ approvals = int(metrics.get("approvals_received", 0))
+ replies = int(metrics.get("positive_replies", 0))
+ meetings = int(metrics.get("meetings_booked", 0))
+
+ score = 0
+ score += min(20, accounts // 3)
+ score += min(20, drafts * 2)
+ score += min(20, approvals * 4)
+ score += min(20, replies * 5)
+ score += min(20, meetings * 8)
+ score = max(0, min(100, score))
+
+ if score >= 70:
+ verdict = "strong"
+ elif score >= 40:
+ verdict = "decent"
+ else:
+ verdict = "needs_focus"
+
+ return {"score": score, "verdict": verdict}
+
+
+def build_acquisition_scorecard(metrics: dict[str, Any]) -> dict[str, Any]:
+ """Build a comprehensive Arabic acquisition scorecard."""
+ pipeline = calculate_pipeline_created(metrics.get("opportunities", []))
+ meetings = calculate_meetings_booked(metrics.get("events", []))
+ risks = calculate_risks_blocked(metrics.get("actions", []))
+ productivity = calculate_productivity_score(metrics)
+
+ return {
+ "summary_ar": [
+ f"الحسابات المُحلّلة: {metrics.get('accounts_researched', 0)}",
+ f"أصحاب القرار المُعرَّفين: {metrics.get('decision_makers_mapped', 0)}",
+ f"رسائل drafts: {metrics.get('drafts_created', 0)}",
+ f"اعتمادات: {metrics.get('approvals_received', 0)}",
+ f"ردود إيجابية: {metrics.get('positive_replies', 0)}",
+ f"اجتماعات: {meetings['total']}",
+ f"Pipeline متأثر: {pipeline['pipeline_sar']:.0f} ريال",
+ f"مخاطر تم منعها: {risks['total']}",
+ ],
+ "pipeline": pipeline,
+ "meetings": meetings,
+ "risks_blocked": risks,
+ "productivity_score": productivity,
+ }
diff --git a/dealix/auto_client_acquisition/targeting_os/buyer_role_mapper.py b/dealix/auto_client_acquisition/targeting_os/buyer_role_mapper.py
new file mode 100644
index 00000000..858a0b93
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/buyer_role_mapper.py
@@ -0,0 +1,151 @@
+"""Map buying committees — من غالباً يقرر داخل الشركة."""
+
+from __future__ import annotations
+
+from typing import Any
+
+# All buyer roles Dealix knows about, with Arabic labels.
+ALL_BUYER_ROLES: dict[str, str] = {
+ "founder_ceo": "المؤسس / الرئيس التنفيذي",
+ "coo": "مدير العمليات",
+ "head_of_sales": "مدير المبيعات",
+ "marketing_manager": "مدير التسويق",
+ "business_development": "تطوير الأعمال",
+ "operations_manager": "مدير العمليات التشغيلية",
+ "clinic_manager": "مدير العيادة",
+ "branch_manager": "مدير الفرع",
+ "hr_manager": "مدير الموارد البشرية",
+ "procurement_manager": "مدير المشتريات",
+ "agency_owner": "صاحب الوكالة",
+ "store_manager": "مدير المتجر",
+ "growth_manager": "مدير النمو",
+ "cto": "المدير التقني",
+}
+
+# Sector-specific decision-maker priors (descending priority).
+_DM_BY_SECTOR: dict[str, list[str]] = {
+ "training": ["founder_ceo", "head_of_sales", "hr_manager"],
+ "saas": ["founder_ceo", "head_of_sales", "growth_manager"],
+ "real_estate": ["founder_ceo", "head_of_sales", "branch_manager"],
+ "retail": ["founder_ceo", "store_manager", "marketing_manager"],
+ "healthcare": ["clinic_manager", "founder_ceo", "operations_manager"],
+ "logistics": ["coo", "operations_manager", "founder_ceo"],
+ "fintech": ["founder_ceo", "growth_manager", "cto"],
+ "agency": ["agency_owner", "head_of_sales", "growth_manager"],
+ "education": ["founder_ceo", "operations_manager", "marketing_manager"],
+ "consulting": ["founder_ceo", "business_development", "head_of_sales"],
+}
+
+_INFLUENCERS_BY_SECTOR: dict[str, list[str]] = {
+ "training": ["marketing_manager", "operations_manager"],
+ "saas": ["marketing_manager", "cto"],
+ "real_estate": ["marketing_manager"],
+ "retail": ["operations_manager"],
+ "healthcare": ["marketing_manager", "operations_manager"],
+ "logistics": ["procurement_manager"],
+ "fintech": ["marketing_manager", "head_of_sales"],
+ "agency": ["marketing_manager", "business_development"],
+ "education": ["hr_manager"],
+ "consulting": ["marketing_manager"],
+}
+
+# Goal-based message angles per role.
+_ROLE_ANGLES_AR: dict[str, str] = {
+ "founder_ceo": "نمو إيرادات ملموس بدون توظيف فريق كبير.",
+ "coo": "تنظيم العمليات وقياس الأثر يومياً.",
+ "head_of_sales": "ملء الـ pipeline بفرص مؤهلة + متابعة منظمة.",
+ "marketing_manager": "تحويل الـ traffic والإعلانات إلى اجتماعات.",
+ "business_development": "فتح قنوات شراكة وتوزيع جديدة.",
+ "operations_manager": "أتمتة المتابعات + تقليل الوقت الضائع.",
+ "clinic_manager": "تذكير المرضى + ردود التقييمات + قنوات حجز.",
+ "branch_manager": "إدارة عملاء الفرع + reactivation.",
+ "hr_manager": "برامج تدريب وتوظيف بدون فوضى inbox.",
+ "procurement_manager": "تقييم مزودين + التزامات SLA واضحة.",
+ "agency_owner": "خدمة عملاء الوكالة + Proof Pack + revenue share.",
+ "store_manager": "استرجاع العملاء + payment links + reviews.",
+ "growth_manager": "تجارب نمو منظمة + قياس Proof.",
+ "cto": "أمان البيانات + PDPL + تكاملات مصرّحة.",
+}
+
+
+def _norm_sector(sector: str) -> str:
+ s = (sector or "").lower().strip()
+ return s if s in _DM_BY_SECTOR else "saas"
+
+
+def map_buying_committee(
+ sector: str,
+ *,
+ company_size: str = "small",
+ goal: str = "fill_pipeline",
+) -> dict[str, Any]:
+ """Build a buying-committee map for a sector + company-size."""
+ s = _norm_sector(sector)
+ dm_keys = _DM_BY_SECTOR[s]
+ inf_keys = _INFLUENCERS_BY_SECTOR[s]
+
+ # For small companies, the founder is almost always the primary DM.
+ if company_size in ("micro", "small") and "founder_ceo" not in dm_keys[:2]:
+ dm_keys = ["founder_ceo"] + [k for k in dm_keys if k != "founder_ceo"]
+
+ return {
+ "sector": s,
+ "company_size": company_size,
+ "goal": goal,
+ "primary_decision_maker": {
+ "role_key": dm_keys[0],
+ "label_ar": ALL_BUYER_ROLES[dm_keys[0]],
+ "angle_ar": _ROLE_ANGLES_AR[dm_keys[0]],
+ },
+ "secondary_decision_makers": [
+ {"role_key": k, "label_ar": ALL_BUYER_ROLES[k],
+ "angle_ar": _ROLE_ANGLES_AR[k]}
+ for k in dm_keys[1:]
+ ],
+ "influencers": [
+ {"role_key": k, "label_ar": ALL_BUYER_ROLES[k],
+ "angle_ar": _ROLE_ANGLES_AR[k]}
+ for k in inf_keys
+ ],
+ "approach_notes_ar": (
+ "ابدأ بمحاور أعلى — المؤسس أو مدير المبيعات. "
+ "اشمل الـ influencers في الرسالة الثانية لبناء التوافق الداخلي."
+ ),
+ }
+
+
+def recommend_decision_maker_roles(
+ sector: str, *, goal: str = "fill_pipeline",
+) -> list[dict[str, str]]:
+ s = _norm_sector(sector)
+ return [
+ {"role_key": k, "label_ar": ALL_BUYER_ROLES[k],
+ "angle_ar": _ROLE_ANGLES_AR[k]}
+ for k in _DM_BY_SECTOR[s]
+ ]
+
+
+def recommend_influencer_roles(
+ sector: str, *, goal: str = "fill_pipeline",
+) -> list[dict[str, str]]:
+ s = _norm_sector(sector)
+ return [
+ {"role_key": k, "label_ar": ALL_BUYER_ROLES[k],
+ "angle_ar": _ROLE_ANGLES_AR[k]}
+ for k in _INFLUENCERS_BY_SECTOR[s]
+ ]
+
+
+def draft_role_based_angle(
+ role_key: str, *, sector: str = "saas", offer: str = "",
+) -> dict[str, str]:
+ """Build a one-sentence Arabic angle suited to a role."""
+ role_key = role_key if role_key in ALL_BUYER_ROLES else "founder_ceo"
+ role_ar = ALL_BUYER_ROLES[role_key]
+ base_angle = _ROLE_ANGLES_AR[role_key]
+ offer_part = f" — {offer}" if offer else ""
+ return {
+ "role_key": role_key,
+ "role_ar": role_ar,
+ "angle_ar": f"رسالة لـ{role_ar}: {base_angle}{offer_part}",
+ }
diff --git a/dealix/auto_client_acquisition/targeting_os/contact_source_policy.py b/dealix/auto_client_acquisition/targeting_os/contact_source_policy.py
new file mode 100644
index 00000000..2bfc463b
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/contact_source_policy.py
@@ -0,0 +1,148 @@
+"""Contact source policy — كل contact له مصدر، غرض، ومستوى مخاطرة."""
+
+from __future__ import annotations
+
+# All recognized contact sources, ordered roughly safest → riskiest.
+ALL_SOURCES: tuple[str, ...] = (
+ "crm_customer",
+ "inbound_lead",
+ "website_form",
+ "linkedin_lead_form",
+ "event_lead",
+ "referral",
+ "partner_intro",
+ "manual_research",
+ "uploaded_list",
+ "unknown_source",
+ "cold_list",
+ "opt_out",
+)
+
+# Risk score per source (0..100; higher = riskier).
+_SOURCE_RISK: dict[str, int] = {
+ "crm_customer": 5,
+ "inbound_lead": 5,
+ "website_form": 10,
+ "linkedin_lead_form": 10,
+ "event_lead": 20,
+ "referral": 25,
+ "partner_intro": 25,
+ "manual_research": 50,
+ "uploaded_list": 60,
+ "unknown_source": 80,
+ "cold_list": 95,
+ "opt_out": 100,
+}
+
+
+def classify_source(source: str) -> dict[str, object]:
+ """Classify a single source string. Unknown maps to `unknown_source`."""
+ s = (source or "").lower().strip()
+ if s not in ALL_SOURCES:
+ s = "unknown_source"
+ return {"source": s, "risk_score": _SOURCE_RISK[s]}
+
+
+def allowed_channels_for_source(
+ source: str, *, opt_in_status: str = "unknown",
+) -> dict[str, object]:
+ """
+ Return which channels Dealix may attempt for this source/opt-in combo.
+
+ Each channel is "safe" / "needs_review" / "blocked".
+ """
+ s = classify_source(source)["source"]
+ opt = (opt_in_status or "unknown").lower()
+
+ if s == "opt_out":
+ return {
+ "source": s,
+ "channels": {ch: "blocked" for ch in
+ ("whatsapp", "email", "linkedin", "phone", "social_dm")},
+ "notes_ar": "العميل سحب موافقته — كل القنوات محظورة.",
+ }
+
+ safe_inbound = s in ("crm_customer", "inbound_lead", "website_form",
+ "linkedin_lead_form", "referral", "partner_intro")
+ is_unknown = s in ("unknown_source", "manual_research", "uploaded_list",
+ "cold_list")
+
+ out: dict[str, str] = {}
+ # WhatsApp — strict
+ if opt == "yes" and not s == "cold_list":
+ out["whatsapp"] = "safe"
+ elif s == "inbound_lead" or s == "crm_customer":
+ out["whatsapp"] = "needs_review"
+ else:
+ out["whatsapp"] = "blocked"
+
+ # Email — looser when business context exists
+ if safe_inbound:
+ out["email"] = "safe"
+ elif is_unknown:
+ out["email"] = "needs_review"
+ else:
+ out["email"] = "needs_review"
+
+ # LinkedIn — only via lead forms / manual approved
+ if s == "linkedin_lead_form":
+ out["linkedin"] = "safe"
+ else:
+ out["linkedin"] = "needs_review"
+
+ # Phone — heavy review
+ out["phone"] = "blocked" if s in ("cold_list", "unknown_source") else "needs_review"
+
+ # Social DM — only with explicit context
+ out["social_dm"] = "blocked" if s in ("cold_list", "unknown_source") else "needs_review"
+
+ return {
+ "source": s,
+ "opt_in_status": opt,
+ "channels": out,
+ "notes_ar": (
+ "البريد افضل قناة في الغالب لمصادر العمل المعروفة. "
+ "واتساب يحتاج opt-in واضح. لينكدإن عبر Lead Forms فقط."
+ ),
+ }
+
+
+def required_review_level(source: str) -> str:
+ """Returns: 'auto_safe' | 'human_review' | 'block'."""
+ s = classify_source(source)["source"]
+ if s == "opt_out":
+ return "block"
+ if s in ("crm_customer", "inbound_lead", "website_form",
+ "linkedin_lead_form"):
+ return "auto_safe"
+ if s in ("event_lead", "referral", "partner_intro"):
+ return "human_review"
+ return "human_review"
+
+
+def retention_recommendation(source: str) -> dict[str, object]:
+ """Return PDPL-shaped retention guidance per source."""
+ s = classify_source(source)["source"]
+ if s == "crm_customer":
+ days = 365 * 3 # 3 years
+ elif s in ("inbound_lead", "website_form", "linkedin_lead_form",
+ "event_lead", "referral", "partner_intro"):
+ days = 365 * 2
+ else:
+ days = 180
+ return {
+ "source": s,
+ "retention_days": days,
+ "lawful_basis_ar": (
+ "علاقة قائمة" if s == "crm_customer"
+ else "موافقة" if s in ("website_form", "linkedin_lead_form",
+ "inbound_lead", "event_lead")
+ else "مصلحة مشروعة محدودة"
+ ),
+ "notes_ar": "حذف تلقائي عند تجاوز المدة أو طلب opt-out.",
+ }
+
+
+def source_risk_score(source: str) -> int:
+ """Return the integer risk score for the source."""
+ return int(classify_source(source)["risk_score"])
diff --git a/dealix/auto_client_acquisition/targeting_os/contactability_matrix.py b/dealix/auto_client_acquisition/targeting_os/contactability_matrix.py
new file mode 100644
index 00000000..1560dc2c
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/contactability_matrix.py
@@ -0,0 +1,134 @@
+"""Contactability matrix — هل التواصل مع هذا الـcontact مسموح؟"""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .contact_source_policy import (
+ allowed_channels_for_source,
+ classify_source,
+ source_risk_score,
+)
+
+ACTION_MODES: tuple[str, ...] = (
+ "suggest_only",
+ "draft_only",
+ "approval_required",
+ "approved_execute",
+ "blocked",
+)
+
+BLOCK_REASONS: dict[str, str] = {
+ "opt_out": "العميل سحب موافقته.",
+ "cold_whatsapp": "واتساب بارد محظور (PDPL).",
+ "no_lawful_basis": "لا يوجد أساس نظامي للتواصل.",
+ "missing_consent": "موافقة opt-in مفقودة.",
+ "secret_in_payload": "الـ payload يحوي قيمة حساسة.",
+ "high_value_no_approval": "صفقة عالية القيمة بدون اعتماد.",
+ "channel_paused": "القناة موقوفة لتدهور السمعة.",
+ "frequency_cap_hit": "تجاوز سقف التواصل الأسبوعي.",
+ "unknown_source": "مصدر الـ contact غير معروف — تحتاج مراجعة.",
+}
+
+
+def block_reason_codes() -> dict[str, str]:
+ """Expose all block reason codes (Arabic)."""
+ return dict(BLOCK_REASONS)
+
+
+def evaluate_contactability(
+ contact: dict[str, Any],
+ *,
+ desired_channel: str | None = None,
+) -> dict[str, Any]:
+ """
+ Evaluate whether contacting `contact` via `desired_channel` is permitted.
+
+ Returns a structured verdict with status and Arabic reasons.
+ """
+ source = contact.get("source", "unknown_source")
+ opt_in = contact.get("opt_in_status", "unknown")
+ opt_out = bool(contact.get("opt_out", False))
+ has_relationship = bool(contact.get("has_relationship", False))
+
+ risk = source_risk_score(source)
+ classified = classify_source(source)["source"]
+
+ if opt_out or classified == "opt_out":
+ return {
+ "status": "blocked",
+ "reason_codes": ["opt_out"],
+ "reasons_ar": [BLOCK_REASONS["opt_out"]],
+ "allowed_action_mode": "blocked",
+ "allowed_channels": [],
+ }
+
+ channel_map = allowed_channels_for_source(source, opt_in_status=str(opt_in))["channels"]
+
+ if desired_channel:
+ ch = desired_channel.lower()
+ ch_status = channel_map.get(ch, "blocked")
+ if ch_status == "blocked":
+ reason = "cold_whatsapp" if ch == "whatsapp" else "no_lawful_basis"
+ return {
+ "status": "blocked",
+ "reason_codes": [reason],
+ "reasons_ar": [BLOCK_REASONS[reason]],
+ "allowed_action_mode": "blocked",
+ "allowed_channels": [k for k, v in channel_map.items() if v != "blocked"],
+ }
+ if ch_status == "needs_review":
+ return {
+ "status": "needs_review",
+ "reason_codes": ["unknown_source"] if classified == "unknown_source" else [],
+ "reasons_ar": (
+ [BLOCK_REASONS["unknown_source"]] if classified == "unknown_source"
+ else ["تحتاج مراجعة بشرية قبل الإرسال."]
+ ),
+ "allowed_action_mode": "approval_required",
+ "allowed_channels": [k for k, v in channel_map.items() if v != "blocked"],
+ }
+ # safe
+ return {
+ "status": "safe",
+ "reason_codes": [],
+ "reasons_ar": [],
+ "allowed_action_mode": "draft_only" if not has_relationship else "approval_required",
+ "allowed_channels": [k for k, v in channel_map.items() if v != "blocked"],
+ }
+
+ # No desired_channel → return per-channel verdict
+ return {
+ "status": "safe" if any(v == "safe" for v in channel_map.values()) else "needs_review",
+ "reason_codes": [],
+ "reasons_ar": [],
+ "allowed_action_mode": "draft_only",
+ "allowed_channels": [k for k, v in channel_map.items() if v != "blocked"],
+ "channel_status": channel_map,
+ "risk_score": risk,
+ }
+
+
+def explain_contactability_ar(result: dict[str, Any]) -> str:
+ """Build a human Arabic explanation from a contactability result."""
+ status = result.get("status", "unknown")
+ reasons = result.get("reasons_ar", [])
+ channels = result.get("allowed_channels", [])
+ if status == "blocked":
+ return f"محظور: {' / '.join(reasons) or 'سياسة عامة'}."
+ if status == "needs_review":
+ return (
+ f"يحتاج مراجعة: {' / '.join(reasons) or 'بدون مصدر واضح'}. "
+ f"القنوات المتاحة بعد المراجعة: {', '.join(channels) or 'لا شيء'}."
+ )
+ return f"آمن. القنوات المسموحة: {', '.join(channels)}."
+
+
+def allowed_action_modes(result: dict[str, Any]) -> list[str]:
+ """Return the action modes available given a contactability verdict."""
+ status = result.get("status", "blocked")
+ if status == "blocked":
+ return ["blocked"]
+ if status == "needs_review":
+ return ["suggest_only", "draft_only", "approval_required"]
+ return ["draft_only", "approval_required", "approved_execute"]
diff --git a/dealix/auto_client_acquisition/targeting_os/contract_drafts.py b/dealix/auto_client_acquisition/targeting_os/contract_drafts.py
new file mode 100644
index 00000000..2666de0f
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/contract_drafts.py
@@ -0,0 +1,121 @@
+"""Contract draft outlines — Arabic skeletons; legal review required."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+_DISCLAIMER_AR = (
+ "هذه مسودة هيكلية فقط، ليست استشارة قانونية. "
+ "لا تُوقَّع قبل مراجعة محامٍ مرخّص في المملكة العربية السعودية."
+)
+
+
+def draft_pilot_agreement_outline() -> dict[str, Any]:
+ """Pilot Agreement outline (Arabic skeleton)."""
+ return {
+ "title_ar": "اتفاقية تجربة Pilot لخدمة Dealix",
+ "sections_ar": [
+ "الأطراف والتعريفات.",
+ "نطاق الـ Pilot ومدته (7 أيام).",
+ "المدخلات المطلوبة من العميل.",
+ "المخرجات المُتفق عليها (10 فرص + رسائل + Proof Pack).",
+ "السرية وعدم استخدام بيانات العميل لأغراض أخرى.",
+ "PDPL وحقوق الموضوعات (الأشخاص).",
+ "السعر وطريقة الدفع (Pilot أو case study).",
+ "إنهاء الاتفاقية والاستمرارية.",
+ "حدود المسؤولية.",
+ "القانون الواجب التطبيق والاختصاص.",
+ ],
+ "approval_required": True,
+ "legal_review_required": True,
+ "not_legal_advice": True,
+ "disclaimer_ar": _DISCLAIMER_AR,
+ }
+
+
+def draft_dpa_outline() -> dict[str, Any]:
+ """Data Processing Addendum outline (Arabic skeleton, PDPL-aware)."""
+ return {
+ "title_ar": "ملحق معالجة البيانات (DPA)",
+ "sections_ar": [
+ "التعريفات حسب نظام حماية البيانات الشخصية السعودي (PDPL).",
+ "أدوار الأطراف (Controller / Processor).",
+ "أنواع البيانات والـ subjects.",
+ "أغراض المعالجة.",
+ "الإجراءات الأمنية المطبّقة.",
+ "نقل البيانات خارج المملكة (إن وُجد).",
+ "الاحتفاظ والإتلاف.",
+ "حقوق الموضوعات (طلبات الوصول/التصحيح/الحذف).",
+ "خرق البيانات والإبلاغ.",
+ "الـ subprocessors المعتمدون.",
+ "التدقيق والامتثال.",
+ ],
+ "approval_required": True,
+ "legal_review_required": True,
+ "not_legal_advice": True,
+ "disclaimer_ar": _DISCLAIMER_AR,
+ }
+
+
+def draft_referral_agreement_outline() -> dict[str, Any]:
+ """Referral Agreement outline."""
+ return {
+ "title_ar": "اتفاقية إحالة (Referral)",
+ "sections_ar": [
+ "تعريف الـ Referrer والإحالة المؤهلة.",
+ "نموذج الـ revenue share (نسبة + مدة).",
+ "شروط الدفع وتاريخ الاستحقاق.",
+ "السرية.",
+ "عدم الإغراء (no-poach اختيارية).",
+ "سياسات PDPL لمشاركة بيانات الـ leads.",
+ "إنهاء الاتفاقية.",
+ ],
+ "approval_required": True,
+ "legal_review_required": True,
+ "not_legal_advice": True,
+ "disclaimer_ar": _DISCLAIMER_AR,
+ }
+
+
+def draft_agency_partner_outline() -> dict[str, Any]:
+ """Agency Partner Agreement outline (white-label/co-branded)."""
+ return {
+ "title_ar": "اتفاقية شريك وكالة لـ Dealix",
+ "sections_ar": [
+ "هيكل الشراكة (revenue share / setup fee / co-branding).",
+ "نطاق الخدمات المقدّمة من الوكالة لعملائها.",
+ "Proof Packs مشتركة العلامة.",
+ "حقوق الملكية الفكرية.",
+ "السرية والـ NDAs.",
+ "PDPL ونقل البيانات بين Dealix والوكالة.",
+ "حدود المسؤولية والـ SLA.",
+ "إنهاء الاتفاقية وتسليم العملاء.",
+ ],
+ "approval_required": True,
+ "legal_review_required": True,
+ "not_legal_advice": True,
+ "disclaimer_ar": _DISCLAIMER_AR,
+ }
+
+
+def draft_scope_of_work() -> dict[str, Any]:
+ """Generic Scope-of-Work outline."""
+ return {
+ "title_ar": "نطاق العمل (SOW)",
+ "sections_ar": [
+ "ملخص الخدمة.",
+ "المدخلات المطلوبة من العميل.",
+ "المخرجات والـ deliverables.",
+ "الجدول الزمني والـ milestones.",
+ "المسؤوليات والـ approvals.",
+ "السعر وطريقة الدفع.",
+ "حدود نطاق العمل وما خارجه.",
+ "تغييرات النطاق (Change Requests).",
+ "معايير القبول (Acceptance Criteria).",
+ ],
+ "approval_required": True,
+ "legal_review_required": True,
+ "not_legal_advice": True,
+ "disclaimer_ar": _DISCLAIMER_AR,
+ }
diff --git a/dealix/auto_client_acquisition/targeting_os/daily_autopilot.py b/dealix/auto_client_acquisition/targeting_os/daily_autopilot.py
new file mode 100644
index 00000000..db9fc7b6
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/daily_autopilot.py
@@ -0,0 +1,106 @@
+"""Daily autopilot — يومياً يبني brief + يقترح أفعال + ينظمها بالأولوية."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def build_daily_targeting_brief(
+ company_profile: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+ """Build today's Arabic targeting brief for the founder/growth manager."""
+ company_profile = company_profile or {}
+ sector = company_profile.get("sector", "saas")
+ city = company_profile.get("city", "Riyadh")
+
+ return {
+ "greeting_ar": "صباح الخير 👋",
+ "summary_ar": [
+ f"عندك اليوم: 10 شركات جديدة مناسبة في قطاع {sector} ({city}).",
+ "5 رسائل drafts تنتظر اعتمادك.",
+ "3 leads متأخرة في المتابعة (>72 ساعة).",
+ "1 فرصة شريك في جدة جاهزة للتواصل.",
+ "1 قناة (واتساب) تحتاج مراجعة سمعة.",
+ ],
+ "priority_decisions_ar": [
+ "اعتمد 5 رسائل إيميل (10 دقائق).",
+ "راجع 12 رقم بدون مصدر واضح قبل أي واتساب.",
+ "احجز ديمو مع شريك الوكالة هذا الأسبوع.",
+ ],
+ "do_not_do_today_ar": [
+ "لا تفعّل live WhatsApp send.",
+ "لا ترفع قائمة باردة بدون تصنيف مصدر.",
+ "لا تعد بنتائج مضمونة في الرسائل.",
+ ],
+ }
+
+
+def recommend_today_actions(
+ company_profile: dict[str, Any] | None = None,
+) -> list[dict[str, Any]]:
+ """Return ordered actions for today (deterministic 7-action set)."""
+ company_profile = company_profile or {}
+ return [
+ {"id": "approve_5_email_drafts", "label_ar": "اعتمد 5 مسودات إيميل",
+ "minutes": 10, "approval_required": True, "priority": 1},
+ {"id": "review_unknown_source_contacts", "label_ar": "راجع 12 رقم بدون مصدر",
+ "minutes": 8, "approval_required": True, "priority": 2},
+ {"id": "schedule_partner_demo", "label_ar": "احجز ديمو شريك",
+ "minutes": 5, "approval_required": True, "priority": 3},
+ {"id": "respond_to_overdue_leads", "label_ar": "رد على 3 leads متأخرة",
+ "minutes": 12, "approval_required": True, "priority": 4},
+ {"id": "review_whatsapp_quality", "label_ar": "راجع مؤشرات سمعة واتساب",
+ "minutes": 5, "approval_required": False, "priority": 5},
+ {"id": "draft_one_partner_message", "label_ar": "اكتب رسالة شريك وكالة",
+ "minutes": 8, "approval_required": True, "priority": 6},
+ {"id": "log_proof_events", "label_ar": "حدّث Proof Ledger",
+ "minutes": 3, "approval_required": False, "priority": 7},
+ ]
+
+
+def prioritize_cards(cards: list[dict[str, Any]]) -> list[dict[str, Any]]:
+ """Sort cards by `priority` (asc), then by `risk_level` (high first)."""
+ risk_rank = {"high": 0, "medium": 1, "low": 2, None: 3}
+ return sorted(
+ cards,
+ key=lambda c: (
+ int(c.get("priority", 99)),
+ risk_rank.get(c.get("risk_level"), 9),
+ ),
+ )
+
+
+def build_end_of_day_report(
+ day_metrics: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+ """Build today's Arabic end-of-day report from metrics."""
+ m = day_metrics or {}
+ accounts = int(m.get("accounts_analyzed", 32))
+ opps = int(m.get("opportunities_generated", 10))
+ drafts = int(m.get("drafts_approved", 6))
+ replies = int(m.get("positive_replies", 2))
+ meetings = int(m.get("meetings_drafted", 1))
+ risks = int(m.get("risks_blocked", 8))
+
+ return {
+ "today_metrics": {
+ "accounts_analyzed": accounts,
+ "opportunities_generated": opps,
+ "drafts_approved": drafts,
+ "positive_replies": replies,
+ "meetings_drafted": meetings,
+ "risks_blocked": risks,
+ },
+ "summary_ar": [
+ f"تم تحليل {accounts} حساب اليوم.",
+ f"تم توليد {opps} فرصة جديدة.",
+ f"تم اعتماد {drafts} مسودة.",
+ f"تم تسجيل {replies} رد إيجابي.",
+ f"تم تجهيز {meetings} اجتماع.",
+ f"تم منع {risks} مخاطر تلقائياً.",
+ ],
+ "tomorrow_recommendation_ar": (
+ "غداً: ركّز على متابعة الردود الإيجابية أولاً، ثم اعتماد رسائل جديدة، "
+ "ثم جدولة 1-2 ديمو إن أمكن."
+ ),
+ }
diff --git a/dealix/auto_client_acquisition/targeting_os/email_strategy.py b/dealix/auto_client_acquisition/targeting_os/email_strategy.py
new file mode 100644
index 00000000..37f4f42d
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/email_strategy.py
@@ -0,0 +1,160 @@
+"""Email strategy — drafts only, unsubscribe always, pacing-aware."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def draft_b2b_email(
+ contact: dict[str, Any],
+ *,
+ offer: str = "",
+ why_now: str = "",
+ tone: str = "professional_saudi",
+) -> dict[str, Any]:
+ """Build a B2B email draft (Arabic). Never sends."""
+ name = contact.get("name", "")
+ company = contact.get("company", "")
+ role = contact.get("role", "")
+
+ salutation = f"هلا {name}" if name else "هلا"
+ company_part = f" من {company}" if company else ""
+ why_now_part = f"\n{why_now}\n" if why_now else "\n"
+
+ body_ar = (
+ f"{salutation}،\n\n"
+ f"أكتب لك{company_part} باختصار. "
+ f"نشتغل على Dealix كمدير نمو عربي للشركات السعودية:"
+ f"{why_now_part}"
+ "خلال 7 أيام، نطلع لك:\n"
+ "• 10 فرص B2B مناسبة لقطاعكم\n"
+ "• رسائل عربية جاهزة بنبرتنا\n"
+ "• خطة متابعة قابلة للتنفيذ\n"
+ "• Proof Pack بعد الأسبوع\n\n"
+ f"{offer or 'Pilot بـ 499 ريال أو مجاني مقابل case study.'}\n\n"
+ "إذا الفكرة تناسبك، نحدد مكالمة 15 دقيقة هذا الأسبوع.\n"
+ "وإن ما كانت الأولوية الآن خبرني وأرتاح.\n\nشاكر لك."
+ )
+
+ return {
+ "subject_ar": (
+ f"فرصة نمو لـ{company}" if company else "فرصة نمو B2B خلال 7 أيام"
+ ),
+ "body_ar": include_unsubscribe_footer(body_ar),
+ "tone": tone,
+ "target_role": role,
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def include_unsubscribe_footer(body: str) -> str:
+ """Append a one-line unsubscribe footer (Arabic + English)."""
+ if not body:
+ return body
+ footer = (
+ "\n\n———\n"
+ "لإيقاف هذه الرسائل، رد بكلمة \"إلغاء\" / Reply STOP to unsubscribe."
+ )
+ return body + footer
+
+
+def recommend_pacing(domain_reputation: str = "fresh") -> dict[str, Any]:
+ """Recommend a daily send pacing based on domain reputation."""
+ rep = (domain_reputation or "fresh").lower()
+ table = {
+ "fresh": {"max_daily": 20, "warmup_days": 21, "ramp_step": 5},
+ "warmed": {"max_daily": 60, "warmup_days": 0, "ramp_step": 10},
+ "trusted": {"max_daily": 200, "warmup_days": 0, "ramp_step": 25},
+ "damaged": {"max_daily": 5, "warmup_days": 30, "ramp_step": 1},
+ }
+ plan = table.get(rep, table["fresh"])
+ return {
+ "domain_reputation": rep,
+ **plan,
+ "notes_ar": (
+ "ابدأ بحدود يومية صغيرة على domain جديد، وارتفع تدريجياً. "
+ "domain متضرر يحتاج فترة تبريد + warmup قبل العودة."
+ ),
+ }
+
+
+def score_email_risk(
+ contact: dict[str, Any], message: str = "",
+) -> dict[str, Any]:
+ """
+ Score an outbound email's risk 0..100 (higher = riskier).
+
+ Looks at source, opt_in, message content for spam triggers.
+ """
+ source = contact.get("source", "unknown_source")
+ opt_in = (contact.get("opt_in_status") or "unknown").lower()
+
+ risk = 0
+ reasons: list[str] = []
+
+ if source == "cold_list":
+ risk += 50; reasons.append("قائمة باردة — مخاطرة spam مرتفعة.")
+ elif source == "unknown_source":
+ risk += 30; reasons.append("مصدر غير معروف — يحتاج مراجعة.")
+ elif source in ("inbound_lead", "crm_customer", "website_form"):
+ risk -= 10 # safer
+
+ if opt_in not in ("yes", "double"):
+ risk += 10
+
+ msg = (message or "").lower()
+ spam_triggers = ["ضمان 100%", "ضمان مضمون", "act now", "urgent",
+ "free money", "click here now", "limited offer"]
+ for t in spam_triggers:
+ if t in msg.lower() or t in (message or ""):
+ risk += 15
+ reasons.append(f"عبارة spam: {t}")
+
+ risk = max(0, min(100, risk))
+ if risk >= 60:
+ verdict = "blocked"
+ elif risk >= 30:
+ verdict = "needs_review"
+ else:
+ verdict = "safe"
+
+ return {"risk": risk, "verdict": verdict, "reasons_ar": reasons}
+
+
+def build_followup_sequence(
+ contact: dict[str, Any], *, offer: str = "",
+) -> dict[str, Any]:
+ """Build a 3-step Arabic email follow-up sequence."""
+ name = contact.get("name", "")
+ sal = f"هلا {name}" if name else "هلا"
+ return {
+ "approval_required": True,
+ "live_send_allowed": False,
+ "steps": [
+ {
+ "day": 0,
+ "subject_ar": "فرصة نمو B2B خلال 7 أيام",
+ "body_ar": include_unsubscribe_footer(
+ f"{sal}، (الرسالة الأولى مع العرض الكامل)"
+ ),
+ },
+ {
+ "day": 3,
+ "subject_ar": "متابعة سريعة",
+ "body_ar": include_unsubscribe_footer(
+ f"{sal}، أتابع رسالتي السابقة. "
+ "هل أرتب لك ديمو 12 دقيقة هذا الأسبوع؟"
+ ),
+ },
+ {
+ "day": 7,
+ "subject_ar": "آخر متابعة",
+ "body_ar": include_unsubscribe_footer(
+ f"{sal}، آخر متابعة من جهتي. "
+ "إذا ما كانت الأولوية الآن أرتاح وأرشّفها. "
+ "وإن أردت ديمو لاحقاً، أنا موجود."
+ ),
+ },
+ ],
+ }
diff --git a/dealix/auto_client_acquisition/targeting_os/free_diagnostic.py b/dealix/auto_client_acquisition/targeting_os/free_diagnostic.py
new file mode 100644
index 00000000..86338cb5
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/free_diagnostic.py
@@ -0,0 +1,147 @@
+"""Free Growth Diagnostic — العرض المجاني الذي يجلب pilots."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .account_finder import recommend_accounts
+from .contact_source_policy import classify_source
+from .contactability_matrix import evaluate_contactability
+
+
+def build_free_growth_diagnostic(
+ company_profile: dict[str, Any],
+) -> dict[str, Any]:
+ """
+ Build a free 5-section Arabic growth diagnostic for a prospect.
+
+ Inputs: company_profile = {sector, city, offer, goal, has_list?, channels?}
+ Outputs: 3 opportunities + 1 message + 1 risk + 1 mini proof plan + paid pilot offer.
+ """
+ sector = company_profile.get("sector", "saas")
+ city = company_profile.get("city", "Riyadh")
+ offer = company_profile.get("offer", "")
+
+ accounts = recommend_accounts(
+ sector=sector, city=city, offer=offer, goal="diagnostic", limit=3,
+ )["accounts"]
+
+ sample_message = (
+ f"هلا، لاحظت توسعكم في قطاع {sector}. "
+ "نشتغل على Dealix كمدير نمو عربي للشركات السعودية. "
+ "خلال 7 أيام نطلع لكم 10 فرص B2B + رسائل + خطة متابعة. "
+ "يناسبكم ديمو 12 دقيقة هذا الأسبوع؟"
+ )
+
+ risk_summary = {
+ "label_ar": "احتمال إضرار سمعة الـdomain أو رقم واتساب",
+ "why_ar": (
+ "لو أرسلت لقائمة بدون opt-in، تتجاوز PDPL ويمكن أن تُحظر القناة. "
+ "الحل: ابدأ بمصادر آمنة فقط."
+ ),
+ "mitigation_ar": [
+ "صنّف كل contact حسب المصدر.",
+ "أوقف أي رقم بدون opt-in.",
+ "ابدأ بـ Free Diagnostic ثم Pilot.",
+ ],
+ }
+
+ mini_proof = build_mini_proof_plan()
+
+ return {
+ "company_profile": {"sector": sector, "city": city, "offer": offer},
+ "delivered_at": "draft",
+ "approval_required": True,
+ "sections": {
+ "opportunities_ar": accounts,
+ "sample_message_ar": sample_message,
+ "risk_summary_ar": risk_summary,
+ "mini_proof_plan_ar": mini_proof,
+ "paid_pilot_offer": recommend_paid_pilot_offer({"sector": sector}),
+ },
+ "next_step_ar": (
+ "إذا أعجبتك العينة، نكمل Pilot 7 أيام بـ499 ريال "
+ "أو مجاناً مقابل case study بعد انتهاء الـPilot."
+ ),
+ }
+
+
+def analyze_uploaded_list_preview(
+ contacts: list[dict[str, Any]],
+) -> dict[str, Any]:
+ """
+ Preview-only analysis of a customer-uploaded list.
+
+ Classifies sources + contactability without storing. Returns aggregate.
+ """
+ if not contacts:
+ return {"total": 0, "by_status": {}, "preview": []}
+
+ by_status: dict[str, int] = {"safe": 0, "needs_review": 0, "blocked": 0}
+ preview: list[dict[str, Any]] = []
+
+ for i, c in enumerate(contacts[:20]): # only first 20 for preview
+ verdict = evaluate_contactability(c)
+ status = verdict["status"]
+ by_status[status] = by_status.get(status, 0) + 1
+ preview.append({
+ "index": i,
+ "source": classify_source(c.get("source", "unknown_source"))["source"],
+ "contactability": status,
+ "allowed_channels": verdict.get("allowed_channels", []),
+ })
+
+ # Aggregate over the FULL list
+ full_by_status = dict(by_status)
+ if len(contacts) > 20:
+ # Project remaining proportionally — deterministic.
+ scale = len(contacts) / 20
+ full_by_status = {k: int(v * scale) for k, v in by_status.items()}
+
+ return {
+ "total": len(contacts),
+ "by_status": full_by_status,
+ "preview": preview,
+ "recommendations_ar": [
+ "ابدأ بالـsafe contacts فقط في الأسبوع الأول.",
+ "راجع الـneeds_review يدوياً قبل أي إرسال.",
+ "تخطّ الـblocked تماماً (opt-out).",
+ ],
+ }
+
+
+def recommend_paid_pilot_offer(diagnostic: dict[str, Any]) -> dict[str, Any]:
+ """Recommend a paid Pilot offer based on diagnostic context."""
+ return {
+ "offer_id": "first_10_opportunities_pilot_7d",
+ "name_ar": "Pilot 7 أيام: 10 فرص + رسائل + متابعة + Proof Pack",
+ "price_sar_min": 499,
+ "price_sar_max": 1500,
+ "free_alternative_ar": "مجاني مقابل case study بعد انتهاء الـPilot.",
+ "deliverables_ar": [
+ "10 فرص B2B مع why-now.",
+ "10 رسائل عربية جاهزة.",
+ "خطة متابعة 7 أيام.",
+ "Proof Pack تفصيلي.",
+ ],
+ "approval_required": True,
+ }
+
+
+def build_mini_proof_plan() -> dict[str, Any]:
+ """A small Proof Pack template anyone can run in their head."""
+ return {
+ "metrics_to_track": [
+ "leads_created",
+ "drafts_approved",
+ "positive_replies",
+ "meetings_drafted",
+ "pipeline_influenced_sar",
+ "risks_blocked",
+ ],
+ "how_to_count_ar": (
+ "كل metric يُحسب يومياً عبر Proof Ledger. "
+ "في نهاية الأسبوع، نولّد PDF/JSON ونشاركه مع الإدارة."
+ ),
+ "review_frequency": "weekly",
+ }
diff --git a/dealix/auto_client_acquisition/targeting_os/linkedin_strategy.py b/dealix/auto_client_acquisition/targeting_os/linkedin_strategy.py
new file mode 100644
index 00000000..74900021
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/linkedin_strategy.py
@@ -0,0 +1,124 @@
+"""LinkedIn strategy — Lead Forms + manual research + Ads, NO scraping/auto-DM."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def linkedin_do_not_do() -> list[str]:
+ """The hard 'NEVER' list for LinkedIn — encoded explicitly so tests can lock it."""
+ return [
+ "scrape_profiles",
+ "auto_connect",
+ "auto_dm",
+ "browser_automation",
+ "fake_engagement",
+ "download_contacts_from_linkedin",
+ "buy_scraped_leads",
+ "use_unauthorized_extensions",
+ ]
+
+
+def recommend_linkedin_strategy(
+ segment: str, *, goal: str = "fill_pipeline",
+) -> dict[str, Any]:
+ """
+ Recommend a compliant LinkedIn strategy for a segment.
+
+ Always picks Lead Gen Forms / manual / Ads — never scraping/auto-DM.
+ """
+ return {
+ "segment": segment,
+ "goal": goal,
+ "primary": "lead_gen_forms",
+ "secondary": ["linkedin_ads", "manual_account_research", "content_engagement"],
+ "do_not_do": linkedin_do_not_do(),
+ "rationale_ar": (
+ "لينكدإن يحظر crawlers/bots/extensions التي تسحب البيانات أو ترسل/توجّه "
+ "رسائل أو تصنع تفاعلاً غير أصيل؛ لذلك نعتمد فقط على Lead Gen Forms، "
+ "الإعلانات، والبحث اليدوي المعتمد."
+ ),
+ }
+
+
+def build_lead_gen_form_plan(
+ segment: str, offer: str, *, campaign_name: str = "",
+) -> dict[str, Any]:
+ """Build a structured Lead Gen Form campaign plan."""
+ name = campaign_name or f"{segment} — {offer or 'Pilot'}"
+ return {
+ "campaign_name": name,
+ "audience_ar": (
+ f"المستهدفون: {segment} — أصحاب القرار في القطاع المحدد، "
+ "السعودية والخليج، حجم 11-200 موظف."
+ ),
+ "offer_ar": offer or "Pilot 7 أيام لاستخراج 10 فرص B2B + رسائل عربية + Proof Pack.",
+ "lead_magnet_ar": (
+ "Free Growth Diagnostic — تقرير من 5 صفحات: 3 فرص + رسالة عربية + خطة 7 أيام."
+ ),
+ "form_fields_required": ["full_name", "company_name", "work_email", "role"],
+ "hidden_fields": [
+ {"name": "campaign_name", "value": name},
+ {"name": "sector", "value": segment},
+ {"name": "sales_owner", "value": "{{owner}}"},
+ {"name": "ad_set", "value": "{{ad_set_id}}"},
+ ],
+ "approval_required": True,
+ "notes_ar": (
+ "الـ hidden fields ضرورية لمعرفة مصدر كل lead و ربطه بالـCRM. "
+ "كل lead من Lead Form يدخل Dealix كـ source=linkedin_lead_form (آمن)."
+ ),
+ }
+
+
+def build_manual_research_task(
+ account: dict[str, Any], *, role: str = "head_of_sales",
+) -> dict[str, Any]:
+ """Build a manual LinkedIn research task — for a human, not automation."""
+ company = account.get("name", "?")
+ return {
+ "task_type": "manual_linkedin_research",
+ "company": company,
+ "target_role": role,
+ "instructions_ar": [
+ f"افتح صفحة شركة {company} على LinkedIn يدوياً.",
+ f"حدد الشخص الذي يحمل دور {role}.",
+ "لا تستخدم أي extension أو bot لاستخراج البيانات.",
+ "سجّل اسم الشخص + مسماه فقط — لا تنسخ أي معلومات إضافية.",
+ "أضف الاسم في Dealix كـ source=manual_research → سيدخل needs_review.",
+ ],
+ "approval_required": True,
+ "completion_minutes": 5,
+ }
+
+
+def build_safe_connection_message(
+ role: str, company: str, *, offer: str = "",
+) -> dict[str, Any]:
+ """
+ Build a safe connection-request message for LinkedIn (manual send by user).
+
+ Never auto-sends. Always returns draft with approval_required=True.
+ """
+ role_ar = role
+ body_ar = (
+ f"هلا، تابعت أعمال {company} مؤخراً وعجبني التوسع. "
+ f"أعمل على Dealix كمدير نمو عربي للشركات السعودية. "
+ f"يناسبك نتعارف هنا؟"
+ )
+ if offer:
+ body_ar += f" وفي حال فيه فرصة لـ{offer}، أكون سعيد أشاركك أمثلة."
+
+ return {
+ "channel": "linkedin_connection_request",
+ "target_role": role_ar,
+ "target_company": company,
+ "body_ar": body_ar[:280], # LinkedIn note limit
+ "approval_required": True,
+ "live_send_allowed": False,
+ "send_method": "manual_only",
+ "notes_ar": (
+ "هذه مسودة. أرسلها يدوياً من حسابك على LinkedIn. "
+ "Dealix لا يرسل تلقائياً ولا يستخدم أي extension أو bot."
+ ),
+ }
diff --git a/dealix/auto_client_acquisition/targeting_os/outreach_scheduler.py b/dealix/auto_client_acquisition/targeting_os/outreach_scheduler.py
new file mode 100644
index 00000000..9e528a31
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/outreach_scheduler.py
@@ -0,0 +1,133 @@
+"""Outreach scheduler — pace, follow-up, opt-out enforcement."""
+
+from __future__ import annotations
+
+from typing import Any
+
+DEFAULT_LIMITS: dict[str, int] = {
+ "max_daily_email_drafts": 30,
+ "max_daily_whatsapp_approved_sends": 10,
+ "max_followups": 3,
+ "cooldown_days": 7,
+ "max_same_domain_contacts": 5,
+}
+
+
+def build_outreach_plan(
+ targets: list[dict[str, Any]],
+ *,
+ channels: list[str] | None = None,
+ goal: str = "fill_pipeline",
+) -> dict[str, Any]:
+ """
+ Build a per-target outreach plan across channels.
+
+ Each target gets day-by-day actions; never schedules a live send.
+ """
+ channels = channels or ["email", "linkedin_lead_form"]
+ plan: list[dict[str, Any]] = []
+
+ for t in targets:
+ steps: list[dict[str, Any]] = [
+ {"day": 0, "channel": channels[0],
+ "action": "draft_first_message",
+ "approval_required": True,
+ "live_send_allowed": False},
+ {"day": 3, "channel": channels[0],
+ "action": "draft_followup_1",
+ "approval_required": True,
+ "live_send_allowed": False},
+ ]
+ if "linkedin_lead_form" in channels or "linkedin" in channels:
+ steps.append({
+ "day": 5, "channel": "linkedin_manual",
+ "action": "manual_research_task",
+ "approval_required": True,
+ "live_send_allowed": False,
+ })
+ steps.append({
+ "day": 7, "channel": channels[0],
+ "action": "draft_final_followup_or_archive",
+ "approval_required": True,
+ "live_send_allowed": False,
+ })
+ plan.append({
+ "target_company": t.get("name", "?"),
+ "target_role": t.get("role", "?"),
+ "channels": channels,
+ "steps": steps,
+ })
+
+ return {
+ "goal": goal,
+ "channels": channels,
+ "total_targets": len(targets),
+ "plan": plan,
+ "limits": DEFAULT_LIMITS,
+ "notes_ar": (
+ "كل خطوة draft تحتاج اعتماد. "
+ "لا إرسال آلي، ولا تجاوز الحدود اليومية."
+ ),
+ }
+
+
+def schedule_followups(plan: dict[str, Any]) -> dict[str, Any]:
+ """Add follow-up timing to each target in a plan."""
+ out = dict(plan)
+ out["scheduled"] = True
+ return out
+
+
+def enforce_daily_limits(
+ plan: dict[str, Any],
+ *,
+ limits: dict[str, int] | None = None,
+) -> dict[str, Any]:
+ """Cap actions in the plan to the configured daily limits."""
+ limits = limits or DEFAULT_LIMITS
+ targets = plan.get("plan", [])
+
+ capped: list[dict[str, Any]] = []
+ daily_email = 0
+ domain_count: dict[str, int] = {}
+
+ for t in targets:
+ company = t.get("target_company", "")
+ # treat company as a proxy for domain in test data
+ if company in domain_count and domain_count[company] >= limits["max_same_domain_contacts"]:
+ continue
+ ok_steps = []
+ for step in t.get("steps", []):
+ if step.get("channel") == "email":
+ if daily_email >= limits["max_daily_email_drafts"]:
+ continue
+ daily_email += 1
+ ok_steps.append(step)
+ if ok_steps:
+ capped.append({**t, "steps": ok_steps})
+ domain_count[company] = domain_count.get(company, 0) + 1
+
+ return {
+ **plan,
+ "plan": capped,
+ "applied_limits": limits,
+ "capped_total_targets": len(capped),
+ }
+
+
+def stop_on_opt_out(plan: dict[str, Any]) -> dict[str, Any]:
+ """Filter out targets where the contact has opted out."""
+ targets = plan.get("plan", [])
+ kept = [t for t in targets if not t.get("opt_out")]
+ return {**plan, "plan": kept, "stopped_due_to_opt_out": len(targets) - len(kept)}
+
+
+def summarize_plan_ar(plan: dict[str, Any]) -> str:
+ """Build an Arabic one-paragraph summary of an outreach plan."""
+ n = plan.get("total_targets") or len(plan.get("plan", []))
+ channels = ", ".join(plan.get("channels", []))
+ return (
+ f"خطة تواصل لـ{n} هدف عبر القنوات: {channels}. "
+ f"كل خطوة draft، تتطلب اعتماد، ولا إرسال آلي. "
+ f"الحدود اليومية مفعّلة. opt-out يوقف فوراً."
+ )
diff --git a/dealix/auto_client_acquisition/targeting_os/reputation_guard.py b/dealix/auto_client_acquisition/targeting_os/reputation_guard.py
new file mode 100644
index 00000000..d8bb68e1
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/reputation_guard.py
@@ -0,0 +1,135 @@
+"""Reputation guard — يحمي القنوات من الحظر."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def risk_thresholds() -> dict[str, dict[str, float]]:
+ """The thresholds where a channel needs throttling/pause."""
+ return {
+ "email": {
+ "bounce_rate_warn": 0.02, "bounce_rate_pause": 0.05,
+ "complaint_rate_warn": 0.001, "complaint_rate_pause": 0.003,
+ "opt_out_rate_warn": 0.05, "opt_out_rate_pause": 0.10,
+ "min_reply_rate": 0.02,
+ },
+ "whatsapp": {
+ "block_rate_warn": 0.01, "block_rate_pause": 0.03,
+ "report_rate_warn": 0.005, "report_rate_pause": 0.02,
+ "opt_out_rate_warn": 0.05, "opt_out_rate_pause": 0.10,
+ "min_reply_rate": 0.10,
+ },
+ "linkedin": {
+ "connection_decline_warn": 0.3, "connection_decline_pause": 0.5,
+ },
+ }
+
+
+def calculate_channel_reputation(
+ metrics: dict[str, float],
+ *,
+ channel: str = "email",
+) -> dict[str, Any]:
+ """Compute a 0..100 reputation score for a channel based on metrics."""
+ th = risk_thresholds().get(channel, {})
+ score = 100
+ reasons_ar: list[str] = []
+
+ if channel == "email":
+ bounce = float(metrics.get("bounce_rate", 0))
+ complaint = float(metrics.get("complaint_rate", 0))
+ opt_out = float(metrics.get("opt_out_rate", 0))
+ reply = float(metrics.get("reply_rate", 0.05))
+
+ if bounce >= th["bounce_rate_pause"]:
+ score -= 40; reasons_ar.append("معدل الـ bounce تجاوز الحد الحرج.")
+ elif bounce >= th["bounce_rate_warn"]:
+ score -= 15; reasons_ar.append("ارتفاع في الـ bounce — راقب.")
+
+ if complaint >= th["complaint_rate_pause"]:
+ score -= 50; reasons_ar.append("شكاوى spam مرتفعة جداً.")
+ elif complaint >= th["complaint_rate_warn"]:
+ score -= 20; reasons_ar.append("بداية شكاوى spam.")
+
+ if opt_out >= th["opt_out_rate_pause"]:
+ score -= 25; reasons_ar.append("نسبة opt-out مرتفعة جداً.")
+
+ if reply < th["min_reply_rate"]:
+ score -= 10; reasons_ar.append("معدل الرد منخفض — راجع الجودة.")
+
+ elif channel == "whatsapp":
+ block = float(metrics.get("block_rate", 0))
+ report = float(metrics.get("report_rate", 0))
+ opt_out = float(metrics.get("opt_out_rate", 0))
+
+ if block >= th["block_rate_pause"]:
+ score -= 60; reasons_ar.append("نسبة الحظر مرتفعة جداً — أوقف.")
+ elif block >= th["block_rate_warn"]:
+ score -= 25; reasons_ar.append("بداية حظر — راجع المحتوى.")
+
+ if report >= th["report_rate_pause"]:
+ score -= 50; reasons_ar.append("بلاغات spam على واتساب.")
+
+ if opt_out >= th["opt_out_rate_pause"]:
+ score -= 30; reasons_ar.append("opt-out واتساب مرتفع.")
+
+ score = max(0, min(100, score))
+ return {
+ "channel": channel,
+ "score": score,
+ "reasons_ar": reasons_ar,
+ "verdict": ("healthy" if score >= 70
+ else "watch" if score >= 40
+ else "pause"),
+ }
+
+
+def should_pause_channel(
+ metrics: dict[str, float], *, channel: str = "email",
+) -> dict[str, Any]:
+ """Boolean wrapper: should we pause this channel right now?"""
+ rep = calculate_channel_reputation(metrics, channel=channel)
+ return {
+ "should_pause": rep["verdict"] == "pause",
+ "reputation_score": rep["score"],
+ "reasons_ar": rep["reasons_ar"],
+ }
+
+
+def recommend_recovery_action(
+ metrics: dict[str, float], *, channel: str = "email",
+) -> dict[str, Any]:
+ """Recommend recovery actions based on reputation problems."""
+ rep = calculate_channel_reputation(metrics, channel=channel)
+ actions: list[str] = []
+ if rep["verdict"] == "pause":
+ actions = [
+ "أوقف إرسال جميع الحملات الجديدة على هذه القناة.",
+ "ابدأ فترة تبريد لمدة 14 يوماً على الأقل.",
+ "افحص قائمة الـ contacts وحدّث opt-in.",
+ "نظّف عناوين الـ bounce وأعد التحقق.",
+ ]
+ elif rep["verdict"] == "watch":
+ actions = [
+ "خفّض الحجم اليومي بنسبة 50%.",
+ "ركّز على المصادر الآمنة فقط (CRM/inbound).",
+ "راجع الرسائل لتقليل العبارات المخاطرة.",
+ ]
+ else:
+ actions = ["استمر — راقب أسبوعياً."]
+ return {
+ "channel": channel,
+ "verdict": rep["verdict"],
+ "actions_ar": actions,
+ "score": rep["score"],
+ }
+
+
+def summarize_reputation_ar(metrics: dict[str, float], *, channel: str = "email") -> str:
+ """One-line Arabic summary of channel health."""
+ rep = calculate_channel_reputation(metrics, channel=channel)
+ return (
+ f"قناة {channel}: score {rep['score']} ({rep['verdict']}). "
+ + (rep["reasons_ar"][0] if rep["reasons_ar"] else "حالة صحية.")
+ )
diff --git a/dealix/auto_client_acquisition/targeting_os/self_growth_mode.py b/dealix/auto_client_acquisition/targeting_os/self_growth_mode.py
new file mode 100644
index 00000000..a6122a14
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/self_growth_mode.py
@@ -0,0 +1,157 @@
+"""Self-Growth Mode — Dealix يستهدف عملاءه ويصنع فرصاً لنفسه."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .account_finder import recommend_accounts
+from .buyer_role_mapper import map_buying_committee
+from .daily_autopilot import (
+ build_daily_targeting_brief,
+ recommend_today_actions,
+)
+
+
+# Dealix's own ICP (deterministic).
+DEALIX_ICP_FOCUSES: tuple[dict[str, str], ...] = (
+ {"sector": "agency", "city": "Riyadh", "label_ar": "وكالات تسويق B2B في الرياض"},
+ {"sector": "training", "city": "Riyadh", "label_ar": "شركات تدريب B2B في الرياض"},
+ {"sector": "consulting", "city": "Riyadh", "label_ar": "شركات استشارات نمو"},
+ {"sector": "saas", "city": "Riyadh", "label_ar": "SaaS سعودية صغيرة-متوسطة"},
+ {"sector": "real_estate", "city": "Jeddah", "label_ar": "وسطاء عقار B2B في جدة"},
+)
+
+
+def recommend_dealix_targets(
+ *,
+ sector_focus: str | None = None,
+ city_focus: str | None = None,
+ limit: int = 10,
+) -> dict[str, Any]:
+ """Build Dealix's own daily target list."""
+ sector = sector_focus or DEALIX_ICP_FOCUSES[0]["sector"]
+ city = city_focus or DEALIX_ICP_FOCUSES[0]["city"]
+ accounts = recommend_accounts(
+ sector=sector, city=city, goal="self_growth",
+ offer="Pilot 7 أيام لاستخراج 10 فرص B2B",
+ limit=limit,
+ )
+ committee = map_buying_committee(sector=sector, company_size="small",
+ goal="fill_pipeline")
+ return {
+ "icp": {"sector": sector, "city": city},
+ "targets": accounts,
+ "buying_committee_template": committee,
+ "approval_required": True,
+ "live_send_allowed": False,
+ "notes_ar": (
+ "هذه قائمة استهداف Dealix لنفسه. كل تواصل draft فقط، "
+ "ولا يُرسل إلا بعد اعتماد المؤسس."
+ ),
+ }
+
+
+def build_free_service_offer(target: dict[str, Any]) -> dict[str, Any]:
+ """Build a 'Free Growth Diagnostic' offer card for a single target."""
+ company = target.get("name", "?")
+ return {
+ "target_company": company,
+ "offer_id": "free_growth_diagnostic",
+ "title_ar": f"تشخيص نمو مجاني لـ{company}",
+ "deliverables_ar": [
+ "3 فرص B2B مناسبة لقطاعكم.",
+ "1 رسالة عربية مخصصة.",
+ "1 تقرير مخاطر سريع.",
+ "1 خطة Pilot مقترحة.",
+ ],
+ "delivery_time": "خلال 24 ساعة عمل",
+ "price": 0,
+ "currency": "SAR",
+ "follow_up_offer_ar": (
+ "إذا أعجبكم، نكمل Pilot 7 أيام بـ499 ريال أو مجاني مقابل case study."
+ ),
+ "approval_required": True,
+ }
+
+
+def build_self_growth_daily_brief(
+ *,
+ sector_focus: str | None = None,
+ city_focus: str | None = None,
+) -> dict[str, Any]:
+ """Build today's self-growth brief for Dealix (founder-facing)."""
+ sector = sector_focus or DEALIX_ICP_FOCUSES[0]["sector"]
+ city = city_focus or DEALIX_ICP_FOCUSES[0]["city"]
+ company_brief = build_daily_targeting_brief({"sector": sector, "city": city})
+ actions = recommend_today_actions({"sector": sector, "city": city})
+
+ targets = recommend_dealix_targets(
+ sector_focus=sector, city_focus=city, limit=10,
+ )
+
+ return {
+ "icp": {"sector": sector, "city": city},
+ "company_brief": company_brief,
+ "today_actions": actions,
+ "top_10_targets": targets["targets"]["accounts"][:10],
+ "recommended_first_action_ar": (
+ "ابعث 3 رسائل Free Diagnostic مخصصة هذا الصباح، "
+ "ثم تابع 2 ديمو من الأمس."
+ ),
+ }
+
+
+def build_weekly_learning_report(
+ results: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+ """Build a weekly Arabic learning report from Dealix's own results."""
+ r = results or {}
+ diagnostics = int(r.get("free_diagnostics_delivered", 0))
+ pilots = int(r.get("paid_pilots_started", 0))
+ meetings = int(r.get("meetings_held", 0))
+ case_studies = int(r.get("case_studies_published", 0))
+ revenue = float(r.get("revenue_sar", 0))
+
+ return {
+ "week_metrics": {
+ "free_diagnostics": diagnostics,
+ "paid_pilots": pilots,
+ "meetings": meetings,
+ "case_studies": case_studies,
+ "revenue_sar": revenue,
+ },
+ "learning_questions_ar": [
+ "أي قطاع رد أكثر هذا الأسبوع؟",
+ "أي رسالة نجحت؟ ولماذا؟",
+ "أي قناة فعّالة (إيميل / لينكدإن / شركاء)؟",
+ "أي اعتراض تكرر أكثر من مرتين؟",
+ "ما العرض الذي يبيع بسهولة؟",
+ ],
+ "next_week_experiments_ar": [
+ "جرّب angle جديد لقطاع التدريب: ROI ملموس لـHR.",
+ "أرسل Free Diagnostic لـ20 وكالة تسويق.",
+ "اعقد ديمو واحد مع شركة SaaS سعودية.",
+ "اطلب أول case study من أنجح Pilot.",
+ ],
+ }
+
+
+def build_dealix_self_growth_plan() -> dict[str, Any]:
+ """Top-level monthly plan for Dealix using its own OS to grow."""
+ return {
+ "icp_focuses": list(DEALIX_ICP_FOCUSES),
+ "monthly_targets": {
+ "free_diagnostics_delivered": 30,
+ "paid_pilots_started": 6,
+ "growth_os_subscriptions": 3,
+ "agency_partners_signed": 1,
+ "case_studies_published": 1,
+ },
+ "operating_loop_ar": [
+ "كل صباح: اعرض 10 شركات جديدة + 5 رسائل drafts.",
+ "كل ظهر: راجع الردود + جدول 1-2 ديمو.",
+ "كل مساء: حدّث Proof Ledger + أرسل Free Diagnostic لـ3 شركات.",
+ "كل أسبوع: اكتب learning report + جرّب angle جديد.",
+ "كل شهر: راجع Service Excellence Score لكل خدمة.",
+ ],
+ }
diff --git a/dealix/auto_client_acquisition/targeting_os/service_offers.py b/dealix/auto_client_acquisition/targeting_os/service_offers.py
new file mode 100644
index 00000000..5fcfa28f
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/service_offers.py
@@ -0,0 +1,161 @@
+"""Targeting-tier service offers — quick lookup of buyable offers."""
+
+from __future__ import annotations
+
+from typing import Any
+
+# Targeting-OS-related offers. The full Service Tower has more.
+TARGETING_OFFERS: tuple[dict[str, Any], ...] = (
+ {
+ "id": "list_intelligence",
+ "name_ar": "تحليل قائمة (List Intelligence)",
+ "target_customer_ar": "شركة عندها قائمة أرقام/إيميلات/عملاء قدامى",
+ "outcome_ar": "أفضل 50 target من قائمتك + رسائل + خطة 7 أيام",
+ "price_min_sar": 499,
+ "price_max_sar": 1500,
+ },
+ {
+ "id": "first_10_opportunities_sprint",
+ "name_ar": "10 فرص في 10 دقائق",
+ "target_customer_ar": "شركة B2B تحتاج فرص مؤهلة بسرعة",
+ "outcome_ar": "10 فرص + رسائل + خطة متابعة + Proof Pack",
+ "price_min_sar": 499,
+ "price_max_sar": 1500,
+ },
+ {
+ "id": "self_growth_operator",
+ "name_ar": "مدير نمو شخصي (Self-Growth Operator)",
+ "target_customer_ar": "مؤسسون / مستشارون / وكالات صغيرة",
+ "outcome_ar": "Daily brief + drafts + متابعة + تقارير",
+ "price_min_sar": 999,
+ "price_max_sar": 999,
+ },
+ {
+ "id": "linkedin_lead_gen_setup",
+ "name_ar": "إعداد LinkedIn Lead Gen",
+ "target_customer_ar": "شركات B2B تحتاج decision makers",
+ "outcome_ar": "حملة Lead Gen Form + audiences + ربط CRM",
+ "price_min_sar": 2000,
+ "price_max_sar": 7500,
+ },
+ {
+ "id": "whatsapp_compliance_setup",
+ "name_ar": "إعداد امتثال واتساب",
+ "target_customer_ar": "شركات تستخدم واتساب بشكل عشوائي",
+ "outcome_ar": "تصنيف القوائم + opt-in templates + audit",
+ "price_min_sar": 1500,
+ "price_max_sar": 4000,
+ },
+ {
+ "id": "partner_sprint",
+ "name_ar": "سبرنت شراكات",
+ "target_customer_ar": "شركات تبغى نمو عبر الشركاء",
+ "outcome_ar": "20 شريك محتمل + رسائل + 5 اجتماعات",
+ "price_min_sar": 3000,
+ "price_max_sar": 7500,
+ },
+ {
+ "id": "free_growth_diagnostic",
+ "name_ar": "تشخيص نمو مجاني",
+ "target_customer_ar": "أي شركة B2B تريد عينة قبل الـPilot",
+ "outcome_ar": "3 فرص + رسالة + تقرير مخاطر + خطة Pilot",
+ "price_min_sar": 0,
+ "price_max_sar": 0,
+ },
+)
+
+
+def list_targeting_services() -> dict[str, Any]:
+ return {
+ "total": len(TARGETING_OFFERS),
+ "offers": [dict(o) for o in TARGETING_OFFERS],
+ }
+
+
+def recommend_service_offer(
+ customer_type: str,
+ *,
+ goal: str = "fill_pipeline",
+) -> dict[str, Any]:
+ """Recommend the best-fit offer for a customer type + goal."""
+ ct = (customer_type or "").lower()
+
+ if "agency" in ct or "وكالة" in ct:
+ chosen = next(o for o in TARGETING_OFFERS if o["id"] == "partner_sprint")
+ elif "list" in ct or "قائمة" in ct:
+ chosen = next(o for o in TARGETING_OFFERS if o["id"] == "list_intelligence")
+ elif "founder" in ct or "مؤسس" in ct:
+ chosen = next(o for o in TARGETING_OFFERS if o["id"] == "self_growth_operator")
+ elif "saas" in ct or "b2b" in ct:
+ chosen = next(o for o in TARGETING_OFFERS if o["id"] == "first_10_opportunities_sprint")
+ elif "whatsapp" in ct or "واتساب" in ct:
+ chosen = next(o for o in TARGETING_OFFERS if o["id"] == "whatsapp_compliance_setup")
+ else:
+ chosen = next(o for o in TARGETING_OFFERS if o["id"] == "free_growth_diagnostic")
+
+ return {
+ "recommended_offer": dict(chosen),
+ "reasoning_ar": (
+ f"بناءً على نوع العميل ({customer_type}) والهدف ({goal})، "
+ f"الأنسب: {chosen['name_ar']}."
+ ),
+ }
+
+
+def build_offer_card(service: dict[str, Any] | str) -> dict[str, Any]:
+ """Build an Arabic offer card (≤3 buttons) for the inbox/feed."""
+ if isinstance(service, str):
+ service = next((o for o in TARGETING_OFFERS if o["id"] == service),
+ {"id": service, "name_ar": service,
+ "outcome_ar": "", "price_min_sar": 0, "price_max_sar": 0})
+ price_label = (
+ "مجاني"
+ if service.get("price_min_sar") == 0
+ else f"{service.get('price_min_sar')}–{service.get('price_max_sar')} ريال"
+ )
+ return {
+ "type": "service_offer",
+ "service_id": service.get("id"),
+ "title_ar": service.get("name_ar", "خدمة"),
+ "summary_ar": service.get("outcome_ar", ""),
+ "price_ar": price_label,
+ "buttons_ar": ["ابدأ الآن", "اطلب عرض", "تخطي"],
+ "approval_required": True,
+ }
+
+
+def estimate_service_price(
+ service_id: str,
+ *,
+ company_size: str = "small",
+ urgency: str = "normal",
+ channels_count: int = 1,
+) -> dict[str, Any]:
+ """Estimate a SAR price range for a service given inputs."""
+ base = next((o for o in TARGETING_OFFERS if o["id"] == service_id), None)
+ if base is None:
+ return {"error": f"unknown service: {service_id}"}
+
+ p_min = float(base["price_min_sar"])
+ p_max = float(base["price_max_sar"])
+
+ # Size multiplier
+ size_mult = {"micro": 0.8, "small": 1.0, "medium": 1.3, "large": 1.7}.get(
+ company_size, 1.0,
+ )
+ # Urgency multiplier
+ urgency_mult = {"normal": 1.0, "rush": 1.3, "asap": 1.5}.get(urgency, 1.0)
+ # Channel multiplier
+ ch_mult = 1.0 + max(0, channels_count - 1) * 0.15
+
+ return {
+ "service_id": service_id,
+ "estimated_min_sar": round(p_min * size_mult * urgency_mult * ch_mult),
+ "estimated_max_sar": round(p_max * size_mult * urgency_mult * ch_mult),
+ "currency": "SAR",
+ "factors": {
+ "company_size": company_size,
+ "urgency": urgency,
+ "channels_count": channels_count,
+ },
+ }
diff --git a/dealix/auto_client_acquisition/targeting_os/social_strategy.py b/dealix/auto_client_acquisition/targeting_os/social_strategy.py
new file mode 100644
index 00000000..65d63c39
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/social_strategy.py
@@ -0,0 +1,94 @@
+"""Social strategy — official APIs + opt-in DMs only, public replies as drafts."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def social_do_not_do() -> list[str]:
+ return [
+ "scrape_public_profiles",
+ "auto_dm_strangers",
+ "fake_engagement",
+ "buy_followers_or_engagement",
+ "use_unauthorized_apis",
+ "ignore_platform_terms",
+ ]
+
+
+def recommend_social_sources(
+ sector: str, *, goal: str = "fill_pipeline",
+) -> dict[str, Any]:
+ """Recommend social sources by sector — only safe, official channels."""
+ s = (sector or "").lower()
+ by_sector = {
+ "real_estate": ["instagram_graph_api", "x_api_mentions", "google_business_reviews"],
+ "retail": ["instagram_graph_api", "google_business_reviews", "tiktok_business"],
+ "healthcare": ["google_business_reviews", "instagram_graph_api"],
+ "saas": ["x_api_mentions", "linkedin_lead_gen_forms"],
+ "training": ["linkedin_lead_gen_forms", "x_api_mentions"],
+ "agency": ["linkedin_lead_gen_forms", "x_api_mentions"],
+ }
+ return {
+ "sector": s,
+ "recommended_sources": by_sector.get(s, ["linkedin_lead_gen_forms",
+ "google_business_reviews"]),
+ "do_not_do": social_do_not_do(),
+ "notes_ar": (
+ "نلتزم بالـ official APIs والصلاحيات الرسمية فقط. "
+ "DMs بدون تفاعل سابق محظورة."
+ ),
+ }
+
+
+def build_social_listening_plan(
+ sector: str, keywords: list[str] | None = None,
+) -> dict[str, Any]:
+ """Build a social listening plan — listening only, no auto-replies."""
+ keywords = keywords or [
+ "نمو", "B2B", "leads", "اجتماعات",
+ "Pilot", "تدريب مبيعات", "أتمتة",
+ ]
+ return {
+ "sector": sector,
+ "keywords_ar_or_en": keywords,
+ "listen_for": [
+ "mentions_of_company",
+ "competitor_mentions",
+ "buying_signals",
+ "complaints",
+ "hiring_signals",
+ "events_and_launches",
+ ],
+ "convert_to_cards_for": [
+ "lead", "competitor_move", "review_response",
+ "content_idea", "partner_suggestion",
+ ],
+ "no_auto_reply": True,
+ "approval_required_for_reply": True,
+ }
+
+
+def draft_public_reply(
+ comment: str,
+ *,
+ brand_voice: str = "professional_saudi",
+) -> dict[str, Any]:
+ """Build a public reply draft to a comment/review (Arabic)."""
+ body_ar = (
+ "شكراً على ملاحظتك. نأخذ تعليقك بجد وسنتواصل معك مباشرة لتفاصيل أكثر "
+ "ومعالجة الموضوع. سعدنا بمشاركتك."
+ )
+ return {
+ "draft": True,
+ "body_ar": body_ar,
+ "brand_voice": brand_voice,
+ "approval_required": True,
+ "live_publish_allowed": False,
+ "guidelines_ar": [
+ "لا تكشف بيانات شخصية في الرد العام.",
+ "حول التفاصيل لقناة خاصة.",
+ "لا تتجاهل العميل المنزعج.",
+ "لا تحذف أو ترد بشكل دفاعي.",
+ ],
+ }
diff --git a/dealix/auto_client_acquisition/targeting_os/whatsapp_strategy.py b/dealix/auto_client_acquisition/targeting_os/whatsapp_strategy.py
new file mode 100644
index 00000000..68faa391
--- /dev/null
+++ b/dealix/auto_client_acquisition/targeting_os/whatsapp_strategy.py
@@ -0,0 +1,124 @@
+"""WhatsApp strategy — opt-in only, never cold, draft-first."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def whatsapp_do_not_do() -> list[str]:
+ return [
+ "cold_send_without_consent",
+ "scrape_groups",
+ "buy_phone_lists",
+ "auto_send_without_approval",
+ "send_outside_business_hours_without_consent",
+ "ignore_opt_out",
+ ]
+
+
+def requires_opt_in(contact: dict[str, Any]) -> dict[str, Any]:
+ """
+ Check whether reaching this contact via WhatsApp requires opt-in.
+
+ Returns the opt-in requirement + how to obtain it if missing.
+ """
+ source = contact.get("source", "unknown_source")
+ opt_in = (contact.get("opt_in_status") or "unknown").lower()
+ has_relationship = bool(contact.get("has_relationship", False))
+
+ needs = True
+ if has_relationship and source == "crm_customer" and opt_in == "yes":
+ needs = False
+ if source == "inbound_lead" and opt_in in ("yes", "double"):
+ needs = False
+
+ return {
+ "needs_opt_in": needs,
+ "current_status": opt_in,
+ "source": source,
+ "obtain_via_ar": (
+ "نموذج موقع + تأكيد بالـemail (double opt-in) أو "
+ "Lead Gen Form + شرح صريح بنوع الرسائل."
+ ),
+ }
+
+
+def draft_whatsapp_message(
+ contact: dict[str, Any], *, offer: str = "", why_now: str = "",
+) -> dict[str, Any]:
+ """Build a WhatsApp message draft. Never sends; always approval-required."""
+ name = contact.get("name", "")
+ sal = f"هلا {name}" if name else "هلا"
+ why_now_part = f" {why_now}" if why_now else ""
+ body_ar = (
+ f"{sal}.{why_now_part} نشتغل على Dealix كمدير نمو عربي. "
+ "خلال 7 أيام نطلع 10 فرص B2B + رسائل + خطة متابعة. "
+ f"{offer or 'Pilot بـ 499 ريال أو مجاني مقابل case study.'} "
+ "يناسبك ديمو 12 دقيقة هذا الأسبوع؟"
+ "\n\nلو ما تفضل هذه الرسائل، اكتب \"إلغاء\" وأوقفها."
+ )
+ risk = score_whatsapp_risk(contact, body_ar)
+ return {
+ "channel": "whatsapp",
+ "body_ar": body_ar,
+ "approval_required": True,
+ "live_send_allowed": False,
+ "opt_in_check": requires_opt_in(contact),
+ "risk": risk,
+ "do_not_do": whatsapp_do_not_do(),
+ }
+
+
+def score_whatsapp_risk(contact: dict[str, Any], message: str = "") -> dict[str, Any]:
+ """Score WhatsApp risk 0..100; very strict."""
+ source = contact.get("source", "unknown_source")
+ opt_in = (contact.get("opt_in_status") or "unknown").lower()
+ risk = 0
+ reasons: list[str] = []
+
+ if source == "cold_list":
+ risk += 100
+ reasons.append("قائمة باردة — واتساب محظور تلقائياً.")
+ if opt_in not in ("yes", "double"):
+ risk += 40
+ reasons.append("لا يوجد opt-in واضح.")
+ if source == "unknown_source":
+ risk += 30
+ reasons.append("مصدر غير معروف.")
+
+ risky_phrases = ["ضمان 100%", "آخر فرصة", "اضغط الآن", "نتائج مضمونة"]
+ for p in risky_phrases:
+ if p in message:
+ risk += 25
+ reasons.append(f"عبارة محظورة: {p}")
+
+ risk = max(0, min(100, risk))
+ if risk >= 50:
+ verdict = "blocked"
+ elif risk >= 25:
+ verdict = "needs_review"
+ else:
+ verdict = "safe"
+ return {"risk": risk, "verdict": verdict, "reasons_ar": reasons}
+
+
+def build_opt_in_request_template(
+ company_name: str = "Dealix",
+) -> dict[str, Any]:
+ """Build an opt-in request template the customer can send via website/forms."""
+ return {
+ "channel": "website_or_form",
+ "body_ar": (
+ f"بالاشتراك في تنبيهات {company_name} عبر واتساب، أوافق على استقبال "
+ "رسائل تتعلق بالعروض والمحتوى الخاص بالشركة. أعرف أنه يمكنني الانسحاب "
+ "في أي وقت بكتابة \"إلغاء\"."
+ ),
+ "explicit_purpose_required": True,
+ "explicit_company_name_required": True,
+ "explicit_unsubscribe_required": True,
+ "double_opt_in_recommended": True,
+ "notes_ar": (
+ "WhatsApp Business يتطلب شرحاً صريحاً لما سيستقبله المستخدم. "
+ "ظهور رقم واتساب في موقع لا يكفي كموافقة."
+ ),
+ }
diff --git a/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md b/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md
index b8559292..625e46ab 100644
--- a/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md
+++ b/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md
@@ -209,6 +209,75 @@ OAuth Gmail/Calendar، حصص، سياسات.
**ممنوع اليوم:** live WhatsApp send, live Gmail send, live Calendar insert, payment charge, scraping social, وعود "نضمن نتائج".
+## 36. Targeting & Acquisition OS — نظام الاستهداف الذكي
+
+طبقة جديدة (16 module + 20 endpoint + 47 اختبار) تجعل Dealix يستهدف بذكاء بدلاً من جمع عشوائي:
+
+- **Account-first**: `account_finder` يحدد 10-25 شركة لكل (sector, city) مع `fit_score` و`why_now_ar`.
+- **Buying Committee**: `buyer_role_mapper` بـ14 دور وخرائط حسب القطاع (training/saas/real_estate/...).
+- **Contact Source Policy**: 12 مصدر (crm_customer → opt_out) مع risk_score + retention.
+- **Contactability Matrix**: 5 action modes (suggest_only/draft_only/approval_required/approved_execute/blocked).
+- **LinkedIn Strategy**: Lead Forms + Ads + Manual فقط — `linkedin_do_not_do()` يقفل scraping/auto-DM/auto-connect.
+- **Email Strategy**: drafts + unsubscribe + pacing per domain reputation.
+- **WhatsApp Strategy**: opt-in only، rejects cold + risky phrases تلقائياً.
+- **Outreach Scheduler**: day-by-day plan + daily limits + opt-out enforcement.
+- **Reputation Guard**: bounce/complaint/opt-out thresholds → healthy/watch/pause مع recovery actions.
+- **Daily Autopilot**: Arabic brief + 7 today actions + EOD report.
+- **Self-Growth Mode**: 5 ICP focuses لـ Dealix نفسه + daily brief + weekly learning.
+- **Free Growth Diagnostic**: العرض المجاني الذي يجلب Pilots.
+- **Contract Drafts**: Pilot/DPA/Referral/Agency outlines (legal review required, PDPL-aware).
+
+**Endpoints:** `/api/v1/targeting/{accounts/recommend, buying-committee/map, contacts/evaluate, uploaded-list/analyze, outreach/plan, daily-autopilot/demo, self-growth/demo, reputation/status, linkedin/strategy, drafts/email, drafts/whatsapp, free-diagnostic, services, contracts/templates, ...}`. **التفصيل:** [`TARGETING_ACQUISITION_OS.md`](TARGETING_ACQUISITION_OS.md).
+
+## 37. Service Tower — برج الخدمات الذاتية
+
+**12 Productized Service** + Wizard + Pricing Engine + Scorecard + WhatsApp CEO Control + Upgrade Paths (8 modules + 20 endpoint + 38 اختبار):
+
+| الخدمة | السعر | النوع |
+|--------|------|------|
+| Free Growth Diagnostic | مجاني | one_time |
+| List Intelligence | 499–1,500 | one_time |
+| First 10 Opportunities Sprint | 499–1,500 | sprint |
+| Self-Growth Operator | 999/شهر | monthly |
+| Growth OS Monthly | 2,999/شهر | monthly |
+| Email Revenue Rescue | 1,500–5,000 | one_time |
+| Meeting Booking Sprint | 1,500–5,000 | sprint |
+| Partner Sprint | 3,000–7,500 | sprint |
+| Agency Partner Program | 10,000–50,000 | one_time |
+| WhatsApp Compliance Setup | 1,500–4,000 | one_time |
+| LinkedIn Lead Gen Setup | 2,000–7,500 | one_time |
+| Executive Growth Brief | 499–999/شهر | monthly |
+
+**3 أبواب للعميل:**
+1. أريد عملاء جدد.
+2. عندي بيانات وأبغى أستفيد منها.
+3. أبغى توسع وشراكات.
+
+**Endpoints:** `/api/v1/services/{catalog, recommend, {id}/start, {id}/workflow, {id}/quote, {id}/scorecard, {id}/upgrade-path, ceo/daily-brief, ceo/approval-card, ...}`. **التفصيل:** [`SERVICE_TOWER_STRATEGY.md`](SERVICE_TOWER_STRATEGY.md).
+
+## 38. Service Excellence OS — مصنع الخدمات الممتازة
+
+**8 modules + 22 endpoint + 33 اختبار** يضمنون أن كل خدمة تطلق بـ score ≥80 وتجاوز 4 quality gates، وتستمر في التحسين الأسبوعي:
+
+- **Feature Matrix** — 12 must-have لكل خدمة + advanced/premium/future.
+- **Service Scoring** — 10 أبعاد × 10 = 100 → launch_ready/beta_only/needs_work.
+- **Quality Review** — 4 gates: proof / approval / pricing / channels.
+- **Competitor Gap** — مقارنة بـ7 فئات منافسين (CRM, WA tools, email assistants, LinkedIn tools, agencies, revenue intelligence, generic AI).
+- **Proof Metrics** — ROI estimate (pipeline_x + closed_won_x).
+- **Research Lab** — brief شهري + hypotheses + experiments.
+- **Improvement Backlog** — feedback → backlog → prioritized weekly tasks.
+- **Launch Package** — landing + sales script + 12-min demo + 5-day onboarding.
+
+**Endpoints:** `/api/v1/service-excellence/{id}/{feature-matrix, score, quality-review, proof-metrics, gap-analysis, research-brief, experiments, monthly-review, backlog, launch-package, sales-script, demo-script}` + `/review/all`. **التفصيل:** [`SERVICE_EXCELLENCE_OS.md`](SERVICE_EXCELLENCE_OS.md).
+
+## 39. Landing Pages
+
+- `landing/services.html` — 3 أبواب + 12 خدمة productized.
+- `landing/free-diagnostic.html` — العرض المجاني.
+- `landing/first-10-opportunities.html` — Kill Feature.
+- `landing/agency-partner.html` — برنامج الوكالة الشريكة.
+- `landing/private-beta.html` — Private Beta launch.
+
---
**الخلاصة:** المنتج **قوي كأساس سوقي وتقني**؛ الإطلاق العام يحتاج تشغيلاً وامتثالاً وتجربة عميل مغلقة أولاً. الإطلاق اليوم = Private Beta + Pilots + Proof Pack، ليس Public Launch.
diff --git a/dealix/docs/SERVICE_EXCELLENCE_OS.md b/dealix/docs/SERVICE_EXCELLENCE_OS.md
new file mode 100644
index 00000000..5032d51e
--- /dev/null
+++ b/dealix/docs/SERVICE_EXCELLENCE_OS.md
@@ -0,0 +1,185 @@
+# Service Excellence OS — مصنع الخدمات الممتازة
+
+> **القاعدة:** لا خدمة تطلق إنتاجياً إلا إذا حصلت على score ≥80 وتجاوزت 4 quality gates. ولا تتوقف عند الإطلاق — تستمر في التحسين الأسبوعي.
+
+---
+
+## 1. الوحدات
+
+| الوحدة | الدور |
+|--------|------|
+| `feature_matrix` | 12 must-have feature لكل خدمة + advanced/premium/future. |
+| `service_scoring` | 10 أبعاد × 10 نقاط = 100. status: launch_ready / beta_only / needs_work. |
+| `quality_review` | 4 gates: proof / approval / pricing / channels. |
+| `competitor_gap` | مقارنة structural بـ7 فئات منافسين. |
+| `proof_metrics` | الـ metrics المطلوبة + ROI estimate. |
+| `research_lab` | brief شهري + hypotheses + experiments. |
+| `service_improvement_backlog` | feedback → backlog → prioritization. |
+| `launch_package` | landing + sales + demo + onboarding. |
+
+---
+
+## 2. الـ 12 Must-Have Features (لكل خدمة)
+
+1. Self-Serve Intake.
+2. AI Recommendation.
+3. Data Quality Check.
+4. Contactability / Risk Gate.
+5. Channel Strategy.
+6. Arabic Contextual Drafting.
+7. Approval Cards.
+8. Execution Mode (draft/export/approved).
+9. Proof Pack.
+10. Learning Loop.
+11. Upsell Path.
+12. Service Score.
+
+---
+
+## 3. الـ 10 أبعاد للـ Score
+
+| البُعد | الوزن |
+|------|----:|
+| Clarity (وضوح الألم) | 10 |
+| Speed-to-Value | 10 |
+| Automation | 10 |
+| Compliance | 10 |
+| Proof | 10 |
+| Upsell | 10 |
+| Uniqueness (Saudi-first) | 10 |
+| Scalability (multi-sector) | 10 |
+| Ops Daily (autopilot) | 10 |
+| Proof Data | 10 |
+
+**Status:**
+- ≥80: `launch_ready`
+- ≥60: `beta_only`
+- <60: `needs_work`
+
+---
+
+## 4. الـ 4 Quality Gates
+
+قبل إطلاق أي خدمة:
+
+1. **Proof gate** — لا proof_metrics → blocked.
+2. **Approval gate** — لا approval_policy → blocked.
+3. **Pricing gate** — تسعير غير منطقي → blocked.
+4. **Channels gate** — تكامل غير آمن (scraping/auto_dm/etc.) → blocked.
+
+`review_service_before_launch(service_id)` يُرجع verdict واحد من:
+- `launch_ready`
+- `beta_only`
+- `needs_work`
+- `blocked_at_gate`
+
+---
+
+## 5. Competitor Gap (7 فئات)
+
+| Category | Strengths | Limits |
+|----------|-----------|--------|
+| CRM عام | تخزين بيانات | ينتظر إدخال يدوي |
+| WhatsApp tools | Broadcast | لا approval-first |
+| Email assistants | كتابة أسرع | لا تحول الإيميل لـ pipeline |
+| LinkedIn tools | إيجاد leads | كثيرها يخالف ToS |
+| وكالات | خبرة بشرية | لا تتوسع |
+| Revenue intelligence | تحليل calls | تبدأ بعد المكالمة |
+| Generic AI agent | مرن | بدون سياق شركة |
+
+**ميزات Dealix:**
+- موجّه للسوق السعودي.
+- Approval-first.
+- Proof Pack شهري.
+- Multi-channel orchestration.
+- Self-improving Curator.
+- PDPL-aware.
+
+---
+
+## 6. Research Lab (شهرياً)
+
+لكل خدمة:
+- 6 أسئلة بحث (من اشترى، TTV، اعتراضات، deliverables، metrics، pricing).
+- 4-5 hypotheses للتحسين.
+- 3 experiments الأولوية (impact/effort).
+- Monthly review بـ score حالي + gap + experiments.
+
+---
+
+## 7. Improvement Backlog
+
+- `convert_feedback_to_backlog` — Feedback → backlog item.
+- `prioritize_backlog_items` — impact desc, effort asc.
+- `recommend_weekly_improvements` — 3 weekly tasks.
+
+---
+
+## 8. Launch Package (per service)
+
+1. **Landing outline** (RTL Arabic): hero, promise, 3-step how-it-works, deliverables, pricing, proof, safety, FAQ, CTA.
+2. **Sales script**: 5 discovery questions + pitch + 4 objection handlers + close.
+3. **Demo script**: 12-min minute-by-minute Arabic walkthrough.
+4. **Onboarding checklist**: first-5-days plan.
+
+---
+
+## 9. Endpoints (`/api/v1/service-excellence/...`)
+
+```
+GET /{id}/feature-matrix
+GET /{id}/feature-classification
+GET /{id}/missing-features
+GET /{id}/score
+GET /{id}/quality-review
+GET /review/all
+GET /{id}/proof-metrics
+POST /{id}/roi-estimate
+GET /{id}/gap-analysis
+GET /{id}/research-brief
+GET /{id}/feature-hypotheses
+GET /{id}/experiments
+GET /{id}/monthly-review
+GET /{id}/backlog
+POST /{id}/backlog/from-feedback
+POST /{id}/backlog/prioritize
+GET /{id}/weekly-improvements
+GET /{id}/launch-package
+GET /{id}/landing-outline
+GET /{id}/sales-script
+GET /{id}/demo-script
+GET /{id}/onboarding-checklist
+```
+
+---
+
+## 10. اختبارات
+
+`tests/unit/test_service_excellence.py` — 33 اختبار:
+- Feature matrix ≥10 must-haves.
+- Score returns valid status.
+- Every catalogued service passes the 4 gates.
+- ROI estimate returns x-multiples.
+- Competitor gap lists advantages + do-not-copy.
+- Research brief has ≥5 questions.
+- Hypotheses ≥3 + experiments ≤3.
+- Backlog conversion + prioritization.
+- Launch package complete.
+- Demo script = 12 minutes.
+
+---
+
+## 11. Weekly Improvement Loop
+
+```
+كل اثنين:
+1. شغّل /review/all على كل الـ 12 خدمة.
+2. أي خدمة < 80 → افتح backlog item.
+3. أي خدمة blocked → إصلاح فوري قبل إطلاق جديد.
+4. اختر experiment واحد لكل خدمة.
+
+كل جمعة:
+1. سجل النتائج في Service Scorecard.
+2. حدّث الـ improvement backlog.
+3. أرسل executive brief للمؤسس.
+```
diff --git a/dealix/docs/SERVICE_TOWER_STRATEGY.md b/dealix/docs/SERVICE_TOWER_STRATEGY.md
new file mode 100644
index 00000000..ad5003ac
--- /dev/null
+++ b/dealix/docs/SERVICE_TOWER_STRATEGY.md
@@ -0,0 +1,136 @@
+# Service Tower Strategy — برج الخدمات الذاتي
+
+> **الفكرة:** كل قدرة في Dealix تتحول إلى **Productized Service** بمواصفات: target customer + outcome + inputs + workflow + deliverables + pricing + risk + proof + upgrade path.
+
+---
+
+## 1. القاعدة الذهبية
+
+**العميل لا يشتري ميزة. يشتري نتيجة منظمة.**
+
+كل خدمة تمشي في نفس الـ pipeline:
+```
+Goal → Intake → Data Check → Risk Check → Strategy →
+Drafts → Approval → Execution/Export → Tracking → Proof → Upsell
+```
+
+---
+
+## 2. الـ12 خدمة (Productized)
+
+| # | الخدمة | المدخلات | المخرجات | السعر |
+|---|--------|----------|---------|-------|
+| 1 | Free Growth Diagnostic | sector/city/offer/goal | 3 فرص + رسالة + مخاطر + خطة Pilot | 0 |
+| 2 | List Intelligence | CSV + channels | تنظيف + أفضل 50 + رسائل | 499–1,500 |
+| 3 | First 10 Opportunities Sprint | sector/city/offer/goal | 10 فرص + رسائل + Proof Pack | 499–1,500 |
+| 4 | Self-Growth Operator | company profile + goals | Daily brief + drafts + reports | 999/شهر |
+| 5 | Growth OS Monthly | channels + team_size | المنصة الكاملة شهرياً | 2,999/شهر |
+| 6 | Email Revenue Rescue | gmail label + ICP | استخراج فرص ضائعة + drafts | 1,500–5,000 |
+| 7 | Meeting Booking Sprint | prospects + calendar | invitations + briefs + follow-ups | 1,500–5,000 |
+| 8 | Partner Sprint | sector + partner goal | 20 شريك + رسائل + 5 اجتماعات | 3,000–7,500 |
+| 9 | Agency Partner Program | agency profile | بيع Dealix لعملاء الوكالة | 10,000–50,000 |
+| 10 | WhatsApp Compliance Setup | contact list + practice | audit + opt-in templates + ledger | 1,500–4,000 |
+| 11 | LinkedIn Lead Gen Setup | ICP + offer + ad budget | حملة Lead Form + ربط CRM | 2,000–7,500 |
+| 12 | Executive Growth Brief | company profile | موجز يومي 3+3+3 | 499–999/شهر |
+
+---
+
+## 3. الـ Wizard
+
+```
+العميل يجيب:
+- نوع الشركة
+- الهدف
+- هل عندك قائمة؟
+- ما القنوات المتاحة؟
+- الميزانية
+
+النظام يوصي بخدمة واحدة + يبرر القرار.
+```
+
+ترتيب القرارات:
+1. وكالة → Partner Sprint / Agency Program.
+2. عنده قائمة → List Intelligence.
+3. مؤسس → Self-Growth Operator.
+4. CEO → Executive Growth Brief.
+5. واتساب → Compliance Setup.
+6. هدف rescue → Email Revenue Rescue.
+7. هدف اجتماعات → Meeting Booking Sprint.
+8. هدف شراكات → Partner Sprint.
+9. ميزانية شهرية ≥ 2999 → Growth OS.
+10. الافتراضي → First 10 Opportunities.
+
+---
+
+## 4. WhatsApp CEO Control
+
+كل قرار يصل المؤسس عبر واتساب كـ كرت:
+- Daily Service Brief (≤3 buttons).
+- Service Approval Card (`اعتمد / عدّل / ارفض`).
+- Risk Alert Card.
+- End-of-Day Report.
+
+---
+
+## 5. Pricing Engine
+
+ضرّابات السعر:
+- `company_size`: micro 0.8x, small 1.0x, medium 1.3x, large 1.7x.
+- `urgency`: normal 1.0x, rush 1.3x, asap 1.5x.
+- `channels_count`: +15% لكل قناة إضافية.
+
+Setup fee = month-equivalent للـ monthly services. السنوي بخصم 15%.
+
+---
+
+## 6. Upgrade Paths
+
+```
+Free Diagnostic → First 10 Opportunities → Growth OS Monthly → Agency Partner
+List Intelligence → Growth OS Monthly
+Self-Growth Operator → Growth OS Monthly
+Email Revenue Rescue → Growth OS Monthly
+Partner Sprint → Agency Partner Program
+```
+
+كل upgrade path له upsell message عربي جاهز.
+
+---
+
+## 7. Endpoints (`/api/v1/services/...`)
+
+```
+GET /catalog
+GET /summary
+POST /recommend
+GET /{id}/intake-questions
+POST /{id}/start
+GET /{id}/workflow
+GET /{id}/deliverables
+GET /{id}/proof-pack-template
+GET /{id}/client-report-outline
+GET /{id}/operator-checklist
+POST /{id}/quote
+GET /{id}/setup-fee
+GET /{id}/monthly-offer
+POST /{id}/scorecard
+GET /{id}/upgrade-path
+GET /{id}/post-service-plan
+GET /ceo/daily-brief
+POST /ceo/approval-card
+GET /ceo/risk-alert/demo
+GET /ceo/end-of-day/demo
+```
+
+---
+
+## 8. اختبارات
+
+`tests/unit/test_service_tower.py` — 38 اختبار:
+- Catalog ≥12 خدمة + critical services.
+- Pricing + proof metrics + deliverables موجودة.
+- Wizard recommendations (agency, list, founder, CEO, budget).
+- Workflow includes approval.
+- Quote scaling by size.
+- CEO cards ≤3 buttons + لا live send.
+- Upgrade paths.
diff --git a/dealix/docs/TARGETING_ACQUISITION_OS.md b/dealix/docs/TARGETING_ACQUISITION_OS.md
new file mode 100644
index 00000000..81b1fbe7
--- /dev/null
+++ b/dealix/docs/TARGETING_ACQUISITION_OS.md
@@ -0,0 +1,184 @@
+# Targeting & Acquisition OS — نظام الاستهداف الذكي
+
+> **القاعدة:** Dealix لا يجمع كل شيء من كل مكان. يستهدف بذكاء، عبر مصادر مصرّح بها، مع موافقات بشرية، ومراقبة سمعة، وتعلّم يومي.
+
+---
+
+## 1. لماذا Targeting OS؟
+
+أي أداة تستطيع جمع أرقام بالـ scraping. القوة الحقيقية:
+- **Account-first**: ابحث عن الشركات قبل الأشخاص.
+- **Buying Committee**: من غالباً يقرر داخل كل شركة؟
+- **Contactability Gate**: هل التواصل مسموح؟
+- **Channel Strategy**: ما القناة الأفضل لكل مصدر؟
+- **Reputation Guard**: إذا تدهورت السمعة → أوقف القناة تلقائياً.
+- **Daily Autopilot**: brief يومي + actions + Proof.
+- **Self-Growth Mode**: Dealix يستهدف عملاءه بنفس النظام.
+
+---
+
+## 2. الوحدات (16 module)
+
+| الوحدة | الدور |
+|--------|------|
+| `account_finder` | يحدد 10-25 شركة مناسبة لكل (sector, city). |
+| `buyer_role_mapper` | 14 دور + خرائط buying committee حسب القطاع. |
+| `contact_source_policy` | 12 مصدر، كل واحد له risk_score + channels مسموحة + retention. |
+| `contactability_matrix` | 5 action modes: suggest_only / draft_only / approval_required / approved_execute / blocked. |
+| `linkedin_strategy` | Lead Forms + Ads + Manual فقط. **لا scraping/auto-DM/auto-connect**. |
+| `email_strategy` | Drafts + unsubscribe + pacing حسب domain reputation. |
+| `whatsapp_strategy` | Opt-in only؛ rejects cold + risky phrases. |
+| `social_strategy` | Listening + drafts فقط؛ لا auto-publish. |
+| `outreach_scheduler` | Day-by-day plan + daily limits + opt-out enforcement. |
+| `reputation_guard` | Bounce/complaint/opt-out thresholds → healthy/watch/pause. |
+| `daily_autopilot` | Daily brief + 7 today actions + EOD report. |
+| `acquisition_scorecard` | Pipeline / meetings / risks / productivity score. |
+| `self_growth_mode` | Dealix ICP focus + daily brief + weekly learning. |
+| `free_diagnostic` | Free 5-section Arabic diagnostic → paid pilot offer. |
+| `contract_drafts` | Pilot/DPA/Referral/Agency/SOW outlines (legal review required). |
+| `service_offers` | 7 targeting-tier offers + pricing + recommend. |
+
+---
+
+## 3. القنوات والقواعد
+
+### LinkedIn
+**الممنوع** (encoded in `linkedin_do_not_do()`):
+- `scrape_profiles, auto_connect, auto_dm, browser_automation, fake_engagement, download_contacts_from_linkedin, buy_scraped_leads, use_unauthorized_extensions`.
+
+**المسموح**:
+- LinkedIn Lead Gen Forms (أساسي).
+- LinkedIn Ads.
+- البحث اليدوي المعتمد (manual research task).
+- Connection requests يدوية بمسودات Dealix.
+
+### WhatsApp
+- لا cold بدون opt-in واضح.
+- opt-in template يحتاج: اسم النشاط + الغرض + خيار الانسحاب.
+- double opt-in موصى به.
+
+### Email
+- سياق واضح + unsubscribe.
+- Pacing حسب `domain_reputation`: fresh/warmed/trusted/damaged.
+- إيقاف على bounce ≥ 5%.
+
+### Social
+- API رسمية فقط.
+- Listening مسموح.
+- Replies = drafts بموافقة.
+
+---
+
+## 4. مصادر الـ Contacts (12)
+
+| Source | Risk | Status الافتراضي |
+|--------|------|-----------------|
+| `crm_customer` | 5 | safe |
+| `inbound_lead` | 5 | safe |
+| `website_form` | 10 | safe |
+| `linkedin_lead_form` | 10 | safe |
+| `event_lead` | 20 | needs_review |
+| `referral` | 25 | needs_review |
+| `partner_intro` | 25 | needs_review |
+| `manual_research` | 50 | needs_review |
+| `uploaded_list` | 60 | needs_review |
+| `unknown_source` | 80 | needs_review |
+| `cold_list` | 95 | blocked (waتساب)/needs_review (إيميل) |
+| `opt_out` | 100 | blocked (كل القنوات) |
+
+---
+
+## 5. Daily Operating Loop
+
+```
+صباحاً:
+- 10 شركات جديدة مناسبة
+- 5 رسائل drafts للموافقة
+- 3 leads متأخرة (>72h)
+- 1 فرصة شريك
+- 1 خطر سمعة
+
+ظهراً:
+- اعتماد + إرسال 5 emails
+- مراجعة 12 رقم بدون مصدر
+- ديمو شريك
+
+مساءً:
+- 32 حساب تم تحليله
+- 6 مسودات معتمدة
+- 2 ردود إيجابية
+- 1 اجتماع مجدول
+- 8 مخاطر منعت
+```
+
+---
+
+## 6. Self-Growth Mode
+
+5 ICP focuses لـ Dealix نفسه:
+1. وكالات تسويق B2B في الرياض.
+2. شركات تدريب B2B في الرياض.
+3. شركات استشارات نمو.
+4. SaaS سعودية صغيرة-متوسطة.
+5. وسطاء عقار B2B في جدة.
+
+كل صباح: 10 شركات + 5 رسائل + اعتماد المؤسس.
+
+أهداف شهرية: 30 Free Diagnostic، 6 Paid Pilots، 3 Growth OS، 1 وكالة شريكة.
+
+---
+
+## 7. Endpoints (`/api/v1/targeting/...`)
+
+```
+POST /accounts/recommend
+POST /buying-committee/map
+POST /contacts/evaluate
+POST /uploaded-list/analyze
+POST /outreach/plan
+GET /daily-autopilot/demo
+GET /self-growth/demo
+POST /self-growth/targets
+POST /self-growth/weekly-report
+GET /reputation/status
+POST /reputation/recovery
+POST /linkedin/strategy
+POST /drafts/email
+POST /drafts/whatsapp
+POST /drafts/email-followup
+POST /drafts/role-angle
+POST /free-diagnostic
+GET /services
+POST /services/recommend
+GET /contracts/templates
+```
+
+---
+
+## 8. اختبارات
+
+`tests/unit/test_targeting_os.py` — 47 اختبار:
+- Account finder + Arabic + safe sources.
+- Buying committee + role-based angles.
+- Source classification + 12 sources.
+- Contactability (opt-out, cold WA, inbound safe, unknown review).
+- LinkedIn (لا scraping/auto-DM).
+- Email risk + unsubscribe + 3-step follow-up.
+- WhatsApp risk + opt-in templates.
+- Outreach plan + daily limits.
+- Reputation guard + recovery.
+- Self-growth + free diagnostic + uploaded list preview.
+- Contracts (legal review + PDPL).
+- Acquisition scorecard.
+
+---
+
+## 9. ما لا تفعله
+
+- لا scraping LinkedIn/social.
+- لا auto-DM في أي منصة.
+- لا cold WhatsApp.
+- لا charge بدون تأكيد.
+- لا scraping ToS-مخالف.
+- لا وعود بنتائج مضمونة.
+- لا تخزين بطاقات.
diff --git a/dealix/landing/agency-partner.html b/dealix/landing/agency-partner.html
new file mode 100644
index 00000000..a3ba5fa3
--- /dev/null
+++ b/dealix/landing/agency-partner.html
@@ -0,0 +1,90 @@
+
+
+
+
+
+Dealix — برنامج وكالة شريكة
+
+
+
+
+
+
+
+ ماذا تحصل الوكالة؟
+
+
+
Setup Fee
+
10,000–50,000 ريال حسب الحزمة.
+
+
+
Revenue Share
+
على كل عميل تجلبه + اشتراكاته الشهرية.
+
+
+
Co-Branded Proof
+
تقارير شهرية بعلامة الوكالة لعملائها.
+
+
+
Client Dashboard
+
لوحة لإدارة كل عملاء الوكالة في مكان واحد.
+
+
+
+
+
+ كيف تبدأ؟
+
+ Onboarding للوكالة (2-3 أيام).
+ أول عميل عبر Free Diagnostic.
+ تشغيل Pilot 7 أيام.
+ تسليم Proof Pack بعلامة الوكالة.
+ تحويل العميل لـ Growth OS الشهري + revenue share.
+
+
+
+
+ لماذا Dealix بدلاً من البناء داخلياً؟
+
+ Saudi-first — رسائل عربية طبيعية.
+ Approval-first — تحمي سمعة عملائك.
+ PDPL-aware — لا cold WhatsApp.
+ Multi-channel — واتساب + إيميل + تقويم + Sheets.
+ Proof Pack شهري قابل للتسليم.
+ Self-improving — Curator يحسّن الرسائل أسبوعياً.
+
+
+
+
+
+
+
diff --git a/dealix/landing/first-10-opportunities.html b/dealix/landing/first-10-opportunities.html
new file mode 100644
index 00000000..a9a331f5
--- /dev/null
+++ b/dealix/landing/first-10-opportunities.html
@@ -0,0 +1,98 @@
+
+
+
+
+
+Dealix — 10 فرص في 10 دقائق
+
+
+
+
+
+
+
+ ما الذي ستحصل عليه
+
+ 10 فرص B2B مرتبة حسب fit_score
+ سبب "لماذا الآن" لكل فرصة (إشارات شراء حقيقية)
+ صانع القرار المحتمل (founder/head of sales/etc.)
+ أفضل قناة (email/LinkedIn Lead Form/شريك)
+ 10 رسائل عربية بنبرة طبيعية سعودية
+ contactability gate (safe / needs_review / blocked)
+ خطة متابعة 7 أيام
+ Proof Pack تفصيلي بعد الأسبوع
+
+
+
+
+ الأسعار
+
+
+
Pilot 7 أيام
+
499 ريال
+
أو مجاني مقابل case study
+
+
+
Paid Pilot 30 يوم
+
1,500 ريال
+
إعداد موسّع + 3 جولات
+
+
+
Growth OS شهري
+
2,999 ريال
+
المنصة الكاملة شهرياً
+
+
+
+
+
+ الأمان
+
+ Approval-first — لا إرسال بدون موافقتك.
+ لا cold WhatsApp (PDPL-aware).
+ لا scraping ولا auto-DM على LinkedIn.
+ لا charge بدون تأكيد.
+ لا وعود بنتائج مضمونة.
+
+
+
+
+
+
+
diff --git a/dealix/landing/free-diagnostic.html b/dealix/landing/free-diagnostic.html
new file mode 100644
index 00000000..781f538f
--- /dev/null
+++ b/dealix/landing/free-diagnostic.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+Dealix — تشخيص نمو مجاني
+
+
+
+
+
+
+
+ ماذا تستلم؟
+
+ 3 فرص B2B مناسبة لقطاعك ومدينتك
+ سبب "لماذا الآن" لكل فرصة
+ رسالة عربية مخصصة (≤80 كلمة، نبرة سعودية طبيعية)
+ تقرير مخاطر (PDPL + سمعة القناة)
+ خطة Pilot 7 أيام مقترحة
+
+
+
+
+ كيف نعمل؟
+
+ تملأ نموذج 3 دقائق (قطاع/مدينة/عرض/هدف)
+ Dealix يولّد التشخيص خلال 24 ساعة عمل
+ تستلمه على إيميلك (PDF + JSON)
+ إذا أعجبك، نكمل Pilot 7 أيام بـ 499 ريال
+ أو مجاني مقابل case study بعد انتهاء الـPilot
+
+
+
+
+ ضمانات Dealix:
+ Approval-first — لا نرسل أي شيء قبل موافقتك.
+ لا cold WhatsApp. لا scraping. لا وعود بنتائج مضمونة.
+ PDPL-aware من اليوم الأول.
+
+
+
+
+
+
diff --git a/dealix/landing/services.html b/dealix/landing/services.html
new file mode 100644
index 00000000..fff44286
--- /dev/null
+++ b/dealix/landing/services.html
@@ -0,0 +1,156 @@
+
+
+
+
+
+Dealix — الخدمات
+
+
+
+
+
+
+
+
+
أريد عملاء جدد
+
+ First 10 Opportunities Sprint
+ Targeting OS
+ LinkedIn Lead Gen Setup
+ Email Outreach Drafts
+ Meeting Booking Sprint
+ Growth OS
+
+
+
+
عندي بيانات وأبغى أستفيد منها
+
+ List Intelligence
+ WhatsApp Compliance Setup
+ Email Revenue Rescue
+ Customer Reactivation
+
+
+
+
أبغى توسع واستراتيجية
+
+ Partner Sprint
+ Agency Partner Program
+ Executive Growth Brief
+ Self-Growth Operator
+
+
+
+
+ 12 خدمة productized
+
+
+
Free Growth Diagnostic
+
مجاني
+
3 فرص + رسالة + مخاطر + خطة Pilot — خلال 24 ساعة
+
+
+
List Intelligence
+
499–1,500 ريال
+
تنظيف + تصنيف + أفضل 50 + رسائل عربية
+
+
+
First 10 Opportunities Sprint
+
499–1,500 ريال
+
10 فرص + رسائل + متابعة 7 أيام + Proof Pack
+
+
+
Self-Growth Operator
+
999 ريال شهرياً
+
Daily brief + drafts + متابعة + reports
+
+
+
Growth OS
+
2,999 ريال شهرياً
+
المنصة الكاملة: قنوات + autopilot + proof
+
+
+
Email Revenue Rescue
+
1,500–5,000 ريال
+
استخراج فرص ضائعة من Gmail + drafts
+
+
+
Meeting Booking Sprint
+
1,500–5,000 ريال
+
دعوات + briefs + follow-ups
+
+
+
Partner Sprint
+
3,000–7,500 ريال
+
20 شريك محتمل + 10 رسائل + 5 اجتماعات
+
+
+
Agency Partner Program
+
10,000–50,000 ريال
+
بيع Dealix لعملاء الوكالة + revenue share
+
+
+
WhatsApp Compliance Setup
+
1,500–4,000 ريال
+
audit + opt-in templates + ledger
+
+
+
LinkedIn Lead Gen Setup
+
2,000–7,500 ريال
+
حملة Lead Form + audiences + ربط CRM
+
+
+
Executive Growth Brief
+
499–999 ريال شهرياً
+
3 قرارات + 3 فرص + 3 مخاطر يومياً
+
+
+
+
+
+
+ Dealix — Service Tower · 2026 · bassam.m.assiri@gmail.com
+
+
diff --git a/dealix/tests/unit/test_service_excellence.py b/dealix/tests/unit/test_service_excellence.py
new file mode 100644
index 00000000..1f10a5e9
--- /dev/null
+++ b/dealix/tests/unit/test_service_excellence.py
@@ -0,0 +1,238 @@
+"""Unit tests for Service Excellence OS."""
+
+from __future__ import annotations
+
+from auto_client_acquisition.service_excellence import (
+ build_backlog,
+ build_demo_script,
+ build_feature_matrix,
+ build_landing_page_outline,
+ build_monthly_service_review,
+ build_onboarding_checklist,
+ build_proof_pack_template_excellence,
+ build_sales_script,
+ build_service_launch_package,
+ build_service_research_brief,
+ calculate_service_excellence_score,
+ calculate_service_roi_estimate,
+ classify_features,
+ compare_against_categories,
+ convert_feedback_to_backlog,
+ generate_feature_hypotheses,
+ prioritize_backlog_items,
+ recommend_missing_features,
+ recommend_next_experiments,
+ recommend_weekly_improvements,
+ required_proof_metrics,
+ review_service_before_launch,
+ score_clarity,
+ score_compliance,
+ score_proof,
+ summarize_proof_ar,
+)
+from auto_client_acquisition.service_excellence.quality_review import (
+ block_if_missing_proof,
+ block_if_unclear_pricing,
+ block_if_unsafe_channel,
+)
+from auto_client_acquisition.service_tower import ALL_SERVICES, get_service
+
+
+# ── Feature matrix ───────────────────────────────────────────
+def test_feature_matrix_has_must_have_features():
+ out = build_feature_matrix("growth_os_monthly")
+ assert len(out["must_have"]) >= 10
+
+
+def test_classify_features_returns_three_tiers():
+ out = classify_features("growth_os_monthly")
+ assert "must_have" in out
+ assert "advanced" in out
+ assert "premium" in out
+
+
+def test_recommend_missing_features_returns_list():
+ out = recommend_missing_features("first_10_opportunities_sprint")
+ assert isinstance(out, list)
+
+
+def test_unknown_service_feature_matrix_errors():
+ out = build_feature_matrix("totally_unknown")
+ assert "error" in out
+
+
+# ── Scoring ──────────────────────────────────────────────────
+def test_score_returns_status():
+ out = calculate_service_excellence_score("growth_os_monthly")
+ assert out["status"] in ("launch_ready", "beta_only", "needs_work")
+
+
+def test_score_clarity_for_complete_service():
+ s = get_service("first_10_opportunities_sprint")
+ score = score_clarity(s)
+ assert score >= 7
+
+
+def test_score_compliance_high_for_approval_first():
+ s = get_service("growth_os_monthly")
+ score = score_compliance(s)
+ assert score >= 8
+
+
+def test_score_proof_high_when_metrics_present():
+ s = get_service("growth_os_monthly")
+ score = score_proof(s)
+ assert score >= 6
+
+
+# ── Quality review ───────────────────────────────────────────
+def test_quality_review_returns_verdict():
+ out = review_service_before_launch("growth_os_monthly")
+ assert out["verdict"] in ("launch_ready", "beta_only", "needs_work",
+ "blocked_at_gate")
+
+
+def test_quality_review_all_services_no_blocks():
+ """Every catalogued service should pass the gates (it's our catalog)."""
+ for s in ALL_SERVICES:
+ out = review_service_before_launch(s.id)
+ assert out["verdict"] != "blocked_at_gate", f"{s.id} blocked at gate"
+
+
+def test_block_if_missing_proof():
+ out = block_if_missing_proof("growth_os_monthly")
+ assert out["blocked"] is False # all our services have proof metrics
+
+
+def test_block_if_unclear_pricing():
+ out = block_if_unclear_pricing("growth_os_monthly")
+ assert out["blocked"] is False
+
+
+def test_block_if_unsafe_channel():
+ out = block_if_unsafe_channel("growth_os_monthly")
+ assert out["blocked"] is False
+
+
+# ── Proof metrics ────────────────────────────────────────────
+def test_required_proof_metrics_present():
+ metrics = required_proof_metrics("growth_os_monthly")
+ assert len(metrics) >= 1
+
+
+def test_proof_pack_template_excellence():
+ out = build_proof_pack_template_excellence("growth_os_monthly")
+ assert out["signature_required"] is True
+
+
+def test_roi_estimate_returns_x_multiples():
+ out = calculate_service_roi_estimate(
+ "first_10_opportunities_sprint",
+ {"price_paid_sar": 1000, "pipeline_sar": 25000, "closed_won_sar": 5000},
+ )
+ assert out["roi_pipeline_x"] == 25.0
+ assert out["roi_closed_x"] == 5.0
+
+
+def test_summarize_proof_ar_arabic():
+ msg = summarize_proof_ar(
+ "first_10_opportunities_sprint",
+ {"price_paid_sar": 1000, "pipeline_sar": 18000, "closed_won_sar": 3000},
+ )
+ assert any("" <= ch <= "ۿ" for ch in msg)
+
+
+# ── Competitor gap ───────────────────────────────────────────
+def test_competitor_gap_lists_advantages():
+ out = compare_against_categories("growth_os_monthly")
+ assert out["dealix_advantages_ar"]
+ assert out["do_not_copy_ar"]
+
+
+def test_competitor_gap_unknown_service():
+ out = compare_against_categories("bogus")
+ assert "error" in out
+
+
+# ── Research lab ─────────────────────────────────────────────
+def test_research_brief_has_questions():
+ out = build_service_research_brief("growth_os_monthly")
+ assert len(out["questions_to_answer_ar"]) >= 5
+
+
+def test_feature_hypotheses_returned():
+ out = generate_feature_hypotheses("growth_os_monthly")
+ assert len(out) >= 3
+
+
+def test_recommend_next_experiments_max_three():
+ out = recommend_next_experiments("growth_os_monthly")
+ assert len(out["experiments"]) <= 3
+
+
+def test_monthly_review_includes_score():
+ out = build_monthly_service_review("growth_os_monthly")
+ assert "current_excellence_score" in out
+
+
+# ── Backlog ──────────────────────────────────────────────────
+def test_backlog_returns_skeleton():
+ out = build_backlog("growth_os_monthly")
+ assert out["service_id"] == "growth_os_monthly"
+ assert "items" in out
+
+
+def test_prioritize_backlog_items():
+ items = [
+ {"impact": "low", "effort": "high"},
+ {"impact": "high", "effort": "low"},
+ {"impact": "medium", "effort": "medium"},
+ ]
+ out = prioritize_backlog_items(items)
+ # high+low effort should be first
+ assert out[0]["impact"] == "high"
+
+
+def test_convert_feedback_to_backlog():
+ feedback = [
+ {"text": "العميل بطيء في الرد على الـ drafts", "sentiment": "negative"},
+ {"text": "الـ pricing واضح", "sentiment": "positive"},
+ ]
+ out = convert_feedback_to_backlog(feedback)
+ assert len(out) == 2
+
+
+def test_weekly_improvements_returned():
+ out = recommend_weekly_improvements("growth_os_monthly")
+ assert len(out["weekly_plan_ar"]) >= 1
+
+
+# ── Launch package ───────────────────────────────────────────
+def test_launch_package_complete():
+ out = build_service_launch_package("first_10_opportunities_sprint")
+ assert "landing" in out
+ assert "sales_script" in out
+ assert "demo_script" in out
+ assert "onboarding" in out
+
+
+def test_landing_outline_includes_safety():
+ out = build_landing_page_outline("growth_os_monthly")
+ assert any("Approval-first" in s or "approval" in s.lower()
+ for s in out["must_include_ar"])
+
+
+def test_sales_script_has_objection_handling():
+ out = build_sales_script("growth_os_monthly")
+ assert "price" in out["objection_handling_ar"]
+ assert "timing" in out["objection_handling_ar"]
+
+
+def test_demo_script_is_12_minutes():
+ out = build_demo_script("first_10_opportunities_sprint")
+ assert out["duration_minutes"] == 12
+
+
+def test_onboarding_blocks_live_send():
+ out = build_onboarding_checklist("growth_os_monthly")
+ assert out["live_send_allowed"] is False
diff --git a/dealix/tests/unit/test_service_tower.py b/dealix/tests/unit/test_service_tower.py
new file mode 100644
index 00000000..adf37010
--- /dev/null
+++ b/dealix/tests/unit/test_service_tower.py
@@ -0,0 +1,241 @@
+"""Unit tests for Service Tower."""
+
+from __future__ import annotations
+
+from auto_client_acquisition.service_tower import (
+ ALL_SERVICES,
+ build_ceo_daily_service_brief,
+ build_client_report_outline,
+ build_deliverables,
+ build_intake_questions,
+ build_internal_operator_checklist,
+ build_proof_pack_template,
+ build_risk_alert_card,
+ build_service_approval_card,
+ build_service_scorecard,
+ build_service_workflow,
+ build_upsell_message_ar,
+ calculate_monthly_offer,
+ calculate_setup_fee,
+ catalog_summary,
+ get_service,
+ list_all_services,
+ map_service_to_growth_mission,
+ map_service_to_subscription,
+ quote_service,
+ recommend_next_step,
+ recommend_plan_after_service,
+ recommend_service,
+ recommend_upgrade,
+ summarize_recommendation_ar,
+ summarize_scorecard_ar,
+ validate_service_inputs,
+)
+
+
+# ── Catalog ──────────────────────────────────────────────────
+def test_catalog_has_at_least_12_services():
+ out = list_all_services()
+ assert out["total"] >= 12
+
+
+def test_catalog_includes_critical_services():
+ ids = {s.id for s in ALL_SERVICES}
+ for required in (
+ "free_growth_diagnostic", "list_intelligence",
+ "first_10_opportunities_sprint", "self_growth_operator",
+ "growth_os_monthly", "email_revenue_rescue",
+ "meeting_booking_sprint", "partner_sprint",
+ "agency_partner_program", "whatsapp_compliance_setup",
+ "linkedin_lead_gen_setup", "executive_growth_brief",
+ ):
+ assert required in ids
+
+
+def test_every_service_has_pricing():
+ for s in ALL_SERVICES:
+ assert s.pricing_min_sar >= 0
+ assert s.pricing_max_sar >= s.pricing_min_sar
+
+
+def test_every_service_has_proof_metrics():
+ for s in ALL_SERVICES:
+ assert s.proof_metrics, f"{s.id} missing proof_metrics"
+
+
+def test_every_service_has_deliverables():
+ for s in ALL_SERVICES:
+ assert s.deliverables_ar, f"{s.id} missing deliverables"
+
+
+def test_every_service_has_approval_policy():
+ for s in ALL_SERVICES:
+ assert s.approval_policy
+
+
+def test_summary_aggregates_pricing_models():
+ s = catalog_summary()
+ assert s["total"] == len(ALL_SERVICES)
+ assert "by_pricing_model" in s
+ assert "free_growth_diagnostic" in s["free_offers"]
+
+
+# ── Wizard ───────────────────────────────────────────────────
+def test_wizard_recommends_partner_sprint_for_agency():
+ out = recommend_service(company_type="agency", goal="expand_partners")
+ assert out["recommended_service_id"] in ("partner_sprint",
+ "agency_partner_program")
+
+
+def test_wizard_recommends_list_intelligence_when_has_list():
+ out = recommend_service(company_type="b2b", has_contact_list=True)
+ assert out["recommended_service_id"] == "list_intelligence"
+
+
+def test_wizard_recommends_growth_os_for_monthly_budget():
+ out = recommend_service(company_type="b2b saas", budget_sar=3500)
+ assert out["recommended_service_id"] == "growth_os_monthly"
+
+
+def test_wizard_default_falls_back_to_kill_feature():
+ out = recommend_service(company_type="random", budget_sar=500)
+ assert out["recommended_service_id"] == "first_10_opportunities_sprint"
+
+
+def test_intake_questions_for_known_service():
+ out = build_intake_questions("first_10_opportunities_sprint")
+ assert len(out["questions"]) >= 5
+
+
+def test_intake_questions_unknown_service():
+ out = build_intake_questions("totally_made_up")
+ assert "error" in out
+
+
+def test_validate_service_inputs_missing_field():
+ out = validate_service_inputs("list_intelligence", {"sector": "training"})
+ assert out["valid"] is False
+
+
+def test_summarize_recommendation_arabic():
+ out = recommend_service(company_type="b2b saas", budget_sar=3500)
+ summary = summarize_recommendation_ar(out)
+ assert any("" <= ch <= "ۿ" for ch in summary)
+
+
+# ── Mission templates ────────────────────────────────────────
+def test_workflow_includes_approval():
+ w = build_service_workflow("first_10_opportunities_sprint")
+ step_ids = [s["step_id"] for s in w["workflow_steps"]]
+ assert "approval" in step_ids
+
+
+def test_workflow_links_to_growth_mission():
+ w = build_service_workflow("first_10_opportunities_sprint")
+ assert w["linked_growth_mission"] == "first_10_opportunities"
+
+
+def test_map_service_to_subscription():
+ sub = map_service_to_subscription("free_growth_diagnostic")
+ assert sub # always returns something
+
+
+# ── Pricing engine ───────────────────────────────────────────
+def test_quote_free_service_returns_zero():
+ q = quote_service("free_growth_diagnostic")
+ assert q.get("is_free") is True
+ assert q["estimated_min_sar"] == 0
+
+
+def test_quote_paid_service_scales_with_size():
+ q_small = quote_service("first_10_opportunities_sprint", company_size="small")
+ q_large = quote_service("first_10_opportunities_sprint", company_size="large")
+ assert q_large["estimated_max_sar"] > q_small["estimated_max_sar"]
+
+
+def test_quote_unknown_service_errors():
+ q = quote_service("bogus_service")
+ assert "error" in q
+
+
+def test_setup_fee_only_for_monthly():
+ fee_monthly = calculate_setup_fee("growth_os_monthly")
+ fee_sprint = calculate_setup_fee("first_10_opportunities_sprint")
+ assert fee_monthly["setup_fee_sar"] > 0
+ assert fee_sprint["setup_fee_sar"] == 0
+
+
+def test_monthly_offer_only_for_monthly_services():
+ out_m = calculate_monthly_offer("growth_os_monthly")
+ out_s = calculate_monthly_offer("first_10_opportunities_sprint")
+ assert out_m["is_monthly"] is True
+ assert out_s["is_monthly"] is False
+
+
+# ── Deliverables ─────────────────────────────────────────────
+def test_deliverables_returns_arabic_list():
+ out = build_deliverables("first_10_opportunities_sprint")
+ assert out["deliverables_ar"]
+
+
+def test_proof_pack_template_lists_metrics():
+ out = build_proof_pack_template("first_10_opportunities_sprint")
+ assert out["metrics_to_track"]
+
+
+def test_client_report_outline_includes_executive_summary():
+ out = build_client_report_outline("growth_os_monthly")
+ assert "ملخص تنفيذي (10 أسطر)" in out["sections_ar"]
+
+
+def test_operator_checklist_blocks_live_actions():
+ out = build_internal_operator_checklist("growth_os_monthly")
+ assert any("live" in s.lower() for s in out["do_not_do_ar"])
+
+
+# ── Scorecard ────────────────────────────────────────────────
+def test_scorecard_strong_outcome():
+ out = build_service_scorecard("first_10_opportunities_sprint", {
+ "drafts_approved": 5, "positive_replies": 3,
+ "meetings": 2, "pipeline_sar": 25000,
+ "risks_blocked": 4, "customer_satisfaction": 9,
+ })
+ assert out["score"] >= 50
+
+
+def test_scorecard_summarize_arabic():
+ out = build_service_scorecard("first_10_opportunities_sprint",
+ {"meetings": 3, "pipeline_sar": 30000})
+ summary = summarize_scorecard_ar(out)
+ assert any("" <= ch <= "ۿ" for ch in summary)
+
+
+# ── CEO control ──────────────────────────────────────────────
+def test_ceo_daily_brief_buttons_capped_at_three():
+ out = build_ceo_daily_service_brief()
+ assert len(out["buttons_ar"]) <= 3
+
+
+def test_approval_card_blocks_live_send():
+ out = build_service_approval_card("first_10_opportunities_sprint",
+ "send_email")
+ assert out["live_send_allowed"] is False
+ assert len(out["buttons_ar"]) <= 3
+
+
+def test_risk_alert_card_marks_high_risk():
+ out = build_risk_alert_card()
+ assert out["risk_level"] == "high"
+
+
+# ── Upgrade paths ────────────────────────────────────────────
+def test_upgrade_recommends_next_service():
+ out = recommend_upgrade("first_10_opportunities_sprint")
+ assert out["recommended_service_id"] in ("growth_os_monthly",
+ "self_growth_operator")
+
+
+def test_upsell_message_arabic():
+ msg = build_upsell_message_ar("first_10_opportunities_sprint",
+ "growth_os_monthly")
+ assert any("" <= ch <= "ۿ" for ch in msg)
diff --git a/dealix/tests/unit/test_targeting_os.py b/dealix/tests/unit/test_targeting_os.py
new file mode 100644
index 00000000..7decbe29
--- /dev/null
+++ b/dealix/tests/unit/test_targeting_os.py
@@ -0,0 +1,329 @@
+"""Unit tests for Targeting & Acquisition OS."""
+
+from __future__ import annotations
+
+from auto_client_acquisition.targeting_os import (
+ ALL_BUYER_ROLES,
+ ALL_SOURCES,
+ allowed_action_modes,
+ analyze_uploaded_list_preview,
+ build_acquisition_scorecard,
+ build_dealix_self_growth_plan,
+ build_followup_sequence,
+ build_free_growth_diagnostic,
+ build_lead_gen_form_plan,
+ build_outreach_plan,
+ build_self_growth_daily_brief,
+ calculate_channel_reputation,
+ classify_source,
+ draft_b2b_email,
+ draft_role_based_angle,
+ draft_whatsapp_message,
+ enforce_daily_limits,
+ evaluate_contactability,
+ explain_contactability_ar,
+ list_targeting_services,
+ map_buying_committee,
+ recommend_accounts,
+ recommend_dealix_targets,
+ recommend_linkedin_strategy,
+ recommend_recovery_action,
+ recommend_service_offer,
+ score_email_risk,
+ score_whatsapp_risk,
+ should_pause_channel,
+)
+from auto_client_acquisition.targeting_os.contract_drafts import (
+ draft_dpa_outline,
+ draft_pilot_agreement_outline,
+)
+from auto_client_acquisition.targeting_os.linkedin_strategy import linkedin_do_not_do
+
+
+# ── Account finder ───────────────────────────────────────────
+def test_recommend_accounts_returns_arabic_targets():
+ out = recommend_accounts(sector="training", city="Riyadh", limit=5)
+ assert out["total"] == 5
+ for a in out["accounts"]:
+ assert "fit_score" in a
+ assert "why_now_ar" in a
+ assert any("" <= ch <= "ۿ" for ch in a["why_now_ar"])
+
+
+def test_recommend_accounts_blocks_unsafe_sources():
+ out = recommend_accounts(sector="saas", city="Riyadh", limit=2)
+ for a in out["accounts"]:
+ assert "scraped_email" not in a["primary_sources"]
+ assert "scraped_phone" not in a["primary_sources"]
+
+
+# ── Buyer role mapper ────────────────────────────────────────
+def test_buying_committee_for_training_includes_dm():
+ out = map_buying_committee(sector="training", company_size="small")
+ assert "primary_decision_maker" in out
+ assert out["primary_decision_maker"]["role_key"] in ALL_BUYER_ROLES
+
+
+def test_buying_committee_unknown_sector_falls_back():
+ out = map_buying_committee(sector="bogus_xyz")
+ assert out["primary_decision_maker"]["role_key"] in ALL_BUYER_ROLES
+
+
+def test_role_based_angle_returns_arabic():
+ out = draft_role_based_angle("head_of_sales", sector="saas",
+ offer="Pilot 7 أيام")
+ assert any("" <= ch <= "ۿ" for ch in out["angle_ar"])
+
+
+# ── Contact source policy ────────────────────────────────────
+def test_classify_known_source():
+ assert classify_source("crm_customer")["source"] == "crm_customer"
+
+
+def test_classify_unknown_source():
+ assert classify_source("totally_made_up")["source"] == "unknown_source"
+
+
+def test_all_sources_include_critical():
+ for s in ("crm_customer", "linkedin_lead_form", "cold_list", "opt_out"):
+ assert s in ALL_SOURCES
+
+
+# ── Contactability matrix ────────────────────────────────────
+def test_opt_out_contact_blocked():
+ contact = {"source": "opt_out", "opt_out": True}
+ out = evaluate_contactability(contact, desired_channel="whatsapp")
+ assert out["status"] == "blocked"
+ assert "opt_out" in out["reason_codes"]
+
+
+def test_cold_whatsapp_blocked():
+ contact = {"source": "cold_list", "opt_in_status": "no"}
+ out = evaluate_contactability(contact, desired_channel="whatsapp")
+ assert out["status"] == "blocked"
+
+
+def test_inbound_lead_email_safe():
+ contact = {"source": "inbound_lead", "opt_in_status": "yes"}
+ out = evaluate_contactability(contact, desired_channel="email")
+ assert out["status"] == "safe"
+
+
+def test_unknown_source_needs_review():
+ contact = {"source": "unknown_source"}
+ out = evaluate_contactability(contact)
+ assert out["status"] in ("needs_review", "safe")
+
+
+def test_explain_contactability_returns_arabic():
+ contact = {"source": "cold_list"}
+ out = evaluate_contactability(contact, desired_channel="whatsapp")
+ text = explain_contactability_ar(out)
+ assert "محظور" in text
+
+
+def test_allowed_action_modes_includes_blocked_only_for_blocked():
+ blocked_result = {"status": "blocked"}
+ assert allowed_action_modes(blocked_result) == ["blocked"]
+
+
+# ── LinkedIn strategy ────────────────────────────────────────
+def test_linkedin_strategy_never_recommends_scraping():
+ out = recommend_linkedin_strategy("B2B SaaS")
+ assert "scrape_profiles" in out["do_not_do"]
+ assert "auto_dm" in out["do_not_do"]
+ assert out["primary"] == "lead_gen_forms"
+
+
+def test_linkedin_do_not_do_lock_list():
+ nope = linkedin_do_not_do()
+ for required in ("scrape_profiles", "auto_dm", "auto_connect",
+ "browser_automation"):
+ assert required in nope
+
+
+def test_lead_gen_form_plan_has_hidden_fields():
+ plan = build_lead_gen_form_plan("training", "Pilot 7 أيام")
+ field_names = [f["name"] for f in plan["hidden_fields"]]
+ assert "campaign_name" in field_names
+ assert "sector" in field_names
+
+
+# ── Email strategy ───────────────────────────────────────────
+def test_email_draft_includes_unsubscribe():
+ contact = {"name": "أحمد", "company": "X"}
+ out = draft_b2b_email(contact, offer="Pilot 7 أيام")
+ assert "إلغاء" in out["body_ar"] or "STOP" in out["body_ar"]
+ assert out["live_send_allowed"] is False
+
+
+def test_email_risk_blocks_cold_list():
+ contact = {"source": "cold_list", "opt_in_status": "no"}
+ out = score_email_risk(contact, "ضمان 100% نتائج مضمونة")
+ assert out["verdict"] in ("blocked", "needs_review")
+
+
+def test_email_followup_has_three_steps():
+ out = build_followup_sequence({"name": "أحمد"})
+ assert len(out["steps"]) == 3
+ assert out["live_send_allowed"] is False
+
+
+# ── WhatsApp strategy ────────────────────────────────────────
+def test_whatsapp_cold_blocked():
+ contact = {"source": "cold_list", "opt_in_status": "no"}
+ out = draft_whatsapp_message(contact)
+ assert out["live_send_allowed"] is False
+ assert out["risk"]["verdict"] in ("blocked", "needs_review")
+
+
+def test_whatsapp_risk_blocks_risky_phrase():
+ contact = {"source": "inbound_lead", "opt_in_status": "yes"}
+ out = score_whatsapp_risk(contact, "ضمان 100% نتائج مضمونة آخر فرصة")
+ assert out["risk"] >= 25
+
+
+# ── Outreach scheduler ───────────────────────────────────────
+def test_outreach_plan_generates_steps():
+ targets = [{"name": "Acme", "role": "CEO"}, {"name": "Beta", "role": "Sales"}]
+ out = build_outreach_plan(targets, channels=["email", "linkedin"])
+ assert out["total_targets"] == 2
+ for t in out["plan"]:
+ for step in t["steps"]:
+ assert step["live_send_allowed"] is False
+
+
+def test_enforce_daily_limits_caps_emails():
+ targets = [{"name": f"co_{i}"} for i in range(50)]
+ plan = build_outreach_plan(targets, channels=["email"])
+ capped = enforce_daily_limits(plan, limits={"max_daily_email_drafts": 5,
+ "max_same_domain_contacts": 99,
+ "max_followups": 3,
+ "cooldown_days": 7,
+ "max_daily_whatsapp_approved_sends": 5})
+ # Across all targets, emails total should not exceed 5
+ total_emails = sum(
+ sum(1 for s in t["steps"] if s["channel"] == "email")
+ for t in capped["plan"]
+ )
+ assert total_emails <= 5
+
+
+# ── Reputation guard ─────────────────────────────────────────
+def test_reputation_pauses_high_bounce():
+ """Multiple critical metrics together should trigger pause."""
+ metrics = {"bounce_rate": 0.15, "complaint_rate": 0.005,
+ "opt_out_rate": 0.15, "reply_rate": 0.005}
+ out = should_pause_channel(metrics, channel="email")
+ assert out["should_pause"] is True
+
+
+def test_reputation_recommends_recovery_actions():
+ metrics = {"bounce_rate": 0.10, "complaint_rate": 0.005,
+ "opt_out_rate": 0.12, "reply_rate": 0.01}
+ out = recommend_recovery_action(metrics, channel="email")
+ assert out["actions_ar"]
+
+
+def test_reputation_healthy_email():
+ metrics = {"bounce_rate": 0.005, "complaint_rate": 0.0001,
+ "opt_out_rate": 0.01, "reply_rate": 0.05}
+ rep = calculate_channel_reputation(metrics, channel="email")
+ assert rep["verdict"] == "healthy"
+
+
+# ── Daily autopilot ──────────────────────────────────────────
+def test_today_actions_returned():
+ from auto_client_acquisition.targeting_os import recommend_today_actions
+ out = recommend_today_actions()
+ assert len(out) >= 5
+ for a in out:
+ assert "label_ar" in a
+
+
+# ── Self-growth mode ─────────────────────────────────────────
+def test_self_growth_targets_list():
+ out = recommend_dealix_targets(limit=5)
+ assert out["live_send_allowed"] is False
+ assert out["targets"]["total"] >= 5
+
+
+def test_self_growth_daily_brief_has_ten_targets():
+ out = build_self_growth_daily_brief()
+ assert len(out["top_10_targets"]) >= 5
+
+
+def test_self_growth_plan_has_monthly_targets():
+ out = build_dealix_self_growth_plan()
+ assert "monthly_targets" in out
+
+
+# ── Free diagnostic ──────────────────────────────────────────
+def test_free_diagnostic_returns_three_opportunities():
+ out = build_free_growth_diagnostic({"sector": "training", "city": "Riyadh"})
+ assert out["sections"]["opportunities_ar"]
+ assert len(out["sections"]["opportunities_ar"]) == 3
+
+
+def test_uploaded_list_preview_classifies():
+ contacts = [
+ {"source": "crm_customer", "opt_in_status": "yes"},
+ {"source": "cold_list", "opt_in_status": "no"},
+ {"source": "unknown_source"},
+ ]
+ out = analyze_uploaded_list_preview(contacts)
+ assert out["total"] == 3
+ assert out["preview"]
+
+
+# ── Service offers ──────────────────────────────────────────
+def test_service_offers_includes_free_diagnostic():
+ offers = list_targeting_services()
+ ids = {o["id"] for o in offers["offers"]}
+ assert "free_growth_diagnostic" in ids
+ assert "first_10_opportunities_sprint" in ids
+
+
+def test_recommend_offer_for_agency():
+ rec = recommend_service_offer("agency partner growth")
+ assert rec["recommended_offer"]["id"] == "partner_sprint"
+
+
+# ── Contracts ────────────────────────────────────────────────
+def test_pilot_contract_requires_legal_review():
+ out = draft_pilot_agreement_outline()
+ assert out["legal_review_required"] is True
+ assert out["not_legal_advice"] is True
+
+
+def test_dpa_includes_pdpl():
+ out = draft_dpa_outline()
+ assert any("PDPL" in s for s in out["sections_ar"])
+
+
+# ── Acquisition scorecard ───────────────────────────────────
+def test_scorecard_aggregates_pipeline():
+ out = build_acquisition_scorecard({
+ "accounts_researched": 50,
+ "decision_makers_mapped": 25,
+ "drafts_created": 20,
+ "approvals_received": 10,
+ "positive_replies": 5,
+ "opportunities": [{"expected_value_sar": 18000},
+ {"expected_value_sar": 12000}],
+ "events": [{"status": "drafted"}, {"status": "confirmed"}],
+ "actions": [{"status": "blocked", "block_reason": "cold_whatsapp"}],
+ })
+ assert out["pipeline"]["pipeline_sar"] == 30000
+ assert out["meetings"]["total"] == 2
+ assert out["risks_blocked"]["total"] == 1
+
+
+def test_productivity_score_strong_with_meetings():
+ from auto_client_acquisition.targeting_os import calculate_productivity_score
+ out = calculate_productivity_score({
+ "accounts_researched": 30, "drafts_created": 10,
+ "approvals_received": 5, "positive_replies": 4,
+ "meetings_booked": 3,
+ })
+ assert out["score"] >= 50