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

@@ -5,6 +5,7 @@ Mobile Edge API — serves iPhone Edge and Android Phone Edge apps.
Surfaces:
GET /mobile-edge/events — communication events for a lead
GET /mobile-edge/bulk — coordinated iPad refresh bundle
POST /mobile-edge/events — log a new communication event
GET /mobile-edge/memory — memory facts for a lead
POST /mobile-edge/imports — operator-assisted import of a recording/note
@@ -54,6 +55,22 @@ def _tenant_scope(user) -> str:
return user.tenant_id
def _normalise_lead_ids(raw_value: Optional[str], max_items: int = 24) -> list[str]:
if not raw_value:
return []
seen: set[str] = set()
lead_ids: list[str] = []
for part in raw_value.split(","):
lead_id = part.strip()
if not lead_id or lead_id in seen:
continue
seen.add(lead_id)
lead_ids.append(lead_id)
if len(lead_ids) >= max_items:
break
return lead_ids
# ── Pydantic models ───────────────────────────────────────────────────────────
VALID_CHANNELS = {
@@ -135,6 +152,12 @@ class SessionHeartbeat(BaseModel):
metadata: dict = Field(default_factory=dict)
class MobileEdgeBulkRequest(BaseModel):
lead_ids: list[str] = Field(default_factory=list, max_length=100)
events_limit_per_lead: int = Field(default=4, ge=1, le=25)
calendar_limit: int = Field(default=50, ge=1, le=200)
# ── Communication Events ───────────────────────────────────────────────────────
@router.get("/events", summary="List communication events for a lead")
@@ -173,6 +196,134 @@ async def list_events(
}
@router.get("/bulk", summary="Bulk mobile-edge refresh bundle")
async def bulk_mobile_edge(
request: Request,
lead_ids: Optional[str] = Query(None, description="Comma-separated lead IDs to hydrate timeline events for"),
events_limit_per_lead: int = Query(4, ge=1, le=25),
calendar_limit: int = Query(50, ge=1, le=200),
user=Depends(get_current_user),
):
"""
Returns a single coordinated payload for native surface refreshes.
The iPad app uses this endpoint to avoid one request for alerts, one request
for calendar, and then one request per lead timeline.
"""
return await _bulk_mobile_edge_payload(
request=request,
user=user,
selected_lead_ids=_normalise_lead_ids(lead_ids),
events_limit_per_lead=events_limit_per_lead,
calendar_limit=calendar_limit,
)
@router.post("/bulk", summary="Bulk mobile-edge refresh bundle")
async def bulk_mobile_edge_post(
request: Request,
body: MobileEdgeBulkRequest,
user=Depends(get_current_user),
):
"""POST variant for native clients that need a larger explicit lead set."""
seen: set[str] = set()
selected_lead_ids = []
for lead_id in body.lead_ids:
normalized = lead_id.strip()
if normalized and normalized not in seen:
seen.add(normalized)
selected_lead_ids.append(normalized)
return await _bulk_mobile_edge_payload(
request=request,
user=user,
selected_lead_ids=selected_lead_ids[:100],
events_limit_per_lead=body.events_limit_per_lead,
calendar_limit=body.calendar_limit,
)
async def _bulk_mobile_edge_payload(
*,
request: Request,
user,
selected_lead_ids: list[str],
events_limit_per_lead: int,
calendar_limit: int,
):
tenant_id = _tenant_scope(user)
pool = _pool(request)
async with pool.acquire() as conn:
calendar_rows = await conn.fetch(
"""
SELECT calendar_event_id, lead_id, title, description, start_at, end_at,
all_day, status, reminder_minutes, created_by, location, metadata, created_at
FROM user_calendar_events
WHERE tenant_id=$1 AND owner_user_id=$2
AND status <> 'cancelled'
ORDER BY start_at ASC LIMIT $3
""",
tenant_id, user.user_id, calendar_limit,
)
if selected_lead_ids:
event_rows = await conn.fetch(
"""
SELECT event_id, lead_id, channel, direction, provider, capture_mode,
consent_state, timestamp, duration_seconds, summary, raw_reference,
recording_ref, provider_metadata, created_at
FROM (
SELECT event_id, lead_id, channel, direction, provider, capture_mode,
consent_state, timestamp, duration_seconds, summary, raw_reference,
recording_ref, provider_metadata, created_at,
ROW_NUMBER() OVER (PARTITION BY lead_id ORDER BY timestamp DESC) AS row_number
FROM edge_communication_events
WHERE tenant_id=$1 AND lead_id = ANY($2::text[])
) ranked_events
WHERE row_number <= $3
ORDER BY lead_id ASC, timestamp DESC
""",
tenant_id, selected_lead_ids, events_limit_per_lead,
)
else:
event_rows = []
pending_insights = await conn.fetchval(
"SELECT COUNT(*) FROM insight_recommendations WHERE tenant_id=$1 AND status='pending'",
tenant_id,
)
upcoming_events = await conn.fetchval(
"""
SELECT COUNT(*) FROM user_calendar_events
WHERE tenant_id=$1 AND owner_user_id=$2
AND status='confirmed'
AND start_at BETWEEN NOW() AND NOW() + INTERVAL '24 hours'
""",
tenant_id, user.user_id,
)
pending_transcriptions = await conn.fetchval(
"SELECT COUNT(*) FROM edge_transcription_jobs WHERE tenant_id=$1 AND status='pending'",
tenant_id,
)
events_by_lead_id: dict[str, list[dict[str, Any]]] = {lead_id: [] for lead_id in selected_lead_ids}
for row in event_rows:
event = dict(row)
events_by_lead_id.setdefault(event["lead_id"], []).append(event)
return {
"calendar_events": [dict(r) for r in calendar_rows],
"lead_events": events_by_lead_id,
"alerts": {
"pending_insights": pending_insights,
"upcoming_calendar_events_24h": upcoming_events,
"pending_transcriptions": pending_transcriptions,
"generated_at": _now(),
},
"generated_at": _now(),
}
@router.post("/events", status_code=status.HTTP_201_CREATED, summary="Log a communication event")
async def create_event(
request: Request,