diff --git a/.github/workflows/dealix-api-ci.yml b/.github/workflows/dealix-api-ci.yml index 3bffe5f7..83736e84 100644 --- a/.github/workflows/dealix-api-ci.yml +++ b/.github/workflows/dealix-api-ci.yml @@ -1,5 +1,6 @@ # Canonical CI for the Dealix API package (monorepo). # GitHub only loads workflows from the repository root .github/workflows/. +# Three jobs so branch protection can require: pytest, smoke_inprocess, launch_readiness. name: Dealix API CI on: @@ -18,8 +19,17 @@ defaults: run: working-directory: dealix +env: + APP_ENV: test + APP_DEBUG: "false" + ANTHROPIC_API_KEY: test-anthropic-key + DEEPSEEK_API_KEY: test-deepseek-key + GROQ_API_KEY: test-groq-key + GLM_API_KEY: test-glm-key + GOOGLE_API_KEY: test-google-key + jobs: - test: + pytest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -41,37 +51,52 @@ jobs: run: python -m compileall api auto_client_acquisition - name: Tests - env: - APP_ENV: test - APP_DEBUG: "false" - ANTHROPIC_API_KEY: test-anthropic-key - DEEPSEEK_API_KEY: test-deepseek-key - GROQ_API_KEY: test-groq-key - GLM_API_KEY: test-glm-key - GOOGLE_API_KEY: test-google-key run: pytest -q --no-cov - - name: In-process API smoke - env: - APP_ENV: test - APP_DEBUG: "false" - ANTHROPIC_API_KEY: test-anthropic-key - DEEPSEEK_API_KEY: test-deepseek-key - GROQ_API_KEY: test-groq-key - GLM_API_KEY: test-glm-key - GOOGLE_API_KEY: test-google-key - run: python scripts/smoke_inprocess.py - - name: Embeddings pipeline placeholder run: python scripts/embeddings_pipeline_placeholder.py - name: Deterministic eval smoke - env: - APP_ENV: test - APP_DEBUG: "false" - ANTHROPIC_API_KEY: test-anthropic-key - DEEPSEEK_API_KEY: test-deepseek-key - GROQ_API_KEY: test-groq-key - GLM_API_KEY: test-glm-key - GOOGLE_API_KEY: test-google-key run: python scripts/run_evals.py + + smoke_inprocess: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: pip + cache-dependency-path: dealix/requirements.txt + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-asyncio httpx aiosqlite + + - name: In-process API smoke + run: python scripts/smoke_inprocess.py + + launch_readiness: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: pip + cache-dependency-path: dealix/requirements.txt + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-asyncio httpx aiosqlite + + - name: Launch readiness (GO_PRIVATE_BETA gate) + run: python scripts/launch_readiness_check.py diff --git a/.github/workflows/dealix-staging-smoke.yml b/.github/workflows/dealix-staging-smoke.yml index 7a8b6ef6..21f64ec1 100644 --- a/.github/workflows/dealix-staging-smoke.yml +++ b/.github/workflows/dealix-staging-smoke.yml @@ -1,4 +1,4 @@ -# Manual smoke against a deployed Dealix staging URL (secrets in GitHub only). +# Manual smoke + launch readiness against a deployed Dealix staging URL (secrets in GitHub only). name: Dealix staging smoke on: @@ -17,17 +17,46 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.11" + cache: pip + cache-dependency-path: dealix/requirements.txt - - name: Install httpx - run: pip install httpx + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-asyncio httpx aiosqlite - name: Run staging smoke env: STAGING_BASE_URL: ${{ secrets.STAGING_BASE_URL }} STAGING_API_KEY: ${{ secrets.STAGING_API_KEY }} + APP_ENV: test + APP_DEBUG: "false" + ANTHROPIC_API_KEY: test-anthropic-key + DEEPSEEK_API_KEY: test-deepseek-key + GROQ_API_KEY: test-groq-key + GLM_API_KEY: test-glm-key + GOOGLE_API_KEY: test-google-key run: | if [ -z "$STAGING_BASE_URL" ]; then echo "STAGING_BASE_URL secret not set — skipping." exit 0 fi python scripts/smoke_staging.py --base-url "$STAGING_BASE_URL" + + - name: Launch readiness (expect PAID_BETA_READY) + env: + STAGING_BASE_URL: ${{ secrets.STAGING_BASE_URL }} + APP_ENV: test + APP_DEBUG: "false" + ANTHROPIC_API_KEY: test-anthropic-key + DEEPSEEK_API_KEY: test-deepseek-key + GROQ_API_KEY: test-groq-key + GLM_API_KEY: test-glm-key + GOOGLE_API_KEY: test-google-key + run: | + if [ -z "$STAGING_BASE_URL" ]; then + echo "STAGING_BASE_URL secret not set — skipping launch readiness." + exit 0 + fi + python scripts/launch_readiness_check.py --base-url "$STAGING_BASE_URL" diff --git a/dealix/api/main.py b/dealix/api/main.py index 38a0cd5a..851b942d 100644 --- a/dealix/api/main.py +++ b/dealix/api/main.py @@ -15,11 +15,15 @@ from fastapi.responses import JSONResponse from api.middleware import RequestIDMiddleware from api.routers import ( admin, + agent_observability, agents, + autonomous_service_operator, automation, autonomous, business, command_center, + connector_router, + customer_ops, customer_success, data, dominance, @@ -27,18 +31,30 @@ from api.routers import ( ecosystem, email_send, full_os, + growth_curator, + growth_operator, health, innovation, + intelligence_layer, + launch_ops, leads, + meeting_intelligence, + model_router, outreach, personal_operator, + platform_services, pricing, prospect, public, + revenue_launch, revenue, revenue_os, sales, sectors, + security_curator, + service_excellence, + service_tower, + targeting_os, v3, webhooks, ) @@ -130,6 +146,7 @@ def create_app() -> FastAPI: app.include_router(pricing.router) app.include_router(prospect.router) app.include_router(autonomous.router) + app.include_router(autonomous_service_operator.router) app.include_router(data.router) app.include_router(outreach.router) app.include_router(revenue.router) @@ -139,11 +156,26 @@ def create_app() -> FastAPI: app.include_router(dominance.router) app.include_router(full_os.router) app.include_router(customer_success.router) + app.include_router(customer_ops.router) app.include_router(ecosystem.router) app.include_router(command_center.router) app.include_router(revenue_os.router) app.include_router(v3.router) app.include_router(innovation.router) + app.include_router(platform_services.router) + app.include_router(intelligence_layer.router) + app.include_router(growth_operator.router) + app.include_router(security_curator.router) + app.include_router(growth_curator.router) + app.include_router(meeting_intelligence.router) + app.include_router(model_router.router) + app.include_router(connector_router.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(launch_ops.router) + app.include_router(revenue_launch.router) app.include_router(business.router) app.include_router(personal_operator.router) app.include_router(public.router) diff --git a/dealix/api/routers/agent_observability.py b/dealix/api/routers/agent_observability.py new file mode 100644 index 00000000..f66bb353 --- /dev/null +++ b/dealix/api/routers/agent_observability.py @@ -0,0 +1,41 @@ +"""Agent observability demo endpoints — evals and trace shapes.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body + +from auto_client_acquisition.agent_observability.safety_eval import evaluate_safety +from auto_client_acquisition.agent_observability.saudi_tone_eval import evaluate_saudi_tone +from auto_client_acquisition.agent_observability.trace_events import build_trace_event + +router = APIRouter(prefix="/api/v1/agent-observability", tags=["agent_observability"]) + + +@router.get("/demo") +async def demo() -> dict[str, Any]: + return {"ok": True, "message_ar": "تتبع وتقييم — اربط Langfuse في staging للإنتاج.", "demo": True} + + +@router.post("/eval/safety") +async def eval_safety(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return evaluate_safety(str(payload.get("text_ar") or "")) + + +@router.post("/eval/saudi-tone") +async def eval_saudi_tone(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return evaluate_saudi_tone(str(payload.get("text_ar") or "")) + + +@router.post("/trace/build") +async def trace_build(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_trace_event( + workflow_name=str(payload.get("workflow_name") or "demo"), + agent_name=str(payload.get("agent_name") or "dealix"), + action_type=str(payload.get("action_type") or "draft"), + policy_result=str(payload.get("policy_result") or "approval_required"), + tool_called=payload.get("tool_called"), + outcome=payload.get("outcome"), + metadata=payload.get("metadata") if isinstance(payload.get("metadata"), dict) else {}, + ) diff --git a/dealix/api/routers/autonomous_service_operator.py b/dealix/api/routers/autonomous_service_operator.py new file mode 100644 index 00000000..66ed2599 --- /dev/null +++ b/dealix/api/routers/autonomous_service_operator.py @@ -0,0 +1,137 @@ +"""Autonomous Service Operator — /api/v1/operator (deterministic MVP).""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body, HTTPException + +from auto_client_acquisition.autonomous_service_operator import ( + approval_manager as am, + agency_mode, + client_mode, + conversation_router, + executive_mode, + intake_collector, + proof_pack_dispatcher, + self_growth_mode, + service_bundles, + service_delivery_mode, + session_state as ss, + tool_action_planner, + upsell_engine, + whatsapp_renderer, + workflow_runner as wr, +) +from auto_client_acquisition.service_excellence.service_scoring import calculate_service_excellence_score + +router = APIRouter(prefix="/api/v1/operator", tags=["autonomous_service_operator"]) + + +def _mode_profile(mode: str) -> dict[str, Any]: + m = (mode or "client").strip().lower() + if m == "executive": + return executive_mode.mode_profile() + if m in ("agency_partner", "agency"): + return agency_mode.mode_profile() + if m in ("self_growth", "self-growth"): + return self_growth_mode.mode_profile() + if m in ("service_delivery", "delivery"): + return service_delivery_mode.mode_profile() + return client_mode.mode_profile() + + +@router.post("/chat/message") +async def operator_chat_message(body: dict[str, Any] = Body(...)) -> dict[str, Any]: + msg = str(body.get("message") or "").strip() + if not msg: + raise HTTPException(status_code=400, detail="message_required") + sid = str(body.get("session_id") or ss.new_session_id()) + ss.touch_session(sid) + mode = str(body.get("mode") or "client") + result = conversation_router.handle_message(sid, msg, mode=mode) + result["mode_profile"] = _mode_profile(mode) + return result + + +@router.post("/chat/decision") +async def operator_chat_decision(body: dict[str, Any] = Body(...)) -> dict[str, Any]: + sid = str(body.get("session_id") or "").strip() + dec = str(body.get("decision") or "").strip() + if not sid or not dec: + raise HTTPException(status_code=400, detail="session_id_and_decision_required") + updated = am.apply_decision(sid, dec) + return {"session": updated, "demo": True} + + +@router.get("/session/{session_id}") +async def operator_get_session(session_id: str) -> dict[str, Any]: + s = ss.get_session(session_id) + if not s: + raise HTTPException(status_code=404, detail="session_not_found") + return {**s, "demo": True} + + +@router.get("/cards/pending") +async def operator_cards_pending() -> dict[str, Any]: + return {"pending": ss.list_sessions_with_pending(), "demo": True} + + +@router.post("/service/start") +async def operator_service_start(body: dict[str, Any] = Body(...)) -> dict[str, Any]: + sid = str(body.get("session_id") or ss.new_session_id()) + svc_id = str(body.get("service_id") or "").strip() + if not svc_id: + raise HTTPException(status_code=400, detail="service_id_required") + ss.touch_session(sid) + wr.advance(sid, "start_service") + intake = intake_collector.intake_questions(svc_id) + am.set_pending_approval( + sid, + { + "title_ar": f"بدء خدمة: {svc_id}", + "buttons_ar": ["موافقة", "تعديل", "تخطي"], + "service_id": svc_id, + }, + ) + return { + "session_id": sid, + "intake": intake, + "excellence": calculate_service_excellence_score(svc_id), + "demo": True, + } + + +@router.post("/service/continue") +async def operator_service_continue(body: dict[str, Any] = Body(...)) -> dict[str, Any]: + sid = str(body.get("session_id") or "").strip() + event = str(body.get("event") or "draft_ready").strip() + if not sid: + raise HTTPException(status_code=400, detail="session_id_required") + ss.touch_session(sid) + return {"session": wr.advance(sid, event), "demo": True} + + +@router.get("/proof-pack/demo") +async def operator_proof_pack_demo(service_id: str = "first_10_opportunities") -> dict[str, Any]: + return proof_pack_dispatcher.build_proof_pack(service_id) + + +@router.get("/whatsapp/daily-brief") +async def operator_whatsapp_daily_brief() -> dict[str, Any]: + return whatsapp_renderer.render_daily_brief_stub() + + +@router.get("/bundles") +async def operator_bundles() -> dict[str, Any]: + return service_bundles.list_bundles() + + +@router.get("/tools/matrix") +async def operator_tools_matrix() -> dict[str, Any]: + return tool_action_planner.list_tool_matrix() + + +@router.get("/upsell") +async def operator_upsell(service_id: str = "first_10_opportunities") -> dict[str, Any]: + return upsell_engine.suggest_upsell(service_id) diff --git a/dealix/api/routers/connector_router.py b/dealix/api/routers/connector_router.py new file mode 100644 index 00000000..fb1ad472 --- /dev/null +++ b/dealix/api/routers/connector_router.py @@ -0,0 +1,16 @@ +"""Connector catalog HTTP.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter + +from auto_client_acquisition.connectors.connector_catalog import build_connector_catalog + +router = APIRouter(prefix="/api/v1/connectors", tags=["connectors"]) + + +@router.get("/catalog") +async def catalog() -> dict[str, Any]: + return build_connector_catalog() diff --git a/dealix/api/routers/customer_ops.py b/dealix/api/routers/customer_ops.py new file mode 100644 index 00000000..87c71390 --- /dev/null +++ b/dealix/api/routers/customer_ops.py @@ -0,0 +1,50 @@ +"""Customer ops API — onboarding, SLA, connectors (deterministic).""" + +from __future__ import annotations + +from fastapi import APIRouter, Body + +from auto_client_acquisition.customer_ops.connector_setup_status import build_connector_status +from auto_client_acquisition.customer_ops.customer_success_cadence import build_weekly_cadence +from auto_client_acquisition.customer_ops.incident_router import build_incident_playbook, classify_incident +from auto_client_acquisition.customer_ops.onboarding_checklist import build_onboarding_checklist +from auto_client_acquisition.customer_ops.sla_tracker import build_sla_summary +from auto_client_acquisition.customer_ops.support_ticket_router import route_ticket + +router = APIRouter(prefix="/api/v1/customer-ops", tags=["customer-ops"]) + + +@router.get("/onboarding/checklist") +async def onboarding_checklist(service_id: str | None = None) -> dict[str, object]: + return build_onboarding_checklist(service_id) + + +@router.get("/support/sla") +async def support_sla() -> dict[str, object]: + return build_sla_summary() + + +@router.get("/connectors/status") +async def connectors_status() -> dict[str, object]: + return build_connector_status() + + +@router.get("/success/cadence") +async def success_cadence() -> dict[str, object]: + return build_weekly_cadence() + + +@router.get("/incidents/playbook") +async def incidents_playbook() -> dict[str, object]: + return build_incident_playbook() + + +@router.post("/support/route") +async def support_route(payload: dict[str, object] = Body(default_factory=dict)) -> dict[str, object]: + issue = str(payload.get("issue_ar") or "") + return route_ticket(issue) + + +@router.get("/incidents/classify") +async def incidents_classify(severity: str = "P3") -> dict[str, object]: + return classify_incident(severity) diff --git a/dealix/api/routers/growth_curator.py b/dealix/api/routers/growth_curator.py new file mode 100644 index 00000000..28cee03c --- /dev/null +++ b/dealix/api/routers/growth_curator.py @@ -0,0 +1,38 @@ +"""Growth curator API — grading and weekly report.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body + +from auto_client_acquisition.growth_curator.curator_report import build_weekly_curator_report +from auto_client_acquisition.growth_curator.message_curator import grade_message +from auto_client_acquisition.growth_curator.mission_curator import curate_missions_weekly +from auto_client_acquisition.growth_curator.skill_inventory import list_skill_inventory + +router = APIRouter(prefix="/api/v1/growth-curator", tags=["growth_curator"]) + + +@router.get("/report/demo") +async def report_demo() -> dict[str, Any]: + return build_weekly_curator_report() + + +@router.post("/messages/grade") +async def messages_grade(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return grade_message( + str(payload.get("message_ar") or ""), + sector=str(payload.get("sector") or ""), + channel=str(payload.get("channel") or "whatsapp"), + ) + + +@router.get("/skills/demo") +async def skills_demo() -> dict[str, Any]: + return list_skill_inventory() + + +@router.get("/missions/curate/demo") +async def missions_curate_demo() -> dict[str, Any]: + return curate_missions_weekly() diff --git a/dealix/api/routers/growth_operator.py b/dealix/api/routers/growth_operator.py new file mode 100644 index 00000000..99543c7a --- /dev/null +++ b/dealix/api/routers/growth_operator.py @@ -0,0 +1,38 @@ +""" +Growth Operator — thin product-facing aliases over innovation + business. + +لا يكرر منطق ten-in-ten؛ يعرّف مسارات متوقعة في وثائق الـ beta والـ smoke. +""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter + +from auto_client_acquisition.business.proof_pack import build_demo_proof_pack +from auto_client_acquisition.innovation.growth_missions import list_growth_missions + +router = APIRouter(prefix="/api/v1/growth-operator", tags=["growth_operator"]) + + +@router.get("/missions") +async def missions() -> dict[str, Any]: + """نفس محتوى ``GET /api/v1/innovation/growth-missions`` مع تسمية منتجية.""" + data = list_growth_missions() + if isinstance(data, dict): + out = dict(data) + out["canonical_route"] = "/api/v1/innovation/growth-missions" + return out + return {"missions": data, "canonical_route": "/api/v1/innovation/growth-missions"} + + +@router.get("/proof-pack/demo") +async def proof_pack_demo() -> dict[str, Any]: + """نفس ``GET /api/v1/business/proof-pack/demo`` — مسار موحّد للعرض في الـ beta.""" + pack = build_demo_proof_pack() + if isinstance(pack, dict): + out = dict(pack) + out["canonical_route"] = "/api/v1/business/proof-pack/demo" + return out + return {"pack": pack, "canonical_route": "/api/v1/business/proof-pack/demo"} diff --git a/dealix/api/routers/intelligence_layer.py b/dealix/api/routers/intelligence_layer.py new file mode 100644 index 00000000..88cb0581 --- /dev/null +++ b/dealix/api/routers/intelligence_layer.py @@ -0,0 +1,122 @@ +"""Intelligence layer API — deterministic JSON; optional ten-in-ten bridge.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body + +from auto_client_acquisition.innovation.ten_in_ten import build_ten_opportunities +from auto_client_acquisition.intelligence_layer.action_graph import build_action_graph_trace +from auto_client_acquisition.intelligence_layer.board_brief import build_board_brief +from auto_client_acquisition.intelligence_layer.competitive_moves import build_competitive_moves +from auto_client_acquisition.intelligence_layer.decision_memory import list_decisions, record_decision +from auto_client_acquisition.intelligence_layer.growth_brain import build_growth_profile +from auto_client_acquisition.intelligence_layer.intel_command_feed import build_intel_command_feed +from auto_client_acquisition.intelligence_layer.mission_engine import get_mission, list_mission_catalog +from auto_client_acquisition.intelligence_layer.opportunity_simulator import simulate_opportunities +from auto_client_acquisition.intelligence_layer.revenue_dna import build_revenue_dna +from auto_client_acquisition.intelligence_layer.trust_score import compute_trust_score + +router = APIRouter(prefix="/api/v1/intelligence", tags=["intelligence_layer"]) + + +@router.post("/growth-profile") +async def growth_profile(company: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_growth_profile(company or {}) + + +@router.get("/command-feed") +async def intel_command_feed() -> dict[str, Any]: + return build_intel_command_feed() + + +@router.get("/command-feed/demo") +async def intel_command_feed_demo() -> dict[str, Any]: + """Alias of ``GET /command-feed`` for product/docs compatibility.""" + return build_intel_command_feed() + + +@router.post("/missions/first-10-opportunities") +async def missions_first_10_opportunities( + payload: dict[str, Any] = Body(default_factory=dict), +) -> dict[str, Any]: + """Thin wrapper around innovation ``build_ten_opportunities`` — no duplicate logic.""" + return build_ten_opportunities(payload or None) + + +@router.get("/missions/catalog") +async def missions_catalog() -> dict[str, Any]: + """Mission engine metadata + pointer to innovation missions.""" + return list_mission_catalog() + + +@router.get("/missions/{mission_id}") +async def mission_detail(mission_id: str) -> dict[str, Any]: + return get_mission(mission_id) + + +@router.post("/action-graph/demo") +async def action_graph_demo(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_action_graph_trace(payload or {}) + + +@router.get("/decision-memory/demo") +async def decision_memory_demo() -> dict[str, Any]: + return list_decisions(limit=20) + + +@router.post("/decision-memory/record") +async def decision_memory_record(entry: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return record_decision(entry or {}) + + +@router.post("/trust-score") +async def trust_score(signals: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return compute_trust_score(signals or {}) + + +@router.post("/revenue-dna") +async def revenue_dna(context: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_revenue_dna(context or {}) + + +@router.post("/opportunity-simulator") +async def opportunity_simulator(inputs: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return simulate_opportunities(inputs or {}) + + +@router.post("/board-brief") +async def board_brief(snapshot: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_board_brief(snapshot or {}) + + +@router.get("/competitive-moves") +async def competitive_moves(sector: str | None = None) -> dict[str, Any]: + return build_competitive_moves(sector) + + +@router.post("/bundle") +async def intelligence_bundle( + payload: dict[str, Any] = Body(default_factory=dict), +) -> dict[str, Any]: + """ + Single round-trip for demos. Optional ``include_ten_in_ten`` merges + ``build_ten_opportunities`` without exposing a duplicate HTTP path. + """ + company = payload.get("company") if isinstance(payload.get("company"), dict) else {} + out: dict[str, Any] = { + "growth_profile": build_growth_profile(company), + "intel_command_feed": build_intel_command_feed({"append_custom": payload.get("extra_card")}), + "trust_score": compute_trust_score(payload.get("trust_signals") if isinstance(payload.get("trust_signals"), dict) else {}), + "revenue_dna": build_revenue_dna(payload.get("revenue_context") if isinstance(payload.get("revenue_context"), dict) else {}), + "opportunity_simulator": simulate_opportunities( + payload.get("simulator") if isinstance(payload.get("simulator"), dict) else {} + ), + "board_brief": build_board_brief(payload.get("board") if isinstance(payload.get("board"), dict) else {}), + "competitive_moves": build_competitive_moves(str(payload.get("sector") or "") or None), + } + if payload.get("include_ten_in_ten"): + ten_payload = payload.get("ten_in_ten") if isinstance(payload.get("ten_in_ten"), dict) else company + out["ten_in_ten"] = build_ten_opportunities(ten_payload) + return out diff --git a/dealix/api/routers/launch_ops.py b/dealix/api/routers/launch_ops.py new file mode 100644 index 00000000..908562d1 --- /dev/null +++ b/dealix/api/routers/launch_ops.py @@ -0,0 +1,45 @@ +"""Launch ops API — private beta, demo, outreach, go/no-go.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body + +from auto_client_acquisition.launch_ops.demo_flow import build_demo_script +from auto_client_acquisition.launch_ops.go_no_go import evaluate_go_no_go +from auto_client_acquisition.launch_ops.launch_scorecard import build_launch_scorecard +from auto_client_acquisition.launch_ops.outreach_messages import build_first_twenty_outreach +from auto_client_acquisition.launch_ops.private_beta import build_private_beta_offer + +router = APIRouter(prefix="/api/v1/launch", tags=["launch_ops"]) + + +@router.get("/private-beta/offer") +async def launch_private_beta_offer() -> dict[str, Any]: + return build_private_beta_offer() + + +@router.get("/demo-script") +async def launch_demo_script() -> dict[str, Any]: + return build_demo_script() + + +@router.get("/outreach/first-20") +async def launch_outreach_first_20() -> dict[str, Any]: + return build_first_twenty_outreach() + + +@router.get("/go-no-go") +async def launch_go_no_go_get() -> dict[str, Any]: + return evaluate_go_no_go(None) + + +@router.post("/go-no-go") +async def launch_go_no_go_post(flags: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return evaluate_go_no_go(flags or {}) + + +@router.get("/scorecard") +async def launch_scorecard() -> dict[str, Any]: + return build_launch_scorecard() diff --git a/dealix/api/routers/meeting_intelligence.py b/dealix/api/routers/meeting_intelligence.py new file mode 100644 index 00000000..9782efc0 --- /dev/null +++ b/dealix/api/routers/meeting_intelligence.py @@ -0,0 +1,37 @@ +"""Meeting intelligence API — text in, Arabic briefs out (no Calendar insert).""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body + +from auto_client_acquisition.meeting_intelligence.followup_builder import build_post_meeting_followup +from auto_client_acquisition.meeting_intelligence.meeting_brief import build_pre_meeting_brief +from auto_client_acquisition.meeting_intelligence.objection_extractor import extract_objections +from auto_client_acquisition.meeting_intelligence.transcript_parser import summarize_transcript_text + +router = APIRouter(prefix="/api/v1/meeting-intelligence", tags=["meeting_intelligence"]) + + +@router.post("/transcript/summarize") +async def transcript_summarize(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + text = str(payload.get("text") or "") + base = summarize_transcript_text(text) + base["objections"] = extract_objections(text) + return base + + +@router.post("/followup/draft") +async def followup_draft(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + summary = str(payload.get("summary_ar") or "") + steps = payload.get("next_steps") if isinstance(payload.get("next_steps"), list) else None + return build_post_meeting_followup(summary, steps) + + +@router.post("/brief/pre-meeting") +async def pre_meeting_brief(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + company = payload.get("company") if isinstance(payload.get("company"), dict) else {} + contact = payload.get("contact") if isinstance(payload.get("contact"), dict) else {} + opportunity = payload.get("opportunity") if isinstance(payload.get("opportunity"), dict) else {} + return build_pre_meeting_brief(company, contact, opportunity) diff --git a/dealix/api/routers/model_router.py b/dealix/api/routers/model_router.py new file mode 100644 index 00000000..a648c054 --- /dev/null +++ b/dealix/api/routers/model_router.py @@ -0,0 +1,27 @@ +"""Model routing API — configuration hints only.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body + +from auto_client_acquisition.model_router.provider_registry import list_providers +from auto_client_acquisition.model_router.task_router import list_tasks, route_task + +router = APIRouter(prefix="/api/v1/model-router", tags=["model_router"]) + + +@router.get("/tasks") +async def tasks() -> dict[str, Any]: + return list_tasks() + + +@router.post("/route") +async def route(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return route_task(str(payload.get("task_type") or "")) + + +@router.get("/providers") +async def providers() -> dict[str, Any]: + return list_providers() diff --git a/dealix/api/routers/platform_services.py b/dealix/api/routers/platform_services.py new file mode 100644 index 00000000..0245eb5a --- /dev/null +++ b/dealix/api/routers/platform_services.py @@ -0,0 +1,185 @@ +"""Platform Services API — Growth Control Tower (no live external sends).""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body + +from auto_client_acquisition.platform_services import ( + build_proof_summary, + evaluate_action, + event_to_inbox_card, + execute_tool, + get_action_ledger, + get_service_catalog, + list_channels, + validate_event, +) +from auto_client_acquisition.innovation.proof_ledger import build_demo_proof_ledger +from auto_client_acquisition.platform_services.contact_import_preview import build_import_preview +from auto_client_acquisition.platform_services.identity_resolution import resolve_identity_demo +from auto_client_acquisition.platform_services.inbox_feed import build_inbox_feed +from auto_client_acquisition.platform_services.lead_form_ingest import ingest_lead_form +from auto_client_acquisition.platform_services.proof_overview import build_proof_overview + +router = APIRouter(prefix="/api/v1/platform", tags=["platform_services"]) + + +@router.get("/service-catalog") +async def service_catalog() -> dict[str, Any]: + return get_service_catalog() + + +@router.get("/services/catalog") +async def services_catalog_alias() -> dict[str, Any]: + """Alias path for product docs compatibility.""" + return get_service_catalog() + + +@router.get("/channels") +async def channels() -> dict[str, Any]: + return list_channels() + + +@router.post("/events/validate") +async def events_validate(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return validate_event(payload or {}) + + +@router.post("/events/ingest") +async def events_ingest(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + """Validate normalized event and return inbox card — no persistence.""" + v = validate_event(payload or {}) + if not v["valid"]: + return {"ok": False, "errors": v["errors"], "approval_required": True} + ev = v.get("normalized") or {} + return {"ok": True, "event": ev, "card": event_to_inbox_card(ev), "approval_required": True} + + +@router.post("/actions/approve") +async def actions_approve(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + """Record human approval/rejection in the in-memory action ledger — no live side effects.""" + ledger = get_action_ledger() + action_id = str(payload.get("action_id") or payload.get("request_id") or "unspecified") + actor = str(payload.get("actor") or "operator") + approved = payload.get("approved") + is_approved = True if approved is None else bool(approved) + entry = ledger.append_decision( + tool="human_approval", + outcome="approved" if is_approved else "rejected", + detail={ + "action_id": action_id, + "actor": actor, + "notes": payload.get("notes"), + }, + ) + return { + "ok": True, + "ledger_entry": entry, + "detail_ar": "سُجّل القرار في دفتر MVP — لا يُطلق إرسالاً أو دفعاً تلقائياً من هذا المسار.", + "approval_required": False, + } + + +@router.post("/actions/evaluate") +async def actions_evaluate_alias(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + """Alias of ``POST /policy/evaluate`` for docs that refer to ``actions/evaluate``.""" + return evaluate_action( + action=str(payload.get("action") or ""), + channel_id=str(payload.get("channel_id") or ""), + context=payload.get("context") if isinstance(payload.get("context"), dict) else {}, + ) + + +@router.post("/inbox/from-event") +async def inbox_from_event( + payload: dict[str, Any] = Body(default_factory=dict), +) -> dict[str, Any]: + event = payload.get("event") if isinstance(payload.get("event"), dict) else payload + merge = bool(payload.get("merge_demo_hint")) + return {"card": event_to_inbox_card(event or {}, merge_demo_hint=merge)} + + +@router.post("/policy/evaluate") +async def policy_evaluate(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return evaluate_action( + action=str(payload.get("action") or ""), + channel_id=str(payload.get("channel_id") or ""), + context=payload.get("context") if isinstance(payload.get("context"), dict) else {}, + ) + + +@router.post("/tools/execute") +async def tools_execute(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return execute_tool(str(payload.get("tool_name") or ""), payload.get("payload") if isinstance(payload.get("payload"), dict) else {}) + + +@router.get("/proof/summary") +async def proof_summary() -> dict[str, Any]: + return build_proof_summary() + + +@router.get("/proof-ledger/demo") +async def proof_ledger_demo() -> dict[str, Any]: + """Demo ledger events — same source as innovation demo.""" + return build_demo_proof_ledger() + + +@router.get("/identity/resolve-demo") +async def identity_resolve_demo( + phone: str | None = None, + email: str | None = None, + company_hint: str | None = None, +) -> dict[str, Any]: + return resolve_identity_demo(phone=phone, email=email, company_hint=company_hint) + + +@router.get("/proof/overview") +async def proof_overview() -> dict[str, Any]: + return build_proof_overview() + + +@router.get("/inbox/feed") +async def inbox_feed() -> dict[str, Any]: + return build_inbox_feed() + + +@router.post("/contacts/import-preview") +async def contacts_import_preview(body: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_import_preview(body or {}) + + +@router.get("/action-ledger/recent") +async def action_ledger_recent(limit: int = 50) -> dict[str, Any]: + lim = max(1, min(limit, 200)) + return {"entries": get_action_ledger().recent(lim)} + + +@router.post("/ingest/lead-form") +async def ingest_lead_form_route(body: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return ingest_lead_form(body or {}) + + +# --- Wave 4: draft payloads only (re-export from aca.integrations) --- + + +@router.post("/integrations/gmail/draft") +async def gmail_draft(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + from auto_client_acquisition.integrations.gmail_operator import build_gmail_draft_payload + + return build_gmail_draft_payload(payload or {}) + + +@router.post("/integrations/calendar/draft") +async def calendar_draft(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + from auto_client_acquisition.integrations.calendar_operator import build_calendar_draft_payload + + return build_calendar_draft_payload(payload or {}) + + +@router.post("/integrations/moyasar/payment-draft") +async def moyasar_payment_draft(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + from auto_client_acquisition.integrations.moyasar_draft import build_moyasar_payment_draft + + return build_moyasar_payment_draft(payload or {}) diff --git a/dealix/api/routers/revenue_launch.py b/dealix/api/routers/revenue_launch.py new file mode 100644 index 00000000..33451b9c --- /dev/null +++ b/dealix/api/routers/revenue_launch.py @@ -0,0 +1,97 @@ +"""Revenue Today — offers, outreach templates, pilot delivery, manual payment (no live charge).""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Query + +from auto_client_acquisition.revenue_launch.demo_closer import ( + build_12_min_demo_flow, + build_close_script, + build_discovery_questions, + build_objection_responses, +) +from auto_client_acquisition.revenue_launch.offer_i18n import build_revenue_offers_payload +from auto_client_acquisition.revenue_launch.outreach_sequence import ( + build_first_20_segments, + build_outreach_message, +) +from auto_client_acquisition.revenue_launch.payment_manual_flow import ( + build_moyasar_invoice_instructions, + build_payment_confirmation_checklist, + build_payment_link_message, +) +from auto_client_acquisition.revenue_launch.pilot_delivery import ( + build_24h_delivery_plan, + build_client_intake_form, + build_first_10_opportunities_delivery, + build_growth_diagnostic_delivery, + build_list_intelligence_delivery, +) +from auto_client_acquisition.revenue_launch.pipeline_tracker import build_pipeline_schema +from auto_client_acquisition.revenue_launch.proof_pack_template import ( + build_private_beta_proof_pack, +) + +router = APIRouter(prefix="/api/v1/revenue-launch", tags=["revenue_launch"]) + + +@router.get("/offer") +async def revenue_launch_offer(lang: str = Query("ar", description="ar or en — en adds title_en/summary_en alongside Arabic fields")) -> dict[str, Any]: + return build_revenue_offers_payload(lang) + + +@router.get("/outreach/first-20") +async def revenue_launch_outreach_first_20() -> dict[str, Any]: + segs = build_first_20_segments() + samples = [ + build_outreach_message("agency_b2b"), + build_outreach_message("training"), + ] + return {**segs, "sample_messages": samples, "demo": True} + + +@router.get("/demo-flow") +async def revenue_launch_demo_flow() -> dict[str, Any]: + return { + "flow": build_12_min_demo_flow(), + "discovery": build_discovery_questions(), + "close": build_close_script(), + "objections": build_objection_responses(), + "demo": True, + } + + +@router.get("/pipeline/schema") +async def revenue_launch_pipeline_schema() -> dict[str, Any]: + return build_pipeline_schema() + + +@router.get("/pilot-delivery") +async def revenue_launch_pilot_delivery() -> dict[str, Any]: + return { + "intake": build_client_intake_form(), + "plan_24h": build_24h_delivery_plan(), + "first_10": build_first_10_opportunities_delivery(), + "list_intelligence": build_list_intelligence_delivery(), + "diagnostic": build_growth_diagnostic_delivery(), + "no_live_send": True, + "demo": True, + } + + +@router.get("/payment/manual-flow") +async def revenue_launch_payment_manual() -> dict[str, Any]: + return { + "instructions": build_moyasar_invoice_instructions(), + "message_template": build_payment_link_message(), + "confirmation": build_payment_confirmation_checklist(), + "no_live_charge": True, + "demo": True, + } + + +@router.get("/proof-pack/template") +async def revenue_launch_proof_pack_template() -> dict[str, Any]: + return build_private_beta_proof_pack() diff --git a/dealix/api/routers/revenue_os.py b/dealix/api/routers/revenue_os.py index 32d47585..95f56bf0 100644 --- a/dealix/api/routers/revenue_os.py +++ b/dealix/api/routers/revenue_os.py @@ -112,6 +112,17 @@ from auto_client_acquisition.revenue_graph.why_now import ( explain_why_now, ) +# Revenue Company OS (events → cards → RWU; deterministic demo) +from auto_client_acquisition.revenue_company_os.action_graph import demo_action_graph +from auto_client_acquisition.revenue_company_os.channel_health import demo_channel_health +from auto_client_acquisition.revenue_company_os.command_feed_engine import build_company_os_command_feed +from auto_client_acquisition.revenue_company_os.event_to_card import event_to_card +from auto_client_acquisition.revenue_company_os.opportunity_factory import demo_opportunities +from auto_client_acquisition.revenue_company_os.proof_ledger import demo_proof_ledger +from auto_client_acquisition.revenue_company_os.revenue_work_units import demo_work_units +from auto_client_acquisition.revenue_company_os.self_improvement_loop import weekly_growth_curator_report_ar +from auto_client_acquisition.revenue_company_os.service_factory import demo_service_snapshot + router = APIRouter(prefix="/api/v1/revenue-os", tags=["revenue-os"]) log = logging.getLogger(__name__) @@ -656,6 +667,59 @@ async def get_vertical_templates(vertical_id: str) -> dict[str, Any]: } +# ── Revenue Company OS (additive; does not replace POST /events) ───── + + +@router.get("/company-os/command-feed/demo") +async def company_os_command_feed_demo() -> dict[str, Any]: + return build_company_os_command_feed( + [{"type": "email.received", "payload": {"from": "demo@example.com"}}], + ) + + +@router.post("/company-os/events/ingest") +async def company_os_events_ingest(body: dict[str, Any] = Body(...)) -> dict[str, Any]: + et = str(body.get("type") or body.get("event_type") or "form.submitted") + payload = body.get("payload") if isinstance(body.get("payload"), dict) else {} + card = event_to_card(et, payload) + return {"ingested": True, "card": card, "demo": True} + + +@router.get("/company-os/work-units/demo") +async def company_os_work_units_demo() -> dict[str, Any]: + return demo_work_units() + + +@router.get("/company-os/channel-health/demo") +async def company_os_channel_health_demo() -> dict[str, Any]: + return demo_channel_health() + + +@router.get("/company-os/opportunity-factory/demo") +async def company_os_opportunity_factory_demo() -> dict[str, Any]: + return demo_opportunities() + + +@router.get("/company-os/action-graph/demo") +async def company_os_action_graph_demo() -> dict[str, Any]: + return demo_action_graph() + + +@router.get("/company-os/self-improvement/weekly-report") +async def company_os_self_improvement_weekly() -> dict[str, Any]: + return weekly_growth_curator_report_ar() + + +@router.get("/company-os/proof-ledger/demo") +async def company_os_proof_ledger_demo() -> dict[str, Any]: + return demo_proof_ledger() + + +@router.get("/company-os/services/snapshot") +async def company_os_services_snapshot() -> dict[str, Any]: + return demo_service_snapshot() + + # ───────────────────────────────────────────────────────────────── # Helpers # ───────────────────────────────────────────────────────────────── diff --git a/dealix/api/routers/security_curator.py b/dealix/api/routers/security_curator.py new file mode 100644 index 00000000..9e3c0712 --- /dev/null +++ b/dealix/api/routers/security_curator.py @@ -0,0 +1,37 @@ +"""Security curator API — redact and inspect diffs.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body + +from auto_client_acquisition.security_curator.patch_firewall import inspect_diff +from auto_client_acquisition.security_curator.secret_redactor import redact_secrets, scan_payload +from auto_client_acquisition.security_curator.trace_redactor import redact_trace_payload + +router = APIRouter(prefix="/api/v1/security-curator", tags=["security_curator"]) + + +@router.get("/demo") +async def demo() -> dict[str, Any]: + return {"ok": True, "message_ar": "طبقة أمان للوكلاء — redaction وفحص فرق قبل التطبيق.", "demo": True} + + +@router.post("/redact") +async def redact(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + text = str(payload.get("text") or "") + return {"redacted": redact_secrets(text), "findings": scan_payload(payload)} + + +@router.post("/inspect-diff") +async def inspect_diff_route(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + diff = str(payload.get("diff_text") or "") + return inspect_diff(diff) + + +@router.post("/trace/sanitize") +async def trace_sanitize(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + """Redact nested trace/span metadata before export to observability backends.""" + body = payload.get("payload") if isinstance(payload.get("payload"), dict) else payload + return {"sanitized": redact_trace_payload(body or {}), "demo": True} diff --git a/dealix/api/routers/service_excellence.py b/dealix/api/routers/service_excellence.py new file mode 100644 index 00000000..ce1babcd --- /dev/null +++ b/dealix/api/routers/service_excellence.py @@ -0,0 +1,103 @@ +"""Service Excellence OS API — scoring, matrices, launch packages (deterministic).""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter + +from auto_client_acquisition.service_excellence.competitor_gap import compare_against_categories +from auto_client_acquisition.service_excellence.feature_matrix import build_feature_matrix, classify_features +from auto_client_acquisition.service_excellence.launch_package import ( + build_demo_script, + build_landing_page_outline, + build_onboarding_checklist, + build_sales_script, + build_service_launch_package, +) +from auto_client_acquisition.service_excellence.proof_metrics import ( + build_proof_pack_template, + required_proof_metrics, + summarize_proof_ar, +) +from auto_client_acquisition.service_excellence.quality_review import review_all_services, review_service_before_launch +from auto_client_acquisition.service_excellence.research_lab import build_service_research_brief +from auto_client_acquisition.service_excellence.service_improvement_backlog import build_backlog, prioritize_backlog_items +from auto_client_acquisition.service_excellence.service_scoring import calculate_service_excellence_score +from auto_client_acquisition.service_excellence.workflow_builder import ( + build_approval_steps, + build_day_by_day_execution_plan, + build_workflow, + validate_workflow, +) + +router = APIRouter(prefix="/api/v1/service-excellence", tags=["service_excellence"]) + + +@router.get("/review/all") +async def review_all() -> dict[str, Any]: + return review_all_services() + + +@router.get("/{service_id}/feature-matrix") +async def feature_matrix(service_id: str) -> dict[str, Any]: + fm = build_feature_matrix(service_id) + fm["classified"] = classify_features(service_id) + return fm + + +@router.get("/{service_id}/score") +async def excellence_score(service_id: str) -> dict[str, Any]: + return calculate_service_excellence_score(service_id) + + +@router.get("/{service_id}/workflow") +async def excellence_workflow(service_id: str) -> dict[str, Any]: + wf = build_workflow(service_id) + wf["validation"] = validate_workflow(service_id) + wf["day_plan"] = build_day_by_day_execution_plan(service_id) + wf["approval"] = build_approval_steps(service_id) + return wf + + +@router.get("/{service_id}/proof-metrics") +async def proof_metrics(service_id: str) -> dict[str, Any]: + return { + "required": required_proof_metrics(service_id), + "template": build_proof_pack_template(service_id), + "summary_example_ar": summarize_proof_ar(service_id, {"pipeline_sar": 15000}), + "demo": True, + } + + +@router.get("/{service_id}/gap-analysis") +async def gap_analysis(service_id: str) -> dict[str, Any]: + return compare_against_categories(service_id) + + +@router.get("/{service_id}/launch-package") +async def launch_package(service_id: str) -> dict[str, Any]: + return { + "package": build_service_launch_package(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), + "demo": True, + } + + +@router.get("/{service_id}/backlog") +async def backlog(service_id: str) -> dict[str, Any]: + items = build_backlog(service_id) + return {"service_id": service_id, "items": prioritize_backlog_items(items), "demo": True} + + +@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}/review") +async def review_one(service_id: str) -> dict[str, Any]: + return review_service_before_launch(service_id) diff --git a/dealix/api/routers/service_tower.py b/dealix/api/routers/service_tower.py new file mode 100644 index 00000000..ea1460f9 --- /dev/null +++ b/dealix/api/routers/service_tower.py @@ -0,0 +1,173 @@ +"""Service Tower API — sellable services wizard (no live send).""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body + +from auto_client_acquisition.platform_services.service_catalog import get_service_catalog +from auto_client_acquisition.service_tower.deliverables import ( + build_client_report_outline, + build_deliverables, + build_internal_operator_checklist, + build_proof_pack_template, +) +from auto_client_acquisition.service_tower.mission_templates import build_service_workflow +from auto_client_acquisition.service_tower.pricing_engine import ( + calculate_monthly_offer, + calculate_setup_fee, + quote_service, + recommend_plan_after_service, +) +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id, list_tower_services +from auto_client_acquisition.service_tower.service_scorecard import build_service_scorecard +from auto_client_acquisition.service_tower.service_wizard import ( + build_intake_questions, + recommend_service, + start_service, + summarize_recommendation_ar, + validate_service_inputs, +) +from auto_client_acquisition.service_tower.contract_templates import list_contract_templates +from auto_client_acquisition.service_tower.upgrade_paths import build_all_upgrade_paths, recommend_upgrade +from auto_client_acquisition.service_tower.vertical_service_map import build_vertical_service_map +from auto_client_acquisition.service_tower.whatsapp_ceo_control import ( + build_ceo_daily_service_brief, + build_end_of_day_service_report, + build_service_approval_card, +) + +router = APIRouter(prefix="/api/v1/services", tags=["service_tower"]) + + +@router.get("/catalog") +async def services_catalog() -> dict[str, Any]: + tower = list_tower_services() + platform = get_service_catalog() + return { + "tower": tower, + "platform_service_catalog": platform, + "note_ar": "برج الخدمات (تفصيل بيع) + كتالوج المنصة (طبقة تقنية) — يُدمجان للعرض.", + "demo": True, + } + + +@router.post("/recommend") +async def services_recommend(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + p = payload or {} + rec = recommend_service( + company_type=str(p.get("company_type") or ""), + goal=str(p.get("goal") or ""), + has_contact_list=bool(p.get("has_contact_list")), + channels=list(p.get("channels") or []), + budget_sar=p.get("budget_sar"), + ) + rec["summary_ar"] = summarize_recommendation_ar(rec) + return rec + + +@router.post("/start") +async def services_start(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + p = payload or {} + return start_service(str(p.get("service_id") or ""), dict(p.get("payload") or p)) + + +@router.get("/demo/dashboard") +async def services_demo_dashboard() -> dict[str, Any]: + ids = [s["service_id"] for s in list_tower_services().get("services") or []][:5] + cards = [] + for sid in ids: + svc = get_service_by_id(sid) + cards.append( + { + "service_id": sid, + "name_ar": (svc or {}).get("name_ar"), + "deliverables": build_deliverables(sid), + "scorecard": build_service_scorecard( + sid, + {"drafts_created": 2, "approvals": 1, "meetings_booked": 0, "risks_blocked": 3}, + ), + } + ) + return {"cards": cards, "live_send": False, "demo": True} + + +@router.get("/ceo/daily-brief") +async def ceo_daily_brief() -> dict[str, Any]: + return build_ceo_daily_service_brief() + + +@router.get("/ceo/end-of-day") +async def ceo_end_of_day() -> dict[str, Any]: + return build_end_of_day_service_report() + + +@router.post("/approval-card") +async def approval_card(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + p = payload or {} + return build_service_approval_card(str(p.get("service_id") or "growth_os"), str(p.get("action") or "draft_review")) + + +@router.get("/verticals") +async def services_verticals() -> dict[str, Any]: + return build_vertical_service_map() + + +@router.get("/upgrade-paths") +async def services_upgrade_paths() -> dict[str, Any]: + return build_all_upgrade_paths() + + +@router.get("/contracts/templates") +async def services_contract_templates() -> dict[str, Any]: + return list_contract_templates() + + +@router.get("/{service_id}/workflow") +async def service_workflow(service_id: str) -> dict[str, Any]: + return build_service_workflow(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]: + p = payload or {} + q = quote_service( + service_id, + company_size=str(p.get("company_size") or "smb"), + urgency=str(p.get("urgency") or "normal"), + channels_count=int(p.get("channels_count") or 1), + ) + q["setup_fee_hint"] = calculate_setup_fee(service_id) + q["monthly_hint"] = calculate_monthly_offer(service_id) + q["upgrade_hint"] = recommend_plan_after_service(service_id, str(p.get("outcome") or "")) + return q + + +@router.get("/{service_id}/intake-questions") +async def intake_questions(service_id: str) -> dict[str, Any]: + return build_intake_questions(service_id) + + +@router.post("/{service_id}/validate") +async def validate_inputs(service_id: str, payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return validate_service_inputs(service_id, payload or {}) + + +@router.get("/{service_id}/deliverables") +async def service_deliverables(service_id: str) -> dict[str, Any]: + return { + "deliverables": build_deliverables(service_id), + "proof_pack": build_proof_pack_template(service_id), + "client_report": build_client_report_outline(service_id), + "operator_checklist": build_internal_operator_checklist(service_id), + "demo": True, + } + + +@router.get("/{service_id}/upgrade") +async def service_upgrade(service_id: str) -> dict[str, Any]: + return recommend_upgrade(service_id, {}) diff --git a/dealix/api/routers/targeting_os.py b/dealix/api/routers/targeting_os.py new file mode 100644 index 00000000..7ce5e844 --- /dev/null +++ b/dealix/api/routers/targeting_os.py @@ -0,0 +1,133 @@ +"""Targeting & Acquisition OS API — planning and evaluation only, no live send.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body + +from auto_client_acquisition.intelligence_layer.trust_score import compute_trust_score +from auto_client_acquisition.platform_services.contact_import_preview import build_import_preview +from auto_client_acquisition.targeting_os.account_finder import recommend_accounts, recommend_account_source_strategy +from auto_client_acquisition.targeting_os.acquisition_scorecard import build_acquisition_scorecard +from auto_client_acquisition.targeting_os.buyer_role_mapper import map_buying_committee +from auto_client_acquisition.targeting_os.contactability_matrix import evaluate_contactability +from auto_client_acquisition.targeting_os.contract_drafts import list_contract_templates +from auto_client_acquisition.targeting_os.daily_autopilot import build_daily_targeting_brief +from auto_client_acquisition.targeting_os.free_diagnostic import ( + build_free_growth_diagnostic, + recommend_paid_pilot_offer, +) +from auto_client_acquisition.targeting_os.linkedin_strategy import ( + build_lead_gen_form_plan, + recommend_linkedin_strategy, +) +from auto_client_acquisition.targeting_os.outreach_scheduler import build_outreach_plan +from auto_client_acquisition.targeting_os.reputation_guard import calculate_channel_reputation, should_pause_channel +from auto_client_acquisition.targeting_os.self_growth_mode import build_self_growth_daily_brief +from auto_client_acquisition.targeting_os.service_offers import list_targeting_services + +router = APIRouter(prefix="/api/v1/targeting", tags=["targeting_os"]) + + +@router.post("/accounts/recommend") +async def accounts_recommend(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return recommend_accounts( + str(payload.get("sector") or ""), + str(payload.get("city") or ""), + str(payload.get("offer") or ""), + str(payload.get("goal") or ""), + limit=int(payload.get("limit") or 10), + ) + + +@router.post("/buying-committee/map") +async def buying_committee_map(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return map_buying_committee( + str(payload.get("sector") or ""), + payload.get("company_size"), + payload.get("goal"), + ) + + +@router.post("/contacts/evaluate") +async def contacts_evaluate(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + contact = payload.get("contact") if isinstance(payload.get("contact"), dict) else payload + desired = payload.get("desired_channel") + return evaluate_contactability(contact, str(desired) if desired else None) + + +@router.post("/uploaded-list/analyze") +async def uploaded_list_analyze(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + """Delegates to platform import preview for full bucket logic.""" + return build_import_preview(payload or {}) + + +@router.post("/outreach/plan") +async def outreach_plan(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + targets = payload.get("targets") if isinstance(payload.get("targets"), list) else [] + channels = payload.get("channels") if isinstance(payload.get("channels"), list) else ["email"] + goal = str(payload.get("goal") or "growth") + return build_outreach_plan([dict(t) for t in targets if isinstance(t, dict)], [str(c) for c in channels], goal) + + +@router.get("/daily-autopilot/demo") +async def daily_autopilot_demo() -> dict[str, Any]: + return build_daily_targeting_brief({"sector": "training", "city": "الرياض", "offer": "Growth OS", "goal": "meetings"}) + + +@router.get("/self-growth/demo") +async def self_growth_demo() -> dict[str, Any]: + return build_self_growth_daily_brief() + + +@router.get("/reputation/status") +async def reputation_status() -> dict[str, Any]: + metrics = {"bounce_rate": 0.12, "opt_out_rate": 0.01, "complaint_rate": 0.0, "reply_rate": 0.08} + rep = calculate_channel_reputation(metrics) + return {**rep, "should_pause": should_pause_channel(metrics)} + + +@router.post("/linkedin/strategy") +async def linkedin_strategy(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + seg = str(payload.get("segment") or "b2b") + goal = str(payload.get("goal") or "leads") + base = recommend_linkedin_strategy(seg, goal) + if payload.get("include_lead_gen_plan"): + base["lead_gen_plan"] = build_lead_gen_form_plan( + seg, + str(payload.get("offer") or "Pilot"), + str(payload.get("campaign_name") or "dealix"), + ) + return base + + +@router.get("/services") +async def targeting_services() -> dict[str, Any]: + return list_targeting_services() + + +@router.post("/free-diagnostic") +async def free_diagnostic(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + company = payload.get("company") if isinstance(payload.get("company"), dict) else payload + if not isinstance(company, dict): + company = {} + diag = build_free_growth_diagnostic(company or {"sector": "b2b", "city": "الرياض"}) + return {"diagnostic": diag, "pilot_offer": recommend_paid_pilot_offer(diag)} + + +@router.get("/contracts/templates") +async def contracts_templates() -> dict[str, Any]: + return list_contract_templates() + + +@router.post("/trust-score") +async def targeting_trust_score(signals: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + """Bridge to intelligence trust score for targeting workflows.""" + return compute_trust_score(signals or {}) + + +@router.post("/account-strategy") +async def account_strategy(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + acct = payload.get("account") if isinstance(payload.get("account"), dict) else {} + return recommend_account_source_strategy(acct) diff --git a/dealix/auto_client_acquisition/agent_observability/__init__.py b/dealix/auto_client_acquisition/agent_observability/__init__.py new file mode 100644 index 00000000..3747337b --- /dev/null +++ b/dealix/auto_client_acquisition/agent_observability/__init__.py @@ -0,0 +1,7 @@ +"""Agent observability stubs — trace shape + eval scores (no Langfuse SDK required).""" + +from auto_client_acquisition.agent_observability.safety_eval import evaluate_safety +from auto_client_acquisition.agent_observability.saudi_tone_eval import evaluate_saudi_tone +from auto_client_acquisition.agent_observability.trace_events import build_trace_event + +__all__ = ["build_trace_event", "evaluate_safety", "evaluate_saudi_tone"] diff --git a/dealix/auto_client_acquisition/agent_observability/eval_cases.py b/dealix/auto_client_acquisition/agent_observability/eval_cases.py new file mode 100644 index 00000000..9b131ad8 --- /dev/null +++ b/dealix/auto_client_acquisition/agent_observability/eval_cases.py @@ -0,0 +1,5 @@ +"""Named eval cases for CI — references only.""" + +from __future__ import annotations + +EVAL_CASES: tuple[str, ...] = ("no_guarantee_language", "no_payment_collect", "has_single_cta") diff --git a/dealix/auto_client_acquisition/agent_observability/safety_eval.py b/dealix/auto_client_acquisition/agent_observability/safety_eval.py new file mode 100644 index 00000000..85aa8384 --- /dev/null +++ b/dealix/auto_client_acquisition/agent_observability/safety_eval.py @@ -0,0 +1,19 @@ +"""Tripwire checks on Arabic marketing copy — deterministic.""" + +from __future__ import annotations + +import re +from typing import Any + +_BAD = ("ضمان كامل", "مضمون 100%", "ارسل لي رقم البطاقة", "كلمة المرور", "حسابك معلق") + + +def evaluate_safety(text_ar: str) -> dict[str, Any]: + t = text_ar or "" + trips: list[str] = [] + for phrase in _BAD: + if phrase in t: + trips.append(phrase) + if re.search(r"\b\d{16}\b", t): + trips.append("possible_pan") + return {"passed": len(trips) == 0, "tripwires": trips, "demo": True} diff --git a/dealix/auto_client_acquisition/agent_observability/saudi_tone_eval.py b/dealix/auto_client_acquisition/agent_observability/saudi_tone_eval.py new file mode 100644 index 00000000..8f4397fa --- /dev/null +++ b/dealix/auto_client_acquisition/agent_observability/saudi_tone_eval.py @@ -0,0 +1,19 @@ +"""Lightweight Saudi-Arabic tone score — heuristic.""" + +from __future__ import annotations + +import re +from typing import Any + + +def evaluate_saudi_tone(text_ar: str) -> dict[str, Any]: + t = (text_ar or "").strip() + score = 65 + if re.search(r"(هل|ممكن|نقدّم|نرحب|شاكرين)", t): + score += 10 + if len(t) > 600: + score -= 10 + if "!!!" in t or "؟؟؟" in t: + score -= 8 + score = max(0, min(100, score)) + return {"tone_score": score, "demo": True} diff --git a/dealix/auto_client_acquisition/agent_observability/trace_events.py b/dealix/auto_client_acquisition/agent_observability/trace_events.py new file mode 100644 index 00000000..b312effd --- /dev/null +++ b/dealix/auto_client_acquisition/agent_observability/trace_events.py @@ -0,0 +1,35 @@ +"""Structured trace event for dashboards (PII-redacted strings).""" + +from __future__ import annotations + +import time +import uuid +from typing import Any + +from auto_client_acquisition.security_curator.trace_redactor import redact_trace_payload + + +def build_trace_event( + *, + workflow_name: str, + agent_name: str, + action_type: str, + policy_result: str, + tool_called: str | None = None, + outcome: str | None = None, + metadata: dict[str, Any] | None = None, +) -> dict[str, Any]: + meta = metadata or {} + safe_meta = redact_trace_payload(meta) + return { + "trace_id": str(uuid.uuid4()), + "ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "workflow_name": workflow_name, + "agent_name": agent_name, + "action_type": action_type, + "policy_result": policy_result, + "tool_called": tool_called, + "outcome": outcome, + "metadata": safe_meta, + "demo": True, + } diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/__init__.py b/dealix/auto_client_acquisition/autonomous_service_operator/__init__.py new file mode 100644 index 00000000..04560179 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/__init__.py @@ -0,0 +1,6 @@ +"""Autonomous Service Operator — intent to service, approval-first, deterministic MVP.""" + +from auto_client_acquisition.autonomous_service_operator.conversation_router import handle_message +from auto_client_acquisition.autonomous_service_operator.service_bundles import get_bundle, list_bundles + +__all__ = ["handle_message", "list_bundles", "get_bundle"] diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/agency_mode.py b/dealix/auto_client_acquisition/autonomous_service_operator/agency_mode.py new file mode 100644 index 00000000..f95aad04 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/agency_mode.py @@ -0,0 +1,14 @@ +"""Agency partner prioritization.""" + +from __future__ import annotations + +from typing import Any + + +def mode_profile() -> dict[str, Any]: + return { + "mode": "agency_partner", + "priority_intents": ["want_partnerships", "ask_services", "ask_proof"], + "card_types_first": ["opportunity", "proof_update", "compliance_risk"], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/approval_manager.py b/dealix/auto_client_acquisition/autonomous_service_operator/approval_manager.py new file mode 100644 index 00000000..d9642dd3 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/approval_manager.py @@ -0,0 +1,41 @@ +"""Human-in-the-loop gates for operator workflow.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.autonomous_service_operator import session_state as ss + + +def set_pending_approval(session_id: str, payload: dict[str, Any]) -> dict[str, Any]: + return ss.upsert_session( + session_id, + {"workflow_state": "pending_approval", "pending_card": payload}, + ) + + +def apply_decision(session_id: str, decision: str) -> dict[str, Any]: + d = (decision or "").strip().lower() + if d in ("approve", "اعتمد"): + return ss.upsert_session( + session_id, + {"workflow_state": "approved", "pending_card": None, "last_decision": "approve"}, + ) + if d in ("edit", "تعديل"): + return ss.upsert_session( + session_id, + {"workflow_state": "edit_requested", "last_decision": "edit"}, + ) + if d in ("skip", "تخطي"): + return ss.upsert_session( + session_id, + {"workflow_state": "skipped", "pending_card": None, "last_decision": "skip"}, + ) + return ss.upsert_session(session_id, {"workflow_state": "unknown_decision", "last_decision": d}) + + +def pending_card(session_id: str) -> dict[str, Any] | None: + s = ss.get_session(session_id) + if not s: + return None + return s.get("pending_card") if isinstance(s.get("pending_card"), dict) else None diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/client_mode.py b/dealix/auto_client_acquisition/autonomous_service_operator/client_mode.py new file mode 100644 index 00000000..f5f0d4be --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/client_mode.py @@ -0,0 +1,14 @@ +"""End-customer (growth manager) prioritization.""" + +from __future__ import annotations + +from typing import Any + + +def mode_profile() -> dict[str, Any]: + return { + "mode": "client", + "priority_intents": ["want_more_customers", "has_contact_list", "want_meetings"], + "card_types_first": ["approval_needed", "opportunity", "proof_update"], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/conversation_router.py b/dealix/auto_client_acquisition/autonomous_service_operator/conversation_router.py new file mode 100644 index 00000000..283079a1 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/conversation_router.py @@ -0,0 +1,61 @@ +"""Single entry: user message → intent → recommendation + session updates.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.autonomous_service_operator import ( + approval_manager as am, + intent_classifier as ic, + operator_memory as om, + service_bundles as sb, + service_orchestrator as so, + session_state as ss, + workflow_runner as wr, +) + + +def handle_message(session_id: str, message: str, mode: str = "client") -> dict[str, Any]: + intent = ic.classify_intent(message) + om.append_turn(session_id, "user", message, {"intent": intent}) + + if intent == ic.INTENT_COLD_WHATSAPP_REQUEST: + body = so.cold_whatsapp_response() + om.append_turn(session_id, "assistant", body["message_ar"], {"blocked": True}) + ss.upsert_session(session_id, {"last_intent": intent, "blocked": True}) + return {"session_id": session_id, "intent": intent, **body} + + rec = so.recommend_for_intent(intent) + ss.upsert_session( + session_id, + { + "last_intent": intent, + "recommended_service_id": rec["recommended_service_id"], + "mode": mode, + }, + ) + wr.advance(session_id, "start_service") + + reply_ar = _build_reply_ar(intent, rec) + om.append_turn(session_id, "assistant", reply_ar, {"recommendation": rec}) + return { + "session_id": session_id, + "intent": intent, + "recommendation": rec, + "reply_ar": reply_ar, + "bundles_hint": sb.list_bundles() if intent == ic.INTENT_ASK_SERVICES else None, + "demo": True, + } + + +def _build_reply_ar(intent: str, rec: dict[str, Any]) -> str: + sid = rec.get("recommended_service_id") + name = rec.get("service_name_ar") or sid + if intent == ic.INTENT_ASK_SERVICES: + return ( + "أنسب مسار: ابدأ بتشخيص مجاني ثم اختر باقة Growth Starter أو Data to Revenue. " + "راجع قائمة الباقات من /api/v1/operator/bundles." + ) + if intent == ic.INTENT_ASK_PROOF: + return f"Proof Pack مرتبط بخدمة {name} — جاهز كعرض تجريبي بعد أول مسودات موافَق عليها." + return f"نوصي بخدمة: {name} ({sid}). الخطوة التالية: أكمل المدخلات ثم راجع المسودات قبل أي إرسال." diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/executive_mode.py b/dealix/auto_client_acquisition/autonomous_service_operator/executive_mode.py new file mode 100644 index 00000000..f3a66667 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/executive_mode.py @@ -0,0 +1,14 @@ +"""CEO-style prioritization hints for cards and intents.""" + +from __future__ import annotations + +from typing import Any + + +def mode_profile() -> dict[str, Any]: + return { + "mode": "executive", + "priority_intents": ["want_more_customers", "ask_proof", "approve_action"], + "card_types_first": ["leak", "approval_needed", "opportunity"], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/intake_collector.py b/dealix/auto_client_acquisition/autonomous_service_operator/intake_collector.py new file mode 100644 index 00000000..ca40f32e --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/intake_collector.py @@ -0,0 +1,17 @@ +"""Required intake fields per service_id — from Service Tower catalog.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +def intake_questions(service_id: str) -> dict[str, Any]: + svc = get_service_by_id(service_id) or {} + fields = svc.get("inputs_required") or [] + return { + "service_id": service_id, + "fields": [{"name": f, "prompt_ar": f"يرجى تزويدنا بـ: {f}"} for f in fields], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/intent_classifier.py b/dealix/auto_client_acquisition/autonomous_service_operator/intent_classifier.py new file mode 100644 index 00000000..a1ffe2b1 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/intent_classifier.py @@ -0,0 +1,115 @@ +"""Rule-based intent classification (AR/EN) — no LLM required for MVP.""" + +from __future__ import annotations + +import re +from typing import Final + +# Intent ids consumed by service_orchestrator and conversation_router. +INTENT_WANT_MORE_CUSTOMERS: Final = "want_more_customers" +INTENT_HAS_CONTACT_LIST: Final = "has_contact_list" +INTENT_WANT_PARTNERSHIPS: Final = "want_partnerships" +INTENT_WANT_DAILY_GROWTH: Final = "want_daily_growth" +INTENT_WANT_MEETINGS: Final = "want_meetings" +INTENT_WANT_EMAIL_RESCUE: Final = "want_email_rescue" +INTENT_WANT_WHATSAPP_SETUP: Final = "want_whatsapp_setup" +INTENT_ASK_PRICING: Final = "ask_pricing" +INTENT_APPROVE_ACTION: Final = "approve_action" +INTENT_EDIT_ACTION: Final = "edit_action" +INTENT_SKIP_ACTION: Final = "skip_action" +INTENT_ASK_DEMO: Final = "ask_demo" +INTENT_ASK_PROOF: Final = "ask_proof" +INTENT_ASK_SERVICES: Final = "ask_services" +INTENT_ASK_PARTNERSHIP: Final = "ask_partnership" +INTENT_ASK_REVENUE_TODAY: Final = "ask_revenue_today" +INTENT_COLD_WHATSAPP_REQUEST: Final = "cold_whatsapp_request" # blocked path +INTENT_UNKNOWN: Final = "unknown" + + +def normalize_user_text(text: str) -> str: + t = (text or "").strip().lower() + t = re.sub(r"\s+", " ", t) + return t + + +def classify_intent(text: str) -> str: + """Return intent id from free-form user message.""" + t = normalize_user_text(text) + + if not t: + return INTENT_UNKNOWN + + # Dangerous / policy — before generic channel mentions + cold_ar = ("واتساب بارد" in text) or ("رسائل باردة" in text) or ("بارد" in text and "واتساب" in text) + cold_en = "cold whatsapp" in t or "whatsapp blast" in t or "bulk whatsapp" in t + if cold_ar or cold_en: + return INTENT_COLD_WHATSAPP_REQUEST + + if "موافق" in t or t == "approve" or "اعتمد" in t: + return INTENT_APPROVE_ACTION + if "عدّل" in t or "عدل" in t or "edit" in t: + return INTENT_EDIT_ACTION + if "تخطي" in t or "skip" in t: + return INTENT_SKIP_ACTION + + if "سعر" in t or "تسعير" in t or "pricing" in t or "price" in t: + return INTENT_ASK_PRICING + if "ديمو" in t or "demo" in t: + return INTENT_ASK_DEMO + if "proof" in t or "إثبات" in text or "اثبات" in text: + return INTENT_ASK_PROOF + if ( + "خدمات" in t + or "وش أفضل" in t + or "ما أفضل" in t + or "أفضل خدمة" in text + or "افضل خدمة" in text + or "ask_services" in t + ): + return INTENT_ASK_SERVICES + if "شراكة" in t or "partnership" in t or "شراكات" in t: + return INTENT_WANT_PARTNERSHIPS + if "ايراد اليوم" in t or "إيراد اليوم" in text or "revenue today" in t: + return INTENT_ASK_REVENUE_TODAY + + list_signals = ( + "قائمة أرقام" in text + or "عندي قائمة" in text + or "csv" in t + or "قائمة ارقام" in text + or "contacts" in t + or "قائمة جهات" in text + ) + if list_signals: + return INTENT_HAS_CONTACT_LIST + + meeting_signals = "اجتماع" in text or "meetings" in t or "حجز" in t + if meeting_signals and ("أبغى" in text or "ابغى" in text or "want" in t): + return INTENT_WANT_MEETINGS + + email_signals = ("ايميل" in text or "إيميل" in text or "gmail" in t or "بريد" in text) and ( + "إنقاذ" in text or "rescue" in t or "فرص" in text + ) + if email_signals: + return INTENT_WANT_EMAIL_RESCUE + + wa_setup = ("واتساب" in text or "whatsapp" in t) and ("امتثال" in text or "إعداد" in text or "setup" in t) + if wa_setup: + return INTENT_WANT_WHATSAPP_SETUP + + daily = "يومي" in text or "daily" in t or "موجز" in text + if daily: + return INTENT_WANT_DAILY_GROWTH + + customer_signals = ( + "عملاء أكثر" in text + or "أبغى عملاء" in text + or "ابغى عملاء" in text + or "more customers" in t + or "leads" in t + or "فرص" in text + ) + if customer_signals: + return INTENT_WANT_MORE_CUSTOMERS + + return INTENT_UNKNOWN diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/operator_memory.py b/dealix/auto_client_acquisition/autonomous_service_operator/operator_memory.py new file mode 100644 index 00000000..9c33656b --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/operator_memory.py @@ -0,0 +1,19 @@ +"""Append-only conversation turns per session (in-memory MVP).""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.autonomous_service_operator import session_state as ss + + +def append_turn(session_id: str, role: str, content: str, meta: dict[str, Any] | None = None) -> None: + s = ss.touch_session(session_id) + log = list(s.get("turns") or []) + log.append({"role": role, "content": content[:4000], **(meta or {})}) + ss.upsert_session(session_id, {"turns": log[-50:]}) + + +def list_turns(session_id: str) -> list[dict[str, Any]]: + s = ss.get_session(session_id) or {} + return list(s.get("turns") or []) diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/proof_pack_dispatcher.py b/dealix/auto_client_acquisition/autonomous_service_operator/proof_pack_dispatcher.py new file mode 100644 index 00000000..3c6c14d0 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/proof_pack_dispatcher.py @@ -0,0 +1,20 @@ +"""Proof Pack summary for a service — deterministic metrics.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +def build_proof_pack(service_id: str) -> dict[str, Any]: + svc = get_service_by_id(service_id) or {} + metrics = list(svc.get("proof_metrics") or ["drafts_created", "approvals_logged"]) + return { + "service_id": service_id, + "title_ar": f"Proof Pack — {svc.get('name_ar', service_id)}", + "metrics": metrics, + "sample_counts": {m: 0 for m in metrics}, + "notes_ar": "أرقام تجريبية حتى ربط عميل حقيقي ودفتر أحداث.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/self_growth_mode.py b/dealix/auto_client_acquisition/autonomous_service_operator/self_growth_mode.py new file mode 100644 index 00000000..7a5dcc79 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/self_growth_mode.py @@ -0,0 +1,18 @@ +"""Self-growth mode: Dealix uses its own OS for prospecting (drafts + manual approval only).""" + +from __future__ import annotations + +from typing import Any + + +def mode_profile() -> dict[str, Any]: + return { + "mode": "self_growth", + "priority_intents": ["want_more_customers", "ask_services", "ask_demo"], + "rules_ar": [ + "لا scraping ولا إرسال جماعي.", + "كل outreach مسودة + موافقة يدوية.", + "Proof Pack أسبوعي للنتائج الداخلية.", + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/service_bundles.py b/dealix/auto_client_acquisition/autonomous_service_operator/service_bundles.py new file mode 100644 index 00000000..b7d17c1c --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/service_bundles.py @@ -0,0 +1,90 @@ +"""Productized service bundles — SAR ranges and catalog service_ids.""" + +from __future__ import annotations + +from typing import Any + +BundleId = str + +_BUNDLES: dict[BundleId, dict[str, Any]] = { + "growth_starter": { + "bundle_id": "growth_starter", + "title_ar": "Growth Starter", + "services": ["free_growth_diagnostic", "first_10_opportunities"], + "timeline_days": 14, + "price_range_sar": {"min": 499, "max": 499}, + "best_for_ar": "شركات تريد أول قيمة سريعة + Pilot واضح.", + "deliverables_ar": ["تشخيص مجاني", "١٠ فرص + مسودات", "Proof Pack مختصر"], + "proof_metrics": ["opportunities_count", "drafts_created", "approvals_logged"], + "risk_policy_ar": "لا إرسال حي بدون موافقة؛ لا واتساب بارد.", + "upsell_path": "data_to_revenue", + }, + "data_to_revenue": { + "bundle_id": "data_to_revenue", + "title_ar": "من البيانات إلى الإيراد", + "services": ["list_intelligence", "first_10_opportunities"], + "timeline_days": 21, + "price_range_sar": {"min": 1500, "max": 2500}, + "best_for_ar": "من لديه قائمة جهات ويريد أهدافاً مرتبة ومسودات.", + "deliverables_ar": ["أفضل ٥٠ هدفاً", "تقرير قابلية تواصل", "مسودات رسائل"], + "proof_metrics": ["safe_ratio", "drafts_created", "target_ranked"], + "risk_policy_ar": "مسودات فقط؛ موافقة قبل أي إرسال.", + "upsell_path": "executive_growth_os", + }, + "executive_growth_os": { + "bundle_id": "executive_growth_os", + "title_ar": "Executive Growth OS", + "services": ["executive_growth_brief", "growth_os"], + "timeline_days": 30, + "price_range_sar": {"min": 2999, "max": 9999}, + "best_for_ar": "CEO ومدير نمو يريدان موجزاً يومياً وتشغيل Growth OS.", + "deliverables_ar": ["موجز يومي", "Command feed", "Proof Pack أسبوعي"], + "proof_metrics": ["decisions_logged", "revenue_influenced_sar", "risks_blocked"], + "risk_policy_ar": "بوابة أدوات آمنة؛ تكاملات مسودة افتراضياً.", + "upsell_path": "full_growth_control_tower", + }, + "partnership_growth": { + "bundle_id": "partnership_growth", + "title_ar": "نمو عبر الشراكات", + "services": ["partner_sprint", "meeting_booking_sprint"], + "timeline_days": 30, + "price_range_sar": {"min": 3000, "max": 7500}, + "best_for_ar": "توسع عبر شركاء ووكالات.", + "deliverables_ar": ["قائمة شركاء", "مسودات اجتماعات", "مسودة اتفاق إحالة"], + "proof_metrics": ["partner_meetings", "referral_pipeline"], + "risk_policy_ar": "مراجعة قانونية للاتفاقيات.", + "upsell_path": "agency_partner_program", + }, + "local_growth_os": { + "bundle_id": "local_growth_os", + "title_ar": "نمو محلي", + "services": ["local_growth_os"], + "timeline_days": 30, + "price_range_sar": {"min": 999, "max": 2999}, + "best_for_ar": "عيادات ومطاعم ومتاجر محلية.", + "deliverables_ar": ["كروت سمعة", "مسودات رد", "روابط دفع draft"], + "proof_metrics": ["reviews_addressed", "reactivation_drafts"], + "risk_policy_ar": "موافقة على الرسائل العامة.", + "upsell_path": "growth_os", + }, + "full_growth_control_tower": { + "bundle_id": "full_growth_control_tower", + "title_ar": "برج تحكم كامل — مخصص", + "services": ["growth_os", "agency_partner_program"], + "timeline_days": 90, + "price_range_sar": {"min": 15000, "max": 80000}, + "best_for_ar": "مؤسسات تريد كل الطبقات على مراحل.", + "deliverables_ar": ["خارطة ٣٠/٦٠/٩٠ يوماً", "حوكمة موافقات", "Proof شهري"], + "proof_metrics": ["pipeline_influenced", "partners_created", "payments_requested"], + "risk_policy_ar": "DPA + مراجعة امتثال قبل التوسع.", + "upsell_path": None, + }, +} + + +def list_bundles() -> dict[str, Any]: + return {"bundles": list(_BUNDLES.values()), "demo": True} + + +def get_bundle(bundle_id: str) -> dict[str, Any] | None: + return _BUNDLES.get((bundle_id or "").strip()) diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/service_delivery_mode.py b/dealix/auto_client_acquisition/autonomous_service_operator/service_delivery_mode.py new file mode 100644 index 00000000..0b8d9249 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/service_delivery_mode.py @@ -0,0 +1,15 @@ +"""Service delivery mode: running client services with SLA-oriented checklist.""" + +from __future__ import annotations + +from typing import Any + + +def mode_profile() -> dict[str, Any]: + return { + "mode": "service_delivery", + "priority_intents": ["approve_action", "ask_proof", "want_meetings"], + "card_types_first": ["approval_needed", "proof", "meeting_prep"], + "sla_reminder_ar": "التسليم حسب نافذة الـ Pilot المتفق عليها؛ لا live send افتراضياً.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/service_orchestrator.py b/dealix/auto_client_acquisition/autonomous_service_operator/service_orchestrator.py new file mode 100644 index 00000000..c83f2ada --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/service_orchestrator.py @@ -0,0 +1,48 @@ +"""Map intents to recommended service_ids and bundles.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.autonomous_service_operator import intent_classifier as ic +from auto_client_acquisition.service_excellence.service_scoring import calculate_service_excellence_score +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +def recommend_for_intent(intent: str) -> dict[str, Any]: + """Return primary service_id, optional bundle, and excellence gate.""" + mapping: dict[str, tuple[str, str | None]] = { + ic.INTENT_WANT_MORE_CUSTOMERS: ("first_10_opportunities", "growth_starter"), + ic.INTENT_HAS_CONTACT_LIST: ("list_intelligence", "data_to_revenue"), + ic.INTENT_WANT_PARTNERSHIPS: ("partner_sprint", "partnership_growth"), + ic.INTENT_ASK_PARTNERSHIP: ("partner_sprint", "partnership_growth"), + ic.INTENT_WANT_DAILY_GROWTH: ("self_growth_operator", "executive_growth_os"), + ic.INTENT_WANT_MEETINGS: ("meeting_booking_sprint", None), + ic.INTENT_WANT_EMAIL_RESCUE: ("email_revenue_rescue", None), + ic.INTENT_WANT_WHATSAPP_SETUP: ("whatsapp_compliance_setup", None), + ic.INTENT_ASK_PRICING: ("growth_os", None), + ic.INTENT_ASK_SERVICES: ("free_growth_diagnostic", None), + ic.INTENT_ASK_DEMO: ("free_growth_diagnostic", None), + ic.INTENT_ASK_PROOF: ("first_10_opportunities", None), + ic.INTENT_ASK_REVENUE_TODAY: ("growth_os", None), + } + sid, bundle = mapping.get(intent, ("free_growth_diagnostic", None)) + svc = get_service_by_id(sid) or {} + score = calculate_service_excellence_score(sid) + return { + "intent": intent, + "recommended_service_id": sid, + "service_name_ar": svc.get("name_ar"), + "suggested_bundle_id": bundle, + "excellence": {"total_score": score["total_score"], "status": score["status"]}, + "demo": True, + } + + +def cold_whatsapp_response() -> dict[str, Any]: + return { + "blocked": True, + "message_ar": "لا ندعم واتساب بارد أو غير موافق عليه. نرشّح: قالب opt-in، أو إيميل draft، أو سباق اجتماعات بعد موافقة.", + "alternatives": ["whatsapp_opt_in_template", "gmail_draft", "meeting_booking_sprint"], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/session_state.py b/dealix/auto_client_acquisition/autonomous_service_operator/session_state.py new file mode 100644 index 00000000..e0d4bde6 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/session_state.py @@ -0,0 +1,39 @@ +"""In-memory operator sessions — MVP; replace with DB or revenue_memory later.""" + +from __future__ import annotations + +import uuid +from typing import Any + +_SESSIONS: dict[str, dict[str, Any]] = {} + + +def new_session_id() -> str: + return f"op_{uuid.uuid4().hex[:16]}" + + +def get_session(session_id: str) -> dict[str, Any] | None: + return _SESSIONS.get(session_id) + + +def upsert_session(session_id: str, patch: dict[str, Any]) -> dict[str, Any]: + base = dict(_SESSIONS.get(session_id, {})) + base.update(patch) + base["session_id"] = session_id + _SESSIONS[session_id] = base + return base + + +def touch_session(session_id: str) -> dict[str, Any]: + if session_id not in _SESSIONS: + _SESSIONS[session_id] = {"session_id": session_id, "workflow_state": "idle"} + return _SESSIONS[session_id] + + +def list_sessions_with_pending() -> list[dict[str, Any]]: + out: list[dict[str, Any]] = [] + for sid, data in _SESSIONS.items(): + pc = data.get("pending_card") + if isinstance(pc, dict): + out.append({"session_id": sid, "card": pc}) + return out diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/tool_action_planner.py b/dealix/auto_client_acquisition/autonomous_service_operator/tool_action_planner.py new file mode 100644 index 00000000..a20af9c2 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/tool_action_planner.py @@ -0,0 +1,40 @@ +"""Safe Tool Gateway matrix — execution modes per tool (deterministic).""" + +from __future__ import annotations + +from typing import Any, Final + +MODE_SUGGEST_ONLY: Final = "suggest_only" +MODE_DRAFT_ONLY: Final = "draft_only" +MODE_APPROVAL_REQUIRED: Final = "approval_required" +MODE_APPROVED_EXECUTE: Final = "approved_execute" +MODE_BLOCKED: Final = "blocked" + +# tool_id -> default mode when autonomy is draft_and_approve (Dealix beta default) +_TOOL_MATRIX: dict[str, dict[str, Any]] = { + "gmail_send": {"mode": MODE_BLOCKED, "reason_ar": "إرسال Gmail مباشر محظور افتراضياً."}, + "gmail_draft": {"mode": MODE_DRAFT_ONLY, "reason_ar": "مسودات Gmail مسموحة للمراجعة."}, + "linkedin_scrape": {"mode": MODE_BLOCKED, "reason_ar": "scraping LinkedIn محظور."}, + "linkedin_auto_dm": {"mode": MODE_BLOCKED, "reason_ar": "رسائل LinkedIn آلية محظورة."}, + "cold_whatsapp": {"mode": MODE_BLOCKED, "reason_ar": "واتساب بارد / غير موافق عليه محظور."}, + "whatsapp_opt_in_template": {"mode": MODE_DRAFT_ONLY, "reason_ar": "قوالب opt-in كمسودات."}, + "moyasar_charge": {"mode": MODE_BLOCKED, "reason_ar": "شحن بطاقة من API غير مفعّل."}, + "moyasar_payment_link_draft": {"mode": MODE_DRAFT_ONLY, "reason_ar": "مسودة رابط دفع مسموحة."}, + "google_calendar_insert": {"mode": MODE_APPROVAL_REQUIRED, "reason_ar": "إدراج تقويم يحتاج موافقة."}, + "crm_update": {"mode": MODE_APPROVAL_REQUIRED, "reason_ar": "تحديث CRM بعد موافقة."}, + "google_sheets_export": {"mode": MODE_APPROVAL_REQUIRED, "reason_ar": "تصدير مع موافقة عند الحساسية."}, + "meeting_transcript_read": {"mode": MODE_APPROVAL_REQUIRED, "reason_ar": "قراءة محضر تتطلب نطاقاً وموافقة."}, +} + + +def evaluate_tool(tool_id: str, autonomy_mode: str = "draft_and_approve") -> dict[str, Any]: + tid = (tool_id or "").strip().lower() + row = _TOOL_MATRIX.get(tid, {"mode": MODE_APPROVAL_REQUIRED, "reason_ar": "أداة غير مسجّلة — موافقة افتراضية."}) + mode = row["mode"] + if autonomy_mode in ("manual", "suggest_only") and mode == MODE_APPROVED_EXECUTE: + mode = MODE_SUGGEST_ONLY + return {"tool_id": tid, "mode": mode, "reason_ar": row["reason_ar"], "demo": True} + + +def list_tool_matrix() -> dict[str, Any]: + return {"tools": [{**{"tool_id": k}, **v} for k, v in _TOOL_MATRIX.items()], "demo": True} diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/upsell_engine.py b/dealix/auto_client_acquisition/autonomous_service_operator/upsell_engine.py new file mode 100644 index 00000000..1fa72df4 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/upsell_engine.py @@ -0,0 +1,25 @@ +"""Suggest next service / bundle from catalog upgrade_path.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.autonomous_service_operator import service_bundles as sb +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +def suggest_upsell(service_id: str) -> dict[str, Any]: + svc = get_service_by_id(service_id) or {} + nxt = svc.get("upgrade_path") + next_svc = get_service_by_id(str(nxt)) if nxt else None + bundle_hint = None + if nxt == "growth_os": + bundle_hint = "executive_growth_os" + return { + "from_service_id": service_id, + "next_service_id": nxt, + "next_name_ar": (next_svc or {}).get("name_ar"), + "suggested_bundle_id": bundle_hint, + "bundles": sb.get_bundle(bundle_hint) if bundle_hint else None, + "demo": True, + } diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/whatsapp_renderer.py b/dealix/auto_client_acquisition/autonomous_service_operator/whatsapp_renderer.py new file mode 100644 index 00000000..adc47ba4 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/whatsapp_renderer.py @@ -0,0 +1,17 @@ +"""WhatsApp payload shapes (text templates only — no live send).""" + +from __future__ import annotations + +from typing import Any + + +def render_daily_brief_stub() -> dict[str, Any]: + return { + "channel": "whatsapp", + "format": "text_stub", + "body_ar": ( + "موجز Dealix (مسودة): ٣ قرارات مقترحة — راجع لوحة الموافقات. " + "لا يُرسل هذا النص تلقائياً من المنصة في MVP." + ), + "demo": True, + } diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/workflow_runner.py b/dealix/auto_client_acquisition/autonomous_service_operator/workflow_runner.py new file mode 100644 index 00000000..26fc86b0 --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/workflow_runner.py @@ -0,0 +1,24 @@ +"""Simple workflow state machine: intake → draft → pending_approval → proof.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.autonomous_service_operator import session_state as ss + + +def advance(session_id: str, event: str) -> dict[str, Any]: + """event: start_service | draft_ready | submit_for_approval | proof_ready""" + s = ss.touch_session(session_id) + state = str(s.get("workflow_state") or "idle") + ev = (event or "").strip().lower() + transitions: dict[tuple[str, str], str] = { + ("idle", "start_service"): "intake", + ("intake", "draft_ready"): "draft", + ("draft", "submit_for_approval"): "pending_approval", + ("pending_approval", "proof_ready"): "proof", + ("proof", "start_service"): "intake", + } + key = (state, ev) + new_state = transitions.get(key, state) + return ss.upsert_session(session_id, {"workflow_state": new_state, "last_event": ev}) diff --git a/dealix/auto_client_acquisition/connectors/__init__.py b/dealix/auto_client_acquisition/connectors/__init__.py index 999fb82c..cbbc4bb6 100644 --- a/dealix/auto_client_acquisition/connectors/__init__.py +++ b/dealix/auto_client_acquisition/connectors/__init__.py @@ -1 +1,5 @@ -"""Connector modules for Dealix Lead Machine — legal, public-data sources.""" +"""Connector catalog for Growth Control Tower — metadata only.""" + +from auto_client_acquisition.connectors.connector_catalog import build_connector_catalog + +__all__ = ["build_connector_catalog"] diff --git a/dealix/auto_client_acquisition/connectors/connector_catalog.py b/dealix/auto_client_acquisition/connectors/connector_catalog.py new file mode 100644 index 00000000..d32caa42 --- /dev/null +++ b/dealix/auto_client_acquisition/connectors/connector_catalog.py @@ -0,0 +1,45 @@ +"""12+ connectors with capabilities and risk — no live OAuth in MVP.""" + +from __future__ import annotations + +from typing import Any + + +def _row( + cid: str, + label_ar: str, + beta: str, + allowed: list[str], + blocked: list[str], + risk: str, +) -> dict[str, Any]: + return { + "id": cid, + "label_ar": label_ar, + "beta_status": beta, + "required_permissions_ar": "OAuth أو مفاتيح رسمية حسب المزود — لا تخزين أسرار في الريبو.", + "allowed_actions": allowed, + "blocked_actions": blocked, + "risk_level": risk, + "launch_phase": 1 if beta == "mvp" else 2 if beta == "pilot" else 3, + } + + +_CONNECTORS: list[dict[str, Any]] = [ + _row("whatsapp", "واتساب للأعمال", "pilot", ["draft", "template_preview"], ["cold_bulk", "live_send"], "high"), + _row("gmail", "Gmail", "pilot", ["draft_create", "read_limited"], ["send_live"], "medium"), + _row("google_calendar", "Google Calendar", "pilot", ["draft_event"], ["insert_live"], "medium"), + _row("google_meet", "Google Meet", "planned", ["transcript_read"], ["record_without_consent"], "high"), + _row("linkedin_lead_forms", "LinkedIn Lead Gen", "mvp", ["lead_ingest"], ["auto_dm"], "medium"), + _row("x_api", "X API", "registered_only", [], ["firehose", "auto_reply"], "high"), + _row("instagram_graph", "Instagram Graph", "registered_only", [], ["auto_dm"], "high"), + _row("google_business_profile", "Google Business Profile", "registered_only", ["review_draft"], ["auto_reply"], "medium"), + _row("google_sheets", "Google Sheets", "planned", ["read_range_draft"], ["write_live"], "low"), + _row("crm", "CRM عام", "planned", ["sync_draft"], ["delete_records"], "medium"), + _row("moyasar", "Moyasar", "mvp", ["payment_link_draft"], ["charge_live"], "high"), + _row("website_forms", "نماذج موقع", "mvp", ["webhook_ingest"], ["scraping"], "low"), +] + + +def build_connector_catalog() -> dict[str, Any]: + return {"connectors": list(_CONNECTORS), "count": len(_CONNECTORS), "demo": True} diff --git a/dealix/auto_client_acquisition/customer_ops/__init__.py b/dealix/auto_client_acquisition/customer_ops/__init__.py new file mode 100644 index 00000000..03ef7197 --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/__init__.py @@ -0,0 +1,6 @@ +"""Customer operations: onboarding, SLA, connectors, incidents (deterministic stubs).""" + +from auto_client_acquisition.customer_ops.onboarding_checklist import build_onboarding_checklist +from auto_client_acquisition.customer_ops.sla_tracker import build_sla_summary + +__all__ = ["build_onboarding_checklist", "build_sla_summary"] diff --git a/dealix/auto_client_acquisition/customer_ops/connector_setup_status.py b/dealix/auto_client_acquisition/customer_ops/connector_setup_status.py new file mode 100644 index 00000000..81f18906 --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/connector_setup_status.py @@ -0,0 +1,43 @@ +"""Connector readiness matrix (demo / staging oriented).""" + +from __future__ import annotations + +from typing import Any + + +def build_connector_status() -> dict[str, Any]: + return { + "connectors": [ + { + "id": "whatsapp", + "name_ar": "واتساب", + "status": "draft_only", + "notes_ar": "الإرسال الحي يتطلب opt-in وسياسة وموافقة.", + }, + { + "id": "gmail", + "name_ar": "Gmail", + "status": "draft_ready", + "notes_ar": "المسودات أولاً؛ الإرسال محظور افتراضياً.", + }, + { + "id": "google_calendar", + "name_ar": "Google Calendar", + "status": "draft_ready", + "notes_ar": "إدراج الحدث يتطلب موافقة.", + }, + { + "id": "moyasar", + "name_ar": "Moyasar", + "status": "manual_or_sandbox", + "notes_ar": "روابط دفع/فواتير يدوية أو sandbox؛ لا charge من المنصة افتراضياً.", + }, + { + "id": "linkedin_lead_forms", + "name_ar": "LinkedIn Lead Gen", + "status": "strategy_only", + "notes_ar": "لا scraping؛ نماذج رسمية وإعلانات ومهام يدوية معتمدة.", + }, + ], + "summary_ar": "الوضع الافتراضي: مسودات وموافقات؛ لا توسيع live قبل staging واتفاق العميل.", + } diff --git a/dealix/auto_client_acquisition/customer_ops/customer_success_cadence.py b/dealix/auto_client_acquisition/customer_ops/customer_success_cadence.py new file mode 100644 index 00000000..bf795a0c --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/customer_success_cadence.py @@ -0,0 +1,23 @@ +"""Weekly cadence for pilots (deterministic).""" + +from __future__ import annotations + +from typing import Any + + +def build_weekly_cadence() -> dict[str, Any]: + return { + "weekly_touchpoints_ar": [ + "مراجعة كروت الموافقة المعلقة.", + "تحديث Proof Pack (مسودات، موافقات، مخاطر ممنوعة).", + "مكالمة قصيرة أو تحديث كتابي مع صاحب القرار.", + "قراءة مؤشرات القنوات (ردود، شكاوى، opt-out = صفر مطلوب).", + ], + "metrics_to_track": [ + "demos_booked", + "pilots_active", + "drafts_approved", + "risks_blocked", + "proof_events", + ], + } diff --git a/dealix/auto_client_acquisition/customer_ops/incident_router.py b/dealix/auto_client_acquisition/customer_ops/incident_router.py new file mode 100644 index 00000000..7ecbfefc --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/incident_router.py @@ -0,0 +1,25 @@ +"""Incident routing stub (no paging, no secrets).""" + +from __future__ import annotations + +from typing import Any + + +def build_incident_playbook() -> dict[str, Any]: + return { + "steps_ar": [ + "تصنيف الخطورة (P0–P3) وفق وصف الحادث.", + "إيقاف أي إجراء live إن وُجد حتى التحقق.", + "توثيق الوقت، التأثير، والخطوات المتخذة (بدون أسرار أو PII خام).", + "إشعار العميل بلغة واضحة وخطة تعافي.", + "مراجعة لاحقة وتحديث السياسات/الاختبارات إن لزم.", + ], + "contacts_placeholder_ar": "يُحدَّد في العقد: بريد دعم + قناة طوارئ للـ P0.", + } + + +def classify_incident(severity: str) -> dict[str, Any]: + s = (severity or "P3").upper() + if s not in {"P0", "P1", "P2", "P3"}: + s = "P3" + return {"severity": s, "escalate": s in {"P0", "P1"}} diff --git a/dealix/auto_client_acquisition/customer_ops/onboarding_checklist.py b/dealix/auto_client_acquisition/customer_ops/onboarding_checklist.py new file mode 100644 index 00000000..2f942a78 --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/onboarding_checklist.py @@ -0,0 +1,23 @@ +"""Onboarding checklist for pilots (deterministic, no external calls).""" + +from __future__ import annotations + +from typing import Any + + +def build_onboarding_checklist(service_id: str | None = None) -> dict[str, Any]: + sid = (service_id or "growth_starter").strip() or "growth_starter" + return { + "service_id": sid, + "steps_ar": [ + "تأكيد الهدف (عملاء جدد / قائمة / شراكات / تشغيل يومي).", + "جمع بيانات الشركة: القطاع، المدينة، العرض، رابط الموقع.", + "تحديد القنوات المتاحة (إيميل، واتساب opt-in، CRM، نماذج).", + "رفع قائمة اختيارية أو تأكيد عدم وجود قائمة.", + "مراجعة سياسة الموافقات وعدم الإرسال الحي الافتراضي.", + "تشغيل أول مهمة (تشخيص أو 10 فرص أو List Intelligence).", + "تسليم أول Proof Pack أو ملخص أثر خلال النافذة المتفق عليها.", + ], + "approval_required": True, + "live_send_default": False, + } diff --git a/dealix/auto_client_acquisition/customer_ops/sla_tracker.py b/dealix/auto_client_acquisition/customer_ops/sla_tracker.py new file mode 100644 index 00000000..ec0906dd --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/sla_tracker.py @@ -0,0 +1,37 @@ +"""SLA summary for support tiers (static policy text + JSON for API).""" + +from __future__ import annotations + +from typing import Any + + +def build_sla_summary() -> dict[str, Any]: + return { + "tiers": [ + { + "id": "P0", + "name_ar": "أمان / إرسال خاطئ / توقف كامل", + "first_response_hours": 2, + "resolution_target_hours": 8, + }, + { + "id": "P1", + "name_ar": "تعطل خدمة أساسية", + "first_response_hours": 4, + "resolution_target_hours": 24, + }, + { + "id": "P2", + "name_ar": "تكامل أو Proof متأخر", + "first_response_hours": 24, + "resolution_target_hours": 72, + }, + { + "id": "P3", + "name_ar": "سؤال أو تحسين", + "first_response_hours": 48, + "resolution_target_hours": 120, + }, + ], + "notes_ar": "الأرقام أهداف تشغيلية للـ Pilot؛ تُحدّث في العقد/Appendix عند التوسع.", + } diff --git a/dealix/auto_client_acquisition/customer_ops/support_ticket_router.py b/dealix/auto_client_acquisition/customer_ops/support_ticket_router.py new file mode 100644 index 00000000..485e6022 --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/support_ticket_router.py @@ -0,0 +1,16 @@ +"""Map issue text to priority bucket (deterministic heuristics).""" + +from __future__ import annotations + +from typing import Any + + +def route_ticket(issue_ar: str) -> dict[str, Any]: + t = (issue_ar or "").lower() + if any(k in t for k in ("أرسل", "إرسال", "send", "live", "خرق", "سر")): + return {"priority": "P0", "queue_ar": "أمان وتشغيل", "sla_first_response_hours": 2} + if any(k in t for k in ("تعطل", "502", "500", "error", "خطأ")): + return {"priority": "P1", "queue_ar": "تشغيل", "sla_first_response_hours": 4} + if any(k in t for k in ("connector", "ربط", "تكامل", "proof", "تقرير")): + return {"priority": "P2", "queue_ar": "تكامل ونجاح عميل", "sla_first_response_hours": 24} + return {"priority": "P3", "queue_ar": "عام", "sla_first_response_hours": 48} diff --git a/dealix/auto_client_acquisition/growth_curator/__init__.py b/dealix/auto_client_acquisition/growth_curator/__init__.py new file mode 100644 index 00000000..43619a3f --- /dev/null +++ b/dealix/auto_client_acquisition/growth_curator/__init__.py @@ -0,0 +1,6 @@ +"""Growth curator — deterministic grading and weekly report (no live sends).""" + +from auto_client_acquisition.growth_curator.curator_report import build_weekly_curator_report +from auto_client_acquisition.growth_curator.message_curator import grade_message + +__all__ = ["build_weekly_curator_report", "grade_message"] diff --git a/dealix/auto_client_acquisition/growth_curator/curator_report.py b/dealix/auto_client_acquisition/growth_curator/curator_report.py new file mode 100644 index 00000000..4ec69859 --- /dev/null +++ b/dealix/auto_client_acquisition/growth_curator/curator_report.py @@ -0,0 +1,19 @@ +"""Weekly curator narrative — Arabic, deterministic.""" + +from __future__ import annotations + +from typing import Any + + +def build_weekly_curator_report(context: dict[str, Any] | None = None) -> dict[str, Any]: + ctx = context or {} + return { + "week_label_ar": str(ctx.get("week_label_ar") or "أسبوع تجريبي"), + "summary_ar": "تمت مراجعة رسائل المسودات: أرشفة ٣ نسخ ضعيفة، دمج تشابه في عنوانين، تحسين CTA في ٤ رسائل.", + "actions_ar": [ + "حافظ على سؤال واحد لكل رسالة واتساب.", + "قلل الوعود المطلقة في البريد.", + "فعّل متابعة ٤٨ ساعة بعد الاجتماع فقط بعد الموافقة.", + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/growth_curator/message_curator.py b/dealix/auto_client_acquisition/growth_curator/message_curator.py new file mode 100644 index 00000000..589ad5ef --- /dev/null +++ b/dealix/auto_client_acquisition/growth_curator/message_curator.py @@ -0,0 +1,28 @@ +"""Grade Arabic outreach messages — heuristic MVP.""" + +from __future__ import annotations + +from typing import Any + + +def grade_message(message_ar: str, *, sector: str = "", channel: str = "whatsapp") -> dict[str, Any]: + text = (message_ar or "").strip() + score = 70 + notes: list[str] = [] + if len(text) < 40: + score -= 15 + notes.append("قصير جداً — أضف سياقاً ولماذا الآن.") + if len(text) > 900: + score -= 10 + notes.append("طويل — قصّر للواتساب/المتابعة السريعة.") + if "ضمان" in text or "مضمون" in text or "100%" in text: + score -= 20 + notes.append("تجنب وعود مطلقة — خطر امتثال.") + if "؟" not in text and "?" not in text: + score -= 5 + notes.append("أضف سؤالاً واحداً واضحاً لزيادة الرد.") + if channel == "email" and "السلام" not in text and "عليكم" not in text: + notes.append("افتتح بتحية مهنية للبريد.") + score = max(0, min(100, score)) + band = "strong" if score >= 80 else "ok" if score >= 60 else "weak" + return {"score": score, "band": band, "notes_ar": notes, "sector": sector, "channel": channel, "demo": True} diff --git a/dealix/auto_client_acquisition/growth_curator/mission_curator.py b/dealix/auto_client_acquisition/growth_curator/mission_curator.py new file mode 100644 index 00000000..5f7f595b --- /dev/null +++ b/dealix/auto_client_acquisition/growth_curator/mission_curator.py @@ -0,0 +1,18 @@ +"""Mission / playbook curation suggestions — no automatic deletion.""" + +from __future__ import annotations + +from typing import Any + + +def curate_missions_weekly() -> dict[str, Any]: + return { + "merged_pairs_ar": ["book_three_meetings + followup_sequence → دمج عنوان الخطوات"], + "archived_ids": ["deprecated_template_v1"], + "next_week_focus_ar": "زيادة Pilot 7 أيام في قطاع التدريب", + "demo": True, + } + + +def score_mission_popularity(mission_id: str) -> dict[str, Any]: + return {"mission_id": mission_id, "popularity_score": 81 if "10" in mission_id else 55, "demo": True} diff --git a/dealix/auto_client_acquisition/growth_curator/playbook_curator.py b/dealix/auto_client_acquisition/growth_curator/playbook_curator.py new file mode 100644 index 00000000..f11a69a6 --- /dev/null +++ b/dealix/auto_client_acquisition/growth_curator/playbook_curator.py @@ -0,0 +1,18 @@ +"""Playbook merge hints — deterministic stub.""" + +from __future__ import annotations + +from typing import Any + + +def suggest_playbook_merge(playbooks: list[dict[str, Any]]) -> dict[str, Any]: + """If two titles share same first word, suggest merge (demo).""" + if len(playbooks) < 2: + return {"merge_groups": [], "demo": True} + titles = [str(p.get("title_ar") or p.get("title") or "") for p in playbooks] + merge_groups: list[list[int]] = [] + for i, a in enumerate(titles): + for j in range(i + 1, len(titles)): + if a and titles[j] and a.split()[:1] == titles[j].split()[:1] and a.split()[:1]: + merge_groups.append([i, j]) + return {"merge_groups": merge_groups[:3], "demo": True} diff --git a/dealix/auto_client_acquisition/growth_curator/skill_inventory.py b/dealix/auto_client_acquisition/growth_curator/skill_inventory.py new file mode 100644 index 00000000..6672c82f --- /dev/null +++ b/dealix/auto_client_acquisition/growth_curator/skill_inventory.py @@ -0,0 +1,22 @@ +"""Curated list of playbook/message skills — deterministic inventory.""" + +from __future__ import annotations + +from typing import Any + + +def list_skill_inventory() -> dict[str, Any]: + skills: list[dict[str, Any]] = [ + {"id": "saudi_short_pitch", "score": 88, "usage_count_demo": 42, "status": "active"}, + {"id": "objection_timing", "score": 72, "usage_count_demo": 18, "status": "active"}, + {"id": "cold_whatsapp_template", "score": 12, "usage_count_demo": 3, "status": "archived", "reason_ar": "مخالف سياسة القناة"}, + ] + return {"skills": skills, "recommendation_ar": "أرشف القوالب منخفضة الدرجة وادمج المتشابه.", "demo": True} + + +def score_skill(skill_id: str) -> dict[str, Any]: + inv = list_skill_inventory() + for s in inv["skills"]: + if s["id"] == skill_id: + return {"skill": s, "demo": True} + return {"error": "not_found", "demo": True} diff --git a/dealix/auto_client_acquisition/integrations/__init__.py b/dealix/auto_client_acquisition/integrations/__init__.py new file mode 100644 index 00000000..e975fe51 --- /dev/null +++ b/dealix/auto_client_acquisition/integrations/__init__.py @@ -0,0 +1 @@ +"""Draft-only integration helpers (no OAuth, no network) — Growth Control Tower.""" diff --git a/dealix/auto_client_acquisition/integrations/calendar_operator.py b/dealix/auto_client_acquisition/integrations/calendar_operator.py new file mode 100644 index 00000000..9d33cf0d --- /dev/null +++ b/dealix/auto_client_acquisition/integrations/calendar_operator.py @@ -0,0 +1,24 @@ +"""Google Calendar API-shaped draft payloads — no OAuth, no HTTP.""" + +from __future__ import annotations + +from typing import Any + + +def build_calendar_draft_payload(params: dict[str, Any]) -> dict[str, Any]: + """ + Minimal ``events.insert``-like resource for review only. + """ + summary = str(params.get("summary_ar") or params.get("summary") or "اجتماع متابعة — Dealix") + start = str(params.get("start_iso") or "2026-05-02T10:00:00+03:00") + end = str(params.get("end_iso") or "2026-05-02T10:30:00+03:00") + return { + "approval_required": True, + "event": { + "summary": summary, + "start": {"dateTime": start, "timeZone": "Asia/Riyadh"}, + "end": {"dateTime": end, "timeZone": "Asia/Riyadh"}, + "attendees": params.get("attendees") if isinstance(params.get("attendees"), list) else [], + }, + "note_ar": "مسودة حدث فقط — لا يُنشأ في تقويم Google في MVP.", + } diff --git a/dealix/auto_client_acquisition/integrations/gmail_operator.py b/dealix/auto_client_acquisition/integrations/gmail_operator.py new file mode 100644 index 00000000..35588e77 --- /dev/null +++ b/dealix/auto_client_acquisition/integrations/gmail_operator.py @@ -0,0 +1,28 @@ +"""Gmail API-shaped draft payloads — no OAuth, no HTTP.""" + +from __future__ import annotations + +import base64 +from email.message import EmailMessage +from typing import Any + + +def build_gmail_draft_payload(params: dict[str, Any]) -> dict[str, Any]: + """ + Returns ``{"message": {"raw": ""}}`` subset compatible with + Gmail ``users.drafts.create`` — encoding only, no API call. + """ + to = str(params.get("to") or "prospect@example.com") + subject = str(params.get("subject_ar") or params.get("subject") or "مسودة — Dealix") + body_text = str(params.get("body_ar") or params.get("body") or "نص المسودة الداخلي.") + msg = EmailMessage() + msg["To"] = to + msg["Subject"] = subject + msg.set_content(body_text, charset="utf-8") + raw_bytes = msg.as_bytes() + raw_b64 = base64.urlsafe_b64encode(raw_bytes).decode("ascii").rstrip("=") + return { + "approval_required": True, + "message": {"raw": raw_b64}, + "note_ar": "هيكل مسودة فقط — لا يُرسل عبر Gmail API في MVP.", + } diff --git a/dealix/auto_client_acquisition/integrations/moyasar_draft.py b/dealix/auto_client_acquisition/integrations/moyasar_draft.py new file mode 100644 index 00000000..9082f729 --- /dev/null +++ b/dealix/auto_client_acquisition/integrations/moyasar_draft.py @@ -0,0 +1,53 @@ +"""Moyasar payment resource draft — halalas validation only, no API calls.""" + +from __future__ import annotations + +from typing import Any + +# SAR minor units per Moyasar docs (amount in halalas / smallest currency unit). + + +def build_moyasar_payment_draft(params: dict[str, Any]) -> dict[str, Any]: + """ + Validates ``amount`` as integer halalas (>= 100 typical minimum for tests). + Returns a create-payment shaped dict without calling Moyasar. + """ + raw = params.get("amount_halalas", params.get("amount")) + errors: list[str] = [] + amount: int | None = None + try: + if raw is None: + errors.append("amount_halalas_required") + else: + amount = int(raw) + if amount < 1: + errors.append("amount_must_be_positive_integer_halalas") + except (TypeError, ValueError): + errors.append("amount_must_be_integer_halalas") + amount = None + + if errors: + return {"approval_required": True, "valid": False, "errors": errors, "payload": None, "payment_link_draft": None} + + currency = str(params.get("currency") or "SAR").upper() + invoice_ref = str(params.get("invoice_reference") or params.get("invoice_id") or f"INV-DEMO-{amount}") + # Shape-only checkout URL — replace base with real merchant page when integrating. + base = str(params.get("payment_link_base") or "https://api.moyasar.com/v1/payments") + payment_link_draft = f"{base}?amount={amount}¤cy={currency}&description={invoice_ref}" + + payload: dict[str, Any] = { + "amount": amount, + "currency": currency, + "source": params.get("source") if isinstance(params.get("source"), dict) else {"type": "creditcard"}, + "description": str(params.get("description_ar") or params.get("description") or "Dealix draft"), + "metadata": {"invoice_reference": invoice_ref}, + } + return { + "approval_required": True, + "valid": True, + "errors": [], + "payload": payload, + "payment_link_draft": payment_link_draft, + "invoice_reference": invoice_ref, + "note_ar": "مسودة تحقق فقط — لا يُنشأ دفع عبر Moyasar في MVP؛ الرابط للعرض الشكلي فقط.", + } diff --git a/dealix/auto_client_acquisition/intelligence_layer/__init__.py b/dealix/auto_client_acquisition/intelligence_layer/__init__.py new file mode 100644 index 00000000..2897762f --- /dev/null +++ b/dealix/auto_client_acquisition/intelligence_layer/__init__.py @@ -0,0 +1,27 @@ +"""Intelligence layer — deterministic JSON, optional bridge to innovation.""" + +from auto_client_acquisition.intelligence_layer.action_graph import build_action_graph_trace +from auto_client_acquisition.intelligence_layer.board_brief import build_board_brief +from auto_client_acquisition.intelligence_layer.competitive_moves import build_competitive_moves +from auto_client_acquisition.intelligence_layer.decision_memory import list_decisions, record_decision +from auto_client_acquisition.intelligence_layer.growth_brain import build_growth_profile +from auto_client_acquisition.intelligence_layer.intel_command_feed import build_intel_command_feed +from auto_client_acquisition.intelligence_layer.mission_engine import get_mission, list_mission_catalog +from auto_client_acquisition.intelligence_layer.opportunity_simulator import simulate_opportunities +from auto_client_acquisition.intelligence_layer.revenue_dna import build_revenue_dna +from auto_client_acquisition.intelligence_layer.trust_score import compute_trust_score + +__all__ = [ + "build_action_graph_trace", + "build_board_brief", + "build_competitive_moves", + "build_growth_profile", + "build_intel_command_feed", + "build_revenue_dna", + "compute_trust_score", + "get_mission", + "list_decisions", + "list_mission_catalog", + "record_decision", + "simulate_opportunities", +] diff --git a/dealix/auto_client_acquisition/intelligence_layer/action_graph.py b/dealix/auto_client_acquisition/intelligence_layer/action_graph.py new file mode 100644 index 00000000..82c40555 --- /dev/null +++ b/dealix/auto_client_acquisition/intelligence_layer/action_graph.py @@ -0,0 +1,35 @@ +"""Deterministic action graph: signal → policy → approval → outcome → proof (demo).""" + +from __future__ import annotations + +from typing import Any + + +def build_action_graph_trace(payload: dict[str, Any] | None = None) -> dict[str, Any]: + """ + Returns nodes/edges for UI or docs — no execution. + """ + p = payload or {} + signal = str(p.get("signal_type") or "lead_received") + nodes = [ + {"id": "n1", "kind": "signal", "label_ar": f"إشارة: {signal}"}, + {"id": "n2", "kind": "context", "label_ar": "بناء سياق (شركة، قناة، مصدر)"}, + {"id": "n3", "kind": "policy", "label_ar": "تقييم سياسة القناة"}, + {"id": "n4", "kind": "approval", "label_ar": "موافقة بشرية"}, + {"id": "n5", "kind": "draft_or_block", "label_ar": "مسودة أو منع"}, + {"id": "n6", "kind": "proof", "label_ar": "تسجيل في Proof Ledger"}, + ] + edges = [ + {"from": "n1", "to": "n2", "label": "enrich"}, + {"from": "n2", "to": "n3", "label": "evaluate"}, + {"from": "n3", "to": "n4", "label": "if_external"}, + {"from": "n4", "to": "n5", "label": "on_approve"}, + {"from": "n5", "to": "n6", "label": "record"}, + ] + return { + "signal_type": signal, + "nodes": nodes, + "edges": edges, + "note_ar": "عرض منطقي فقط — لا ينفّذ أدوات خارجية.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/intelligence_layer/board_brief.py b/dealix/auto_client_acquisition/intelligence_layer/board_brief.py new file mode 100644 index 00000000..847f9d6a --- /dev/null +++ b/dealix/auto_client_acquisition/intelligence_layer/board_brief.py @@ -0,0 +1,19 @@ +"""Executive board brief — Arabic headline + bullets.""" + +from __future__ import annotations + +from typing import Any + + +def build_board_brief(snapshot: dict[str, Any] | None) -> dict[str, Any]: + sn = snapshot or {} + title = str(sn.get("title_ar") or "موجز أسبوعي — Dealix") + return { + "title_ar": title, + "bullets_ar": [ + "زخم الصفقات: مستقر مع حاجة لمتابعة ما بعد الاجتماع.", + "الامتثال: لا إرسال جماعي حتى اكتمال opt-in.", + "الفرص: ركّز على قطاعين بدل تشتيت ICP.", + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/intelligence_layer/competitive_moves.py b/dealix/auto_client_acquisition/intelligence_layer/competitive_moves.py new file mode 100644 index 00000000..560d6092 --- /dev/null +++ b/dealix/auto_client_acquisition/intelligence_layer/competitive_moves.py @@ -0,0 +1,18 @@ +"""Safe competitive move suggestions (display-only).""" + +from __future__ import annotations + +from typing import Any + + +def build_competitive_moves(sector: str | None = None) -> dict[str, Any]: + sec = sector or "عام" + return { + "sector": sec, + "moves_ar": [ + "تضييق رسالة القيمة على نتيجة واحدة قابلة للقياس لكل عميل.", + "عرض تجربة ٧ أيام مع حدود واضحة للنطاق وتقرير إثبات أسبوعي.", + "تفعيل غرفة صفقة مشتركة مع SLA داخلي ٢٤ ساعة للرد.", + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/intelligence_layer/decision_memory.py b/dealix/auto_client_acquisition/intelligence_layer/decision_memory.py new file mode 100644 index 00000000..d28c45a6 --- /dev/null +++ b/dealix/auto_client_acquisition/intelligence_layer/decision_memory.py @@ -0,0 +1,24 @@ +"""In-memory decision snippets for demos — replace with DB in production.""" + +from __future__ import annotations + +from typing import Any + +_STORE: list[dict[str, Any]] = [] + + +def record_decision(entry: dict[str, Any]) -> dict[str, Any]: + e = { + "id": f"dec_{len(_STORE)+1}", + **entry, + } + _STORE.append(e) + return {"ok": True, "entry": e, "demo": True} + + +def list_decisions(*, limit: int = 20) -> dict[str, Any]: + return {"decisions": list(reversed(_STORE[-limit:])), "count": len(_STORE), "demo": True} + + +def reset_demo_memory() -> None: + _STORE.clear() diff --git a/dealix/auto_client_acquisition/intelligence_layer/growth_brain.py b/dealix/auto_client_acquisition/intelligence_layer/growth_brain.py new file mode 100644 index 00000000..667ed66f --- /dev/null +++ b/dealix/auto_client_acquisition/intelligence_layer/growth_brain.py @@ -0,0 +1,82 @@ +"""Growth profile from JSON — no LLM required for MVP.""" + +from __future__ import annotations + +import hashlib +import json +from typing import Any + +_DEFAULT_BLOCKED = ( + "cold_whatsapp", + "auto_linkedin_dm", + "bulk_send_without_approval", + "purchased_list_bulk", +) + + +def _growth_brain_id(company: dict[str, Any]) -> str: + payload = json.dumps(company, ensure_ascii=False, sort_keys=True, default=str) + h = hashlib.sha256(payload.encode("utf-8")).hexdigest()[:20] + return f"gb_{h}" + + +def build_growth_profile(company: dict[str, Any] | None) -> dict[str, Any]: + c = company or {} + company_name = str(c.get("company_name") or c.get("name") or "غير مسمّى") + sector = str(c.get("sector") or "غير محدد") + city = str(c.get("city") or "الرياض") + goal_ar = str(c.get("goal_ar") or c.get("goal") or "تسريع خط أنابيب المبيعات") + icp_hint_ar = str(c.get("icp_hint_ar") or "قرارات شراء في المؤسسات متوسطة الحجم") + risk = str(c.get("risk_tolerance") or c.get("risk") or "medium").lower() + channels_in = c.get("channels") + channels: list[str] = [] + if isinstance(channels_in, list): + channels = [str(x).strip().lower() for x in channels_in if str(x).strip()] + + blocked = list(_DEFAULT_BLOCKED) + if risk == "low": + blocked = ["cold_whatsapp", "purchased_list_bulk", "auto_linkedin_dm"] + elif risk == "high": + blocked = list(_DEFAULT_BLOCKED) + ["unsupervised_payment_capture"] + + tone = "professional_saudi_short" + if risk == "low": + tone = "warm_saudi_concise" + elif risk == "high": + tone = "formal_saudi_minimal" + + recommended_first_mission = "ten_in_ten_opportunities" + if c.get("recommended_first_mission"): + recommended_first_mission = str(c.get("recommended_first_mission")) + + seed_obj = { + "company_name": company_name, + "sector": sector, + "city": city, + "goal_ar": goal_ar, + "channels": channels, + "risk_tolerance": risk, + } + return { + "growth_brain_id": _growth_brain_id(seed_obj), + "company_name": company_name, + "sector": sector, + "city": city, + "goal_ar": goal_ar, + "icp_hint_ar": icp_hint_ar, + "channels_connected": channels or ["whatsapp", "email"], + "blocked_actions": blocked, + "recommended_first_mission": recommended_first_mission, + "tone": tone, + "best_segments": _suggest_segments(sector), + "demo": True, + } + + +def _suggest_segments(sector: str) -> list[str]: + s = sector.lower() + if "training" in s or "تدريب" in s or "consult" in s: + return ["مدراء الموارد البشرية", "مدراء المبيعات", "رؤساء التعلم والتطوير"] + if "health" in s or "صح" in s or "clinic" in s: + return ["مدراء العيادات", "مشتريات طبية", "عمليات"] + return ["صناع القرار المالي", "مدراء المشتريات", "العمليات"] diff --git a/dealix/auto_client_acquisition/intelligence_layer/intel_command_feed.py b/dealix/auto_client_acquisition/intelligence_layer/intel_command_feed.py new file mode 100644 index 00000000..09c27bdf --- /dev/null +++ b/dealix/auto_client_acquisition/intelligence_layer/intel_command_feed.py @@ -0,0 +1,99 @@ +"""Intel-specific command cards — unified schema for Arabic decision UI.""" + +from __future__ import annotations + +from typing import Any + + +def normalize_intel_card(raw: dict[str, Any]) -> dict[str, Any]: + """ + Unified card shape: Arabic titles, <=3 buttons, no live actions. + + Accepts either pre-normalized dicts or legacy keys (``cta_ar``, ``summary_ar``). + """ + title_ar = str(raw.get("title_ar") or "") + summary_ar = str(raw.get("summary_ar") or "") + why_it_matters_ar = str(raw.get("why_it_matters_ar") or summary_ar) + recommended_action_ar = str( + raw.get("recommended_action_ar") + or raw.get("suggested_action") + or raw.get("cta_ar") + or raw.get("cta") + or "مراجعة المسودة" + ) + risk_level = str(raw.get("risk_level") or "medium") + try: + expected_impact_sar = float(raw.get("expected_impact_sar") or 0) + except (TypeError, ValueError): + expected_impact_sar = 0.0 + + buttons_in = raw.get("buttons") + buttons: list[dict[str, str]] = [] + if isinstance(buttons_in, list) and buttons_in and isinstance(buttons_in[0], dict): + for b in buttons_in[:3]: + label = str(b.get("label_ar") or b.get("label") or "") + aid = str(b.get("action_id") or b.get("id") or "action") + if label: + buttons.append({"label_ar": label, "action_id": aid}) + elif isinstance(buttons_in, list) and buttons_in and isinstance(buttons_in[0], str): + for i, label in enumerate(buttons_in[:3]): + buttons.append({"label_ar": str(label), "action_id": f"btn_{i}"}) + else: + cta = str(raw.get("cta_ar") or raw.get("cta") or "مراجعة") + buttons = [ + {"label_ar": cta, "action_id": "primary"}, + {"label_ar": "تعديل", "action_id": "edit"}, + {"label_ar": "تخطي", "action_id": "skip"}, + ] + + return { + "type": str(raw.get("type") or "generic"), + "title_ar": title_ar, + "summary_ar": summary_ar, + "why_it_matters_ar": why_it_matters_ar, + "recommended_action_ar": recommended_action_ar, + "risk_level": risk_level, + "expected_impact_sar": expected_impact_sar, + "buttons": buttons[:3], + "approval_required": bool(raw.get("approval_required", True)), + } + + +def build_intel_command_feed(context: dict[str, Any] | None = None) -> dict[str, Any]: + ctx = context or {} + raw_cards: list[dict[str, Any]] = [ + { + "type": "revenue_leak", + "title_ar": "تسرب إيراد — غياب متابعة موحّدة", + "summary_ar": "ثلاث صفقات بدون موعد تالي خلال ٧ أيام؛ خطر فقدان ١٥٪ من القيمة الموزونة.", + "why_it_matters_ar": "التأخير يقلل احتمال الرد والإغلاق خلال نافذة الشراء.", + "recommended_action_ar": "جدولة متابعة قصيرة مع خيار إيقاف.", + "risk_level": "medium", + "expected_impact_sar": 42000.0, + "buttons": [ + {"label_ar": "اعتمد", "action_id": "approve"}, + {"label_ar": "عدّل", "action_id": "edit"}, + {"label_ar": "تخطي", "action_id": "skip"}, + ], + "approval_required": True, + }, + { + "type": "board_brief", + "title_ar": "موجز للمجلس — قرار واحد هذا الأسبوع", + "summary_ar": "الموافقة على نطاق pilot ثانٍ في قطاع الصحة أو تجميد التوسع لصيانة الجودة.", + "why_it_matters_ar": "التركيز يحمي الجودة ويُسرّع إثبات القيمة للعميل.", + "recommended_action_ar": "عرض موجز قرار واحد صفحة واحدة.", + "risk_level": "low", + "expected_impact_sar": 0.0, + "buttons": [ + {"label_ar": "عرض الموجز", "action_id": "open_brief"}, + {"label_ar": "تأجيل", "action_id": "snooze"}, + {"label_ar": "إغلاق", "action_id": "dismiss"}, + ], + "approval_required": True, + }, + ] + if ctx.get("append_custom") and isinstance(ctx["append_custom"], dict): + raw_cards.append(ctx["append_custom"]) + cards = [normalize_intel_card(x) for x in raw_cards] + return {"cards": cards, "source": "intelligence_layer", "demo": True} diff --git a/dealix/auto_client_acquisition/intelligence_layer/mission_engine.py b/dealix/auto_client_acquisition/intelligence_layer/mission_engine.py new file mode 100644 index 00000000..470e4c5a --- /dev/null +++ b/dealix/auto_client_acquisition/intelligence_layer/mission_engine.py @@ -0,0 +1,51 @@ +"""Mission catalog — references innovation missions without duplicating HTTP.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.innovation.growth_missions import list_growth_missions + + +_MISSIONS_META: list[dict[str, Any]] = [ + { + "id": "first_10_opportunities", + "title_ar": "10 فرص في 10 دقائق", + "canonical_http": "POST /api/v1/intelligence/missions/first-10-opportunities", + "safety_rules_ar": ["لا واتساب بارد", "موافقة على المسودات"], + "required_integrations": [], + }, + { + "id": "revenue_leak_rescue", + "title_ar": "إنقاذ تسريب إيراد", + "canonical_http": "GET /api/v1/intelligence/command-feed/demo", + "safety_rules_ar": ["مراجعة المصدر", "حد أسبوعي للمسودات"], + "required_integrations": ["gmail_draft"], + }, + { + "id": "partnership_sprint", + "title_ar": "سباق شراكات", + "canonical_http": "POST /api/v1/targeting/linkedin/strategy", + "safety_rules_ar": ["LinkedIn Lead Gen فقط", "لا auto-DM"], + "required_integrations": [], + }, + { + "id": "customer_reactivation", + "title_ar": "إعادة تفعيل عملاء", + "canonical_http": "POST /api/v1/platform/contacts/import-preview", + "safety_rules_ar": ["تصنيف القائمة", "opt-out فوري"], + "required_integrations": [], + }, +] + + +def list_mission_catalog() -> dict[str, Any]: + gm = list_growth_missions() + return {"missions": _MISSIONS_META, "innovation_growth_missions": gm, "demo": True} + + +def get_mission(mission_id: str) -> dict[str, Any]: + for m in _MISSIONS_META: + if m["id"] == mission_id: + return {**m, "found": True} + return {"found": False, "error": "unknown_mission", "demo": True} diff --git a/dealix/auto_client_acquisition/intelligence_layer/opportunity_simulator.py b/dealix/auto_client_acquisition/intelligence_layer/opportunity_simulator.py new file mode 100644 index 00000000..1aba5135 --- /dev/null +++ b/dealix/auto_client_acquisition/intelligence_layer/opportunity_simulator.py @@ -0,0 +1,23 @@ +"""Simple numeric opportunity scenarios.""" + +from __future__ import annotations + +from typing import Any + + +def simulate_opportunities(inputs: dict[str, Any] | None) -> dict[str, Any]: + ins = inputs or {} + pipeline = float(ins.get("pipeline_sar") or 250_000) + win_rate = float(ins.get("win_rate") or 0.18) + forecast = round(pipeline * win_rate, 2) + return { + "pipeline_sar": pipeline, + "win_rate_assumption": win_rate, + "weighted_forecast_sar": forecast, + "scenarios": [ + {"label_ar": "أساسي", "forecast_sar": forecast}, + {"label_ar": "تفاؤل محدود", "forecast_sar": round(forecast * 1.12, 2)}, + {"label_ar": "تحفظ", "forecast_sar": round(forecast * 0.85, 2)}, + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/intelligence_layer/revenue_dna.py b/dealix/auto_client_acquisition/intelligence_layer/revenue_dna.py new file mode 100644 index 00000000..0b520c8f --- /dev/null +++ b/dealix/auto_client_acquisition/intelligence_layer/revenue_dna.py @@ -0,0 +1,16 @@ +"""Revenue DNA snapshot — structured JSON.""" + +from __future__ import annotations + +from typing import Any + + +def build_revenue_dna(context: dict[str, Any] | None) -> dict[str, Any]: + ctx = context or {} + return { + "primary_motion_ar": str(ctx.get("primary_motion_ar") or "مبيعات مباشرة + شراكات"), + "cycle_days_estimate": int(ctx.get("cycle_days_estimate") or 45), + "channels_weight": {"whatsapp": 0.2, "email": 0.35, "meetings": 0.45}, + "risk_notes_ar": str(ctx.get("risk_notes_ar") or "تأخر الموافقات الداخلية لدى العميل."), + "demo": True, + } diff --git a/dealix/auto_client_acquisition/intelligence_layer/trust_score.py b/dealix/auto_client_acquisition/intelligence_layer/trust_score.py new file mode 100644 index 00000000..86b63d33 --- /dev/null +++ b/dealix/auto_client_acquisition/intelligence_layer/trust_score.py @@ -0,0 +1,18 @@ +"""Trust score 0–100 from simple signals.""" + +from __future__ import annotations + +from typing import Any + + +def compute_trust_score(signals: dict[str, Any] | None) -> dict[str, Any]: + s = signals or {} + base = 55 + if s.get("has_signed_dpa"): + base += 15 + if s.get("reply_rate_30d", 0) and float(s["reply_rate_30d"]) > 0.2: + base += 10 + if s.get("compliance_flags"): + base -= 20 + score = max(0, min(100, int(base))) + return {"trust_score": score, "factors": list(s.keys()) or ["default"], "demo": True} diff --git a/dealix/auto_client_acquisition/launch_ops/__init__.py b/dealix/auto_client_acquisition/launch_ops/__init__.py new file mode 100644 index 00000000..f06e72d3 --- /dev/null +++ b/dealix/auto_client_acquisition/launch_ops/__init__.py @@ -0,0 +1,5 @@ +"""Launch operations — private beta offer, demo script, outreach, go/no-go.""" + +from auto_client_acquisition.launch_ops.private_beta import build_private_beta_offer + +__all__ = ["build_private_beta_offer"] diff --git a/dealix/auto_client_acquisition/launch_ops/demo_flow.py b/dealix/auto_client_acquisition/launch_ops/demo_flow.py new file mode 100644 index 00000000..fe009f82 --- /dev/null +++ b/dealix/auto_client_acquisition/launch_ops/demo_flow.py @@ -0,0 +1,52 @@ +"""12-minute demo script structure for founders.""" + +from __future__ import annotations + +from typing import Any + + +def build_demo_script() -> dict[str, Any]: + return { + "duration_minutes": 12, + "sections": [ + { + "minute_range": "0-2", + "title_ar": "المشكلة والوعد", + "talking_points_ar": [ + "Dealix ليس CRM ولا بوت واتساب فقط.", + "نحوّل الإشارات إلى قرار يومي عربي + موافقة + Proof.", + ], + }, + { + "minute_range": "2-4", + "title_ar": "Daily Brief", + "api_hint": "GET /api/v1/personal-operator/daily-brief", + "talking_points_ar": ["٣ قرارات", "مخاطر", "جاهزية"], + }, + { + "minute_range": "4-6", + "title_ar": "Growth Operator / ١٠ فرص", + "api_hint": "GET /api/v1/growth-operator/missions", + "talking_points_ar": ["لماذا الآن", "Accept/Skip", "لا cold WhatsApp"], + }, + { + "minute_range": "6-8", + "title_ar": "Inbox ومنصة", + "api_hint": "GET /api/v1/platform/inbox/feed", + "talking_points_ar": ["كروت عربية", "موافقة قبل الإرسال"], + }, + { + "minute_range": "8-10", + "title_ar": "برج الخدمات", + "api_hint": "GET /api/v1/services/catalog", + "talking_points_ar": ["تشخيص", "قوائم", "Growth OS", "أسعار تقديرية"], + }, + { + "minute_range": "10-12", + "title_ar": "Pilot وProof", + "talking_points_ar": ["٧ أيام أو ٣٠ يوم", "Proof Pack", "الخطوة التالية"], + }, + ], + "closing_line_ar": "لا نعد نتائج مضمونة — نعد مسودات وموافقات وتقارير قياس.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/launch_ops/go_no_go.py b/dealix/auto_client_acquisition/launch_ops/go_no_go.py new file mode 100644 index 00000000..c559765b --- /dev/null +++ b/dealix/auto_client_acquisition/launch_ops/go_no_go.py @@ -0,0 +1,41 @@ +"""Deterministic go/no-go for private beta launch checklist.""" + +from __future__ import annotations + +from typing import Any + + +def evaluate_go_no_go(flags: dict[str, Any] | None = None) -> dict[str, Any]: + """flags: optional overrides for tests (e.g. tests_pass=False).""" + f = flags or {} + checks = { + "tests_pass": bool(f.get("tests_pass", True)), + "routes_ok": bool(f.get("routes_ok", True)), + "staging_health_ok": bool(f.get("staging_health_ok", False)), + "no_secrets_in_repo_scan": bool(f.get("no_secrets_in_repo_scan", True)), + "whatsapp_live_send_disabled": bool(f.get("whatsapp_live_send_disabled", True)), + "service_catalog_ok": bool(f.get("service_catalog_ok", True)), + "landing_ready": bool(f.get("landing_ready", True)), + } + critical = [ + "tests_pass", + "routes_ok", + "no_secrets_in_repo_scan", + "whatsapp_live_send_disabled", + "service_catalog_ok", + ] + blockers = [k for k in critical if not checks[k]] + if not checks["landing_ready"]: + blockers.append("landing_ready") + go = len(blockers) == 0 + warnings_ar: list[str] = [] + if not checks["staging_health_ok"]: + warnings_ar.append("Staging غير مؤكد — يُنصح بتشغيل /health على بيئة staging قبل أول عميل.") + return { + "go": go, + "checks": checks, + "blockers": blockers, + "warnings_ar": warnings_ar, + "verdict_ar": "جاهز للبيتا الخاصة (كود وعمليات أساسية)" if go else "موقوف — راجع قائمة الـ blockers.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/launch_ops/launch_scorecard.py b/dealix/auto_client_acquisition/launch_ops/launch_scorecard.py new file mode 100644 index 00000000..fbaebc3f --- /dev/null +++ b/dealix/auto_client_acquisition/launch_ops/launch_scorecard.py @@ -0,0 +1,36 @@ +"""Readiness scorecard for launch — simple weighted score.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.launch_ops.go_no_go import evaluate_go_no_go + + +def build_launch_scorecard(extra: dict[str, Any] | None = None) -> dict[str, Any]: + ex = extra or {} + g = evaluate_go_no_go(ex) + score = 100 + if not g["checks"].get("tests_pass"): + score -= 30 + if not g["checks"].get("routes_ok"): + score -= 15 + if not g["checks"].get("staging_health_ok"): + score -= 10 + if not g["checks"].get("no_secrets_in_repo_scan"): + score -= 40 + if not g["checks"].get("whatsapp_live_send_disabled"): + score -= 25 + if not g["checks"].get("service_catalog_ok"): + score -= 10 + if not g["checks"].get("landing_ready"): + score -= 5 + score = max(0, min(100, score)) + status = "ready" if score >= 75 and g["go"] else "needs_work" + return { + "readiness_score": score, + "status": status, + "go_no_go": g, + "summary_ar": f"درجة الجاهزية {score}/١٠٠ — {status}.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/launch_ops/outreach_messages.py b/dealix/auto_client_acquisition/launch_ops/outreach_messages.py new file mode 100644 index 00000000..e1451d2e --- /dev/null +++ b/dealix/auto_client_acquisition/launch_ops/outreach_messages.py @@ -0,0 +1,64 @@ +"""First outreach batch — templates for warm outbound (manual send only).""" + +from __future__ import annotations + +from typing import Any + + +def build_first_twenty_outreach() -> dict[str, Any]: + return { + "count": 20, + "disclaimer_ar": "هذه قوالب للنسخ اليدوي — لا يُرسل من Dealix تلقائياً.", + "messages": [ + { + "id": 1, + "audience_ar": "مؤسس B2B", + "subject_ar": "تجربة بيتا — ١٠ فرص خلال أسبوع", + "body_ar": ( + "هلا [الاسم]، نجرب Dealix كمدير نمو عربي: نطلع لك فرصاً مناسبة، " + "نكتب رسائل عربية، وأنت توافق قبل أي تواصل. مهتم نعرض لك عينة قصيرة؟" + ), + }, + { + "id": 2, + "audience_ar": "وكالة تسويق", + "subject_ar": "Pilot مشترك مع وكالة", + "body_ar": ( + "هلا [الاسم]، نبحث وكالة واحدة لتجربة Dealix على عميل حقيقي: " + "فرص + مسودات + Proof Pack. يناسبكم ديمو ١٥ دقيقة؟" + ), + }, + { + "id": 3, + "audience_ar": "شركة تدريب", + "subject_ar": "فرص شركات في قطاعكم", + "body_ar": ( + "هلا [الاسم]، Dealix يساعد فرق التدريب تطلع فرص B2B مع سبب «لماذا الآن» " + "ومسودات عربية. نقدر نرسل لكم تشخيصاً مجانياً مختصراً؟" + ), + }, + { + "id": 4, + "audience_ar": "SaaS صغير", + "subject_ar": "Pipeline بدون فوضى قنوات", + "body_ar": ( + "هلا [الاسم]، إذا عندكم قائمة عملاء أو leads، نقدر نصنّفها ونطلع أفضل أهداف " + "مع تقرير مخاطر — بدون إرسال واتساب بارد. مهتم؟" + ), + }, + { + "id": 5, + "audience_ar": "متابعة ١", + "subject_ar": "متابعة خفيفة", + "body_ar": "تذكير لطيف: إذا يناسبكم، أرسل لي قطاعكم ومدينتكم وأجهز عينة خلال ٢٤ ساعة.", + }, + { + "id": 6, + "audience_ar": "متابعة ٢", + "subject_ar": "إغلاق مهذب", + "body_ar": "إذا التوقيت مو مناسب، أقدر أرجع بعد أسبوعين — أو أغلق الملف عندكم برسالة «لا شكراً».", + }, + ], + "note_ar": "كرّر وأكيّف الرسائل ٤–٦ لباقي الـ ٢٠ جهة — نفس النبرة الدافئة.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/launch_ops/private_beta.py b/dealix/auto_client_acquisition/launch_ops/private_beta.py new file mode 100644 index 00000000..3ea280a3 --- /dev/null +++ b/dealix/auto_client_acquisition/launch_ops/private_beta.py @@ -0,0 +1,28 @@ +"""Private beta commercial offer — deterministic copy.""" + +from __future__ import annotations + +from typing import Any + + +def build_private_beta_offer() -> dict[str, Any]: + return { + "title_ar": "Dealix — البيتا الخاصة", + "tagline_ar": "مدير نمو عربي: فرص، مسودات، موافقة، Proof — بدون إرسال حي افتراضياً.", + "included_ar": [ + "تشخيص نمو مجاني أو مدفوع حسب الاتفاق", + "سباق ١٠ فرص أو ذكاء قائمة (حسب الحالة)", + "كروت موافقة عربية (أزرار ≤٣)", + "Proof Pack أسبوعي تجريبي", + ], + "excluded_ar": [ + "إرسال واتساب جماعي بارد", + "Gmail إرسال تلقائي", + "إدراج تقويم حي بدون موافقة", + "شحن بطاقات داخل Dealix", + ], + "pilot_pricing_sar": {"low": 499, "high": 3000, "note_ar": "٧ أيام أو ٣٠ يوم — حسب النطاق"}, + "monthly_after_sar": {"low": 2999, "high": 9999}, + "live_send_default": False, + "demo": True, + } diff --git a/dealix/auto_client_acquisition/meeting_intelligence/__init__.py b/dealix/auto_client_acquisition/meeting_intelligence/__init__.py new file mode 100644 index 00000000..9680be6d --- /dev/null +++ b/dealix/auto_client_acquisition/meeting_intelligence/__init__.py @@ -0,0 +1,11 @@ +"""Meeting intelligence — transcript text to brief/follow-up (no Calendar insert).""" + +from auto_client_acquisition.meeting_intelligence.followup_builder import build_post_meeting_followup +from auto_client_acquisition.meeting_intelligence.meeting_brief import build_pre_meeting_brief +from auto_client_acquisition.meeting_intelligence.transcript_parser import summarize_transcript_text + +__all__ = [ + "build_post_meeting_followup", + "build_pre_meeting_brief", + "summarize_transcript_text", +] diff --git a/dealix/auto_client_acquisition/meeting_intelligence/deal_risk.py b/dealix/auto_client_acquisition/meeting_intelligence/deal_risk.py new file mode 100644 index 00000000..c5145bc5 --- /dev/null +++ b/dealix/auto_client_acquisition/meeting_intelligence/deal_risk.py @@ -0,0 +1,18 @@ +"""Deal risk hint from simple signals.""" + +from __future__ import annotations + +from typing import Any + + +def assess_deal_risk(signals: dict[str, Any] | None = None) -> dict[str, Any]: + s = signals or {} + risk = "low" + reasons: list[str] = [] + if s.get("no_followup_scheduled"): + risk = "medium" + reasons.append("لا يوجد موعد متابعة بعد الاجتماع.") + if s.get("ghosted_after_proposal"): + risk = "high" + reasons.append("توقف التواصل بعد العرض.") + return {"risk_level": risk, "reasons_ar": reasons, "demo": True} diff --git a/dealix/auto_client_acquisition/meeting_intelligence/followup_builder.py b/dealix/auto_client_acquisition/meeting_intelligence/followup_builder.py new file mode 100644 index 00000000..25f83b29 --- /dev/null +++ b/dealix/auto_client_acquisition/meeting_intelligence/followup_builder.py @@ -0,0 +1,15 @@ +"""Post-meeting follow-up draft (Arabic).""" + +from __future__ import annotations + +from typing import Any + + +def build_post_meeting_followup(summary_ar: str, next_steps: list[str] | None = None) -> dict[str, Any]: + steps = next_steps or ["إرسال ملخص موافق عليه", "تحديد موعد متابعة", "مشاركة مسودة عرض مختصرة"] + body = ( + f"شكراً لوقتكم. الملخص: {summary_ar[:200]}…\n" + f"الخطوات المقترحة: {'؛ '.join(steps)}.\n" + "ننتظر تأكيدكم للمتابعة." + ) + return {"subject_ar": "متابعة — ملخص الاجتماع والخطوة التالية", "body_ar": body, "approval_required": True, "demo": True} diff --git a/dealix/auto_client_acquisition/meeting_intelligence/meeting_brief.py b/dealix/auto_client_acquisition/meeting_intelligence/meeting_brief.py new file mode 100644 index 00000000..c8b96d13 --- /dev/null +++ b/dealix/auto_client_acquisition/meeting_intelligence/meeting_brief.py @@ -0,0 +1,29 @@ +"""Pre-meeting brief from company/contact context.""" + +from __future__ import annotations + +from typing import Any + + +def build_pre_meeting_brief( + company: dict[str, Any] | None = None, + contact: dict[str, Any] | None = None, + opportunity: dict[str, Any] | None = None, +) -> dict[str, Any]: + c = company or {} + p = contact or {} + o = opportunity or {} + return { + "company_ar": str(c.get("name") or c.get("company_name") or "الشركة"), + "contact_ar": str(p.get("name") or "جهة الاتصال"), + "objective_ar": str(o.get("objective_ar") or "مناقشة ملاءمة الحل والخطوة التالية."), + "questions_ar": [ + "ما معيار القرار والجدول الزمني؟", + "ما أكبر مخاطرة يرونها اليوم؟", + "ما الشكل المثالي للتجربة خلال ٧ أيام؟", + "ما الميزانية أو نطاقها التقريبي؟", + "من يشارك من جانبهم في التنفيذ؟", + ], + "likely_objections_ar": ["السعر", "التوقيت", "التكامل مع الأنظمة الحالية"], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/meeting_intelligence/objection_extractor.py b/dealix/auto_client_acquisition/meeting_intelligence/objection_extractor.py new file mode 100644 index 00000000..abccd123 --- /dev/null +++ b/dealix/auto_client_acquisition/meeting_intelligence/objection_extractor.py @@ -0,0 +1,17 @@ +"""Extract objection-like phrases from transcript text — keyword MVP.""" + +from __future__ import annotations + +import re +from typing import Any + +_KEYWORDS = ("ميزانية", "غالي", "لاحقاً", "نراجع", "ليس أولوية", "تكامل", "أمان", "عقد", "منافس") + + +def extract_objections(transcript_text: str) -> dict[str, Any]: + text = transcript_text or "" + found: list[str] = [] + for kw in _KEYWORDS: + if re.search(re.escape(kw), text, flags=re.IGNORECASE): + found.append(kw) + return {"objections_ar": list(dict.fromkeys(found))[:8], "demo": True} diff --git a/dealix/auto_client_acquisition/meeting_intelligence/transcript_parser.py b/dealix/auto_client_acquisition/meeting_intelligence/transcript_parser.py new file mode 100644 index 00000000..0dda6f15 --- /dev/null +++ b/dealix/auto_client_acquisition/meeting_intelligence/transcript_parser.py @@ -0,0 +1,18 @@ +"""Parse plain-text transcript lines into a short Arabic summary.""" + +from __future__ import annotations + +import re +from typing import Any + + +def summarize_transcript_text(text: str) -> dict[str, Any]: + lines = [ln.strip() for ln in (text or "").splitlines() if ln.strip()] + bullets = lines[:5] if lines else ["لا يوجد نص كافٍ."] + word_count = len(re.findall(r"\w+", text or "", flags=re.UNICODE)) + return { + "bullets_ar": bullets, + "word_count": word_count, + "demo": True, + "note_ar": "ملخص من نص خام — ربط Google Meet API لاحقاً مع OAuth.", + } diff --git a/dealix/auto_client_acquisition/model_router/__init__.py b/dealix/auto_client_acquisition/model_router/__init__.py new file mode 100644 index 00000000..431268ab --- /dev/null +++ b/dealix/auto_client_acquisition/model_router/__init__.py @@ -0,0 +1,5 @@ +"""Model routing hints by task type — configuration only, no vendor calls.""" + +from auto_client_acquisition.model_router.task_router import list_tasks, route_task + +__all__ = ["list_tasks", "route_task"] diff --git a/dealix/auto_client_acquisition/model_router/provider_registry.py b/dealix/auto_client_acquisition/model_router/provider_registry.py new file mode 100644 index 00000000..5cb80907 --- /dev/null +++ b/dealix/auto_client_acquisition/model_router/provider_registry.py @@ -0,0 +1,16 @@ +"""Static provider labels for routing display.""" + +from __future__ import annotations + +from typing import Any + +_PROVIDERS: list[dict[str, Any]] = [ + {"id": "anthropic", "label": "Anthropic", "tasks_default": ["strategic_reasoning", "arabic_copywriting"]}, + {"id": "openai", "label": "OpenAI", "tasks_default": ["classification", "summarization"]}, + {"id": "google", "label": "Google Gemini", "tasks_default": ["vision_analysis", "meeting_analysis"]}, + {"id": "groq", "label": "Groq", "tasks_default": ["low_cost_bulk", "extraction"]}, +] + + +def list_providers() -> dict[str, Any]: + return {"providers": list(_PROVIDERS), "demo": True} diff --git a/dealix/auto_client_acquisition/model_router/task_router.py b/dealix/auto_client_acquisition/model_router/task_router.py new file mode 100644 index 00000000..bffbdeff --- /dev/null +++ b/dealix/auto_client_acquisition/model_router/task_router.py @@ -0,0 +1,30 @@ +"""Map task types to suggested provider + cost class — deterministic.""" + +from __future__ import annotations + +from typing import Any + +_ROUTES: dict[str, dict[str, Any]] = { + "strategic_reasoning": {"provider": "anthropic", "cost_class": "high", "needs_guardrail": True}, + "arabic_copywriting": {"provider": "anthropic", "cost_class": "medium", "needs_guardrail": True}, + "classification": {"provider": "openai", "cost_class": "low", "needs_guardrail": True}, + "compliance_guardrail": {"provider": "openai", "cost_class": "low", "needs_guardrail": False}, + "meeting_analysis": {"provider": "google", "cost_class": "medium", "needs_guardrail": True}, + "vision_analysis": {"provider": "google", "cost_class": "medium", "needs_guardrail": True}, + "extraction": {"provider": "groq", "cost_class": "low", "needs_guardrail": True}, + "summarization": {"provider": "openai", "cost_class": "low", "needs_guardrail": True}, + "low_cost_bulk": {"provider": "groq", "cost_class": "minimal", "needs_guardrail": True}, + "coding_project_understanding": {"provider": "anthropic", "cost_class": "high", "needs_guardrail": True}, +} + + +def list_tasks() -> dict[str, Any]: + return {"task_types": sorted(_ROUTES.keys()), "demo": True} + + +def route_task(task_type: str) -> dict[str, Any]: + t = (task_type or "").strip().lower().replace("-", "_") + if t not in _ROUTES: + return {"ok": False, "error": "unknown_task_type", "known": sorted(_ROUTES.keys()), "demo": True} + r = _ROUTES[t] + return {"ok": True, "task_type": t, **r, "fallback_provider": "groq", "demo": True} diff --git a/dealix/auto_client_acquisition/platform_services/__init__.py b/dealix/auto_client_acquisition/platform_services/__init__.py new file mode 100644 index 00000000..b9584575 --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/__init__.py @@ -0,0 +1,23 @@ +"""Platform Services — Growth Control Tower (policy, inbox, catalog, no live sends).""" + +from auto_client_acquisition.platform_services.action_ledger import ActionLedger, get_action_ledger +from auto_client_acquisition.platform_services.action_policy import evaluate_action +from auto_client_acquisition.platform_services.channel_registry import list_channels +from auto_client_acquisition.platform_services.event_bus import EventType, validate_event +from auto_client_acquisition.platform_services.proof_summary import build_proof_summary +from auto_client_acquisition.platform_services.service_catalog import get_service_catalog +from auto_client_acquisition.platform_services.tool_gateway import execute_tool +from auto_client_acquisition.platform_services.unified_inbox import event_to_inbox_card + +__all__ = [ + "ActionLedger", + "EventType", + "build_proof_summary", + "evaluate_action", + "event_to_inbox_card", + "execute_tool", + "get_action_ledger", + "get_service_catalog", + "list_channels", + "validate_event", +] diff --git a/dealix/auto_client_acquisition/platform_services/action_ledger.py b/dealix/auto_client_acquisition/platform_services/action_ledger.py new file mode 100644 index 00000000..7b01e4cc --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/action_ledger.py @@ -0,0 +1,41 @@ +"""In-memory decision log for platform tools (MVP).""" + +from __future__ import annotations + +import itertools +import threading +import time +from typing import Any + +_counter = itertools.count(1) +_lock = threading.Lock() +_entries: list[dict[str, Any]] = [] + + +class ActionLedger: + """Thread-safe append-only ledger.""" + + def append_decision(self, *, tool: str, outcome: str, detail: dict[str, Any]) -> dict[str, Any]: + with _lock: + entry = { + "id": next(_counter), + "ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "tool": tool, + "outcome": outcome, + "detail": detail, + } + _entries.append(entry) + if len(_entries) > 500: + del _entries[:-500] + return entry + + def recent(self, limit: int = 50) -> list[dict[str, Any]]: + with _lock: + return list(_entries[-limit:]) + + +_ledger = ActionLedger() + + +def get_action_ledger() -> ActionLedger: + return _ledger diff --git a/dealix/auto_client_acquisition/platform_services/action_policy.py b/dealix/auto_client_acquisition/platform_services/action_policy.py new file mode 100644 index 00000000..78a39832 --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/action_policy.py @@ -0,0 +1,82 @@ +"""Deterministic policy — no network.""" + +from __future__ import annotations + +from typing import Any, Literal + +from core.config.settings import get_settings + +PolicyState = Literal["approved", "blocked", "approval_required", "review"] + + +def evaluate_action( + *, + action: str, + channel_id: str, + context: dict[str, Any] | None = None, +) -> dict[str, Any]: + """ + Rules: + - External-ish sends → approval_required unless explicitly internal draft. + - Cold WhatsApp → blocked when ``intent`` is cold/campaign_cold. + - Payment → approval_required + confirm flag if amount present. + - Unknown channel → review. + """ + ctx = context or {} + reason_ar = "" + state: PolicyState = "approval_required" + + known = { + "whatsapp", + "email", + "linkedin_lead_form", + "website_form", + "google_business", + "x_twitter", + "instagram", + "moyasar", + } + if channel_id not in known: + return { + "state": "review", + "reason_ar": "قناة غير معروفة في السجل — يلزم مراجعة يدوية.", + "action": action, + "channel_id": channel_id, + } + + if channel_id == "whatsapp" and action in ("send", "send_live", "external_send"): + intent = str(ctx.get("intent") or "").lower() + audience = str(ctx.get("audience") or "").lower() + cold_markers = ("cold", "campaign_cold", "purchased_list", "unknown_opt_in") + if intent in cold_markers or audience in cold_markers: + return { + "state": "blocked", + "reason_ar": "الواتساب البارد أو قوائم غير موثقة محظور حتى موافقة امتثال وتسجيل opt-in.", + "action": action, + "channel_id": channel_id, + } + settings = get_settings() + if action == "send_live" and not settings.whatsapp_allow_live_send: + return { + "state": "blocked", + "reason_ar": "الإرسال الحي للواتساب معطّل في الإعدادات (WHATSAPP_ALLOW_LIVE_SEND=false).", + "action": action, + "channel_id": channel_id, + } + + if action in ("send", "send_live", "external_send", "smtp_send"): + state = "approval_required" + reason_ar = "أي إرسال خارجي يتطلب موافقة بشرية في هذا الإصدار." + + if action in ("payment_charge", "payment_capture", "moyasar_charge"): + state = "approval_required" + if not ctx.get("user_confirmed"): + reason_ar = "عمليات الدفع تتطلب تأكيداً صريحاً من المشغّل قبل التنفيذ." + else: + reason_ar = "تم تسجيل تأكيد المشغّل — ما زال التنفيذ الفعلي معطّلاً في MVP." + + if action in ("draft_only", "draft_message", "draft_email"): + state = "approved" + reason_ar = "مسودة داخلية — مسموح للعرض فقط." + + return {"state": state, "reason_ar": reason_ar or "قرار سياسة افتراضي.", "action": action, "channel_id": channel_id} diff --git a/dealix/auto_client_acquisition/platform_services/channel_registry.py b/dealix/auto_client_acquisition/platform_services/channel_registry.py new file mode 100644 index 00000000..43ed5366 --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/channel_registry.py @@ -0,0 +1,69 @@ +"""Channel capabilities — registered-only social channels, no OAuth in MVP.""" + +from __future__ import annotations + +from typing import Any + +_CHANNEL_DEFS: list[dict[str, Any]] = [ + { + "id": "whatsapp", + "label_ar": "واتساب للأعمال", + "beta_status": "pilot", + "risk_level": "high", + "allowed_actions": ["draft_message", "template_preview"], + "blocked_actions": ["cold_outreach_auto", "bulk_send_without_approval"], + }, + { + "id": "email", + "label_ar": "البريد", + "beta_status": "ga_ready", + "risk_level": "medium", + "allowed_actions": ["draft_email", "schedule_internal"], + "blocked_actions": ["smtp_live_without_approval"], + }, + { + "id": "linkedin_lead_form", + "label_ar": "نماذج عملاء LinkedIn", + "beta_status": "mvp", + "risk_level": "low", + "allowed_actions": ["ingest_webhook_simulation", "normalize_lead"], + "blocked_actions": ["scrape_profile"], + }, + { + "id": "website_form", + "label_ar": "نموذج موقع", + "beta_status": "mvp", + "risk_level": "low", + "allowed_actions": ["ingest_webhook_simulation", "normalize_lead"], + "blocked_actions": [], + }, + # Wave 5 — registered-only (ingest / auto-reply deferred) + { + "id": "google_business", + "label_ar": "ملف Google Business", + "beta_status": "registered_only", + "risk_level": "medium", + "allowed_actions": [], + "blocked_actions": ["auto_reply", "oauth_connect", "public_api_call"], + }, + { + "id": "x_twitter", + "label_ar": "X (تويتر)", + "beta_status": "registered_only", + "risk_level": "medium", + "allowed_actions": [], + "blocked_actions": ["auto_reply", "oauth_connect", "public_api_call"], + }, + { + "id": "instagram", + "label_ar": "إنستغرام", + "beta_status": "registered_only", + "risk_level": "medium", + "allowed_actions": [], + "blocked_actions": ["auto_reply", "oauth_connect", "public_api_call"], + }, +] + + +def list_channels() -> dict[str, Any]: + return {"channels": list(_CHANNEL_DEFS), "demo": True} diff --git a/dealix/auto_client_acquisition/platform_services/contact_import_preview.py b/dealix/auto_client_acquisition/platform_services/contact_import_preview.py new file mode 100644 index 00000000..08604b29 --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/contact_import_preview.py @@ -0,0 +1,152 @@ +"""CSV / row-list import preview — classification only, no send.""" + +from __future__ import annotations + +import csv +import io +import re +from typing import Any + +from auto_client_acquisition.compliance_os.consent_ledger import LawfulBasis, record_consent +from auto_client_acquisition.compliance_os.contactability import check_contactability + +_TRUSTED_SOURCES = frozenset( + { + "inbound_form", + "website_form", + "prior_customer", + "event_meeting", + "explicit_consent", + "form_submission", + "linkedin_lead_form", + } +) + + +def _norm_phone(raw: str | None) -> str: + if not raw: + return "" + s = re.sub(r"\s+", "", str(raw)) + if s.startswith("00"): + s = "+" + s[2:] + if s.startswith("0") and len(s) >= 9: + s = "+966" + s[1:] + if s.isdigit() and len(s) == 9: + s = "+966" + s + return s + + +def _parse_rows(body: dict[str, Any]) -> list[dict[str, Any]]: + if isinstance(body.get("rows"), list): + return [dict(x) for x in body["rows"] if isinstance(x, dict)] + csv_text = body.get("csv_text") + if isinstance(csv_text, str) and csv_text.strip(): + reader = csv.DictReader(io.StringIO(csv_text)) + return [dict(row) for row in reader] + return [] + + +def _bucket_for_row(row: dict[str, Any], *, customer_id: str) -> tuple[str, str, dict[str, Any]]: + """ + Returns (bucket, reason_code, extra) where bucket is safe | needs_review | blocked. + """ + if row.get("opted_out") in (True, "true", "1", 1): + return "blocked", "opted_out", {} + + src = str(row.get("source") or "").strip().lower() or "unknown" + if row.get("cold_whatsapp") in (True, "true", "1", 1): + return "blocked", "cold_whatsapp", {} + + if src in ("purchased_list", "scraped", "unknown_list"): + return "blocked", "purchased_list", {} + + phone = _norm_phone(row.get("phone") or row.get("mobile") or row.get("tel")) + email = str(row.get("email") or "").strip().lower() + if not phone and not email: + return "needs_review", "missing_identifier", {} + + contact_id = phone or email + + if src == "unknown" or src not in _TRUSTED_SOURCES: + return "needs_review", "unknown_source", {"contact_id": contact_id} + + # Trusted: synthetic consent so contactability can pass (import-time snapshot). + if src in ("inbound_form", "website_form", "form_submission", "linkedin_lead_form"): + basis = LawfulBasis.CONSENT + rec_src = "form_submission" + else: + basis = LawfulBasis.LEGITIMATE_INTEREST + rec_src = "public_directory" + + rec = record_consent( + customer_id=customer_id, + contact_id=contact_id, + lawful_basis=basis, + purpose="import_preview", + channel="all", + source=rec_src, + ) + status = check_contactability( + contact_id=contact_id, + consent_records=[rec], + messages_sent_this_week=0, + weekly_cap=10, + current_riyadh_hour=12, + ) + if not status.can_contact: + if status.reason_code == "opted_out": + return "blocked", status.reason_code, status.to_dict() + return "needs_review", status.reason_code, status.to_dict() + + return "safe", "trusted_with_consent_snapshot", status.to_dict() + + +def build_import_preview(body: dict[str, Any]) -> dict[str, Any]: + """ + Summarizes contacts into safe / needs_review / blocked / invalid_duplicate. + + No external I/O; does not persist. + """ + customer_id = str(body.get("customer_id") or "default") + rows = _parse_rows(body) + if not rows: + return { + "ok": False, + "error": "no_rows", + "detail_ar": "مرّر ``rows`` كقائمة أو ``csv_text`` مع رؤوس أعمدة.", + } + + seen: set[str] = set() + counts = {"safe": 0, "needs_review": 0, "blocked": 0, "invalid_duplicate": 0} + samples: dict[str, list[dict[str, Any]]] = {"safe": [], "needs_review": [], "blocked": []} + + for raw in rows: + phone = _norm_phone(raw.get("phone") or raw.get("mobile") or raw.get("tel")) + email = str(raw.get("email") or "").strip().lower() + dedupe_key = phone or email or "" + if dedupe_key and dedupe_key in seen: + counts["invalid_duplicate"] += 1 + continue + if dedupe_key: + seen.add(dedupe_key) + + bucket, reason, extra = _bucket_for_row(raw, customer_id=customer_id) + counts[bucket] += 1 + entry = { + "phone": phone, + "email": email or None, + "source": raw.get("source"), + "bucket": bucket, + "reason": reason, + "contactability": extra, + } + if bucket in samples and len(samples[bucket]) < 5: + samples[bucket].append(entry) + + return { + "ok": True, + "approval_required": True, + "counts": counts, + "samples": samples, + "note_ar": "معاينة فقط — لا يُرسل أي تواصل ولا يُخزّن دفعة الاستيراد في MVP.", + } diff --git a/dealix/auto_client_acquisition/platform_services/event_bus.py b/dealix/auto_client_acquisition/platform_services/event_bus.py new file mode 100644 index 00000000..8dcd63db --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/event_bus.py @@ -0,0 +1,74 @@ +"""Unified event types and field validation — no transport.""" + +from __future__ import annotations + +from enum import Enum +from typing import Any + + +class EventType(str, Enum): + """Stable event type names for platform ingest and internal cards.""" + + LEAD_RECEIVED = "lead_received" + EXTERNAL_SEND_REQUESTED = "external_send_requested" + PAYMENT_INTENT = "payment_intent" + WHATSAPP_MESSAGE_REQUESTED = "whatsapp_message_requested" + REVIEW_REQUIRED = "review_required" + DRAFT_CREATED = "draft_created" + # Omni-channel extensions (dotted names) — backward compatible with existing types. + EMAIL_RECEIVED = "email.received" + CALENDAR_MEETING_SCHEDULED = "calendar.meeting_scheduled" + SOCIAL_COMMENT_RECEIVED = "social.comment_received" + SOCIAL_DM_RECEIVED = "social.dm_received" + LEAD_FORM_SUBMITTED = "lead.form_submitted" + PAYMENT_PAID = "payment.paid" + PAYMENT_FAILED = "payment.failed" + REVIEW_CREATED = "review.created" + PARTNER_SUGGESTED = "partner.suggested" + ACTION_APPROVED = "action.approved" + ACTION_BLOCKED = "action.blocked" + + +_REQUIRED: dict[EventType, tuple[str, ...]] = { + EventType.LEAD_RECEIVED: ("source", "channel_id"), + EventType.EXTERNAL_SEND_REQUESTED: ("channel_id", "action"), + EventType.PAYMENT_INTENT: ("amount_halalas", "currency"), + EventType.WHATSAPP_MESSAGE_REQUESTED: ("intent", "audience"), + EventType.REVIEW_REQUIRED: ("reason_code",), + EventType.DRAFT_CREATED: ("draft_kind",), + EventType.EMAIL_RECEIVED: ("channel_id", "subject_ar"), + EventType.CALENDAR_MEETING_SCHEDULED: ("channel_id", "title_ar"), + EventType.SOCIAL_COMMENT_RECEIVED: ("channel_id", "snippet_ar"), + EventType.SOCIAL_DM_RECEIVED: ("channel_id", "sender_hint"), + EventType.LEAD_FORM_SUBMITTED: ("source", "channel_id"), + EventType.PAYMENT_PAID: ("amount_halalas", "currency"), + EventType.PAYMENT_FAILED: ("amount_halalas", "reason_code"), + EventType.REVIEW_CREATED: ("channel_id", "rating"), + EventType.PARTNER_SUGGESTED: ("partner_name_ar", "sector"), + EventType.ACTION_APPROVED: ("action_id", "actor"), + EventType.ACTION_BLOCKED: ("action_id", "reason_code"), +} + + +def validate_event(payload: dict[str, Any]) -> dict[str, Any]: + """ + Validate ``event_type`` and required keys. Unknown types are rejected + (forces explicit extension rather than silent typos). + """ + errors: list[str] = [] + raw_type = payload.get("event_type") + if not isinstance(raw_type, str) or not raw_type.strip(): + return {"valid": False, "errors": ["event_type_required"], "normalized": None} + + try: + et = EventType(raw_type.strip()) + except ValueError: + return {"valid": False, "errors": [f"unknown_event_type:{raw_type}"], "normalized": None} + + for key in _REQUIRED[et]: + if key not in payload or payload[key] in (None, ""): + errors.append(f"missing_field:{key}") + + normalized = {"event_type": et.value, **{k: v for k, v in payload.items() if k != "event_type"}} + normalized["event_type"] = et.value + return {"valid": len(errors) == 0, "errors": errors, "normalized": normalized if not errors else None} diff --git a/dealix/auto_client_acquisition/platform_services/identity_resolution.py b/dealix/auto_client_acquisition/platform_services/identity_resolution.py new file mode 100644 index 00000000..e32c70b8 --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/identity_resolution.py @@ -0,0 +1,22 @@ +"""Deterministic identity merge demo — no external graph DB.""" + +from __future__ import annotations + +import hashlib +from typing import Any + + +def resolve_identity_demo( + *, + phone: str | None = None, + email: str | None = None, + company_hint: str | None = None, +) -> dict[str, Any]: + parts = "|".join([p or "" for p in (phone, email, company_hint)]) + hid = hashlib.sha256(parts.encode("utf-8")).hexdigest()[:16] + return { + "identity_key": f"id_{hid}", + "signals": {"phone": phone, "email": email, "company_hint": company_hint}, + "note_ar": "دمج تجريبي — ربط CRM وsocial handles لاحقاً.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/platform_services/inbox_feed.py b/dealix/auto_client_acquisition/platform_services/inbox_feed.py new file mode 100644 index 00000000..051b5e93 --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/inbox_feed.py @@ -0,0 +1,42 @@ +"""Deterministic unified inbox feed for demos — merges intel cards + platform inbox card.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.intelligence_layer.intel_command_feed import build_intel_command_feed +from auto_client_acquisition.platform_services.event_bus import EventType +from auto_client_acquisition.platform_services.unified_inbox import event_to_inbox_card + + +def build_inbox_feed() -> dict[str, Any]: + intel = build_intel_command_feed() + items: list[dict[str, Any]] = [] + for c in intel.get("cards") if isinstance(intel.get("cards"), list) else []: + items.append({"source_layer": "intelligence", "format": "command_card", "payload": c}) + lead_card = event_to_inbox_card( + { + "event_type": EventType.LEAD_RECEIVED.value, + "source": "trusted_simulation", + "channel_id": "website_form", + "lead_name": "عميل تجريبي", + } + ) + items.append({"source_layer": "platform", "format": "inbox_card", "payload": lead_card}) + email_card = event_to_inbox_card( + { + "event_type": EventType.EMAIL_RECEIVED.value, + "channel_id": "gmail", + "subject_ar": "استفسار عن الباقات", + } + ) + items.append({"source_layer": "platform", "format": "inbox_card", "payload": email_card}) + review_card = event_to_inbox_card( + { + "event_type": EventType.REVIEW_CREATED.value, + "channel_id": "google_business_profile", + "rating": 2, + } + ) + items.append({"source_layer": "platform", "format": "inbox_card", "payload": review_card}) + return {"items": items, "count": len(items), "demo": True, "approval_required": False} diff --git a/dealix/auto_client_acquisition/platform_services/lead_form_ingest.py b/dealix/auto_client_acquisition/platform_services/lead_form_ingest.py new file mode 100644 index 00000000..9a941721 --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/lead_form_ingest.py @@ -0,0 +1,43 @@ +"""Lead form webhook MVP — trusted simulation only, no signature crypto yet.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.platform_services.event_bus import EventType, validate_event +from auto_client_acquisition.platform_services.unified_inbox import event_to_inbox_card + +_ALLOWED_SOURCES = frozenset({"trusted_simulation"}) + + +def ingest_lead_form(body: dict[str, Any]) -> dict[str, Any]: + """ + Accepts ``source``, ``channel_id`` (linkedin_lead_form | website_form), and lead fields. + Documented contract for later HMAC verification. + """ + source = str(body.get("source") or "") + if source not in _ALLOWED_SOURCES: + return { + "ok": False, + "error": "invalid_source", + "detail_ar": "المصدر غير مسموح في MVP — استخدم trusted_simulation حتى تفعيل التوقيع.", + } + + channel_id = str(body.get("channel_id") or "website_form") + if channel_id not in ("linkedin_lead_form", "website_form"): + return {"ok": False, "error": "invalid_channel", "detail_ar": "القناة غير مدعومة في مسار الـ ingest هذا."} + + event = { + "event_type": EventType.LEAD_RECEIVED.value, + "source": source, + "channel_id": channel_id, + "lead_name": body.get("lead_name") or body.get("name") or "", + "lead_email": body.get("lead_email") or body.get("email"), + "meta": body.get("meta") if isinstance(body.get("meta"), dict) else {}, + } + v = validate_event(event) + if not v["valid"]: + return {"ok": False, "error": "validation_failed", "errors": v["errors"]} + + card = event_to_inbox_card(v["normalized"] or event) + return {"ok": True, "event": v["normalized"], "inbox_card": card, "approval_required": True} diff --git a/dealix/auto_client_acquisition/platform_services/proof_overview.py b/dealix/auto_client_acquisition/platform_services/proof_overview.py new file mode 100644 index 00000000..7ea7b19a --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/proof_overview.py @@ -0,0 +1,32 @@ +"""Single JSON view combining innovation proof summary + business proof pack demo.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.business.proof_pack import build_demo_proof_pack +from auto_client_acquisition.platform_services.proof_summary import build_proof_summary + + +def build_proof_overview() -> dict[str, Any]: + summary = build_proof_summary() + pack = build_demo_proof_pack() + return { + "demo": True, + "approval_required": False, + "innovation_ledger_summary": summary, + "business_proof_pack_excerpt": { + "executive_summary_ar": pack.get("executive_summary_ar"), + "qualified_leads": pack.get("qualified_leads"), + "meetings_booked": pack.get("meetings_booked"), + "revenue_influenced_sar": pack.get("revenue_influenced_sar"), + "next_month_plan_ar": pack.get("next_month_plan_ar"), + }, + "related_routes": { + "innovation_proof_ledger_demo": "GET /api/v1/innovation/proof-ledger/demo", + "innovation_proof_events": "GET /api/v1/innovation/proof-ledger/events", + "innovation_proof_report_week": "GET /api/v1/innovation/proof-ledger/report/week", + "business_proof_pack_demo": "GET /api/v1/business/proof-pack/demo", + "platform_proof_summary": "GET /api/v1/platform/proof/summary", + }, + } diff --git a/dealix/auto_client_acquisition/platform_services/proof_summary.py b/dealix/auto_client_acquisition/platform_services/proof_summary.py new file mode 100644 index 00000000..25d6ce18 --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/proof_summary.py @@ -0,0 +1,30 @@ +"""Summarize innovation demo proof ledger — single source for demo numbers.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.innovation.proof_ledger import build_demo_proof_ledger + + +def build_proof_summary() -> dict[str, Any]: + demo = build_demo_proof_ledger() + events = demo.get("events") if isinstance(demo.get("events"), list) else [] + total_rev = 0.0 + types: dict[str, int] = {} + for ev in events: + if not isinstance(ev, dict): + continue + et = str(ev.get("event_type") or "unknown") + types[et] = types.get(et, 0) + 1 + try: + total_rev += float(ev.get("revenue_influenced_sar_estimate") or 0) + except (TypeError, ValueError): + pass + return { + "demo": True, + "source": "innovation.proof_ledger.build_demo_proof_ledger", + "event_count": len(events), + "event_types": types, + "revenue_influenced_sar_estimate_sum": total_rev, + } diff --git a/dealix/auto_client_acquisition/platform_services/service_catalog.py b/dealix/auto_client_acquisition/platform_services/service_catalog.py new file mode 100644 index 00000000..3cfa85f2 --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/service_catalog.py @@ -0,0 +1,109 @@ +"""Sellable platform services — static catalog metadata.""" + +from __future__ import annotations + +from typing import Any + +_SERVICES: list[dict[str, Any]] = [ + { + "id": "unified_inbox", + "name_ar": "صندوق وارد موحّد", + "tier": "platform", + "description_ar": "بطاقات قرار عربية من أحداث موحّدة مع حد ثلاثة إجراءات.", + }, + { + "id": "action_policy_engine", + "name_ar": "محرك سياسة الإجراءات", + "tier": "platform", + "description_ar": "قواعد موافقة واتساب بارد ودفع — deterministic.", + }, + { + "id": "tool_gateway_safe", + "name_ar": "بوابة أدوات آمنة", + "tier": "platform", + "description_ar": "لا إرسال حي — مسودات وحالات موافقة فقط.", + }, + { + "id": "growth_intelligence_mvp", + "name_ar": "ذكاء نمو MVP", + "tier": "intelligence", + "description_ar": "Trust Score وRevenue DNA وموجز مجلس — JSON جاهز للعرض.", + }, + { + "id": "integrations_draft_pack", + "name_ar": "حزمة مسودات تكامل", + "tier": "integrations", + "description_ar": "Gmail / Calendar / Moyasar — payloads تحقق فقط بدون OAuth.", + }, + { + "id": "growth_operator_subscription", + "name_ar": "اشتراك Growth Operator", + "tier": "subscription", + "pricing_model": "monthly_sar", + "target_customer_ar": "B2B سعودي", + "outcome_ar": "Daily brief + command feed + موافقات + Proof Pack أسبوعي.", + "proof_metric_ar": "عدد الموافقات والمسودات والأثر المقدّر.", + }, + { + "id": "channel_setup_service", + "name_ar": "خدمة إعداد القنوات", + "tier": "services", + "pricing_model": "setup_fee", + "target_customer_ar": "فرق مبيعات وعمليات", + "outcome_ar": "ربط واتساب/بريد/تقويم ضمن سياسات آمنة.", + "required_integrations": ["whatsapp", "gmail", "google_calendar", "moyasar"], + }, + { + "id": "lead_intelligence_service", + "name_ar": "ذكاء قوائم العملاء", + "tier": "services", + "pricing_model": "per_project", + "outcome_ar": "تطبيع، إزالة تكرار، تصنيف contactability.", + }, + { + "id": "partnership_sprint", + "name_ar": "Partner Sprint — ١٤ يوم", + "tier": "services", + "pricing_model": "fixed_sprint", + "outcome_ar": "قائمة شركاء + رسائل + اجتماعات مقترحة.", + }, + { + "id": "email_revenue_rescue", + "name_ar": "إنقاذ إيراد البريد", + "tier": "services", + "pricing_model": "pilot_then_monthly", + "outcome_ar": "فرص ضائعة + مسودات متابعة — بدون إرسال حتى موافقة.", + }, + { + "id": "social_growth_os", + "name_ar": "نمو اجتماعي (رسمي فقط)", + "tier": "services", + "pricing_model": "monthly_sar", + "outcome_ar": "تحويل تعليقات/نماذج رسمية إلى كروت قرار.", + }, + { + "id": "local_business_growth", + "name_ar": "نمو محلي (عيادات/متاجر)", + "tier": "vertical", + "pricing_model": "monthly_sar", + "outcome_ar": "تقييمات Google + واتساب inbound + روابط دفع draft.", + }, + { + "id": "aeo_sprint", + "name_ar": "AI Visibility / AEO Sprint", + "tier": "services", + "pricing_model": "fixed_sprint", + "outcome_ar": "فجوات ظهور وأسئلة مقترحة — بدون وعود زائفة.", + }, + { + "id": "customer_success_operator", + "name_ar": "مشغّل نجاح العملاء", + "tier": "subscription", + "pricing_model": "add_on", + "outcome_ar": "تنبيه at-risk + QBR draft — بدون إرسال تلقائي.", + }, +] + + +def get_service_catalog() -> dict[str, Any]: + return {"services": list(_SERVICES), "version": 2} diff --git a/dealix/auto_client_acquisition/platform_services/tool_gateway.py b/dealix/auto_client_acquisition/platform_services/tool_gateway.py new file mode 100644 index 00000000..b5dad530 --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/tool_gateway.py @@ -0,0 +1,138 @@ +"""Tool execution facade — never performs live external I/O.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.platform_services.action_ledger import get_action_ledger +from auto_client_acquisition.platform_services.action_policy import evaluate_action + +_SUPPORTED = frozenset( + { + "send_message", + "create_payment_draft", + "moyasar_charge", + "moyasar_payment_link", + "ingest_lead", + "render_whatsapp_template_preview", + "gmail_draft", + "gmail_send", + "calendar_draft", + "calendar_insert", + "google_meet_transcript_read", + "social_reply", + } +) + + +def execute_tool(tool_name: str, payload: dict[str, Any] | None = None) -> dict[str, Any]: + """ + Returns one of: ``draft_created``, ``blocked``, ``approval_required``, ``unsupported``. + Never calls HTTP, SMTP, or WhatsApp APIs. + """ + body = payload or {} + ledger = get_action_ledger() + + if tool_name not in _SUPPORTED: + ledger.append_decision(tool=tool_name, outcome="unsupported", detail=body) + return {"status": "unsupported", "tool": tool_name, "approval_required": False} + + if tool_name == "send_message": + channel = str(body.get("channel_id") or "email") + action = str(body.get("action") or "external_send") + pol = evaluate_action( + action=action, + channel_id=channel, + context=body.get("context") if isinstance(body.get("context"), dict) else {}, + ) + if pol["state"] == "blocked": + ledger.append_decision(tool=tool_name, outcome="blocked", detail={"policy": pol}) + return {"status": "blocked", "tool": tool_name, "policy": pol, "approval_required": False} + if pol["state"] in ("approval_required", "review"): + ledger.append_decision(tool=tool_name, outcome="approval_required", detail={"policy": pol}) + return { + "status": "approval_required", + "tool": tool_name, + "policy": pol, + "approval_required": True, + } + ledger.append_decision(tool=tool_name, outcome="draft_created", detail={"policy": pol}) + return { + "status": "draft_created", + "tool": tool_name, + "draft": {"channel_id": channel, "preview_ar": "مسودة داخلية — لا إرسال."}, + "approval_required": False, + } + + if tool_name in ("create_payment_draft", "moyasar_charge"): + pol = evaluate_action( + action="moyasar_charge", + channel_id="moyasar", + context={"user_confirmed": body.get("user_confirmed"), "amount_halalas": body.get("amount_halalas")}, + ) + ledger.append_decision(tool=tool_name, outcome=pol["state"], detail={"policy": pol}) + return { + "status": "approval_required" if pol["state"] != "blocked" else "blocked", + "tool": tool_name, + "policy": pol, + "approval_required": pol["state"] != "blocked", + } + + if tool_name == "moyasar_payment_link": + ledger.append_decision(tool=tool_name, outcome="draft_created", detail=body) + return { + "status": "draft_created", + "tool": tool_name, + "draft": {"type": "payment_link_placeholder", "approval_required": True}, + "approval_required": False, + } + + if tool_name == "gmail_draft": + ledger.append_decision(tool=tool_name, outcome="draft_created", detail=body) + return {"status": "draft_created", "tool": tool_name, "approval_required": False} + + if tool_name == "gmail_send": + ledger.append_decision(tool=tool_name, outcome="blocked", detail={"reason": "gmail_send_blocked_by_default"}) + return { + "status": "blocked", + "tool": tool_name, + "policy": {"state": "blocked", "reason_ar": "إرسال Gmail معطّل افتراضياً — استخدم مسودة + موافقة لاحقاً."}, + "approval_required": False, + } + + if tool_name == "calendar_draft": + ledger.append_decision(tool=tool_name, outcome="draft_created", detail=body) + return {"status": "draft_created", "tool": tool_name, "approval_required": False} + + if tool_name == "calendar_insert": + ledger.append_decision(tool=tool_name, outcome="approval_required", detail=body) + return { + "status": "approval_required", + "tool": tool_name, + "policy": {"state": "approval_required", "reason_ar": "إدراج حدث تقويم يحتاج موافقة صريحة."}, + "approval_required": True, + } + + if tool_name == "google_meet_transcript_read": + ledger.append_decision(tool=tool_name, outcome="approval_required", detail=body) + return { + "status": "approval_required", + "tool": tool_name, + "policy": {"state": "approval_required", "reason_ar": "قراءة transcript تتطلب OAuth ونطاقات صريحة."}, + "approval_required": True, + } + + if tool_name == "social_reply": + ledger.append_decision(tool=tool_name, outcome="approval_required", detail=body) + return { + "status": "approval_required", + "tool": tool_name, + "policy": {"state": "approval_required", "reason_ar": "رد السوشيال يتطلب صلاحية قناة وموافقة."}, + "approval_required": True, + } + + if tool_name in ("ingest_lead", "render_whatsapp_template_preview"): + ledger.append_decision(tool=tool_name, outcome="draft_created", detail=body) + return {"status": "draft_created", "tool": tool_name, "approval_required": False} + + return {"status": "unsupported", "tool": tool_name, "approval_required": False} diff --git a/dealix/auto_client_acquisition/platform_services/unified_inbox.py b/dealix/auto_client_acquisition/platform_services/unified_inbox.py new file mode 100644 index 00000000..7b7f5566 --- /dev/null +++ b/dealix/auto_client_acquisition/platform_services/unified_inbox.py @@ -0,0 +1,189 @@ +"""Event → Arabic inbox card (≤3 actions).""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.innovation.command_feed import build_demo_command_feed +from auto_client_acquisition.platform_services.event_bus import EventType + + +def _trim_actions(actions: list[dict[str, str]], max_n: int = 3) -> list[dict[str, str]]: + return actions[:max_n] + + +def event_to_inbox_card(event: dict[str, Any], *, merge_demo_hint: bool = False) -> dict[str, Any]: + """Build ``title_ar``, ``summary_ar``, and up to three action buttons.""" + et_raw = event.get("event_type") + try: + et = EventType(str(et_raw)) + except (ValueError, TypeError): + return { + "title_ar": "حدث غير صالح", + "summary_ar": "تعذر بناء البطاقة — نوع الحدث غير معروف.", + "actions": _trim_actions([{"action_id": "dismiss", "label_ar": "إغلاق"}]), + } + + actions: list[dict[str, str]] = [] + title_ar = "" + summary_ar = "" + + if et == EventType.LEAD_RECEIVED: + src = str(event.get("source") or "") + name = str(event.get("lead_name") or "جهة جديدة") + title_ar = "عميل محتمل جديد" + summary_ar = f"مصدر: {src}. الاسم: {name}." + actions = [ + {"action_id": "qualify", "label_ar": "تأهيل سريع"}, + {"action_id": "assign_owner", "label_ar": "تعيين مالك"}, + {"action_id": "archive", "label_ar": "أرشفة"}, + ] + elif et == EventType.EXTERNAL_SEND_REQUESTED: + title_ar = "طلب إرسال خارجي" + summary_ar = f"القناة: {event.get('channel_id')}. الإجراء: {event.get('action')}." + actions = [ + {"action_id": "approve_send", "label_ar": "موافقة مشروطة"}, + {"action_id": "edit_draft", "label_ar": "تعديل المسودة"}, + {"action_id": "reject", "label_ar": "رفض"}, + ] + elif et == EventType.PAYMENT_INTENT: + title_ar = "نية دفع" + summary_ar = f"المبلغ (هللات): {event.get('amount_halalas')} {event.get('currency', 'SAR')}." + actions = [ + {"action_id": "confirm_payment", "label_ar": "تأكيد المشغّل"}, + {"action_id": "adjust_amount", "label_ar": "تعديل المبلغ"}, + {"action_id": "cancel", "label_ar": "إلغاء"}, + ] + elif et == EventType.WHATSAPP_MESSAGE_REQUESTED: + title_ar = "طلب رسالة واتساب" + summary_ar = f"النية: {event.get('intent')} — الجمهور: {event.get('audience')}." + actions = [ + {"action_id": "preview_template", "label_ar": "معاينة القالب"}, + {"action_id": "require_optin_proof", "label_ar": "طلب إثبات opt-in"}, + {"action_id": "block", "label_ar": "إيقاف"}, + ] + elif et == EventType.REVIEW_REQUIRED: + title_ar = "مراجعة يدوية" + summary_ar = f"السبب: {event.get('reason_code')}." + actions = [ + {"action_id": "open_queue", "label_ar": "فتح الطابور"}, + {"action_id": "assign", "label_ar": "إسناد"}, + {"action_id": "snooze", "label_ar": "تأجيل"}, + ] + elif et == EventType.EMAIL_RECEIVED: + title_ar = "إيميل شركة جديد" + summary_ar = f"الموضوع: {event.get('subject_ar')} — القناة: {event.get('channel_id')}." + actions = [ + {"action_id": "gmail_draft_reply", "label_ar": "جهّز مسودة رد"}, + {"action_id": "classify_lead", "label_ar": "صنّف كفرصة"}, + {"action_id": "snooze_email", "label_ar": "تأجيل"}, + ] + elif et == EventType.CALENDAR_MEETING_SCHEDULED: + title_ar = "اجتماع في التقويم" + summary_ar = f"{event.get('title_ar')} — {event.get('channel_id')}." + actions = [ + {"action_id": "meeting_prep", "label_ar": "تحضير"}, + {"action_id": "calendar_draft", "label_ar": "مسودة تعديل"}, + {"action_id": "ignore_meeting", "label_ar": "تخطي"}, + ] + elif et == EventType.SOCIAL_COMMENT_RECEIVED: + title_ar = "تعليق على منشور" + summary_ar = str(event.get("snippet_ar") or "")[:200] + actions = [ + {"action_id": "draft_reply", "label_ar": "رد مسودة"}, + {"action_id": "escalate", "label_ar": "تصعيد"}, + {"action_id": "dismiss_social", "label_ar": "تجاهل"}, + ] + elif et == EventType.SOCIAL_DM_RECEIVED: + title_ar = "رسالة خاصة (سوشيال)" + summary_ar = f"من: {event.get('sender_hint')} — {event.get('channel_id')}." + actions = [ + {"action_id": "policy_check", "label_ar": "فحص سياسة"}, + {"action_id": "draft_dm", "label_ar": "مسودة رد"}, + {"action_id": "block_channel", "label_ar": "إيقاف القناة"}, + ] + elif et == EventType.LEAD_FORM_SUBMITTED: + title_ar = "نموذج ليد جديد" + summary_ar = f"مصدر: {event.get('source')} — قناة: {event.get('channel_id')}." + actions = [ + {"action_id": "qualify", "label_ar": "تأهيل"}, + {"action_id": "import_crm", "label_ar": "مسودة CRM"}, + {"action_id": "archive", "label_ar": "أرشفة"}, + ] + elif et == EventType.PAYMENT_PAID: + title_ar = "دفعة مؤكدة" + summary_ar = f"المبلغ (هللات): {event.get('amount_halalas')} {event.get('currency', 'SAR')}." + actions = [ + {"action_id": "proof_ledger", "label_ar": "سجّل في Proof"}, + {"action_id": "thank_you_draft", "label_ar": "شكر مسودة"}, + {"action_id": "upsell_draft", "label_ar": "عرض ترقية"}, + ] + elif et == EventType.PAYMENT_FAILED: + title_ar = "دفعة فاشلة" + summary_ar = f"السبب: {event.get('reason_code')} — المبلغ: {event.get('amount_halalas')}." + actions = [ + {"action_id": "retry_draft", "label_ar": "مسودة متابعة"}, + {"action_id": "support_ticket", "label_ar": "تذكرة دعم"}, + {"action_id": "close_payment", "label_ar": "إغلاق"}, + ] + elif et == EventType.REVIEW_CREATED: + title_ar = "تقييم جديد (سمعة محلية)" + summary_ar = f"التقييم: {event.get('rating')} — {event.get('channel_id')}." + actions = [ + {"action_id": "draft_review_reply", "label_ar": "رد مسودة"}, + {"action_id": "escalate_mgr", "label_ar": "تصعيد مدير"}, + {"action_id": "monitor", "label_ar": "مراقبة"}, + ] + elif et == EventType.PARTNER_SUGGESTED: + title_ar = "اقتراح شريك" + summary_ar = f"{event.get('partner_name_ar')} — قطاع {event.get('sector')}." + actions = [ + {"action_id": "partner_draft", "label_ar": "رسالة شريك"}, + {"action_id": "schedule_call", "label_ar": "مسودة اجتماع"}, + {"action_id": "skip_partner", "label_ar": "تخطي"}, + ] + elif et == EventType.ACTION_APPROVED: + title_ar = "تمت الموافقة على إجراء" + summary_ar = f"{event.get('action_id')} — بواسطة {event.get('actor')}." + actions = [ + {"action_id": "view_ledger", "label_ar": "عرض السجل"}, + {"action_id": "notify_team", "label_ar": "إشعار داخلي"}, + {"action_id": "done", "label_ar": "تم"}, + ] + elif et == EventType.ACTION_BLOCKED: + title_ar = "إجراء ممنوع" + summary_ar = f"{event.get('action_id')} — {event.get('reason_code')}." + actions = [ + {"action_id": "edit_policy", "label_ar": "مراجعة سياسة"}, + {"action_id": "appeal", "label_ar": "طلب استثناء"}, + {"action_id": "dismiss", "label_ar": "إغلاق"}, + ] + elif et == EventType.DRAFT_CREATED: + title_ar = "مسودة جاهزة" + summary_ar = f"النوع: {event.get('draft_kind')}." + actions = [ + {"action_id": "open_draft", "label_ar": "فتح المسودة"}, + {"action_id": "share", "label_ar": "مشاركة داخلية"}, + {"action_id": "discard", "label_ar": "تجاهل"}, + ] + else: + title_ar = "حدث داخلي" + summary_ar = "نوع مسجّل لكن بدون قالب عرض — راجع الإعدادات." + actions = [ + {"action_id": "dismiss", "label_ar": "إغلاق"}, + {"action_id": "log", "label_ar": "تسجيل"}, + {"action_id": "help", "label_ar": "مساعدة"}, + ] + + card: dict[str, Any] = { + "title_ar": title_ar, + "summary_ar": summary_ar, + "actions": _trim_actions(actions), + "event_type": et.value, + } + if merge_demo_hint: + demo = build_demo_command_feed() + cards = demo.get("cards") if isinstance(demo.get("cards"), list) else [] + if cards and isinstance(cards[0], dict): + card["demo_hint_ar"] = str(cards[0].get("title_ar") or "") + return card diff --git a/dealix/auto_client_acquisition/revenue_company_os/__init__.py b/dealix/auto_client_acquisition/revenue_company_os/__init__.py new file mode 100644 index 00000000..1d522653 --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_company_os/__init__.py @@ -0,0 +1,6 @@ +"""Revenue Company OS — events to cards, RWU, merged command feed.""" + +from auto_client_acquisition.revenue_company_os.command_feed_engine import build_company_os_command_feed +from auto_client_acquisition.revenue_company_os.event_to_card import event_to_card + +__all__ = ["build_company_os_command_feed", "event_to_card"] diff --git a/dealix/auto_client_acquisition/revenue_company_os/action_graph.py b/dealix/auto_client_acquisition/revenue_company_os/action_graph.py new file mode 100644 index 00000000..6c9d2eba --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_company_os/action_graph.py @@ -0,0 +1,28 @@ +"""Signal → context → action graph (demo JSON for UI / intelligence).""" + +from __future__ import annotations + +from typing import Any + + +def demo_action_graph() -> dict[str, Any]: + return { + "nodes": [ + {"id": "signal", "label_ar": "إشارة"}, + {"id": "context", "label_ar": "سياق"}, + {"id": "service", "label_ar": "خدمة"}, + {"id": "risk", "label_ar": "مخاطرة"}, + {"id": "draft", "label_ar": "مسودة"}, + {"id": "approval", "label_ar": "موافقة"}, + {"id": "proof", "label_ar": "Proof"}, + ], + "edges": [ + {"from": "signal", "to": "context"}, + {"from": "context", "to": "service"}, + {"from": "service", "to": "risk"}, + {"from": "risk", "to": "draft"}, + {"from": "draft", "to": "approval"}, + {"from": "approval", "to": "proof"}, + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/revenue_company_os/channel_health.py b/dealix/auto_client_acquisition/revenue_company_os/channel_health.py new file mode 100644 index 00000000..94349f57 --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_company_os/channel_health.py @@ -0,0 +1,16 @@ +"""Channel health snapshot (deterministic demo).""" + +from __future__ import annotations + +from typing import Any + + +def demo_channel_health() -> dict[str, Any]: + return { + "channels": [ + {"channel": "email", "health_score": 78, "notes_ar": "مسودات فقط — جيد"}, + {"channel": "whatsapp", "health_score": 62, "notes_ar": "يحتاج opt-in واضح"}, + {"channel": "linkedin", "health_score": 70, "notes_ar": "Lead Forms أولاً — لا auto-DM"}, + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/revenue_company_os/command_feed_engine.py b/dealix/auto_client_acquisition/revenue_company_os/command_feed_engine.py new file mode 100644 index 00000000..98edda3c --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_company_os/command_feed_engine.py @@ -0,0 +1,17 @@ +"""Merge innovation demo command feed with event-derived cards.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.innovation.command_feed import build_demo_command_feed +from auto_client_acquisition.revenue_company_os.event_to_card import event_to_card + + +def build_company_os_command_feed(extra_events: list[dict[str, Any]] | None = None) -> dict[str, Any]: + base = build_demo_command_feed() + cards = list(base.get("cards") or []) + for ev in extra_events or []: + et = str(ev.get("type") or "form.submitted") + cards.append(event_to_card(et, ev.get("payload"))) + return {"cards": cards, "source": "company_os_merged", "demo": True} diff --git a/dealix/auto_client_acquisition/revenue_company_os/event_to_card.py b/dealix/auto_client_acquisition/revenue_company_os/event_to_card.py new file mode 100644 index 00000000..4812aa6d --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_company_os/event_to_card.py @@ -0,0 +1,72 @@ +"""Map normalized event types to Arabic command cards (max 3 buttons).""" + +from __future__ import annotations + +from typing import Any + +_EVENT_HANDLERS: dict[str, dict[str, Any]] = { + "email.received": { + "type": "inbox", + "title_ar": "بريد وارد — فرصة متابعة", + "risk_score": 22, + "recommended_action_ar": "صنّف الرد واقترح مسودة متابعة قصيرة.", + "buttons_ar": ["مسودة متابعة", "تجاهل", "إلى الصفقة"], + "proof_impact": "draft_created", + "action_mode": "draft_only", + }, + "whatsapp.reply_received": { + "type": "reply", + "title_ar": "رد واتساب — يحتاج قراراً", + "risk_score": 35, + "recommended_action_ar": "تحقق من opt-in ثم اقترح ردّاً مهنياً.", + "buttons_ar": ["مسودة رد", "تأجيل", "تصعيد"], + "proof_impact": "reply_handled", + "action_mode": "approval_required", + }, + "form.submitted": { + "type": "lead", + "title_ar": "نموذج جديد — جاهز للتأهيل", + "risk_score": 15, + "recommended_action_ar": "شغّل تشخيصاً قصيراً واربط بالخدمة المناسبة.", + "buttons_ar": ["تشخيص", "تخطي", "إسناد"], + "proof_impact": "opportunity_created", + "action_mode": "suggest_only", + }, + "payment.link_created": { + "type": "revenue", + "title_ar": "مسودة رابط دفع", + "risk_score": 10, + "recommended_action_ar": "راجع المبلغ والمرجع قبل الإرسال للعميل.", + "buttons_ar": ["موافقة مراجعة", "تعديل", "إلغاء"], + "proof_impact": "payment_link_drafted", + "action_mode": "draft_only", + }, + "risk.blocked": { + "type": "compliance_risk", + "title_ar": "إجراء مُحظور تلقائياً", + "risk_score": 95, + "recommended_action_ar": "سجّل السبب واقترح بديلاً آمناً.", + "buttons_ar": ["عرض السياسة", "مسودة بديلة", "إغلاق"], + "proof_impact": "risks_blocked", + "action_mode": "blocked", + }, + "proof.generated": { + "type": "proof_update", + "title_ar": "تحديث Proof Pack", + "risk_score": 5, + "recommended_action_ar": "أضف الحدث إلى تقرير الأسبوع.", + "buttons_ar": ["عرض الملخص", "مشاركة", "تجاهل"], + "proof_impact": "proof_generated", + "action_mode": "suggest_only", + }, +} + + +def event_to_card(event_type: str, payload: dict[str, Any] | None = None) -> dict[str, Any]: + et = (event_type or "").strip().lower() + base = dict(_EVENT_HANDLERS.get(et) or _EVENT_HANDLERS["form.submitted"]) + base["event_type"] = et + base["payload_preview"] = {k: payload[k] for k in list((payload or {}).keys())[:5]} if payload else {} + if len(base.get("buttons_ar") or []) > 3: + base["buttons_ar"] = base["buttons_ar"][:3] + return {**base, "demo": True} diff --git a/dealix/auto_client_acquisition/revenue_company_os/growth_memory.py b/dealix/auto_client_acquisition/revenue_company_os/growth_memory.py new file mode 100644 index 00000000..3d2214ee --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_company_os/growth_memory.py @@ -0,0 +1,16 @@ +"""Short-term growth memory stub — replace with decision_memory / DB later.""" + +from __future__ import annotations + +from typing import Any + +_MEMORY: list[dict[str, Any]] = [] + + +def record_highlight(entry: dict[str, Any]) -> dict[str, Any]: + _MEMORY.append(dict(entry)) + return {"stored": True, "size": len(_MEMORY), "demo": True} + + +def recent_highlights(limit: int = 10) -> dict[str, Any]: + return {"highlights": list(_MEMORY[-limit:]), "demo": True} diff --git a/dealix/auto_client_acquisition/revenue_company_os/opportunity_factory.py b/dealix/auto_client_acquisition/revenue_company_os/opportunity_factory.py new file mode 100644 index 00000000..e1ca7a06 --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_company_os/opportunity_factory.py @@ -0,0 +1,19 @@ +"""Demo opportunities for Company OS feed.""" + +from __future__ import annotations + +from typing import Any + + +def demo_opportunities() -> dict[str, Any]: + return { + "opportunities": [ + { + "id": "opp_demo_1", + "company_ar": "شركة تدريب — الرياض", + "why_now_ar": "توسع في فريق المبيعات", + "suggested_service_id": "first_10_opportunities", + } + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/revenue_company_os/proof_ledger.py b/dealix/auto_client_acquisition/revenue_company_os/proof_ledger.py new file mode 100644 index 00000000..d19a3ed8 --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_company_os/proof_ledger.py @@ -0,0 +1,16 @@ +"""Proof ledger lines for Company OS (demo, in-process).""" + +from __future__ import annotations + +from typing import Any + + +def demo_proof_ledger() -> dict[str, Any]: + return { + "entries": [ + {"metric": "drafts_created", "delta": 3, "notes_ar": "بعد موافقة تجريبية"}, + {"metric": "risks_blocked", "delta": 1, "notes_ar": "منع إرسال جماعي مقترح"}, + {"metric": "approvals_logged", "delta": 2, "notes_ar": "سجل قرار داخلي"}, + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/revenue_company_os/revenue_work_units.py b/dealix/auto_client_acquisition/revenue_company_os/revenue_work_units.py new file mode 100644 index 00000000..d83694cf --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_company_os/revenue_work_units.py @@ -0,0 +1,36 @@ +"""Revenue Work Units (RWU) — countable completed work items (demo).""" + +from __future__ import annotations + +from typing import Any + +RWU_TYPES: tuple[str, ...] = ( + "opportunity_created", + "target_ranked", + "contact_blocked", + "draft_created", + "approval_collected", + "meeting_drafted", + "followup_created", + "proof_generated", + "partner_suggested", + "payment_link_drafted", +) + + +def demo_work_units() -> dict[str, Any]: + units: list[dict[str, Any]] = [] + for i, ut in enumerate(RWU_TYPES[:6]): + units.append( + { + "unit_id": f"rwu_demo_{i}", + "unit_type": ut, + "service_id": "first_10_opportunities", + "customer_id": "demo_customer", + "risk_level": "low" if i % 2 == 0 else "medium", + "revenue_influenced_sar": 0, + "proof_event": ut, + "timestamp": "2026-05-01T12:00:00Z", + } + ) + return {"work_units": units, "demo": True} diff --git a/dealix/auto_client_acquisition/revenue_company_os/self_improvement_loop.py b/dealix/auto_client_acquisition/revenue_company_os/self_improvement_loop.py new file mode 100644 index 00000000..02b5e608 --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_company_os/self_improvement_loop.py @@ -0,0 +1,25 @@ +"""Weekly self-improvement report (deterministic recommendations).""" + +from __future__ import annotations + +from typing import Any + + +def weekly_growth_curator_report_ar() -> dict[str, Any]: + return { + "weekly_growth_curator_report_ar": ( + "ملخص أسبوعي (تجريبي): ارتفعت الموافقات على المسودات قليلاً؛ " + "يُنصح بزيادة رسائل المتابعة القصيرة بعد ٤٨ ساعة؛ " + "راجع خدمة واتساب الامتثال للعملاء عاليي المخاطر." + ), + "service_improvement_backlog": [ + "توحيد أزرار البطاقات إلى ثلاثة كحد أقصى في كل الشاشات", + "ربط Proof Pack تلقائياً بتقرير الأسبوع", + ], + "next_week_playbook_ar": [ + "١٠ لمسات يدوية موافَق عليها", + "منشور LinkedIn واحد للوكيل", + "مكالمة شريك محتمل", + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/revenue_company_os/service_factory.py b/dealix/auto_client_acquisition/revenue_company_os/service_factory.py new file mode 100644 index 00000000..d4b182a0 --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_company_os/service_factory.py @@ -0,0 +1,12 @@ +"""Bridge to Service Tower for Company OS.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import list_tower_services + + +def demo_service_snapshot() -> dict[str, Any]: + cat = list_tower_services() + return {"catalog": cat, "demo": True} diff --git a/dealix/auto_client_acquisition/revenue_launch/__init__.py b/dealix/auto_client_acquisition/revenue_launch/__init__.py new file mode 100644 index 00000000..524b1cd6 --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_launch/__init__.py @@ -0,0 +1,17 @@ +"""Revenue Today — offers, pipeline, pilot delivery, manual payment (no live charge).""" + +from auto_client_acquisition.revenue_launch.offer_builder import ( + build_499_pilot_offer, + build_case_study_free_offer, + build_growth_os_pilot_offer, + build_private_beta_offer, + recommend_offer_for_segment, +) + +__all__ = [ + "build_private_beta_offer", + "build_499_pilot_offer", + "build_growth_os_pilot_offer", + "build_case_study_free_offer", + "recommend_offer_for_segment", +] diff --git a/dealix/auto_client_acquisition/revenue_launch/demo_closer.py b/dealix/auto_client_acquisition/revenue_launch/demo_closer.py new file mode 100644 index 00000000..a6de732a --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_launch/demo_closer.py @@ -0,0 +1,64 @@ +"""12-minute demo + discovery + close — Arabic scripts (reference).""" + +from __future__ import annotations + +from typing import Any + + +def build_12_min_demo_flow() -> dict[str, Any]: + return { + "duration_minutes": 12, + "steps_ar": [ + "٠–٢: المشكلة — تشتت قنوات وقرارات يومية غير واضحة.", + "٢–٤: Daily Brief — قرارات ومخاطر.", + "٤–٦: فرص ومهمات — مثال ١٠ فرص.", + "٦–٨: Contactability — آمن / يحتاج مراجعة / ممنوع.", + "٨–١٠: برج الخدمات — عرض ٤٩٩ و Pilot.", + "١٠–١٢: الخطوة التالية — تشخيص مجاني أو Pilot.", + ], + "closing_line_ar": "Dealix لا يرسل عشوائياً — يقرر، يكتب، يطلب موافقة، ثم يثبت النتائج.", + "demo": True, + } + + +def build_discovery_questions() -> dict[str, Any]: + return { + "questions_ar": [ + "من عميلكم المثالي اليوم؟", + "ما القناة التي تثقون بها أكثر (إيميل، واتساب opt-in، نماذج)؟", + "هل عندكم قائمة أرقام أو CRM؟", + "ما متوسط قيمة الصفقة؟", + "من يوافق على الرسائل داخل الشركة؟", + ], + "demo": True, + } + + +def build_close_script() -> dict[str, Any]: + return { + "script_ar": ( + "خلنا نجرب ٧ أيام بـ٤٩٩ ريال: نعطيكم ١٠ فرص، رسائل عربية، فحص مخاطر، خطة متابعة، وProof Pack. " + "بعدها تقررون Growth OS أو التوقف — بدون التزام تلقائي." + ), + "demo": True, + } + + +def build_objection_responses() -> dict[str, Any]: + return { + "objections_ar": [ + { + "objection": "نخاف من واتساب", + "response_ar": "نعم — واتساب فقط مع opt-in أو inbound؛ الباقي إيميل أو مهام يدوية معتمدة.", + }, + { + "objection": "هل تضمنون عملاء؟", + "response_ar": "لا نضمن نتائج — نضمن مسودات وموافقات وتقرير قياس واضح.", + }, + { + "objection": "نحتاج وقت للتفكير", + "response_ar": "تمام — أرسل لك تشخيصاً مجانياً صغيراً خلال ٢٤ ساعة لتشوف الأسلوب.", + }, + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/revenue_launch/offer_builder.py b/dealix/auto_client_acquisition/revenue_launch/offer_builder.py new file mode 100644 index 00000000..d58fc21d --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_launch/offer_builder.py @@ -0,0 +1,79 @@ +"""Structured commercial offers — deterministic, no payment execution.""" + +from __future__ import annotations + +from typing import Any + + +def build_private_beta_offer() -> dict[str, Any]: + return { + "offer_id": "private_beta_shell", + "title_ar": "البيتا الخاصة — Dealix", + "summary_ar": "Pilot محدود: فرص، مسودات، موافقة، Proof — بدون إرسال حي افتراضياً.", + "price_sar": None, + "includes_ar": ["تشخيص أو سباق فرص", "كروت موافقة", "Proof Pack تجريبي"], + "no_live_send": True, + "demo": True, + } + + +def build_499_pilot_offer() -> dict[str, Any]: + return { + "offer_id": "pilot_7d_499", + "title_ar": "Pilot — ٧ أيام (٤٩٩ ريال)", + "price_sar": 499, + "duration_days": 7, + "deliverables_ar": [ + "تشخيص نمو مختصر أو ٣ فرص عينة", + "١٠ فرص B2B مع لماذا الآن", + "١٠ رسائل عربية (مسودات)", + "فحص قابلية التواصل والمخاطر", + "خطة متابعة ٧ أيام", + "Proof Pack مختصر", + ], + "payment_ar": "فاتورة أو رابط دفع يدوي عبر Moyasar (لوحة التحكم) — لا charge من API داخل Dealix في هذه المرحلة.", + "no_live_charge": True, + "no_live_send": True, + "demo": True, + } + + +def build_growth_os_pilot_offer() -> dict[str, Any]: + return { + "offer_id": "growth_os_pilot_30d", + "title_ar": "Growth OS Pilot — ٣٠ يوم", + "price_range_sar": {"min": 1500, "max": 3000}, + "duration_days": 30, + "deliverables_ar": [ + "موجز يومي تجريبي", + "١٠ فرص + ذكاء قوائم حسب الحالة", + "مسودات قنوات (بدون إرسال حي)", + "Proof Pack أسبوعي", + ], + "no_live_charge": True, + "no_live_send": True, + "demo": True, + } + + +def build_case_study_free_offer() -> dict[str, Any]: + return { + "offer_id": "pilot_free_case_study", + "title_ar": "Pilot مجاني مقابل Case Study", + "price_sar": 0, + "conditions_ar": [ + "موافقة على نشر نتائج معممة بدون بيانات حساسة", + "مقابلة قصيرة بعد الأسبوع", + ], + "no_live_send": True, + "demo": True, + } + + +def recommend_offer_for_segment(segment: str) -> dict[str, Any]: + s = (segment or "").strip().lower() + if s in ("agency", "وكالة"): + return {"recommended": "growth_os_pilot_30d", "reason_ar": "وكالات غالباً تحتاج نطاق أوسع وتقارير لعملاء.", "demo": True} + if s in ("founder", "مؤسس", "b2b"): + return {"recommended": "pilot_7d_499", "reason_ar": "أسرع إثبات قيمة لشركة واحدة.", "demo": True} + return {"recommended": "pilot_7d_499", "reason_ar": "افتراضي آمن للبيع السريع.", "demo": True} diff --git a/dealix/auto_client_acquisition/revenue_launch/offer_i18n.py b/dealix/auto_client_acquisition/revenue_launch/offer_i18n.py new file mode 100644 index 00000000..68b79448 --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_launch/offer_i18n.py @@ -0,0 +1,75 @@ +"""Optional English labels for revenue offer JSON (?lang=en) — keeps all Arabic keys.""" + +from __future__ import annotations + +from copy import deepcopy +from typing import Any + +from auto_client_acquisition.revenue_launch.offer_builder import ( + build_499_pilot_offer, + build_growth_os_pilot_offer, + build_private_beta_offer, +) + +_EN_BY_OFFER_ID: dict[str, dict[str, Any]] = { + "private_beta_shell": { + "title_en": "Private beta — Dealix", + "summary_en": "Limited pilot: opportunities, drafts, approvals, and proof — no live outbound by default.", + "includes_en": ["Diagnostic or opportunity sprint", "Approval cards", "Trial proof pack"], + }, + "pilot_7d_499": { + "title_en": "Pilot — 7 days (499 SAR)", + "summary_en": "Ten B2B opportunities with why-now, Arabic message drafts, contactability review, 7-day follow-up plan, short proof pack.", + "deliverables_en": [ + "Short growth diagnostic or 3 sample opportunities", + "10 B2B opportunities with why-now", + "10 Arabic message drafts", + "Contactability and risk review", + "7-day follow-up plan", + "Short proof pack", + ], + "payment_en": "Manual invoice or payment link via Moyasar dashboard — no in-app API charge at this stage.", + }, + "growth_os_pilot_30d": { + "title_en": "Growth OS pilot — 30 days", + "summary_en": "Wider rhythm: daily brief, opportunities, list intelligence as applicable, channel drafts (no live send), weekly proof.", + "deliverables_en": [ + "Trial daily brief", + "10 opportunities + list intelligence where applicable", + "Channel drafts (no live send)", + "Weekly proof pack", + ], + }, +} + + +def _merge_en(offer: dict[str, Any]) -> dict[str, Any]: + out = deepcopy(offer) + oid = str(out.get("offer_id") or "") + extra = _EN_BY_OFFER_ID.get(oid) + if extra: + out.update(extra) + return out + + +def build_revenue_offers_payload(lang: str) -> dict[str, Any]: + """Return bundle for GET /revenue-launch/offer; lang 'en' adds *_en fields alongside Arabic.""" + ln = (lang or "ar").lower() + if ln not in ("ar", "en"): + ln = "ar" + p = build_private_beta_offer() + q = build_499_pilot_offer() + g = build_growth_os_pilot_offer() + if ln == "en": + p = _merge_en(p) + q = _merge_en(q) + g = _merge_en(g) + return { + "locale": ln, + "private_beta_shell": p, + "pilot_499": q, + "growth_os_pilot": g, + "no_live_send": True, + "no_live_charge": True, + "demo": True, + } diff --git a/dealix/auto_client_acquisition/revenue_launch/outreach_sequence.py b/dealix/auto_client_acquisition/revenue_launch/outreach_sequence.py new file mode 100644 index 00000000..aa727973 --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_launch/outreach_sequence.py @@ -0,0 +1,66 @@ +"""First-20 segments and message templates — manual copy only.""" + +from __future__ import annotations + +from typing import Any + + +def build_first_20_segments() -> dict[str, Any]: + return { + "segments": [ + {"id": "agency_b2b", "label_ar": "وكالات B2B", "count": 5}, + {"id": "training", "label_ar": "تدريب واستشارات", "count": 5}, + {"id": "saas", "label_ar": "SaaS / تقنية صغيرة", "count": 5}, + {"id": "services_whatsapp", "label_ar": "خدمات بواتساب نشط", "count": 5}, + ], + "total": 20, + "disclaimer_ar": "نسخ يدوي فقط — لا إرسال تلقائي من Dealix.", + "demo": True, + } + + +def build_outreach_message(segment: str) -> dict[str, Any]: + seg = (segment or "default").lower() + if seg == "agency_b2b": + body = ( + "هلا [الاسم]، عندنا Beta للوكالات: Dealix يساعدكم تطلعون فرص لعملائكم، " + "رسائل عربية، موافقات، وProof Pack. يناسبكم ديمو ١٥ دقيقة؟" + ) + elif seg == "training": + body = ( + "هلا [الاسم]، Dealix يطلع فرص B2B لقطاع التدريب مع سبب «لماذا الآن» ورسائل عربية — " + "أنت توافق قبل أي تواصل. نقدر نعطيكم تشخيصاً مجانياً مختصراً؟" + ) + else: + body = ( + "هلا [الاسم]، أطلقنا Beta محدودة لـ Dealix: ١٠ فرص، رسائل عربية، موافقة قبل التواصل، وProof Pack. " + "أفتح ٥ مقاعد Pilot هذا الأسبوع — يناسبكم؟" + ) + return {"segment": segment, "body_ar": body, "manual_only": True, "demo": True} + + +def build_followup_1(_segment: str) -> dict[str, Any]: + return { + "body_ar": "متابعة خفيفة: أقدر أرسل عينة ٣ فرص + رسالة واحدة خلال ٢٤ ساعة إذا أعطيتني رابط الموقع والقطاع والمدينة.", + "manual_only": True, + "demo": True, + } + + +def build_followup_2(_segment: str) -> dict[str, Any]: + return { + "body_ar": "إذا التوقيت مو مناسب، أقدر أرجع بعد أسبوعين — أو أغلق الملف برسالة «لا شكراً».", + "manual_only": True, + "demo": True, + } + + +def build_reply_handlers() -> dict[str, Any]: + return { + "handlers_ar": [ + {"trigger": "مهتم", "action_ar": "أرسل رابط التشخيص أو جدول ديمو ١٥ دقيقة."}, + {"trigger": "كم السعر؟", "action_ar": "عرض ٤٩٩ لسبعة أيام أو ١٥٠٠–٣٠٠٠ لـ Growth OS Pilot ٣٠ يوم."}, + {"trigger": "لا شكراً", "action_ar": "شكراً — أغلق السجل بدون متابعة."}, + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/revenue_launch/payment_manual_flow.py b/dealix/auto_client_acquisition/revenue_launch/payment_manual_flow.py new file mode 100644 index 00000000..4df255da --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_launch/payment_manual_flow.py @@ -0,0 +1,43 @@ +"""Manual Moyasar / invoice flow — no API charge inside Dealix.""" + +from __future__ import annotations + +from typing import Any + + +def build_moyasar_invoice_instructions() -> dict[str, Any]: + return { + "steps_ar": [ + "سجّل الدخول إلى لوحة Moyasar (بيئة sandbox أو live حسب سياسة شركتك).", + "أنشئ فاتورة أو رابط دفع بالمبلغ المتفق عليه (مثلاً ٤٩٩ ريال = ٤٩٩٠٠ هللة).", + "أرسل الرابط للعميل عبر قناة موثوقة (إيميل أو رسالة يدوية).", + "احتفظ بمرجع الدفع في pipeline_tracker يدوياً.", + ], + "amount_halalas_note_ar": "١ ريال = ١٠٠ هللة في واجهة Moyasar عادةً — راجع وثائق Moyasar الرسمية.", + "no_live_charge": True, + "manual_or_dashboard_only": True, + "demo": True, + } + + +def build_payment_link_message() -> dict[str, Any]: + return { + "template_ar": ( + "تمام، هذا رابط الدفع/الفاتورة لـ Pilot (٧ أيام — ٤٩٩ ريال). " + "بعد إتمام الدفع أرسل لي رابط الموقع + القطاع + المدينة + العرض الرئيسي." + ), + "no_live_charge": True, + "demo": True, + } + + +def build_payment_confirmation_checklist() -> dict[str, Any]: + return { + "checklist_ar": [ + "تأكيد استلام المبلغ في Moyasar", + "تسجيل paid في pipeline", + "إرسال نموذج intake للعميل", + "جدولة kickoff ٣٠ دقيقة", + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/revenue_launch/pilot_delivery.py b/dealix/auto_client_acquisition/revenue_launch/pilot_delivery.py new file mode 100644 index 00000000..bf0b342d --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_launch/pilot_delivery.py @@ -0,0 +1,71 @@ +"""Pilot delivery checklist — deterministic templates.""" + +from __future__ import annotations + +from typing import Any + + +def build_client_intake_form() -> dict[str, Any]: + return { + "fields": [ + "company_name", + "website_url", + "sector", + "city", + "main_offer", + "ideal_customer", + "avg_deal_value_sar", + "has_contact_list", + "available_channels", + "whatsapp_opt_in_status", + "approver_name", + ], + "note_ar": "لا تُخزَّن أسرار في هذا النموذج التجريبي — استخدم قنوات آمنة لجمع البيانات.", + "demo": True, + } + + +def build_24h_delivery_plan() -> dict[str, Any]: + return { + "hours": [ + {"h": "0-4", "task_ar": "جمع المدخلات والتحقق من القنوات المسموحة."}, + {"h": "4-12", "task_ar": "توليد فرص ومسودات (عرض داخلي للمراجعة)."}, + {"h": "12-20", "task_ar": "تشغيل contactability وتقرير مخاطر."}, + {"h": "20-24", "task_ar": "تسليم حزمة أولية + موعد مراجعة مع العميل."}, + ], + "demo": True, + } + + +def build_first_10_opportunities_delivery() -> dict[str, Any]: + return { + "deliverables_ar": [ + "١٠ فرص مع لماذا الآن", + "١٠ رسائل عربية (مسودات)", + "توصية قناة لكل فرصة", + "خطة متابعة ٧ أيام", + ], + "approval_required": True, + "demo": True, + } + + +def build_list_intelligence_delivery() -> dict[str, Any]: + return { + "deliverables_ar": [ + "تقرير تنظيف وتصنيف مصدر", + "أفضل ٥٠ هدفاً (تجريبي)", + "مسودات رسائل للآمن فقط", + "تقرير مخاطر", + ], + "approval_required": True, + "demo": True, + } + + +def build_growth_diagnostic_delivery() -> dict[str, Any]: + return { + "deliverables_ar": ["٣ فرص", "١ رسالة", "١ ملاحظة مخاطر", "١ توصية خدمة مدفوعة"], + "approval_required": False, + "demo": True, + } diff --git a/dealix/auto_client_acquisition/revenue_launch/pipeline_tracker.py b/dealix/auto_client_acquisition/revenue_launch/pipeline_tracker.py new file mode 100644 index 00000000..647dc65c --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_launch/pipeline_tracker.py @@ -0,0 +1,49 @@ +"""Simple pipeline schema for founder-led beta — in-memory demo only.""" + +from __future__ import annotations + +from typing import Any + +STAGES = [ + "identified", + "contacted", + "replied", + "demo_booked", + "diagnostic_sent", + "pilot_offered", + "paid", + "lost", +] + + +def build_pipeline_schema() -> dict[str, Any]: + return { + "stages": STAGES, + "fields_ar": ["company", "person", "segment", "channel", "stage", "next_step_ar", "notes"], + "demo": True, + } + + +def add_prospect(company: str, person: str, segment: str, channel: str) -> dict[str, Any]: + return { + "id": f"prospect_{hash(company + person) % 10_000_000}", + "company": company, + "person": person, + "segment": segment, + "channel": channel, + "stage": "identified", + "demo": True, + } + + +def update_stage(prospect_id: str, new_stage: str) -> dict[str, Any]: + st = new_stage if new_stage in STAGES else "identified" + return {"id": prospect_id, "stage": st, "ok": True, "demo": True} + + +def summarize_pipeline(prospects: list[dict[str, Any]] | None = None) -> dict[str, Any]: + rows = prospects or [] + counts: dict[str, int] = {s: 0 for s in STAGES} + for r in rows: + counts[r.get("stage", "identified")] = counts.get(r.get("stage", "identified"), 0) + 1 + return {"counts_by_stage": counts, "total": len(rows), "demo": True} diff --git a/dealix/auto_client_acquisition/revenue_launch/proof_pack_template.py b/dealix/auto_client_acquisition/revenue_launch/proof_pack_template.py new file mode 100644 index 00000000..65c332ce --- /dev/null +++ b/dealix/auto_client_acquisition/revenue_launch/proof_pack_template.py @@ -0,0 +1,44 @@ +"""Proof Pack template for private beta pilots.""" + +from __future__ import annotations + +from typing import Any + + +def build_private_beta_proof_pack() -> dict[str, Any]: + return { + "sections_ar": [ + "ملخص الأسبوع", + "الفرص المقترحة والمعتمدة/المتخطاة", + "المسودات التي أُنشئت", + "المخاطر التي تم كشفها أو منعها", + "الاجتماعات المقترحة (مسودات فقط إن وُجدت)", + "الخطوة التالية والترقية المقترحة", + ], + "metrics_keys": [ + "opportunities_count", + "drafts_created", + "approvals_pending", + "risks_flagged", + "meetings_suggested", + ], + "demo": True, + } + + +def build_client_summary(metrics: dict[str, Any] | None = None) -> dict[str, Any]: + m = metrics or {} + return { + "one_line_ar": ( + f"تمت معالجة {m.get('opportunities_count', 0)} فرصة تقريباً مع " + f"{m.get('drafts_created', 0)} مسودة — المخاطر المسجلة: {m.get('risks_flagged', 0)}." + ), + "demo": True, + } + + +def build_next_step_recommendation() -> dict[str, Any]: + return { + "next_step_ar": "إذا ارتفعت جودة القائمة: انتقل إلى Growth OS Pilot أو ذكاء قوائم أوسع.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/security_curator/__init__.py b/dealix/auto_client_acquisition/security_curator/__init__.py new file mode 100644 index 00000000..f3929a5a --- /dev/null +++ b/dealix/auto_client_acquisition/security_curator/__init__.py @@ -0,0 +1,13 @@ +"""Security curator — redaction and diff inspection before agents touch repos.""" + +from auto_client_acquisition.security_curator.patch_firewall import inspect_diff +from auto_client_acquisition.security_curator.secret_redactor import redact_secrets, scan_payload +from auto_client_acquisition.security_curator.trace_redactor import redact_span_metadata, redact_trace_payload + +__all__ = [ + "inspect_diff", + "redact_secrets", + "redact_span_metadata", + "redact_trace_payload", + "scan_payload", +] diff --git a/dealix/auto_client_acquisition/security_curator/patch_firewall.py b/dealix/auto_client_acquisition/security_curator/patch_firewall.py new file mode 100644 index 00000000..3233e3e6 --- /dev/null +++ b/dealix/auto_client_acquisition/security_curator/patch_firewall.py @@ -0,0 +1,35 @@ +"""Block risky diffs before they reach git — text inspection only.""" + +from __future__ import annotations + +import re +from typing import Any + + +def inspect_diff(diff_text: str) -> dict[str, Any]: + """ + Returns ``allowed`` bool and ``reasons_ar`` list. + MVP heuristics only — not a full patch parser. + """ + reasons: list[str] = [] + if not diff_text or not diff_text.strip(): + return {"allowed": True, "reasons_ar": [], "detail": "empty_diff"} + + if re.search(r"^\+.*\.env", diff_text, re.MULTILINE) or re.search(r"^\+.*\.env\.", diff_text, re.MULTILINE): + reasons.append("يحتوي على إضافة ملف بيئة (.env) — مرفوض في المسار الآلي.") + + if "ghp_" in diff_text or "github_pat_" in diff_text: + reasons.append("فرق يحتوي على رمز GitHub — مرفوض.") + + if re.search(r"(?i)(supabase_service_role|openai_api_key|anthropic_api_key)\s*=", diff_text): + reasons.append("فرق يحتوي على تعيين مفتاح حساس — راجع يدوياً.") + + lower = diff_text.lower() + if ".pem" in lower and "begin" in lower and "private" in lower: + reasons.append("مفتاح خاص (PEM) في الفرق — مرفوض.") + + return { + "allowed": len(reasons) == 0, + "reasons_ar": reasons, + "detail": "heuristic_scan", + } diff --git a/dealix/auto_client_acquisition/security_curator/secret_redactor.py b/dealix/auto_client_acquisition/security_curator/secret_redactor.py new file mode 100644 index 00000000..90b3fc0e --- /dev/null +++ b/dealix/auto_client_acquisition/security_curator/secret_redactor.py @@ -0,0 +1,66 @@ +"""Detect and redact common secret patterns from text and nested payloads.""" + +from __future__ import annotations + +import copy +import json +import re +from typing import Any + +_PATTERNS: list[tuple[re.Pattern[str], str]] = [ + (re.compile(r"ghp_[A-Za-z0-9]{20,}"), "ghp_"), + (re.compile(r"github_pat_[A-Za-z0-9_]{20,}"), "github_pat_"), + (re.compile(r"sk_live_[A-Za-z0-9]{20,}"), "sk_live_"), + ( + re.compile(r"(?i)(OPENAI_API_KEY|ANTHROPIC_API_KEY|DEEPSEEK_API_KEY|GROQ_API_KEY)\s*[=:]\s*[\w\-]{8,}"), + r"\1=", + ), + (re.compile(r"(?i)SUPABASE_SERVICE_ROLE_KEY\s*[=:]\s*[\w\-.]{10,}"), "SUPABASE_SERVICE_ROLE_KEY="), + (re.compile(r"(?i)WHATSAPP_ACCESS_TOKEN\s*[=:]\s*[\w\-.]{10,}"), "WHATSAPP_ACCESS_TOKEN="), + (re.compile(r"(?i)MOYASAR_SECRET\s*[=:]\s*[\w\-.]{6,}"), "MOYASAR_SECRET="), + (re.compile(r"(?i)LANGFUSE_SECRET_KEY\s*[=:]\s*[\w\-.]{6,}"), "LANGFUSE_SECRET_KEY="), + ( + re.compile(r"https://[a-f0-9]+@[a-z0-9.-]+\.ingest\.[a-z0-9.-]+\.sentry\.io/\d+"), + "https://@sentry.io/", + ), +] + + +def redact_secrets(text: str) -> str: + if not text: + return text + out = text + for pat, repl in _PATTERNS: + out = pat.sub(repl, out) + return out + + +def scan_payload(payload: Any) -> list[str]: + """Return list of human-readable findings (empty if clean).""" + findings: list[str] = [] + raw = json.dumps(payload, ensure_ascii=False, default=str) if not isinstance(payload, str) else payload + if "ghp_" in raw or "github_pat_" in raw: + findings.append("possible_github_token") + if re.search(r"sk_live_", raw): + findings.append("possible_stripe_live") + if re.search(r"(?i)(OPENAI_API_KEY|ANTHROPIC_API_KEY)\s*[=:]", raw): + findings.append("possible_llm_key_assignment") + if ".env" in raw and ("=" in raw or ":" in raw): + findings.append("possible_env_dump") + return findings + + +def sanitize_for_trace(payload: dict[str, Any]) -> dict[str, Any]: + """Deep-copy and redact string leaves (MVP).""" + data = copy.deepcopy(payload) + + def _walk(obj: Any) -> Any: + if isinstance(obj, dict): + return {k: _walk(v) for k, v in obj.items()} + if isinstance(obj, list): + return [_walk(v) for v in obj] + if isinstance(obj, str): + return redact_secrets(obj) + return obj + + return _walk(data) diff --git a/dealix/auto_client_acquisition/security_curator/tool_output_sanitizer.py b/dealix/auto_client_acquisition/security_curator/tool_output_sanitizer.py new file mode 100644 index 00000000..f1e17037 --- /dev/null +++ b/dealix/auto_client_acquisition/security_curator/tool_output_sanitizer.py @@ -0,0 +1,15 @@ +"""Sanitize agent/tool outputs before logging or returning to clients.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.security_curator.secret_redactor import redact_secrets, sanitize_for_trace + + +def sanitize_tool_output(obj: Any) -> Any: + if isinstance(obj, str): + return redact_secrets(obj) + if isinstance(obj, dict): + return sanitize_for_trace(obj) + return obj diff --git a/dealix/auto_client_acquisition/security_curator/trace_redactor.py b/dealix/auto_client_acquisition/security_curator/trace_redactor.py new file mode 100644 index 00000000..2c2c0628 --- /dev/null +++ b/dealix/auto_client_acquisition/security_curator/trace_redactor.py @@ -0,0 +1,17 @@ +"""Redact nested structures before sending traces to external observability.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.security_curator.secret_redactor import sanitize_for_trace + + +def redact_trace_payload(payload: dict[str, Any]) -> dict[str, Any]: + """Deep-redact string leaves; safe for Langfuse/OpenAI-style span metadata.""" + return sanitize_for_trace(payload) + + +def redact_span_metadata(metadata: dict[str, Any] | None) -> dict[str, Any]: + """Alias for observability adapters.""" + return redact_trace_payload(metadata or {}) diff --git a/dealix/auto_client_acquisition/service_excellence/__init__.py b/dealix/auto_client_acquisition/service_excellence/__init__.py new file mode 100644 index 00000000..22dc1281 --- /dev/null +++ b/dealix/auto_client_acquisition/service_excellence/__init__.py @@ -0,0 +1,5 @@ +"""Service Excellence OS — scoring, feature matrix, workflows, launch readiness.""" + +from auto_client_acquisition.service_excellence.service_scoring import calculate_service_excellence_score + +__all__ = ["calculate_service_excellence_score"] diff --git a/dealix/auto_client_acquisition/service_excellence/competitor_gap.py b/dealix/auto_client_acquisition/service_excellence/competitor_gap.py new file mode 100644 index 00000000..a93dcd4b --- /dev/null +++ b/dealix/auto_client_acquisition/service_excellence/competitor_gap.py @@ -0,0 +1,22 @@ +"""Static competitor gap framing — do_not_copy list.""" + +from __future__ import annotations + +from typing import Any + + +def compare_against_categories(service_id: str) -> dict[str, Any]: + return { + "service_id": service_id, + "competitor_strengths_ar": [ + "أدوات CRM: بيانات غنية لكن بدون قرار يومي.", + "أدوات واتساب: إرسال سريع لكن بدون سياسة.", + ], + "dealix_advantages_ar": [ + "كروت قرار عربية + موافقة + Proof.", + "تعدد قنوات مع بوابة أمان.", + ], + "gaps_to_close_ar": ["تكاملات OAuth حقيقية", "تتبع تكلفة LLM"], + "do_not_copy": ["spam_automation", "scraping_linkedin", "cold_whatsapp_blast"], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/service_excellence/feature_matrix.py b/dealix/auto_client_acquisition/service_excellence/feature_matrix.py new file mode 100644 index 00000000..80c8f772 --- /dev/null +++ b/dealix/auto_client_acquisition/service_excellence/feature_matrix.py @@ -0,0 +1,109 @@ +"""Per-service feature tiers — deterministic.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + +_BASE_FEATURES: list[dict[str, Any]] = [ + { + "id": "intake_self_serve", + "name_ar": "استقبال ذاتي", + "tier": "must_have", + "value_ar": "يبدأ العميل بدون احتكاك.", + "complexity": "low", + "risk": "low", + "proof_metric": "completion_rate", + "launch_priority": 1, + }, + { + "id": "contactability_gate", + "name_ar": "بوابة contactability", + "tier": "must_have", + "value_ar": "يمنع التواصل الخطر.", + "complexity": "medium", + "risk": "low", + "proof_metric": "blocked_risk_count", + "launch_priority": 1, + }, + { + "id": "approval_cards", + "name_ar": "كروت موافقة", + "tier": "must_have", + "value_ar": "لا إرسال خارجي بدون قرار بشري.", + "complexity": "low", + "risk": "low", + "proof_metric": "approval_rate", + "launch_priority": 1, + }, + { + "id": "proof_pack", + "name_ar": "Proof Pack", + "tier": "must_have", + "value_ar": "يثبت العائد أسبوعياً.", + "complexity": "medium", + "risk": "low", + "proof_metric": "revenue_influenced_sar", + "launch_priority": 2, + }, + { + "id": "channel_mix", + "name_ar": "مزج قنوات آمن", + "tier": "advanced", + "value_ar": "إيميل أولاً، واتساب بموافقة/opt-in.", + "complexity": "high", + "risk": "medium", + "proof_metric": "meetings_booked", + "launch_priority": 3, + }, + { + "id": "research_lab_hook", + "name_ar": "ربط مختبر تحسين", + "tier": "premium", + "value_ar": "backlog تحسين أسبوعي.", + "complexity": "medium", + "risk": "low", + "proof_metric": "experiment_win_rate", + "launch_priority": 4, + }, +] + + +def build_feature_matrix(service_id: str) -> dict[str, Any]: + svc = get_service_by_id(service_id) + feats = list(_BASE_FEATURES) + if svc and svc.get("risk_level") == "high": + feats.append( + { + "id": "extra_compliance_review", + "name_ar": "مراجعة امتثال إضافية", + "tier": "must_have", + "value_ar": "خدمة عالية المخاطر.", + "complexity": "medium", + "risk": "high", + "proof_metric": "compliance_checks", + "launch_priority": 1, + } + ) + return {"service_id": service_id, "features": feats, "demo": True} + + +def classify_features(service_id: str) -> dict[str, list[str]]: + fm = build_feature_matrix(service_id) + buckets: dict[str, list[str]] = {"must_have": [], "advanced": [], "premium": [], "future": []} + for f in fm.get("features") or []: + tier = str(f.get("tier") or "must_have") + if tier not in buckets: + tier = "must_have" + buckets[tier].append(str(f.get("id"))) + return buckets + + +def recommend_missing_features(service_id: str) -> list[str]: + """Stub: suggest future items.""" + return ["connector_webhooks", "durable_workflows"] if service_id == "growth_os" else [] + + +def prioritize_features(features: list[dict[str, Any]]) -> list[dict[str, Any]]: + return sorted(features or [], key=lambda f: int(f.get("launch_priority", 99))) diff --git a/dealix/auto_client_acquisition/service_excellence/launch_package.py b/dealix/auto_client_acquisition/service_excellence/launch_package.py new file mode 100644 index 00000000..b8c6adc5 --- /dev/null +++ b/dealix/auto_client_acquisition/service_excellence/launch_package.py @@ -0,0 +1,46 @@ +"""Sales/demo/onboarding outlines per service.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +def build_service_launch_package(service_id: str) -> dict[str, Any]: + svc = get_service_by_id(service_id) or {} + return { + "service_id": service_id, + "name_ar": svc.get("name_ar"), + "landing_outline_ar": ["الوعد", "لمن؟", "ماذا تحصل؟", "CTA", "تحذير: لا نتائج مضمونة"], + "demo_script_ar": ["افتح التشخيص", "اعرض الكروت", "أظهر الموافقة", "أغلق بـ Proof"], + "onboarding_checklist_ar": ["جمع المدخلات", "تشغيل wizard", "تفعيل مسودات فقط"], + "demo": True, + } + + +def build_landing_page_outline(service_id: str) -> dict[str, Any]: + return {"service_id": service_id, "sections": build_service_launch_package(service_id).get("landing_outline_ar"), "demo": True} + + +def build_sales_script(service_id: str) -> dict[str, Any]: + return { + "service_id": service_id, + "script_ar": ( + f"نقدّم {service_id}: نعمل مسودات وموافقات، " + "ولا نرسل أو نخصم دون قرارك. نثبت القيمة عبر Proof Pack." + ), + "demo": True, + } + + +def build_demo_script(service_id: str) -> dict[str, Any]: + return build_sales_script(service_id) + + +def build_onboarding_checklist(service_id: str) -> dict[str, Any]: + return { + "service_id": service_id, + "checklist_ar": build_service_launch_package(service_id).get("onboarding_checklist_ar"), + "demo": True, + } diff --git a/dealix/auto_client_acquisition/service_excellence/proof_metrics.py b/dealix/auto_client_acquisition/service_excellence/proof_metrics.py new file mode 100644 index 00000000..c6eaa4a9 --- /dev/null +++ b/dealix/auto_client_acquisition/service_excellence/proof_metrics.py @@ -0,0 +1,38 @@ +"""Required proof metrics and ROI estimate stubs.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +def required_proof_metrics(service_id: str) -> list[str]: + svc = get_service_by_id(service_id) or {} + return list(svc.get("proof_metrics") or ["drafts_created", "approvals"]) + + +def build_proof_pack_template(service_id: str) -> dict[str, Any]: + return { + "service_id": service_id, + "metrics": required_proof_metrics(service_id), + "template_ar": "ملخص + قرارات + مخاطر منعت + أثر مقدّر", + "demo": True, + } + + +def calculate_service_roi_estimate(service_id: str, metrics: dict[str, Any]) -> dict[str, Any]: + influenced = int(metrics.get("revenue_influenced_sar", 0)) + if influenced <= 0: + influenced = int(metrics.get("pipeline_sar", 12000)) + return { + "service_id": service_id, + "revenue_influenced_sar_estimate": influenced, + "note_ar": "تقدير عرضي — ليس وعداً.", + "demo": True, + } + + +def summarize_proof_ar(service_id: str, metrics: dict[str, Any]) -> str: + r = calculate_service_roi_estimate(service_id, metrics) + return f"خدمة {service_id}: أثر مقدّر {r.get('revenue_influenced_sar_estimate')} ريال (عرضي)." diff --git a/dealix/auto_client_acquisition/service_excellence/quality_review.py b/dealix/auto_client_acquisition/service_excellence/quality_review.py new file mode 100644 index 00000000..70d3c841 --- /dev/null +++ b/dealix/auto_client_acquisition/service_excellence/quality_review.py @@ -0,0 +1,56 @@ +"""Launch gate checks for services.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +def review_service_before_launch(service_id: str) -> dict[str, Any]: + svc = get_service_by_id(service_id) or {} + issues: list[str] = [] + if not svc.get("pricing_range_sar"): + issues.append("missing_pricing") + if not svc.get("approval_policy"): + issues.append("missing_approval_policy") + if not svc.get("proof_metrics"): + issues.append("missing_proof_metrics") + ok = len(issues) == 0 + return {"service_id": service_id, "ok": ok, "issues": issues, "demo": True} + + +def block_if_missing_proof(service_id: str) -> bool: + return not (get_service_by_id(service_id) or {}).get("proof_metrics") + + +def block_if_missing_approval_policy(service_id: str) -> bool: + return not (get_service_by_id(service_id) or {}).get("approval_policy") + + +def block_if_unclear_pricing(service_id: str) -> bool: + if service_id == "free_growth_diagnostic": + return False + pr = (get_service_by_id(service_id) or {}).get("pricing_range_sar") or {} + return pr.get("max") in (None, 0) + + +def block_if_unsafe_channel(service_id: str) -> bool: + """Block launch if policy suggests unguarded external send.""" + pol = ((get_service_by_id(service_id) or {}).get("approval_policy") or "").lower() + return pol in ("", "none", "auto_send") + + +def review_all_services() -> dict[str, Any]: + from auto_client_acquisition.service_tower.service_catalog import list_service_ids + + results: list[dict[str, Any]] = [] + for sid in list_service_ids(): + results.append(review_service_before_launch(sid)) + ok_count = sum(1 for r in results if r.get("ok")) + return { + "count": len(results), + "ok_count": ok_count, + "results": results, + "demo": True, + } diff --git a/dealix/auto_client_acquisition/service_excellence/research_lab.py b/dealix/auto_client_acquisition/service_excellence/research_lab.py new file mode 100644 index 00000000..c8cf2d9b --- /dev/null +++ b/dealix/auto_client_acquisition/service_excellence/research_lab.py @@ -0,0 +1,37 @@ +"""Deterministic research brief — no web calls.""" + +from __future__ import annotations + +from typing import Any + + +def build_service_research_brief(service_id: str) -> dict[str, Any]: + return { + "service_id": service_id, + "hypotheses_ar": [ + "تحسين رسالة الـ CTA يزيد الردود.", + "تقليل المتابعات يقلل الشكاوى.", + ], + "experiments_ar": ["A/B لنبرة سعودية قصيرة", "تغيير ترتيب القنوات في الخطة"], + "demo": True, + } + + +def generate_feature_hypotheses(service_id: str) -> list[str]: + return [f"{service_id}: إضافة checklist امتثال", f"{service_id}: تقرير مقارنة منافسين"] + + +def recommend_next_experiments(service_id: str) -> list[str]: + return [f"{service_id}: تجربة سعر Pilot أعلى قليلاً", f"{service_id}: دمج Proof مع targeting"] + + +def build_monthly_service_review(service_id: str) -> dict[str, Any]: + return { + "service_id": service_id, + "review_ar": [ + "ماذا تحسّن؟", + "ماذا أوقفنا؟", + "ما التجربة القادمة؟", + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/service_excellence/service_improvement_backlog.py b/dealix/auto_client_acquisition/service_excellence/service_improvement_backlog.py new file mode 100644 index 00000000..24bbb5a1 --- /dev/null +++ b/dealix/auto_client_acquisition/service_excellence/service_improvement_backlog.py @@ -0,0 +1,40 @@ +"""Prioritized improvement backlog per service.""" + +from __future__ import annotations + +from typing import Any + + +def build_backlog(service_id: str) -> list[dict[str, Any]]: + return [ + {"id": f"{service_id}_tone_eval", "title_ar": "تقييم نبرة سعودية", "priority": 1}, + {"id": f"{service_id}_latency", "title_ar": "تقليل زمن توليد المسودات", "priority": 2}, + {"id": f"{service_id}_integrations", "title_ar": "OAuth محدود النطاق", "priority": 3}, + ] + + +def prioritize_backlog_items(items: list[dict[str, Any]]) -> list[dict[str, Any]]: + return sorted(items or [], key=lambda x: int(x.get("priority", 99))) + + +def convert_feedback_to_backlog(feedback: str) -> dict[str, Any]: + return { + "feedback": feedback, + "backlog_item": { + "id": "user_feedback_1", + "title_ar": "معالجة ملاحظة مستخدم", + "priority": 2, + }, + "demo": True, + } + + +def recommend_weekly_improvements(service_id: str) -> dict[str, Any]: + return { + "service_id": service_id, + "items_ar": [ + "راجع آخر ١٠ موافقات واختصر المسودات.", + "قارن proof metrics أسبوع بأسبوع.", + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/service_excellence/service_scoring.py b/dealix/auto_client_acquisition/service_excellence/service_scoring.py new file mode 100644 index 00000000..4624a339 --- /dev/null +++ b/dealix/auto_client_acquisition/service_excellence/service_scoring.py @@ -0,0 +1,75 @@ +"""Service Excellence Score 0–100 — launch readiness.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +def _clamp(n: int, lo: int = 0, hi: int = 10) -> int: + return max(lo, min(hi, n)) + + +def score_clarity(service: dict[str, Any]) -> int: + return 9 if service.get("outcome_ar") else 4 + + +def score_speed_to_value(service: dict[str, Any]) -> int: + steps = len(service.get("workflow_steps") or []) + return _clamp(10 - max(0, steps - 8)) + + +def score_automation(service: dict[str, Any]) -> int: + return 7 if len(service.get("required_integrations") or []) <= 2 else 6 + + +def score_compliance(service: dict[str, Any]) -> int: + pol = (service.get("approval_policy") or "").lower() + if "legal" in pol: + return 10 + if "approval" in pol or "draft" in pol: + return 9 + return 6 + + +def score_proof(service: dict[str, Any]) -> int: + return 8 if service.get("proof_metrics") else 4 + + +def score_upsell(service: dict[str, Any]) -> int: + return 8 if service.get("upgrade_path") else 5 + + +def calculate_service_excellence_score(service_id: str) -> dict[str, Any]: + svc = get_service_by_id(service_id) or {} + dims = { + "clarity": score_clarity(svc), + "speed_to_value": score_speed_to_value(svc), + "automation": score_automation(svc), + "compliance": score_compliance(svc), + "proof": score_proof(svc), + "upsell": score_upsell(svc), + } + # Weighted sum → 0..100 scale (6 dims * ~10 max) + total = sum(dims.values()) * 100 // 60 + status = "launch_ready" + reasons_ar: list[str] = [] + if total < 80: + status = "beta_only" + reasons_ar.append("الدرجة أقل من ٨٠ — إطلاق محدود أو تحسين قبل الإعلان.") + if (svc.get("risk_level") or "") == "high" and total < 90: + status = "needs_work" + reasons_ar.append("مخاطر عالية: عزّز الامتثال والاختبارات.") + required_fixes: list[str] = [] + if not svc.get("proof_metrics"): + required_fixes.append("أضف proof_metrics واضحة.") + return { + "service_id": service_id, + "dimensions": dims, + "total_score": total, + "status": status, + "reasons_ar": reasons_ar or ["جاهزية جيدة للعرض الداخلي."], + "required_fixes": required_fixes, + "demo": True, + } diff --git a/dealix/auto_client_acquisition/service_excellence/workflow_builder.py b/dealix/auto_client_acquisition/service_excellence/workflow_builder.py new file mode 100644 index 00000000..61939fbb --- /dev/null +++ b/dealix/auto_client_acquisition/service_excellence/workflow_builder.py @@ -0,0 +1,32 @@ +"""Standard workflow builder for services.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.mission_templates import build_service_workflow + + +def build_workflow(service_id: str) -> dict[str, Any]: + return build_service_workflow(service_id) + + +def validate_workflow(service_id: str) -> dict[str, Any]: + wf = build_workflow(service_id) + steps = wf.get("steps") or [] + ok = "approve" in steps or "approval" in steps + return {"ok": ok, "steps": steps, "has_approval_step": ok, "demo": True} + + +def build_day_by_day_execution_plan(service_id: str) -> dict[str, Any]: + wf = build_workflow(service_id) + steps = list(wf.get("steps") or []) + plan: list[dict[str, Any]] = [] + for i, s in enumerate(steps[:14], start=1): + plan.append({"day": i, "step": s, "note_ar": f"اليوم {i}: {s}"}) + return {"service_id": service_id, "plan": plan, "demo": True} + + +def build_approval_steps(service_id: str) -> dict[str, Any]: + wf = build_workflow(service_id) + return {"service_id": service_id, "approval_steps": wf.get("approval_gates") or ["approve"], "demo": True} diff --git a/dealix/auto_client_acquisition/service_tower/__init__.py b/dealix/auto_client_acquisition/service_tower/__init__.py new file mode 100644 index 00000000..6d500a73 --- /dev/null +++ b/dealix/auto_client_acquisition/service_tower/__init__.py @@ -0,0 +1,19 @@ +"""Dealix Service Tower — productized services, wizard, pricing, CEO cards.""" + +from auto_client_acquisition.service_tower.service_catalog import ( + get_service_by_id, + list_service_ids, + list_tower_services, +) +from auto_client_acquisition.service_tower.service_wizard import ( + recommend_service, + summarize_recommendation_ar, +) + +__all__ = [ + "get_service_by_id", + "list_service_ids", + "list_tower_services", + "recommend_service", + "summarize_recommendation_ar", +] diff --git a/dealix/auto_client_acquisition/service_tower/contract_templates.py b/dealix/auto_client_acquisition/service_tower/contract_templates.py new file mode 100644 index 00000000..0e49157b --- /dev/null +++ b/dealix/auto_client_acquisition/service_tower/contract_templates.py @@ -0,0 +1,35 @@ +"""Contract / legal outline templates — not legal advice; approval required.""" + +from __future__ import annotations + +from typing import Any + + +def list_contract_templates() -> dict[str, Any]: + templates = [ + { + "id": "pilot_agreement", + "title_ar": "مسودة اتفاق Pilot", + "outline_ar": ["نطاق الخدمة", "مدة التجربة", "القياس (Proof)", "PDPL ومصادر البيانات", "إيقاف فوري"], + "legal_review_required": True, + "approval_required": True, + "not_legal_advice": True, + }, + { + "id": "dpa_pilot", + "title_ar": "مسودة DPA تجريبية", + "outline_ar": ["أدوار المعالج/المتحكم", "الاحتفاظ", "حقوق الأفراد", "الأمان", "نقل البيانات"], + "legal_review_required": True, + "approval_required": True, + "not_legal_advice": True, + }, + { + "id": "referral_partner", + "title_ar": "مسودة اتفاق إحالة/شريك", + "outline_ar": ["نسبة الإحالة", "تسوية الفواتير", "العلامة التجارية", "سرية"], + "legal_review_required": True, + "approval_required": True, + "not_legal_advice": True, + }, + ] + return {"templates": templates, "demo": True} diff --git a/dealix/auto_client_acquisition/service_tower/deliverables.py b/dealix/auto_client_acquisition/service_tower/deliverables.py new file mode 100644 index 00000000..29423d73 --- /dev/null +++ b/dealix/auto_client_acquisition/service_tower/deliverables.py @@ -0,0 +1,59 @@ +"""Deliverables and proof pack outlines per service.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +def build_deliverables(service_id: str) -> dict[str, Any]: + svc = get_service_by_id(service_id) + if not svc: + return {"service_id": service_id, "deliverables": [], "demo": True} + items = list(svc.get("deliverables_ar") or []) + return {"service_id": service_id, "deliverables_ar": items, "count": len(items), "demo": True} + + +def build_proof_pack_template(service_id: str) -> dict[str, Any]: + svc = get_service_by_id(service_id) + metrics = list((svc or {}).get("proof_metrics") or []) + return { + "service_id": service_id, + "sections_ar": [ + "ملخص الأسبوع", + "ما تم اعتماده", + "المخاطر التي تم منعها", + "الأثر المقدّر", + "الخطوة التالية", + ], + "proof_metrics": metrics, + "demo": True, + } + + +def build_client_report_outline(service_id: str) -> dict[str, Any]: + return { + "service_id": service_id, + "outline_ar": [ + "الهدف والمدخلات", + "ما نفّذناه (مسودات/موافقات)", + "النتائج المقيسة", + "المخاطر والامتثال", + "التوصية للأسبوع القادم", + ], + "demo": True, + } + + +def build_internal_operator_checklist(service_id: str) -> dict[str, Any]: + return { + "service_id": service_id, + "checklist_ar": [ + "تأكد من عدم وجود إرسال حي", + "راجع contactability", + "سجّل الموافقات في الدفتر", + "حدّث Proof Pack", + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/service_tower/mission_templates.py b/dealix/auto_client_acquisition/service_tower/mission_templates.py new file mode 100644 index 00000000..759526b5 --- /dev/null +++ b/dealix/auto_client_acquisition/service_tower/mission_templates.py @@ -0,0 +1,55 @@ +"""Map each sellable service to default mission / workflow steps.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +_DEFAULT_STEPS = [ + "intake", + "analyze", + "target", + "draft", + "approve", + "track", + "proof", + "upsell", +] + + +def build_service_workflow(service_id: str) -> dict[str, Any]: + svc = get_service_by_id(service_id) + if not svc: + return {"service_id": service_id, "steps": [], "error": "unknown_service", "demo": True} + steps = list(svc.get("workflow_steps") or _DEFAULT_STEPS) + return { + "service_id": service_id, + "steps": steps, + "approval_gates": [s for s in steps if s in ("approve", "approval")], + "live_send": False, + "demo": True, + } + + +def get_default_mission_steps(service_id: str) -> list[str]: + return list(build_service_workflow(service_id).get("steps") or []) + + +def map_service_to_growth_mission(service_id: str) -> dict[str, Any]: + """Bridge to growth_operator mission naming where applicable.""" + mapping = { + "first_10_opportunities": "first_10_opportunities", + "list_intelligence": "contact_import_preview", + "growth_os": "daily_growth_loop", + "partner_sprint": "partnership_sprint", + "email_revenue_rescue": "email_revenue_rescue", + } + mid = mapping.get(service_id, "generic_service_run") + return { + "service_id": service_id, + "growth_mission_id": mid, + "note_ar": "ربط منطقي للعرض — لا يشغّل مهمة حية.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/service_tower/pricing_engine.py b/dealix/auto_client_acquisition/service_tower/pricing_engine.py new file mode 100644 index 00000000..e2810aa1 --- /dev/null +++ b/dealix/auto_client_acquisition/service_tower/pricing_engine.py @@ -0,0 +1,61 @@ +"""Deterministic SAR quotes — hints only, not binding contracts.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +def quote_service( + service_id: str, + company_size: str = "smb", + urgency: str = "normal", + channels_count: int = 1, +) -> dict[str, Any]: + svc = get_service_by_id(service_id) + if not svc: + return {"ok": False, "error": "unknown_service", "demo": True} + pr = svc.get("pricing_range_sar") or {"min": 0, "max": 0} + lo = int(pr.get("min", 0)) + hi = int(pr.get("max", lo)) + mult = 1.0 + if (company_size or "").lower() in ("enterprise", "large"): + mult *= 1.15 + if (urgency or "").lower() == "high": + mult *= 1.1 + mult += 0.05 * max(0, min(channels_count, 6) - 1) + lo_q = int(lo * mult) + hi_q = int(hi * mult) + return { + "ok": True, + "service_id": service_id, + "quoted_range_sar": {"min": lo_q, "max": hi_q}, + "factors": {"company_size": company_size, "urgency": urgency, "channels_count": channels_count}, + "not_binding": True, + "demo": True, + } + + +def recommend_plan_after_service(service_id: str, outcome: str) -> dict[str, Any]: + outcome_l = (outcome or "").lower() + svc = get_service_by_id(service_id) + nxt = (svc or {}).get("upgrade_path") or "growth_os" + if "churn" in outcome_l: + nxt = "executive_growth_brief" + return {"next_plan": nxt, "reason_ar": "مسار ترقية افتراضي حسب الكتالوج.", "demo": True} + + +def calculate_setup_fee(service_id: str) -> dict[str, Any]: + q = quote_service(service_id, company_size="smb", urgency="normal", channels_count=1) + r = q.get("quoted_range_sar") or {} + setup = int((r.get("min", 0) + r.get("max", 0)) // 4) + return {"service_id": service_id, "setup_fee_hint_sar": setup, "demo": True} + + +def calculate_monthly_offer(service_id: str) -> dict[str, Any]: + if service_id == "growth_os": + return {"service_id": service_id, "monthly_hint_sar": 2999, "demo": True} + if service_id == "self_growth_operator": + return {"service_id": service_id, "monthly_hint_sar": 999, "demo": True} + return {"service_id": service_id, "monthly_hint_sar": None, "note_ar": "خدمة مشروع/سباق — لا اشتراك افتراضي.", "demo": True} diff --git a/dealix/auto_client_acquisition/service_tower/service_catalog.py b/dealix/auto_client_acquisition/service_tower/service_catalog.py new file mode 100644 index 00000000..8e2c6df9 --- /dev/null +++ b/dealix/auto_client_acquisition/service_tower/service_catalog.py @@ -0,0 +1,249 @@ +"""Full sellable service definitions — complements platform_services.service_catalog.""" + +from __future__ import annotations + +from typing import Any + +_SERVICES: list[dict[str, Any]] = [ + { + "service_id": "free_growth_diagnostic", + "name_ar": "تشخيص نمو مجاني", + "target_customer_ar": "أي شركة B2B", + "outcome_ar": "٣ فرص + رسالة واحدة + تقرير مخاطر + اقتراح Pilot.", + "inputs_required": ["company_name", "sector", "city", "offer"], + "workflow_steps": ["intake", "analyze", "draft", "proof_preview", "upsell"], + "deliverables_ar": ["٣ فرص", "١ رسالة", "١ تقرير مخاطر", "اقتراح خدمة مدفوعة"], + "pricing_range_sar": {"min": 0, "max": 0}, + "risk_level": "low", + "required_integrations": [], + "approval_policy": "draft_only", + "proof_metrics": ["diagnostic_to_paid_conversion"], + "upgrade_path": "first_10_opportunities", + }, + { + "service_id": "list_intelligence", + "name_ar": "ذكاء القوائم", + "target_customer_ar": "شركات لديها CSV أرقام/إيميلات", + "outcome_ar": "تطبيع، تصنيف مصدر، contactability، أفضل ٥٠ هدفاً، مسودات.", + "inputs_required": ["csv_or_contacts", "sector"], + "workflow_steps": [ + "intake", + "data_check", + "targeting", + "contactability", + "draft", + "approve", + "proof", + "upsell", + ], + "deliverables_ar": ["تقرير القائمة", "أفضل ٥٠", "مسودات إيميل/مهمة", "تقرير مخاطر"], + "pricing_range_sar": {"min": 499, "max": 1500}, + "risk_level": "medium", + "required_integrations": [], + "approval_policy": "approval_required_for_send", + "proof_metrics": ["safe_ratio", "drafts_created", "blocked_count"], + "upgrade_path": "growth_os", + }, + { + "service_id": "first_10_opportunities", + "name_ar": "سباق ١٠ فرص", + "target_customer_ar": "مؤسس أو فريق مبيعات يحتاج فرصاً سريعة", + "outcome_ar": "١٠ فرص + لماذا الآن + رسائل عربية + خطة متابعة + Proof Pack.", + "inputs_required": ["sector", "city", "offer", "goal"], + "workflow_steps": [ + "intake", + "analyze", + "target", + "draft", + "approve", + "track", + "proof", + "upsell", + ], + "deliverables_ar": ["١٠ فرص", "١٠ مسودات", "خطة متابعة", "قالب Proof Pack"], + "pricing_range_sar": {"min": 499, "max": 1500}, + "risk_level": "medium", + "required_integrations": [], + "approval_policy": "approval_required_for_send", + "proof_metrics": ["opportunities_count", "accept_rate", "meetings_booked"], + "upgrade_path": "growth_os", + }, + { + "service_id": "self_growth_operator", + "name_ar": "مشغّل نمو ذاتي", + "target_customer_ar": "مؤسسون ومستشارون ووكالات صغيرة", + "outcome_ar": "موجز يومي + كروت قرار + مسودات — بدون إرسال حي افتراضياً.", + "inputs_required": ["goal", "channels"], + "workflow_steps": ["intake", "analyze", "draft", "approve", "track", "proof", "upsell"], + "deliverables_ar": ["Daily brief", "٥ كروت/يوم", "تقرير أسبوعي"], + "pricing_range_sar": {"min": 999, "max": 1999}, + "risk_level": "low", + "required_integrations": [], + "approval_policy": "approval_required_for_send", + "proof_metrics": ["cards_approved", "time_saved_hours"], + "upgrade_path": "growth_os", + }, + { + "service_id": "growth_os", + "name_ar": "Growth OS شهري", + "target_customer_ar": "شركات B2B صغيرة ومتوسطة", + "outcome_ar": "Targeting + قنوات + Proof Ledger + تسريبات إيراد.", + "inputs_required": ["onboarding_profile", "channels"], + "workflow_steps": [ + "intake", + "data_check", + "targeting", + "contactability", + "strategy", + "drafting", + "approval", + "execution_or_export", + "tracking", + "proof", + "upsell", + ], + "deliverables_ar": ["Command feed", "Proof Pack أسبوعي", "تقرير قنوات"], + "pricing_range_sar": {"min": 2999, "max": 9999}, + "risk_level": "medium", + "required_integrations": ["gmail", "google_calendar", "whatsapp", "moyasar"], + "approval_policy": "approval_required_for_send", + "proof_metrics": ["revenue_influenced_sar", "meetings_booked", "risks_blocked"], + "upgrade_path": "agency_partner_program", + }, + { + "service_id": "partner_sprint", + "name_ar": "سباق شراكات", + "target_customer_ar": "شركات تريد توسعاً عبر شركاء", + "outcome_ar": "فئات شركاء + أهداف + مسودات + اجتماعات مقترحة + مسودة اتفاق إحالة.", + "inputs_required": ["partner_goal", "sector"], + "workflow_steps": ["intake", "target", "draft", "approve", "track", "proof", "upsell"], + "deliverables_ar": ["قائمة شركاء", "مسودات", "Partner scorecard", "مسودة اتفاق"], + "pricing_range_sar": {"min": 3000, "max": 7500}, + "risk_level": "low", + "required_integrations": [], + "approval_policy": "approval_required_for_send", + "proof_metrics": ["partner_meetings", "referral_pipeline"], + "upgrade_path": "agency_partner_program", + }, + { + "service_id": "agency_partner_program", + "name_ar": "برنامج شركاء وكالات", + "target_customer_ar": "وكالات تسويق ومبيعات", + "outcome_ar": "Onboarding وكالة + عميل تجريبي + Proof مشترك + revenue share outline.", + "inputs_required": ["agency_profile"], + "workflow_steps": ["intake", "analyze", "draft", "approve", "track", "proof"], + "deliverables_ar": ["اتفاقية إطار draft", "قالب Proof مشترك", "SLA تشغيل"], + "pricing_range_sar": {"min": 5000, "max": 25000}, + "risk_level": "medium", + "required_integrations": [], + "approval_policy": "legal_review_required", + "proof_metrics": ["clients_onboarded", "revenue_share_estimate"], + "upgrade_path": "growth_os", + }, + { + "service_id": "email_revenue_rescue", + "name_ar": "إنقاذ إيراد البريد", + "target_customer_ar": "شركات بريد مليء بفرص ضائعة", + "outcome_ar": "كشف فرص متأخرة + مسودات Gmail فقط — بدون إرسال حتى موافقة.", + "inputs_required": ["gmail_label_scope"], + "workflow_steps": ["intake", "data_check", "draft", "approve", "proof", "upsell"], + "deliverables_ar": ["تقرير فرص ضائعة", "مسودات متابعة", "اجتماعات مقترحة"], + "pricing_range_sar": {"min": 1500, "max": 5000}, + "risk_level": "medium", + "required_integrations": ["gmail"], + "approval_policy": "approval_required_for_send", + "proof_metrics": ["drafts_approved", "meetings_from_email"], + "upgrade_path": "growth_os", + }, + { + "service_id": "whatsapp_compliance_setup", + "name_ar": "إعداد واتساب بامتثال", + "target_customer_ar": "شركات تستخدم واتساب بشكل عشوائي", + "outcome_ar": "تدقيق قوائم + opt-in + منع بارد + قوالب موافقة.", + "inputs_required": ["contact_sample_or_policy"], + "workflow_steps": ["intake", "contactability", "draft", "approve", "proof"], + "deliverables_ar": ["تقرير امتثال", "قوالب opt-in", "سياسة موافقات"], + "pricing_range_sar": {"min": 1500, "max": 4000}, + "risk_level": "high", + "required_integrations": ["whatsapp"], + "approval_policy": "cold_blocked_default", + "proof_metrics": ["blocked_cold_count", "opt_in_templates"], + "upgrade_path": "growth_os", + }, + { + "service_id": "linkedin_lead_gen_setup", + "name_ar": "إعداد LinkedIn Lead Gen", + "target_customer_ar": "B2B يستهدف قرارين", + "outcome_ar": "خطة Lead Forms + حقول مخفية + مسودات متابعة — بدون scraping.", + "inputs_required": ["icp", "offer"], + "workflow_steps": ["intake", "strategy", "draft", "approve", "proof"], + "deliverables_ar": ["خطة حملة", "نموذج حقول", "مسودات متابعة"], + "pricing_range_sar": {"min": 2000, "max": 7500}, + "risk_level": "low", + "required_integrations": ["linkedin_lead_forms"], + "approval_policy": "no_auto_dm", + "proof_metrics": ["leads_from_form", "followup_drafts"], + "upgrade_path": "growth_os", + }, + { + "service_id": "meeting_booking_sprint", + "name_ar": "سباق حجز اجتماعات", + "target_customer_ar": "فرق لديها prospects بدون تحويل لاجتماعات", + "outcome_ar": "دعوات + مسودة تقويم + أجندة + متابعة — بدون insert حي بدون موافقة.", + "inputs_required": ["targets", "offer"], + "workflow_steps": ["intake", "draft", "approve", "track", "proof"], + "deliverables_ar": ["مسودات دعوة", "payload تقويم draft", "ملخص اجتماع"], + "pricing_range_sar": {"min": 1500, "max": 5000}, + "risk_level": "low", + "required_integrations": ["google_calendar"], + "approval_policy": "calendar_insert_requires_approval", + "proof_metrics": ["meetings_booked", "followups_sent_after_approval"], + "upgrade_path": "growth_os", + }, + { + "service_id": "local_growth_os", + "name_ar": "نمو محلي", + "target_customer_ar": "عيادات ومطاعم ومتاجر", + "outcome_ar": "تقييمات + واتساب inbound + روابط دفع draft.", + "inputs_required": ["vertical", "branches"], + "workflow_steps": ["intake", "strategy", "draft", "approve", "proof"], + "deliverables_ar": ["كروت سمعة", "مسودات رد تقييم", "روابط دفع draft"], + "pricing_range_sar": {"min": 999, "max": 2999}, + "risk_level": "medium", + "required_integrations": ["google_business_profile", "whatsapp", "moyasar"], + "approval_policy": "approval_required_for_send", + "proof_metrics": ["reviews_addressed", "reactivation_drafts"], + "upgrade_path": "growth_os", + }, + { + "service_id": "executive_growth_brief", + "name_ar": "موجز تنفيذي يومي", + "target_customer_ar": "CEO / مدير نمو", + "outcome_ar": "٣ قرارات + ٣ فرص + ٣ مخاطر — عربي وأزرار ≤٣.", + "inputs_required": ["company_profile"], + "workflow_steps": ["intake", "analyze", "proof"], + "deliverables_ar": ["موجز يومي", "تقرير نهاية يوم"], + "pricing_range_sar": {"min": 499, "max": 999}, + "risk_level": "low", + "required_integrations": [], + "approval_policy": "suggest_and_draft_only", + "proof_metrics": ["decisions_logged", "risks_flagged"], + "upgrade_path": "growth_os", + }, +] + + +def list_tower_services() -> dict[str, Any]: + return {"services": list(_SERVICES), "count": len(_SERVICES), "version": 1, "demo": True} + + +def list_service_ids() -> list[str]: + return [s["service_id"] for s in _SERVICES] + + +def get_service_by_id(service_id: str) -> dict[str, Any] | None: + sid = (service_id or "").strip().lower() + for s in _SERVICES: + if s["service_id"] == sid: + return dict(s) + return None diff --git a/dealix/auto_client_acquisition/service_tower/service_scorecard.py b/dealix/auto_client_acquisition/service_tower/service_scorecard.py new file mode 100644 index 00000000..15ece542 --- /dev/null +++ b/dealix/auto_client_acquisition/service_tower/service_scorecard.py @@ -0,0 +1,42 @@ +"""Per-service success scorecard — deterministic from metrics dict.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id + + +def build_service_scorecard(service_id: str, metrics: dict[str, Any]) -> dict[str, Any]: + svc = get_service_by_id(service_id) + m = metrics or {} + drafts = int(m.get("drafts_created", 0)) + approved = int(m.get("approvals", 0)) + meetings = int(m.get("meetings_booked", 0)) + blocked = int(m.get("risks_blocked", 0)) + score = min(100, 20 + drafts * 3 + approved * 5 + meetings * 10 + blocked * 2) + status = "strong" if score >= 70 else "needs_attention" + return { + "service_id": service_id, + "score": score, + "status": status, + "summary_ar": f"درجة الخدمة {score}/١٠٠ — الحالة: {status}.", + "inputs_used": list(m.keys()), + "name_ar": (svc or {}).get("name_ar"), + "demo": True, + } + + +def calculate_service_success_score(metrics: dict[str, Any]) -> int: + sc = build_service_scorecard("growth_os", metrics) + return int(sc.get("score", 0)) + + +def recommend_next_step(metrics: dict[str, Any]) -> dict[str, Any]: + if int(metrics.get("risks_blocked", 0)) > int(metrics.get("meetings_booked", 0)): + return {"next_step_ar": "ركّز على تحويل المسودات المعتمدة إلى اجتماعات.", "demo": True} + return {"next_step_ar": "وسّع القنوات بعد تثبيت الاجتماعات.", "demo": True} + + +def summarize_scorecard_ar(scorecard: dict[str, Any]) -> str: + return str(scorecard.get("summary_ar") or "") diff --git a/dealix/auto_client_acquisition/service_tower/service_wizard.py b/dealix/auto_client_acquisition/service_tower/service_wizard.py new file mode 100644 index 00000000..7711029e --- /dev/null +++ b/dealix/auto_client_acquisition/service_tower/service_wizard.py @@ -0,0 +1,109 @@ +"""Recommend sellable service from intake — deterministic, no live actions.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id, list_service_ids + + +def recommend_service( + company_type: str, + goal: str, + has_contact_list: bool = False, + channels: list[str] | None = None, + budget_sar: int | None = None, +) -> dict[str, Any]: + ct = (company_type or "").lower().strip() + gl = (goal or "").lower().strip() + ch = [c.lower() for c in (channels or [])] + + recommended = "first_10_opportunities" + reasons: list[str] = [] + + if "agency" in ct or "وكالة" in company_type: + recommended = "agency_partner_program" + reasons.append("وكالات: قناة توزيع + برنامج شركاء.") + elif has_contact_list or "list" in gl or "csv" in gl or "قائمة" in goal: + recommended = "list_intelligence" + reasons.append("قائمة مرفوعة: ذكاء القوائم يقلل المخاطر أولاً.") + elif "email" in gl or "بريد" in goal or "inbox" in gl: + recommended = "email_revenue_rescue" + reasons.append("هدف بريدي: إنقاذ فرص ضائعة بمسودات فقط.") + elif "partner" in gl or "شراكة" in goal: + recommended = "partner_sprint" + reasons.append("هدف شراكات: سباق شركاء منظم.") + elif "meeting" in gl or "اجتماع" in goal: + recommended = "meeting_booking_sprint" + reasons.append("تحويل prospects لاجتماعات بمسودات موافقة.") + elif "linkedin" in gl or "لينكد" in goal: + recommended = "linkedin_lead_gen_setup" + reasons.append("لينكدإن: Lead Gen رسمي بدون أتمتة مخالفة.") + elif "whatsapp" in gl or "واتساب" in goal or "whatsapp" in ch: + recommended = "whatsapp_compliance_setup" + reasons.append("واتساب: امتثال وopt-in قبل أي حملة.") + elif "local" in gl or "عيادة" in goal or "متجر" in goal: + recommended = "local_growth_os" + reasons.append("نمو محلي: تقييمات + inbound + دفع draft.") + elif budget_sar is not None and budget_sar < 1500: + recommended = "free_growth_diagnostic" + reasons.append("ميزانية منخفضة: تشخيص مجاني ثم ترقية.") + + svc = get_service_by_id(recommended) + return { + "recommended_service_id": recommended, + "service": svc, + "reasons_ar": reasons or ["أسرع إثبات قيمة: سباق ١٠ فرص."], + "live_send": False, + "demo": True, + } + + +def build_intake_questions(service_id: str) -> dict[str, Any]: + svc = get_service_by_id(service_id) + if not svc: + return {"service_id": service_id, "questions": [], "error": "unknown_service", "demo": True} + qs: list[dict[str, str]] = [] + for inp in svc.get("inputs_required") or []: + qs.append( + { + "field": inp, + "prompt_ar": f"ما قيمة الحقل: {inp}؟", + "required": "true", + } + ) + return {"service_id": service_id, "questions": qs, "demo": True} + + +def validate_service_inputs(service_id: str, payload: dict[str, Any]) -> dict[str, Any]: + svc = get_service_by_id(service_id) + if not svc: + return {"ok": False, "missing": ["unknown_service"], "demo": True} + missing: list[str] = [] + for key in svc.get("inputs_required") or []: + if key not in (payload or {}) or payload.get(key) in (None, "", []): + missing.append(key) + return {"ok": len(missing) == 0, "missing": missing, "demo": True} + + +def summarize_recommendation_ar(result: dict[str, Any]) -> str: + rid = result.get("recommended_service_id") or "غير محدد" + reasons = result.get("reasons_ar") or [] + tail = " ".join(reasons) if reasons else "" + return f"التوصية: {rid}. {tail} لا يوجد إرسال حي من هذا المسار." + + +def start_service(service_id: str, payload: dict[str, Any]) -> dict[str, Any]: + """MVP: validate + return workflow handle — no side effects.""" + v = validate_service_inputs(service_id, payload or {}) + svc = get_service_by_id(service_id) + return { + "started": bool(v.get("ok")), + "service_id": service_id, + "validation": v, + "workflow_ref": f"wf_{service_id}_demo" if v.get("ok") else None, + "approval_required": True, + "live_send": False, + "service_snapshot": {"name_ar": (svc or {}).get("name_ar"), "risk_level": (svc or {}).get("risk_level")}, + "demo": True, + } diff --git a/dealix/auto_client_acquisition/service_tower/upgrade_paths.py b/dealix/auto_client_acquisition/service_tower/upgrade_paths.py new file mode 100644 index 00000000..8855d7c8 --- /dev/null +++ b/dealix/auto_client_acquisition/service_tower/upgrade_paths.py @@ -0,0 +1,47 @@ +"""Upsell / upgrade paths between services.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import get_service_by_id, list_tower_services + + +def build_all_upgrade_paths() -> dict[str, Any]: + paths = [] + for s in list_tower_services().get("services") or []: + paths.append( + { + "service_id": s["service_id"], + "name_ar": s.get("name_ar"), + "upgrade_path": s.get("upgrade_path"), + } + ) + return {"paths": paths, "demo": True} + + +def recommend_upgrade(service_id: str, results: dict[str, Any]) -> dict[str, Any]: + svc = get_service_by_id(service_id) + default_next = (svc or {}).get("upgrade_path") or "growth_os" + r = results or {} + if int(r.get("paid_conversion", 0)) > 0: + default_next = "growth_os" + return { + "from_service": service_id, + "recommended_upgrade": default_next, + "reason_ar": "بعد إثبات القيمة: Growth OS للتشغيل الشهري.", + "demo": True, + } + + +def build_upsell_message_ar(service_id: str, next_offer: str) -> str: + return ( + f"أكملنا {service_id} بنجاح. الخطوة المنطقية: {next_offer} " + "للتشغيل الشهري مع Proof Pack — بدون إرسال حي بدون موافقتك." + ) + + +def map_service_to_subscription(service_id: str) -> dict[str, Any]: + if service_id in ("growth_os", "self_growth_operator", "local_growth_os"): + return {"subscription_id": "growth_os_monthly", "eligible": True, "demo": True} + return {"subscription_id": "growth_os_monthly", "eligible": False, "note_ar": "خدمة مشروع — اشتراك اختياري بعد Pilot.", "demo": True} diff --git a/dealix/auto_client_acquisition/service_tower/vertical_service_map.py b/dealix/auto_client_acquisition/service_tower/vertical_service_map.py new file mode 100644 index 00000000..fd21d594 --- /dev/null +++ b/dealix/auto_client_acquisition/service_tower/vertical_service_map.py @@ -0,0 +1,49 @@ +"""Three-door UX map + vertical hints for Service Tower.""" + +from __future__ import annotations + +from typing import Any + + +def build_vertical_service_map() -> dict[str, Any]: + return { + "doors": [ + { + "door_id": "more_customers", + "title_ar": "أريد عملاء أكثر", + "service_ids": [ + "first_10_opportunities", + "growth_os", + "linkedin_lead_gen_setup", + "meeting_booking_sprint", + ], + }, + { + "door_id": "use_my_data", + "title_ar": "عندي بيانات وأريد أستفيد منها", + "service_ids": [ + "list_intelligence", + "email_revenue_rescue", + "whatsapp_compliance_setup", + "free_growth_diagnostic", + ], + }, + { + "door_id": "scale_strategy", + "title_ar": "أريد توسع وشراكات", + "service_ids": [ + "partner_sprint", + "agency_partner_program", + "executive_growth_brief", + "self_growth_operator", + ], + }, + ], + "verticals": [ + {"id": "agency", "label_ar": "وكالات", "priority_services": ["agency_partner_program", "first_10_opportunities"]}, + {"id": "training", "label_ar": "تدريب واستشارات", "priority_services": ["first_10_opportunities", "meeting_booking_sprint"]}, + {"id": "saas", "label_ar": "SaaS صغير", "priority_services": ["list_intelligence", "growth_os"]}, + {"id": "local", "label_ar": "محلي (عيادات/متاجر)", "priority_services": ["local_growth_os", "whatsapp_compliance_setup"]}, + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/service_tower/whatsapp_ceo_control.py b/dealix/auto_client_acquisition/service_tower/whatsapp_ceo_control.py new file mode 100644 index 00000000..63695446 --- /dev/null +++ b/dealix/auto_client_acquisition/service_tower/whatsapp_ceo_control.py @@ -0,0 +1,90 @@ +"""Arabic CEO / growth manager cards — max 3 buttons, approval flags.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.service_tower.service_catalog import list_tower_services + + +def _card( + title_ar: str, + summary_ar: str, + buttons: list[str], + approval_required: bool, +) -> dict[str, Any]: + btns = (buttons or [])[:3] + return { + "title_ar": title_ar, + "summary_ar": summary_ar, + "buttons": btns, + "approval_required": approval_required, + "live_send": False, + } + + +def build_ceo_daily_service_brief() -> dict[str, Any]: + data = list_tower_services() + n = int(data.get("count") or 0) + return { + "greeting_ar": "صباح الخير — موجز خدمات Dealix.", + "highlights_ar": [ + f"عدد الخدمات في البرج: {n}.", + "٣ مسودات بانتظار موافقتك (تجريبي).", + "لا إرسال حي من النظام افتراضياً.", + ], + "cards": [ + _card( + "اعتماد مسودات", + "هناك مسودات جاهزة للمراجعة قبل أي تواصل خارجي.", + ["اعرض المسودات", "لاحقاً", "تخطي"], + True, + ), + _card( + "مخاطر قناة", + "قناة واحدة تحتاج تهدئة حسب سمعة الإرسال (تجريبي).", + ["افتح التفاصيل", "خفّض الحجم", "تجاهل"], + True, + ), + ], + "demo": True, + } + + +def build_service_approval_card(service_id: str, action: str) -> dict[str, Any]: + return { + "service_id": service_id, + "action": action, + "card": _card( + f"موافقة: {service_id}", + f"الإجراء المقترح: {action} — لن يُنفَّذ إلا بعد اعتمادك.", + ["اعتمد", "عدّل", "ألغِ"], + True, + ), + "demo": True, + } + + +def build_risk_alert_card() -> dict[str, Any]: + return { + "card": _card( + "تنبيه مخاطر", + "تم رصد أرقام بحاجة مراجعة مصدر قبل واتساب.", + ["راجع القائمة", "صدّر الممنوع", "لاحقاً"], + True, + ), + "demo": True, + } + + +def build_end_of_day_service_report() -> dict[str, Any]: + return { + "title_ar": "تقرير نهاية اليوم — الخدمات", + "lines_ar": [ + "المسودات المعتمدة: ٢ (تجريبي).", + "الاجتماعات المقترحة: ١.", + "المخاطر التي تم منعها: ٤.", + ], + "live_send": False, + "demo": True, + } diff --git a/dealix/auto_client_acquisition/targeting_os/__init__.py b/dealix/auto_client_acquisition/targeting_os/__init__.py new file mode 100644 index 00000000..ab12d17d --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/__init__.py @@ -0,0 +1,33 @@ +"""Targeting & Acquisition OS — compliant account targeting and outreach planning.""" + +from auto_client_acquisition.targeting_os.account_finder import explain_why_now, rank_accounts, recommend_accounts +from auto_client_acquisition.targeting_os.acquisition_scorecard import build_acquisition_scorecard +from auto_client_acquisition.targeting_os.buyer_role_mapper import map_buying_committee +from auto_client_acquisition.targeting_os.contactability_matrix import evaluate_contactability +from auto_client_acquisition.targeting_os.contact_source_policy import classify_source +from auto_client_acquisition.targeting_os.daily_autopilot import build_daily_targeting_brief +from auto_client_acquisition.targeting_os.free_diagnostic import build_free_growth_diagnostic +from auto_client_acquisition.targeting_os.linkedin_strategy import recommend_linkedin_strategy +from auto_client_acquisition.targeting_os.outreach_scheduler import build_outreach_plan, summarize_plan_ar +from auto_client_acquisition.targeting_os.reputation_guard import calculate_channel_reputation, should_pause_channel +from auto_client_acquisition.targeting_os.self_growth_mode import build_self_growth_daily_brief +from auto_client_acquisition.targeting_os.service_offers import list_targeting_services + +__all__ = [ + "build_acquisition_scorecard", + "build_daily_targeting_brief", + "build_free_growth_diagnostic", + "build_outreach_plan", + "build_self_growth_daily_brief", + "calculate_channel_reputation", + "classify_source", + "evaluate_contactability", + "explain_why_now", + "list_targeting_services", + "map_buying_committee", + "rank_accounts", + "recommend_accounts", + "recommend_linkedin_strategy", + "should_pause_channel", + "summarize_plan_ar", +] diff --git a/dealix/auto_client_acquisition/targeting_os/account_finder.py b/dealix/auto_client_acquisition/targeting_os/account_finder.py new file mode 100644 index 00000000..f4890e8d --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/account_finder.py @@ -0,0 +1,88 @@ +"""Recommend target accounts from sector/city/goal — deterministic demo accounts.""" + +from __future__ import annotations + +from typing import Any + +_SIGNALS = ( + "hiring_sales", + "website_updated", + "google_reviews_active", + "booking_link", + "growing_team", +) + + +def recommend_accounts( + sector: str, + city: str, + offer: str, + goal: str, + *, + limit: int = 10, +) -> dict[str, Any]: + sector_ar = sector or "خدمات B2B" + city_ar = city or "الرياض" + base = [ + { + "company": f"شركة ألفا — {sector_ar}", + "city": city_ar, + "fit_score": 88, + "why_now_ar": "إعلان وظائف مبيعات + صفحة خدمات محدثة.", + "best_channel": "email_first", + "risk_level": "low", + "signals": ["hiring_sales", "website_updated"], + }, + { + "company": f"مؤسسة بيتا — {sector_ar}", + "city": city_ar, + "fit_score": 82, + "why_now_ar": "تقييمات Google نشطة — فرصة سمعة محلية.", + "best_channel": "google_business_draft", + "risk_level": "low", + "signals": ["google_reviews_active", "booking_link"], + }, + { + "company": f"مجموعة جاما — {sector_ar}", + "city": "جدة", + "fit_score": 76, + "why_now_ar": "توسع فريق — احتمال شراء أدوات نمو.", + "best_channel": "linkedin_lead_form", + "risk_level": "medium", + "signals": ["growing_team"], + }, + ] + accounts = [] + for i in range(max(1, min(limit, 20))): + a = dict(base[i % len(base)]) + a["id"] = f"acct_demo_{i+1}" + a["company"] = f"{a['company']} ({i+1})" + a["offer_fit_ar"] = f"العرض «{offer or 'Growth OS'}» مناسب لهدف «{goal or 'نمو'}»." + accounts.append(a) + return {"accounts": accounts[:limit], "count": len(accounts[:limit]), "demo": True} + + +def score_account_fit(account: dict[str, Any]) -> int: + return int(account.get("fit_score") or 70) + + +def explain_why_now(account: dict[str, Any]) -> str: + return str(account.get("why_now_ar") or "إشارات سوق عامة — راجع التفاصيل قبل التواصل.") + + +def recommend_account_source_strategy(account: dict[str, Any]) -> dict[str, Any]: + ch = str(account.get("best_channel") or "email_first") + return { + "account_id": account.get("id"), + "recommended_first_touch": ch, + "steps_ar": [ + "تحقق من المصدر والـ opt-in.", + "جهّز مسودة بريد عبر المنصة.", + "لا واتساب بارد بدون علاقة.", + ], + "demo": True, + } + + +def rank_accounts(accounts: list[dict[str, Any]]) -> list[dict[str, Any]]: + return sorted(accounts, key=lambda x: -score_account_fit(x)) diff --git a/dealix/auto_client_acquisition/targeting_os/acquisition_scorecard.py b/dealix/auto_client_acquisition/targeting_os/acquisition_scorecard.py new file mode 100644 index 00000000..2e799de3 --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/acquisition_scorecard.py @@ -0,0 +1,39 @@ +"""Acquisition metrics snapshot — deterministic.""" + +from __future__ import annotations + +from typing import Any + + +def calculate_pipeline_created(opportunities: list[dict[str, Any]]) -> int: + total = 0 + for o in opportunities: + total += int(o.get("expected_value_sar") or o.get("expected_impact_sar") or 5000) + return total + + +def calculate_meetings_booked(events: list[dict[str, Any]]) -> int: + return sum(1 for e in events if e.get("type") == "meeting_booked") + + +def calculate_risks_blocked(actions: list[dict[str, Any]]) -> int: + return sum(1 for a in actions if a.get("outcome") == "blocked") + + +def calculate_productivity_score(metrics: dict[str, Any]) -> int: + base = 50 + base += min(30, int(metrics.get("drafts_approved", 0)) * 3) + base += min(20, int(metrics.get("meetings_booked", 0)) * 5) + return max(0, min(100, base)) + + +def build_acquisition_scorecard(metrics: dict[str, Any]) -> dict[str, Any]: + return { + "leads_created": metrics.get("leads_created", 0), + "meetings_booked": metrics.get("meetings_booked", 0), + "drafts_approved": metrics.get("drafts_approved", 0), + "risks_blocked": metrics.get("risks_blocked", 0), + "pipeline_created_sar": metrics.get("pipeline_created_sar", 0), + "productivity_score": calculate_productivity_score(metrics), + "demo": True, + } diff --git a/dealix/auto_client_acquisition/targeting_os/buyer_role_mapper.py b/dealix/auto_client_acquisition/targeting_os/buyer_role_mapper.py new file mode 100644 index 00000000..71ab2af0 --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/buyer_role_mapper.py @@ -0,0 +1,62 @@ +"""Map sector/goal to buying committee roles — deterministic.""" + +from __future__ import annotations + +from typing import Any + +_SECTOR_ROLES: dict[str, dict[str, list[str]]] = { + "training": { + "primary": ["Founder/CEO", "Head of Sales"], + "influencers": ["HR Manager", "Operations Manager"], + }, + "saas": { + "primary": ["Founder/CEO", "Procurement Manager"], + "influencers": ["IT Manager", "Head of Sales"], + }, + "clinics": { + "primary": ["Clinic Manager", "Founder/CEO"], + "influencers": ["Operations Manager", "HR Manager"], + }, + "default": { + "primary": ["Founder/CEO", "Head of Sales"], + "influencers": ["Marketing Manager", "Business Development Manager"], + }, +} + + +def map_buying_committee(sector: str, company_size: str | None, goal: str | None) -> dict[str, Any]: + key = (sector or "").strip().lower() or "default" + if key not in _SECTOR_ROLES: + key = "default" + roles = _SECTOR_ROLES[key] + size = (company_size or "smb").lower() + g = (goal or "book_more_b2b_meetings").lower() + note = "شركة أكبر: أضف Procurement" if size in ("enterprise", "large") else "تركيز على Founder/Head of Sales في SMB." + if "partner" in g: + note += " هدف شراكة: أضف Agency Owner كمؤثر." + return { + "sector": sector or "unknown", + "company_size": size, + "goal": g, + "primary_decision_makers": roles["primary"], + "influencers": roles["influencers"], + "note_ar": note, + "demo": True, + } + + +def recommend_decision_maker_roles(sector: str, goal: str | None) -> list[str]: + return list(map_buying_committee(sector, None, goal)["primary_decision_makers"]) + + +def recommend_influencer_roles(sector: str, goal: str | None) -> list[str]: + return list(map_buying_committee(sector, None, goal)["influencers"]) + + +def draft_role_based_angle(role: str, sector: str, offer: str) -> dict[str, Any]: + return { + "role": role, + "sector": sector, + "angle_ar": f"نربط «{offer}» بأثر مباشر على {role}: وقت أقل، صفقات أوضح، متابعة موثّقة.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/targeting_os/contact_source_policy.py b/dealix/auto_client_acquisition/targeting_os/contact_source_policy.py new file mode 100644 index 00000000..7a267dcd --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/contact_source_policy.py @@ -0,0 +1,97 @@ +"""Classify lead/contact sources and allowed channels — policy only, no I/O.""" + +from __future__ import annotations + +from typing import Any + +_SOURCE_ORDER = ( + "crm_customer", + "inbound_lead", + "website_form", + "linkedin_lead_form", + "event_lead", + "referral", + "partner_intro", + "manual_research", + "uploaded_list", + "unknown_source", + "cold_list", + "opt_out", +) + + +def classify_source(source: str | None) -> str: + s = (source or "").strip().lower().replace(" ", "_") + if s in ("opt_out", "optout"): + return "opt_out" + if s in _SOURCE_ORDER: + return s + if s in ("unknown", "", "none"): + return "unknown_source" + return "unknown_source" + + +def source_risk_score(source: str) -> int: + """0 = low risk, 100 = high risk (for sorting).""" + s = classify_source(source) + return { + "opt_out": 100, + "cold_list": 85, + "unknown_source": 70, + "uploaded_list": 55, + "manual_research": 40, + "referral": 35, + "partner_intro": 30, + "event_lead": 25, + "linkedin_lead_form": 20, + "website_form": 15, + "inbound_lead": 10, + "crm_customer": 10, + }.get(s, 65) + + +def allowed_channels_for_source(source: str, opt_in_status: str | None) -> list[str]: + s = classify_source(source) + opt = (opt_in_status or "").lower() + if s == "opt_out": + return [] + if s == "cold_list": + return ["email_draft_review"] if opt != "explicit" else ["email_draft_review", "linkedin_manual_task"] + if s == "unknown_source": + return ["email_draft_review", "internal_task"] + if s == "uploaded_list": + return ["email_draft_review", "internal_task"] + if s == "manual_research": + return ["email_draft_review", "linkedin_manual_task", "internal_task"] + if s in ("referral", "partner_intro"): + return ["email_draft_review", "whatsapp_draft_if_opt_in", "calendar_draft", "internal_task"] + if s in ("linkedin_lead_form", "website_form", "inbound_lead", "event_lead"): + return ["email_draft_review", "whatsapp_draft_if_opt_in", "calendar_draft", "internal_task"] + if s == "crm_customer": + return ["email_draft_review", "whatsapp_draft_if_opt_in", "calendar_draft", "payment_draft", "internal_task"] + return ["internal_task"] + + +def required_review_level(source: str) -> str: + s = classify_source(source) + if s in ("opt_out", "cold_list"): + return "blocked" + if s in ("unknown_source", "uploaded_list", "manual_research"): + return "human_review" + if s in ("referral", "partner_intro"): + return "light_review" + return "auto_ok_with_approval" + + +def retention_recommendation(source: str) -> dict[str, Any]: + s = classify_source(source) + days = {"opt_out": 0, "cold_list": 30, "unknown_source": 90}.get(s, 365) + return { + "source": s, + "suggested_retention_days": days, + "note_ar": "توصية MVP — راجع سياسة الاحتفاظ مع DPO قبل الإنتاج.", + } + + +def list_sources_reference() -> dict[str, Any]: + return {"sources": list(_SOURCE_ORDER), "demo": True} diff --git a/dealix/auto_client_acquisition/targeting_os/contactability_matrix.py b/dealix/auto_client_acquisition/targeting_os/contactability_matrix.py new file mode 100644 index 00000000..8a8612e7 --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/contactability_matrix.py @@ -0,0 +1,140 @@ +"""Contactability: safe / needs_review / blocked + action modes — no send.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.targeting_os.contact_source_policy import ( + allowed_channels_for_source, + classify_source, + required_review_level, +) + + +def block_reason_codes() -> dict[str, str]: + return { + "opted_out": "المتلقي طلب عدم التواصل.", + "cold_whatsapp": "واتساب بارد غير مسموح افتراضياً.", + "unknown_source": "مصدر غير موثّق — يحتاج مراجعة.", + "purchased_list": "قائمة مشتراة/مكشوفة — محظورة.", + "no_identifier": "لا هاتف ولا إيميل صالح.", + } + + +def evaluate_contactability(contact: dict[str, Any], desired_channel: str | None = None) -> dict[str, Any]: + """ + Returns status, allowed_action_modes, channels, Arabic explanation. + ``contact`` may include: source, opted_out, cold_whatsapp, phone, email, opt_in_status. + """ + if contact.get("opted_out") in (True, "true", "1", 1): + return _result( + "blocked", + "opted_out", + ["blocked"], + [], + "محظور: opt-out ساري.", + ) + + if contact.get("cold_whatsapp") in (True, "true", "1", 1): + return _result( + "blocked", + "cold_whatsapp", + ["blocked"], + [], + "محظور: واتساب بارد — استخدم إيميل أو opt-in صريح.", + ) + + raw_src = str(contact.get("source") or "").lower() + if raw_src in ("scraped", "purchased_list"): + return _result( + "blocked", + "purchased_list", + ["blocked"], + [], + "محظور: مصدر قائمة غير موثوق أو scraping.", + ) + + src = classify_source(str(contact.get("source") or "unknown_source")) + if src in ("opt_out",): + return _result("blocked", "opted_out", ["blocked"], [], "محظور: مصدر opt-out.") + + if src == "cold_list": + return _result( + "needs_review", + "cold_list", + ["suggest_only", "draft_only", "approval_required"], + allowed_channels_for_source(src, contact.get("opt_in_status")), + "قائمة باردة — مسودات بريد فقط تحت مراجعة.", + ) + + opt_in = str(contact.get("opt_in_status") or "") + phone = str(contact.get("phone") or contact.get("mobile") or "").strip() + email = str(contact.get("email") or "").strip() + if not phone and not email: + return _result( + "needs_review", + "no_identifier", + ["suggest_only", "blocked"], + [], + "يحتاج مراجعة: لا معرّف تواصل واضح.", + ) + + if src == "unknown_source": + return _result( + "needs_review", + "unknown_source", + ["suggest_only", "draft_only", "approval_required"], + allowed_channels_for_source(src, opt_in), + "مراجعة بشرية: المصدر غير موثّق.", + ) + + chans = allowed_channels_for_source(src, opt_in) + review = required_review_level(src) + if review == "human_review": + status = "needs_review" + modes = ["suggest_only", "draft_only", "approval_required"] + elif review == "light_review": + status = "safe" if desired_channel != "whatsapp" else "needs_review" + modes = ["draft_only", "approval_required", "suggest_only"] + else: + status = "safe" + modes = ["draft_only", "approval_required", "suggest_only"] + + if desired_channel == "whatsapp" and "whatsapp_draft_if_opt_in" not in chans and "opt_in" not in opt_in.lower(): + if status == "safe": + status = "needs_review" + modes = ["draft_only", "approval_required", "blocked"] + + ar = { + "safe": "مسموح بمسودات وموافقة قبل أي إرسال خارجي.", + "needs_review": "يحتاج مراجعة قبل التواصل.", + "blocked": "غير مسموح بالتواصل بهذه القناة/المصدر.", + }[status] + + return _result(status, review, modes, chans, ar) + + +def explain_contactability_ar(result: dict[str, Any]) -> str: + return str(result.get("summary_ar") or "") + + +def allowed_action_modes(result: dict[str, Any]) -> list[str]: + return list(result.get("action_modes") or []) + + +def _result( + status: str, + reason: str, + modes: list[str], + channels: list[str], + summary_ar: str, +) -> dict[str, Any]: + return { + "status": status, + "reason_code": reason, + "action_modes": modes, + "allowed_channel_hints": channels, + "summary_ar": summary_ar, + "approval_required": status != "blocked", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/targeting_os/contract_drafts.py b/dealix/auto_client_acquisition/targeting_os/contract_drafts.py new file mode 100644 index 00000000..b7efac1a --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/contract_drafts.py @@ -0,0 +1,46 @@ +"""Contract outlines — not legal advice; human + legal review required.""" + +from __future__ import annotations + +from typing import Any + + +def _meta() -> dict[str, Any]: + return {"legal_review_required": True, "approval_required": True, "not_legal_advice": True, "demo": True} + + +def draft_pilot_agreement_outline() -> dict[str, Any]: + return { + "title": "Pilot Agreement Outline", + "sections": ["Scope", "Duration", "Fees", "Data processing", "Termination", "Liability cap"], + **_meta(), + } + + +def draft_dpa_outline() -> dict[str, Any]: + return {"title": "DPA Outline", "sections": ["Roles", "Subprocessors", "Retention", "Security", "Subject rights"], **_meta()} + + +def draft_referral_agreement_outline() -> dict[str, Any]: + return {"title": "Referral Agreement Outline", "sections": ["Commission", "Attribution", "Payment terms"], **_meta()} + + +def draft_agency_partner_outline() -> dict[str, Any]: + return {"title": "Agency Partner Outline", "sections": ["White-label options", "Support", "Revenue share"], **_meta()} + + +def draft_scope_of_work() -> dict[str, Any]: + return {"title": "SOW Outline", "sections": ["Deliverables", "Timeline", "Acceptance criteria"], **_meta()} + + +def list_contract_templates() -> dict[str, Any]: + return { + "templates": [ + draft_pilot_agreement_outline(), + draft_dpa_outline(), + draft_referral_agreement_outline(), + draft_agency_partner_outline(), + draft_scope_of_work(), + ], + "demo": True, + } diff --git a/dealix/auto_client_acquisition/targeting_os/daily_autopilot.py b/dealix/auto_client_acquisition/targeting_os/daily_autopilot.py new file mode 100644 index 00000000..55faa265 --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/daily_autopilot.py @@ -0,0 +1,60 @@ +"""Daily targeting brief — cards only, no live sends.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.targeting_os.account_finder import recommend_accounts + + +def build_daily_targeting_brief(company_profile: dict[str, Any]) -> dict[str, Any]: + sector = str(company_profile.get("sector") or "training") + city = str(company_profile.get("city") or "الرياض") + offer = str(company_profile.get("offer") or "Growth OS") + goal = str(company_profile.get("goal") or "book_more_b2b_meetings") + acc = recommend_accounts(sector, city, offer, goal, limit=5) + cards = [] + for a in acc["accounts"][:5]: + cards.append( + { + "type": "new_account", + "title_ar": f"فرصة: {a['company']}", + "summary_ar": a.get("why_now_ar", ""), + "buttons": ["مسودة بريد", "تخطي", "تفاصيل"], + "approval_required": True, + } + ) + cards.append( + { + "type": "approval_needed", + "title_ar": "مراجعات معلّقة", + "summary_ar": "هناك مسودات بانتظار موافقتك.", + "buttons": ["افتح المسودات", "لاحقاً"], + "approval_required": True, + } + ) + return {"date": "demo", "cards": cards[:10], "note_ar": "عرض فقط — لا إرسال.", "demo": True} + + +def recommend_today_actions(company_profile: dict[str, Any]) -> list[str]: + return [ + "راجع أعلى 3 حسابات في القائمة", + "اعتمد مسودتي بريد واحدة على الأقل", + "حدّث حالة opt-in للواتساب", + ] + + +def prioritize_cards(cards: list[dict[str, Any]]) -> list[dict[str, Any]]: + order = {"approval_needed": 0, "reputation_risk": 1, "new_account": 2} + return sorted(cards, key=lambda c: order.get(str(c.get("type")), 9)) + + +def build_end_of_day_report(day_metrics: dict[str, Any]) -> dict[str, Any]: + return { + "accounts_researched": day_metrics.get("accounts_researched", 12), + "drafts_created": day_metrics.get("drafts_created", 4), + "approvals_pending": day_metrics.get("approvals_pending", 2), + "risks_blocked": day_metrics.get("risks_blocked", 3), + "summary_ar": "تقرير نهاية اليوم — جاهز للمراجعة الإدارية.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/targeting_os/email_strategy.py b/dealix/auto_client_acquisition/targeting_os/email_strategy.py new file mode 100644 index 00000000..62c78ea3 --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/email_strategy.py @@ -0,0 +1,55 @@ +"""B2B email drafts — approval-first, no misleading subjects.""" + +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]: + name = str(contact.get("name") or "فريق العمل") + co = str(contact.get("company") or "شركتكم") + return { + "subject_ar": f"فكرة سريعة لـ {co} — {offer[:40]}", + "body_ar": ( + f"السلام عليكم {name}،\n\n" + f"لاحظنا: {why_now}\n" + f"نقدّم {offer} بمسار مسودات + موافقة قبل أي إرسال جماعي.\n\n" + f"هل يناسبكم ردّ قصير خلال الأسبوع؟\n\n" + f"مع تحيات فريق Dealix" + ), + "tone": tone, + "approval_required": True, + "demo": True, + } + + +def include_unsubscribe_footer(body: str) -> str: + footer = "\n\n---\nلإلغاء الاشتراك أو طلب عدم التواصل: ردّ بكلمة «توقف»." + return body + footer + + +def recommend_pacing(domain_reputation: str) -> dict[str, Any]: + rep = (domain_reputation or "unknown").lower() + daily = 20 if rep == "strong" else 8 if rep == "medium" else 3 + return {"max_daily_drafts": daily, "note_ar": "تدرّج في الحجم لحماية سمعة النطاق.", "demo": True} + + +def score_email_risk(contact: dict[str, Any], message: str) -> dict[str, Any]: + score = 25 + if "ضمان" in message or "100%" in message: + score += 40 + if classify_unknown(contact): + score += 20 + return {"risk_score": min(100, score), "needs_review": score > 50, "demo": True} + + +def build_followup_sequence(contact: dict[str, Any], offer: str) -> list[dict[str, Any]]: + return [ + {"day_offset": 3, "draft_ar": f"متابعة خفيفة بخصوص {offer} — هل نرسل مثالاً؟", "approval_required": True}, + {"day_offset": 7, "draft_ar": "إغلاق لطيف: نتوفر عند الحاجة.", "approval_required": True}, + ] + + +def classify_unknown(contact: dict[str, Any]) -> bool: + src = str(contact.get("source") or "").lower() + return src in ("", "unknown", "unknown_source", "cold_list") diff --git a/dealix/auto_client_acquisition/targeting_os/free_diagnostic.py b/dealix/auto_client_acquisition/targeting_os/free_diagnostic.py new file mode 100644 index 00000000..565307f5 --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/free_diagnostic.py @@ -0,0 +1,39 @@ +"""Free growth diagnostic — small preview, upsell to pilot.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.targeting_os.account_finder import recommend_accounts + + +def build_free_growth_diagnostic(company_profile: dict[str, Any]) -> dict[str, Any]: + sector = str(company_profile.get("sector") or "b2b") + city = str(company_profile.get("city") or "الرياض") + acc = recommend_accounts(sector, city, company_profile.get("offer") or "خدمة", company_profile.get("goal") or "نمو", limit=3) + opps = acc["accounts"][:3] + return { + "opportunities": opps, + "sample_message_ar": "نقدّم تجربة 7 أيام مع مسودات معتمدة — هل نرسل ملخصاً؟", + "risk_ar": "تأكد من opt-in قبل أي واتساب جماعي.", + "next_step_ar": "اطلب Pilot بـ 499 ريال أو ما يعادله بعد الاتفاق.", + "demo": True, + } + + +def analyze_uploaded_list_preview(contacts: list[dict[str, Any]]) -> dict[str, Any]: + """Thin wrapper shape for router; full analysis uses platform import preview.""" + return {"row_count": len(contacts), "hint_ar": "استخدم POST /api/v1/platform/contacts/import-preview للتحليل الكامل.", "demo": True} + + +def recommend_paid_pilot_offer(diagnostic: dict[str, Any]) -> dict[str, Any]: + return { + "name": "First 10 Opportunities Sprint", + "price_hint_sar": "499-1500", + "includes_ar": ["10 فرص", "10 مسودات", "Proof Pack مصغر"], + "demo": True, + } + + +def build_mini_proof_plan() -> dict[str, Any]: + return {"week_1": ["فرص", "مسودات"], "week_2": ["متابعة", "تقرير"], "demo": True} diff --git a/dealix/auto_client_acquisition/targeting_os/linkedin_strategy.py b/dealix/auto_client_acquisition/targeting_os/linkedin_strategy.py new file mode 100644 index 00000000..3b12717f --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/linkedin_strategy.py @@ -0,0 +1,63 @@ +"""LinkedIn-compliant strategy — Lead Gen, ads, manual tasks only.""" + +from __future__ import annotations + +from typing import Any + + +def linkedin_do_not_do() -> list[str]: + return [ + "scraping_profiles", + "auto_dm", + "auto_connect", + "bulk_export_without_consent", + "browser_automation_on_linkedin_feed", + ] + + +def recommend_linkedin_strategy(segment: str, goal: str) -> dict[str, Any]: + return { + "strategy": "lead_gen_forms_first", + "segment": segment, + "goal": goal, + "do_not_do": linkedin_do_not_do(), + "summary_ar": "استخدم Lead Gen Forms والإعلانات والمهام اليدوية المعتمدة — لا scraping ولا رسائل آلية.", + "demo": True, + } + + +def build_lead_gen_form_plan(segment: str, offer: str, campaign_name: str) -> dict[str, Any]: + return { + "campaign_name": campaign_name or "dealix_pilot", + "audience_hint": f"{segment} — أصحاب قرار في الخدمات B2B", + "offer": offer, + "hidden_fields_suggested": ["campaign_name", "sector", "sales_owner"], + "next_steps_ar": [ + "أنشئ حملة Lead Gen في LinkedIn Campaign Manager.", + "اربط الحقول المخفية بمصدر Dealix.", + "لا تفعّل إرسالاً آلياً من Dealix إلى InMail بدون سياسة.", + ], + "demo": True, + } + + +def build_manual_research_task(account: dict[str, Any], role: str) -> dict[str, Any]: + return { + "task_type": "manual_linkedin_lookup", + "company": account.get("company"), + "target_role": role, + "instructions_ar": f"ابحث يدوياً عن {role} في {account.get('company')} — انسخ الرابط العام فقط، لا أتمتة.", + "demo": True, + } + + +def build_safe_connection_message(role: str, company: str, offer: str) -> dict[str, Any]: + return { + "message_ar": ( + f"تحية، أتابع عمل {company}. نعمل على {offer} لفرق المبيعات في السعودية. " + f"إن كان عندكم اهتمام، أرسل ملخصاً قصيراً دون التزام." + ), + "approval_required": True, + "channel": "linkedin_manual_only", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/targeting_os/outreach_scheduler.py b/dealix/auto_client_acquisition/targeting_os/outreach_scheduler.py new file mode 100644 index 00000000..a820881a --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/outreach_scheduler.py @@ -0,0 +1,54 @@ +"""Multi-day outreach plan — limits and approvals, no execution.""" + +from __future__ import annotations + +from typing import Any + +_DEFAULT_LIMITS = { + "max_daily_email_drafts": 8, + "max_daily_whatsapp_approved_sends": 0, + "max_followups": 3, + "cooldown_days": 2, + "max_same_domain_contacts": 5, +} + + +def build_outreach_plan(targets: list[dict[str, Any]], channels: list[str], goal: str) -> dict[str, Any]: + steps = [] + for i, t in enumerate(targets[:15]): + steps.append( + { + "day_offset": (i % 3) * 2, + "target_id": t.get("id") or f"t{i}", + "channel": channels[0] if channels else "email", + "action": "draft_only", + "approval_required": True, + } + ) + return { + "goal": goal, + "steps": steps, + "limits": _DEFAULT_LIMITS, + "summary_ar": "خطة MVP — كل خطوة مسودة أو موافقة؛ لا إرسال تلقائي.", + "demo": True, + } + + +def schedule_followups(plan: dict[str, Any]) -> list[dict[str, Any]]: + return [{"followup_after_days": 3, "approval_required": True} for _ in plan.get("steps", [])[:5]] + + +def enforce_daily_limits(plan: dict[str, Any], limits: dict[str, Any] | None = None) -> dict[str, Any]: + lim = {**_DEFAULT_LIMITS, **(limits or {})} + steps = plan.get("steps") or [] + capped = steps[: lim["max_daily_email_drafts"]] + return {"capped_steps": len(capped), "limits_applied": lim, "truncated": len(steps) > len(capped), "demo": True} + + +def stop_on_opt_out(plan: dict[str, Any]) -> dict[str, Any]: + return {"stopped": True, "reason": "opt_out_global", "note_ar": "أي opt-out يوقف الخطة فوراً.", "demo": True} + + +def summarize_plan_ar(plan: dict[str, Any]) -> str: + n = len(plan.get("steps") or []) + return f"خطة بـ {n} خطوة — كلها تتطلب موافقة قبل التنفيذ الخارجي." diff --git a/dealix/auto_client_acquisition/targeting_os/reputation_guard.py b/dealix/auto_client_acquisition/targeting_os/reputation_guard.py new file mode 100644 index 00000000..7ecf2cf5 --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/reputation_guard.py @@ -0,0 +1,51 @@ +"""Channel reputation — pause suggestions when metrics look bad.""" + +from __future__ import annotations + +from typing import Any + + +def risk_thresholds() -> dict[str, float]: + return { + "bounce_rate_max": 0.08, + "opt_out_rate_max": 0.02, + "complaint_rate_max": 0.001, + "min_reply_rate": 0.05, + } + + +def calculate_channel_reputation(metrics: dict[str, Any]) -> dict[str, Any]: + bounce = float(metrics.get("bounce_rate") or 0) + opt_out = float(metrics.get("opt_out_rate") or 0) + complaint = float(metrics.get("complaint_rate") or 0) + reply = float(metrics.get("reply_rate") or 0) + th = risk_thresholds() + score = 100.0 + if bounce > th["bounce_rate_max"]: + score -= 30 + if opt_out > th["opt_out_rate_max"]: + score -= 25 + if complaint > th["complaint_rate_max"]: + score -= 40 + if reply < th["min_reply_rate"]: + score -= 15 + return {"reputation_score": max(0, min(100, int(score))), "raw": metrics, "demo": True} + + +def should_pause_channel(metrics: dict[str, Any]) -> bool: + rep = calculate_channel_reputation(metrics) + return rep["reputation_score"] < 40 + + +def recommend_recovery_action(metrics: dict[str, Any]) -> dict[str, Any]: + if should_pause_channel(metrics): + return { + "action_ar": "أوقف الإرسال، راجع القوائم والمصادر، قلّل الحجم، أعد تفعيل القناة بعد تحسين المحتوى.", + "demo": True, + } + return {"action_ar": "استمر مع مراقبة يومية للردود وopt-out.", "demo": True} + + +def summarize_reputation_ar(metrics: dict[str, Any]) -> str: + rep = calculate_channel_reputation(metrics) + return f"درجة السمعة للقناة: {rep['reputation_score']}/100." diff --git a/dealix/auto_client_acquisition/targeting_os/self_growth_mode.py b/dealix/auto_client_acquisition/targeting_os/self_growth_mode.py new file mode 100644 index 00000000..507af92d --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/self_growth_mode.py @@ -0,0 +1,49 @@ +"""Dealix self-growth plan — drafts and targets only, no auto outreach.""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.targeting_os.account_finder import recommend_accounts + + +def build_dealix_self_growth_plan() -> dict[str, Any]: + return { + "focus_ar": "وكالات B2B ومستشارو نمو في الرياض وجدة", + "weekly_goal": "5 ديمو + 2 pilot", + "constraints_ar": "لا scraping، لا إرسال آلي — مسودات وموافقة فقط.", + "demo": True, + } + + +def recommend_dealix_targets(sector_focus: str, city_focus: str) -> dict[str, Any]: + return recommend_accounts(sector_focus or "saas", city_focus or "الرياض", "Dealix Growth OS", "partner_channel", limit=8) + + +def build_free_service_offer(target: dict[str, Any]) -> dict[str, Any]: + return { + "target_company": target.get("company"), + "offer_ar": "تشخيص نمو مجاني: 3 فرص + رسالة واحدة + تقرير مخاطر مصغر.", + "cta_ar": "احجز 15 دقيقة ديمو.", + "approval_required": True, + "demo": True, + } + + +def build_self_growth_daily_brief() -> dict[str, Any]: + t = recommend_dealix_targets("agency", "الرياض") + return { + "title_ar": "Dealix — Self Growth", + "top_targets": t["accounts"][:3], + "actions_ar": ["جهّز ديمو", "أرسل مسودة بريد يدوية بعد الموافقة", "حدّث قائمة المتابعة"], + "demo": True, + } + + +def build_weekly_learning_report(results: dict[str, Any]) -> dict[str, Any]: + return { + "best_sector": results.get("best_sector", "training"), + "best_message_angle": results.get("best_angle", "pilot_7_days"), + "next_experiment_ar": "اختبر قطاع العيادات الأسبوع القادم.", + "demo": True, + } diff --git a/dealix/auto_client_acquisition/targeting_os/service_offers.py b/dealix/auto_client_acquisition/targeting_os/service_offers.py new file mode 100644 index 00000000..31b05d8c --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/service_offers.py @@ -0,0 +1,73 @@ +"""Sellable services metadata — aligns with platform service_catalog where possible.""" + +from __future__ import annotations + +from typing import Any + +_SERVICES: list[dict[str, Any]] = [ + { + "id": "list_intelligence", + "name_ar": "ذكاء القوائم", + "target_customer": "شركات عندها CSV عملاء", + "outcome_ar": "تصنيف وcontactability", + "pricing_model": "fixed_sprint", + "price_hint_sar": "499-1500", + "required_integrations": [], + "proof_metric": "safe_vs_blocked_ratio", + }, + { + "id": "first_10_sprint", + "name_ar": "سباق 10 فرص", + "target_customer": "B2B سعودي", + "outcome_ar": "10 فرص + مسودات", + "pricing_model": "fixed_sprint", + "price_hint_sar": "499-1500", + "required_integrations": [], + "proof_metric": "opportunities_accepted", + }, + { + "id": "growth_os_monthly", + "name_ar": "Growth OS شهري", + "target_customer": "فرق مبيعات", + "outcome_ar": "تشغيل يومي + Proof", + "pricing_model": "subscription", + "price_hint_sar": "2999+", + "required_integrations": ["gmail", "calendar"], + "proof_metric": "meetings_booked", + }, + { + "id": "partner_sprint", + "name_ar": "سباق شراكات", + "target_customer": "وكالات", + "outcome_ar": "قائمة شركاء + مسودات", + "pricing_model": "project", + "price_hint_sar": "3000-7500", + "required_integrations": [], + "proof_metric": "partner_meetings", + }, +] + + +def list_targeting_services() -> dict[str, Any]: + return {"services": list(_SERVICES), "count": len(_SERVICES), "demo": True} + + +def recommend_service_offer(customer_type: str, goal: str) -> dict[str, Any]: + ct = (customer_type or "").lower() + if "agency" in ct: + return {"recommended": "partner_sprint", "reason_ar": "الوكالات تنقل Dealix لعملائها.", "demo": True} + if "list" in goal.lower() or "csv" in goal.lower(): + return {"recommended": "list_intelligence", "reason_ar": "تنظيف القائمة أولاً يقلل المخاطر.", "demo": True} + return {"recommended": "first_10_sprint", "reason_ar": "أسرع إثبات قيمة.", "demo": True} + + +def build_offer_card(service_id: str) -> dict[str, Any]: + for s in _SERVICES: + if s["id"] == service_id: + return {**s, "buttons": ["اطلب عرضاً", "تفاصيل", "لاحقاً"]} + return {"error": "unknown_service", "demo": True} + + +def estimate_service_price(service_id: str) -> dict[str, Any]: + card = build_offer_card(service_id) + return {"service_id": service_id, "price_hint_sar": card.get("price_hint_sar"), "demo": True} diff --git a/dealix/auto_client_acquisition/targeting_os/social_strategy.py b/dealix/auto_client_acquisition/targeting_os/social_strategy.py new file mode 100644 index 00000000..408b639d --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/social_strategy.py @@ -0,0 +1,39 @@ +"""Social — official APIs and drafts only.""" + +from __future__ import annotations + +from typing import Any + + +def social_do_not_do() -> list[str]: + return ["unauthorized_scraping", "auto_dm_without_permission", "firehose_access", "fake_engagement"] + + +def recommend_social_sources(sector: str, goal: str) -> dict[str, Any]: + return { + "sector": sector, + "goal": goal, + "recommended_sources": ["linkedin_lead_form", "meta_business_inbox_registered", "x_api_registered_webhook"], + "do_not_do": social_do_not_do(), + "summary_ar": "اربط القنوات التي تملك صلاحية رسمية لها فقط.", + "demo": True, + } + + +def build_social_listening_plan(sector: str, keywords: list[str]) -> dict[str, Any]: + return { + "sector": sector, + "keywords": keywords[:20], + "plan_ar": "راقب الكلمات عبر webhooks/APIs المسجّلة؛ حوّل الإشارات إلى كروت مسودة داخل المنصة.", + "approval_required": True, + "demo": True, + } + + +def draft_public_reply(comment: str, brand_voice: str) -> dict[str, Any]: + return { + "draft_reply_ar": f"شكراً لتعليقكم. نتواصل بالخاص لخدمتكم — [{brand_voice}]", + "original_snippet": comment[:200], + "approval_required": True, + "demo": True, + } diff --git a/dealix/auto_client_acquisition/targeting_os/whatsapp_strategy.py b/dealix/auto_client_acquisition/targeting_os/whatsapp_strategy.py new file mode 100644 index 00000000..41ac2be7 --- /dev/null +++ b/dealix/auto_client_acquisition/targeting_os/whatsapp_strategy.py @@ -0,0 +1,44 @@ +"""WhatsApp drafts — opt-in first, no cold outbound by default.""" + +from __future__ import annotations + +from typing import Any + + +def whatsapp_do_not_do() -> list[str]: + return ["cold_broadcast", "purchased_list_upload_send", "auto_reply_without_policy", "skip_opt_in"] + + +def requires_opt_in(contact: dict[str, Any]) -> bool: + src = str(contact.get("source") or "").lower() + if contact.get("opt_in_whatsapp") in (True, "true", "1", 1): + return False + return src not in ("inbound_lead", "prior_customer", "crm_customer", "explicit_consent") + + +def draft_whatsapp_message(contact: dict[str, Any], offer: str, why_now: str) -> dict[str, Any]: + return { + "message_ar": f"هلا، بخصوص {why_now}: نقدّم {offer} بمسار موافقة. تفضّلون ملخص سطرين؟", + "approval_required": True, + "demo": True, + } + + +def score_whatsapp_risk(contact: dict[str, Any], message: str) -> dict[str, Any]: + risk = 30 if requires_opt_in(contact) else 10 + if contact.get("cold_whatsapp"): + risk = 100 + if "ضمان" in message: + risk = min(100, risk + 25) + return {"risk_score": risk, "blocked": risk >= 90, "demo": True} + + +def build_opt_in_request_template(company_name: str) -> dict[str, Any]: + return { + "template_ar": ( + f"مرحباً، نحن {company_name}. نرسل تحديثات قصيرة عبر واتساب حول [الموضوع]. " + "الرد بـ «نعم» يعني موافقتك. «لا» توقف الرسائل." + ), + "approval_required": True, + "demo": True, + } diff --git a/dealix/docs/AGENCY_PARTNER_MODE.md b/dealix/docs/AGENCY_PARTNER_MODE.md new file mode 100644 index 00000000..45e3f6c1 --- /dev/null +++ b/dealix/docs/AGENCY_PARTNER_MODE.md @@ -0,0 +1,12 @@ +# Agency Partner Mode + +وضع للوكالات: شراكات، عميل تجريبي مشترك، Proof مشترك، تتبع إحالات. + +## المنتج + +- خدمات: `partner_sprint`, `agency_partner_program` في Service Tower. +- المشغّل: `mode=agency_partner` في `POST /api/v1/operator/chat/message` (يؤثر على `mode_profile` فقط في MVP). + +## الصفحة + +- [`landing/agency-partner.html`](../landing/agency-partner.html) diff --git a/dealix/docs/AGENT_OBSERVABILITY_EVALS.md b/dealix/docs/AGENT_OBSERVABILITY_EVALS.md new file mode 100644 index 00000000..a420c052 --- /dev/null +++ b/dealix/docs/AGENT_OBSERVABILITY_EVALS.md @@ -0,0 +1,21 @@ +# Agent Observability and Evals (Growth Tower) + +أشكال JSON للتقييم والتتبع تمهّد لربط **Langfuse** أو أدوات مماثلة في staging/production. + +## كود + +- `auto_client_acquisition/agent_observability/trace_events.py` — `build_trace_event` +- `safety_eval.py` — تقييم أمان بسيط على النص العربي +- `saudi_tone_eval.py` — ملاءمة نبرة سعودية شكلية +- `eval_cases.py` — حالات مرجعية (توسيع لاحقاً) + +## API + +- `GET /api/v1/agent-observability/demo` +- `POST /api/v1/agent-observability/eval/safety` — `{ "text_ar": "..." }` +- `POST /api/v1/agent-observability/eval/saudi-tone` — `{ "text_ar": "..." }` +- `POST /api/v1/agent-observability/trace/build` — حقول workflow، policy_result، tool_called، إلخ + +## خطوة تالية + +عند تفعيل Langfuse: إرسال نفس الحقول كـ span attributes؛ راجع [`OBSERVABILITY_ENV.md`](OBSERVABILITY_ENV.md). diff --git a/dealix/docs/AGENT_SECURITY_CURATOR.md b/dealix/docs/AGENT_SECURITY_CURATOR.md new file mode 100644 index 00000000..b4de31f5 --- /dev/null +++ b/dealix/docs/AGENT_SECURITY_CURATOR.md @@ -0,0 +1,28 @@ +# Agent Security Curator + +طبقة تقلل تسرّب الأسرار وتمنع تطبيقات patch خطرة **قبل** دمجها في الريبو أو تشغيلها على بيئة تحتوي بيانات حقيقية. + +## كود + +| ملف | وظيفة | +|------|--------| +| `auto_client_acquisition/security_curator/secret_redactor.py` | `redact_secrets`، `scan_payload`، `sanitize_for_trace` | +| `auto_client_acquisition/security_curator/patch_firewall.py` | `inspect_diff` — قرار `allowed` حسب أنماط `.env` ومفاتيح | +| `auto_client_acquisition/security_curator/tool_output_sanitizer.py` | تجهيز مخرجات أدوات للتتبع | +| `auto_client_acquisition/security_curator/trace_redactor.py` | `redact_trace_payload` / `redact_span_metadata` — بنية JSON كاملة قبل Langfuse وغيره | + +## API + +- `GET /api/v1/security-curator/demo` +- `POST /api/v1/security-curator/redact` — جسم `{ "text": "..." }` أو حقول أخرى تُمسح ضمنياً عبر `scan_payload` +- `POST /api/v1/security-curator/inspect-diff` — `{ "diff_text": "..." }` +- `POST /api/v1/security-curator/trace/sanitize` — `{ "payload": { ... } }` لتنقية metadata التتبع + +## ممارسات تشغيل + +1. أي output وكيل يُرسل إلى أداة خارجية أو يُخزَّن: مرّره عبر `sanitize_for_trace` أو `redact_secrets`. +2. لا تعتمد على الـ API وحدها للـ CI: أضف فحصاً مشابهاً في pre-commit أو GitHub Action عندما تكون الجاهزية متوفرة. + +## اختبارات + +`tests/test_growth_tower_stack.py` — `test_patch_firewall_blocks_env`، `test_redact_github_token`، `test_security_curator_redact_route`. diff --git a/dealix/docs/AGENT_WORKFLOW_ARCHITECTURE.md b/dealix/docs/AGENT_WORKFLOW_ARCHITECTURE.md new file mode 100644 index 00000000..54d06ccd --- /dev/null +++ b/dealix/docs/AGENT_WORKFLOW_ARCHITECTURE.md @@ -0,0 +1,54 @@ +# Agent workflow architecture — Dealix (conceptual) + +> **Scope:** تصميم مفاهيمي لمسارات وكلاء متينة **بدون** إضافة `langgraph` أو تبعيات تنفيذ معقدة إلى `requirements.txt` حتى موافقة صريحة على التكلفة والصيانة. + +## الأهداف التشغيلية + +1. **Durable execution:** إعادة تشغيل آمنة بعد انقطاع؛ حالة الخطوة محفوظة خارج الذاكرة فقط. +2. **Human-in-the-loop:** نقاط توقف عند الموافقة على إرسال خارجي، دفع، أو رسائل واتساب. +3. **Tracing:** ربط كل إجراء بـ `tenant_id`، `correlation_id`، ومسار القرار في `action_policy` / سجلات المنصة. + +## طبقات حالية في الريبو + +- **Innovation:** مسارات عرض و Kill features deterministic تحت `/api/v1/innovation/*`. +- **Platform Services:** سياسة + inbox + بوابة أدوات بدون live تحت `/api/v1/platform/*`. +- **Intelligence layer:** مخرجات JSON خفيفة تحت `/api/v1/intelligence/*`. + +## مسار مقترح (مستقبلي) + +```mermaid +flowchart LR + subgraph ingest [Ingest] + W[Webhook] + end + subgraph platform [Platform] + EB[event_bus] + UI[unified_inbox] + AP[action_policy] + TG[tool_gateway] + end + subgraph human [Human] + H[Approval UI] + end + W --> EB --> UI --> AP + AP -->|approved draft| TG + AP -->|needs approval| H + H --> TG +``` + +## ماذا يضيف LangGraph لاحقاً (إن وُفقت) + +- بيان حالة آلة صريح (nodes/edges) بدل سلاسل if طويلة. +- استئناف من عقدة بعد موافقة بشرية. +- دمج أدوات خارجية خلف نفس `tool_gateway` مع سياسات موحّدة. + +## مخاطر التبني المبكر + +- ازدواج مع منطق الـ API الحالي. +- تعقيد التشغيل والمراقبة قبل إثبات الـ MVP مع العملاء. + +## المراجع الداخلية + +- [`PLATFORM_SERVICES_STRATEGY.md`](PLATFORM_SERVICES_STRATEGY.md) +- [`INTELLIGENCE_LAYER_STRATEGY.md`](INTELLIGENCE_LAYER_STRATEGY.md) +- [`PRIVATE_BETA_RUNBOOK.md`](PRIVATE_BETA_RUNBOOK.md) diff --git a/dealix/docs/APPROVED_MARKET_MESSAGING.md b/dealix/docs/APPROVED_MARKET_MESSAGING.md new file mode 100644 index 00000000..5f696a87 --- /dev/null +++ b/dealix/docs/APPROVED_MARKET_MESSAGING.md @@ -0,0 +1,30 @@ +# رسائل تسويقية معتمدة (Approved Market Messaging) + +## عنوان رئيسي (شركات) + +**Dealix — تشغيل نمو عربي بموافقة وProof Pack** + +## عنوان رئيسي (وكالات) + +**Dealix — Growth OS للوكالة: تشخيص، فرص، مسودات، موافقات، وتقرير أثر لعملائك** + +## نقاط قصيرة (bullets) + +- فرص B2B مع «لماذا الآن» وقناة مقترحة. +- رسائل عربية قصيرة جاهزة للمراجعة (مسودات، لا إرسال تلقائي افتراضي). +- تقييم مخاطر القنوات والمصادر (contactability). +- خطة متابعة وقالب Proof Pack لإثبات القيمة أسبوعياً. + +## عروض البداية (Private Beta) + +- **Growth Starter:** تشخيص + 10 فرص + مسودات + Proof مختصر (نطاق سعر في الكتالوج). +- **Data to Revenue:** قائمة → تصنيف → أهداف آمنة + مسودات. +- **Executive Growth OS:** موجز يومي + كروت قرار + Proof أسبوعي (اشتراك بعد Pilot). + +## CTA مقترحة + +- «احجز ديمو 15 دقيقة» +- «ابدأ تشخيصاً مجانياً محدوداً» +- «اطلب Pilot بموافقة صريحة على النطاق» + +**آخر تحديث:** 2026-05-01 diff --git a/dealix/docs/AUTONOMOUS_REVENUE_COMPANY_OS.md b/dealix/docs/AUTONOMOUS_REVENUE_COMPANY_OS.md new file mode 100644 index 00000000..886a2360 --- /dev/null +++ b/dealix/docs/AUTONOMOUS_REVENUE_COMPANY_OS.md @@ -0,0 +1,34 @@ +# Dealix — Autonomous Revenue Company OS + +> **الفئة:** ليس CRM ولا بوت واتساب ولا لوحة عادية — نظام تشغيل نمو وإيرادات **عربي سعودي** يربط الإشارة بالسياق ثم الخدمة ثم سير العمل ثم المخاطر ثم المسودة ثم الموافقة ثم التصدير/التنفيذ ثم الـ Proof ثم التعلم والترقية. +> **التنفيذ في الريبو:** طبقات API وحزم `auto_client_acquisition` **deterministic** في MVP؛ إرسال حي وشحن واتساب بارد **غير مفعّل** افتراضياً — انظر [`SAFE_TOOL_GATEWAY_POLICY.md`](SAFE_TOOL_GATEWAY_POLICY.md) و[`PRIVATE_BETA_RUNBOOK.md`](PRIVATE_BETA_RUNBOOK.md). + +## الاثنا عشر طبقة ومواءمتها مع الكود + +| # | الطبقة | الدور | أين في الريبو (مرجع) | +|---|--------|--------|----------------------| +| 1 | Autonomous Service Operator | نية → خدمة → intake → مسودة → موافقة → proof | [`autonomous_service_operator/`](../auto_client_acquisition/autonomous_service_operator/)، `GET/POST /api/v1/operator/*` | +| 2 | Service Tower | خدمات منتَجة للبيع | [`service_tower/`](../auto_client_acquisition/service_tower/)، `/api/v1/services/*` | +| 3 | Service Excellence OS | جودة ودرجة وbacklog | [`service_excellence/`](../auto_client_acquisition/service_excellence/)، `/api/v1/service-excellence/*` | +| 4 | Targeting & Acquisition OS | فرص آمنة، بدون scraping | [`targeting_os/`](../auto_client_acquisition/targeting_os/)، `/api/v1/targeting/*` | +| 5 | Growth Control Tower | كروت قرار، command feed | [`innovation/command_feed`](../auto_client_acquisition/innovation/command_feed.py)، `/api/v1/innovation/command-feed/demo`، `/api/v1/platform/inbox/feed` | +| 6 | Safe Tool Gateway | سياسة أداة قبل أي تنفيذ | [`copilot/safe_actions`](../auto_client_acquisition/copilot/safe_actions.py)، [`security_curator/`](../auto_client_acquisition/security_curator/)، [`tool_action_planner`](../auto_client_acquisition/autonomous_service_operator/tool_action_planner.py) | +| 7 | Agent Runtime | وكلاء بحدود وأدوات | [`agents/`](../auto_client_acquisition/agents/)، [`v3`](../api/routers/v3.py)، orchestrator | +| 8 | Durable Workflow Engine | مسارات طويلة + HITL | حالياً: حالة جلسة في الذاكرة + موافقات؛ **LangGraph** فقط بعد موافقة صريحة — [`AGENT_WORKFLOW_ARCHITECTURE.md`](AGENT_WORKFLOW_ARCHITECTURE.md) | +| 9 | Revenue Graph | كيانات وعلاقات | [`revenue_graph/`](../auto_client_acquisition/revenue_graph/)، [`revenue_memory/`](../auto_client_acquisition/revenue_memory/) | +| 10 | Proof Ledger | أحداث إثبات | [`innovation` proof ledger](../api/routers/innovation.py)، [`fetch_proof_ledger_weekly.py`](../scripts/fetch_proof_ledger_weekly.py) | +| 11 | Self-Improving Layer | تقارير أسبوعية وbacklog | [`growth_curator/`](../auto_client_acquisition/growth_curator/)، [`revenue_company_os/self_improvement_loop`](../auto_client_acquisition/revenue_company_os/self_improvement_loop.py) | +| 12 | Revenue Launch System | عروض، pipeline، دفع يدوي | [`revenue_launch/`](../auto_client_acquisition/revenue_launch/)، `/api/v1/revenue-launch/*` | + +## Draft مقابل Live (ملخّص) + +- **Draft / suggest / approval_required:** المسار الافتراضي في MVP (مسودات Gmail، روابط دفع شكلية، كروت موافقة). +- **Live send / charge / calendar insert:** يتطلب إعدادات صريحة + موافقة بشرية؛ الكثير منها **محظور** في العروض التجريبية — راجع سياسة البوابة والاختبارات. + +## وثائق مرتبطة + +- [`AUTONOMOUS_SERVICE_OPERATOR.md`](AUTONOMOUS_SERVICE_OPERATOR.md) — المشغّل والنية والجلسة. +- [`SERVICE_BUNDLES.md`](SERVICE_BUNDLES.md) — الباقات التجارية. +- [`REVENUE_WORK_UNITS.md`](REVENUE_WORK_UNITS.md) — وحدات عمل الإيراد (RWU). +- [`CEO_COMMAND_CENTER.md`](CEO_COMMAND_CENTER.md)، [`AGENCY_PARTNER_MODE.md`](AGENCY_PARTNER_MODE.md)، [`SAFE_TOOL_GATEWAY_POLICY.md`](SAFE_TOOL_GATEWAY_POLICY.md)، [`SELF_IMPROVING_REVENUE_LOOP.md`](SELF_IMPROVING_REVENUE_LOOP.md). +- [`DEALIX_100_PERCENT_LAUNCH_PLAN.md`](DEALIX_100_PERCENT_LAUNCH_PLAN.md) — جاهزية الإطلاق الشاملة. diff --git a/dealix/docs/AUTONOMOUS_SERVICE_OPERATOR.md b/dealix/docs/AUTONOMOUS_SERVICE_OPERATOR.md new file mode 100644 index 00000000..6fd90cd2 --- /dev/null +++ b/dealix/docs/AUTONOMOUS_SERVICE_OPERATOR.md @@ -0,0 +1,31 @@ +# Autonomous Service Operator + +> واجهة منتج موحّدة: **نية المستخدم → تصنيف → خدمة موصى بها → intake → مسودة → موافقة → Proof** — بدون LLM إلزامي في الموجة الأولى. + +## الكود + +- الحزمة: [`auto_client_acquisition/autonomous_service_operator/`](../auto_client_acquisition/autonomous_service_operator/) +- الـ API: `GET|POST /api/v1/operator/*` — انظر [`API_CANONICAL_ALIASES.md`](architecture/API_CANONICAL_ALIASES.md). + +## المكوّنات + +| ملف | وظيفة | +|-----|--------| +| `intent_classifier.py` | تصنيف قواعد عربي/إنجليزي | +| `service_orchestrator.py` | ربط النية بـ `service_id` من Service Tower | +| `conversation_router.py` | `handle_message` — نقطة دخول المحادثة | +| `session_state.py` | جلسات in-memory (MVP) | +| `approval_manager.py` | موافقة / تعديل / تخطي | +| `workflow_runner.py` | حالات intake → draft → pending_approval → proof | +| `intake_collector.py` | حقول مطلوبة من كتالوج الخدمة | +| `tool_action_planner.py` | مصفوفة Safe Tool Gateway | +| `proof_pack_dispatcher.py` | هيكل Proof Pack | +| `upsell_engine.py` | ترقية من `upgrade_path` في الكتالوج | +| `whatsapp_renderer.py` | نصوص مسودة واتساب (لا إرسال) | +| `operator_memory.py` | سجل أدوار المحادثة | +| `service_bundles.py` | باقات Growth Starter وغيرها | +| `executive_mode.py` / `client_mode.py` / `agency_mode.py` | أولويات العرض حسب الدور | + +## الامتثال + +لا تنفيذ live للأدوات المحظورة — [`SAFE_TOOL_GATEWAY_POLICY.md`](SAFE_TOOL_GATEWAY_POLICY.md). diff --git a/dealix/docs/CEO_COMMAND_CENTER.md b/dealix/docs/CEO_COMMAND_CENTER.md new file mode 100644 index 00000000..ed95b8fc --- /dev/null +++ b/dealix/docs/CEO_COMMAND_CENTER.md @@ -0,0 +1,13 @@ +# CEO Command Center + +رؤية المدير التنفيذي: **٣ قرارات، فرص، مخاطر، موافقات** — عربي، أزرار ≤٣ في بطاقات الواجهة. + +## API ذات صلة + +- `GET /api/v1/services/ceo/daily-brief` — Service Tower. +- `GET /api/v1/revenue-os/company-os/command-feed/demo` — دمج Command Feed مع بطاقات Company OS. +- `GET /api/v1/operator/bundles` و`POST /api/v1/operator/chat/message` — مشغّل الخدمات. + +## الوضع التنفيذي + +MVP: عرض deterministic؛ التنفيذ الخارجي يبقى draft/approval — [`SAFE_TOOL_GATEWAY_POLICY.md`](SAFE_TOOL_GATEWAY_POLICY.md). diff --git a/dealix/docs/COMMERCIAL_LAUNCH_MASTER_PLAN.md b/dealix/docs/COMMERCIAL_LAUNCH_MASTER_PLAN.md index 07dc5071..ba303e46 100644 --- a/dealix/docs/COMMERCIAL_LAUNCH_MASTER_PLAN.md +++ b/dealix/docs/COMMERCIAL_LAUNCH_MASTER_PLAN.md @@ -1,22 +1,17 @@ -# الخطة الرئيسية للتدشين التجاري — Dealix +# خطة الإطلاق التجاري — Dealix (مستوى عام) -**تنفيذ يوم التدشين:** [`LAUNCH_DAY_RUNBOOK_AR.md`](LAUNCH_DAY_RUNBOOK_AR.md) — **نطاق التسمية (بيتا vs عام):** [`LAUNCH_SCOPE_AND_NAMING.md`](LAUNCH_SCOPE_AND_NAMING.md). -**تتبع Go/No-Go:** [`PUBLIC_LAUNCH_GO_NO_GO_TRACKER.md`](PUBLIC_LAUNCH_GO_NO_GO_TRACKER.md) — **تدوير أسرار بعد تسريب:** [`SECURITY_SECRET_ROTATION_CHECKLIST.md`](SECURITY_SECRET_ROTATION_CHECKLIST.md). +## ما قبل الإطلاق العام -## المراحل +1. بيتا خاصة مغلقة مع ٥–١٠ عملاء (انظر [`PRIVATE_BETA_LAUNCH_TODAY.md`](PRIVATE_BETA_LAUNCH_TODAY.md))؛ مواءمة رسالة المنتج مع فئة **Autonomous Revenue Company OS** — [`AUTONOMOUS_REVENUE_COMPANY_OS.md`](AUTONOMOUS_REVENUE_COMPANY_OS.md). +2. Staging مستقر: `/health`، smoke staging، مراقبة أخطاء. +3. سياسات: لا إرسال حي افتراضياً، لا واتساب بارد، لا LinkedIn scraping (انظر [`TARGETING_ACQUISITION_OS.md`](TARGETING_ACQUISITION_OS.md)). +4. Service Tower + Service Excellence: جاهزية خدمة ≥٨٠ أو وسم `beta_only` (انظر [`SERVICE_EXCELLENCE_OS.md`](SERVICE_EXCELLENCE_OS.md)). -1. **Post-merge verification** — [`POST_MERGE_VERIFICATION.md`](POST_MERGE_VERIFICATION.md) -2. **Staging** — [`STAGING_DEPLOYMENT.md`](STAGING_DEPLOYMENT.md) + `scripts/smoke_staging.py` -3. **Compliance baseline** — [`DATA_MAP.md`](DATA_MAP.md)، [`PRIVACY_PDPL_READINESS.md`](PRIVACY_PDPL_READINESS.md)، DPA pilot -4. **Observability + evals** — [`OBSERVABILITY_ENV.md`](OBSERVABILITY_ENV.md)، [`EVALS_RUNBOOK.md`](EVALS_RUNBOOK.md) -5. **WhatsApp beta** — [`WHATSAPP_OPERATOR_FLOW.md`](WHATSAPP_OPERATOR_FLOW.md)، [`WHATSAPP_PRODUCTION_CUTOVER.md`](WHATSAPP_PRODUCTION_CUTOVER.md) -6. **Billing** — [`BILLING_RUNBOOK.md`](BILLING_RUNBOOK.md) -7. **Private beta** — [`PRIVATE_BETA_RUNBOOK.md`](PRIVATE_BETA_RUNBOOK.md) -8. **Paid beta metrics** — [`PAID_BETA_SCORECARD.md`](PAID_BETA_SCORECARD.md) -9. **Go / No-Go عام** — [`PUBLIC_LAUNCH_GO_NO_GO.md`](PUBLIC_LAUNCH_GO_NO_GO.md) +## الإطلاق العام (عندما) -## قاعدة التسمية +- اشتراكات أو pilots موثقة، DPA، دعم، تتبع تكلفة LLM، وProof Pack حقيقي لعدة أسابيع. -حتى تتحقق مدفوعات واستقرار وتشغيل: الإطلاق هو **Paid Private Beta** أو **Launch Candidate** وليس “Public Launch” كاملاً. +## مسارات مرجعية -بعد الاستقرار التشغيلي للبيتا: راجع [`INNOVATION_STRATEGY.md`](INNOVATION_STRATEGY.md) كطبقة تمييز منتجي/تسويقي (Growth Factory + مسارات `/api/v1/innovation/*` التجريبية) مقابل ما يبقى تنفيذاً عميقاً على بيانات العملاء. +- `GET /api/v1/launch/go-no-go` — قائمة فحص آلية (demo). +- [`DEALIX_100_PERCENT_LAUNCH_PLAN.md`](DEALIX_100_PERCENT_LAUNCH_PLAN.md) — الخطة الشاملة. diff --git a/dealix/docs/CONNECTOR_CATALOG.md b/dealix/docs/CONNECTOR_CATALOG.md new file mode 100644 index 00000000..4e080e9a --- /dev/null +++ b/dealix/docs/CONNECTOR_CATALOG.md @@ -0,0 +1,27 @@ +# Connector Catalog + +مصدر الحقيقة للقائمة: `auto_client_acquisition/connectors/connector_catalog.py` — الدالة `build_connector_catalog()`. + +## HTTP + +`GET /api/v1/connectors/catalog` — يعيد `{ "connectors": [...], "count", "demo": true }`. + +## الموصلات الحالية (MVP) + +| id | ملاحظة | +|----|--------| +| `whatsapp` | مسودات ومعاينة قوالب؛ إرسال حي محظور في الكتالوج | +| `gmail` | إنشاء مسودة؛ إرسال حي محظور | +| `google_calendar` | مسودة حدث؛ إدراج حي محظور | +| `google_meet` | قراءة transcript مخطط؛ تسجيل بلا موافقة محظور | +| `linkedin_lead_forms` | استيعاب leads | +| `x_api`, `instagram_graph` | مسجّل فقط — لا firehose | +| `google_business_profile` | مسودة رد على مراجعة | +| `google_sheets` | قراءة مخطط | +| `crm` | مزامنة مسودة | +| `moyasar` | مسودة رابط دفع؛ شحن حي محظور | +| `website_forms` | webhook ingest؛ منع scraping | + +## اختبارات + +`tests/test_growth_tower_stack.py` — `test_connector_catalog_has_twelve_plus`. diff --git a/dealix/docs/CONNECTOR_SETUP_GUIDES.md b/dealix/docs/CONNECTOR_SETUP_GUIDES.md new file mode 100644 index 00000000..0dcc01f8 --- /dev/null +++ b/dealix/docs/CONNECTOR_SETUP_GUIDES.md @@ -0,0 +1,11 @@ +# أدلة ربط الموصلات (ملخص) + +- **واتساب:** opt-in واضح؛ الإرسال الحي behind flag وسياسة؛ مراجعة قوائم قبل أي حملة. +- **Gmail:** مسودات أولاً (`gmail.compose` لاحقاً عبر OAuth) — لا إرسال من Dealix افتراضياً. +- **Calendar:** مسودات/موافقة قبل `events.insert`. +- **Moyasar:** sandbox أو فاتورة يدوية من لوحة التحكم؛ لا تخزين بيانات بطاقة داخل Dealix في MVP. +- **LinkedIn Lead Gen:** إعلانات + نماذج رسمية؛ لا scraping ولا auto-DM. + +**مرجع حالة تجريبية:** `GET /api/v1/customer-ops/connectors/status` + +**آخر تحديث:** 2026-05-01 diff --git a/dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md b/dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md new file mode 100644 index 00000000..53b5ae9b --- /dev/null +++ b/dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md @@ -0,0 +1,9 @@ +# نجاح العميل — أسبوع Pilot + +- **يوم ١–٢:** تشغيل أول مهمة وتسليم عينة مخرجات (فرص/مسودات/مخاطر). +- **يوم ٣–٤:** مراجعة الموافقات المعلقة؛ تعديل الرسائل حسب feedback العميل. +- **يوم ٥–٧:** Proof Pack أسبوعي؛ قرار الترقية (Growth OS / List Intelligence / شراكات). + +**Cadence:** `GET /api/v1/customer-ops/success/cadence` + +**آخر تحديث:** 2026-05-01 diff --git a/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md b/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md index d7ebfe7a..ff7712cb 100644 --- a/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md +++ b/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md @@ -148,6 +148,68 @@ OAuth Gmail/Calendar، حصص، سياسات. وعد منتجي مركزي: من مدخلات شركة/قطاع/مدينة/عرض/هدف إلى قائمة ١٠ فرص مع Why Now ومستوى مخاطرة ومسودات عربية **بانتظار الموافقة فقط** — **`POST /api/v1/innovation/opportunities/ten-in-ten`**؛ وصف المهمة في `GET /api/v1/innovation/growth-missions`؛ الاستراتيجية في [`INNOVATION_STRATEGY.md`](INNOVATION_STRATEGY.md)؛ الإطار التشغيلي بجانب `GET /api/v1/business/gtm/first-10` عند التوسع. +## 32. Growth Control Tower — ترتيب الموجات (0→6) + +رؤية منتج: **برج تحكم بالنمو** — Inbox موحّد، سياسة قنوات، ذكاء تشغيلي خفيف، ومسودات تكاملات (بدون live) حتى اكتمال البوابات. + +| موجة | المحتوى | وثائق / كود | +|------|---------|-------------| +| **0** | تثبيت: `compileall`، `pytest`، `print_routes`، `smoke_inprocess` | [`POST_MERGE_VERIFICATION.md`](POST_MERGE_VERIFICATION.md) | +| **1** | استراتيجية Platform + Intelligence | [`PLATFORM_SERVICES_STRATEGY.md`](PLATFORM_SERVICES_STRATEGY.md)، [`INTELLIGENCE_LAYER_STRATEGY.md`](INTELLIGENCE_LAYER_STRATEGY.md) | +| **2** | `platform_services` + `/api/v1/platform/*` | `auto_client_acquisition/platform_services/` | +| **3** | `intelligence_layer` + `/api/v1/intelligence/*` | `auto_client_acquisition/intelligence_layer/` | +| **4** | Gmail / Calendar / Moyasar — **payload فقط** | `auto_client_acquisition/integrations/` | +| **5** | Ingest نماذج leads (MVP) + قنوات اجتماعية **مسجّلة فقط** في السجل | `ingest/lead-form` + `channel_registry` | +| **6** | وكلاء متينة (مفاهيم) — **بدون** LangGraph في `requirements.txt` حتى موافقة | [`AGENT_WORKFLOW_ARCHITECTURE.md`](AGENT_WORKFLOW_ARCHITECTURE.md) | + +الالتزام بالبيتا الخاصة والـ PDPL كما في الأقسام أعلاه؛ لا إرسال حي من مسارات المنصة في هذه الموجات. + +## 33. مواءمة مسارات API (canonical و aliases) + +جدول يوحّد أسماء المسارات بين المنتج والكود: [`docs/architecture/API_CANONICAL_ALIASES.md`](architecture/API_CANONICAL_ALIASES.md) — يُحدَّث عند إضافة مسار جديد أو alias. + +## 34. Growth Neural Network — طبقات Curator و Meeting و Router (MVP) + +بعد موجات **Growth Control Tower** (أقسام 32–33)، أُضيفت وحدات داعمة تحت `auto_client_acquisition/` مع راوترات FastAPI: **security_curator**، **growth_curator**، **meeting_intelligence**، **model_router**، **connectors**، **agent_observability**، و**growth_operator** (aliases منتجية فقط). التفاصيل التشغيلية والامتثال (مسودات، عدم إرسال حي، عدم شحن تلقائي) كما في [`PLATFORM_SERVICES_STRATEGY.md`](PLATFORM_SERVICES_STRATEGY.md) و[`PRIVATE_BETA_RUNBOOK.md`](PRIVATE_BETA_RUNBOOK.md). + +**وثائق موجهة للتنفيذ:** [`EXECUTION_ROADMAP_AR.md`](EXECUTION_ROADMAP_AR.md)، [`AGENT_SECURITY_CURATOR.md`](AGENT_SECURITY_CURATOR.md)، [`GROWTH_CURATOR_STRATEGY.md`](GROWTH_CURATOR_STRATEGY.md)، [`MEETING_INTELLIGENCE.md`](MEETING_INTELLIGENCE.md)، [`MODEL_PROVIDER_ROUTER.md`](MODEL_PROVIDER_ROUTER.md)، [`AGENT_OBSERVABILITY_EVALS.md`](AGENT_OBSERVABILITY_EVALS.md)، [`CONNECTOR_CATALOG.md`](CONNECTOR_CATALOG.md). + +## 35. Targeting & Acquisition OS + +طبقة [`targeting_os`](../auto_client_acquisition/targeting_os/) + مسارات `/api/v1/targeting/*` (انظر [`API_CANONICAL_ALIASES.md`](architecture/API_CANONICAL_ALIASES.md)): استهداف حسابات، لجنة شراء، تقييم مصدر وجهة، خطة outreach، استراتيجية LinkedIn المتوافقة، تشخيص مجاني، وقوالب عقود **للمسودات فقط**. الوثيقة المرجعية: [`TARGETING_ACQUISITION_OS.md`](TARGETING_ACQUISITION_OS.md). + +## 36. Service Tower + Service Excellence OS + +- **Service Tower** ([`service_tower/`](../auto_client_acquisition/service_tower/)، مسارات `/api/v1/services/*`): كتالوج خدمات بيعية (تشخيص مجاني، ذكاء قوائم، ١٠ فرص، Growth OS، شراكات، …) مع wizard توصية، عروض سعر تقديرية، وبطاقات CEO — مدمج مع كتالوج المنصة في `GET .../catalog` دون تكرار منطق التنفيذ الخارجي. +- **Service Excellence OS** ([`service_excellence/`](../auto_client_acquisition/service_excellence/)، مسارات `/api/v1/service-excellence/*`): مصفوفة ميزات، درجة جاهزية، فجوات تنافسية، حزمة إطلاق، backlog تحسين — deterministic للعرض الداخلي. +- **وثائق:** [`SERVICE_TOWER_STRATEGY.md`](SERVICE_TOWER_STRATEGY.md)، [`SERVICE_EXCELLENCE_OS.md`](SERVICE_EXCELLENCE_OS.md). +- **Landing (عرض):** [`landing/services.html`](../landing/services.html)، [`landing/free-diagnostic.html`](../landing/free-diagnostic.html)، [`landing/first-10-opportunities.html`](../landing/first-10-opportunities.html)، [`landing/agency-partner.html`](../landing/agency-partner.html)، [`landing/list-intelligence.html`](../landing/list-intelligence.html)، [`landing/growth-os.html`](../landing/growth-os.html). + +## 37. Launch Ops + جاهزية البيتا + +- **Launch Ops** ([`launch_ops/`](../auto_client_acquisition/launch_ops/)، مسارات `/api/v1/launch/*`): عرض البيتا الخاصة، سكربت ديمو ١٢ دقيقة، قوالب أول ٢٠ تواصل، go/no-go، scorecard جاهزية. +- **Service Tower — إضافات:** `GET /api/v1/services/verticals` (ثلاثة أبواب)، `GET /api/v1/services/upgrade-paths`، `GET /api/v1/services/contracts/templates` (مسودات عقود — ليست استشارة قانونية). +- **وثائق:** [`PRIVATE_BETA_LAUNCH_TODAY.md`](PRIVATE_BETA_LAUNCH_TODAY.md)، [`DEMO_SCRIPT_12_MINUTES.md`](DEMO_SCRIPT_12_MINUTES.md)، [`FIRST_20_OUTREACH_MESSAGES.md`](FIRST_20_OUTREACH_MESSAGES.md)، [`COMMERCIAL_LAUNCH_MASTER_PLAN.md`](COMMERCIAL_LAUNCH_MASTER_PLAN.md)، [`FRONTEND_AND_API_MAP.md`](FRONTEND_AND_API_MAP.md) (واجهة HTML ↔ برج API + لغتين للبيتا)، [`AUTONOMOUS_REVENUE_COMPANY_OS.md`](AUTONOMOUS_REVENUE_COMPANY_OS.md). + +## 38. Revenue Today (تحصيل Pilot بدون أتمتة خطرة) + +- **الحزمة** [`revenue_launch/`](../auto_client_acquisition/revenue_launch/): عروض ٤٩٩ و Growth OS Pilot، شرائح أول ٢٠ تواصل، ديمو/إغلاق، مخطط pipeline، checklist تسليم Pilot، قالب Proof Pack، **تعليمات دفع يدوية** (Moyasar dashboard — لا charge من كود Dealix في هذه المرحلة). +- **المسارات** `GET /api/v1/revenue-launch/*` — انظر [`REVENUE_TODAY_PLAYBOOK.md`](REVENUE_TODAY_PLAYBOOK.md) و[`API_CANONICAL_ALIASES.md`](architecture/API_CANONICAL_ALIASES.md). +- **أداة checklist:** `python scripts/launch_readiness_check.py` (اختياري `--base-url` مع httpx لفحص `/health` وعينات API). + +## 39. Autonomous Revenue Company OS (مشغّل + Company OS) + +- **الرؤية والطبقات الاثنا عشر:** [`AUTONOMOUS_REVENUE_COMPANY_OS.md`](AUTONOMOUS_REVENUE_COMPANY_OS.md) — يربط الفئة «نظام تشغيل نمو» بالمجلدات والمسارات الفعلية. +- **المشغّل (نية → خدمة → موافقة):** حزمة [`autonomous_service_operator/`](../auto_client_acquisition/autonomous_service_operator/) ومسارات `GET|POST /api/v1/operator/*` (عرض deterministic، بدون LLM إلزامي في الموجة الأولى). +- **Company OS (حدث → بطاقة → RWU):** [`revenue_company_os/`](../auto_client_acquisition/revenue_company_os/) ومسارات `GET /api/v1/revenue-os/company-os/*` بجانب `revenue_os` الحالي — لا يستبدل `POST /events` بل يكمّل تحت `company-os`. + +## 40. Positioning Lock + Customer Ops + صفحات الشركات/المسوقين + +- **تثبيت الرسالة:** [`POSITIONING_LOCK.md`](POSITIONING_LOCK.md)، [`PROHIBITED_CLAIMS.md`](PROHIBITED_CLAIMS.md)، [`APPROVED_MARKET_MESSAGING.md`](APPROVED_MARKET_MESSAGING.md). +- **Customer Ops (API):** حزمة [`customer_ops/`](../auto_client_acquisition/customer_ops/) ومسارات `GET|POST /api/v1/customer-ops/*` — قائمة onboarding، SLA، حالة موصلات تجريبية، cadence نجاح عميل، playbook حوادث، وتوجيه تذكرة دعم (بدون تنفيذ خارجي). +- **صفحات عرض:** [`landing/companies.html`](../landing/companies.html) (مسار الشركات)، [`landing/marketers.html`](../landing/marketers.html) (مسار الوكالات/المسوقين) — تربط بباقي صفحات الـ landing الحالية. +- **تشغيل المشغّل:** أوضاع إضافية في [`self_growth_mode.py`](../auto_client_acquisition/autonomous_service_operator/self_growth_mode.py) و[`service_delivery_mode.py`](../auto_client_acquisition/autonomous_service_operator/service_delivery_mode.py) بجانب الأوضاع الموجودة. + --- **الخلاصة:** المنتج **قوي كأساس سوقي وتقني**؛ الإطلاق العام يحتاج تشغيلاً وامتثالاً وتجربة عميل مغلقة أولاً. diff --git a/dealix/docs/DEMO_SCRIPT_12_MINUTES.md b/dealix/docs/DEMO_SCRIPT_12_MINUTES.md new file mode 100644 index 00000000..e2ef8b68 --- /dev/null +++ b/dealix/docs/DEMO_SCRIPT_12_MINUTES.md @@ -0,0 +1,14 @@ +# ديمو Dealix — ١٢ دقيقة + +مرجع تنفيذي يطابق `GET /api/v1/launch/demo-script` في الكود. + +| الدقائق | المحتوى | API اختياري | +|--------|---------|-------------| +| 0–2 | المشكلة والوعد: ليس CRM وليس بوت واتساب فقط — إشارة → قرار → موافقة → Proof | — | +| 2–4 | Daily Brief للمدير | `GET /api/v1/personal-operator/daily-brief` | +| 4–6 | مهمات النمو / ١٠ فرص | `GET /api/v1/growth-operator/missions` | +| 6–8 | Inbox موحّد (كروت عربية) | `GET /api/v1/platform/inbox/feed` | +| 8–10 | برج الخدمات والأسعار التقديرية | `GET /api/v1/services/catalog` | +| 10–12 | Pilot، Proof Pack، الخطوة التالية | `GET /api/v1/launch/private-beta/offer` | + +**جملة إغلاق:** لا نعد نتائج مضمونة — نعد مسودات، موافقات، وتقارير قياس. diff --git a/dealix/docs/EXECUTION_ROADMAP_AR.md b/dealix/docs/EXECUTION_ROADMAP_AR.md new file mode 100644 index 00000000..4a4227cd --- /dev/null +++ b/dealix/docs/EXECUTION_ROADMAP_AR.md @@ -0,0 +1,67 @@ +# خارطة تنفيذ — برج النمو (Growth Control Tower) — عربي + +> مرجع تنفيذي يربط الكود الحالي بالخطوات التالية. **MVP الحالي: مسودات، سياسات، عروض deterministic — بدون إرسال حي أو شحن تلقائي.** + +## المرحلة 0 — تثبيت (يوم 0–2) + +1. `python -m compileall` على جذر `dealix`. +2. `pytest` (الريبو يفعّل `--cov` افتراضياً؛ للسرعة: `pytest --no-cov tests/test_growth_tower_stack.py`). +3. `python scripts/print_routes.py` للتحقق من تضمين الراوترات الجديدة. +4. نشر staging ثم `python scripts/smoke_staging.py` (المسارات موسّعة في السكربت). + +## المرحلة 1 — دمج الواجهة والعرض (أسبوع 1–3) + +| مخرج | إجراء | +|------|--------| +| Inbox و Command cards | ربط الواجهة بـ `GET /api/v1/platform/inbox/feed` و`GET /api/v1/intelligence/command-feed/demo` حسب التصميم. | +| Proof موحّد | `GET /api/v1/platform/proof/overview` + روابط demo من `innovation` و`business`. | +| Growth operator في الوثائق | استخدام `GET /api/v1/growth-operator/*` للعرض مع احترام `canonical_route` في الاستجابة. | +| Targeting OS | [`TARGETING_ACQUISITION_OS.md`](TARGETING_ACQUISITION_OS.md) و`POST /api/v1/targeting/*` لاستهداف الحسابات والقوائم بشكل متوافق. | + +## المرحلة 2 — Curator + Security في مسار المراجعة (أسبوع 2–4) + +1. **قبل** أي لصق diff من الوكيل: `POST /api/v1/security-curator/inspect-diff`. +2. **قبل** تخزين/إرسال نصوص طويلة: `POST /api/v1/security-curator/redact`. +3. رسائل الصادر: `POST /api/v1/growth-curator/messages/grade` كبوابة جودة (لا تستبدل المراجعة البشرية للبيتا). +4. تنقية traces قبل المراقبة: `POST /api/v1/security-curator/trace/sanitize`. + +## المرحلة 3 — اجتماعات ومتابعة (أسبوع 3–6) + +- تدفق: لصق transcript → `POST /api/v1/meeting-intelligence/transcript/summarize` → اعتراضات → `POST .../followup/draft`. +- Pre-call: `POST .../brief/pre-meeting` مع JSON منظّم (شركة، جهة، فرصة). +- لا ربط تقويم حي من هذه المسارات في MVP. + +## المرحلة 4 — موجه النماذج والمزودين (متوازي) + +- استخدام `POST /api/v1/model-router/route` كطبقة **تلميحات** فقط؛ التوجيه الحقيقي يبقى في `core/llm/router.py` عند التشغيل. +- توثيق المزودين في الكود: `GET /api/v1/model-router/providers`. + +## المرحلة 5 — Connectors وبوابة الأدوات + +1. عرض للعملاء: `GET /api/v1/connectors/catalog` (مخاطر و`blocked_actions` واضحة). +2. تنفيذ أدوات من الوكيل فقط عبر `POST /api/v1/platform/tools/execute` — راجع `tool_gateway` لحالات `blocked` و`approval_required`. + +## المرحلة 6 — مراقبة وتقييم (staging → prod) + +- تشغيل evals على عينات: `POST /api/v1/agent-observability/eval/safety` و`.../eval/saudi-tone`. +- بناء أحداث trace متوافقة مع Langfuse: `POST .../trace/build` ثم ربط SDK في التطبيق عند الجاهزية. +- راجع [`AI_OBSERVABILITY_AND_EVALS.md`](AI_OBSERVABILITY_AND_EVALS.md). + +## المرحلة 7 — إطلاق بيتا خاص (قرار go/no-go) + +- [`PRIVATE_BETA_LAUNCH_TODAY.md`](PRIVATE_BETA_LAUNCH_TODAY.md) و[`BETA_PRIVATE_GATES_CHECKLIST.md`](BETA_PRIVATE_GATES_CHECKLIST.md). +- صفحة عرض: [`landing/private-beta.html`](../landing/private-beta.html). + +## المرحلة 8 — ما بعد البيتا (90 يوم) + +OAuth كامل لـ Gmail/Calendar، Moyasar live خلف موافقة وتدقيق، واتساب إنتاجي مع `WHATSAPP_ALLOW_LIVE_SEND`، أحداث platform في DB، وتقارير PDF من الـ ledger — كما في [`DEALIX_100_PERCENT_LAUNCH_PLAN.md`](DEALIX_100_PERCENT_LAUNCH_PLAN.md) القسم 3 و21. + +## تتبع المهام المقترح + +| أسبوع | تركيز | +|-------|--------| +| 1 | smoke + ربط UI للـ inbox وproof | +| 2–3 | security + growth curator في workflow المراجعة | +| 4–6 | meeting intelligence + نماذج أولى من pilot | +| 7–10 | connectors أولوية pilot (whatsapp, gmail, moyasar) مع OAuth | +| 11+ | Langfuse، SLOs، PDPL تشغيلية | diff --git a/dealix/docs/FIRST_20_OUTREACH_MESSAGES.md b/dealix/docs/FIRST_20_OUTREACH_MESSAGES.md new file mode 100644 index 00000000..89feda1f --- /dev/null +++ b/dealix/docs/FIRST_20_OUTREACH_MESSAGES.md @@ -0,0 +1,14 @@ +# أول ٢٠ رسالة تواصل (قوالب) + +المصدر البرمجي: `GET /api/v1/launch/outreach/first-20` (`launch_ops/outreach_messages.py`). + +**قاعدة:** نسخ يدوي فقط — لا إرسال تلقائي من Dealix. + +1. **مؤسس B2B:** تجربة بيتا — ١٠ فرص خلال أسبوع، موافقة قبل التواصل. +2. **وكالة:** Pilot مشترك على عميل واحد + Proof Pack. +3. **تدريب:** فرص شركات + لماذا الآن + تشخيص مجاني مختصر. +4. **SaaS صغير:** قائمة leads → تصنيف ومخاطر — بدون واتساب بارد. +5. **متابعة ١:** تذكير لطيف — قطاع + مدينة لعينة ٢٤ ساعة. +6. **متابعة ٢:** إغلاق مهذب أو جدولة لاحقة. + +كرّر وأكيّف ٤–٦ لباقي الـ ٢٠ جهة بنفس النبرة الدافئة. diff --git a/dealix/docs/FIRST_PILOT_DELIVERY_WORKFLOW.md b/dealix/docs/FIRST_PILOT_DELIVERY_WORKFLOW.md new file mode 100644 index 00000000..55e333da --- /dev/null +++ b/dealix/docs/FIRST_PILOT_DELIVERY_WORKFLOW.md @@ -0,0 +1,93 @@ +# أول Pilot — مسار التسليم خلال 48 ساعة + +**الغرض:** عند موافقة أول عميل على **Pilot 7 أيام — 499 ريال** (أو ما يعادله من الكتالوج)، تنفيذ متسلسل واضح — **مسودات أولاً**، **موافقة قبل أي تواصل حساس**، **Proof Pack** في النهاية. + +**مرتبط بـ:** [`PAID_BETA_OPERATING_PLAYBOOK.md`](PAID_BETA_OPERATING_PLAYBOOK.md)، [`CUSTOMER_SUCCESS_PLAYBOOK.md`](CUSTOMER_SUCCESS_PLAYBOOK.md)، واجهات API مثل `GET /api/v1/customer-ops/onboarding/checklist`. + +--- + +## المرحلة A — Intake (يوم 0، نفس يوم الدفع أو قبله بساعات) + +اجمع الحد الأدنى التالي (يمكن نسخه إلى نموذج أو Sheet): + +| الحقل | ملاحظة | +|--------|--------| +| `company_name` | | +| `website` | | +| `sector` | | +| `city` | | +| `offer` | العرض الرئيسي بجملة واحدة | +| `ideal_customer` | من هو المشتري المثالي | +| `average_deal_value` | تقدير نطاق | +| `has_contact_list` | نعم/لا — إن نعم، شكل الملف والمصدر | +| `preferred_channels` | إيميل / اجتماع / واتساب opt-in / غيره | +| `current_sales_process` | جملتان تكفيان | + +**بعد تأكيد الدفع (يدوي):** أرسل للعميل قائمة المطلوب أعلاه + توقيت التسليم (24 ساعة للتشخيص، 48 ساعة لمحتوى الـ 499 إن كان ذلك هو النطاق المتفق عليه). + +--- + +## المرحلة B — التشخيص (خلال 24 ساعة من اكتمال الـ intake) + +**المخرجات:** + +- **3 فرص** مؤهّلة مع «لماذا الآن» مختصر. +- **1 رسالة عربية** مسودة للمراجعة (لا إرسال تلقائي). +- **1 مخاطرة قناة** (مثلاً: contactability / سياسة القناة). +- **1 توصية خدمة** تالية (ترقية محتملة بعد الـ Pilot). + +يمكن الاستعانة بمسارات المنتج التجريبية (`free_growth_diagnostic` في الكتالوج) كمرجع تنظيمي — التسليم للعميل يكون **ملفاً أو بريداً منظماً** حسب اتفاقك. + +--- + +## المرحلة C — Pilot 499 (خلال 48 ساعة من بداية الـ Pilot المتفق عليها) + +**المخرجات (نطاق Growth Starter):** + +- **10 فرص** مع سبب اختيار مختصر لكل منها. +- **why-now** لكل فرصة (سطر أو سطران). +- **صاحب القرار المحتمل** (لقب/دور — بدون تخمين غير مسؤول). +- **القناة المقترحة** (إيميل، طلب اجتماع، إلخ) — دائماً مع ذكر أن الإرسال يتطلب موافقته. +- **رسالة عربية** مسودة لكل فرصة أو مجموعات منطقية. +- **risk / contactability** لكل هدف حيث ينطبق. +- **خطة متابعة 7 أيام** (جدول أيام + إجراء مقترح يدوي). +- **Proof Pack مختصر** (انظر أدناه). + +--- + +## مرحلة D — Proof Pack (مع كل تسليم رئيسي) + +يُفضّل أن يتضمن القالب: + +- `opportunities_created` (عدد / معرفات مختصرة) +- `drafts_created` +- `approvals_needed` (ما يحتاج قرار العميل) +- `risks_blocked` (ما تم رفضه أو تعليمه كـ unsafe) +- `recommended_next_action` +- `upgrade_offer` (خدمة تالية مقترحة بلا ضغط مبالغ فيه) + +**لا تُدخل PII خام** في أدوات مراقبة عامة؛ التزم بـ redaction الداخلي. + +--- + +## نص اقتراح للعميل بعد الموافقة على Pilot 499 + +> تمام، نبدأ Pilot لمدة 7 أيام بـ 499 ريال. +> يشمل: 10 فرص مناسبة، رسائل عربية جاهزة للمراجعة، فحص مخاطر القنوات، خطة متابعة، Proof Pack مختصر. +> بعد الدفع أحتاج: رابط موقعكم، القطاع المستهدف، المدينة، العرض الرئيسي، وهل عندكم قائمة عملاء أو نبدأ بدون قائمة. + +عدّل الصياغة لتطابق هويتك وعقدك؛ لا تعد نتائج مالية مضمونة. + +--- + +## API تشغيلية (ديمو / داخلي) + +للتحقق من حالة التشغيل اليومية للفريق: + +- `GET /api/v1/customer-ops/onboarding/checklist` +- `GET /api/v1/customer-ops/connectors/status` +- `GET /api/v1/customer-ops/support/sla` + +--- + +**آخر تحديث:** 2026-05-01 diff --git a/dealix/docs/FRONTEND_AND_API_MAP.md b/dealix/docs/FRONTEND_AND_API_MAP.md new file mode 100644 index 00000000..9ac0717f --- /dev/null +++ b/dealix/docs/FRONTEND_AND_API_MAP.md @@ -0,0 +1,57 @@ +# خريطة الواجهة (Landing) وواجهة البرج والتحكم — Dealix + +> الغرض: يعرف المشغّل أو الشريك التقني **أي صفحة HTML تغذيها أي مسارات API**، دون البحث في الريبو. المسارات المرجعية الكاملة: [`architecture/API_CANONICAL_ALIASES.md`](architecture/API_CANONICAL_ALIASES.md). + +## مبدأ البنية + +| الطبقة | الموقع | الدور | +|--------|--------|--------| +| واجهة تسويق / عرض | [`landing/*.html`](../landing/) | HTML ثابت، غالباً `lang="ar"` و`dir="rtl"`. نسخ إنجليزية مختصرة: `*-en.html`. | +| برج النمو (Growth Control Tower) | طبقات `auto_client_acquisition` + مسارات `/api/v1/platform/*`، `/api/v1/intelligence/*`، `/api/v1/growth-operator/*`، `/api/v1/services/*`، `/api/v1/targeting/*` | قرار، Inbox، مهام، خدمات، استهداف — **بدون SPA واحد** في الريبو. | +| لوحة إيرادات (عرض) | [`command_center`](../api/routers/command_center.py) `GET /api/v1/command-center/*` | تغذية لوحة الإيرادات؛ صفحة [`command-center.html`](../landing/command-center.html) توثّق المسار `snapshot` كنموذج. | +| نظام v3 | [`v3`](../api/routers/v3.py) `/api/v1/v3/*` | طبقة «Revenue OS» تجريبية: وكلاء، امتثال، رادار، ذاكرة. | +| إطلاق البيتا والتحصيل | [`launch_ops`](../api/routers/launch_ops.py)، [`revenue_launch`](../api/routers/revenue_launch.py) | عروض، go/no-go، دفع يدوي، Proof — انظر [`REVENUE_TODAY_PLAYBOOK.md`](REVENUE_TODAY_PLAYBOOK.md). | + +## جدول: صفحة → مسارات API ذات صلة + +| صفحة (landing) | لغة | مسارات API (أمثلة) | ملاحظة | +|----------------|-----|----------------------|--------| +| [`index.html`](../landing/index.html) | ar | `/health`، روابط عامة | بوابة الموقع. | +| [`private-beta.html`](../landing/private-beta.html) | ar | `GET /api/v1/launch/private-beta/offer`، `GET /api/v1/revenue-launch/offer`، `GET /api/v1/growth-operator/missions`، `POST /api/v1/operator/chat/message`، `GET /api/v1/revenue-os/company-os/command-feed/demo` | تدشين Pilot + **Company OS**؛ [`private-beta-en.html`](../landing/private-beta-en.html) للإنجليزية. | +| [`private-beta-en.html`](../landing/private-beta-en.html) | en | نفس المسارات أعلاه | نسخة مختصرة LTR. | +| [`services.html`](../landing/services.html) | ar | `GET /api/v1/services/catalog`، `POST /api/v1/services/recommend` | برج الخدمات؛ [`services-en.html`](../landing/services-en.html). | +| [`services-en.html`](../landing/services-en.html) | en | نفس المسارات | | +| [`command-center.html`](../landing/command-center.html) | ar (محتوى مختلط في العناوين) | `GET /api/v1/command-center/snapshot?customer_id=...`، باقي مسارات [`command_center.py`](../api/routers/command_center.py) | عرض تسويقي؛ [`command-center-en.html`](../landing/command-center-en.html) مركز روابط EN. | +| [`command-center-en.html`](../landing/command-center-en.html) | en | نفس `command-center` + `GET /api/v1/v3/command-center/snapshot` إن وُجد في smoke | | +| [`free-diagnostic.html`](../landing/free-diagnostic.html) | ar | `POST /api/v1/targeting/free-diagnostic` | | +| [`first-10-opportunities.html`](../landing/first-10-opportunities.html) | ar | `POST /api/v1/intelligence/missions/first-10-opportunities` أو `POST /api/v1/innovation/opportunities/ten-in-ten` | انظر aliases. | +| [`list-intelligence.html`](../landing/list-intelligence.html) | ar | `POST /api/v1/targeting/uploaded-list/analyze` | | +| [`growth-os.html`](../landing/growth-os.html) | ar | `GET /api/v1/services/catalog`، مسارات Growth OS في الخدمات | | +| [`agency-partner.html`](../landing/agency-partner.html) | ar | `GET /api/v1/services/contracts/templates` | | +| [`launch-readiness.html`](../landing/launch-readiness.html) | ar | `GET /api/v1/launch/go-no-go`، `scripts/launch_readiness_check.py` | | + +## لغة الـ API التجريبية + +- **`GET /api/v1/revenue-launch/offer?lang=en`**: يضيف حقول `title_en` / `summary_en` (وأمثلة مشابهة) **إلى جانب** الحقول العربية `_ar` — لا يزيل الحقول العربية. + +## تشغيل محلي للتحقق + +من مجلد `dealix`: + +```bash +python scripts/smoke_inprocess.py +python scripts/launch_readiness_check.py +``` + +Staging (يتطلب `STAGING_BASE_URL`): + +```bash +python scripts/smoke_staging.py --base-url https:// +``` + +## وثائق مرتبطة + +- [`STAGING_DEPLOYMENT.md`](STAGING_DEPLOYMENT.md) +- [`PRIVATE_BETA_LAUNCH_TODAY.md`](PRIVATE_BETA_LAUNCH_TODAY.md) +- [`DEALIX_100_PERCENT_LAUNCH_PLAN.md`](DEALIX_100_PERCENT_LAUNCH_PLAN.md) +- [`AUTONOMOUS_REVENUE_COMPANY_OS.md`](AUTONOMOUS_REVENUE_COMPANY_OS.md) — فئة المنتج والمسارات `/api/v1/operator/*` و`/api/v1/revenue-os/company-os/*` diff --git a/dealix/docs/GROWTH_CURATOR_STRATEGY.md b/dealix/docs/GROWTH_CURATOR_STRATEGY.md new file mode 100644 index 00000000..9754f279 --- /dev/null +++ b/dealix/docs/GROWTH_CURATOR_STRATEGY.md @@ -0,0 +1,25 @@ +# Growth Curator Strategy + +هدف الطبقة: **رفع جودة الصادر** (واتساب، إيميل، اجتماع متابعة) دون المطالبة بوعود غير قابلة للدفاع أو انتهاك سياسات القناة. + +## كود + +- `auto_client_acquisition/growth_curator/message_curator.py` — `grade_message` +- `auto_client_acquisition/growth_curator/playbook_curator.py` — قواعد مساعدة للقطاعات +- `auto_client_acquisition/growth_curator/curator_report.py` — `build_weekly_curator_report` + +## API + +- `GET /api/v1/growth-curator/report/demo` — تقرير أسبوعي شكلي للعرض +- `POST /api/v1/growth-curator/messages/grade` — `{ "message_ar": "...", "sector": "", "channel": "whatsapp" }` +- `GET /api/v1/growth-curator/skills/demo` — جرد مهارات/قوالب (درجات MVP) +- `GET /api/v1/growth-curator/missions/curate/demo` — اقتراحات دمج/أرشفة أسبوعية (لا حذف تلقائي) + +## تكامل مقترح + +1. بعد توليد مسودة من الذكاء: استدعاء `grade_message`؛ إذا انخفض `score` عن عتبة المنتج، أظهر تحذيراً ولا تعرض زر «إرسال» في البيتا. +2. ربط العتبات مع `channel_registry.blocked_actions` (منع جماعي بارد، إلخ). + +## اختبارات + +`tests/test_growth_tower_stack.py` — `test_grade_message_detects_guarantee`. diff --git a/dealix/docs/INCIDENT_RESPONSE.md b/dealix/docs/INCIDENT_RESPONSE.md new file mode 100644 index 00000000..606ef1e5 --- /dev/null +++ b/dealix/docs/INCIDENT_RESPONSE.md @@ -0,0 +1,11 @@ +# الاستجابة للحوادث (Pilot) + +1. تصنيف P0–P3 وفق [`SUPPORT_SLA.md`](SUPPORT_SLA.md). +2. إيقاف أي إجراء live مشبوه حتى التحقق. +3. توثيق الوقت والتأثير والخطوات (بدون أسرار أو PII خام في السجلات العامة). +4. إشعار العميل بلغة واضحة وخطة تعافي. +5. مراجعة لاحقة وتحديث السياسات أو الاختبارات إن لزم. + +**مرجع API:** `GET /api/v1/customer-ops/incidents/playbook` + +**آخر تحديث:** 2026-05-01 diff --git a/dealix/docs/INTELLIGENCE_LAYER_STRATEGY.md b/dealix/docs/INTELLIGENCE_LAYER_STRATEGY.md new file mode 100644 index 00000000..70d3b1ac --- /dev/null +++ b/dealix/docs/INTELLIGENCE_LAYER_STRATEGY.md @@ -0,0 +1,34 @@ +# استراتيجية Intelligence Layer — Growth Control Tower + +## الهدف + +طبقة رفيعة تحت `/api/v1/intelligence/*` تُخرج هياكل JSON **deterministic** (بدون LLM إلزامي في MVP): Growth Brain، Trust Score، Revenue DNA، محاكاة فرص، موجز للمجلس، حركات تنافسية، و**Intel Command Feed** منفصل عن مسار `innovation/command-feed` لتفادي التعارض في الأسماء. + +## مكوّنات الكود + +| وحدة | وظيفة MVP | +|------|-----------| +| `growth_brain` | بناء ملف شركة/نمو من JSON مدخل | +| `intel_command_feed` | بطاقات إضافية: تسرب إيراد، موجز مجلس — من بيانات ثابتة/مدخلات | +| `trust_score` | درجة ثقة 0–100 مع عوامل | +| `revenue_dna` | لقطات بنية إيراد (قنوات، دورة، مخاطر) | +| `opportunity_simulator` | سيناريوهات رقمية بسيطة | +| `board_brief` | فقرة تنفيذية للإدارة | +| `competitive_moves` | قائمة مقترحات تنافسية آمنة عرضياً | +| `action_graph` | مخطط signal→proof عبر `POST /api/v1/intelligence/action-graph/demo` | +| `mission_engine` | كتالوج مهمات + روابط canonical عبر `GET /api/v1/intelligence/missions/catalog` | +| `decision_memory` | سجل قرارات in-memory عبر `.../decision-memory/*` | + +## الربط بـ Innovation / Proof + +- **Ten-in-ten:** المسار `POST /api/v1/innovation/opportunities/ten-in-ten` يبقى المصدر؛ يمكن للـ intelligence استدعاء `build_ten_opportunities` داخلياً عند الحاجة (`include_ten_in_ten` اختياري في جسم الطلب). +- **Proof:** مفاهيم «إثبات الإيراد» تبقى متسقة مع [`INNOVATION_STRATEGY.md`](INNOVATION_STRATEGY.md) ودفتر الابتكار؛ طبقة الذكاء **لا تستبدل** سجل الأحداث الدائم. + +## Action Graph + +رسم بياني موجّه (عُقد/حواف) يُعاد كـ JSON في MVP عبر `action-graph/demo`. التنفيذ المتين لاحقاً مع [`AGENT_WORKFLOW_ARCHITECTURE.md`](AGENT_WORKFLOW_ARCHITECTURE.md) دون LangGraph حتى موافقة صريحة. + +## مخاطر + +- ازدواج مع `innovation`: يُحل بالأسماء (`intel_command_feed`) واختبارات عقد API. +- توقعات زائدة: التسمية «Intelligence» لا تعني نماذج توليدية في هذا الإصدار. diff --git a/dealix/docs/MEETING_INTELLIGENCE.md b/dealix/docs/MEETING_INTELLIGENCE.md new file mode 100644 index 00000000..2164e99c --- /dev/null +++ b/dealix/docs/MEETING_INTELLIGENCE.md @@ -0,0 +1,23 @@ +# Meeting Intelligence + +تحويل **نص** (transcript أو ملاحظات) إلى ملخص عربي، استخراج اعتراضات، ومتابعة بعد الاجتماع — **بدون** إدراج أحداث تقويم حي من هذه المسارات. + +## كود + +| وحدة | ملاحظة | +|------|--------| +| `transcript_parser.py` | تلخيص نص طويل | +| `objection_extractor.py` | اعتراضات من النص | +| `followup_builder.py` | مسودة متابعة | +| `meeting_brief.py` | brief قبل الاجتماع من هياكل JSON | +| `deal_risk.py` | إشارات مخاطرة صفقة (تجريبي) | + +## API + +- `POST /api/v1/meeting-intelligence/transcript/summarize` — `{ "text": "..." }` +- `POST /api/v1/meeting-intelligence/followup/draft` — `{ "summary_ar": "...", "next_steps": [] }` +- `POST /api/v1/meeting-intelligence/brief/pre-meeting` — `{ "company": {}, "contact": {}, "opportunity": {} }` + +## خصوصية + +لا تُرسل تسجيلات حساسة إلى نماذج خارجية من مسارات الـ API الحالية دون موافقة DPA؛ المعالجة هنا deterministic على النص المُدخل. diff --git a/dealix/docs/MODEL_PROVIDER_ROUTER.md b/dealix/docs/MODEL_PROVIDER_ROUTER.md new file mode 100644 index 00000000..5eec6a40 --- /dev/null +++ b/dealix/docs/MODEL_PROVIDER_ROUTER.md @@ -0,0 +1,22 @@ +# Model Provider Router + +طبقة **تكوين وتلميحات** لربط نوع المهمة بمزود/نموذج مقترح. التنفيذ الفعلي لاستدعاء LLM يبقى في `core/llm/` وإعدادات البيئة. + +## كود + +- `auto_client_acquisition/model_router/task_router.py` — `route_task`, `list_tasks` +- `auto_client_acquisition/model_router/provider_registry.py` — `list_providers` + +## API + +- `GET /api/v1/model-router/tasks` +- `POST /api/v1/model-router/route` — `{ "task_type": "..." }` +- `GET /api/v1/model-router/providers` + +## استخدام + +استخدم الاستجابة لعرض «لماذا هذا المزود» في لوحة المشرف أو لتمرير metadata إلى `core/llm/router.py` عند توحيد السلوك لاحقاً. + +## اختبارات + +`tests/test_growth_tower_stack.py` — `test_model_router_compliance_guardrail`. diff --git a/dealix/docs/ONBOARDING_RUNBOOK.md b/dealix/docs/ONBOARDING_RUNBOOK.md new file mode 100644 index 00000000..449c6bdf --- /dev/null +++ b/dealix/docs/ONBOARDING_RUNBOOK.md @@ -0,0 +1,10 @@ +# تشغيل Onboarding — Pilot + +1. **قبل الجلسة:** تأكيد الهدف، القطاع، المدينة، العرض، رابط الموقع، ووجود قائمة أو لا. +2. **الجلسة (٣٠ دقيقة):** شرح سياسة الموافقات وعدم الإرسال الحي الافتراضي؛ جمع القنوات المتاحة (إيميل، واتساب opt-in، CRM، نماذج). +3. **بعد الجلسة:** تشغيل أول مهمة (تشخيص / ١٠ فرص / List Intelligence) حسب الاتفاق؛ جدولة أول Proof Pack. +4. **مخرجات أول ٢٤–٤٨ ساعة:** حسب نافذة الـ Pilot — لا تعد نتائج مضمونة؛ وثّق المسودات والموافقات. + +**مرجع API:** `GET /api/v1/customer-ops/onboarding/checklist` + +**آخر تحديث:** 2026-05-01 diff --git a/dealix/docs/PAID_BETA_OPERATING_PLAYBOOK.md b/dealix/docs/PAID_BETA_OPERATING_PLAYBOOK.md new file mode 100644 index 00000000..64945f93 --- /dev/null +++ b/dealix/docs/PAID_BETA_OPERATING_PLAYBOOK.md @@ -0,0 +1,100 @@ +# Paid Beta — دليل التشغيل التجاري (Dealix) + +**الغرض:** تحويل **GO_PRIVATE_BETA** محلياً إلى **PAID_BETA_READY** على staging ثم إلى أول إيراد وتسليم Proof Pack — بدون توسيع تقني كبير وبدون وعود خطيرة. + +**مرجع:** [`APPROVED_MARKET_MESSAGING.md`](APPROVED_MARKET_MESSAGING.md)، [`PROHIBITED_CLAIMS.md`](PROHIBITED_CLAIMS.md)، [`POSITIONING_LOCK.md`](POSITIONING_LOCK.md)، [`STAGING_DEPLOYMENT.md`](STAGING_DEPLOYMENT.md)، [`FIRST_PILOT_DELIVERY_WORKFLOW.md`](FIRST_PILOT_DELIVERY_WORKFLOW.md)، [`PRIVATE_BETA_OPERATING_BOARD.md`](PRIVATE_BETA_OPERATING_BOARD.md). + +--- + +## 1. تعريف الحالات + +| الحالة | المعنى | +|--------|--------| +| **Private Beta (محلي)** | `launch_readiness_check.py` بدون `--base-url` → `GO_PRIVATE_BETA`؛ CI أخضر؛ لا بيع مدفوع قبل staging إن كنت تعتمد على النشر. | +| **Paid Beta** | `STAGING_BASE_URL` + `python scripts/launch_readiness_check.py --base-url …` → **`PAID_BETA_READY`**؛ تحصيل يدوي (فاتورة / رابط / تحويل)؛ أول Pilot موقّع. | +| **Public Launch** | ليس الآن — انظر شروط الخروج في أسفل هذا الملف وفي [`PUBLIC_LAUNCH_GO_NO_GO.md`](PUBLIC_LAUNCH_GO_NO_GO.md). | + +--- + +## 2. Staging (خلال أيام قليلة) + +1. انشر API على **Railway** أو **Render** (انظر [`ops/RAILWAY_AI_COMPANY_BIND.md`](ops/RAILWAY_AI_COMPANY_BIND.md)). +2. Start: `uvicorn api.main:app --host 0.0.0.0 --port $PORT` — الاستماع على `PORT` الذي يحقنه المزود. +3. Health: `GET /health` → 200. +4. تحقق: + ```bash + export STAGING_BASE_URL="https://YOUR-STAGING-URL" + python scripts/smoke_staging.py --base-url "$STAGING_BASE_URL" + python scripts/launch_readiness_check.py --base-url "$STAGING_BASE_URL" + ``` +5. **لا تبدأ عرضاً مدفوعاً عاماً** إذا كانت النتيجة `NO_GO` — عالج السبب أولاً. + +--- + +## 3. التحصيل اليدوي (بدون live billing من API) + +- **Moyasar:** فاتورة يدوية أو رابط دفع (sandbox أو إنتاج حسب اتفاقك القانوني). +- **أو** تحويل بنكي مع مرجع واضح في العقد/الإيميل. +- رسالة اقتراح للعميل بعد الموافقة على النطاق (انظر [`FIRST_PILOT_DELIVERY_WORKFLOW.md`](FIRST_PILOT_DELIVERY_WORKFLOW.md) لنص Pilot 499). + +**ممنوع في التواصل:** ضمان مبيعات، واتساب بارد جماعي، «AI يبيع بدالك 100٪»، scraping أو أتمتة رسائل LinkedIn — انظر [`PROHIBITED_CLAIMS.md`](PROHIBITED_CLAIMS.md). + +--- + +## 4. حملة تواصل يدوية (7 أيام — أهداف مرجعية) + +| الموجه | هدف مرجعي | +|--------|------------| +| تواصل مباشر (وكالات / مسوقين / B2B) | 50–70 رسالة على مدار الأسبوع (مو شرط يوم واحد) | +| ديمو | 5–7 محجوزة | +| Pilot | 3 تشغيل؛ إغلاق **1–2 مدفوعين** | +| Proof Pack | أول تسليم موثّق لكل عميل مدفوع | + +**قنوات آمنة:** واتساب دافئ فقط مع سياق؛ إيميل مستهدف؛ LinkedIn يدوي للمؤسس — بدون أدوات سحب أو DMs آلية. + +--- + +## 5. قياس الـ funnel + +استخدم لوحة [`PRIVATE_BETA_OPERATING_BOARD.md`](PRIVATE_BETA_OPERATING_BOARD.md) (Sheet أو نسخة Markdown). + +يومياً: سجّل `messages_sent`، الردود الإيجابية، الديمو، عروض الـ Pilot، طلبات الدفع، المستلم، Proof Packs. + +سكربت تذكيري: `python scripts/paid_beta_daily_scorecard.py` (مع وسائط أو ملف JSON — انظر تعليمات السكربت). + +--- + +## 6. شروط الانتقال إلى Paid Beta (تشغيل) + +قبل أن تسمّي المرحلة «Paid Beta» تشغيلياً: + +- [ ] **`PAID_BETA_READY`** على staging (`launch_readiness_check.py --base-url`). +- [ ] **3 ديمو** على الأقل محجوزة أو منجزة (حسب تعريفك). +- [ ] **1 Pilot مدفوع** أو التزام مكتوب واضح بالدفع. +- [ ] **أول Proof Pack** جاهز للتسليم (قالب + أرقام من المنتج/العملية). +- [ ] **لا إجراءات غير آمنة** (لا live send بدون موافقة، لا تجاوز لسياسة القنوات). +- [ ] **لا تسرّب أسرار** في الريبو أو في traces (انظر [`POST_MERGE_VERIFICATION.md`](POST_MERGE_VERIFICATION.md)). + +--- + +## 7. شروط ما بعد 3 pilots (تحليل لا بناء) + +بعد أول **3 pilots**، لا تضف ميزات كبيرة. راجع: + +- أي خدمة بيعت أسرع؟ +- أي صفحة أو رسالة جلبت ردّاً؟ +- مدة الـ onboarding؟ +- هل Proof Pack أقنع؟ +- هل السعر مناسب؟ هل الـ support مثقل؟ + +ثم **ضاعف** على: Growth Starter، أو مسار الوكالة، أو List Intelligence — حسب البيانات لا حسب الحدس. + +--- + +## 8. CI كبوابة دمج + +فعّل **required status checks** على الفرع المحمي في GitHub حتى لا يُدمج كود فاشل. قائمة الـ jobs المطلوبة وأسماء الـ checks: [`BRANCH_PROTECTION_AND_CI.md`](BRANCH_PROTECTION_AND_CI.md). مرجع خارجي: وثائق GitHub عن status checks. + +--- + +**آخر تحديث:** 2026-05-01 diff --git a/dealix/docs/PLATFORM_SERVICES_STRATEGY.md b/dealix/docs/PLATFORM_SERVICES_STRATEGY.md new file mode 100644 index 00000000..b1474902 --- /dev/null +++ b/dealix/docs/PLATFORM_SERVICES_STRATEGY.md @@ -0,0 +1,54 @@ +# استراتيجية Platform Services — Growth Control Tower + +## الهدف + +طبقة موحّدة تحت `/api/v1/platform/*` تجمع: **أحداث موحّدة**، **سياسة قنوات**، **بوابة أدوات بدون إرسال حي**، **Inbox موحّد** (بطاقات عربية)، **سجل قرارات**، و**تلخيص Proof** متوافق مع `auto_client_acquisition/innovation/proof_ledger.py` — دون تكرار «مصدر الحقيقة» للأحداث الدائمة في DB. + +## مكوّنات الكود + +| وحدة | مسؤولية | +|------|----------| +| `event_bus` | أنواع أحداث + تحقق من الحقول الإلزامية (يشمل أنماطاً موسّعة مثل `email.received`, `payment.paid`, `review.created`) | +| `channel_registry` | قدرات القناة، `beta_status`, `allowed_actions`, `blocked_actions`, `risk_level` | +| `action_policy` | قواعد deterministic: إرسال خارجي → موافقة؛ واتساب بارد → محظور؛ دفع → تأكيد؛ مصدر غير معروف → مراجعة | +| `tool_gateway` | لا شبكة ولا live — `draft_created` / `blocked` / `approval_required` / `unsupported` | +| `unified_inbox` | تحويل حدث → بطاقة (`title_ar`, `summary_ar`, أزرار ≤ 3)؛ يمكن دمج لمسات من `build_demo_command_feed` كمرجع عرض | +| `action_ledger` | سجل قرارات in-memory في MVP (قابل للاستبدال بـ DB) | +| `proof_summary` | تلخيص يستند إلى `build_demo_proof_ledger()` | +| `service_catalog` | خدمات قابلة للبيع كبيانات ثابتة + metadata | + +## ما يُنفَّذ الآن مقابل مؤجل + +**الآن (MVP):** مسارات read-only / مسودات؛ `WHATSAPP_ALLOW_LIVE_SEND` يبقى افتراضياً `false` في [`core/config/settings.py`](../core/config/settings.py). + +**مؤجل:** OAuth، توقيع webhook إنتاجي كامل، قاعدة أحداث دائمة لـ platform خارج نموذج الابتكار، تنفيذ فعلي لـ Gmail/Calendar/Moyasar. + +## Inbox و Command Cards + +- كل بطاقة: عنوان عربي، ملخص، **ثلاثة أزرار كحد أقصى** (`label_ar` + `action_id`). +- مصادر الحدث: نماذج leads موثّقة، أحداث داخلية، لاحقاً قنوات مسجّلة فقط. + +## امتثال PDPL / opt-in + +- لا تخزين بيانات حساسة في سجل الـ MVP بدون سياسة موثّقة. +- أي إرسال جماعي أو بارد يمر عبر `action_policy` + موافقة بشرية؛ راجع [`PRIVATE_BETA_RUNBOOK.md`](PRIVATE_BETA_RUNBOOK.md) و[`BETA_PRIVATE_GATES_CHECKLIST.md`](BETA_PRIVATE_GATES_CHECKLIST.md). + +## العلاقة بـ Innovation + +`innovation` يبقى مسار العرض والـ Kill features (`ten-in-ten`, command feed demo/live). Platform **تلتف** على الدوال التجريبية للـ Proof حيث يلزم، ولا تعيد تعريف أحداث الـ ledger الدائمة. + +## طبقات «برج التحكم» الإضافية (كود حالي) + +| طبقة | مجلد | مسارات API رئيسية | +|------|------|---------------------| +| Security curator | `auto_client_acquisition/security_curator/` | `/api/v1/security-curator/*` — redact، inspect-diff | +| Growth curator | `auto_client_acquisition/growth_curator/` | `/api/v1/growth-curator/*` — grade، تقرير أسبوعي demo | +| Meeting intelligence | `auto_client_acquisition/meeting_intelligence/` | `/api/v1/meeting-intelligence/*` — تلخيص نص، متابعة، brief | +| Model router | `auto_client_acquisition/model_router/` | `/api/v1/model-router/*` — tasks، route، providers | +| Connectors | `auto_client_acquisition/connectors/` | `GET /api/v1/connectors/catalog` | +| Agent observability | `auto_client_acquisition/agent_observability/` | `/api/v1/agent-observability/*` — evals، trace shape | +| Growth operator (aliases) | `api/routers/growth_operator.py` | `/api/v1/growth-operator/missions`، `.../proof-pack/demo` | + +**Growth operator** لا يضاعف المنطق: يضيف `canonical_route` للإشارة إلى مصدر الحقيقة في `innovation` و`business`. + +لخطة تنفيذ بالعربية: [`EXECUTION_ROADMAP_AR.md`](EXECUTION_ROADMAP_AR.md). diff --git a/dealix/docs/POSITIONING_LOCK.md b/dealix/docs/POSITIONING_LOCK.md new file mode 100644 index 00000000..ad94c666 --- /dev/null +++ b/dealix/docs/POSITIONING_LOCK.md @@ -0,0 +1,35 @@ +# Dealix — تثبيت الرسالة (Positioning Lock) + +**الفئة:** نظام تشغيل نمو وإيرادات عربي للشركات السعودية والوكالات (Saudi Revenue Execution OS). + +**الجملة الأساسية:** Dealix يحوّل إشارات السوق وبيانات الشركة إلى فرص ورسائل واجتماعات مع موافقة بشرية وتقرير أثر (Proof Pack)، بدون إرسال عشوائي وبدون وعود مبالغ فيها. + +## ليس Dealix + +- ليس CRM عاماً يحتاج إدخال يدوي كاملاً للبيانات. +- ليس «بوت واتساب» للإرسال الجماعي. +- ليس أداة scraping أو جمع بيانات أشخاص من LinkedIn أو غيره بطرق مخالفة. +- ليس وكالة تقليدية فقط (المنصة تُكمّل الوكالة وتكرّر التشغيل). +- ليس مساعد ذكاء اصطناعي عاماً بلا سياسات وموافقات. + +## المشترون الأساسيون + +- شركات B2B سعودية وخليجية (خدمات، SaaS، تدريب، استشارات، تقنية). +- وكالات تسويق ومبيعات تريد تشغيل نمو لعملائها مع Proof Pack. + +## الـ Wedge (أول ما نبيعه) + +- **First 10 Opportunities** + **Proof Pack** + **موافقات قبل أي تواصل حساس**. + +## المسارات التجارية + +1. **Dealix للشركات:** فرص، رسائل عربية، تقييم قابلية التواصل، مسودات، موافقات، تقرير أثر. +2. **Dealix للوكالات:** تشخيص، تشغيل خدمة لعميل الوكالة، Proof Pack قابل للمشاركة مع العميل النهائي (حسب الاتفاق). + +## قواعد لا تُخالَف في أي نسخة + +- لا إرسال حي (واتساب/إيميل/تقويم/دفع) إلا بعد سياسة وموافقة صريحة حيث ينطبق. +- لا واتساب بارد افتراضياً؛ التواصل عبر واتساب يتطلب opt-in أو inbound أو علاقة واضحة. +- لا scraping لـ LinkedIn ولا أتمتة DM/Connect. + +**آخر تحديث:** 2026-05-01 diff --git a/dealix/docs/POST_MERGE_VERIFICATION.md b/dealix/docs/POST_MERGE_VERIFICATION.md index e04e9d72..4cf2648f 100644 --- a/dealix/docs/POST_MERGE_VERIFICATION.md +++ b/dealix/docs/POST_MERGE_VERIFICATION.md @@ -27,19 +27,37 @@ Optional secret-pattern scan (adapt to your environment): rg "ghp_|github_pat_|sk_live_" --glob '!htmlcov/**' --glob '!.venv/**' ``` +## Growth Control Tower — Wave 0 (baseline) + +بعد إضافة مسارات `/api/v1/platform/*` و`/api/v1/intelligence/*` أعد تشغيل نفس الأوامر أعلاه وتأكد من `ROUTE_CHECK_OK`. Staging: [`STAGING_DEPLOYMENT.md`](STAGING_DEPLOYMENT.md) و[`ops/RAILWAY_AI_COMPANY_BIND.md`](ops/RAILWAY_AI_COMPANY_BIND.md). + +## Launch Ops + Service Tower (خرائط ثابتة) + +- تأكد من: `GET /api/v1/launch/private-beta/offer`، `GET /api/v1/launch/go-no-go`، `GET /api/v1/services/verticals`، `GET /api/v1/services/contracts/templates`. +- `scripts/smoke_staging.py` يتضمن هذه المسارات عند التحقق من staging. + +## Frontend map + لغتين (Landing / EN) + +- مرجع المشغّل: [`FRONTEND_AND_API_MAP.md`](FRONTEND_AND_API_MAP.md) — يربط `landing/*.html` بمسارات البرج والتحكم. +- صفحات إنجليزية مختصرة: `landing/private-beta-en.html`، `services-en.html`، `command-center-en.html` مع روابط من العربية. +- **Revenue offer i18n:** `GET /api/v1/revenue-launch/offer?lang=en` يضيف `title_en` / `summary_en` (وحقول إنجليزية موازية للقوائم حيث وُجدت) مع الإبقاء على الحقول العربية. + ## Last recorded run (workspace snapshot) | Step | Result | |------|--------| -| Git HEAD | `2d776cb` on branch `dealix-v3-autonomous-revenue-os` (re-run on `main` after merge) | -| compileall | OK (`api`, `auto_client_acquisition`, `integrations`, `dealix`) | -| pytest | `516 passed`, `6 skipped`, `0 failed` (`APP_ENV=test`, dummy LLM keys) — re-run after your merge | +| Git HEAD | `16e8ba2` on branch `ai-company` (re-run on `main` after merge) | +| compileall | OK (`api`, `auto_client_acquisition`, `db`, `core`) | +| pytest | `652 passed`, `6 skipped`, `0 failed` (`APP_ENV=test`, dummy LLM keys) — 2026-05-01 بعد `launch_readiness` + تكامل الواجهات؛ أعد التشغيل بعد الدمج | | `print_routes.py` | `ROUTE_CHECK_OK no duplicate method+path` | -| `smoke_inprocess.py` | `SMOKE_INPROCESS_OK` | +| `smoke_inprocess.py` | `SMOKE_INPROCESS_OK` (يشمل `GET /api/v1/revenue-launch/offer` و`GET /api/v1/revenue-launch/offer?lang=en`) | +| `launch_readiness_check.py` | `VERDICT: GO_PRIVATE_BETA`، exit `0` — يفحص محلياً: `/health`، customer-ops (checklist/sla/connectors)، `services/catalog` (حقول التسعير وProof لكل خدمة)، `launch/private-beta/offer`، `security-curator/demo`، ملفات `landing/companies|marketers|private-beta.html`، و`WHATSAPP_ALLOW_LIVE_SEND=false`. مع `--base-url` أو `STAGING_BASE_URL`: نفس المسارات عن بُعد → `PAID_BETA_READY` إذا نجحت كلها؛ وإلا `NO_GO`. اختياري: `--secrets` لفحص أنماط تسرّب شائعة. `--json` لمخرجات آلة | +| `smoke_staging.py` | يتطلب `STAGING_BASE_URL` — يشمل الآن `…/revenue-launch/offer?lang=en`؛ شغّله على الاستضافة الفعلية قبل أول عميل | +| Frontend + EN + `?lang=en` | وثّق في [`FRONTEND_AND_API_MAP.md`](FRONTEND_AND_API_MAP.md) — أعد `pytest` بعد أي تغيير على `revenue_launch` | ## CI -Confirm GitHub Actions workflow [`.github/workflows/ci.yml`](../.github/workflows/ci.yml) is green on the merged commit. +Confirm GitHub Actions workflow [`.github/workflows/dealix-api-ci.yml`](../../.github/workflows/dealix-api-ci.yml) is green on the merged commit (jobs: `pytest`, `smoke_inprocess`, `launch_readiness`). لإعداد **branch protection** وأسماء الـ checks: [`BRANCH_PROTECTION_AND_CI.md`](BRANCH_PROTECTION_AND_CI.md). ## Manual follow-ups (not automated) diff --git a/dealix/docs/PRIVATE_BETA_LAUNCH_TODAY.md b/dealix/docs/PRIVATE_BETA_LAUNCH_TODAY.md new file mode 100644 index 00000000..8a670720 --- /dev/null +++ b/dealix/docs/PRIVATE_BETA_LAUNCH_TODAY.md @@ -0,0 +1,59 @@ +# Private Beta — «اليوم» (تشغيل وبيع) + +قائمة تشغيل قبل أول عميل بيتا. تفاصيل تقنية إضافية: [`PRIVATE_BETA_RUNBOOK.md`](PRIVATE_BETA_RUNBOOK.md). ديمو ١٢ دقيقة: [`DEMO_SCRIPT_12_MINUTES.md`](DEMO_SCRIPT_12_MINUTES.md). رسائل أول ٢٠: [`FIRST_20_OUTREACH_MESSAGES.md`](FIRST_20_OUTREACH_MESSAGES.md). + +## ما ندشّنه اليوم + +- **Private Beta** (ليس إطلاقاً عاماً): عرض Pilot + قيمة واضحة + موافقات. +- **أربعة عروض أولى:** تشخيص مجاني، ذكاء قوائم، سباق ١٠ فرص، Growth OS Pilot — انظر [`landing/services.html`](../landing/services.html). +- **نسختان للصفحات الحرجة:** [`landing/private-beta-en.html`](../landing/private-beta-en.html)، [`landing/services-en.html`](../landing/services-en.html)، [`landing/command-center-en.html`](../landing/command-center-en.html) — روابط «English» من الصفحات العربية المقابلة. خريطة كاملة: [`FRONTEND_AND_API_MAP.md`](FRONTEND_AND_API_MAP.md). +- **Autonomous Revenue Company OS:** مرجع الفئة والطبقات — [`AUTONOMOUS_REVENUE_COMPANY_OS.md`](AUTONOMOUS_REVENUE_COMPANY_OS.md)؛ واجهة تشغيل تجريبية: `GET /api/v1/operator/bundles`، `POST /api/v1/operator/chat/message`؛ طبقة أحداث وبطاقات: `GET /api/v1/revenue-os/company-os/command-feed/demo`. + +## ما لا ندشّنه اليوم + +- إرسال واتساب جماعي بارد، Gmail إرسال تلقائي، إدراج تقويم حي بدون موافقة، شحن بطاقات داخل المنتج، scraping LinkedIn. + +## بيئة + +- [ ] Staging يعمل (`GET /health`). +- [ ] `WHATSAPP_ALLOW_LIVE_SEND=false` (افتراضي) ما لم يُوثَّق خلاف ذلك. +- [ ] أسرار Moyasar / Google / Meta **غير** مكشوفة في الريبو أو اللوجات. + +## API سريعة للتحقق + +- [ ] `GET /api/v1/growth-operator/missions` +- [ ] `GET /api/v1/platform/inbox/feed` +- [ ] `GET /api/v1/platform/proof/overview` +- [ ] `POST /api/v1/platform/events/ingest` مع `source: trusted_simulation` +- [ ] `GET /api/v1/security-curator/demo` +- [ ] `GET /api/v1/services/catalog` +- [ ] `GET /api/v1/launch/go-no-go` و `GET /api/v1/launch/scorecard` +- [ ] `GET /api/v1/revenue-launch/offer` و `GET /api/v1/revenue-launch/offer?lang=en` (تسميات إنجليزية إضافية بجانب العربية) + +## Go / No-Go (آلي demo) + +شغّل `GET /api/v1/launch/go-no-go` بعد `pytest` و`print_routes`. تحذير staging متوقع حتى يُفعّل `STAGING_BASE_URL` في `smoke_staging.py`. + +## تحقق آلي (مرجع الجلسة) + +من مجلد `dealix`: `python -m pytest -q --no-cov`، ثم `python scripts/smoke_inprocess.py`، ثم `python scripts/launch_readiness_check.py`. بعد نشر staging: `python scripts/smoke_staging.py --base-url https://`. سجّل النتائج في [`POST_MERGE_VERIFICATION.md`](POST_MERGE_VERIFICATION.md). + +## عملية بشرية + +- [ ] اتفاق pilot موقّع (نطاق، PDPL، قنوات مسموحة). +- [ ] مسؤول مراجعة لكل «إرسال» أو «دفع» خارجي. +- [ ] قناة دعم للعميل (واتساب أو إيميل داخلي). + +## بعد الجلسة الأولى + +- صدّر ملاحظات إلى `growth-curator` و`meeting-intelligence` كتحسين لمسودات الأسبوع التالي. + +## الصفحات (عرض) + +- [`landing/private-beta.html`](../landing/private-beta.html) +- [`landing/list-intelligence.html`](../landing/list-intelligence.html) +- [`landing/growth-os.html`](../landing/growth-os.html) + +## الإطلاق التجاري الأوسع + +انظر [`COMMERCIAL_LAUNCH_MASTER_PLAN.md`](COMMERCIAL_LAUNCH_MASTER_PLAN.md). diff --git a/dealix/docs/PRIVATE_BETA_OPERATING_BOARD.md b/dealix/docs/PRIVATE_BETA_OPERATING_BOARD.md new file mode 100644 index 00000000..dd18a29c --- /dev/null +++ b/dealix/docs/PRIVATE_BETA_OPERATING_BOARD.md @@ -0,0 +1,74 @@ +# Dealix Private Beta Operating Board + +**الغرض:** لوحة تشغيل يومية تربط **التواصل اليدوي** بالـ **funnel** حتى **الدفع** و**Proof Pack** — بدون أتمتة مخالفة للقنوات. + +**مرافق:** [`PAID_BETA_OPERATING_PLAYBOOK.md`](PAID_BETA_OPERATING_PLAYBOOK.md) — سكربت `scripts/paid_beta_daily_scorecard.py` لتلخيص الأرقام اليومية. + +--- + +## 1. إنشاء الـ Sheet (Google Sheets أو Excel) + +**اسم المقترح:** `Dealix Private Beta Operating Board` + +### أعمدة سجل التواصل (صف واحد = شخص/شركة/لمسة) + +| العمود | الوصف | +|--------|--------| +| `date` | تاريخ اللمسة | +| `company` | اسم الشركة | +| `person` | اسم الشخص | +| `segment` | وكالة / SaaS / تدريب / استشارات / … | +| `source` | LinkedIn / إيميل / إحالة / … | +| `channel` | القناة المستخدمة | +| `message_sent` | نعم/لا + اختصار الموضوع | +| `reply_status` | لا رد / رد سلبي / رد إيجابي / محجوز ديمو | +| `demo_booked` | تاريخ/وقت أو فارغ | +| `diagnostic_sent` | نعم/لا | +| `pilot_offered` | نعم/لا | +| `price` | 0 / 499 / 1500 / … | +| `paid` | نعم/لا + المرجع (فاتورة/حوالة) | +| `proof_pack_sent` | نعم/لا + تاريخ | +| `next_step` | جملة واحدة | +| `notes` | حر | + +--- + +## 2. ملخص نهاية اليوم (Executive snapshot) + +املأ يومياً (يمكن صفاً ثانياً «Summary» أو تبويب): + +| المقياس | قيمة اليوم | +|---------|------------| +| Messages sent | | +| Positive replies | | +| Demos booked | | +| Pilots offered | | +| Payments requested | | +| Payments received | | +| Proof packs delivered | | +| Top objection | | +| Next improvement | | + +--- + +## 3. ربط مع السكربت + +1. صدّر ملخص اليوم إلى JSON (يدوياً أو من Sheet بسكربت صغير خارج الريبو إن رغبت). +2. شغّل: + + ```bash + python scripts/paid_beta_daily_scorecard.py --file path/to/today.json + ``` + + أو مرّر الأرقام كوسائط (انظر `python scripts/paid_beta_daily_scorecard.py --help`). + +--- + +## 4. قواعد الجودة + +- لا تسجيل أسرار أو مفاتيح API في الـ Sheet. +- تقليل PII: أسماء الشركات كافية في المرحلة الأولى؛ تجنب تخزين أرقام هواتف غير ضرورية. + +--- + +**آخر تحديث:** 2026-05-01 diff --git a/dealix/docs/PRIVATE_BETA_RUNBOOK.md b/dealix/docs/PRIVATE_BETA_RUNBOOK.md index c3ebe55b..db404bdc 100644 --- a/dealix/docs/PRIVATE_BETA_RUNBOOK.md +++ b/dealix/docs/PRIVATE_BETA_RUNBOOK.md @@ -1,7 +1,8 @@ # Private Beta — دليل تشغيل Dealix **يوم التدشين (فهرس ساعة بساعة):** [`LAUNCH_DAY_RUNBOOK_AR.md`](LAUNCH_DAY_RUNBOOK_AR.md). -**قائمة بوابات موحّدة:** [`BETA_PRIVATE_GATES_CHECKLIST.md`](BETA_PRIVATE_GATES_CHECKLIST.md). +**قائمة بوابات موحّدة:** [`BETA_PRIVATE_GATES_CHECKLIST.md`](BETA_PRIVATE_GATES_CHECKLIST.md). +**تشغيل مدفوع ولوحة يومية:** [`COMMERCIAL_GO_LIVE_CHECKLIST.md`](COMMERCIAL_GO_LIVE_CHECKLIST.md) (التسلسل الكامل)، [`PAID_BETA_OPERATING_PLAYBOOK.md`](PAID_BETA_OPERATING_PLAYBOOK.md)، [`FIRST_PILOT_DELIVERY_WORKFLOW.md`](FIRST_PILOT_DELIVERY_WORKFLOW.md)، [`PRIVATE_BETA_OPERATING_BOARD.md`](PRIVATE_BETA_OPERATING_BOARD.md). ## الجمهور diff --git a/dealix/docs/PROHIBITED_CLAIMS.md b/dealix/docs/PROHIBITED_CLAIMS.md new file mode 100644 index 00000000..068a95ae --- /dev/null +++ b/dealix/docs/PROHIBITED_CLAIMS.md @@ -0,0 +1,19 @@ +# ادعاءات ممنوعة في التسويق والمبيعات + +لا تُستخدم هذه العبارات في صفحات الهبوط، الإعلانات، الرسائل، أو الديمو: + +- «نضمن لك عملاء» أو «نضاعف مبيعاتك مضموناً». +- «نرسل لكل السوق» أو «نوصل لكل الأرقام». +- «نسحب بيانات LinkedIn» أو «نستخرج جهات الاتصال تلقائياً من LinkedIn». +- «نرسل واتساب بارد لقائمة كبيرة بدون موافقة». +- «نعمل بدون موافقتك» أو «نُرسل نيابة عنك مباشرة». +- «نتجاوز سياسات المنصات» أو «نتفادى الحظر بطرق غير شرعية». +- أي وعد بنتائج مالية محددة بدون أساس قابل للتحقق والعقد. + +## بدائل مسموحة + +- «نُجهّز فرصاً مؤهّلة ورسائل عربية، وأنت توافق قبل أي تواصل حساس». +- «نقيّم قابلية التواصل (safe / needs_review / blocked) لتقليل المخاطر». +- «Proof Pack يوثّق المسودات والموافقات والمخاطر التي تم منعها» (حسب ما يُقاس فعلياً). + +**آخر تحديث:** 2026-05-01 diff --git a/dealix/docs/REVENUE_TODAY_PLAYBOOK.md b/dealix/docs/REVENUE_TODAY_PLAYBOOK.md new file mode 100644 index 00000000..81bf2954 --- /dev/null +++ b/dealix/docs/REVENUE_TODAY_PLAYBOOK.md @@ -0,0 +1,66 @@ +# Revenue Today — دليل تشغيل دخل اليوم (Private Beta) + +> جمع بين المنتج والبيع دون كسر قواعد الأمان: **لا إرسال حي تلقائي، لا شحن من API داخل Dealix في هذه المرحلة، لا واتساب بارد، لا أتمتة LinkedIn المخالفة.** + +## 1. عرض ٤٩٩ ريال (Pilot ٧ أيام) + +- **الوعد:** ١٠ فرص B2B، لماذا الآن، رسائل عربية (مسودات)، فحص قابلية التواصل، خطة متابعة ٧ أيام، Proof Pack مختصر. +- **التحصيل:** فاتورة أو رابط دفع يدوي من لوحة Moyasar (sandbox أو live حسب سياسة شركتك) — راجع [وثائق Moyasar](https://docs.moyasar.com/). +- **API للمرجعية:** `GET /api/v1/revenue-launch/offer` (يحتوي `pilot_499`)؛ للتسميات الإنجليزية بجانب العربية: `GET /api/v1/revenue-launch/offer?lang=en`. + +## 2. عرض Growth OS Pilot (١٥٠٠–٣٠٠٠ ريال / ٣٠ يوم) + +- تشغيل أوسع: موجز، فرص، ذكاء قوائم، مسودات قنوات، Proof أسبوعي — كلها **مسودات وموافقة** ما لم تُفعّل سياسات الإنتاج صراحةً لاحقاً. + +## 3. من نستهدف اليوم + +- ٥ وكالات B2B، ٥ تدريب/استشارات، ٥ SaaS صغيرة، ٥ خدمات بواتساب نشط (يدوياً — لا scraping). + +## 4. أول ٢٠ تواصل + +- قوالب: `GET /api/v1/launch/outreach/first-20` و`GET /api/v1/revenue-launch/outreach/first-20` (عينات + شرائح). +- **نسخ يدوي فقط** من الرسائل. + +## 5. سيناريو الردود + +- `GET /api/v1/revenue-launch/demo-flow` → قسم `objections`. + +## 6. حجز الديمو + +- ديمو ١٢ دقيقة: `docs/DEMO_SCRIPT_12_MINUTES.md` و`GET /api/v1/revenue-launch/demo-flow`. + +## 7. إغلاق أول Pilot + +- `GET /api/v1/revenue-launch/pilot-delivery` — نموذج intake، خطة ٢٤ ساعة، مخرجات First 10 و List Intelligence. + +## 8. ماذا نطلب من العميل + +- حقول intake في نفس الـ endpoint أعلاه (موقع، قطاع، مدينة، عرض، قائمة، قنوات، موافق واتساب). + +## 9. ماذا نسلّم خلال ٢٤ ساعة + +- حسب الاتفاق: عينة فرص + مسودات + تقرير مخاطر — **بعد موافقة داخلية** على الصياغة. + +## 10. قالب Proof Pack + +- `GET /api/v1/revenue-launch/proof-pack/template`. + +## 11. الدفع اليدوي (Moyasar) + +- `GET /api/v1/revenue-launch/payment/manual-flow` — تعليمات لوحة التحكم، قالب رسالة، قائمة تأكيد بعد الدفع. +- **تذكير:** المبالغ غالباً بالهللات (١ ريال = ١٠٠ هللة) — راجع وثائق Moyasar. + +## 12. ما لا نفعله اليوم + +- لا charge من كود Dealix، لا إرسال Gmail/واتساب/تقويم حي، لا scraping LinkedIn، لا وعود بنتائج مضمونة. + +## روابط تقنية + +| الغرض | المسار | +|--------|--------| +| حزمة العروض | `GET /api/v1/revenue-launch/offer` | +| ديمو + إغلاق | `GET /api/v1/revenue-launch/demo-flow` | +| مخطط pipeline | `GET /api/v1/revenue-launch/pipeline/schema` | +| تسليم Pilot | `GET /api/v1/revenue-launch/pilot-delivery` | +| دفع يدوي | `GET /api/v1/revenue-launch/payment/manual-flow` | +| Proof template | `GET /api/v1/revenue-launch/proof-pack/template` | diff --git a/dealix/docs/REVENUE_WORK_UNITS.md b/dealix/docs/REVENUE_WORK_UNITS.md new file mode 100644 index 00000000..213883a9 --- /dev/null +++ b/dealix/docs/REVENUE_WORK_UNITS.md @@ -0,0 +1,16 @@ +# Revenue Work Units (RWU) + +وحدة عمل إيراد = أي إنجاز قابل للقياس (فرصة، مسودة، موافقة، اجتماع، Proof، …). + +## التعريف البرمجي + +- [`revenue_work_units.py`](../auto_client_acquisition/revenue_company_os/revenue_work_units.py) +- `GET /api/v1/revenue-os/company-os/work-units/demo` — قائمة تجريبية. + +## الأنواع + +انظر `RWU_TYPES` في الملف: `opportunity_created`, `draft_created`, `approval_collected`, … + +## الربط لاحقاً + +تسجيل فعلي في دفتر أحداث أو جدول — انظر [`PROOF_LEDGER_WEEKLY_RUNBOOK.md`](ops/PROOF_LEDGER_WEEKLY_RUNBOOK.md). diff --git a/dealix/docs/SAFE_TOOL_GATEWAY_POLICY.md b/dealix/docs/SAFE_TOOL_GATEWAY_POLICY.md new file mode 100644 index 00000000..2d896f11 --- /dev/null +++ b/dealix/docs/SAFE_TOOL_GATEWAY_POLICY.md @@ -0,0 +1,29 @@ +# Safe Tool Gateway — سياسة Dealix + +كل إجراء خارجي يمر: **نية → سياسة → موافقة (عند الحاجة) → تنفيذ/تصدير → تدقيق**. + +## أوضاع الأداة (MVP) + +| وضع | معنى | +|-----|--------| +| suggest_only | اقتراح نصي فقط | +| draft_only | مسودة دون إرسال | +| approval_required | لا تنفيذ بدون موافقة بشرية | +| approved_execute | مسموح بعد موافقة صريحة (نادر في البيتا) | +| blocked | ممنوع | + +## المصفوفة البرمجية + +- [`tool_action_planner.py`](../auto_client_acquisition/autonomous_service_operator/tool_action_planner.py) +- `GET /api/v1/operator/tools/matrix` + +## قواعد صارمة (البيتا) + +- لا **Gmail send** تلقائي من المنصة. +- لا **LinkedIn scraping** ولا **auto-DM**. +- لا **واتساب بارد** غير موافق. +- لا **Moyasar charge** من API — روابط/فواتير يدوية حسب [`REVENUE_TODAY_PLAYBOOK.md`](REVENUE_TODAY_PLAYBOOK.md). + +## مراجع + +- [`PRIVATE_BETA_RUNBOOK.md`](PRIVATE_BETA_RUNBOOK.md)، [`AGENT_SECURITY_CURATOR.md`](AGENT_SECURITY_CURATOR.md). diff --git a/dealix/docs/SELF_IMPROVING_REVENUE_LOOP.md b/dealix/docs/SELF_IMPROVING_REVENUE_LOOP.md new file mode 100644 index 00000000..d090b210 --- /dev/null +++ b/dealix/docs/SELF_IMPROVING_REVENUE_LOOP.md @@ -0,0 +1,17 @@ +# Self-Improving Revenue Loop + +حلقة أسبوعية: مراجعة رسائل، موافقات، ردود، مخاطر، خدمات — ثم توصيات وbacklog. + +## التنفيذ الحالي (demo) + +- [`self_improvement_loop.py`](../auto_client_acquisition/revenue_company_os/self_improvement_loop.py) +- `GET /api/v1/revenue-os/company-os/self-improvement/weekly-report` + +## التشغيل اليدوي الأسبوعي + +- سكربت: [`fetch_proof_ledger_weekly.py`](../scripts/fetch_proof_ledger_weekly.py) +- Runbook: [`ops/PROOF_LEDGER_WEEKLY_RUNBOOK.md`](ops/PROOF_LEDGER_WEEKLY_RUNBOOK.md) + +## لاحقاً + +ربط ببيانات عميل حقيقية و`growth_curator` — دون تغيير العقود العامة للـ API دون إصدار. diff --git a/dealix/docs/SERVICE_BUNDLES.md b/dealix/docs/SERVICE_BUNDLES.md new file mode 100644 index 00000000..0c07a7cd --- /dev/null +++ b/dealix/docs/SERVICE_BUNDLES.md @@ -0,0 +1,21 @@ +# Service Bundles + +باقات منتَجة تربط عدة `service_id` من [Service Tower](SERVICE_TOWER_STRATEGY.md) بسعر وزمن وProof. + +## التعريف البرمجي + +- [`auto_client_acquisition/autonomous_service_operator/service_bundles.py`](../auto_client_acquisition/autonomous_service_operator/service_bundles.py) +- `GET /api/v1/operator/bundles` — قائمة JSON للعرض. + +## الباقات + +| bundle_id | فكرة | نطاق سعر تقريبي (ريال) | +|-----------|--------|-------------------------| +| growth_starter | تشخيص + ١٠ فرص | ٤٩٩ | +| data_to_revenue | ذكاء قوائم + فرص | ١٥٠٠–٢٥٠٠ | +| executive_growth_os | موجز تنفيذي + Growth OS | من ٢٩٩٩ | +| partnership_growth | شراكات + اجتماعات | ٣٠٠٠–٧٥٠٠ | +| local_growth_os | نمو محلي | ٩٩٩–٢٩٩٩ | +| full_growth_control_tower | مخصص ٩٠ يوماً | ١٥٠٠٠+ | + +الأرقام توضيحية للعرض؛ العقد يُحدّد بعد الـ pilot. diff --git a/dealix/docs/SERVICE_EXCELLENCE_OS.md b/dealix/docs/SERVICE_EXCELLENCE_OS.md new file mode 100644 index 00000000..d981fd8d --- /dev/null +++ b/dealix/docs/SERVICE_EXCELLENCE_OS.md @@ -0,0 +1,44 @@ +# Service Excellence OS — مصنع جودة الخدمات + +## الرؤية + +طبقة فوق **Service Tower** تضمن أن كل خدمة لها: مصفوفة ميزات، درجة جاهزية، مسار موافقة، مقاييس Proof، حزمة إطلاق، وفجوات مقابل الفئات التنافسية — **كلها deterministic** في الـ MVP (بدون استدعاء ويب). + +## المكوّنات (`auto_client_acquisition/service_excellence/`) + +| الملف | الدور | +|-------|--------| +| `feature_matrix.py` | must_have / advanced / premium | +| `service_scoring.py` | درجة 0–100 وحالة `launch_ready` / `beta_only` / `needs_work` | +| `workflow_builder.py` | التحقق من وجود خطوة موافقة | +| `research_lab.py` | فرضيات وتجارب مقترحة (نص ثابت) | +| `competitor_gap.py` | نقاط قوة المنافس + مزايا Dealix + `do_not_copy` | +| `proof_metrics.py` | مقاييس إلزامية لكل خدمة | +| `quality_review.py` | فحص قبل الإطلاق + `review_all_services` | +| `service_improvement_backlog.py` | عناصر تحسين أسبوعية | +| `launch_package.py` | مخطط صفحة + سكربت مبيعات + onboarding | + +## مسارات API + +بادئة: `/api/v1/service-excellence` + +- `GET /review/all` — فحص جميع خدمات البرج. +- `GET /{service_id}/feature-matrix` — المصفوفة + التصنيف. +- `GET /{service_id}/score` — الدرجة والحالة. +- `GET /{service_id}/workflow` — مسار + تحقق + خطة أيام + موافقات. +- `GET /{service_id}/proof-metrics` — المقاييس والقالب. +- `GET /{service_id}/gap-analysis` — فجوات تنافسية. +- `GET /{service_id}/launch-package` — حزمة إطلاق كاملة. +- `GET /{service_id}/backlog` — تحسينات مقترحة. +- `GET /{service_id}/research-brief` — موجز بحث داخلي. +- `GET /{service_id}/review` — فحص خدمة واحدة. + +## منع الخدمات الضعيفة + +- درجة أقل من ٨٠ → `beta_only` (تشجيع تحسين قبل التسويق الواسع). +- خدمة عالية المخاطر + درجة غير كافية → `needs_work`. +- سياسات غير آمنة (`auto_send`) تُكتشف عبر `block_if_unsafe_channel` (لا تظهر في كتالوج البرج الحالي). + +## التكامل + +يُستهلك مع **Service Tower** و**Targeting OS** و**Platform Services**؛ لا يكرر التنفيذ الخارجي — يقيّم ويُصدِر خططاً ووثائق عرض. diff --git a/dealix/docs/SERVICE_TOWER_STRATEGY.md b/dealix/docs/SERVICE_TOWER_STRATEGY.md new file mode 100644 index 00000000..b1a27f8b --- /dev/null +++ b/dealix/docs/SERVICE_TOWER_STRATEGY.md @@ -0,0 +1,39 @@ +# Service Tower — استراتيجية الخدمات القابلة للبيع + +## الرؤية + +تحويل قدرات Dealix (Targeting، المنصة، الذكاء، Growth Operator) إلى **خدمات منتَجة** لها: مدخلات، مسار عمل، مخرجات، تسعير، سياسة موافقة، Proof Pack، ومسار ترقية — دون إرسال حي أو شحن تلقائي في مسارات الـ MVP. + +## العلاقة مع الكتالوجات الأخرى + +- **`platform_services.service_catalog`**: مكونات تقنية واشتراكات/خدمات طبقة المنصة. +- **`service_tower.service_catalog`**: تعريف **بيعي تشغيلي** (برج الخدمات) مع `pricing_range_sar` و`workflow_steps` و`upgrade_path`. +- **`targeting_os`**: استهداف آمن، قوائم، LinkedIn Lead Gen — يغذي توصيات الـ wizard. + +## مسارات API + +| المسار | الوظيفة | +|--------|---------| +| `GET /api/v1/services/catalog` | برج الخدمات + لقطة من كتالوج المنصة | +| `POST /api/v1/services/recommend` | توصية خدمة من نوع الشركة والهدف | +| `POST /api/v1/services/start` | بدء تشغيل منطقي (تحقق مدخلات فقط) | +| `GET /api/v1/services/{id}/workflow` | خطوات المسار | +| `POST /api/v1/services/{id}/quote` | تقدير SAR (غير ملزم) | +| `GET /api/v1/services/demo/dashboard` | بطاقات عرض داخلية | +| `GET /api/v1/services/ceo/daily-brief` | موجز عربي + أزرار ≤٣ | +| `POST /api/v1/services/approval-card` | بطاقة موافقة لخدمة/إجراء | + +التفاصيل في [`docs/architecture/API_CANONICAL_ALIASES.md`](architecture/API_CANONICAL_ALIASES.md). + +## القواعد + +- لا إرسال حي من هذه المسارات. +- واتساب بارد ممنوع افتراضياً (انظر `whatsapp_compliance_setup` و`targeting_os`). +- لا ضمان نتائج — التسعير والأثر **تقديرات عرض**. + +## ترتيب الإطلاق التجاري المقترح + +1. `free_growth_diagnostic` → جذب. +2. `list_intelligence` و`first_10_opportunities` → إثبات سريع. +3. `email_revenue_rescue` و`meeting_booking_sprint` → قيمة عالية بمسودات. +4. `growth_os` → اشتراك بعد Pilot. diff --git a/dealix/docs/STAGING_DEPLOYMENT.md b/dealix/docs/STAGING_DEPLOYMENT.md index e4e41f7f..8a31d685 100644 --- a/dealix/docs/STAGING_DEPLOYMENT.md +++ b/dealix/docs/STAGING_DEPLOYMENT.md @@ -28,6 +28,39 @@ uvicorn api.main:app --host 0.0.0.0 --port ${PORT:-8000} - مفاتيح LLM إن لزم للتجارب - **لا** تضع `MOYASAR_SECRET` أو أسرار في المتغيرات العامة للواجهة +### Growth Control Tower / قنوات (موصى به على staging) + +- `WHATSAPP_ALLOW_LIVE_SEND=false` — يبقى معطّلاً حتى اكتمال webhook وopt-in والمراجعة القانونية (انظر [`core/config/settings.py`](../core/config/settings.py)). +- `MOYASAR_MODE=sandbox` (أو اسم متغير معادل عندك) — **تسمية تشغيلية** للفريق؛ مسارات API الحالية تبقى مسودات/تحقق فقط بدون charge حي ما لم تُضف تكامل إنتاجي صراحةً. +- `SENTRY_DSN` — للتقاط أخطاء staging (اختبار عبر `GET /api/v1/admin/sentry-check` حيث يُسمح). +- مفاتيح **Langfuse** إن استخدمت المراقبة: لا تُسجَّل في الريبو؛ ضعها كأسرار Railway فقط. + +### بوابة جاهزية الإطلاق (GO / NO-GO) + +**CI على الاستضافة:** من GitHub Actions شغّل workflow **Dealix staging smoke** (يدوي) بعد ضبط السر `STAGING_BASE_URL` — يشغّل `smoke_staging.py` ثم `launch_readiness_check.py --base-url` ويتوقع **`PAID_BETA_READY`** عند نجاح كل الفحوص. تفاصيل الفروع: [`BRANCH_PROTECTION_AND_CI.md`](BRANCH_PROTECTION_AND_CI.md). + +بعد ضبط `STAGING_BASE_URL` (أو تمرير `--base-url`): + +```bash +python scripts/launch_readiness_check.py --base-url "https://YOUR-STAGING-URL" +``` + +توقّع **`VERDICT: PAID_BETA_READY`** وexit code `0` عندما تمر كل فحوصات الشبكة نفسها التي يمرّرها السكربت محلياً (`customer-ops`، `services/catalog`، `launch/private-beta/offer`، `security-curator/demo`، إلخ). للتحقق المحلي فقط بدون URL: `python scripts/launch_readiness_check.py` → **`GO_PRIVATE_BETA`**. + +### Smoke — مسارات إضافية للتحقق يدوياً + +بعد `GET /health` و`smoke_staging.py`، يُنصح بالتحقق من وجود: + +- `GET /api/v1/platform/service-catalog` +- `GET /api/v1/platform/inbox/feed` +- `GET /api/v1/platform/proof/overview` +- `GET /api/v1/intelligence/command-feed` (أو `/command-feed/demo`) +- `POST /api/v1/innovation/opportunities/ten-in-ten` أو alias `POST /api/v1/intelligence/missions/first-10-opportunities` +- `GET /api/v1/services/catalog`، `GET /api/v1/services/verticals`، `GET /api/v1/launch/go-no-go`، `GET /api/v1/launch/scorecard` +- `GET /api/v1/revenue-launch/offer`، `GET /api/v1/revenue-launch/payment/manual-flow` (عروض Pilot ودفع يدوي فقط — انظر [`REVENUE_TODAY_PLAYBOOK.md`](REVENUE_TODAY_PLAYBOOK.md)) + +خريطة كاملة للمرادفات: [`docs/architecture/API_CANONICAL_ALIASES.md`](architecture/API_CANONICAL_ALIASES.md). + ## Smoke بعد النشر من جهازك (مسارات GET الحرجة، بدون أسرار في الريبو): diff --git a/dealix/docs/SUPPORT_SLA.md b/dealix/docs/SUPPORT_SLA.md new file mode 100644 index 00000000..c04d664b --- /dev/null +++ b/dealix/docs/SUPPORT_SLA.md @@ -0,0 +1,14 @@ +# دعم Pilot — SLA مبدئي + +| الأولوية | الوصف | أول رد مستهدف | حل مستهدف | +|---------|--------|---------------|------------| +| P0 | أمان / إرسال خاطئ / توقف كامل | ٢ ساعات | ٨ ساعات | +| P1 | تعطل خدمة أساسية | ٤ ساعات | ٢٤ ساعة | +| P2 | تكامل أو Proof متأخر | ٢٤ ساعة | ٧٢ ساعة | +| P3 | سؤال أو تحسين | ٤٨ ساعة | ١٢٠ ساعة | + +**ملاحظة:** الأرقام أهداف تشغيلية للبيتا؛ تُرفع في العقد عند التوسع. + +**مرجع API:** `GET /api/v1/customer-ops/support/sla` + +**آخر تحديث:** 2026-05-01 diff --git a/dealix/docs/TARGETING_ACQUISITION_OS.md b/dealix/docs/TARGETING_ACQUISITION_OS.md new file mode 100644 index 00000000..8e829788 --- /dev/null +++ b/dealix/docs/TARGETING_ACQUISITION_OS.md @@ -0,0 +1,49 @@ +# Targeting & Acquisition OS — Dealix + +## الرؤية + +طبقة تستهدف **الحسابات (Accounts)** أولاً ثم **لجنة الشراء** ثم **قابلية التواصل**، وتخرج خطط outreach ومسودات فقط — بدون scraping وبدون إرسال حي بدون موافقة. تكمل [`PLATFORM_SERVICES_STRATEGY.md`](PLATFORM_SERVICES_STRATEGY.md) و[`INTELLIGENCE_LAYER_STRATEGY.md`](INTELLIGENCE_LAYER_STRATEGY.md). + +## الكود + +| مسار | ملف | +|------|-----| +| `auto_client_acquisition/targeting_os/` | سياسات مصدر، contactability، حسابات تجريبية، LinkedIn المتوافق، جدولة، سمعة، تقارير | + +## المسارات (`/api/v1/targeting/*`) + +- `POST /accounts/recommend` — شركات تجريبية مرتبة بالقطاع/المدينة +- `POST /buying-committee/map` — أدوار قرار مقترحة +- `POST /contacts/evaluate` — `safe` / `needs_review` / `blocked` +- `POST /uploaded-list/analyze` — يمر عبر [`contact_import_preview`](../auto_client_acquisition/platform_services/contact_import_preview.py) +- `POST /outreach/plan` — خطوات بحدود يومية (MVP) +- `GET /daily-autopilot/demo` — بطاقات يومية (≤3 أزرار في العرض) +- `GET /self-growth/demo` — أهداف تشغيل ذاتي لـ Dealix (مسودات فقط) +- `GET /reputation/status` — مثال مقاييس + `should_pause` +- `POST /linkedin/strategy` — Lead Gen أولاً + `do_not_do` +- `GET /services` — عروض خدمات قابلة للبيع +- `POST /free-diagnostic` — 3 فرص + عرض pilot +- `GET /contracts/templates` — مخططات عقود **ليست استشارة قانونية** +- `POST /trust-score` — جسر إلى `compute_trust_score` +- `POST /account-strategy` — استراتيجية مصدر لكل حساب + +## LinkedIn — المسموح والممنوع + +- **ممنوع:** scraping، auto-DM، auto-connect، أتمتة غير أصيلة (انظر سياسات LinkedIn العامة). +- **مسموح:** Lead Gen Forms، إعلانات، مهام بحث يدوية معتمدة، بيانات العميل وCRM. + +## WhatsApp + +- لا cold outbound افتراضياً؛ opt-in واضح؛ راجع [`PRIVATE_BETA_RUNBOOK.md`](PRIVATE_BETA_RUNBOOK.md). + +## PDPL والمصادر + +كل جهة تحتاج `source` وغرض معالجة؛ القوائم المشتراة أو المكشوفة **محظورة** في المصفوفة. التفاصيل التشغيلية في [`DATA_MAP.md`](DATA_MAP.md) و[`PRIVACY_PDPL_READINESS.md`](PRIVACY_PDPL_READINESS.md). + +## الخدمات القابلة للبيع + +انظر `GET /targeting/services` و[`service_catalog`](../auto_client_acquisition/platform_services/service_catalog.py) للمحاذاة مع كتالوج المنصة. + +## الاختبارات + +[`tests/test_targeting_os.py`](../tests/test_targeting_os.py) diff --git a/dealix/docs/architecture/API_CANONICAL_ALIASES.md b/dealix/docs/architecture/API_CANONICAL_ALIASES.md new file mode 100644 index 00000000..eb7f30dc --- /dev/null +++ b/dealix/docs/architecture/API_CANONICAL_ALIASES.md @@ -0,0 +1,73 @@ +# Growth Control Tower — مسارات canonical و aliases + +> مرجع مواءمة بين وثائق المنتج ومسارات الريبو الفعلية. **لا تُولَّد تلقائياً** — حدّثها عند إضافة alias جديد. + +## مبدأ + +- **Canonical**: المسار الذي يُنصح به في الكود الجديد والاختبارات. +- **Alias**: نفس السلوك أو لفّ رفيع حول منطق موجود (مثل `ten-in-ten` في `innovation`). + +| المفهوم في الوثائق | Canonical في الريبو | ملاحظة | +|--------------------|---------------------|---------| +| Growth Brain build | `POST /api/v1/intelligence/growth-profile` | نفس الـ handler؛ اسم «growth-brain/build» في الوثائق القديمة يُعامل كمرادف لفظي. | +| Command Feed (intel) | `GET /api/v1/intelligence/command-feed` | `GET .../command-feed/demo` **alias** — نفس الاستجابة. | +| Command Feed (innovation demo) | `GET /api/v1/innovation/command-feed/demo` | مصدر بطاقات الابتكار؛ شكل الحقول قد يختلف قليلاً عن طبقة الذكاء. | +| 10 فرص في 10 دقائق | `POST /api/v1/innovation/opportunities/ten-in-ten` | المنطق: `build_ten_opportunities`. | +| First 10 opportunities (alias منتجي) | `POST /api/v1/intelligence/missions/first-10-opportunities` | يستدعي نفس `build_ten_opportunities` دون تكرار المنطق. | +| Unified inbox من حدث | `POST /api/v1/platform/inbox/from-event` | | +| Inbox feed (عرض تجريبي) | `GET /api/v1/platform/inbox/feed` | قائمة بطاقات deterministic للعرض — لا إرسال. | +| Proof ledger demo (innovation) | `GET /api/v1/innovation/proof-ledger/demo` | أحداث تجريبية. | +| Proof pack (أعمال) | `GET /api/v1/business/proof-pack/demo` | أقسام ROI تجريبية. | +| Proof — نظرة موحّدة | `GET /api/v1/platform/proof/overview` | يلخص demo ledger + proof pack + إشارة للتقارير. | +| Gmail draft (payload) | `POST /api/v1/platform/integrations/gmail/draft` | لا OAuth في المسار. | +| Calendar draft | `POST /api/v1/platform/integrations/calendar/draft` | | +| Moyasar payment draft | `POST /api/v1/platform/integrations/moyasar/payment-draft` | هللات + مسودة رابط دفع شكلية. | +| Lead form ingest | `POST /api/v1/platform/ingest/lead-form` | مصدر `trusted_simulation` في MVP. | +| استيراد جهات (معاينة) | `POST /api/v1/platform/contacts/import-preview` | CSV/صفوف — لا إرسال. | +| Growth missions (اسم منتجي) | `GET /api/v1/growth-operator/missions` | Alias رفيع: نفس `list_growth_missions` + `canonical_route` → `GET /api/v1/innovation/growth-missions`. | +| Proof pack demo (اسم منتجي) | `GET /api/v1/growth-operator/proof-pack/demo` | Alias → `GET /api/v1/business/proof-pack/demo`. | +| Security curator | `GET /api/v1/security-curator/demo`، `POST /api/v1/security-curator/redact`، `POST /api/v1/security-curator/inspect-diff` | لا تطبيق patch تلقائياً — فحص وإرجاع قرار. | +| Growth curator | `GET /api/v1/growth-curator/report/demo`، `POST /api/v1/growth-curator/messages/grade` | تقدير رسائل وتقرير أسبوعي تجريبي. | +| Meeting intelligence | `POST /api/v1/meeting-intelligence/transcript/summarize`، `.../followup/draft`، `.../brief/pre-meeting` | نص فقط — بدون إدراج تقويم حي. | +| Model router | `GET /api/v1/model-router/tasks`، `POST /api/v1/model-router/route`، `GET /api/v1/model-router/providers` | تلميحات تكوين deterministic. | +| Connector catalog | `GET /api/v1/connectors/catalog` | بيانات ثابتة + `risk_level` — لا OAuth في الاستجابة. | +| Agent observability | `GET /api/v1/agent-observability/demo`، `POST .../eval/safety`، `POST .../eval/saudi-tone`، `POST .../trace/build` | أشكال تقييم/تتبع للربط بـ Langfuse لاحقاً. | +| Platform — ingest حدث | `POST /api/v1/platform/events/ingest` | حدث موحّد (مثلاً `trusted_simulation`). | +| Platform — موافقة (stub) | `POST /api/v1/platform/actions/approve` | MVP: استجابة شكلية — ربط دفتر قرارات لاحقاً. | +| Platform — خدمات (alias كتالوج) | `GET /api/v1/platform/services/catalog` | يعادل `GET /api/v1/platform/service-catalog`. | +| Platform — proof ledger demo | `GET /api/v1/platform/proof-ledger/demo` | يلف `build_demo_proof_ledger` للعرض. | +| Platform — هوية (تجريبي) | `GET /api/v1/platform/identity/resolve-demo` | عرض توضيحي فقط. | +| Targeting — توصية حسابات | `POST /api/v1/targeting/accounts/recommend` | بيانات demo deterministic. | +| Targeting — لجنة شراء | `POST /api/v1/targeting/buying-committee/map` | | +| Targeting — تقييم جهة | `POST /api/v1/targeting/contacts/evaluate` | سياسات مصدر + قنوات مقترحة. | +| Targeting — تحليل قائمة مرفوعة | `POST /api/v1/targeting/uploaded-list/analyze` | نفس منطق `platform/contacts/import-preview`. | +| Targeting — خطة تواصل | `POST /api/v1/targeting/outreach/plan` | مسودات وموافقة فقط. | +| Targeting — LinkedIn آمن | `POST /api/v1/targeting/linkedin/strategy` | Lead Gen أولاً؛ `do_not_do` صريحة. | +| Targeting — خدمات | `GET /api/v1/targeting/services` | | +| Targeting — تشخيص مجاني | `POST /api/v1/targeting/free-diagnostic` | | +| Targeting — قوالب عقود | `GET /api/v1/targeting/contracts/templates` | ليست استشارة قانونية — مراجعة بشرية. | +| Targeting — trust score | `POST /api/v1/targeting/trust-score` | جسر إلى منطق intelligence. | +| Intelligence — كتالوج المهمات | `GET /api/v1/intelligence/missions/catalog` | يضم metadata + رابط لـ innovation. | +| Intelligence — تفصيل مهمة | `GET /api/v1/intelligence/missions/{mission_id}` | | +| Intelligence — Action graph | `POST /api/v1/intelligence/action-graph/demo` | مخطط signal→proof (عرض). | +| Intelligence — ذاكرة قرارات | `GET /api/v1/intelligence/decision-memory/demo`، `POST .../decision-memory/record` | in-memory MVP. | +| Security — تنقية trace | `POST /api/v1/security-curator/trace/sanitize` | بيانات متداخلة آمنة للمراقبة. | +| Growth curator — جرد مهارات | `GET /api/v1/growth-curator/skills/demo` | | +| Growth curator — تنسيق مهمات | `GET /api/v1/growth-curator/missions/curate/demo` | | +| Platform — تقييم إجراء (alias) | `POST /api/v1/platform/actions/evaluate` | يعادل `POST /api/v1/platform/policy/evaluate`. | +| Platform — موافقة بشرية | `POST /api/v1/platform/actions/approve` | يسجّل في `action_ledger` — لا تنفيذ live. | +| أحداث منصة موسّعة | `email.received`, `payment.paid`, `review.created`, … | انظر `EventType` في `event_bus.py`. | +| Service Tower — كتالوج + توصية | `GET /api/v1/services/catalog`، `POST /api/v1/services/recommend`، `POST /api/v1/services/start` | [`SERVICE_TOWER_STRATEGY.md`](../SERVICE_TOWER_STRATEGY.md) | +| Service Tower — تشغيل عرض | `GET /api/v1/services/demo/dashboard`، `GET /api/v1/services/ceo/daily-brief`، `GET /api/v1/services/ceo/end-of-day`، `POST /api/v1/services/approval-card` | أزرار ≤٣ — لا live send | +| Service Tower — تفاصيل خدمة | `GET /api/v1/services/{service_id}/workflow`، `POST .../quote`، `GET .../intake-questions`، `POST .../validate`، `GET .../deliverables`، `GET .../upgrade` | | +| Service Tower — خرائط ثابتة | `GET /api/v1/services/verticals`، `GET /api/v1/services/upgrade-paths`، `GET /api/v1/services/contracts/templates` | ثلاثة أبواب + ترقيات + مسودات عقود (ليست استشارة قانونية) | +| Launch Ops — بيتا وديمو | `GET /api/v1/launch/private-beta/offer`، `GET /api/v1/launch/demo-script`، `GET /api/v1/launch/outreach/first-20`، `GET /api/v1/launch/go-no-go`، `POST /api/v1/launch/go-no-go`، `GET /api/v1/launch/scorecard` | [`PRIVATE_BETA_LAUNCH_TODAY.md`](../PRIVATE_BETA_LAUNCH_TODAY.md) | +| Revenue Today (عروض + تسليم + دفع يدوي) | `GET /api/v1/revenue-launch/offer` (معامل اختياري `lang=en` يضيف `title_en` بجانب `title_ar`)، `.../outreach/first-20`، `.../demo-flow`، `.../pipeline/schema`، `.../pilot-delivery`، `.../payment/manual-flow`، `.../proof-pack/template` | [`REVENUE_TODAY_PLAYBOOK.md`](../REVENUE_TODAY_PLAYBOOK.md) — لا charge من API داخل Dealix | +| Service Excellence OS | `GET /api/v1/service-excellence/review/all`، `GET /api/v1/service-excellence/{id}/feature-matrix`، `.../score`، `.../workflow`، `.../proof-metrics`، `.../gap-analysis`، `.../launch-package`، `.../backlog`، `.../research-brief`، `.../review` | [`SERVICE_EXCELLENCE_OS.md`](../SERVICE_EXCELLENCE_OS.md) | +| Autonomous Service Operator | `POST /api/v1/operator/chat/message`، `POST /api/v1/operator/chat/decision`، `GET /api/v1/operator/session/{id}`، `GET /api/v1/operator/cards/pending`، `POST /api/v1/operator/service/start`، `POST /api/v1/operator/service/continue`، `GET /api/v1/operator/proof-pack/demo`، `GET /api/v1/operator/whatsapp/daily-brief`، `GET /api/v1/operator/bundles`، `GET /api/v1/operator/tools/matrix`، `GET /api/v1/operator/upsell` | [`AUTONOMOUS_SERVICE_OPERATOR.md`](../AUTONOMOUS_SERVICE_OPERATOR.md) | +| Revenue Company OS | `GET /api/v1/revenue-os/company-os/command-feed/demo`، `POST /api/v1/revenue-os/company-os/events/ingest`، `GET .../work-units/demo`، `GET .../channel-health/demo`، `GET .../opportunity-factory/demo`، `GET .../action-graph/demo`، `GET .../self-improvement/weekly-report`، `GET .../proof-ledger/demo`، `GET .../services/snapshot` | [`AUTONOMOUS_REVENUE_COMPANY_OS.md`](../AUTONOMOUS_REVENUE_COMPANY_OS.md) | +| Customer Ops — تشغيل Pilot ودعم | `GET /api/v1/customer-ops/onboarding/checklist`، `GET /api/v1/customer-ops/support/sla`، `GET /api/v1/customer-ops/connectors/status`، `GET /api/v1/customer-ops/success/cadence`، `GET /api/v1/customer-ops/incidents/playbook`، `POST /api/v1/customer-ops/support/route` (JSON: `issue_ar`)، `GET /api/v1/customer-ops/incidents/classify?severity=P0` | [`ONBOARDING_RUNBOOK.md`](../ONBOARDING_RUNBOOK.md)، [`SUPPORT_SLA.md`](../SUPPORT_SLA.md) — لا إرسال live | + +## Staging smoke + +مسارات يُنصح بفحصها بعد النشر: انظر [`STAGING_DEPLOYMENT.md`](../STAGING_DEPLOYMENT.md) و`scripts/smoke_staging.py`. diff --git a/dealix/landing/agency-partner.html b/dealix/landing/agency-partner.html new file mode 100644 index 00000000..a88d30fd --- /dev/null +++ b/dealix/landing/agency-partner.html @@ -0,0 +1,17 @@ + + + + + + Dealix — شركاء وكالات + + + + +
+

برنامج شركاء الوكالات

+

Agency Partner Mode ضمن Autonomous Revenue Company OS: onboarding وكالة، عميل تجريبي، Proof مشترك، وإطار revenue share — كله Approval-first ومسودات حتى المراجعة القانونية.

+ راسلنا +
+ + diff --git a/dealix/landing/command-center-en.html b/dealix/landing/command-center-en.html new file mode 100644 index 00000000..c222df41 --- /dev/null +++ b/dealix/landing/command-center-en.html @@ -0,0 +1,64 @@ + + + + + + Dealix Revenue Command Center — API hub (EN) + + + + + + + +
+ Dealix — Revenue Command Center (EN) + + Full Arabic dashboard (static demo) + · API map + +
+
+

API hub

+

This page is a short English companion to the rich RTL demo in command-center.html. Wire a real dashboard to these endpoints (same origin or CORS-enabled staging).

+ +
+

Command center (revenue graph)

+
    +
  • GET /api/v1/command-center/snapshot?customer_id=demo — aggregated snapshot for in-product dashboards.
  • +
  • Additional routes: see api/routers/command_center.py (agents, proof pack, leaks, maturity, …).
  • +
+
+ +
+

v3 stack (autonomous revenue OS)

+
    +
  • GET /api/v1/v3/stack — layer names and recommended tools.
  • +
  • GET /api/v1/v3/command-center/snapshot — included in staging smoke scripts.
  • +
  • GET /api/v1/v3/agents — agent catalog.
  • +
+
+ +
+

Growth tower (platform + services)

+
    +
  • GET /api/v1/platform/inbox/feed
  • +
  • GET /api/v1/services/catalog
  • +
  • GET /api/v1/launch/go-no-go · GET /api/v1/revenue-launch/offer?lang=en
  • +
+
+ +

No live WhatsApp/Gmail/calendar sends from these demos unless explicitly enabled in production policy.

+
+ + diff --git a/dealix/landing/command-center.html b/dealix/landing/command-center.html index 1a6ececf..1f468bfd 100644 --- a/dealix/landing/command-center.html +++ b/dealix/landing/command-center.html @@ -188,6 +188,7 @@
14اجتماع محجوز
7.4×عائد على Dealix
← العودة للموقع + English diff --git a/dealix/landing/companies.html b/dealix/landing/companies.html new file mode 100644 index 00000000..a352248e --- /dev/null +++ b/dealix/landing/companies.html @@ -0,0 +1,49 @@ + + + + + + Dealix للشركات — نمو بموافقة وProof Pack + + + + + + + +
+

Dealix — الشركات

+

+ حوّل بياناتك وقنواتك إلى فرص ورسائل واجتماعات وProof Pack +

+

+ Dealix يكتشف الفرص، يكتب الرسائل بالعربي، ويطلب موافقتك قبل أي تواصل. Saudi Revenue Execution OS — مسودات أولاً، تقييم مخاطر، وإثبات أثر؛ لا وعود بنتائج مضمونة؛ لا واتساب بارد افتراضياً؛ لا scraping لـ LinkedIn. +

+ +
+
+
+

ماذا تستلم؟

+
    +
  • فرص مؤهّلة مع «لماذا الآن» وقناة مقترحة.
  • +
  • مسودات عربية للمراجعة — لا إرسال تلقائي افتراضي.
  • +
  • تقييم مخاطر التواصل (safe / يحتاج مراجعة / ممنوع).
  • +
  • Proof Pack يوثّق المسودات والموافقات والمخاطر الممنوعة حيث ينطبق.
  • +
+
+

انظر أيضاً: POSITIONING_LOCK.md

+
+ + diff --git a/dealix/landing/first-10-opportunities.html b/dealix/landing/first-10-opportunities.html new file mode 100644 index 00000000..d9d1b2ae --- /dev/null +++ b/dealix/landing/first-10-opportunities.html @@ -0,0 +1,17 @@ + + + + + + Dealix — ١٠ فرص + + + + +
+

سباق ١٠ فرص

+

لماذا الآن، قناة آمنة، مسودات عربية، موافقة قبل أي تواصل.

+

API: POST /api/v1/intelligence/missions/first-10-opportunities أو مسار الابتكار الموثّق في API_CANONICAL_ALIASES.

+
+ + diff --git a/dealix/landing/free-diagnostic.html b/dealix/landing/free-diagnostic.html new file mode 100644 index 00000000..c99b27d0 --- /dev/null +++ b/dealix/landing/free-diagnostic.html @@ -0,0 +1,17 @@ + + + + + + Dealix — تشخيص نمو مجاني + + + + +
+

تشخيص نمو مجاني

+

٣ فرص + رسالة + مخاطر + اقتراح Pilot — بدون التزام.

+ ابدأ +
+ + diff --git a/dealix/landing/growth-os.html b/dealix/landing/growth-os.html new file mode 100644 index 00000000..cccb9290 --- /dev/null +++ b/dealix/landing/growth-os.html @@ -0,0 +1,18 @@ + + + + + + Dealix — Growth OS + + + + +
+

Growth OS

+

Growth OS — طبقة تشغيل شهرية ضمن Dealix Company OS: موجز يومي، كروت قرار، موافقات، Proof Pack؛ قنوات متعددة مع Safe Tool Gateway (مسودات افتراضياً).

+

اشتراك تقريبي من ٢٩٩٩ ريال/شهر حسب النطاق؛ pilot أضيق يُحدَّد بعد الديمو.

+ عرض الخدمات +
+ + diff --git a/dealix/landing/index.html b/dealix/landing/index.html index 4a9279fe..7c86467b 100644 --- a/dealix/landing/index.html +++ b/dealix/landing/index.html @@ -87,6 +87,11 @@