feat(dealix): sync full Dealix package to repo

- API routers, ACA modules, integrations (draft operators)
- Docs, landing pages, scripts (launch readiness, scorecard)
- Tests and CI workflow updates for Dealix

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Sami Assiri 2026-05-01 21:01:17 +03:00
parent 4f02f54019
commit b13cb389cc
242 changed files with 11435 additions and 888 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}). الخطوة التالية: أكمل المدخلات ثم راجع المسودات قبل أي إرسال."

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 واتفاق العميل.",
}

View File

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

View File

@ -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": [
"تصنيف الخطورة (P0P3) وفق وصف الحادث.",
"إيقاف أي إجراء 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"}}

View File

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

View File

@ -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 عند التوسع.",
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
"""Draft-only integration helpers (no OAuth, no network) — Growth Control Tower."""

View File

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

View File

@ -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": "<urlsafe base64 RFC822>"}}`` 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.",
}

View File

@ -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}&currency={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؛ الرابط للعرض الشكلي فقط.",
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 ["صناع القرار المالي", "مدراء المشتريات", "العمليات"]

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,18 @@
"""Trust score 0100 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}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More