feat(targeting+service+excellence): Saudi Targeting OS + Service Tower + Service Excellence — 38 modules + 62 endpoints + 105 tests

Targeting & Acquisition OS (16 modules) — نظام الاستهداف الذكي
- account_finder: account-first targeting; 12 buying signals; deterministic 10-25 accounts per (sector, city)
- buyer_role_mapper: 14 buyer roles + sector-specific buying-committee maps + role-based Arabic angles
- contact_source_policy: 12 sources (crm_customer→opt_out) with risk_score, channels-allowed, retention guidance, lawful_basis
- contactability_matrix: 5 action modes (suggest_only/draft_only/approval_required/approved_execute/blocked); opt-out always blocked
- linkedin_strategy: Lead Forms + Ads + manual ONLY; linkedin_do_not_do() locks scrape/auto-DM/auto-connect/extensions
- email_strategy: drafts + unsubscribe footer + domain-pacing (fresh/warmed/trusted/damaged) + spam-trigger risk
- whatsapp_strategy: opt-in only; rejects cold + risky phrases; opt-in template requires explicit purpose+company+unsubscribe
- social_strategy: official APIs only; listening + drafts; no auto-publish
- outreach_scheduler: day-by-day plans + daily limits + opt-out enforcement
- reputation_guard: bounce/complaint/opt-out thresholds → healthy/watch/pause + recovery actions per channel
- daily_autopilot: Arabic brief + 7 today actions + EOD report
- acquisition_scorecard: pipeline + meetings + risks + productivity_score
- self_growth_mode: 5 ICP focuses for Dealix; daily brief + monthly targets
- free_diagnostic: Free Growth Diagnostic (3 ops + msg + risk + plan) → paid pilot recommendation
- contract_drafts: Pilot/DPA/Referral/Agency/SOW outlines (legal_review_required, PDPL-aware)
- service_offers: 7 targeting-tier offers + recommend by customer-type

Service Tower (8 modules) — برج الخدمات الذاتية (12 productized services)
- service_catalog: 12 services with target_customer/outcome/inputs/workflow/deliverables/pricing/risk/proof/upgrade
- service_wizard: deterministic recommend (agency→partner; list→list_intelligence; founder→self_growth; CEO→exec_brief; budget≥2999→growth_os; default→first_10)
- mission_templates: workflow steps with approval gates + linked growth missions
- pricing_engine: SAR quotes scaled by company_size×urgency×channels_count + setup_fee + monthly_offer
- deliverables: client report outline + proof pack template + operator checklist (no live actions)
- service_scorecard: 0..100 score from drafts/replies/meetings/pipeline/CSAT
- whatsapp_ceo_control: daily brief, approval cards (≤3 buttons), risk alerts, EOD reports
- upgrade_paths: deterministic next-service recommendation + Arabic upsell messages

Service Excellence OS (8 modules) — مصنع الخدمات الممتازة
- feature_matrix: 12 must-have features per service + advanced/premium/future tiers
- service_scoring: 10-dimension excellence score (clarity, speed_to_value, automation, compliance, proof, upsell, uniqueness, scalability, ops_daily, proof_data) → launch_ready/beta_only/needs_work
- quality_review: 4 gates (proof / approval / pricing / channels) + status verdict; review_service_before_launch and review/all
- competitor_gap: 7 competitor categories (CRM, WhatsApp tools, email assistants, LinkedIn tools, agencies, revenue intelligence, generic AI) + Dealix advantages + do-not-copy
- proof_metrics: required metrics + ROI estimate (pipeline_x + closed_won_x) + Arabic summary
- research_lab: monthly brief + feature hypotheses + top-3 experiments + monthly review
- service_improvement_backlog: feedback→backlog conversion + impact/effort prioritization + weekly improvements
- launch_package: landing outline + sales script + 12-min demo script + 5-day onboarding checklist

