feat: Ipad app production readiness, Colony orchestration, Social posting
All checks were successful
Production Readiness / backend-contracts (pull_request) Successful in 3m19s
Production Readiness / webos-typecheck (pull_request) Successful in 2m38s
Production Readiness / ipad-parse (pull_request) Successful in 1m44s

This commit is contained in:
Sayan Datta
2026-05-03 18:28:04 +05:30
parent acfc602157
commit 6c93e31741
86 changed files with 20349 additions and 1655 deletions

View File

@@ -13,6 +13,7 @@ import argparse
import asyncio
import json
import os
import re
import uuid
from datetime import datetime, timedelta, timezone
from pathlib import Path
@@ -29,6 +30,7 @@ except ModuleNotFoundError: # pragma: no cover - exercised by operator environm
SEED_SOURCE = "velocity_ipad_investor_demo_2026_04"
DEFAULT_OPERATOR_EMAIL = "sayan@desineuron.in"
NAMESPACE = uuid.uuid5(uuid.NAMESPACE_URL, "https://desineuron.in/project-velocity/ipad-investor-demo")
DEMO_CLIENT_TARGET_COUNT = 50
def _load_env() -> None:
@@ -73,6 +75,7 @@ def _expected_counts() -> dict[str, int]:
"leads": len(DEMO_CLIENTS),
"projects": len(PROJECTS),
"properties": len(PROJECTS),
"property_media": len(PROJECTS) * 2,
"interests": len(DEMO_CLIENTS),
"opportunities": len(DEMO_CLIENTS),
"scores": len(DEMO_CLIENTS) * 3,
@@ -80,6 +83,10 @@ def _expected_counts() -> dict[str, int]:
"edge_events": len(DEMO_CLIENTS),
"reminders": len(DEMO_CLIENTS),
"calendar_events": len(DEMO_CLIENTS),
"insight_recommendations": len(DEMO_CLIENTS),
"comms_threads": len(DEMO_CLIENTS),
"comms_messages": len(DEMO_CLIENTS) * 3,
"comms_call_logs": len(DEMO_CLIENTS),
"import_batches": 1,
"import_proposals": 3,
}
@@ -304,6 +311,201 @@ PROJECTS = {
},
}
PROJECTS.update(
{
"Emaar Palm Heights Dubai": {
"developer": "Emaar Properties",
"micro_market": "Dubai Hills Estate",
"address": "Dubai Hills Estate, Dubai, UAE",
"property_type": "penthouse",
"location": {"city": "Dubai", "district": "Dubai Hills", "lat": 25.1132, "lng": 55.2477},
"price_bands": [
{"unitType": "4BR Sky Penthouse", "minUSD": 3200000, "maxUSD": 5200000},
{"unitType": "3BR Residence", "minUSD": 1800000, "maxUSD": 2600000},
],
"unit_mix": [{"bedrooms": 4, "count": 16, "sizeSqft": 5200}, {"bedrooms": 3, "count": 42, "sizeSqft": 3100}],
},
"Sobha Hartland Estates": {
"developer": "Sobha Realty",
"micro_market": "Mohammed Bin Rashid City",
"address": "Sobha Hartland, MBR City, Dubai, UAE",
"property_type": "villa",
"location": {"city": "Dubai", "district": "MBR City", "lat": 25.1763, "lng": 55.3067},
"price_bands": [
{"unitType": "5BR Waterfront Villa", "minUSD": 4500000, "maxUSD": 7800000},
{"unitType": "4BR Garden Villa", "minUSD": 2900000, "maxUSD": 4200000},
],
"unit_mix": [{"bedrooms": 5, "count": 12, "sizeSqft": 8100}, {"bedrooms": 4, "count": 24, "sizeSqft": 5900}],
},
"Emaar Urban Oasis Gurugram": {
"developer": "Emaar India",
"micro_market": "Golf Course Extension",
"address": "Sector 62, Gurugram, India",
"property_type": "apartment",
"location": {"city": "Gurugram", "district": "Golf Course Extension", "lat": 28.4059, "lng": 77.0964},
"price_bands": [
{"unitType": "4BHK Signature", "minINR": 95000000, "maxINR": 155000000},
{"unitType": "Penthouse", "minINR": 180000000, "maxINR": 290000000},
],
"unit_mix": [{"bedrooms": 4, "count": 44, "sizeSqft": 4200}, {"bedrooms": 5, "count": 10, "sizeSqft": 6200}],
},
"Sobha Neopolis Presidential": {
"developer": "Sobha Limited",
"micro_market": "Panathur",
"address": "Panathur Main Road, Bengaluru, India",
"property_type": "apartment",
"location": {"city": "Bengaluru", "district": "Panathur", "lat": 12.9357, "lng": 77.7037},
"price_bands": [
{"unitType": "4BHK Presidential", "minINR": 85000000, "maxINR": 140000000},
{"unitType": "3BHK Luxury", "minINR": 42000000, "maxINR": 68000000},
],
"unit_mix": [{"bedrooms": 4, "count": 30, "sizeSqft": 3600}, {"bedrooms": 3, "count": 72, "sizeSqft": 2400}],
},
"Emaar Ocean Crown Abu Dhabi": {
"developer": "Emaar Properties",
"micro_market": "Saadiyat Island",
"address": "Saadiyat Cultural District, Abu Dhabi, UAE",
"property_type": "branded_residence",
"location": {"city": "Abu Dhabi", "district": "Saadiyat Island", "lat": 24.5442, "lng": 54.4332},
"price_bands": [
{"unitType": "Royal Beachfront Penthouse", "minUSD": 6200000, "maxUSD": 11000000},
{"unitType": "3BR Beach Residence", "minUSD": 2400000, "maxUSD": 3900000},
],
"unit_mix": [{"bedrooms": 5, "count": 6, "sizeSqft": 9200}, {"bedrooms": 3, "count": 28, "sizeSqft": 3400}],
},
}
)
PROPERTY_MEDIA_URLS = [
"https://images.unsplash.com/photo-1600585154340-be6161a56a0c",
"https://images.unsplash.com/photo-1600607687939-ce8a6c25118c",
"https://images.unsplash.com/photo-1600566753190-17f0baa2a6c3",
"https://images.unsplash.com/photo-1600585154526-990dced4db0d",
"https://images.unsplash.com/photo-1613490493576-7fde63acd811",
"https://images.unsplash.com/photo-1600607687644-aac4c3eac7f4",
"https://images.unsplash.com/photo-1600566752355-35792bedcfea",
"https://images.unsplash.com/photo-1600607687920-4e2a09cf159d",
]
def _media_url(seed_index: int, *, width: int, height: int) -> str:
base = PROPERTY_MEDIA_URLS[seed_index % len(PROPERTY_MEDIA_URLS)]
return f"{base}?auto=format&fit=crop&w={width}&h={height}&q=82"
def _project_media_assets(project_name: str, project: dict[str, Any], index: int) -> list[dict[str, Any]]:
slug = re.sub(r"[^a-z0-9]+", "-", project_name.lower()).strip("-")
location = project["location"]
return [
{
"key": f"{slug}:hero",
"media_type": "image",
"url": _media_url(index, width=1600, height=1000),
"thumbnail_url": _media_url(index, width=640, height=420),
"sort_order": 0,
"metadata": {
"seed_source": SEED_SOURCE,
"demo_asset_kind": "property_hero",
"project_name": project_name,
"developer": project["developer"],
"city": location["city"],
"district": location["district"],
},
},
{
"key": f"{slug}:floorplan",
"media_type": "floorplan",
"url": _media_url(index + 3, width=1600, height=1000),
"thumbnail_url": _media_url(index + 3, width=640, height=420),
"sort_order": 1,
"metadata": {
"seed_source": SEED_SOURCE,
"demo_asset_kind": "floor_plan_reference",
"project_name": project_name,
"unit_mix": project["unit_mix"],
},
},
]
def _phone_for(index: int, country: str) -> str:
if country == "UAE":
return f"+971-50-{7200000 + index:07d}"
return f"+91-98{30000000 + index:08d}"[:14]
def _expand_demo_clients(seed_clients: list[dict[str, Any]], target_count: int) -> list[dict[str, Any]]:
if len(seed_clients) >= target_count:
return seed_clients
first_names = [
"Aarav", "Ishaan", "Kabir", "Reyansh", "Vihaan", "Anika", "Aanya", "Kiara",
"Navya", "Rhea", "Omar", "Mariam", "Zayed", "Leila", "Farah", "Aditya",
"Naina", "Rohan", "Tara", "Samaira", "Armaan", "Dev", "Saanvi", "Ayesha",
]
last_names = [
"Mehta", "Kapoor", "Khanna", "Bhatia", "Sarin", "Al Maktoum", "Al Futtaim",
"Al Habtoor", "Nair", "Menon", "Reddy", "Pillai", "Chopra", "Raheja",
"Jindal", "Goenka", "Dalmia", "Merchant", "Saxena", "Bose",
]
buyer_types = ["hni_end_user", "nri_investor", "family_office", "founder_buyer", "investor"]
statuses = ["qualified", "site_visit_scheduled", "site_visited", "negotiation", "booking_initiated", "contacted"]
stages = ["qualified", "proposal", "site_visit", "negotiation", "booking"]
urgencies = ["medium", "high", "critical"]
project_names = list(PROJECTS.keys())
expanded = list(seed_clients)
for index in range(len(seed_clients), target_count):
first = first_names[index % len(first_names)]
last = last_names[(index * 3) % len(last_names)]
project_name = project_names[index % len(project_names)]
project = PROJECTS[project_name]
city = project["location"]["city"]
is_uae = city in {"Dubai", "Abu Dhabi"}
country = "UAE" if is_uae else "India"
buyer_type = buyer_types[index % len(buyer_types)]
urgency = urgencies[index % len(urgencies)]
high_value_usd = 1_150_000 + (index % 9) * 425_000
value = high_value_usd if is_uae else 82_000_000 + (index % 11) * 17_500_000
budget_label = f"${high_value_usd / 1_000_000:.1f}-${(high_value_usd + 950_000) / 1_000_000:.1f}M" if is_uae else f"{int(value / 10_000_000)}-{int(value / 10_000_000) + 4} Cr"
configuration = project["unit_mix"][0]["bedrooms"]
unit_label = "Royal penthouse stack" if value >= (4_000_000 if is_uae else 180_000_000) else "High-floor signature residence"
full_name = f"{first} {last}"
key = re.sub(r"[^a-z0-9]+", "-", full_name.lower()).strip("-") + f"-{index:02d}"
expanded.append(
{
"key": key,
"name": full_name,
"email": f"{key}@demo.desineuron.in",
"phone": _phone_for(index, country),
"buyer_type": buyer_type,
"city": city,
"nationality": "Emirati" if is_uae and index % 3 == 0 else "Indian",
"persona": [buyer_type, "tier_1_developer_pitch", "high_value_pipeline"],
"budget": budget_label,
"urgency": urgency,
"status": statuses[index % len(statuses)],
"project": project_name,
"configuration": f"{configuration}BR Signature Residence",
"unit": unit_label,
"budget_min": int(value * 0.88),
"budget_max": int(value * 1.22),
"stage": stages[index % len(stages)],
"value": value,
"probability": min(92, 48 + (index * 7) % 43),
"next_action": f"Prepare developer-grade commercial deck for {project_name} and schedule executive walkthrough.",
"scores": (
round(0.68 + ((index * 7) % 28) / 100, 2),
round(0.64 + ((index * 5) % 30) / 100, 2),
round(0.62 + ((index * 11) % 35) / 100, 2),
),
}
)
return expanded
DEMO_CLIENTS = _expand_demo_clients(DEMO_CLIENTS, DEMO_CLIENT_TARGET_COUNT)
async def seed(conn: asyncpg.Connection, tenant_id: str, operator_user_id: str | None, dry_run: bool = False) -> dict[str, int]:
now = datetime.now(timezone.utc)
@@ -313,6 +515,7 @@ async def seed(conn: asyncpg.Connection, tenant_id: str, operator_user_id: str |
"leads": 0,
"projects": 0,
"properties": 0,
"property_media": 0,
"interests": 0,
"opportunities": 0,
"scores": 0,
@@ -320,6 +523,10 @@ async def seed(conn: asyncpg.Connection, tenant_id: str, operator_user_id: str |
"edge_events": 0,
"reminders": 0,
"calendar_events": 0,
"insight_recommendations": 0,
"comms_threads": 0,
"comms_messages": 0,
"comms_call_logs": 0,
"import_batches": 0,
"import_proposals": 0,
}
@@ -327,7 +534,7 @@ async def seed(conn: asyncpg.Connection, tenant_id: str, operator_user_id: str |
return counts
project_ids: dict[str, str] = {}
for name, project in PROJECTS.items():
for project_index, (name, project) in enumerate(PROJECTS.items()):
project_id = _stable_uuid(tenant_id, f"project:{name}")
project_ids[name] = project_id
await conn.execute(
@@ -336,8 +543,8 @@ async def seed(conn: asyncpg.Connection, tenant_id: str, operator_user_id: str |
project_id, project_name, developer_name, city, micro_market, address,
total_units, project_status, location_json, amenities_json, metadata_json
) VALUES (
$1::uuid, $2, $3, 'Kolkata', $4, $5, $6, 'active',
$7::jsonb, $8::jsonb, $9::jsonb
$1::uuid, $2, $3, $4, $5, $6, $7, 'active',
$8::jsonb, $9::jsonb, $10::jsonb
)
ON CONFLICT (project_name) DO UPDATE SET
developer_name = EXCLUDED.developer_name,
@@ -353,6 +560,7 @@ async def seed(conn: asyncpg.Connection, tenant_id: str, operator_user_id: str |
project_id,
name,
project["developer"],
project["location"]["city"],
project["micro_market"],
project["address"],
sum(item["count"] for item in project["unit_mix"]),
@@ -399,6 +607,36 @@ async def seed(conn: asyncpg.Connection, tenant_id: str, operator_user_id: str |
)
counts["properties"] += 1
for media in _project_media_assets(name, project, project_index):
media_asset_id = _stable_uuid(tenant_id, f"inventory-media:{media['key']}")
await conn.execute(
"""
INSERT INTO inventory_media_assets (
media_asset_id, property_id, tenant_id, media_type, url,
thumbnail_url, sort_order, metadata, uploaded_by, created_at
) VALUES (
$1::uuid, $2::uuid, $3, $4, $5, $6, $7, $8::jsonb, $9, NOW()
)
ON CONFLICT (media_asset_id) DO UPDATE SET
media_type = EXCLUDED.media_type,
url = EXCLUDED.url,
thumbnail_url = EXCLUDED.thumbnail_url,
sort_order = EXCLUDED.sort_order,
metadata = inventory_media_assets.metadata || EXCLUDED.metadata,
uploaded_by = EXCLUDED.uploaded_by
""",
media_asset_id,
property_id,
tenant_id,
media["media_type"],
media["url"],
media["thumbnail_url"],
media["sort_order"],
_json(media["metadata"]),
owner_user_ref,
)
counts["property_media"] += 1
for index, client in enumerate(DEMO_CLIENTS):
person_id = _stable_uuid(tenant_id, f"person:{client['key']}")
lead_id = _stable_uuid(tenant_id, f"lead:{client['key']}")
@@ -707,6 +945,142 @@ async def seed(conn: asyncpg.Connection, tenant_id: str, operator_user_id: str |
)
counts["calendar_events"] += 1
recommendation_id = _stable_uuid(tenant_id, f"insight:{client['key']}")
await conn.execute(
"""
INSERT INTO insight_recommendations (
recommendation_id, tenant_id, lead_id, source_event_id,
recommendation_type, summary, suggested_action, target_system,
status, confidence, created_at, updated_at
) VALUES (
$1::uuid, $2, $3, $4::uuid, $5, $6, $7, $8,
'pending', $9, NOW() - ($10::int * INTERVAL '8 minutes'), NOW()
)
ON CONFLICT (recommendation_id) DO UPDATE SET
source_event_id = EXCLUDED.source_event_id,
recommendation_type = EXCLUDED.recommendation_type,
summary = EXCLUDED.summary,
suggested_action = EXCLUDED.suggested_action,
target_system = EXCLUDED.target_system,
status = 'pending',
confidence = EXCLUDED.confidence,
updated_at = NOW()
""",
recommendation_id,
tenant_id,
lead_id,
edge_event_id,
"schedule_meeting" if client["urgency"] in {"high", "critical"} else "send_property_info",
f"{client['name']} is showing strong buying intent for {client['project']}.",
client["next_action"],
"calendar" if client["urgency"] in {"high", "critical"} else "whatsapp",
min(0.98, float(client["scores"][0]) + 0.04),
index,
)
counts["insight_recommendations"] += 1
thread_id = _stable_uuid(tenant_id, f"comms-thread:{client['key']}")
await conn.execute(
"""
INSERT INTO comms_threads (
thread_id, provider, external_thread_id, person_id, phone_e164,
display_name, channel, status, assigned_user_id, last_message_at,
unread_count, metadata_json, created_at, updated_at
) VALUES (
$1::uuid, 'waha', $2, $3::uuid, $4, $5, 'whatsapp',
'open', $6::uuid, $7, 1, $8::jsonb, NOW() - INTERVAL '3 days', NOW()
)
ON CONFLICT (thread_id) DO UPDATE SET
person_id = EXCLUDED.person_id,
phone_e164 = EXCLUDED.phone_e164,
display_name = EXCLUDED.display_name,
assigned_user_id = EXCLUDED.assigned_user_id,
last_message_at = EXCLUDED.last_message_at,
unread_count = EXCLUDED.unread_count,
metadata_json = comms_threads.metadata_json || EXCLUDED.metadata_json,
updated_at = NOW()
""",
thread_id,
f"{SEED_SOURCE}:{client['key']}",
person_id,
client["phone"],
client["name"],
operator_user_id,
now - timedelta(minutes=18 + index * 9),
_json({"seed_source": SEED_SOURCE, "investor_demo": True, "project": client["project"]}),
)
counts["comms_threads"] += 1
message_bodies = [
("inbound", f"Can you send the latest floor stack and payment plan for {client['project']}?"),
("outbound", f"Sharing the executive deck now. I also reserved a walkthrough slot for {client['configuration']}."),
("inbound", f"Please proceed. Budget is aligned around {client['budget']} if the view and handover schedule work."),
]
for message_index, (direction, body) in enumerate(message_bodies):
await conn.execute(
"""
INSERT INTO comms_messages (
message_id, thread_id, provider, external_message_id, direction,
message_type, body, delivery_status, sent_at, delivered_at,
raw_payload, created_at
) VALUES (
$1::uuid, $2::uuid, 'waha', $3, $4, 'text', $5,
'delivered', $6, $6, $7::jsonb, NOW()
)
ON CONFLICT (message_id) DO UPDATE SET
body = EXCLUDED.body,
delivery_status = EXCLUDED.delivery_status,
sent_at = EXCLUDED.sent_at,
delivered_at = EXCLUDED.delivered_at,
raw_payload = EXCLUDED.raw_payload
""",
_stable_uuid(tenant_id, f"comms-message:{client['key']}:{message_index}"),
thread_id,
f"{SEED_SOURCE}:{client['key']}:{message_index}",
direction,
body,
now - timedelta(minutes=46 - message_index * 14 + index * 3),
_json({"seed_source": SEED_SOURCE, "investor_demo": True}),
)
counts["comms_messages"] += 1
call_id = _stable_uuid(tenant_id, f"comms-call:{client['key']}")
await conn.execute(
"""
INSERT INTO comms_call_logs (
call_id, thread_id, person_id, provider, external_call_id,
phone_e164, direction, status, started_at, ended_at,
duration_seconds, recording_url, transcript_text, raw_payload,
created_at
) VALUES (
$1::uuid, $2::uuid, $3::uuid, 'waha', $4, $5, 'outbound',
'completed', $6, $7, $8, $9, $10, $11::jsonb, NOW()
)
ON CONFLICT (call_id) DO UPDATE SET
thread_id = EXCLUDED.thread_id,
person_id = EXCLUDED.person_id,
status = EXCLUDED.status,
started_at = EXCLUDED.started_at,
ended_at = EXCLUDED.ended_at,
duration_seconds = EXCLUDED.duration_seconds,
recording_url = EXCLUDED.recording_url,
transcript_text = EXCLUDED.transcript_text,
raw_payload = EXCLUDED.raw_payload
""",
call_id,
thread_id,
person_id,
f"{SEED_SOURCE}:call:{client['key']}",
client["phone"],
now - timedelta(hours=2 + index),
now - timedelta(hours=2 + index) + timedelta(minutes=7, seconds=30),
450,
f"s3://velocity-demo-media/{SEED_SOURCE}/calls/{client['key']}.m4a",
f"Discussed {client['project']}, {client['configuration']}, budget {client['budget']}, and next action: {client['next_action']}",
_json({"seed_source": SEED_SOURCE, "investor_demo": True}),
)
counts["comms_call_logs"] += 1
batch_id = _stable_uuid(tenant_id, "workflow-import-batch:investor-demo")
await conn.execute(
"""