system-prompts-and-models-o.../dealix/api/routers/revenue_os.py
Sami Assiri b13cb389cc 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>
2026-05-01 21:01:17 +03:00

754 lines
29 KiB
Python

"""
Revenue OS Router — single integration point for the v3 Autonomous layers.
Endpoints under /api/v1/revenue-os/:
Memory: /events /timeline/{account_id} /replay/{customer_id}
Agents: /workflows/run /tasks /tasks/{id}/approve /tasks/{id}/reject
Market: /market-radar/signals /market-radar/sectors /market-radar/cities
/market-radar/opportunities
Copilot: /copilot/ask /copilot/intents /copilot/actions/{id}
Forecast: /forecast /attribution /impact /churn /expansion
Compliance: /contactability /campaign-risk /ropa /dsr /dsr/{id}/process
/vendors
Verticals: /verticals /verticals/{id} /verticals/{id}/templates
"""
from __future__ import annotations
import logging
from datetime import datetime, timedelta, timezone
from typing import Any
from fastapi import APIRouter, Body, HTTPException, Query
# Compliance OS
from auto_client_acquisition.compliance_os.consent_ledger import (
LawfulBasis,
record_consent,
record_opt_out,
)
from auto_client_acquisition.compliance_os.contactability import check_contactability
from auto_client_acquisition.compliance_os.data_subject_requests import (
DSR_TYPES,
DSRStatus,
dsr_dashboard,
open_dsr,
process_dsr,
)
from auto_client_acquisition.compliance_os.risk_engine import score_campaign_risk
from auto_client_acquisition.compliance_os.ropa import build_ropa
from auto_client_acquisition.compliance_os.vendor_registry import (
DEFAULT_VENDORS,
vendors_summary,
)
# Copilot
from auto_client_acquisition.copilot import ask
from auto_client_acquisition.copilot.intent_router import list_intents
from auto_client_acquisition.copilot.safe_actions import SAFE_ACTIONS, get_action
# Market Intelligence
from auto_client_acquisition.market_intelligence.opportunity_feed import (
build_opportunity_feed,
)
from auto_client_acquisition.market_intelligence.sector_pulse import build_sector_pulse
from auto_client_acquisition.market_intelligence.signal_detectors import (
SIGNAL_TYPES,
SignalDetection,
detect_ads_signal,
detect_funding_signal,
detect_hiring_signal,
detect_tender_signal,
detect_website_change,
)
# Orchestrator
from auto_client_acquisition.orchestrator.policies import (
AutonomyMode,
default_policy,
)
from auto_client_acquisition.orchestrator.queue import TaskQueue, TaskStatus
from auto_client_acquisition.orchestrator.runtime import DAILY_GROWTH_RUN, Orchestrator
from auto_client_acquisition.orchestrator.tools import default_executors
# Revenue Memory
from auto_client_acquisition.revenue_memory.event_store import (
InMemoryEventStore,
get_default_store,
)
from auto_client_acquisition.revenue_memory.events import (
EVENT_TYPES,
event_to_dict,
make_event,
)
from auto_client_acquisition.revenue_memory.replay import (
replay_for_account,
replay_for_customer,
)
from auto_client_acquisition.revenue_memory.retention import retention_summary
# Revenue Science
from auto_client_acquisition.revenue_science.attribution import (
compute_first_touch,
compute_last_touch,
compute_linear,
compute_time_decay,
)
from auto_client_acquisition.revenue_science.causal_impact import simulate_impact
from auto_client_acquisition.revenue_science.churn_model import predict_churn
from auto_client_acquisition.revenue_science.expansion_model import predict_expansion
from auto_client_acquisition.revenue_science.forecast import compute_forecast
# Vertical OS
from auto_client_acquisition.vertical_os import (
ALL_VERTICALS,
get_vertical,
list_vertical_summaries,
)
# Why-Now (used by opportunity_feed)
from auto_client_acquisition.revenue_graph.why_now import (
WhyNowSignal,
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__)
def _now() -> datetime:
return datetime.now(timezone.utc).replace(tzinfo=None)
# ── Module-level singletons (in-memory adapters; production replaces) ─
_QUEUE = TaskQueue()
_ORCHESTRATOR_FACTORY = None
def _get_orchestrator(customer_id: str) -> Orchestrator:
"""Build an orchestrator with the default in-memory store + policy."""
store = get_default_store()
def policy_resolver(c):
return default_policy(c)
return Orchestrator(
queue=_QUEUE,
event_store=store,
policy_resolver=policy_resolver,
executor_registry=default_executors(),
)
# ─────────────────────────────────────────────────────────────────
# 1. REVENUE MEMORY ENDPOINTS
# ─────────────────────────────────────────────────────────────────
@router.get("/events/types")
async def list_event_types() -> dict[str, Any]:
"""50 event types Dealix records."""
return {"count": len(EVENT_TYPES), "event_types": list(EVENT_TYPES)}
@router.post("/events")
async def append_event(
event_type: str = Body(..., embed=True),
customer_id: str = Body(..., embed=True),
subject_type: str = Body(..., embed=True),
subject_id: str = Body(..., embed=True),
payload: dict[str, Any] = Body(default_factory=dict, embed=True),
actor: str = Body(default="system", embed=True),
) -> dict[str, Any]:
"""Append a new event to the customer's stream."""
try:
e = make_event(
event_type=event_type,
customer_id=customer_id,
subject_type=subject_type,
subject_id=subject_id,
payload=payload,
actor=actor,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
get_default_store().append(e)
return {"event_id": e.event_id, "event_type": e.event_type}
@router.get("/timeline/{account_id}")
async def get_timeline(account_id: str, customer_id: str = Query(...)) -> dict[str, Any]:
"""Replay account timeline from the event stream."""
timeline = replay_for_account(customer_id=customer_id, account_id=account_id)
return timeline.to_dict()
@router.get("/replay/{customer_id}")
async def replay_customer_roi(
customer_id: str,
period_days: int = Query(default=30, ge=1, le=365),
) -> dict[str, Any]:
"""Compute ROI projection for the customer over the period."""
period_start = _now() - timedelta(days=period_days)
proj = replay_for_customer(customer_id=customer_id, period_start=period_start)
return {
"customer_id": proj.customer_id,
"period_days": period_days,
"n_leads": proj.n_leads,
"n_meetings": proj.n_meetings,
"n_proposals": proj.n_proposals,
"n_deals_won": proj.n_deals_won,
"revenue_won_sar": proj.revenue_won_sar,
"pipeline_added_sar": proj.pipeline_added_sar,
}
@router.get("/retention-summary")
async def get_retention_summary(customer_id: str = Query(...)) -> dict[str, Any]:
"""How many events per retention tier — for Trust Center display."""
events = list(get_default_store().read_for_customer(customer_id))
return retention_summary(events)
# ─────────────────────────────────────────────────────────────────
# 2. AGENT ORCHESTRATOR ENDPOINTS
# ─────────────────────────────────────────────────────────────────
@router.post("/workflows/run")
async def run_workflow(
workflow_id: str = Body(default="daily_growth_run", embed=True),
customer_id: str = Body(..., embed=True),
autonomy_mode: str = Body(default=AutonomyMode.DRAFT_APPROVE, embed=True),
) -> dict[str, Any]:
"""Trigger a workflow — Daily Growth Run by default."""
if workflow_id != "daily_growth_run":
raise HTTPException(status_code=404, detail=f"unknown workflow: {workflow_id}")
store = get_default_store()
def resolver(c):
p = default_policy(c)
p.autonomy_mode = autonomy_mode
return p
orch = Orchestrator(
queue=_QUEUE,
event_store=store,
policy_resolver=resolver,
executor_registry=default_executors(),
)
summary = orch.run_workflow(workflow=DAILY_GROWTH_RUN, customer_id=customer_id)
return summary
@router.get("/tasks")
async def list_tasks(
customer_id: str = Query(...),
status: str | None = Query(default=None),
) -> dict[str, Any]:
if status:
tasks = [t for t in _QUEUE.for_customer(customer_id) if t.status == status]
else:
tasks = _QUEUE.for_customer(customer_id)
return {
"summary": _QUEUE.summary(customer_id),
"tasks": [
{
"task_id": t.task_id,
"agent_id": t.agent_id,
"action_type": t.action_type,
"status": t.status,
"requires_approval": t.requires_approval,
"approval_reason": t.approval_reason,
"created_at": t.created_at.isoformat(),
}
for t in tasks
],
}
@router.post("/tasks/{task_id}/approve")
async def approve_task(task_id: str, approved_by: str = Body(..., embed=True)) -> dict[str, Any]:
orch = _get_orchestrator("any")
try:
task = orch.approve_and_execute(task_id=task_id, approved_by=approved_by)
except (KeyError, ValueError) as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {"task_id": task.task_id, "status": task.status}
@router.post("/tasks/{task_id}/reject")
async def reject_task(
task_id: str,
rejected_by: str = Body(..., embed=True),
reason: str = Body(default="", embed=True),
) -> dict[str, Any]:
orch = _get_orchestrator("any")
try:
task = orch.reject_task(task_id=task_id, rejected_by=rejected_by, reason=reason)
except (KeyError, ValueError) as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {"task_id": task.task_id, "status": task.status}
# ─────────────────────────────────────────────────────────────────
# 3. MARKET RADAR ENDPOINTS
# ─────────────────────────────────────────────────────────────────
@router.get("/market-radar/signal-types")
async def list_signal_types() -> dict[str, Any]:
return {"count": len(SIGNAL_TYPES), "signal_types": list(SIGNAL_TYPES)}
@router.post("/market-radar/detect/hiring")
async def detect_hiring(
company_id: str = Body(..., embed=True),
job_postings: list[dict[str, Any]] = Body(default_factory=list, embed=True),
) -> dict[str, Any]:
# Convert ISO strings to datetimes
parsed = []
for jp in job_postings:
posted = jp.get("posted_at")
if isinstance(posted, str):
try:
jp["posted_at"] = datetime.fromisoformat(posted.replace("Z", "+00:00")).replace(tzinfo=None)
except Exception:
continue
parsed.append(jp)
sigs = detect_hiring_signal(company_id=company_id, job_postings=parsed)
return {"signals": [_signal_to_dict(s) for s in sigs]}
@router.post("/market-radar/sectors/{sector}/pulse")
async def sector_pulse(
sector: str,
signals_this_week: list[dict[str, Any]] = Body(default_factory=list, embed=True),
signals_prior_week: list[dict[str, Any]] = Body(default_factory=list, embed=True),
) -> dict[str, Any]:
this_w = [_signal_from_dict(s) for s in signals_this_week]
prior_w = [_signal_from_dict(s) for s in signals_prior_week]
pulse = build_sector_pulse(
sector=sector, signals_this_week=this_w, signals_prior_week=prior_w
)
return pulse.to_dict()
@router.post("/market-radar/opportunities")
async def opportunities(
signals: list[dict[str, Any]] = Body(default_factory=list, embed=True),
company_metadata: dict[str, dict[str, Any]] = Body(default_factory=dict, embed=True),
sector_trends: dict[str, str] = Body(default_factory=dict, embed=True),
top_n: int = Body(default=20, embed=True),
) -> dict[str, Any]:
parsed_signals = [_signal_from_dict(s) for s in signals]
def explainer(*, company_id, signals, sector, sector_pulse_trend):
wn = [
WhyNowSignal(
signal_type=s.signal_type,
detected_at=s.detected_at,
source=s.source,
evidence_url=s.evidence_url,
payload=s.payload,
)
for s in signals
]
return explain_why_now(
company_id=company_id,
signals=wn,
sector=sector,
sector_pulse_trend=sector_pulse_trend,
)
feed = build_opportunity_feed(
signals=parsed_signals,
company_metadata=company_metadata,
why_now_explainer=explainer,
sector_trends=sector_trends,
top_n=top_n,
)
return {"count": len(feed), "opportunities": [o.to_dict() for o in feed]}
# ─────────────────────────────────────────────────────────────────
# 4. COPILOT ENDPOINTS
# ─────────────────────────────────────────────────────────────────
@router.post("/copilot/ask")
async def copilot_ask(
question_ar: str = Body(..., embed=True),
customer_id: str = Body(..., embed=True),
context: dict[str, Any] = Body(default_factory=dict, embed=True),
) -> dict[str, Any]:
return ask(question_ar=question_ar, customer_id=customer_id, context=context)
@router.get("/copilot/intents")
async def copilot_intents() -> dict[str, Any]:
return {"intents": list_intents()}
@router.get("/copilot/actions")
async def copilot_actions() -> dict[str, Any]:
return {"actions": [a.to_dict() for a in SAFE_ACTIONS]}
@router.get("/copilot/actions/{action_id}")
async def copilot_action_detail(action_id: str) -> dict[str, Any]:
a = get_action(action_id)
if a is None:
raise HTTPException(status_code=404, detail=f"unknown action: {action_id}")
return a.to_dict()
# ─────────────────────────────────────────────────────────────────
# 5. REVENUE SCIENCE ENDPOINTS
# ─────────────────────────────────────────────────────────────────
@router.post("/forecast")
async def forecast_endpoint(
customer_id: str = Body(..., embed=True),
open_deals: list[dict[str, Any]] = Body(default_factory=list, embed=True),
horizon_days: int = Body(default=30, embed=True),
) -> dict[str, Any]:
f = compute_forecast(customer_id=customer_id, open_deals=open_deals, horizon_days=horizon_days)
return {
"customer_id": f.customer_id,
"horizon_days": f.horizon_days,
"period_label": f.period_label,
"best": f.best.__dict__,
"likely": f.likely.__dict__,
"worst": f.worst.__dict__,
"deals_breakdown": f.deals_breakdown,
"risks_ar": f.risks_ar,
"decisions_required_ar": f.decisions_required_ar,
}
@router.post("/attribution")
async def attribution_endpoint(
deals: list[dict[str, Any]] = Body(default_factory=list, embed=True),
model: str = Body(default="time_decay", embed=True),
) -> dict[str, Any]:
if model == "first_touch":
r = compute_first_touch(deals=deals)
elif model == "last_touch":
r = compute_last_touch(deals=deals)
elif model == "linear":
r = compute_linear(deals=deals)
else:
r = compute_time_decay(deals=deals)
return {"model": r.model, "by_channel": r.by_channel, "total_revenue_sar": r.total_revenue_sar}
@router.post("/impact")
async def impact_endpoint(
current_baseline_revenue_sar: float = Body(..., embed=True),
response_time_reduction_hours: float = Body(default=0, embed=True),
extra_followup_touches: int = Body(default=0, embed=True),
shift_to_whatsapp_pct: float = Body(default=0, embed=True),
drop_n_sectors: int = Body(default=0, embed=True),
) -> dict[str, Any]:
out = simulate_impact(
current_baseline_revenue_sar=current_baseline_revenue_sar,
response_time_reduction_hours=response_time_reduction_hours,
extra_followup_touches=extra_followup_touches,
shift_to_whatsapp_pct=shift_to_whatsapp_pct,
drop_n_sectors=drop_n_sectors,
)
return out.__dict__
@router.post("/churn")
async def churn_endpoint(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
p = predict_churn(
customer_id=payload.get("customer_id", "unknown"),
days_since_last_login=int(payload.get("days_since_last_login", 0)),
monthly_engagement_drop_pct=float(payload.get("monthly_engagement_drop_pct", 0)),
support_tickets_open=int(payload.get("support_tickets_open", 0)),
billing_failures_last_90d=int(payload.get("billing_failures_last_90d", 0)),
nps=payload.get("nps"),
pipeline_added_drop_pct=float(payload.get("pipeline_added_drop_pct", 0)),
months_as_customer=int(payload.get("months_as_customer", 6)),
)
return p.__dict__
@router.post("/expansion")
async def expansion_endpoint(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
s = predict_expansion(
customer_id=payload.get("customer_id", "unknown"),
current_plan=payload.get("current_plan", "Growth"),
health_score=float(payload.get("health_score", 0)),
monthly_engagement_growth_pct=float(payload.get("monthly_engagement_growth_pct", 0)),
sectors_targeted=int(payload.get("sectors_targeted", 1)),
pct_of_quota_used=float(payload.get("pct_of_quota_used", 0)),
nps=payload.get("nps"),
pipeline_added_growth_pct=float(payload.get("pipeline_added_growth_pct", 0)),
)
return s.__dict__
# ─────────────────────────────────────────────────────────────────
# 6. COMPLIANCE OS ENDPOINTS
# ─────────────────────────────────────────────────────────────────
@router.post("/compliance/contactability")
async def contactability_endpoint(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
"""Check if a contact can be reached now. Records is a list of consent dicts."""
contact_id = payload["contact_id"]
records_dicts = payload.get("consent_records", [])
# Convert to ConsentRecord (lightweight inline)
from auto_client_acquisition.compliance_os.consent_ledger import ConsentRecord
records = []
for r in records_dicts:
oa = r.get("occurred_at")
if isinstance(oa, str):
try:
oa = datetime.fromisoformat(oa.replace("Z", "+00:00")).replace(tzinfo=None)
except Exception:
oa = _now()
records.append(ConsentRecord(
record_id=r.get("record_id", "x"),
customer_id=r.get("customer_id", ""),
contact_id=contact_id,
record_type=r.get("record_type", "consent_granted"),
lawful_basis=r.get("lawful_basis"),
purpose=r.get("purpose", ""),
channel=r.get("channel"),
source=r.get("source", "api"),
occurred_at=oa,
))
s = check_contactability(
contact_id=contact_id,
consent_records=records,
messages_sent_this_week=int(payload.get("messages_sent_this_week", 0)),
weekly_cap=int(payload.get("weekly_cap", 2)),
current_riyadh_hour=int(payload.get("current_riyadh_hour", 12)),
)
return s.to_dict()
@router.post("/compliance/campaign-risk")
async def campaign_risk_endpoint(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
r = score_campaign_risk(
target_count=int(payload.get("target_count", 0)),
contacts_with_consent=int(payload.get("contacts_with_consent", 0)),
contacts_opted_out=int(payload.get("contacts_opted_out", 0)),
contacts_no_lawful_basis=int(payload.get("contacts_no_lawful_basis", 0)),
template_body=payload.get("template_body", ""),
template_subject=payload.get("template_subject", ""),
channel=payload.get("channel", "email"),
has_unsubscribe_link=bool(payload.get("has_unsubscribe_link", True)),
in_quiet_hours=bool(payload.get("in_quiet_hours", False)),
)
return {
"risk_score": r.risk_score,
"risk_band": r.risk_band,
"issues": r.issues,
"blockers": r.blockers,
"contacts_safe": r.contacts_safe,
"contacts_blocked": r.contacts_blocked,
"contacts_needing_review": r.contacts_needing_review,
"recommended_fixes_ar": r.recommended_fixes_ar,
}
@router.get("/compliance/ropa")
async def get_ropa(
customer_id: str = Query(...),
customer_name: str = Query(default="Customer"),
dpo_email: str | None = Query(default=None),
) -> dict[str, Any]:
r = build_ropa(customer_id=customer_id, customer_name=customer_name, dpo_email=dpo_email)
return r.to_json()
@router.post("/compliance/dsr")
async def open_dsr_endpoint(
customer_id: str = Body(..., embed=True),
data_subject_id: str = Body(..., embed=True),
request_type: str = Body(..., embed=True),
) -> dict[str, Any]:
if request_type not in DSR_TYPES:
raise HTTPException(status_code=400, detail=f"unknown DSR type: {request_type}")
r = open_dsr(customer_id=customer_id, data_subject_id=data_subject_id, request_type=request_type)
return {
"request_id": r.request_id,
"request_type": r.request_type,
"status": r.status,
"received_at": r.received_at.isoformat(),
"sla_due_at": r.sla_due_at.isoformat(),
}
@router.get("/compliance/vendors")
async def list_vendors() -> dict[str, Any]:
return {
"summary": vendors_summary(),
"vendors": [
{
"vendor_id": v.vendor_id, "name": v.name, "purpose_ar": v.purpose_ar,
"data_accessed": v.data_accessed, "region": v.region,
"has_dpa_signed": v.has_dpa_signed, "iso27001": v.iso27001,
"soc2": v.soc2, "risk_tier": v.risk_tier, "status": v.status,
}
for v in DEFAULT_VENDORS
],
}
# ─────────────────────────────────────────────────────────────────
# 7. VERTICAL OS ENDPOINTS
# ─────────────────────────────────────────────────────────────────
@router.get("/verticals")
async def list_verticals() -> dict[str, Any]:
return {"summaries": list_vertical_summaries()}
@router.get("/verticals/{vertical_id}")
async def get_vertical_detail(vertical_id: str) -> dict[str, Any]:
v = get_vertical(vertical_id)
if v is None:
raise HTTPException(status_code=404, detail=f"unknown vertical: {vertical_id}")
return {
"vertical_id": v.vertical_id,
"sector_ar": v.sector_ar,
"sector_en": v.sector_en,
"icp_company_size": list(v.icp_company_size),
"icp_cities": list(v.icp_cities),
"icp_keywords": list(v.icp_keywords),
"pain_points_ar": list(v.pain_points_ar),
"top_objection_ids": list(v.top_objection_ids),
"priority_signals": list(v.priority_signals),
"dashboard_kpis": [
{"metric_id": k.metric_id, "name_ar": k.name_ar, "description_ar": k.description_ar,
"unit": k.unit, "higher_is_better": k.higher_is_better,
"target_p50": k.target_p50, "target_p90": k.target_p90}
for k in v.dashboard_kpis
],
"n_message_templates": len(v.message_templates),
"avg_deal_value_sar": v.avg_deal_value_sar,
"avg_cycle_days": v.avg_cycle_days,
"benchmark_reply_rate": v.benchmark_reply_rate,
"benchmark_meeting_rate": v.benchmark_meeting_rate,
"benchmark_win_rate": v.benchmark_win_rate,
"compliance_notes_ar": list(v.compliance_notes_ar),
"recommended_channel_mix": v.recommended_channel_mix,
}
@router.get("/verticals/{vertical_id}/templates")
async def get_vertical_templates(vertical_id: str) -> dict[str, Any]:
v = get_vertical(vertical_id)
if v is None:
raise HTTPException(status_code=404, detail=f"unknown vertical: {vertical_id}")
return {
"vertical_id": vertical_id,
"templates": [
{
"template_id": t.template_id,
"channel": t.channel,
"purpose": t.purpose,
"subject_ar": t.subject_ar,
"body_ar": t.body_ar,
"variables": list(t.variables),
"expected_reply_rate": t.expected_reply_rate,
}
for t in v.message_templates
],
"proposal_template_ar": v.proposal_template_ar,
"qbr_section_template_ar": v.qbr_section_template_ar,
}
# ── 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
# ─────────────────────────────────────────────────────────────────
def _signal_to_dict(s: SignalDetection) -> dict[str, Any]:
return {
"company_id": s.company_id,
"signal_type": s.signal_type,
"detected_at": s.detected_at.isoformat(),
"source": s.source,
"confidence": s.confidence,
"evidence_url": s.evidence_url,
"payload": s.payload,
}
def _signal_from_dict(d: dict[str, Any]) -> SignalDetection:
detected = d.get("detected_at")
if isinstance(detected, str):
try:
detected = datetime.fromisoformat(detected.replace("Z", "+00:00")).replace(tzinfo=None)
except Exception:
detected = _now()
return SignalDetection(
company_id=d["company_id"],
signal_type=d["signal_type"],
detected_at=detected or _now(),
source=d.get("source", "api"),
confidence=float(d.get("confidence", 0.5)),
evidence_url=d.get("evidence_url"),
payload=d.get("payload", {}),
)