feat: Ipad app production readiness, Colony orchestration, Social posting (#44)
All checks were successful
Production Readiness / backend-contracts (push) Successful in 1m47s
Production Readiness / webos-typecheck (push) Successful in 1m50s
Production Readiness / ipad-parse (push) Successful in 1m34s

#38 Ipad app production readiness, Colony orchestration, Social posting

Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local>
Reviewed-on: #44
This commit was merged in pull request #44.
This commit is contained in:
2026-05-03 18:30:38 +05:30
parent 59d398abc3
commit eeb684b46c
86 changed files with 20349 additions and 1655 deletions

View File

@@ -24,12 +24,15 @@ import logging
import os
import uuid
from datetime import datetime, timezone
from typing import Any, Set
from typing import Any, Literal, Set
import httpx
from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket, WebSocketDisconnect, status
from pydantic import BaseModel, Field
from backend.auth.dependencies import UserPrincipal, get_current_user
from backend.services.colony_gateway import ColonyConfigurationError, ColonyGateway, ColonyGatewayError
from backend.services.colony_repository import ColonyRepository
from .canvas_service import canvas_service
from .collaboration_service import collaboration_service
from .action_service import oracle_action_service
@@ -142,6 +145,263 @@ async def _resolve_page_id(request: Request, user: UserPrincipal, page_id: str)
return str(me["defaultPageId"])
def _oracle_prompt_complexity(prompt: str, conversation_context: list[dict[str, str]] | None = None) -> tuple[str, list[str]]:
text = prompt.lower()
reasons: list[str] = []
complex_markers = (
"multi-round",
"multi round",
"coordinate",
"orchestrate",
"compare",
"writeback",
"write back",
"approve",
"crm",
"catalyst",
"campaign",
"social",
"inventory",
"next best action",
"next-best action",
"follow-up plan",
"strategy",
"across",
)
matched = [marker for marker in complex_markers if marker in text]
if matched:
reasons.append(f"complex markers: {', '.join(matched[:5])}")
if len(prompt) > 700:
reasons.append("long prompt")
if conversation_context and len(conversation_context) >= 4:
reasons.append("multi-turn context")
return ("thinking" if reasons else "fast", reasons)
def _next_canvas_order(components: list[dict[str, Any]]) -> int:
highest = 0
for component in components:
try:
highest = max(highest, int((component.get("layout") or {}).get("orderIndex", 0)))
except (TypeError, ValueError):
continue
return ((highest // 100) + 1) * 100
def _build_colony_status_component(
*,
execution_id: str,
mission_id: str,
prompt: str,
actor_id: str,
branch_id: str,
mode: str,
reasons: list[str],
order_index: int,
) -> dict[str, Any]:
reason_text = "; ".join(reasons) if reasons else "operator selected thinking mode"
return {
"componentId": str(uuid.uuid4()),
"type": "textCanvas",
"title": "Colony Mission Dispatched",
"description": "Oracle routed a complex request to Colony orchestration.",
"dataSourceDescriptor": {
"descriptorId": str(uuid.uuid4()),
"sourceType": "api",
"connectorId": "velocity-colony",
"dataset": "colony_mission",
"authContextRef": f"authctx_{actor_id}_scope",
"queryTemplate": f"/api/colony/missions/{mission_id}",
"queryParameters": {"missionId": mission_id},
"rowLimit": 1,
"privacyTier": "standard",
"cachePolicy": {"mode": "none"},
},
"visualizationParameters": {
"content": (
f"Oracle routed this request to Colony because it needs deeper orchestration.\n\n"
f"Mission ID: {mission_id}\n"
f"Mode: {mode}\n"
f"Routing reason: {reason_text}\n\n"
f"Original request: {prompt}\n\n"
"Colony will coordinate specialist workers and return artifacts/writeback proposals for review."
),
"widthMode": "full",
"adjustableHeight": True,
},
"dataBindings": {"dimensions": [], "measures": [], "series": [], "filters": []},
"version": 1,
"lifecycleState": "active",
"provenance": {
"originType": "prompt_generated",
"promptExecutionId": execution_id,
"sourceBranchId": branch_id,
"createdBy": actor_id,
"createdAt": _now(),
"colonyMissionId": mission_id,
},
"renderingHints": {"estimatedHeightPx": 220, "skeletonVariant": "text", "virtualizationPriority": 5},
"layout": {
"orderIndex": order_index,
"sectionId": f"sec_colony_{execution_id.replace('-', '')[:12]}",
"widthMode": "full",
"minHeightPx": 220,
"stickyHeader": False,
},
"accessControls": {
"visibilityScope": "private",
"allowedRoles": ["senior_broker", "sales_director", "marketing_operator", "data_steward", "compliance_reviewer", "platform_admin"],
"redactionPolicy": "none",
},
"styleSignature": {
"theme": "velocity_glass",
"paletteToken": "ocean_signal",
"motionProfile": "calm_reveal",
"density": "comfortable",
"radiusScale": "lg",
"typographyScale": "balanced",
},
"validationState": {
"schema": "pass",
"policy": "pass",
"a11y": "pass",
"performance": "pass",
"status": "validated",
},
"auditLog": [f"aud_{execution_id}_colony_dispatch"],
"dataRows": [{"missionId": mission_id, "mode": mode, "routingReason": reason_text}],
}
async def _dispatch_colony_from_oracle_prompt(
*,
request: Request,
ctx: PolicyContext,
page_id: str,
payload: PromptSubmitRequest,
resolved_mode: str,
routing_reasons: list[str],
) -> dict[str, Any]:
pool = getattr(request.app.state, "db_pool", None)
if pool is None:
raise HTTPException(status_code=503, detail="Database unavailable.")
try:
gateway = ColonyGateway()
except ColonyConfigurationError as exc:
raise HTTPException(status_code=503, detail=str(exc)) from exc
repo = ColonyRepository(pool)
mission_id = str(uuid.uuid4())
execution_id = str(uuid.uuid4())
mission = {
"mission_id": mission_id,
"mission_type": "oracle_advisory",
"origin_surface": "oracle_canvas",
"tenant_id": ctx.tenant_id,
"actor_id": ctx.actor_id,
"actor_role": ctx.actor_role,
"risk_level": "medium",
"sensitivity_class": "internal",
"time_budget_ms": 120000,
"token_budget": 24000,
"user_goal": payload.prompt,
"normalized_goal": payload.prompt,
"context_refs": {
"page_id": page_id,
"branch_id": payload.branchId,
"client_request_id": payload.clientRequestId,
"execution_mode": payload.executionMode,
"resolved_mode": resolved_mode,
"target_lead_id": payload.targetLeadId,
"conversation_context": payload.conversationContext,
},
"requested_outputs": ["canvas_artifacts", "summary", "writeback_proposals"],
"payload": {
"planned_writeback": payload.plannedWriteback,
"placement_mode": payload.placementMode,
"routing_reasons": routing_reasons,
},
}
row = await repo.create_mission(mission)
await repo.log_event(
mission_id=mission_id,
tenant_id=ctx.tenant_id,
event_type="mission_created_from_oracle_canvas",
actor=ctx.actor_id,
detail={"page_id": page_id, "execution_id": execution_id, "resolved_mode": resolved_mode},
)
try:
dispatch = await gateway.dispatch_mission(mission)
except (ColonyGatewayError, httpx.HTTPError) as exc:
await repo.update_status(mission_id, ctx.tenant_id, "dispatch_failed")
await repo.log_event(
mission_id=mission_id,
tenant_id=ctx.tenant_id,
event_type="mission_dispatch_failed",
actor=ctx.actor_id,
detail={"error": str(exc)},
)
raise HTTPException(
status_code=502,
detail={"message": str(exc), "mission_id": str(row["mission_id"])},
) from exc
queued = await repo.update_status(mission_id, ctx.tenant_id, "queued")
await repo.log_event(
mission_id=mission_id,
tenant_id=ctx.tenant_id,
event_type="mission_dispatched",
actor=ctx.actor_id,
detail={"dispatch": dispatch},
)
page = await canvas_service.get_page(page_id, ctx.tenant_id)
existing_components = page.get("components", []) if page else []
component = _build_colony_status_component(
execution_id=execution_id,
mission_id=mission_id,
prompt=payload.prompt,
actor_id=ctx.actor_id,
branch_id=payload.branchId,
mode=resolved_mode,
reasons=routing_reasons,
order_index=_next_canvas_order(existing_components),
)
revision = await canvas_service.commit_revision(
page_id=page_id,
tenant_id=ctx.tenant_id,
actor_id=ctx.actor_id,
commit_kind="prompt",
commit_summary=f"Colony: {payload.prompt[:80]}",
components=existing_components + [component],
execution_id=execution_id,
idempotency_key=payload.clientRequestId,
)
execution = {
"executionId": execution_id,
"tenantId": ctx.tenant_id,
"pageId": page_id,
"branchId": payload.branchId,
"actorId": ctx.actor_id,
"prompt": payload.prompt,
"intentClass": "mixed",
"status": "executing",
"modelRuntime": "colony_orchestrator",
"semanticModelVersion": "oracle_colony_router_v2026_05_03",
"retrievalPlan": {"route": "colony", "missionId": mission_id, "dispatch": dispatch},
"visualizationPlan": {"components": [component]},
"warnings": [],
"summary": f"Colony mission {mission_id} queued for multi-agent orchestration.",
"componentsCreated": [component["componentId"]],
"clientRequestId": payload.clientRequestId,
"createdAt": _now(),
"completedAt": None,
"workflowDispatch": {"type": "colony_mission", "missionId": mission_id, "status": (queued or row)["status"]},
}
await prompt_orchestrator._persist_execution(execution)
return {"execution": execution, "page": await canvas_service.get_page(page_id, ctx.tenant_id)}
# ── Pydantic Models ───────────────────────────────────────────────────────────
class PromptSubmitRequest(BaseModel):
@@ -150,6 +410,7 @@ class PromptSubmitRequest(BaseModel):
prompt: str = Field(..., min_length=1, max_length=4096)
conversationContext: list[dict[str, str]] = Field(default_factory=list)
placementMode: str = Field("append_after_last_visible_component")
executionMode: Literal["auto", "fast", "thinking"] = "auto"
targetLeadId: str | None = None
plannedWriteback: dict[str, Any] = Field(default_factory=dict)
@@ -202,6 +463,215 @@ class PageUpdateRequest(BaseModel):
# ── Endpoints ─────────────────────────────────────────────────────────────────
@router.get("/mobile/team-performance", summary="Mobile Oracle team performance intelligence")
async def mobile_team_performance(
request: Request,
limit: int = 12,
user: UserPrincipal = Depends(get_current_user),
) -> dict:
pool = getattr(request.app.state, "db_pool", None)
if pool is None:
raise HTTPException(status_code=503, detail="Database unavailable.")
tenant_id = user.tenant_id or _DEFAULT_TENANT_ID
safe_limit = max(1, min(limit, 50))
async with pool.acquire() as conn:
rows = await conn.fetch(
"""
WITH team AS (
SELECT id, full_name, email, avatar_url
FROM users_and_roles
WHERE COALESCE(is_active, TRUE) = TRUE
),
lead_rollup AS (
SELECT assigned_user_id,
COUNT(*)::int AS assigned_leads,
COUNT(*) FILTER (WHERE COALESCE(status::text, '') IN ('won', 'closed_won', 'booked'))::int AS won_leads
FROM crm_leads
WHERE tenant_id = $1
GROUP BY assigned_user_id
),
opportunity_rollup AS (
SELECT cl.assigned_user_id,
COUNT(co.opportunity_id)::int AS active_opportunities,
COALESCE(SUM(co.value) FILTER (WHERE COALESCE(co.stage::text, '') NOT IN ('closed_lost')), 0)::float AS pipeline_value,
COALESCE(SUM(co.value) FILTER (WHERE COALESCE(co.stage::text, '') IN ('closed_won', 'won')), 0)::float AS closed_won_value
FROM crm_opportunities co
INNER JOIN crm_leads cl ON cl.lead_id = co.lead_id
WHERE cl.tenant_id = $1
GROUP BY cl.assigned_user_id
),
task_rollup AS (
SELECT cl.assigned_user_id,
COUNT(ir.reminder_id) FILTER (WHERE COALESCE(ir.status, '') IN ('pending', 'open', 'scheduled', 'snoozed'))::int AS open_tasks,
COUNT(ir.reminder_id) FILTER (WHERE COALESCE(ir.status, '') = 'done')::int AS done_tasks
FROM intel_reminders ir
LEFT JOIN crm_leads cl ON cl.lead_id = ir.lead_id
WHERE COALESCE(ir.tenant_id, $1) = $1
GROUP BY cl.assigned_user_id
),
activity_rollup AS (
SELECT cl.assigned_user_id, MAX(ii.happened_at) AS last_activity_at
FROM intel_interactions ii
LEFT JOIN crm_leads cl ON cl.lead_id = ii.lead_id
WHERE COALESCE(ii.tenant_id, $1) = $1
GROUP BY cl.assigned_user_id
)
SELECT
t.id::text AS user_id,
COALESCE(t.full_name, t.email, t.id::text) AS name,
COALESCE(t.email, '') AS email,
t.avatar_url,
COALESCE(l.assigned_leads, 0)::int AS assigned_leads,
COALESCE(tr.open_tasks, 0)::int AS open_tasks,
COALESCE(tr.done_tasks, 0)::int AS done_tasks,
COALESCE(o.active_opportunities, 0)::int AS active_opportunities,
COALESCE(o.pipeline_value, 0)::float AS pipeline_value,
COALESCE(o.closed_won_value, 0)::float AS closed_won_value,
CASE WHEN COALESCE(l.assigned_leads, 0) = 0 THEN 0
ELSE ROUND((COALESCE(l.won_leads, 0)::numeric / NULLIF(l.assigned_leads, 0)) * 100, 1)::float
END AS conversion_rate,
a.last_activity_at
FROM team t
LEFT JOIN lead_rollup l ON l.assigned_user_id = t.id
LEFT JOIN opportunity_rollup o ON o.assigned_user_id = t.id
LEFT JOIN task_rollup tr ON tr.assigned_user_id = t.id
LEFT JOIN activity_rollup a ON a.assigned_user_id = t.id
WHERE COALESCE(l.assigned_leads, 0) > 0
OR COALESCE(o.active_opportunities, 0) > 0
OR COALESCE(tr.open_tasks, 0) > 0
ORDER BY COALESCE(o.closed_won_value, 0) DESC,
COALESCE(o.pipeline_value, 0) DESC,
COALESCE(l.assigned_leads, 0) DESC,
name ASC
LIMIT $2
""",
tenant_id,
safe_limit,
)
performers = [
{
"userId": row["user_id"],
"name": row["name"],
"email": row["email"],
"avatarUrl": row["avatar_url"],
"assignedLeads": row["assigned_leads"],
"openTasks": row["open_tasks"],
"doneTasks": row["done_tasks"],
"activeOpportunities": row["active_opportunities"],
"pipelineValue": row["pipeline_value"],
"closedWonValue": row["closed_won_value"],
"conversionRate": row["conversion_rate"],
"lastActivityAt": row["last_activity_at"].isoformat() if row["last_activity_at"] else None,
}
for row in rows
]
return _ok(
{
"summary": {
"teamMembers": len(performers),
"assignedLeads": sum(item["assignedLeads"] for item in performers),
"openTasks": sum(item["openTasks"] for item in performers),
"pipelineValue": sum(item["pipelineValue"] for item in performers),
"closedWonValue": sum(item["closedWonValue"] for item in performers),
},
"performers": performers,
}
)
@router.get("/mobile/lead-map", summary="Mobile Oracle lead geo-interest intelligence")
async def mobile_lead_map(
request: Request,
limit: int = 24,
user: UserPrincipal = Depends(get_current_user),
) -> dict:
pool = getattr(request.app.state, "db_pool", None)
if pool is None:
raise HTTPException(status_code=503, detail="Database unavailable.")
tenant_id = user.tenant_id or _DEFAULT_TENANT_ID
safe_limit = max(1, min(limit, 100))
async with pool.acquire() as conn:
has_rollup = await conn.fetchval("SELECT to_regclass('public.lead_geo_interest_rollup') IS NOT NULL")
if has_rollup:
rows = await conn.fetch(
"""
SELECT district AS label,
district AS district,
NULL::text AS city,
lat::float AS latitude,
lng::float AS longitude,
x::float AS x,
y::float AS y,
COALESCE(lead_count, 0)::int AS lead_count,
COALESCE(avg_qd_score, 0)::float AS avg_qd_score,
0::int AS hot_lead_count
FROM lead_geo_interest_rollup
WHERE tenant_id = $1
ORDER BY lead_count DESC, avg_qd_score DESC, district ASC
LIMIT $2
""",
tenant_id,
safe_limit,
)
else:
rows = await conn.fetch(
"""
SELECT
COALESCE(NULLIF(p.city, ''), 'Unknown') AS label,
COALESCE(NULLIF(p.city, ''), 'Unknown') AS city,
NULL::text AS district,
NULL::float AS latitude,
NULL::float AS longitude,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC, COALESCE(NULLIF(p.city, ''), 'Unknown'))::float AS x,
ROUND(AVG(COALESCE(q.current_value, 0.0))::numeric, 3)::float AS y,
COUNT(DISTINCT p.person_id)::int AS lead_count,
ROUND(AVG(COALESCE(q.current_value, 0.0))::numeric, 3)::float AS avg_qd_score,
COUNT(DISTINCT p.person_id) FILTER (WHERE COALESCE(q.current_value, 0.0) >= 0.70)::int AS hot_lead_count
FROM crm_people p
LEFT JOIN crm_leads cl ON cl.person_id = p.person_id AND cl.tenant_id = $1
LEFT JOIN LATERAL (
SELECT current_value
FROM intel_qd_scores q
WHERE q.person_id = p.person_id
ORDER BY q.computed_at DESC
LIMIT 1
) q ON TRUE
WHERE p.tenant_id = $1
GROUP BY COALESCE(NULLIF(p.city, ''), 'Unknown')
ORDER BY lead_count DESC, avg_qd_score DESC, label ASC
LIMIT $2
""",
tenant_id,
safe_limit,
)
points = [
{
"label": row["label"],
"city": row["city"],
"district": row["district"],
"latitude": row["latitude"],
"longitude": row["longitude"],
"x": row["x"],
"y": row["y"],
"leadCount": row["lead_count"],
"avgQdScore": row["avg_qd_score"],
"hotLeadCount": row["hot_lead_count"],
}
for row in rows
]
return _ok(
{
"summary": {
"locations": len(points),
"leadCount": sum(point["leadCount"] for point in points),
"hotLeadCount": sum(point["hotLeadCount"] for point in points),
},
"points": points,
},
meta={"source": "lead_geo_interest_rollup" if has_rollup else "crm_people"},
)
@router.get("/me", summary="Get current user profile")
async def get_me(request: Request, user: UserPrincipal = Depends(get_current_user)) -> dict:
return _ok(await _get_current_user_profile(request, user))
@@ -298,6 +768,50 @@ async def submit_prompt(
) -> dict:
page_id = await _resolve_page_id(request, user, page_id)
ctx = await _ctx_from_request(request, user)
complexity_route, routing_reasons = _oracle_prompt_complexity(payload.prompt, payload.conversationContext)
resolved_mode = payload.executionMode
if payload.executionMode == "auto":
resolved_mode = complexity_route
if resolved_mode == "thinking":
colony_result = await _dispatch_colony_from_oracle_prompt(
request=request,
ctx=ctx,
page_id=page_id,
payload=payload,
resolved_mode=resolved_mode,
routing_reasons=routing_reasons,
)
execution = colony_result["execution"]
page = colony_result["page"]
action = await oracle_action_service.create_from_execution(
execution=execution,
target_entity_type="lead" if payload.targetLeadId else "canvas_page",
target_entity_id=payload.targetLeadId or page_id,
action_type="oracle_colony_mission_dispatch",
writeback_payload={
**payload.plannedWriteback,
"colonyMissionId": execution["workflowDispatch"]["missionId"],
"executionMode": payload.executionMode,
"resolvedMode": resolved_mode,
},
)
return _ok({
"executionId": execution["executionId"],
"actionId": action["actionId"],
"status": execution["status"],
"executionMode": payload.executionMode,
"resolvedMode": resolved_mode,
"pageId": page_id,
"branchId": payload.branchId,
"headRevision": execution.get("headRevision", page.get("headRevision", 0) if page else 0),
"componentsCreated": execution.get("componentsCreated", []),
"summary": execution.get("summary", ""),
"warnings": execution.get("warnings", []),
"components": page.get("components", []) if page else [],
"colonyMissionId": execution["workflowDispatch"]["missionId"],
})
execution = await prompt_orchestrator.execute(
tenant_id=ctx.tenant_id,
page_id=page_id,
@@ -326,6 +840,8 @@ async def submit_prompt(
"executionId": execution["executionId"],
"actionId": action["actionId"],
"status": execution["status"],
"executionMode": payload.executionMode,
"resolvedMode": "fast",
"pageId": page_id,
"branchId": payload.branchId,
"headRevision": execution.get("headRevision", page.get("headRevision", 0) if page else 0),
@@ -551,4 +1067,3 @@ async def oracle_canvas_ws(ws: WebSocket, page_id: str) -> None:
# ── Pre-made templates seed ───────────────────────────────────────────────────