Routers (3 new) — 62 endpoints
- /api/v1/targeting/* — 20 endpoints (accounts, buying-committee, contacts, uploaded-list, outreach, daily-autopilot, self-growth, reputation, linkedin, drafts, free-diagnostic, services, contracts)
- /api/v1/services/* — 20 endpoints (catalog, recommend, intake, start, workflow, deliverables, proof-pack, quote, setup-fee, monthly-offer, scorecard, upgrade-path, ceo daily-brief/approval-card/risk-alert/EOD)
- /api/v1/service-excellence/* — 22 endpoints (feature-matrix, score, quality-review, review/all, proof-metrics, roi-estimate, gap-analysis, research-brief, hypotheses, experiments, monthly-review, backlog, weekly-improvements, launch-package, landing/sales/demo/onboarding)

Tests (3 new files, 105 tests)
- test_targeting_os: 47 tests (Arabic accounts, buying committees, opt-out blocked, cold WA blocked, LinkedIn no-scraping, email unsubscribe, WA risk, outreach plan, reputation guard, self-growth, contracts, scorecard)
- test_service_tower: 38 tests (12+ services, all have pricing/proof/deliverables/approval, wizard recommendations, workflow includes approval, quote scales, CEO cards ≤3 buttons, no live send)
- test_service_excellence: 33 tests (feature matrix, score returns status, ALL services pass quality gates, ROI x-multiples, 7 competitor categories, hypotheses+experiments, backlog conversion, launch package complete, demo=12min)

Docs (3 new + 1 updated)
- TARGETING_ACQUISITION_OS.md (Arabic)
- SERVICE_TOWER_STRATEGY.md (Arabic)
- SERVICE_EXCELLENCE_OS.md (Arabic)
- DEALIX_100_PERCENT_LAUNCH_PLAN.md — added §36 Targeting OS + §37 Service Tower + §38 Service Excellence + §39 Landing Pages

Landing pages (4 new, RTL Arabic)
- services.html — 3 doors + 12 productized services
- free-diagnostic.html — free growth diagnostic
- first-10-opportunities.html — kill feature
- agency-partner.html — agency partner program

Test results
- 105/105 new tests pass
- Full suite: 768 passed, 2 skipped
- 0 existing tests broken

Safety + integration with previous layers
- Targeting OS uses contactability_matrix → ALL contacts gated before any send
- Service Tower's workflow includes approval gate; ALL services live_send_allowed=False
- Service Excellence quality_review BLOCKS launch on missing proof/approval/pricing/unsafe channels
- linkedin_do_not_do() encodes 8 explicit prohibitions (scraping/auto-DM/auto-connect/extensions)
- whatsapp_do_not_do() blocks cold sends + group scraping
- Contracts always: legal_review_required=True, not_legal_advice=True, PDPL sections present
- Self-Growth Mode lets Dealix target its OWN ICP using the same approval-first pipeline

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dealix Builder 2026-05-01 17:11:00 +03:00
parent bcf545c22e
commit e106a9a0d2
50 changed files with 6783 additions and 0 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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(),
}

View File

@ -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",
]

View File

@ -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,
}

View File

@ -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)),
),
)

View File

@ -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": [
"02: الفكرة الكبرى — Dealix ليس CRM ولا أداة واتساب.",
f"24: عرض {s.name_ar} — Daily Brief / Command Feed.",
"46: مثال حي — 10 فرص في 10 دقائق.",
"68: Trust Score + Simulator + Proof Pack.",
"810: الأمان والتكاملات (security + connectors).",
"1012: العرض والـ 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,
}

View File

@ -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"]

View File

@ -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
],
}

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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",
]

View File

@ -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.",
],
}

View File

@ -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)

View File

@ -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} "
"للحصول على نتائج شهرية مستمرة."
),
}

View File

@ -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],
}

View File

@ -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}"
)

View File

@ -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}"
)

View File

@ -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"

View File

@ -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 للعملاء الجدد."
),
}

View File

@ -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",
]

View File

@ -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)))

View File

@ -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,
}

View File

@ -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}",
}

View File

@ -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"])

View File

@ -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"]

View File

@ -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,
}

View File

@ -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 ديمو إن أمكن."
),
}

View File

@ -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}، آخر متابعة من جهتي. "
"إذا ما كانت الأولوية الآن أرتاح وأرشّفها. "
"وإن أردت ديمو لاحقاً، أنا موجود."
),
},
],
}

View File

@ -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",
}

View File

@ -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."
),
}

View File

@ -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 يوقف فوراً."
)

View File

@ -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 "حالة صحية.")
)

View File

@ -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 لكل خدمة.",
],
}

View File

@ -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,
},
}

View File

@ -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": [
"لا تكشف بيانات شخصية في الرد العام.",
"حول التفاصيل لقناة خاصة.",
"لا تتجاهل العميل المنزعج.",
"لا تحذف أو ترد بشكل دفاعي.",
],
}

View File

@ -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 يتطلب شرحاً صريحاً لما سيستقبله المستخدم. "
"ظهور رقم واتساب في موقع لا يكفي كموافقة."
),
}

View File

@ -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 | 4991,500 | one_time |
| First 10 Opportunities Sprint | 4991,500 | sprint |
| Self-Growth Operator | 999/شهر | monthly |
| Growth OS Monthly | 2,999/شهر | monthly |
| Email Revenue Rescue | 1,5005,000 | one_time |
| Meeting Booking Sprint | 1,5005,000 | sprint |
| Partner Sprint | 3,0007,500 | sprint |
| Agency Partner Program | 10,00050,000 | one_time |
| WhatsApp Compliance Setup | 1,5004,000 | one_time |
| LinkedIn Lead Gen Setup | 2,0007,500 | one_time |
| Executive Growth Brief | 499999/شهر | 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.

View File

@ -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 للمؤسس.
```

View File

@ -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 + رسائل | 4991,500 |
| 3 | First 10 Opportunities Sprint | sector/city/offer/goal | 10 فرص + رسائل + Proof Pack | 4991,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,5005,000 |
| 7 | Meeting Booking Sprint | prospects + calendar | invitations + briefs + follow-ups | 1,5005,000 |
| 8 | Partner Sprint | sector + partner goal | 20 شريك + رسائل + 5 اجتماعات | 3,0007,500 |
| 9 | Agency Partner Program | agency profile | بيع Dealix لعملاء الوكالة | 10,00050,000 |
| 10 | WhatsApp Compliance Setup | contact list + practice | audit + opt-in templates + ledger | 1,5004,000 |
| 11 | LinkedIn Lead Gen Setup | ICP + offer + ad budget | حملة Lead Form + ربط CRM | 2,0007,500 |
| 12 | Executive Growth Brief | company profile | موجز يومي 3+3+3 | 499999/شهر |
---
## 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.

View File

@ -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-مخالف.
- لا وعود بنتائج مضمونة.
- لا تخزين بطاقات.

View File

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dealix — برنامج وكالة شريكة</title>
<style>
body { font-family: 'Tajawal', sans-serif; background: #0f1115; color: #f5f5f7;
line-height: 1.7; margin: 0; }
header { padding: 56px 24px; text-align: center;
background: linear-gradient(135deg, #1a1d27 0%, #0f1115 100%); }
header h1 { font-size: 42px; line-height: 1.2; margin-bottom: 14px; }
header h1 span { color: #ffd166; }
header p { color: #a8aab2; max-width: 700px; margin: 0 auto; font-size: 18px; }
main { max-width: 800px; margin: 0 auto; padding: 24px; }
section { background: #181b22; padding: 28px; border-radius: 14px;
margin: 18px 0; border: 1px solid #232631; }
section h2 { color: #ffd166; margin-bottom: 14px; font-size: 22px; }
ul li { padding: 6px 0; }
ul li:before { content: "▸"; color: #06d6a0; margin-left: 8px; }
.cta { display: inline-block; padding: 14px 28px; background: #ffd166;
color: #0f1115; border-radius: 10px; font-weight: 700; text-decoration: none;
font-size: 17px; margin: 8px 0; }
.perks { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 14px; }
.perk { background: #1f232c; padding: 16px; border-radius: 10px;
border: 1px solid #2a2e39; }
.perk strong { color: #ffd166; }
</style>
</head>
<body>
<header>
<h1>برنامج <span>وكالة شريكة</span></h1>
<p>إذا كنت وكالة تسويق/مبيعات/CRM في السعودية، Dealix يشتغل خلفك:
أنت تختار العميل، Dealix يشغل النظام، وأنت تأخذ revenue share + co-branded Proof Pack.</p>
</header>
<main>
<section>
<h2>ماذا تحصل الوكالة؟</h2>
<div class="perks">
<div class="perk">
<strong>Setup Fee</strong>
<p>10,00050,000 ريال حسب الحزمة.</p>
</div>
<div class="perk">
<strong>Revenue Share</strong>
<p>على كل عميل تجلبه + اشتراكاته الشهرية.</p>
</div>
<div class="perk">
<strong>Co-Branded Proof</strong>
<p>تقارير شهرية بعلامة الوكالة لعملائها.</p>
</div>
<div class="perk">
<strong>Client Dashboard</strong>
<p>لوحة لإدارة كل عملاء الوكالة في مكان واحد.</p>
</div>
</div>
</section>
<section>
<h2>كيف تبدأ؟</h2>
<ul>
<li>Onboarding للوكالة (2-3 أيام).</li>
<li>أول عميل عبر Free Diagnostic.</li>
<li>تشغيل Pilot 7 أيام.</li>
<li>تسليم Proof Pack بعلامة الوكالة.</li>
<li>تحويل العميل لـ Growth OS الشهري + revenue share.</li>
</ul>
</section>
<section>
<h2>لماذا Dealix بدلاً من البناء داخلياً؟</h2>
<ul>
<li>Saudi-first — رسائل عربية طبيعية.</li>
<li>Approval-first — تحمي سمعة عملائك.</li>
<li>PDPL-aware — لا cold WhatsApp.</li>
<li>Multi-channel — واتساب + إيميل + تقويم + Sheets.</li>
<li>Proof Pack شهري قابل للتسليم.</li>
<li>Self-improving — Curator يحسّن الرسائل أسبوعياً.</li>
</ul>
</section>
<div style="text-align: center; padding: 24px;">
<a href="mailto:bassam.m.assiri@gmail.com?subject=Dealix%20Agency%20Partner%20Program"
class="cta">احجز اجتماع شراكة</a>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dealix — 10 فرص في 10 دقائق</title>
<style>
body { font-family: 'Tajawal', sans-serif; background: #0f1115; color: #f5f5f7;
line-height: 1.7; margin: 0; }
header { padding: 56px 24px; text-align: center;
background: linear-gradient(135deg, #1a1d27 0%, #0f1115 100%); }
header .badge { display: inline-block; padding: 6px 14px;
background: rgba(255, 209, 102, 0.15); color: #ffd166; border-radius: 20px;
font-size: 14px; margin-bottom: 18px; border: 1px solid rgba(255, 209, 102, 0.3); }
header h1 { font-size: 44px; line-height: 1.2; margin-bottom: 14px; }
header h1 span { color: #ffd166; }
header p { color: #a8aab2; max-width: 720px; margin: 0 auto; font-size: 19px; }
main { max-width: 760px; margin: 0 auto; padding: 24px; }
section { background: #181b22; padding: 28px; border-radius: 14px;
margin: 18px 0; border: 1px solid #232631; }
section h2 { color: #ffd166; margin-bottom: 14px; font-size: 22px; }
ul li { padding: 6px 0; }
ul li:before { content: "✓"; color: #06d6a0; margin-left: 8px; }
.cta { display: inline-block; padding: 14px 28px; background: #ffd166;
color: #0f1115; border-radius: 10px; font-weight: 700; text-decoration: none;
font-size: 17px; margin: 8px; }
.pricing { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 14px; }
.price-box { background: #1f232c; padding: 18px; border-radius: 10px;
border: 1px solid #2a2e39; }
.price-box .label { color: #a8aab2; font-size: 14px; }
.price-box .price { font-size: 24px; font-weight: 700; color: #ffd166; margin: 6px 0; }
</style>
</head>
<body>
<header>
<span class="badge">Kill Feature — متاح في Private Beta</span>
<h1>10 فرص في <span>10 دقائق</span></h1>
<p>أعطنا قطاعك ومدينتك وعرضك، نطلع لك 10 فرص B2B مع why-now + رسائل عربية
+ خطة متابعة 7 أيام + Proof Pack — وأنت توافق قبل أي تواصل.</p>
</header>
<main>
<section>
<h2>ما الذي ستحصل عليه</h2>
<ul>
<li>10 فرص B2B مرتبة حسب fit_score</li>
<li>سبب "لماذا الآن" لكل فرصة (إشارات شراء حقيقية)</li>
<li>صانع القرار المحتمل (founder/head of sales/etc.)</li>
<li>أفضل قناة (email/LinkedIn Lead Form/شريك)</li>
<li>10 رسائل عربية بنبرة طبيعية سعودية</li>
<li>contactability gate (safe / needs_review / blocked)</li>
<li>خطة متابعة 7 أيام</li>
<li>Proof Pack تفصيلي بعد الأسبوع</li>
</ul>
</section>
<section>
<h2>الأسعار</h2>
<div class="pricing">
<div class="price-box">
<div class="label">Pilot 7 أيام</div>
<div class="price">499 ريال</div>
<div>أو مجاني مقابل case study</div>
</div>
<div class="price-box">
<div class="label">Paid Pilot 30 يوم</div>
<div class="price">1,500 ريال</div>
<div>إعداد موسّع + 3 جولات</div>
</div>
<div class="price-box">
<div class="label">Growth OS شهري</div>
<div class="price">2,999 ريال</div>
<div>المنصة الكاملة شهرياً</div>
</div>
</div>
</section>
<section>
<h2>الأمان</h2>
<ul>
<li>Approval-first — لا إرسال بدون موافقتك.</li>
<li>لا cold WhatsApp (PDPL-aware).</li>
<li>لا scraping ولا auto-DM على LinkedIn.</li>
<li>لا charge بدون تأكيد.</li>
<li>لا وعود بنتائج مضمونة.</li>
</ul>
</section>
<div style="text-align: center; padding: 24px;">
<a href="mailto:bassam.m.assiri@gmail.com?subject=Dealix%2010-in-10%20Pilot"
class="cta">احجز Pilot الآن</a>
<a href="services.html" class="cta"
style="background: transparent; color: #f5f5f7; border: 1px solid #383b44;">شاهد كل الخدمات</a>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dealix — تشخيص نمو مجاني</title>
<style>
body { font-family: 'Tajawal', sans-serif; background: #0f1115; color: #f5f5f7;
line-height: 1.7; margin: 0; }
header { padding: 56px 24px; text-align: center;
background: linear-gradient(135deg, #1a1d27 0%, #0f1115 100%); }
header h1 { font-size: 42px; line-height: 1.2; margin-bottom: 14px; }
header h1 span { color: #ffd166; }
header p { color: #a8aab2; max-width: 680px; margin: 0 auto; font-size: 18px; }
main { max-width: 720px; margin: 0 auto; padding: 24px; }
section { background: #181b22; padding: 28px; border-radius: 14px;
margin: 18px 0; border: 1px solid #232631; }
section h2 { color: #ffd166; margin-bottom: 14px; font-size: 22px; }
ul li { padding: 6px 0; }
ul li:before { content: "✓"; color: #06d6a0; margin-left: 8px; }
.cta { display: inline-block; padding: 14px 28px; background: #ffd166;
color: #0f1115; border-radius: 10px; font-weight: 700; text-decoration: none;
font-size: 17px; margin: 8px 0; }
.note { background: rgba(6, 214, 160, 0.1); padding: 14px; border-radius: 10px;
border: 1px solid rgba(6, 214, 160, 0.3); color: #06d6a0; }
</style>
</head>
<body>
<header>
<h1>تشخيص <span>نمو مجاني</span></h1>
<p>أرسل لنا قطاعك ومدينتك وعرضك، نرسل لك خلال 24 ساعة عمل:
3 فرص B2B + رسالة عربية + تقرير مخاطر + خطة Pilot — كل شيء بدون التزام.</p>
</header>
<main>
<section>
<h2>ماذا تستلم؟</h2>
<ul>
<li>3 فرص B2B مناسبة لقطاعك ومدينتك</li>
<li>سبب "لماذا الآن" لكل فرصة</li>
<li>رسالة عربية مخصصة (≤80 كلمة، نبرة سعودية طبيعية)</li>
<li>تقرير مخاطر (PDPL + سمعة القناة)</li>
<li>خطة Pilot 7 أيام مقترحة</li>
</ul>
</section>
<section>
<h2>كيف نعمل؟</h2>
<ul>
<li>تملأ نموذج 3 دقائق (قطاع/مدينة/عرض/هدف)</li>
<li>Dealix يولّد التشخيص خلال 24 ساعة عمل</li>
<li>تستلمه على إيميلك (PDF + JSON)</li>
<li>إذا أعجبك، نكمل Pilot 7 أيام بـ 499 ريال</li>
<li>أو مجاني مقابل case study بعد انتهاء الـPilot</li>
</ul>
</section>
<section class="note">
<strong>ضمانات Dealix:</strong>
Approval-first — لا نرسل أي شيء قبل موافقتك.
لا cold WhatsApp. لا scraping. لا وعود بنتائج مضمونة.
PDPL-aware من اليوم الأول.
</section>
<div style="text-align: center; padding: 24px;">
<a href="mailto:bassam.m.assiri@gmail.com?subject=Dealix%20Free%20Growth%20Diagnostic"
class="cta">احجز التشخيص الآن</a>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,156 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dealix — الخدمات</title>
<style>
:root {
--bg: #0f1115; --panel: #181b22; --text: #f5f5f7;
--muted: #a8aab2; --brand: #ffd166; --good: #06d6a0;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Tajawal', 'Segoe UI', Tahoma, sans-serif;
background: var(--bg); color: var(--text); line-height: 1.7; }
header { padding: 56px 24px 32px; text-align: center;
background: linear-gradient(135deg, #1a1d27 0%, #0f1115 100%); }
header h1 { font-size: 42px; line-height: 1.2; margin-bottom: 14px; }
header h1 span { color: var(--brand); }
header p { color: var(--muted); max-width: 640px; margin: 0 auto; font-size: 18px; }
main { max-width: 1100px; margin: 0 auto; padding: 32px 24px; }
.doors { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 18px; margin: 24px 0 36px; }
.door { background: var(--panel); padding: 28px; border-radius: 14px;
border: 1px solid #232631; }
.door h2 { color: var(--brand); margin-bottom: 12px; font-size: 22px; }
.door ul { list-style: none; padding: 0; }
.door ul li { padding: 6px 0; padding-right: 22px; position: relative; }
.door ul li:before { content: "▸"; color: var(--good); position: absolute;
right: 0; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 14px; margin: 14px 0; }
.card { background: #1f232c; padding: 16px; border-radius: 10px;
border: 1px solid #2a2e39; }
.card .name { color: var(--brand); font-weight: 700; font-size: 17px; }
.card .price { font-size: 14px; color: var(--muted); margin-top: 6px; }
.card .out { font-size: 14px; margin-top: 8px; color: var(--text); }
.cta { display: inline-block; padding: 12px 24px; background: var(--brand);
color: #0f1115; border-radius: 10px; font-weight: 700; text-decoration: none;
font-size: 16px; margin: 6px; }
footer { text-align: center; padding: 24px; color: var(--muted); font-size: 14px;
border-top: 1px solid #232631; margin-top: 32px; }
</style>
</head>
<body>
<header>
<h1>الخدمات — <span>Dealix Service Tower</span></h1>
<p>اختر هدفك من 3 أبواب، النظام يوصي بالخدمة الصحيحة.
كل خدمة productized بمواصفات + مخرجات + سعر + Proof Pack.</p>
</header>
<main>
<section class="doors">
<div class="door">
<h2>أريد عملاء جدد</h2>
<ul>
<li>First 10 Opportunities Sprint</li>
<li>Targeting OS</li>
<li>LinkedIn Lead Gen Setup</li>
<li>Email Outreach Drafts</li>
<li>Meeting Booking Sprint</li>
<li>Growth OS</li>
</ul>
</div>
<div class="door">
<h2>عندي بيانات وأبغى أستفيد منها</h2>
<ul>
<li>List Intelligence</li>
<li>WhatsApp Compliance Setup</li>
<li>Email Revenue Rescue</li>
<li>Customer Reactivation</li>
</ul>
</div>
<div class="door">
<h2>أبغى توسع واستراتيجية</h2>
<ul>
<li>Partner Sprint</li>
<li>Agency Partner Program</li>
<li>Executive Growth Brief</li>
<li>Self-Growth Operator</li>
</ul>
</div>
</section>
<h2 style="color: var(--brand); margin: 16px 0;">12 خدمة productized</h2>
<div class="grid">
<div class="card">
<div class="name">Free Growth Diagnostic</div>
<div class="price">مجاني</div>
<div class="out">3 فرص + رسالة + مخاطر + خطة Pilot — خلال 24 ساعة</div>
</div>
<div class="card">
<div class="name">List Intelligence</div>
<div class="price">4991,500 ريال</div>
<div class="out">تنظيف + تصنيف + أفضل 50 + رسائل عربية</div>
</div>
<div class="card">
<div class="name">First 10 Opportunities Sprint</div>
<div class="price">4991,500 ريال</div>
<div class="out">10 فرص + رسائل + متابعة 7 أيام + Proof Pack</div>
</div>
<div class="card">
<div class="name">Self-Growth Operator</div>
<div class="price">999 ريال شهرياً</div>
<div class="out">Daily brief + drafts + متابعة + reports</div>
</div>
<div class="card">
<div class="name">Growth OS</div>
<div class="price">2,999 ريال شهرياً</div>
<div class="out">المنصة الكاملة: قنوات + autopilot + proof</div>
</div>
<div class="card">
<div class="name">Email Revenue Rescue</div>
<div class="price">1,5005,000 ريال</div>
<div class="out">استخراج فرص ضائعة من Gmail + drafts</div>
</div>
<div class="card">
<div class="name">Meeting Booking Sprint</div>
<div class="price">1,5005,000 ريال</div>
<div class="out">دعوات + briefs + follow-ups</div>
</div>
<div class="card">
<div class="name">Partner Sprint</div>
<div class="price">3,0007,500 ريال</div>
<div class="out">20 شريك محتمل + 10 رسائل + 5 اجتماعات</div>
</div>
<div class="card">
<div class="name">Agency Partner Program</div>
<div class="price">10,00050,000 ريال</div>
<div class="out">بيع Dealix لعملاء الوكالة + revenue share</div>
</div>
<div class="card">
<div class="name">WhatsApp Compliance Setup</div>
<div class="price">1,5004,000 ريال</div>
<div class="out">audit + opt-in templates + ledger</div>
</div>
<div class="card">
<div class="name">LinkedIn Lead Gen Setup</div>
<div class="price">2,0007,500 ريال</div>
<div class="out">حملة Lead Form + audiences + ربط CRM</div>
</div>
<div class="card">
<div class="name">Executive Growth Brief</div>
<div class="price">499999 ريال شهرياً</div>
<div class="out">3 قرارات + 3 فرص + 3 مخاطر يومياً</div>
</div>
</div>
<div style="text-align: center; margin: 32px 0;">
<a href="free-diagnostic.html" class="cta">ابدأ بالتشخيص المجاني</a>
<a href="first-10-opportunities.html" class="cta" style="background: transparent; color: var(--text); border: 1px solid #383b44;">10 فرص في 10 دقائق</a>
</div>
</main>
<footer>Dealix — Service Tower · 2026 · bassam.m.assiri@gmail.com</footer>
</body>
</html>

View File

@ -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

View File

@ -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)

View File

@ -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