Compare commits

...

15 Commits

Author SHA1 Message Date
eeb684b46c 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
2026-05-03 18:30:38 +05:30
Sagnik
59d398abc3 fix: restore velocity backend login
Some checks failed
Production Readiness / ipad-parse (push) Successful in 1m21s
Production Readiness / backend-contracts (push) Failing after 10m8s
Production Readiness / webos-typecheck (push) Failing after 13m47s
2026-04-28 14:02:18 +05:30
Sagnik
3623bacbac feat: Whatsapp Integration
Some checks failed
Production Readiness / backend-contracts (push) Failing after 1m58s
Production Readiness / webos-typecheck (push) Successful in 1m37s
Production Readiness / ipad-parse (push) Successful in 2m17s
2026-04-28 13:41:14 +05:30
7ee51543d9 Merge Conflicts (#41)
Some checks failed
Production Readiness / backend-contracts (push) Failing after 1m47s
Production Readiness / webos-typecheck (push) Successful in 1m57s
Production Readiness / ipad-parse (push) Successful in 1m32s
Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local>
Reviewed-on: #41
2026-04-28 11:32:56 +05:30
Sagnik
61258978e1 fix: Oracle Canvas Metadata and deterministic semantic repair 2026-04-24 15:44:00 +05:30
Sagnik
8d41ba5549 Merge branch 'fix/#36' 2026-04-24 05:14:58 +05:30
Sagnik
cf602822b0 fix: Oracle Canvas JSON Component Generation planning and orchestration logic 2026-04-24 05:14:11 +05:30
Sagnik
9f27e6a017 feat: New Chat, Search Chat and Master Slave DB Architecture for CRM and Oracle Canvas 2026-04-24 04:19:37 +05:30
Sagnik
eabecf7a25 feat: New Chat, Search Chat and Master Slave DB Architecture for CRM and Oracle Canvas 2026-04-24 04:18:33 +05:30
Sagnik
f04571bd7b Feat: CRM v2, Richer synthetic data, Canvas JSON Components 2026-04-23 22:00:44 +05:30
6cdc366718 feat: Oracle Canvas, Revision History and Canvas Sharing (#33)
Co-authored-by: Sagnik <sagnik7896@gmail.com>
Reviewed-on: #33
2026-04-23 01:20:21 +05:30
e519339cc9 feat: Oracle Canvas Component Schema and Qwen 3.6 integration (#31)
Co-authored-by: Sagnik <sagnik7896@gmail.com>
Reviewed-on: #31
2026-04-20 01:43:39 +05:30
57144e1bd3 feat/#28 (#29)
Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local>
Reviewed-on: #29
2026-04-20 00:48:01 +05:30
Sagnik
4e3ce623a6 fix: Applied fix for name, the oracle team sharing, sentinel client list visibility 2026-04-19 17:08:17 +05:30
Sagnik
d886e4a669 fix: Applied fix for name, the oracle team sharing, sentinel client list visibility 2026-04-19 17:07:12 +05:30
481 changed files with 775802 additions and 92226 deletions

View File

@@ -0,0 +1,265 @@
# Velocity Comms Integration — Handoff Document
## 1. Architecture Recommendation
### Goal
Add a native **Conversations** module to Project Velocity so brokers/agents can manage WhatsApp (and future SMS/call) threads without leaving the WebOS.
### Design Philosophy
- **Native Velocity UI**: Dark glass panels, compact density, blue accent, no iframe embeds.
- **Provider-agnostic backend**: Abstract `CommsProvider` class with adapter pattern.
- **CRM-first**: Every thread attempts to link to `crm_people` by `primary_phone`. Unresolved numbers are surfaced for manual linking.
- **Mock-first development**: The module renders fully without real credentials.
### Provider Comparison
| Provider | Best For | Velocity Fit | 72-Hour Viability |
|----------|----------|--------------|-------------------|
| **Chatwoot** | Full support suite (email, SMS, WA) | Too heavy to embed; good UX reference | Low — would require stripping UI |
| **WAHA** | Lightweight WhatsApp Web gateway | Good adapter candidate | High — simple REST, easy webhooks |
| **Evolution API** | Modern WA gateway with groups, status, typing | Best adapter candidate | **High** — active community, clean webhooks |
| **Meta Cloud API** | Official WABA; template-based outbound | Required for production scale at large builders | Medium — needs Meta Business verification |
**Recommended 72-hour route:**
1. **Day 1**: Merge schema + backend routes + mock provider. Frontend compiles with mock data.
2. **Day 2**: Connect Evolution API or WAHA in a staging environment. Test inbound webhook → thread creation.
3. **Day 3**: CRM linking, settings UI, call-log upload placeholder, and smoke tests.
For production, plan a **dual-provider** setup:
- **Evolution/WAHA** for quick conversational messaging (no Meta approval needed).
- **Meta Cloud API** for template-based broadcast/re-engagement once Business Manager is verified.
---
## 2. Exact Files Created
```
app/src/types/commsTypes.ts
app/src/lib/commsApi.ts
app/src/components/modules/Comms.tsx
backend/db/schema_comms.sql
backend/services/comms_provider.py
backend/services/comms_waha_provider.py
backend/services/comms_evolution_provider.py
backend/services/comms_ingest.py
backend/api/routes_comms.py
COMMS_INTEGRATION_HANDOFF.md
```
---
## 3. Patch Instructions for Existing Files
### A. `app/src/types/index.ts`
Add `'comms'` to the `ModuleId` union:
```typescript
export type ModuleId = 'dashboard' | 'oracle' | 'sentinel' | 'inventory' | 'settings' | 'catalyst' | 'admin' | 'crm' | 'comms';
```
### B. `app/src/App.tsx`
1. Import the new component:
```typescript
import { Comms } from '@/components/modules/Comms';
```
2. Insert the route into `MODULE_ROUTES` **just before** `settings`:
```typescript
{ id: 'comms', path: '/comms', title: 'Conversations', component: Comms },
```
### C. `app/src/components/layout/Sidebar.tsx`
1. Import a new icon:
```typescript
import { MessageCircle } from 'lucide-react';
```
2. Add to `NAV_ICONS`:
```typescript
const NAV_ICONS: Record<string, LucideIcon> = {
'/dashboard': LayoutGrid,
'/oracle': MessageSquarePlus,
'/sentinel': ScanFace,
'/inventory': Building2,
'/catalyst': Megaphone,
'/comms': MessageCircle, // ← NEW
'/settings': Sliders,
'/admin': Shield,
'/crm': Users,
};
```
### D. `backend/main.py`
1. Import the router near the other imports:
```python
from backend.api.routes_comms import router as comms_router
```
2. Include it after the other routers:
```python
app.include_router(comms_router, prefix="/api/comms", tags=["Comms"])
```
---
## 4. Environment Variables
Add these to your `.env` or systemd environment:
```bash
# Provider selection: mock | waha | evolution | meta_cloud
COMMS_PROVIDER=mock
# Provider connectivity
COMMS_PROVIDER_BASE_URL=
COMMS_PROVIDER_API_KEY=
COMMS_INSTANCE_ID=default
# Webhook security
COMMS_WEBHOOK_SECRET=
# Phone normalization
COMMS_DEFAULT_COUNTRY_CODE=91
# Media storage
COMMS_MEDIA_STORAGE_DIR=/opt/dlami/nvme/assets/comms
# Transcription (none | openai | local)
COMMS_TRANSCRIPTION_PROVIDER=none
```
**No secrets are hardcoded in source.**
---
## 5. Database Migration
Run the SQL file against your Postgres database:
```bash
psql $DATABASE_URL -f backend/db/schema_comms.sql
```
Tables created:
- `comms_threads` — conversation headers with CRM link
- `comms_messages` — individual messages (inbound/outbound/system)
- `comms_call_logs` — call records with optional transcript
- `comms_settings` — key-value config store
---
## 6. API Routes
| Method | Path | Purpose |
|--------|------|---------|
| GET | `/api/comms/threads` | List threads (search, status, pagination) |
| GET | `/api/comms/threads/{id}` | Get single thread with CRM enrichment |
| GET | `/api/comms/threads/{id}/messages` | Chronological messages |
| POST | `/api/comms/threads/{id}/messages` | Send outbound message via provider |
| POST | `/api/comms/threads/{id}/link-person` | Link thread to `crm_people.id` |
| POST | `/api/comms/threads/{id}/notes` | Add system note |
| POST | `/api/comms/threads/{id}/tasks` | Add system task |
| POST | `/api/comms/webhooks/{provider}` | Public webhook endpoint |
| GET | `/api/comms/settings` | Get comms configuration |
| PATCH | `/api/comms/settings` | Update configuration |
| POST | `/api/comms/provider/test` | Test provider connectivity |
| POST | `/api/comms/recordings/transcribe` | Queue transcription job |
---
## 7. Frontend Route Changes
- New sidebar item: **Conversations** (icon: `MessageCircle`)
- Position: directly above **Settings**
- Route: `/comms`
- Component: `Comms.tsx` with three-pane layout (Inbox | Chat | CRM Rail)
---
## 8. Settings Changes
A new **Communications** subsection should be added inside your existing Settings module (or as a standalone card). Fields:
| Field | Type | Description |
|-------|------|-------------|
| Provider | select | mock / waha / evolution / meta_cloud |
| Provider Base URL | text | e.g. `http://localhost:3000` |
| API Key | password | masked after save |
| Instance ID | text | WA/Evolution session name |
| Phone Number ID | text | Meta Cloud API only |
| Webhook Callback URL | text | Auto-populated or custom |
| Webhook Secret | password | Sets `webhook_secret_set` flag |
| Default Assignment User | select | User dropdown from `/api/auth/users` |
| Auto-link by Phone | toggle | Match `crm_people.primary_phone` automatically |
| Create CRM Interaction on Inbound | toggle | Write to `intel_interactions` if table exists |
| Default Country Code | text | e.g. `91` for India |
| Transcription Provider | select | none / openai / local |
| Connection Test | button | Calls `POST /api/comms/provider/test` |
---
## 9. Smoke Test Steps
1. **DB**: Run `schema_comms.sql`. Verify tables exist.
2. **Backend**: Start FastAPI. Confirm `/health` returns `db_pool: connected`.
3. **Backend**: `curl -X POST http://localhost:8000/api/comms/provider/test` → should return mock success.
4. **Frontend**: Load Velocity. Sidebar should show **Conversations**.
5. **Frontend**: Click Conversations. Mock mode should render 3 threads and messages.
6. **Frontend**: Send a message in mock thread. Optimistic update → mock delivery checkmark.
7. **Backend**: Post sample webhook:
```bash
curl -X POST http://localhost:8000/api/comms/webhooks/evolution -H "Content-Type: application/json" -d '{"event":"messages.upsert","instance":"default","data":{"key":{"remoteJid":"919876543210@s.whatsapp.net","fromMe":false,"id":"test-1"},"message":{"conversation":"Hello from webhook"},"messageTimestamp":1710000000}}'
```
8. **Backend**: Verify thread + message inserted. Check `comms_threads` for new row.
9. **Frontend**: Refresh inbox. New thread should appear.
10. **CRM Link**: Click "Link to Contact" (or call `POST /api/comms/threads/{id}/link-person`) and verify `person_id` is set.
---
## 10. Known Limitations
- **Call recording via WhatsApp API**: Neither WAHA nor Evolution supports native WhatsApp call recording. Call logs are designed for **external telephony intake** (manual upload or webhook from a PBX/VoIP system). Recording file + transcript workflow is scaffolded but needs a real transcription provider (OpenAI Whisper, AWS Transcribe, or faster-whisper) wired in.
- **Media downloads**: `get_media()` is stubbed for WAHA/Evolution. Production needs signed URL handling or local file download.
- **Meta Cloud API adapter**: Not yet implemented. Add `comms_meta_provider.py` when Meta Business verification is complete.
- **Template messages**: Only placeholder methods exist. Template approval flow (Meta) or local template storage must be built for outbound campaigns.
- **Webhook auth**: Currently accepts any payload. Add HMAC/signature verification per provider before production.
- **Rate limiting**: Not implemented. Add FastAPI rate-limit middleware on `/api/comms/webhooks/{provider}`.
- **phonenumbers library**: `comms_ingest.py` gracefully degrades to regex if `phonenumbers` is not installed. Install it for robust E.164 normalization:
```bash
pip install phonenumbers
```
---
## 11. What Still Needs Real Credentials
| Item | What You Need |
|------|---------------|
| **Evolution API** | A running Evolution instance (Docker), API key, and a paired WhatsApp number. |
| **WAHA** | A running WAHA container, session QR-scan, and API key. |
| **Meta Cloud API** | Meta Business Manager, verified business, WhatsApp Business Account, permanent access token, phone number ID. |
| **Transcription** | OpenAI API key (for Whisper) or local faster-whisper model path. |
| **CRM enrichment** | Ensure `crm_people` table exists with `primary_phone` indexed. |
---
## 12. What to Verify Before Production
- [ ] Webhook endpoint is exposed via HTTPS (ngrok/cloudflare tunnel for local dev).
- [ ] `COMMS_WEBHOOK_SECRET` is set and signature verification is enabled in `routes_comms.py`.
- [ ] Database has indexes on `comms_threads(phone_e164)` and `comms_messages(thread_id, created_at)`.
- [ ] `crm_people.primary_phone` is normalized to E.164 before comms matching.
- [ ] Media storage directory exists and is writable (`COMMS_MEDIA_STORAGE_DIR`).
- [ ] Outbound message queue / retry logic is added (currently synchronous).
- [ ] GDPR/opt-out handling is implemented if targeting EU markets.
- [ ] Backup strategy for `comms_messages` (contains legal conversation records).
---
## 13. Next Iteration Ideas
- **Bulk broadcast**: Template-based outbound to filtered CRM segments.
- **AI reply suggestions**: Integrate Oracle / local LLM to draft replies based on CRM context.
- **Voice notes**: Upload `.ogg` audio, transcribe, store transcript as message.
- **Read receipts**: Poll provider for delivery/read status and update `comms_messages`.
- **Assignment rules**: Round-robin or load-based auto-assignment to agents.
---
*Document generated for Project Velocity v1.1 — Comms Module Integration*

View File

@@ -0,0 +1,565 @@
# Codebase Analysis v1.1.md
## Table of Contents / Chapters
### 1. Overview
- Introduction to Project Velocity and its purpose
- Core principles and approach
### 2. Architectural Mapping
- Overall System Architecture (Mermaid diagram)
- File Dependency Graph (Mermaid diagram)
- Data Flow Architecture (Mermaid diagram)
### 3. Logic Decomposition
- Authentication & Authorization
- CRM Data Model
- Sentinel Biometric Intelligence
- Oracle Natural Language Intelligence
- Catalyst Marketing Orchestration
- Infrastructure & Deployment
### 4. Connectivity Matrix
- Component interconnections and data flow
- Interconnection rationale
### 5. First-Principles Guide
- Core Concept: AI-Augmented Sales Intelligence
- Why Real Estate Specifically?
- Principle 1: Data Sovereignty First
- Principle 2: Real-Time Perception Matters
- Principle 3: Intelligence Through Conversation
- Principle 4: Visual Storytelling Drives Sales
- Principle 5: Revision Control for Business Logic
- Design Philosophy: Production-Ready Craft
- Why This Architecture Succeeds
### 6. API Endpoints Reference
- Authentication Endpoints
- CRM Endpoints
- Analytics Endpoints
- Oracle AI Intelligence Endpoints
- Oracle Canvas Management (v1)
- Oracle Template Management
- Sentinel Biometric Intelligence Endpoints
- Catalyst Marketing Orchestration Endpoints
- Vault Trackable Links Endpoints
- CCTV Surveillance Integration Endpoints
- Video Scene Mapping Endpoints
- Marketing Videos Endpoints
- Mobile Edge Communication Endpoints
- Inventory Management Endpoints
- Admin Surface Control Endpoints
- CRM Canonical Data Endpoints
- Runtime LLM Endpoints
- Infrastructure Notes
## Overview
Project Velocity is an on-prem real estate operating system designed for high-value property sales. It combines a premium WebOS, an iPad field app, a FastAPI neural core, ComfyUI-based media generation, and biometric/sentiment-assisted sales intelligence. The system enables brokers to operate at the speed of AI while preserving control, provenance, and safety for customer and revenue-critical data.
This analysis provides a comprehensive understanding of the codebase from first principles, applying the Feynman Technique to distill complex implementations into intuitive concepts.
## Architectural Mapping
### Overall System Architecture
```mermaid
graph TB
subgraph "User Interfaces"
WebOS[Velocity WebOS<br/>React + TypeScript]
iPad[iPad App<br/>Swift + MediaPipe]
end
subgraph "Core Backend"
FastAPI[FastAPI Neural Core<br/>PostgreSQL + JWT Auth]
end
subgraph "AI Services"
Oracle[The Oracle<br/>Natural Language Intelligence]
Sentinel[The Sentinel<br/>Biometric Perception Engine]
Catalyst[The Catalyst<br/>Marketing Campaign Orchestration]
Comfy[ComfyUI / Dream Weaver<br/>Media Generation]
end
subgraph "Infrastructure"
AWS[AWS GPU Workers<br/>NVIDIA GPUs]
S3[S3 Asset Store<br/>Models + Media]
Linux[Linux Control Surface<br/>On-prem Deployment]
end
WebOS --> FastAPI
iPad --> FastAPI
FastAPI --> Oracle
FastAPI --> Sentinel
FastAPI --> Catalyst
Catalyst --> Comfy
Comfy --> AWS
Comfy --> S3
FastAPI --> Linux
style FastAPI fill:#e1f5fe
style Oracle fill:#f3e5f5
style Sentinel fill:#e8f5e8
style Catalyst fill:#fff3e0
```
### File Dependency Graph
```mermaid
graph TD
subgraph "Frontend (React/Vite)"
App[App.tsx<br/>Routing & Auth]
Store[useStore.ts<br/>Zustand State]
Components[Components/<br/>Modules & UI]
API[api.ts<br/>HTTP Client]
end
subgraph "Backend (FastAPI)"
Main[main.py<br/>App Entry]
Routers[routers/<br/>API Endpoints]
Services[services/<br/>Business Logic]
DB[db/<br/>Schema & Pool]
Auth[auth/<br/>JWT & Users]
Oracle[oracle/<br/>AI Intelligence]
end
subgraph "AI Infrastructure"
Comfy[comfy_engine/<br/>Media Generation]
Models[models/<br/>AI Models]
Prompts[nemoclaw_prompts/<br/>LLM Templates]
end
subgraph "Deployment"
Infra[infrastructure/<br/>Linux + AWS]
Agents[agents/<br/>Orchestration]
end
App --> API
API --> Main
Main --> Routers
Routers --> Services
Services --> DB
Services --> Auth
Services --> Oracle
Oracle --> Prompts
Comfy --> Infra
Infra --> Agents
style Main fill:#e3f2fd
style Oracle fill:#fce4ec
```
### Data Flow Architecture
```mermaid
flowchart LR
User[User Input] --> UI[WebOS/iPad UI]
UI --> API[FastAPI Endpoints]
API --> Auth[JWT Authentication]
API --> Policy[Policy Engine<br/>Authorization]
API --> LLM[Nemoclaw LLM<br/>Reasoning & Planning]
LLM --> Query[SQL Generation<br/>Safe Queries]
Query --> DB[(PostgreSQL<br/>CRM + Intelligence)]
DB --> Results[Query Results]
Results --> Viz[Visualization<br/>Components]
Viz --> Canvas[Oracle Canvas<br/>Persistent Views]
Canvas --> UI
Sentinel[Sentinel Biometric] --> WS[WebSocket<br/>Real-time]
WS --> Perception[Face Analysis<br/>MediaPipe]
Perception --> QD[QD Scoring<br/>NemoClaw]
QD --> DB
Catalyst[Catalyst Marketing] --> Comfy[ComfyUI<br/>Media Generation]
Comfy --> S3[S3 Assets]
Comfy --> GPU[AWS GPUs]
style DB fill:#fff9c4
style LLM fill:#e8f5e8
style Comfy fill:#fce4ec
```
## Logic Decomposition
### Authentication & Authorization
**What:** Secure access control for all system functions
**How:** JWT tokens with role-based permissions (ADMIN, SALES_DIRECTOR, SENIOR_BROKER, JUNIOR_BROKER)
**Why:** Real estate involves sensitive client data; strict access prevents unauthorized sales interference
**Key Implementation:**
- `backend/auth/dependencies.py`: JWT validation, user extraction, role enforcement
- `main.py`: Login endpoint with password hashing, user profile management
- Role hierarchy prevents junior brokers from approving high-value deals
### CRM Data Model
**What:** Canonical client and interaction records
**How:** PostgreSQL schema with leads, contacts, opportunities, interactions
**Why:** Real estate sales require accurate pipeline tracking and relationship history
**Key Tables:**
- `leads_intelligence`: Core lead data with QD scores and tags
- `omnichannel_logs`: All interactions (calls, emails, visits) with timestamps
- `crm_people/crm_leads`: Structured client relationships and deal stages
### Sentinel Biometric Intelligence
**What:** Real-time visitor sentiment analysis for showroom engagement
**How:** Browser webcam + MediaPipe face landmarking → blend shapes → QD scoring
**Why:** Human emotion drives buying decisions; AI detects subtle cues brokers miss
**Execution Flow:**
1. Browser captures video stream
2. MediaPipe extracts facial landmarks (68 points)
3. Blend shapes calculated (brow furrow, smile intensity, etc.)
4. NemoClaw LLM scores 1-100 QD (Qualification Desire)
5. Real-time dashboard updates for brokers
**Key Decision:** Browser-side processing preserves privacy (no video leaves client device)
### Oracle Natural Language Intelligence
**What:** AI-powered data analysis through conversational queries
**How:** Natural language → SQL planning → visualization components → canvas
**Why:** Brokers shouldn't need SQL knowledge; AI translates business questions to insights
**Architecture Layers:**
- **Prompt Orchestrator:** Decomposes complex requests into sub-queries
- **Natural DB Agent:** Schema introspection + safe SQL generation
- **Canvas Service:** Persistent visual workspaces with revision history
- **Collaboration:** Fork/merge workflows for team coordination
**Why Canvas?** Unlike transient chat responses, canvases persist as living documents
### Catalyst Marketing Orchestration
**What:** Automated campaign creation and asset generation
**How:** Meta Ads API integration + ComfyUI media workflows
**Why:** Luxury real estate needs high-quality visuals; manual creation is too slow
**Workflow:**
1. Campaign parameters → Meta API calls
2. Creative assets → ComfyUI (Dream Weaver, Wan 2.2, Qwen poster generation)
3. GPU processing on AWS → S3 storage
4. Performance tracking back to CRM
### Infrastructure & Deployment
**What:** Production-ready on-prem + cloud hybrid deployment
**How:** Linux control surface + AWS GPU workers + stable ingress
**Why:** Real estate firms demand data sovereignty; cloud-only solutions unacceptable
**Key Components:**
- **Ingress:** Caddy on EC2 t4g.micro with TLS termination
- **Backend:** Linux systemd services for FastAPI, ComfyUI, LLM runtime
- **GPU:** AWS managed instances for media generation
- **Assets:** S3 for model storage, NVMe for fast access
## Connectivity Matrix
| Component | Inputs | Outputs | Dependencies | Protocols |
|-----------|--------|---------|--------------|-----------|
| WebOS Frontend | User actions, API responses | UI renders, API calls | FastAPI backend | HTTP/WS, JWT |
| iPad App | Camera feeds, user input | Biometric data, inventory scans | FastAPI backend | HTTP/WS |
| FastAPI Core | API requests, WS connections | DB queries, AI responses | PostgreSQL, Redis (future) | SQL, HTTP |
| Oracle Engine | Natural language prompts | Canvas components | NemoClaw LLM, PostgreSQL | Internal API |
| Sentinel Engine | Webcam streams | QD scores, alerts | MediaPipe, NemoClaw | WS real-time |
| Catalyst Engine | Campaign specs | Ad creatives, assets | Meta API, ComfyUI | HTTP, S3 |
| ComfyUI | Generation requests | Images/videos | GPU workers, S3 | Internal queue |
| PostgreSQL | SQL queries | Structured data | - | SQL |
| AWS GPUs | Media jobs | Generated assets | ComfyUI workflows | SSH/tunnel |
**Interconnection Rationale:**
- **WebOS ↔ Backend:** Thin client architecture; all business logic server-side for security
- **Backend ↔ AI Services:** Modular design; each AI component (Oracle, Sentinel, Catalyst) operates independently but shares auth/policy
- **AI Services ↔ DB:** Direct SQL access with row-level security; no ORM abstraction to maintain performance
- **Infrastructure:** Hybrid on-prem/cloud; sensitive data stays on-prem, compute-intensive tasks use cloud GPUs
## First-Principles Guide
### Core Concept: AI-Augmented Sales Intelligence
At its foundation, Project Velocity operates on the principle that human sales professionals excel at relationship-building and deal-closing, while AI excels at pattern recognition, data synthesis, and repetitive analysis. The system doesn't replace brokers—it amplifies their capabilities by providing real-time insights they couldn't otherwise access.
**Why Real Estate Specifically?**
- High-value transactions ($M+ deals) with long sales cycles (months)
- Emotional decision-making influenced by subtle cues
- Complex data relationships (properties, buyers, markets, timing)
- Regulatory compliance requirements
- Need for visual storytelling (luxury properties)
### Principle 1: Data Sovereignty First
**Fundamental Truth:** Real estate firms own their client relationships. Project Velocity runs on-premise or in tenant-controlled cloud to maintain this ownership.
**Implementation:** Linux-based deployment with optional AWS GPU extensions. All client data remains within tenant boundaries; only anonymous model requests leave for AI processing.
### Principle 2: Real-Time Perception Matters
**Fundamental Truth:** Buying decisions happen in moments of emotional connection. Project Velocity captures these moments through biometric analysis.
**Implementation:** Sentinel uses facial expression analysis to score "Qualification Desire" (QD) on a 1-100 scale, alerting brokers to engagement spikes during property tours.
### Principle 3: Intelligence Through Conversation
**Fundamental Truth:** Sales professionals think in business terms, not database queries. The Oracle translates natural language into structured analytics.
**Implementation:** Users ask "Show me whale leads from Dubai this quarter" and receive visual dashboards. The system plans safe SQL queries, executes them, and renders results as persistent canvas components.
### Principle 4: Visual Storytelling Drives Sales
**Fundamental Truth:** Luxury properties sell through aspiration and emotion. AI-generated media must be photorealistic and brand-consistent.
**Implementation:** ComfyUI workflows (Dream Weaver, Wan 2.2) create property visualizations. Catalyst orchestrates campaigns with generated assets automatically uploaded to Meta Ads.
### Principle 5: Revision Control for Business Logic
**Fundamental Truth:** Sales strategies evolve through collaboration and iteration. Oracle canvases use Git-like branching for analytical workflows.
**Implementation:** Canvas pages support forks, merge requests, and revision history. Brokers can experiment with analysis approaches without breaking production views.
### Design Philosophy: Production-Ready Craft
Project Velocity follows "experienced engineer" principles:
- **Error Handling:** No silent failures; all errors surface useful messages
- **Type Safety:** TypeScript frontend, typed Python backend
- **Performance:** Async everywhere, connection pooling, efficient queries
- **Security:** JWT auth, role-based access, input validation
- **Observability:** Structured logging, health checks, WebSocket monitoring
- **Maintainability:** Clear separation of concerns, comprehensive documentation
### Why This Architecture Succeeds
1. **Modular AI Services:** Each intelligence component (Oracle, Sentinel, Catalyst) operates independently, allowing incremental improvement and specialized optimization.
2. **Hybrid Infrastructure:** Combines on-prem reliability with cloud scalability. Sensitive CRM data stays local; compute-intensive media generation uses managed GPUs.
3. **Real-Time Integration:** WebSockets enable live updates across all surfaces. Brokers see lead scoring changes instantly, dashboard metrics update in real-time.
4. **Business Logic in Code:** Revenue-critical workflows (lead qualification, campaign approval, deal closing) are explicit code paths, not AI hallucinations.
5. **User-Centric Design:** The WebOS feels like a native application, not a bolted-on AI interface. Familiar patterns (canvases, dashboards, forms) reduce training time.
This architecture transforms real estate sales from intuition-driven processes into data-augmented, AI-accelerated operations while preserving the human elements that drive luxury transactions.
## API Endpoints Reference
This section provides a comprehensive catalog of all API endpoints exposed by the Project Velocity backend, organized by functional module. Each endpoint includes the HTTP method, URI path, absolute HTTPS URL, and a brief description of its functionality. This serves as the definitive source of truth for the project's routing and interface architecture.
### Authentication Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| POST | /api/auth/login | https://velocity.desineuron.in/api/auth/login | Authenticate a user with email/password and return JWT token |
| GET | /api/auth/me | https://velocity.desineuron.in/api/auth/me | Get current authenticated user's profile information |
| GET | /api/auth/users | https://velocity.desineuron.in/api/auth/users | List all active users in the system |
| POST | /api/auth/profile/avatar | https://velocity.desineuron.in/api/auth/profile/avatar | Upload and update user's profile avatar image |
### CRM Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| GET | /api/leads | https://velocity.desineuron.in/api/leads | List leads with pagination and filtering |
| GET | /api/leads/{lead_id} | https://velocity.desineuron.in/api/leads/{lead_id} | Get detailed information for a specific lead |
| GET | /api/kanban/board | https://velocity.desineuron.in/api/kanban/board | Retrieve the kanban board view of leads by stage |
| GET | /api/chat-logs | https://velocity.desineuron.in/api/chat-logs | List chat logs for leads with optional lead filtering |
| GET | /api/leads/demographics | https://velocity.desineuron.in/api/leads/demographics | Get demographic analytics for leads (source, qualification) |
### Analytics Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| GET | /api/analytics/sentiment-scatter | https://velocity.desineuron.in/api/analytics/sentiment-scatter | Get scatter plot data for sentiment analysis |
### Oracle AI Intelligence Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| GET | /api/oracle/health | https://velocity.desineuron.in/api/oracle/health | Check Oracle system health and MCP tool availability |
| GET | /api/oracle/data-health | https://velocity.desineuron.in/api/oracle/data-health | Get data health metrics for database tables |
| GET | /api/oracle/schema-catalog | https://velocity.desineuron.in/api/oracle/schema-catalog | Retrieve schema catalog for database introspection |
| POST | /api/oracle/query | https://velocity.desineuron.in/api/oracle/query | Execute natural language query against database |
| GET | /api/oracle/mcp/tools | https://velocity.desineuron.in/api/oracle/mcp/tools | List available MCP tools for execution |
| POST | /api/oracle/mcp/execute | https://velocity.desineuron.in/api/oracle/mcp/execute | Execute an MCP tool with given query |
| POST | /api/oracle/workflow/preview | https://velocity.desineuron.in/api/oracle/workflow/preview | Preview workflow plan for a natural language prompt |
#### Oracle Canvas Management (v1)
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| POST | /api/oracle/v1/canvas/pages | https://velocity.desineuron.in/api/oracle/v1/canvas/pages | Create a new Oracle canvas page |
| GET | /api/oracle/v1/canvas/pages | https://velocity.desineuron.in/api/oracle/v1/canvas/pages | List user's Oracle canvas pages |
| GET | /api/oracle/v1/canvas/pages/{page_id} | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id} | Get a specific canvas page |
| PUT | /api/oracle/v1/canvas/pages/{page_id} | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id} | Update a canvas page |
| DELETE | /api/oracle/v1/canvas/pages/{page_id} | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id} | Delete a canvas page |
| POST | /api/oracle/v1/canvas/pages/{page_id}/fork | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id}/fork | Create a fork of a canvas page |
| POST | /api/oracle/v1/canvas/pages/{page_id}/merge | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id}/merge | Merge changes into a canvas page |
| GET | /api/oracle/v1/canvas/pages/{page_id}/revisions | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id}/revisions | List revisions for a canvas page |
| POST | /api/oracle/v1/canvas/pages/{page_id}/revisions | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id}/revisions | Create new revision for a canvas page |
| GET | /api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id} | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id} | Get specific revision |
| PUT | /api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id} | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id} | Update a revision |
| DELETE | /api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id} | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id} | Delete a revision |
| GET | /api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id}/components | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id}/components | List components in a revision |
| POST | /api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id}/components | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id}/components | Add component to revision |
| PUT | /api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id}/components/{component_id} | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id}/components/{component_id} | Update component |
| DELETE | /api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id}/components/{component_id} | https://velocity.desineuron.in/api/oracle/v1/canvas/pages/{page_id}/revisions/{revision_id}/components/{component_id} | Delete component |
#### Oracle Template Management
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| GET | /api/oracle/template-chapters | https://velocity.desineuron.in/api/oracle/template-chapters | List Oracle template chapters |
| POST | /api/oracle/template-chapters | https://velocity.desineuron.in/api/oracle/template-chapters | Create new template chapter |
| GET | /api/oracle/template-subchapters | https://velocity.desineuron.in/api/oracle/template-subchapters | List Oracle template subchapters |
| POST | /api/oracle/template-subchapters | https://velocity.desineuron.in/api/oracle/template-subchapters | Create new template subchapter |
| GET | /api/oracle/component-templates | https://velocity.desineuron.in/api/oracle/component-templates | List Oracle component templates |
| POST | /api/oracle/component-templates | https://velocity.desineuron.in/api/oracle/component-templates | Create new component template |
| GET | /api/oracle/component-templates/{template_id} | https://velocity.desineuron.in/api/oracle/component-templates/{template_id} | Get specific component template |
| POST | /api/oracle/component-templates/{template_id}/seed | https://velocity.desineuron.in/api/oracle/component-templates/{template_id}/seed | Add seed example to template |
| GET | /api/oracle/component-templates/{template_id}/seed | https://velocity.desineuron.in/api/oracle/component-templates/{template_id}/seed | List seed examples for template |
| POST | /api/oracle/component-templates/synthetic-jobs | https://velocity.desineuron.in/api/oracle/component-templates/synthetic-jobs | Submit synthetic template generation job |
### Sentinel Biometric Intelligence Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| WebSocket | /api/sentinel/ws/notifications | wss://velocity.desineuron.in/api/sentinel/ws/notifications | Real-time notifications WebSocket |
| WebSocket | /api/sentinel/ws/perception | wss://velocity.desineuron.in/api/sentinel/ws/perception | Biometric perception data WebSocket |
| POST | /api/sentinel/consent | https://velocity.desineuron.in/api/sentinel/consent | Record biometric consent for lead |
| POST | /api/sentinel/session/complete | https://velocity.desineuron.in/api/sentinel/session/complete | Close a perception session and finalize QD score |
| POST | /api/sentinel/tag-lead | https://velocity.desineuron.in/api/sentinel/tag-lead | Apply NemoClaw lead tagging to CRM lead |
| GET | /api/sentinel/qd-score/{lead_id} | https://velocity.desineuron.in/api/sentinel/qd-score/{lead_id} | Get current QD score for a lead |
### Catalyst Marketing Orchestration Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| POST | /api/catalyst/campaigns/create | https://velocity.desineuron.in/api/catalyst/campaigns/create | Bulk create Meta ad campaigns |
| POST | /api/catalyst/creative/sync | https://velocity.desineuron.in/api/catalyst/creative/sync | Upload ComfyUI assets to Meta |
| GET | /api/catalyst/insights/realtime | https://velocity.desineuron.in/api/catalyst/insights/realtime | Poll Meta Ads Insights API |
| POST | /api/catalyst/audiences/lookalike | https://velocity.desineuron.in/api/catalyst/audiences/lookalike | Push CRM leads to Meta Custom Audience |
| POST | /api/catalyst/auth/meta | https://velocity.desineuron.in/api/catalyst/auth/meta | OAuth token acquisition for Meta |
### Vault Trackable Links Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| POST | /api/vault/generate-link | https://velocity.desineuron.in/api/vault/generate-link | Generate trackable URL for shared asset |
| GET | /vault/{tracking_hash} | https://velocity.desineuron.in/vault/{tracking_hash} | Public access to trackable vault link (no auth required) |
### CCTV Surveillance Integration Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| POST | /api/cctv/event | https://velocity.desineuron.in/api/cctv/event | Ingest CCTV frame event from RTSP/ONVIF bridge |
| POST | /api/cctv/finalize-auto-mode | https://velocity.desineuron.in/api/cctv/finalize-auto-mode | Match or create lead after auto-mode session |
### Video Scene Mapping Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| POST | /api/scenes/upload | https://velocity.desineuron.in/api/scenes/upload | Upload CSV scene map for marketing video |
| GET | /api/scenes/{video_asset_id} | https://velocity.desineuron.in/api/scenes/{video_asset_id} | Get scene map for specific video asset |
### Marketing Videos Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| GET | /api/videos/marketing | https://velocity.desineuron.in/api/videos/marketing | List marketing videos available for Sentinel sessions |
### Mobile Edge Communication Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| GET | /api/mobile-edge/events | https://velocity.desineuron.in/api/mobile-edge/events | List communication events for a lead |
| POST | /api/mobile-edge/events | https://velocity.desineuron.in/api/mobile-edge/events | Log new communication event |
| GET | /api/mobile-edge/memory | https://velocity.desineuron.in/api/mobile-edge/memory | List memory facts for a lead |
| POST | /api/mobile-edge/imports | https://velocity.desineuron.in/api/mobile-edge/imports | Operator-assisted import of recording/note |
| POST | /api/mobile-edge/notes | https://velocity.desineuron.in/api/mobile-edge/notes | Quick note attachment to lead |
| GET | /api/mobile-edge/calendar | https://velocity.desineuron.in/api/mobile-edge/calendar | Calendar events for authenticated user |
| POST | /api/mobile-edge/calendar | https://velocity.desineuron.in/api/mobile-edge/calendar | Create calendar event |
| PATCH | /api/mobile-edge/calendar/{calendar_event_id} | https://velocity.desineuron.in/api/mobile-edge/calendar/{calendar_event_id} | Update calendar event |
| DELETE | /api/mobile-edge/calendar/{calendar_event_id} | https://velocity.desineuron.in/api/mobile-edge/calendar/{calendar_event_id} | Cancel calendar event |
| GET | /api/mobile-edge/transcripts/{event_id} | https://velocity.desineuron.in/api/mobile-edge/transcripts/{event_id} | Transcript segments for event |
| GET | /api/mobile-edge/insights/{lead_id} | https://velocity.desineuron.in/api/mobile-edge/insights/{lead_id} | Insight recommendations for lead |
| POST | /api/mobile-edge/insights/{recommendation_id}/act | https://velocity.desineuron.in/api/mobile-edge/insights/{recommendation_id}/act | Act on or dismiss insight |
| GET | /api/mobile-edge/alerts | https://velocity.desineuron.in/api/mobile-edge/alerts | Active alerts for authenticated user |
| POST | /api/mobile-edge/session | https://velocity.desineuron.in/api/mobile-edge/session | Register surface session heartbeat |
### Inventory Management Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| POST | /api/inventory/import-batches | https://velocity.desineuron.in/api/inventory/import-batches | Create inventory import batch |
| GET | /api/inventory/import-batches | https://velocity.desineuron.in/api/inventory/import-batches | List import batches |
| GET | /api/inventory/import-batches/{batch_id} | https://velocity.desineuron.in/api/inventory/import-batches/{batch_id} | Get batch status |
| POST | /api/inventory/properties | https://velocity.desineuron.in/api/inventory/properties | Create single property |
| GET | /api/inventory/properties | https://velocity.desineuron.in/api/inventory/properties | List inventory properties |
| GET | /api/inventory/properties/{property_id} | https://velocity.desineuron.in/api/inventory/properties/{property_id} | Get property details |
| PATCH | /api/inventory/properties/{property_id} | https://velocity.desineuron.in/api/inventory/properties/{property_id} | Update property |
| DELETE | /api/inventory/properties/{property_id} | https://velocity.desineuron.in/api/inventory/properties/{property_id} | Archive property |
| POST | /api/inventory/properties/{property_id}/media | https://velocity.desineuron.in/api/inventory/properties/{property_id}/media | Attach media to property |
| GET | /api/inventory/properties/{property_id}/media | https://velocity.desineuron.in/api/inventory/properties/{property_id}/media | List media for property |
| DELETE | /api/inventory/media/{media_asset_id} | https://velocity.desineuron.in/api/inventory/media/{media_asset_id} | Remove media asset |
### Admin Surface Control Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| GET | /api/admin-surface/health | https://velocity.desineuron.in/api/admin-surface/health | System health overview |
| GET | /api/admin-surface/queues | https://velocity.desineuron.in/api/admin-surface/queues | Queue depth snapshot |
| GET | /api/admin-surface/installs | https://velocity.desineuron.in/api/admin-surface/installs | Surface session/install overview |
| POST | /api/admin-surface/actions | https://velocity.desineuron.in/api/admin-surface/actions | Submit admin action |
| GET | /api/admin-surface/actions | https://velocity.desineuron.in/api/admin-surface/actions | List admin action history |
| GET | /api/admin-surface/actions/{action_event_id} | https://velocity.desineuron.in/api/admin-surface/actions/{action_event_id} | Get specific admin action |
| GET | /api/admin-surface/logs | https://velocity.desineuron.in/api/admin-surface/logs | Recent Oracle audit event log |
| GET | /api/admin-surface/templates | https://velocity.desineuron.in/api/admin-surface/templates | Template catalog admin view |
| POST | /api/admin-surface/templates/{template_id}/publish | https://velocity.desineuron.in/api/admin-surface/templates/{template_id}/publish | Publish template |
| POST | /api/admin-surface/templates/{template_id}/archive | https://velocity.desineuron.in/api/admin-surface/templates/{template_id}/archive | Archive template |
| GET | /api/admin-surface/template-chapters | https://velocity.desineuron.in/api/admin-surface/template-chapters | List template chapters (admin) |
| GET | /api/admin-surface/synthetic-jobs | https://velocity.desineuron.in/api/admin-surface/synthetic-jobs | List synthetic generation jobs |
| POST | /api/admin-surface/synthetic-jobs/{job_id}/cancel | https://velocity.desineuron.in/api/admin-surface/synthetic-jobs/{job_id}/cancel | Cancel synthetic job |
### CRM Canonical Data Endpoints
| Method | Path | Absolute URL | Description |
|--------|------|--------------|-------------|
| POST | /api/crm/imports | https://velocity.desineuron.in/api/crm/imports | Upload CSV batch for import |
| GET | /api/crm/imports | https://velocity.desineuron.in/api/crm/imports | List import batches |
| GET | /api/crm/imports/{batch_id} | https://velocity.desineuron.in/api/crm/imports/{batch_id} | Get batch detail and proposals |
| PUT | /api/crm/imports/{batch_id}/review-proposal | https://velocity.desineuron.in/api/crm/imports/{batch_id}/review-proposal | Review import proposal |
| POST | /api/crm/imports/{batch_id}/commit | https://velocity.desineuron.in/api/crm/imports/{batch_id}/commit | Commit approved proposals |
| GET | /api/crm/contacts | https://velocity.desineuron.in/api/crm/contacts | Canonical contact list with QD summary |
| POST | /api/crm/contacts | https://velocity.desineuron.in/api/crm/contacts | Create new contact |
| GET | /api/crm/contacts/{person_id} | https://velocity.desineuron.in/api/crm/contacts/{person_id} | Canonical contact detail |
| GET | /api/crm/client-360/{person_id} | https://velocity.desineuron.in/api/crm/client-360/{person_id} | Client 360 aggregated snapshot |
| GET | /api/crm/opportunities | https://velocity.desineuron.in/api/crm/opportunities | Opportunity pipeline list |
| GET | /api/crm/tasks | https://velocity.desineuron.in/api/crm/tasks | Reminder/task list |
| POST | /api/crm/tasks | https://velocity.desineuron.in/api/crm/tasks | Create new task |
| GET | /api/crm/kanban | https://velocity.desineuron.in/api/crm/kanban | Kanban board (canonical leads) |
| GET | /api/crm/qd/{person_id} | https://velocity.desineuron.in/api/crm/qd/{person_id} | QD score history for person |
| GET | /api/crm/client-data | https://velocity.desineuron.in/api/crm/client-data | List client data records |
| GET | /api/crm/client-data/{person_id} | https://velocity.desineuron.in/api/crm/client-data/{person_id} | Get client data for person |
| PATCH | /api/crm/client-data/{person_id} | https://velocity.desineuron.in/api/crm/client-data/{person_id} | Update client data |
| GET | /api/crm/client-data/{person_id}/timeline | https://velocity.desineuron.in/api/crm/client-data/{person_id}/timeline | Client data timeline |
| POST | /api/crm/client-data/{person_id}/tasks | https://velocity.desineuron.in/api/crm/client-data/{person_id}/tasks | Create task for client |
### Runtime LLM Endpoints
Method Path Absolute URL Description
GET /api/runtime/llm/providers https://velocity.desineuron.in/api/runtime/llm/providers List configured LLM providers and models
POST /api/runtime/llm/chat https://velocity.desineuron.in/api/runtime/llm/chat Execute single LLM chat completion
POST /api/runtime/llm/batch https://velocity.desineuron.in/api/runtime/llm/batch Submit persisted LLM batch job
GET /api/runtime/llm/jobs/{job_id} https://velocity.desineuron.in/api/runtime/llm/jobs/{job_id} Get batch job status
GET /api/runtime/llm/jobs/{job_id}/results https://velocity.desineuron.in/api/runtime/llm/jobs/{job_id}/results Get batch job results
### Infrastructure Notes
- **Caddy Reverse Proxy**: All endpoints are served through Caddy on port 443, proxying to FastAPI on localhost:8443 with TLS termination
- **Authentication**: JWT-based auth required for most endpoints (except public vault links)
- **WebSockets**: Real-time features use WebSocket connections for live updates
- **Role-Based Access**: Endpoints enforce role permissions (SENIOR_BROKER, ADMIN, etc.)
- **Tenant Isolation**: Multi-tenant architecture with tenant_id scoping
- **Audit Logging**: All mutations create immutable audit records
- **Health Checks**: System provides comprehensive health and queue monitoring endpoints

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,494 @@
# Desineuron AWS Coding Runtime Truth Book
Date: 2026-04-22
Scope: Coding runtime, Roo Code access, NemoClaw runtime, ingress routing, GPU recovery, model staging
## 1. Current Runtime Truth
The Desineuron shared coding runtime has been cut over from Ollama to SGLang while preserving the public contracts already used by the team.
Locked production decisions:
- Public contract remains stable.
- GPU inference remains on the AWS GPU worker, not on the Linux-origin box.
- Linux-origin remains the control plane.
- Ingress remains the stable routed entrypoint.
- `Qwen 3.6 35B A3B` remains the production target model for the current `4 x L4` rollout.
- `NemoClaw` moves onto the same shared runtime.
- There is no production fallback to Ollama after cutover.
Current live public routes:
- `https://velocity.desineuron.in/llm`
- `https://llm.desineuron.in`
Current live API shape after cutover:
- `https://velocity.desineuron.in/llm/v1/models`
- `https://velocity.desineuron.in/llm/v1/chat/completions`
- `https://llm.desineuron.in/v1/models`
- `https://llm.desineuron.in/v1/chat/completions`
- GPU SGLang bind: `172.31.46.190:30100`
- Linux-origin LLM route-sync target port: `30100`
## 2. Infra Split
### Linux-origin
Responsibilities:
- owns route-sync logic
- owns operational orchestration
- updates ingress upstream target when GPU private IP changes
- does not host the heavy model runtime
### Ingress
Responsibilities:
- terminates public hostname
- renders stable reverse-proxy contracts
- forwards `/llm/*` and `llm.desineuron.in` to the current GPU target
### GPU worker
Responsibilities:
- hosts SGLang
- hosts model payloads on NVMe only
- serves Roo Code, Oracle runtime, runtime LLM, and NemoClaw inference
Non-negotiable rules:
- do not use the GPU public IP directly
- do not keep model state on root disk
- keep all large model/runtime caches on GPU NVMe
## 3. Live Hardware Target
Current worker class:
- `g6.12xlarge`
- `4 x NVIDIA L4`
- `96 GB VRAM total`
Serving profile for this hardware:
- tensor parallel size `4`
- prompt-prefix caching enabled
- async / continuous batching enabled through SGLang
- FlashInfer preferred where supported by the live CUDA stack
Measured validation on the live GPU worker:
- host class: `g6.12xlarge`
- GPU layout: `4 x NVIDIA L4`
- model path used for the validated runtime: `/opt/dlami/nvme/models/Qwen-Qwen3.6-35B-A3B-FP8`
- SGLang served model ID used for the test: `qwen3.6-35b-a3b`
- validated SGLang launch profile:
- `--tp-size 4`
- `--attention-backend flashinfer`
- `--context-length 131072`
- `--mem-fraction-static 0.88`
- `--dist-init-addr 127.0.0.1:50000`
- `--enable-metrics`
- required bind rule on this SGLang build:
- public HTTP server must bind to the GPU private IP, not `0.0.0.0`
- internal scheduler keeps a loopback listener on the API port
- wildcard bind collides with that loopback listener on this build
- public validation after cutover:
- `https://velocity.desineuron.in/llm/v1/models` returns `200`
- `https://llm.desineuron.in/v1/models` returns `200`
- streamed chat TTFT through public ingress measured at about `2.36 s`
- one short non-stream completion measured about `33.86 completion tok/s`
## 4. Production Model Policy
### Primary production model
- user-facing family: `Qwen 3.6 35B A3B`
- exact SGLang served model ID: `qwen3.6-35b-a3b`
Why it remains live:
- fits the current `4 x L4` target
- already aligned with current team workflows
- suitable for coding/runtime use while the SGLang migration lands
- measured well enough for three concurrent coding users on the current hardware
### Staged future model on current L4 hardware
- `cyankiwi/Qwen3.5-122B-A10B-AWQ-4bit`
Status:
- acquisition/staging path is added
- not the live runtime on the current L4 cutover
- should be treated as a staged artifact for later runtime experimentation and hardware-fit validation
Why this is the right 122B staging path for the current worker:
- `4 x L4` is a better fit for an AWQ/int4 track than for an NVFP4 track
- this keeps the 122B experiment aligned with current hardware instead of assuming a Blackwell-oriented path
Why `txn545/Qwen3.5-122B-A10B-NVFP4` is not the active choice on L4:
- NVFP4 is not the safe default for the current L4 rollout
- if the team wants that track later, it should be treated as a separate hardware/runtime validation branch
Why no 122B model is the active live model in this round:
- the current migration is locked to preserving service continuity on the existing `4 x L4` worker
- the 122B track is a separate performance-fit and runtime-tuning exercise
## 5. Runtime Software Stack
Primary runtime after cutover:
- `SGLang`
Primary interface style:
- OpenAI-compatible `/v1/*`
Required runtime features:
- tensor parallel across all four GPUs
- prefix cache / prompt cache
- async scheduling
- continuous batching
- FlashInfer when supported by the live driver/runtime stack
Observed runtime note from the live bring-up:
- FlashInfer required `ninja-build` on the GPU box because it JIT-builds kernels on first run.
- The current GPU image needed:
- `ninja-build`
- `build-essential`
- After installing those packages, the FP8 runtime came up cleanly and served OpenAI-compatible traffic.
If stock SGLang underperforms:
- keep the same public routes
- tune CUDA/runtime behavior behind the same routed contract
- do not reintroduce Ollama fallback
## 6. Implemented Repo Changes
### Backend runtime service
File:
- `backend/services/runtime_llm_service.py`
Current state:
- provider catalog is standardized to `sglang`
- legacy provider names like `ollama` and `nemoclaw` are mapped into `sglang` to avoid immediate caller breakage
- model discovery uses `/v1/models`
### NemoClaw client
File:
- `backend/services/nemoclaw_client.py`
Current state:
- production path now targets the shared SGLang/OpenAI-compatible endpoint
- NVIDIA and Ollama production fallback logic is removed from the runtime path
- legacy env names still seed config where needed
### Prompt expander
File:
- `comfy_engine/scripts/prompt_expander.py`
Current state:
- now uses the shared OpenAI-compatible runtime instead of Ollama `/api/generate`
### NemoClaw deploy helper
File:
- `backend/scripts/nemoclaw_deploy.sh`
Current state:
- rewritten around SGLang-compatible inference
- no Ollama-era deployment assumptions
## 7. Route Sync And Stable Hostnames
Route-sync files:
- `infrastructure/desineuron_ingress/sync_llm_route.py`
- `infrastructure/desineuron_ingress/run_llm_route_sync.sh`
- `infrastructure/desineuron_ingress/desineuron-llm-route-sync.service`
- `infrastructure/desineuron_ingress/desineuron-llm-route-sync.timer`
- `infrastructure/desineuron_ingress/install_linux_llm_route_sync.sh`
Important behavior:
- Linux-origin discovers the current GPU private IP
- Linux-origin updates ingress-managed route state
- ingress forwards `llm.desineuron.in` and `/llm/*` to the GPU worker
Current safe default route-sync port in the repo:
- `11434`
Reason:
- the repo now contains the SGLang installer and watchdog, but the public route should not auto-cut from Ollama to SGLang until the GPU runtime is actually installed and validated on-host
- when SGLang is installed on the GPU worker, operators should flip `LLM_ROUTE_PORT` to the live SGLang port and then run route-sync
Manual operator-safe route sync entrypoint:
- `/usr/local/bin/run_llm_route_sync.sh`
This avoids the prior failure mode where operators accidentally used a system Python without `boto3`.
## 8. GPU Watchdog And Auto-Recovery
Added GPU-side scripts:
- `infrastructure/desineuron_ingress/install_gpu_sglang_runtime.sh`
- `infrastructure/desineuron_ingress/install_gpu_sglang_watchdog.sh`
Installed unit names expected on the GPU worker:
- `desineuron-sglang.service`
- `desineuron-sglang-watchdog.service`
- `desineuron-sglang-watchdog.timer`
Recovery policy:
- ensure the SGLang service is running
- verify `/v1/models` health locally
- if the configured model path is missing, rehydrate from the canonical source
- only report healthy after successful verification
Required recovery assertions for the SGLang watchdog:
- confirm the process is serving `/v1/models`
- confirm the returned model list contains `qwen3.6-35b-a3b`
- confirm all 4 GPUs are engaged during model load
- confirm FlashInfer dependencies are present before declaring runtime healthy
## 9. Model Rehydration And Staging
Added staging helper:
- `infrastructure/desineuron_ingress/acquire_qwen35_122b_nvfp4.sh`
Purpose:
- stages `cyankiwi/Qwen3.5-122B-A10B-AWQ-4bit` onto GPU NVMe by default
- does not automatically flip production traffic to that model
Expected current live model path style:
- `/opt/dlami/nvme/models/Qwen-Qwen3.6-35B-A3B-FP8`
Expected staged 122B path style:
- `/opt/dlami/nvme/models/cyankiwi-Qwen3.5-122B-A10B-AWQ-4bit`
## 10. Roo Code Team Setup
After SGLang cutover, team members should stop using the Ollama provider mode for Desineuron-hosted inference.
Canonical team profile:
- API Provider: OpenAI-compatible / custom OpenAI
- Base URL: `https://llm.desineuron.in/v1`
- Model: `qwen3.6-35b-a3b`
- Temperature: `0.1` to `0.2`
- Server context ceiling: `131072`
- Recommended Roo context: `131072`
Team decision for this wave:
- all three team members can target `128K` context through the same shared runtime
- if real concurrent repo-heavy usage causes OOM or latency regression, the first rollback knob is the client context setting, not the model family
- the current production-ready long-context path is pure VRAM on `4 x L4`, not host-RAM spill
## 11. Measured SGLang Performance
Benchmark date:
- `2026-04-22`
Benchmark topology:
- live AWS GPU worker
- `SGLang + Qwen 3.6 35B A3B FP8`
- tensor parallel `4`
- FlashInfer enabled
- async scheduler / SGLang default continuous batching path
- prompt-prefix caching available in runtime
- server context ceiling: `131072`
Measured results:
- time to first token: `0.12 s`
- streamed completion wall time for a short coding/planning answer: `1.31 s`
- test concurrency: `3`
- aggregate wall time for `3 x 256-token` responses: `3.61 s`
- aggregate completion tokens: `768`
- aggregate prompt tokens: `168`
- aggregate total tokens: `936`
- aggregate completion throughput: `212.76 tokens/s`
Per-request timing under `3` concurrent requests:
- request 1: `3.608 s` for `256` completion tokens
- request 2: `3.609 s` for `256` completion tokens
- request 3: `3.608 s` for `256` completion tokens
Long-context smoke validation:
- prompt size validated: `50010` prompt tokens
- completion size: `8` tokens
- total request size: `50018` tokens
- wall time: `8.345 s`
Operational interpretation:
- the runtime is fast enough for three simultaneous coding users
- TTFT is already in the sub-200 ms range on the warmed runtime
- aggregate decode throughput is materially better than the previous Ollama-backed path while holding a `128K` server context ceiling
- `Qwen 3.6 35B A3B` is the correct production choice for the current one-week delivery window
## 12. Cutover Guidance
Use this model ID consistently across SGLang-facing clients:
- `qwen3.6-35b-a3b`
Do not use this older Ollama-style model ID against SGLang:
- `qwen3.6:35b-a3b`
Why:
- SGLang rejects colons in `served_model_name`
- the colon is reserved internally for adapter syntax
Backend compatibility note:
- the Velocity backend can still map legacy provider naming internally
- external Roo Code and OpenAI-compatible clients should use the hyphenated SGLang model ID only
Canonical Roo configuration:
- API Provider: `OpenAI-compatible` or `Custom OpenAI`
- Base URL: `https://llm.desineuron.in/v1`
- Model: `qwen3.6-35b-a3b`
- Context window: `131072`
- Temperature: `0.1` to `0.2`
Recommended initial values:
- `Base URL`: `https://llm.desineuron.in/v1`
- `Model`: `qwen3.6-35b-a3b`
- `Context Window Size (num_ctx equivalent)`: `131072`
Do not use:
- Ollama provider mode pointing at the public Desineuron route after the cutover
Reason:
- the stable contract is moving to SGLang's OpenAI-compatible interface
## 13. Most Efficient Working Long-Context Strategy On Current Hardware
Strategies tested against the live `4 x L4` worker:
1. Pure-VRAM `131072` context on SGLang with tensor parallel `4`
Result:
- works
- preserves sub-200 ms TTFT on warm short prompts
- preserved about `212.76 tok/s` aggregate completion throughput in the 3-user benchmark
2. Hierarchical host-memory cache with `131072` context
Result:
- not production-safe on the current stack for this model
- first failed on a model-specific `page_size=1` requirement for the hybrid Mamba cache
- second attempt progressed further but one rank died with exit code `-9`
- current interpretation: this path is materially less stable than the pure-VRAM profile
Current decision:
- keep `131072` in VRAM as the production target
- do not use host-RAM hierarchical cache for this model in the current rollout
- if more headroom is needed later, tune kernels and scheduling first before re-opening host-memory spill
## 14. NemoClaw Runtime Policy
NemoClaw should use the same shared SGLang runtime as:
- Roo Code
- Oracle runtime
- backend runtime LLM jobs
This is a deliberate single-stack decision:
- one serving runtime
- one model family for the current wave
- one stable routed contract
If later profiles differ, express that with config, not with a second serving stack in this phase.
## 15. Endpoint Checklist
These should work after cutover:
- `https://velocity.desineuron.in/llm/v1/models`
- `https://velocity.desineuron.in/llm/v1/chat/completions`
- `https://llm.desineuron.in/v1/models`
- `https://llm.desineuron.in/v1/chat/completions`
Internal backend envs:
- `LLM_BASE_URL`
- `SGLANG_BASE_URL`
- `SGLANG_CHAT_URL`
- `SGLANG_MODELS_URL`
- `SGLANG_MODEL`
- `SGLANG_API_TOKEN`
## 16. What Is Left
Still required to complete the migration end to end:
1. Persist the `131072` launch profile into the GPU systemd runtime using the updated installer.
2. Reinstall or update the GPU watchdog so it validates the same `131072` service profile.
3. Repoint Linux-origin route-sync env from `11434` to the live SGLang port after GPU validation.
4. Validate both public routes against `/v1/models`.
5. Run one more public-route benchmark through ingress after cutover to capture real routed TTFT.
6. Generate tuned L4-specific runtime configs if we want to push further on throughput without lowering context.
7. Keep the 122B track separate; it is not part of the current production coding-runtime choice.
## 17. Team Hand-Off
For Roo Code today, once cutover is complete, the team only needs:
- Base URL: `https://llm.desineuron.in/v1`
- Model: `qwen3.6-35b-a3b`
- Context window: `131072`
- Provider type: OpenAI-compatible
For operators, the important truth is:
- Linux-origin controls routing
- ingress owns the stable hostname
- GPU box owns inference
- NVMe owns model state
- SGLang is the production runtime

View File

@@ -0,0 +1,209 @@
# Oracle Canvas Codebook Production Truth
Date: 2026-04-19
Repo: `Project_Velocity`
## Purpose
This document freezes the current production truth for the Oracle Canvas template/codebook system, the expanded GPT and Claude corpora, the runtime merge policy, and the current rendering limits that matter for delivery.
This is not a concept note. It is the implementation-facing truth for the Oracle template layer as it exists now.
## Current Source Of Truth
The Oracle template book is split across three layers:
1. Structural database schema
- `backend/oracle/schema_extension_v2.sql`
- Defines:
- `oracle_template_chapters`
- `oracle_template_subchapters`
- `oracle_template_seed_examples`
- chapter/subchapter linkage on `oracle_component_templates`
- `oracle_synthetic_generation_jobs`
2. Runtime seed DB
- `backend/oracle/oracle_template_seed_db.json`
- This is the lightweight fallback DB shipped with the runtime.
- It is structurally correct but incomplete relative to the intended corpus.
3. Expanded authoring corpora
- GPT pack:
- `Project_Velocity/.Agent Context/Sprint 1/Sayan Multi-Surface and Oracle Delivery Pack/Sample JSON Schema/GPT 5.4/oracle_canvas_json_expansion_pack/db/oracle_template_seed_db_expanded_v1.pretty.json`
- Claude pack:
- `Project_Velocity/.Agent Context/Sprint 1/Sayan Multi-Surface and Oracle Delivery Pack/Sample JSON Schema/Claude Sonnet 4.6/oracle_template_expansion/oracle_template_seed_db_expanded.json`
4. Frozen runtime merge artifact
- `backend/oracle/oracle_runtime_codebook_merged.json`
- This is the deploy-safe merged corpus generated from the GPT and Claude packs.
- Production should prefer this file over the authoring packs whenever it is present.
## Corpus Status
The expanded corpora are materially useful and production-relevant.
### GPT 5.4 pack
- Chapters: `6`
- Subchapters: `24`
- Seed examples: `1200`
- Shape: already close to runtime needs
- Key field for examples: `seed_examples`
### Claude Sonnet 4.6 pack
- Chapters: `6`
- Subchapters: `24`
- Examples: `1200`
- Key field for examples: `examples`
- Shape: close, but requires normalization into runtime form
### Runtime fallback pack
- Chapters: `6`
- Subchapters: `24`
- Seed examples declared in metadata: `36`
- Seed examples physically present: lower than metadata
- Useful only as a fallback, not as the primary production corpus
## Super Codebook Policy
The current runtime now treats the codebook as a merged corpus rather than a single-file static DB.
The merge policy is:
1. Load GPT pack first.
2. Load Claude pack second.
3. Load runtime fallback pack last.
4. Normalize all example records to one runtime contract.
5. Deduplicate by:
- `subchapter_id`
- `template_name`
- `title`
6. Prefer in this order:
- GPT 5.4 examples
- canonical examples
- fallback records only when no richer example exists
This behavior is implemented in:
- `backend/oracle/codebook_service.py`
- `backend/scripts/build_oracle_runtime_codebook.py`
That file is now the effective runtime “super codebook” layer.
The generated runtime artifact currently contains the merged deployable corpus and is suitable for Linux-box deployment without requiring `.Agent Context` lookups at request time.
## What The Runtime Actually Uses
The runtime no longer needs to rely on hardcoded template lists in the Oracle v1 router.
The codebook service now provides:
- merged corpus loading
- search over both corpora
- normalized template listing
- best-match template synthesis from a user prompt
Primary runtime functions:
- `codebook_service.stats()`
- `codebook_service.list_templates(...)`
- `codebook_service.search_examples(prompt, limit=...)`
- `codebook_service.synthesize_template(prompt, data_shapes=...)`
## Current Supported Runtime Output Families
The expanded corpora include more component types than the current frontend renderer supports directly.
The current production-safe strategy is:
1. keep the full codebook corpus
2. map high-variety codebook component families into a smaller supported runtime renderer set
3. let Oracle render reliably today instead of failing on unsupported component types
### Supported runtime renderers today
- `textCanvas`
- `kpiTile`
- `barChart`
- `lineChart`
- `geoMap`
- `table`
- `pipelineBoard`
- `timeline`
- `activityStream`
- `errorNotice`
### Codebook-to-runtime normalization policy
Examples:
- `summary_card`, `summary_strip`, `metric_card_group`, `gauge_stack`
- mapped to `kpiTile`
- `lead_profile_card`, `property_card`, `data_table`, `leaderboard_table`, `matrix_grid`
- mapped to `table`
- `interaction_timeline`, `message_thread_summary`
- mapped to `activityStream`
- `heatmap`
- mapped to `geoMap`
This is deliberate. It keeps the UI stable while preserving the larger design vocabulary inside the template book.
## What Is Production-Ready Now
- Oracle template DB schema exists.
- Oracle template taxonomy APIs exist.
- Expanded GPT and Claude corpora are available locally in the repo.
- Runtime codebook merge and retrieval is implemented in `codebook_service.py`.
- A frozen merged runtime codebook now exists at `backend/oracle/oracle_runtime_codebook_merged.json`.
- Oracle v1 template listing/synthesis is being moved to the codebook-backed path.
- Oracle backend can now emit `textCanvas` planning blocks and the frontend has a renderer for them.
## What Is Still Constrained
- The runtime is not yet rendering all 47+ component families natively.
- The current system uses safe projection into supported runtime renderers.
- The template taxonomy routes existed, but were incorrectly using `user.role` as `tenant_id`; that has been corrected toward a fixed Oracle tenant policy.
- The lightweight fallback JSON DB remains incomplete and should not be treated as the main corpus.
## What Nemoclaw / Oracle Should Use For Retrieval
The correct order for Oracle prompt handling is:
1. Parse prompt.
2. Retrieve matching codebook examples from the merged corpus.
3. Build a safe retrieval plan against allowed DB datasets.
4. Query live CRM/intelligence/inventory datasets.
5. Build Oracle Canvas JSON with supported runtime component types.
6. Append to the existing canvas.
The codebook is not the final UI payload by itself.
It is the reference layer that guides:
- component family selection
- chapter/subchapter intent
- layout direction
- data-shape expectations
- policy hints
- backend contract hints
## Recommended Near-Term Hardening
1. Materialize a generated runtime codebook file if Linux deployment should not depend on `.Agent Context`.
2. Add explicit metadata versioning to the merged corpus.
3. Add a small admin endpoint for codebook stats and source summary.
4. Expand renderer coverage incrementally rather than trying to support all component families at once.
5. Add a batch offline export path if the team wants a frozen deploy artifact.
## Operator Bottom Line
The Oracle “book with chapters and JSON schema examples” is real and already useful.
The correct production interpretation is:
- DB schema and APIs are already present
- GPT and Claude expansion packs are the real high-value corpus
- `backend/oracle/codebook_service.py` is the runtime super-codebook layer
- Oracle should retrieve from this merged corpus first, then query live DB data, then render supported JSON Canvas components

View File

@@ -0,0 +1,382 @@
# Oracle Canvas Runtime and Ollama Batch Architecture
Date: 2026-04-19
Repo: `Project_Velocity`
## Purpose
This document defines the current production Oracle Canvas runtime path, the intended Ollama/Nemoclaw model-routing strategy, and the target batch-processing API shape the team can use if Velocity exposes Oracle or coding-agent capabilities through the local model stack.
This is the operator and engineering artifact. It exists to remove ambiguity.
## Runtime Topology
### Linux origin box
Role:
- hosts Velocity frontend
- hosts FastAPI backend
- hosts PostgreSQL and application services
- terminates app-origin requests under the public site path
Primary concern:
- application routing
- auth/session enforcement
- Oracle API execution
- CRM/intelligence/inventory data access
### GPU box
Role:
- hosts ComfyUI
- hosts heavy model runtime
- hosts Ollama / Nemoclaw execution plane
- stores runtime/model payloads on NVMe only
Primary concern:
- inference
- media generation
- model serving
- agent runtime workloads
### Ingress
Role:
- stable public entry for GPU-backed services
- hides raw GPU host details from application code
Non-negotiable rule:
- never wire Oracle or frontend code to a raw GPU public IP
## Oracle Canvas Current Execution Path
The production-safe Oracle path is now:
1. User submits prompt from Oracle Canvas frontend.
2. Frontend calls:
- `/api/oracle/v1/canvas-pages/{page_id}/prompts`
3. FastAPI Oracle orchestrator:
- loads user context
- retrieves best codebook matches
- builds a safe retrieval plan
- queries approved datasets from PostgreSQL
- produces JSON Canvas components
- commits a page revision
4. Frontend reloads/reconciles the canvas state and renders the new blocks.
## Current Oracle Backend Families
### Live today
- `/api/oracle/v1/me`
- `/api/oracle/v1/canvas-pages/{page_id}`
- `/api/oracle/v1/canvas-pages/{page_id}/prompts`
- `/api/oracle/v1/canvas-pages/{page_id}/forks`
- `/api/oracle/v1/canvas-pages/{page_id}/rollback`
- `/api/oracle/v1/canvas-pages/{page_id}/revisions`
- `/api/oracle/v1/component-templates`
- `/api/oracle/v1/component-templates/synthesize`
- `/api/oracle/v1/merge-requests`
- `/api/oracle/v1/merge-requests/{mr_id}/review`
- `/ws/oracle/canvas/{page_id}`
### Template taxonomy routes
- `/api/oracle/template-chapters`
- `/api/oracle/template-subchapters`
- `/api/oracle/component-templates`
- `/api/oracle/component-templates/{id}`
- `/api/oracle/component-templates/{id}/seed`
- `/api/oracle/component-templates/synthetic-jobs`
## Prompt Analysis Path
Oracle should not rely on one monolithic LLM call.
The correct production split is:
1. codebook retrieval
2. safe dataset selection
3. optional LLM planning
4. live DB fetch
5. JSON Canvas synthesis
6. revision commit
### Why this split is correct
- It reduces hallucination in UI structure.
- It keeps DB access whitelisted and auditable.
- It allows Oracle to keep working even when the LLM runtime is degraded.
- It keeps the Oracle Canvas deterministic enough for operational use.
## Current Model Routing Truth
### Present reality
The current Oracle backend has these runtime modes:
- `codebook_retrieval`
- preferred when the prompt clearly matches the Oracle template corpus
- `nemoclaw_hosted`
- used when `NEMOCLAW_API_URL` and `NEMOCLAW_API_KEY` are configured and reachable
- `deterministic_fallback`
- used when the LLM planner is unavailable
### What Nemoclaw currently means in code
Current dispatch abstraction:
- `backend/services/nemoclaw_runtime.py`
This file is still a light dispatch envelope, not a fully featured provider router.
### Recommended production provider stack
Provider order:
1. codebook retrieval layer
2. Nemoclaw planner endpoint
3. local Ollama fallback
4. deterministic fallback
## Recommended Ollama Model Policy
### Default planning / Oracle analysis model
Use a local reasoning-capable model behind Ollama when Nemoclaw is not available or when the team wants deterministic private execution.
Recommended candidate:
- `qwen3.6:35b-a3b`
Reason:
- strong agentic coding and structured reasoning profile
- local execution path through Ollama
- realistic fit for GPU-box-hosted inference
### Deployment command
Example:
```bash
ollama run qwen3.6:35b-a3b
```
### Routing rule
- Oracle prompt planning:
- small to medium prompts: local Ollama `qwen3.6:35b-a3b`
- larger multi-step analytical plans: Nemoclaw planner if available
- Coding-agent batch workloads:
- Ollama first for local/private jobs
- Nemoclaw for heavier orchestration when the runtime is healthy
## Runtime LLM API
The backend now exposes a first-class runtime LLM family:
- `GET /api/runtime/llm/providers`
- `POST /api/runtime/llm/chat`
- `POST /api/runtime/llm/batch`
- `GET /api/runtime/llm/jobs/{job_id}`
- `GET /api/runtime/llm/jobs/{job_id}/results`
This router is mounted in:
- `backend/api/routes_runtime_llm.py`
The current persistence path uses the existing canonical table:
- `workflow_agent_runs`
That means batch jobs are now persisted against the live Velocity schema without requiring a new table family before the first production rollout.
## Implemented Batch Processing API
This is no longer only a proposal. The following contract family exists now and can be used by Oracle or future coding-agent surfaces.
### Single request inference
- `POST /api/runtime/llm/chat`
Payload:
```json
{
"provider": "ollama",
"model": "qwen3.6:35b-a3b",
"system_prompt": "You are Oracle Planner.",
"messages": [
{ "role": "user", "content": "Build a CRM pipeline view for high-intent NRI buyers." }
],
"temperature": 0.2,
"response_format": "json"
}
```
### Batch submission
- `POST /api/runtime/llm/batch`
Payload:
```json
{
"provider": "ollama",
"model": "qwen3.6:35b-a3b",
"job_type": "oracle_canvas_planning",
"items": [
{
"request_id": "req_001",
"messages": [
{ "role": "user", "content": "Show overdue high-QD follow-ups." }
],
"response_format": "json"
},
{
"request_id": "req_002",
"messages": [
{ "role": "user", "content": "Build a Kolkata luxury inventory comparison block." }
],
"response_format": "json"
}
]
}
```
### Batch status
- `GET /api/runtime/llm/jobs/{job_id}`
Response:
```json
{
"job_id": "job_123",
"status": "running",
"provider": "ollama",
"model": "qwen3.6:35b-a3b",
"submitted_count": 2,
"completed_count": 1,
"failed_count": 0
}
```
### Batch results
- `GET /api/runtime/llm/jobs/{job_id}/results`
### Providers inventory
- `GET /api/runtime/llm/providers`
Example response:
```json
{
"providers": [
{
"id": "nemoclaw",
"status": "online",
"models": ["nemotron", "remote_default"]
},
{
"id": "ollama",
"status": "online",
"models": ["qwen3.6:35b-a3b"]
}
]
}
```
## Batch Processing Design Rules
1. Batch jobs must be persisted.
2. Batch items must be individually addressable by `request_id`.
3. Every batch job must record:
- provider
- model
- submitted payload hash
- start/end timestamps
- failure reason
4. Oracle must not block the main request thread for large batches.
5. Any DB writeback generated from a batch must go through approval tables, not direct execution.
## Oracle-Specific Runtime Policy
For Oracle Canvas, the LLM is not the source of truth for data.
The source of truth order is:
1. canonical DB tables
2. approved dataset projections
3. codebook template corpus
4. model planner
The model is only allowed to:
- classify intent
- choose likely component families
- propose layout direction
- summarize findings
The model is not allowed to:
- invent database facts
- bypass dataset allowlists
- emit arbitrary executable code into production rendering paths
## Current Production Readiness Assessment
### Ready now
- Oracle Canvas frontend-to-backend v1 route family
- codebook-backed template retrieval path
- safe DB execution gateway
- merge/fork/revision path
- deterministic fallback path
- runtime LLM provider inventory
- runtime single-chat execution
- runtime persisted batch execution through `workflow_agent_runs`
- Oracle planner fallback through the shared runtime LLM service
### Still needs explicit implementation if the team approves
- per-model selection UI in Catalyst or Oracle controls
- dedicated `runtime_llm_jobs` / `runtime_llm_job_items` tables if the team wants stronger audit/query ergonomics than `workflow_agent_runs`
- explicit Nemoclaw vs Ollama operator switch in a production admin surface
- richer provider health telemetry beyond simple reachability
## Recommended Next Build Steps
1. Add a dedicated runtime router:
- `backend/api/routes_runtime_llm.py`
2. Add DB tables:
- `runtime_llm_jobs`
- `runtime_llm_job_items`
- `runtime_llm_job_results`
3. Implement provider adapters:
- Nemoclaw adapter
- Ollama adapter
4. Expose provider status to Catalyst/Oracle settings surfaces.
5. Keep Oracle Canvas on the current codebook-first path even after LLM batching exists.
## Bottom Line
Oracle Canvas should be treated as a codebook-guided analytical surface with optional LLM planning, not as a raw chat-to-SQL toy.
The production-safe architecture is:
- Linux origin runs the application and DB access
- GPU box runs ComfyUI and model inference
- Oracle retrieves from the merged codebook first
- DB access stays whitelisted
- Nemoclaw and Ollama sit behind a documented provider interface
- batch processing is a separate runtime service contract, not an implicit side effect of the canvas endpoint

View File

@@ -0,0 +1,10 @@
# Deprecated Title
This document has been superseded by:
- [Desineuron AWS Coding Runtime Truth Book](F:\Workin In Progress\DESINEURON\GITLAB\Project_Velocity\.Agent Context\Desineuron AWS Coding Runtime Truth Book.md)
Reason:
- the coding runtime is no longer being tracked as an Ollama-only Qwen note
- the canonical truth now covers SGLang, Roo Code access, NemoClaw runtime, route-sync, watchdog recovery, and staged support for `txn545/Qwen3.5-122B-A10B-NVFP4`

891
.Agent Context/README.md Normal file
View File

@@ -0,0 +1,891 @@
# Project Velocity — Truthbook
> **What this is:** The single source of truth for Project Velocity. If it's written down here, it's how the system works — not how someone hoped it would work.
---
## Table of Contents
1. [What Is Project Velocity](#what-is-project-velocity)
2. [Quick Start](#quick-start)
3. [Architecture Overview](#architecture-overview)
4. [Runtime Truth](#runtime-truth)
5. [Team Setup](#team-setup)
6. [GPU & Model Runtime](#gpu--model-runtime)
7. [Infrastructure](#infrastructure)
8. [Runbooks](#runbooks)
9. [API Reference](#api-reference)
10. [Contributing](#contributing)
---
## What Is Project Velocity
Project Velocity is a multi-agent AI development platform. It orchestrates intelligent agents (powered by Qwen 3.6 35B A3B and other models) to collaborate on software engineering tasks — code generation, review, testing, deployment — as a coordinated team rather than isolated tools.
**Why it exists:** Single-agent coding tools hit a ceiling. They lack context persistence, cross-task coordination, and operational reliability. Velocity solves this by:
- **Multi-agent collaboration** — Agents communicate via WebSocket channels and shared memory
- **Persistent state** — PostgreSQL backs user data, CRM records, and agent memory
- **GPU-accelerated inference** — Local Ollama runtime on NVIDIA GPU hardware
- **Role-based access control** — Admin and standard user tiers with avatar support
- **Live event broadcasting** — Real-time campaign and catalyst events via WebSocket
**Core stack:**
| Layer | Technology |
|-------|-----------|
| Backend API | Python / FastAPI |
| Database | PostgreSQL (via `databases` library with connection pooling) |
| Frontend | React 19 + TypeScript + Vite + Tailwind CSS + Framer Motion |
| Inference | Ollama (Qwen 3.6 35B A3B primary model) |
| Real-time | WebSocket (Catalyst channel, CRM channel) |
| Deployment | systemd services on Linux with NVIDIA GPU |
---
## Quick Start
### Prerequisites
- **GPU Machine:** NVIDIA GPU with sufficient VRAM (≥16GB recommended for Qwen 3.6 35B A3B)
- **NVMe Storage:** For model weights and cache
- **Linux OS:** Ubuntu 22.04+ or equivalent
- **Python 3.11+:** Backend runtime
- **Node.js 18+:** Frontend build
- **Ollama:** Latest stable with Qwen 3.6 35B A3B model pulled
- **PostgreSQL 15+:** Database backend
### One-Line Bootstrap
```bash
bash bootstrap/setup.sh
```
This script handles:
1. GPU driver verification
2. Ollama installation and model pull
3. PostgreSQL setup
4. Backend dependency installation
5. Frontend dependency installation
6. systemd service creation
### Manual Setup
#### 1. GPU & Ollama
```bash
# Verify GPU
nvidia-smi
# Install Ollama
curl -fsSL https://ollama.ai/install.sh | sh
# Pull the primary model
ollama pull qwen3.6:35b-a3b
# Verify model is loaded
curl http://localhost:11434/api/tags | jq '.models[] | select(.name == "qwen3.6:35b-a3b")'
```
#### 2. Database
```bash
# Start PostgreSQL
sudo systemctl start postgresql
# Create database and user
psql -U postgres -c "CREATE DATABASE velocity;"
psql -U postgres -c "CREATE USER velocity WITH PASSWORD 'secure_password';"
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE velocity TO velocity;"
```
#### 3. Backend
```bash
cd Project_Velocity/backend
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp .env.example .env
# Edit .env with your database credentials and secrets
# Run migrations
python migrate.py
# Start server
uvicorn main:app --host 0.0.0.0 --port 8000
```
#### 4. Frontend
```bash
cd Project_Velocity/app
# Install dependencies
npm install
# Start dev server
npm run dev
```
Frontend is now available at `http://localhost:5173`.
#### 5. Verify Everything
```bash
# Backend health
curl http://localhost:8000/health
# Model availability
curl http://localhost:11434/api/tags
# Frontend
open http://localhost:5173
```
---
## Architecture Overview
### System Diagram
```
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ React UI │────▶│ FastAPI │────▶│ PostgreSQL │
│ (Port 5173)│◀────│ (Port 8000) │◀────│ (Port 5432)│
└─────────────┘ └──────┬───────┘ └─────────────┘
┌──────────────┐
│ Ollama │
│ (Port 11434) │
│ Qwen 3.6 35B │
└──────────────┘
┌──────────────┐
│ NVIDIA GPU │
└──────────────┘
```
### Component Breakdown
#### Backend (`backend/`)
[`main.py`](Project_Velocity/backend/main.py) — FastAPI application with:
- **Auth system** — Login, profile lookup, user listing, avatar upload
- **WebSocket managers** — [`_CatalystManager()`](Project_Velocity/backend/main.py:296) and [`_CRMManager()`](Project_Velocity/backend/main.py:320) for real-time event broadcasting
- **Connection pooling** — PostgreSQL via `databases` library with async context management
- **Lifespan hooks** — [`lifespan()`](Project_Velocity/backend/main.py:83) initializes and cleans up resources
Key endpoints:
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/auth/login` | POST | Authenticate user |
| `/api/auth/me` | GET | Get current user profile |
| `/api/auth/users` | GET | List all users (admin) |
| `/api/auth/profile/avatar` | POST | Upload profile avatar |
| `/ws/catalyst` | WS | Catalyst event channel |
| `/ws/crm` | WS | CRM event channel |
| `/health` | GET | Health check |
#### Frontend (`app/`)
[`App.tsx`](Project_Velocity/app/src/App.tsx) — React application with:
- **Protected routes** — [`ProtectedRoute()`](Project_Velocity/app/src/App.tsx:66) wraps authenticated paths
- **Route module sync** — [`RouteModuleSync()`](Project_Velocity/app/src/App.tsx:90) handles dynamic route loading
- **Main layout** — [`MainLayout()`](Project_Velocity/app/src/App.tsx:90) provides chrome (header, sidebar, content area)
- **Role rendering** — [`formatRoleLabel()`](Project_Velocity/app/src/App.tsx:379) converts role codes to display labels
- **Auth state management** — Dual `useEffect` hooks handle token persistence and user fetch
#### Agent Context (`.Agent Context/`)
Documents that define how agents operate within Velocity:
- [`Qwen 3.6 35B A3B Ollama Access, Recovery, and Team Setup.md`](Project_Velocity/.Agent%20Context/Qwen%203.6%2035B%20A3B%20Ollama%20Access,%20Recovery,%20and%20Team%20Setup.md) — Model runtime, recovery policies, team onboarding
- `README.md` — This file
#### Infrastructure (`.Infrastructure/`)
Deployment and operational documentation:
- systemd unit files for backend, frontend, Ollama services
- Network configuration and ingress rules
- Monitoring and alerting setup
---
## Runtime Truth
### What "Works" Means in Velocity
Velocity has three runtime layers, each with different failure modes:
#### Layer A: Fast Runtime Recovery
If the API crashes or restarts:
- PostgreSQL connection pool rebuilds automatically via [`lifespan()`](Project_Velocity/backend/main.py:83)
- WebSocket managers reinitialize and accept new connections
- No data loss — all state is in PostgreSQL
#### Layer B: Model Rehydration Recovery
If Ollama loses the Qwen model:
- Watchdog systemd unit detects absence via `/api/tags`
- Auto-registers model from NVMe cache or S3 artifact storage
- **Production requirement:** Same-run auto-hydration logic must complete before any agent request
#### Layer C: Full System Recovery
If everything goes down:
1. PostgreSQL recovers WAL logs
2. Ollama watchdog restores model
3. Backend systemd unit restarts API
4. Frontend rebuilds if artifacts are corrupted
### Critical Contracts
**Auth contract:**
```
Client → POST /api/auth/login {email, password}
→ 200 OK {token, user}
Client → GET /api/auth/me (Authorization: Bearer <token>)
→ 200 OK {id, email, role, avatar_url}
→ 401 Unauthorized
```
**WebSocket contract:**
```
Client → WS /ws/catalyst
→ Accepts live events: {event_type, campaign_name, value, timestamp}
Client → WS /ws/crm
→ Accepts CRM events: {type, payload, timestamp}
```
**Model contract:**
```
Ollama → GET /api/tags returns qwen3.6:35b-a3b
→ Context window: 131072 tokens
→ Provider: OpenAI-compatible interface at http://localhost:11434/v1
```
---
## Team Setup
### Developer Onboarding
#### 1. Clone & Bootstrap
```bash
git clone <repo-url>
cd Project_Velocity
bash bootstrap/setup.sh
```
#### 2. VS Code / Roo Code Configuration
Edit `.vscode/settings.json`:
```json
{
"roo-cline.provider": "openai-compatible",
"roo-cline.baseUrl": "http://localhost:11434/v1",
"roo-cline.modelId": "qwen3.6:35b-a3b",
"roo-cline.contextWindow": 131072,
"roo-cline.temperature": 0.7
}
```
#### 3. Verify Team Access
```bash
# Backend health
curl http://localhost:8000/health
# Expected: {"status": "ok"}
# Model loaded
curl http://localhost:11434/api/tags | jq -r '.models[].name'
# Expected: qwen3.6:35b-a3b
# Frontend
open http://localhost:5173
# Expected: Login screen
```
### Role Definitions
| Role | Access Level | Can Do |
|------|-------------|--------|
| `admin` | Full | User management, system config, agent orchestration |
| `developer` | Standard | Code generation, review, testing |
| `viewer` | Read-only | Dashboard, campaign monitoring |
### Performance Expectations
| Scenario | Tokens/sec | Latency |
|----------|-----------|---------|
| Single-stream (local GPU) | ~80-120 tok/s | ~200ms first token |
| Two concurrent requests | ~60-90 tok/s each | ~300ms first token |
| Four-way batch | ~40-60 tok/s each | ~500ms first token |
*Numbers vary by GPU hardware. Measure your setup.*
---
## GPU & Model Runtime
### Hardware Requirements
| Component | Minimum | Recommended |
|-----------|---------|-------------|
| GPU VRAM | 16GB | 24GB+ |
| GPU Compute | Turing architecture | Ada Lovelace / Hopper |
| NVMe Storage | 50GB free | 100GB+ NVMe Gen4 |
| RAM | 32GB | 64GB+ |
### Ollama Watchdog
The watchdog is a systemd-managed service that ensures the Qwen model stays loaded:
**Location:** `.Infrastructure/systemd/ollama-watchdog.service`
**Behavior:**
1. Every 60 seconds, queries `http://localhost:11434/api/tags`
2. If `qwen3.6:35b-a3b` is absent, triggers rehydration
3. Rehydration priority: NVMe cache → S3 artifact → remote pull
4. Logs all actions to journalctl
**Manual watchdog check:**
```bash
sudo systemctl status ollama-watchdog
journalctl -u ollama-watchdog --since "1 hour ago"
```
### Model Hydration Strategies
| Strategy | Speed | Use Case |
|----------|-------|----------|
| NVMe local registration | ~2 seconds | Primary recovery path |
| Local manifest `ollama create` | ~5 seconds | Fresh hydration from extracted weights |
| S3 cold hydrate | ~60-300 seconds | No local cache available |
### Critical: What Watchdog Must NOT Do
- ❌ Delete model layers during recovery
- ❌ Modify GPU memory directly
- ❌ Block agent requests during hydration (graceful degradation only)
- ❌ Restart Ollama process unless absolutely necessary
---
## Infrastructure
### Deployment Topology
```
┌─────────────────────────────────────────────────┐
│ Production Host │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Backend │ │ Frontend │ │ Ollama │ │
│ │ :8000 │ │ :5173 │ │ :11434 │ │
│ │ systemd │ │ nginx │ │ systemd │ │
│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────┴───────────────┘ │
│ │ │
│ ┌──────▼───────┐ │
│ │ PostgreSQL │ │
│ │ :5432 │ │
│ │ systemd │ │
│ └──────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ NVIDIA GPU (CUDA + TensorRT) │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
```
### systemd Services
| Service | File | Restart Policy |
|---------|------|---------------|
| Backend API | `velocity-backend.service` | always |
| Frontend (nginx) | `velocity-frontend.service` | always |
| Ollama | `ollama.service` | on-failure |
| Watchdog | `ollama-watchdog.service` | always |
| PostgreSQL | `postgresql.service` | on-failure |
### Network Rules
| Port | Protocol | Service | External Access |
|------|----------|---------|-----------------|
| 80 | HTTP | nginx → frontend | Yes (public) |
| 443 | HTTPS | nginx → frontend | Yes (public) |
| 8000 | TCP | FastAPI backend | No (internal only) |
| 5173 | TCP | Vite dev server | No (dev only) |
| 5432 | TCP | PostgreSQL | No (internal only) |
| 11434 | TCP | Ollama API | No (internal only) |
### Monitoring
```bash
# All service health
systemctl status velocity-backend ollama postgresql
# GPU utilization
nvidia-smi -l 1
# Model inference logs
journalctl -u ollama -f
# API error rate
curl -s http://localhost:8000/health | jq .
```
---
## Runbooks
### Runbook: Backend Crashes at 2 AM
**Symptom:** Frontend shows 500 errors on API calls.
**Steps:**
```bash
# 1. Check backend status
sudo systemctl status velocity-backend
# Expected: active (running)
# 2. If stopped, restart
sudo systemctl restart velocity-backend
# 3. Check logs for root cause
sudo journalctl -u velocity-backend --since "30 minutes ago" --no-pager
# 4. Verify recovery
curl http://localhost:8000/health
# Expected: {"status": "ok"}
# 5. If crash repeats, check database connectivity
psql -U velocity -d velocity -c "SELECT 1;"
# Expected: 1
```
**If still broken:**
1. Check disk space: `df -h /`
2. Check memory: `free -h`
3. Check PostgreSQL: `sudo systemctl status postgresql`
4. Escalate with logs from step 3
---
### Runbook: Ollama Model Disappeared
**Symptom:** Agents return empty responses or errors.
**Steps:**
```bash
# 1. Check if Ollama is running
sudo systemctl status ollama
# Expected: active (running)
# 2. Check loaded models
curl http://localhost:11434/api/tags | jq '.models[].name'
# Expected: qwen3.6:35b-a3b
# 3. If model is missing, check watchdog
sudo systemctl status ollama-watchdog
journalctl -u ollama-watchdog --since "1 hour ago" --no-pager
# 4. Manual recovery if watchdog failed
ollama pull qwen3.6:35b-a3b
# 5. Verify model is usable
curl http://localhost:11434/api/generate -d '{
"model": "qwen3.6:35b-a3b",
"prompt": "Hello",
"stream": false
}' | jq .done
# Expected: true
```
---
### Runbook: Database Connection Failures
**Symptom:** Backend logs show `connection refused` or `pool exhausted`.
**Steps:**
```bash
# 1. Check PostgreSQL status
sudo systemctl status postgresql
# Expected: active (running)
# 2. Check connection count
psql -U postgres -c "SELECT count(*) FROM pg_stat_activity;"
# Should be < max_connections (default 100)
# 3. Check disk space for WAL files
df -h /var/lib/postgresql
# 4. Restart if hung
sudo systemctl restart postgresql
# 5. Verify backend reconnects
sudo journalctl -u velocity-backend --since "1 minute ago" | grep -i "connected\|error"
```
---
### Runbook: GPU Memory Exhaustion
**Symptom:** Ollama returns `out of memory` errors.
**Steps:**
```bash
# 1. Check current GPU usage
nvidia-smi
# Note: PID, memory usage, temperature
# 2. Kill non-essential GPU processes if needed
nvidia-smi --id=0 --query-compute-apps=pid,name,used_memory --format=csv
kill <PID>
# 3. Check Ollama memory allocation
ollama show qwen3.6:35b-a3b | grep -i "layer\|memory"
# 4. If still exhausted, reduce model quantization
ollama pull qwen3.6:35b-a3b-q4_0
# 5. Monitor recovery
watch -n 1 nvidia-smi
```
---
## API Reference
### Auth Endpoints
#### `POST /api/auth/login`
Authenticate a user and receive a JWT token.
**Request:**
```json
{
"email": "user@example.com",
"password": "secure_password"
}
```
**Response (200 OK):**
```json
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "uuid-here",
"email": "user@example.com",
"role": "developer",
"avatar_url": null
}
}
```
**Errors:**
| Status | Meaning |
|--------|---------|
| 401 | Invalid credentials |
| 422 | Malformed request body |
---
#### `GET /api/auth/me`
Get the current authenticated user's profile.
**Headers:**
```
Authorization: Bearer <token>
```
**Response (200 OK):**
```json
{
"id": "uuid-here",
"email": "user@example.com",
"role": "developer",
"avatar_url": "https://cdn.example.com/avatars/user.png"
}
```
**Errors:**
| Status | Meaning |
|--------|---------|
| 401 | Token missing or invalid |
| 403 | Token expired |
---
#### `GET /api/auth/users`
List all users in the system. Admin only.
**Headers:**
```
Authorization: Bearer <admin_token>
```
**Response (200 OK):**
```json
[
{
"id": "uuid-1",
"email": "admin@example.com",
"role": "admin",
"avatar_url": null
},
{
"id": "uuid-2",
"email": "dev@example.com",
"role": "developer",
"avatar_url": "https://cdn.example.com/avatars/dev.png"
}
]
```
**Errors:**
| Status | Meaning |
|--------|---------|
| 403 | User is not admin |
---
#### `POST /api/auth/profile/avatar`
Upload a profile avatar image.
**Headers:**
```
Authorization: Bearer <token>
Content-Type: multipart/form-data
```
**Form Data:**
| Field | Type | Required |
|-------|------|----------|
| avatar | file (image/jpeg, image/png) | Yes |
**Response (200 OK):**
```json
{
"avatar_url": "https://cdn.example.com/avatars/new-avatar.png"
}
```
**Errors:**
| Status | Meaning |
|--------|---------|
| 401 | Not authenticated |
| 422 | Invalid file type or size > 5MB |
---
### WebSocket Endpoints
#### `WS /ws/catalyst`
Real-time channel for Catalyst events (agent coordination, task updates).
**Connection:**
```javascript
const ws = new WebSocket('ws://localhost:8000/ws/catalyst');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log(data.event_type, data.campaign_name, data.value);
};
```
**Event Format:**
```json
{
"event_type": "task_complete",
"campaign_name": "codegen-sprint-42",
"value": 0.97,
"timestamp": "2026-04-21T16:00:00Z"
}
```
---
#### `WS /ws/crm`
Real-time channel for CRM events (customer interactions, lead updates).
**Connection:**
```javascript
const ws = new WebSocket('ws://localhost:8000/ws/crm');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log(data.type, data.payload);
};
```
**Event Format:**
```json
{
"type": "lead_created",
"payload": {
"id": "crm-uuid",
"name": "Acme Corp",
"status": "new"
},
"timestamp": "2026-04-21T16:00:00Z"
}
```
---
### Health Check
#### `GET /health`
Verify system health.
**Response (200 OK):**
```json
{
"status": "ok",
"database": "connected",
"ollama": "available",
"gpu": "present"
}
```
---
## Contributing
### Code Structure
```
Project_Velocity/
├── .Agent Context/ # Agent documentation, model specs
├── .Infrastructure/ # Deployment configs, systemd units
├── backend/ # FastAPI backend
│ ├── main.py # Application entry point
│ ├── requirements.txt # Python dependencies
│ └── migrate.py # Database migrations
├── app/ # React frontend
│ ├── src/
│ │ ├── App.tsx # Root component
│ │ └── ... # Components, routes, utils
│ ├── package.json # Node dependencies
│ └── vite.config.ts # Build config
├── bootstrap/ # Setup scripts
│ └── setup.sh # One-line bootstrap
└── README.md # This file
```
### Making a Contribution
1. **Fork and branch**
```bash
git checkout -b feature/your-feature-name
```
2. **Make changes**
- Backend: Follow FastAPI conventions, add type hints
- Frontend: Follow React + TypeScript patterns, use existing components
- Docs: Update this README if behavior changes
3. **Test locally**
```bash
# Backend tests
cd backend && pytest
# Frontend checks
cd app && npm run build
```
4. **Submit PR**
- Title: Clear, action-oriented
- Description: What + Why + How to test
- Link any related issues
### Documentation Standards
- **Every endpoint:** Document inputs, outputs, errors
- **Every component:** JSDoc for public APIs
- **Every runbook:** Write as if for on-call at 2am
- **Every decision:** Record in `DECISIONS.md` with rationale
---
## Appendix
### A. Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `DATABASE_URL` | Yes | PostgreSQL connection string |
| `SECRET_KEY` | Yes | JWT signing key |
| `OLLAMA_BASE_URL` | No | Ollama API URL (default: `http://localhost:11434`) |
| `GPU_ENABLED` | No | Enable GPU path (default: `true`) |
| `LOG_LEVEL` | No | Logging level (default: `INFO`) |
### B. Troubleshooting Matrix
| Symptom | Likely Cause | Fix |
|---------|-------------|-----|
| Frontend blank screen | Backend down | `curl http://localhost:8000/health` |
| 401 on all calls | Token expired | Re-login |
| Agent returns empty | Model unloaded | `ollama pull qwen3.6:35b-a3b` |
| Slow responses | GPU not used | Check `nvidia-smi`, verify CUDA |
| Database errors | Pool exhausted | Check `max_connections`, restart backend |
| WebSocket disconnects | Network issue | Check firewall, reverse proxy config |
### C. Useful Commands Cheat Sheet
```bash
# Full system status
systemctl status velocity-backend ollama postgresql ollama-watchdog
# GPU实时监控
watch -n 1 nvidia-smi
# Model check
curl http://localhost:11434/api/tags | jq '.models[].name'
# API health
curl -s http://localhost:8000/health | jq .
# Database connection test
psql -U velocity -d velocity -c "SELECT version();"
# Frontend rebuild
cd app && npm run build && cp -r dist/* ../nginx/html/
# Restart everything (nuclear option)
sudo systemctl restart velocity-backend ollama postgresql
```
---
> **Last verified:** 2026-04-21
> **Maintained by:** Velocity Team
> **If this doc is wrong, the system is broken. Fix the doc first.**

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
I audited iOS/velocity-ipad/velocity for mock/demo/static/fallback data paths. Good news: I did not find hard-coded fake CRM people, fake properties, fake opportunities, fake calendar events, or fake comms threads being rendered as normal production data. The app is mostly live-backed.
That said, there are still several hard-coded or locally synthesized data paths you should decide whether to move fully behind backend/database contracts.
Production-Risk Mock Or Synthetic Data
SimulatorSunOverlayView.swift (line 8)
Uses fake simulator-only location: San Francisco 37.7749, -122.4194.
Uses mock heading: 0.
Wrapped in #if targetEnvironment(simulator), so it should not run on physical iPad, but it is still mock data in app code.
InventoryView.swift (line 941)
If Building.usdz / Building.scn fails to load, Dollhouse falls back to a procedural synthetic building.
The fallback creates hard-coded rooms, walls, colors, and dimensions at InventoryView.swift (line 975).
For production, this should probably fail closed or fetch a real model reference from backend inventory metadata.
Building.usda (line 1) and Building.usdz
Bundled local 3D building asset with hard-coded cube geometry: Podium, TowerA, TowerB, AmenityDeck, Courtyard.
This is not backend/database-backed property inventory. It is a static app asset.
Frontend-Derived Fallback Data
VelocityAPIClient.swift (line 1538)
VelocityClient360DTO.minimal(from:) fabricates a Client 360 snapshot from a contact when richer Client 360 data is unavailable.
It creates local QD overview fields, empty opportunities/interactions/tasks/interests, and note "Derived from the CRM client-data endpoint."
VelocityAPIClient.swift (line 2725)
If Client 360 decode fails with invalidResponse, the app fetches contacts and builds the minimal local snapshot instead of failing.
VelocityAPIClient.swift (line 1905)
If backend gives QD scores but no recommended actions, the app generates: Review {scoreType} score at {displayScore}.
This is local advisory text, not backend intelligence.
AppStore.swift (line 873)
Dashboard metrics fall back to locally computed canonicalDashboardMetrics(...) if /api/dashboard/metrics fails.
The app computes lead count, whale count, property count, today calendar count, pending insights, etc. locally at AppStore.swift (line 902).
AppStore.swift (line 857)
If contact fetch returns 404, app reuses cached contacts instead of treating backend as source-of-truth unavailable.
AppStore.swift (line 869)
Several failed backend calls silently fall back to empty arrays: kanban, opportunities, properties.
This can make missing backend data look like “zero production data.”
Local Offline Data That Becomes Temporary UI Truth
10. AppStore.swift (line 607)
Offline calendar create generates local IDs like local-{UUID} and local createdAt.
The event is merged into UI before backend confirmation.
AppStore.swift (line 436)
If task mutation happens offline and the task cannot be resolved, app fabricates a local task title: "Queued CRM task" and default priority "normal".
OfflineReplayStore.swift (line 16)
App stores offline replay mutations in local Core Data OfflineReplay.sqlite.
This is valid offline architecture, but if you require strict backend-only truth, this should be treated as a write queue only and clearly marked as “pending sync.”
Hard-Coded Business Vocabularies
13. ClientsView.swift (line 35)
Hard-coded lead statuses: new, contacted, qualified, site_visit_scheduled, etc.
ClientsView.swift (line 48)
Hard-coded urgency values: low, medium, high, critical.
ClientsView.swift (line 49)
Hard-coded buyer types: end_user, hni_end_user, nri_investor, family_office, etc.
ClientsView.swift (line 59)
Hard-coded task priorities: low, normal, high, urgent.
OracleView.swift (line 1001)
Hard-coded canonical lead stages.
OracleView.swift (line 1016)
Hard-coded opportunity stages: prospect, qualified, proposal, site_visit, etc.
ImportsView.swift (line 26)
Hard-coded duplicate policies: create_new, update_existing, skip_duplicate.
InventoryView.swift (line 501)
Hard-coded Dream Weaver room types: bedroom, living room, bathroom, kitchen, etc.
These are sent to the backend as room_type.
Hard-Coded Defaults Affecting Created Backend Data
21. VelocityAPIClient.swift (line 2579)
Communications task creation defaults priority to "normal".
VelocityAPIClient.swift (line 2969)
CSV import upload defaults source_system to "ipad_csv_upload".
CalendarView.swift (line 1118)
Calendar create adds local metadata: created_from = ipad_calendar, surface = velocity_ipad.
Config And Environment Defaults
24. SessionConfiguration.swift (line 16)
Default endpoint prompt/value: https://velocity.desineuron.in/api.
SessionConfiguration.swift (line 17)
Default Dream Weaver endpoint: https://dreamweaver.desineuron.in.
SessionConfigurationPanel.swift (line 74)
Placeholder operator email: operator@desineuron.in.
Verdict
The highest-priority removals for true production purity are:
Remove Dollhouse procedural fallback or move model selection to backend inventory metadata.
Replace hard-coded CRM vocabularies/stages with /api/crm/vocabularies or similar.
Remove Client 360 minimal fallback so malformed/missing backend data fails visibly.
Stop silently converting failed backend reads into empty arrays.
Keep offline replay only as a pending-write queue, not as “truth” without clear pending-sync labeling.
Replace simulator fake Sunseeker data with unavailable-state only, if simulator mock paths must be totally absent.

View File

@@ -0,0 +1,225 @@
# Oracle Template Seed DB — Expanded Examples v2.0
**Project:** Velocity — Multi-Surface Platform and Oracle Expansion
**Date:** 2026-04-19
**Owner:** Sayan (generated as part of Sprint 1 Oracle Template Taxonomy deliverable)
**Depends on:** `schema_extension_v2.sql`, `oracle_template_seed_db.json` (v1.0 canonical seeds)
**Total Examples:** 1,200 (50 per subchapter × 24 subchapters × 6 chapters)
---
## What This Is
This package expands the original 8-example Oracle Template Seed DB (`oracle_template_seed_db.json`) into a full 1,200-example corpus covering every subchapter in the Oracle template taxonomy. It is the implementation artifact for Sprint 1 deliverable **§2.4 — Oracle Template Taxonomy and Seed JSON Structure**.
Every example conforms to the established Velocity Oracle component contract shape. They are ready to be ingested into `oracle_template_seed_examples` via the `POST /api/oracle/component-templates/seed` route and consumed by Kimi Synthetic Data expansion jobs (`oracle_synthetic_generation_jobs`).
---
## File Layout
```
oracle_template_expansion/
├── README.md ← This file
├── oracle_template_seed_db_expanded.json ← Master combined file (all 1,200 examples)
├── sub-001-01_pricing_trends.json ← 50 examples
├── sub-001-02_demand_signals.json ← 50 examples
├── sub-001-03_competitive_landscape.json ← 50 examples
├── sub-001-04_location_index.json ← 50 examples
├── sub-002-01_lead_profile.json ← 50 examples
├── sub-002-02_qd_score.json ← 50 examples
├── sub-002-03_pipeline_health.json ← 50 examples
├── sub-002-04_engagement_history.json ← 50 examples
├── sub-003-01_call_summary.json ← 50 examples
├── sub-003-02_promise_tracker.json ← 50 examples
├── sub-003-03_whatsapp_thread.json ← 50 examples
├── sub-003-04_reminder_surface.json ← 50 examples
├── sub-004-01_property_card.json ← 50 examples
├── sub-004-02_availability_matrix.json ← 50 examples
├── sub-004-03_absorption_rate.json ← 50 examples
├── sub-004-04_inventory_comparison.json ← 50 examples
├── sub-005-01_showroom_traffic.json ← 50 examples
├── sub-005-02_team_performance.json ← 50 examples
├── sub-005-03_campaign_metrics.json ← 50 examples
├── sub-005-04_system_health.json ← 50 examples
├── sub-006-01_calendar_view.json ← 50 examples
├── sub-006-02_action_queue.json ← 50 examples
├── sub-006-03_follow-up_plan.json ← 50 examples
└── sub-006-04_reminder_cards.json ← 50 examples
```
---
## Chapter and Subchapter Map
| Chapter | Name | Subchapters | Examples |
|---------|------|-------------|----------|
| ch-001 | Market Intelligence | Pricing Trends, Demand Signals, Competitive Landscape, Location Index | 200 |
| ch-002 | Lead Intelligence | Lead Profile, QD Score, Pipeline Health, Engagement History | 200 |
| ch-003 | Communication Intelligence | Call Summary, Promise Tracker, WhatsApp Thread, Reminder Surface | 200 |
| ch-004 | Inventory Analytics | Property Card, Availability Matrix, Absorption Rate, Inventory Comparison | 200 |
| ch-005 | Operational Metrics | Showroom Traffic, Team Performance, Campaign Metrics, System Health | 200 |
| ch-006 | Calendar and Follow-Up | Calendar View, Action Queue, Follow-Up Plan, Reminder Cards | 200 |
| **Total** | | **24 subchapters** | **1,200** |
---
## Component Type Coverage
| componentType | Subchapters Used In | Approx Count |
|---------------|---------------------|--------------|
| `line_chart` | sub-001-01, sub-001-02, sub-004-03, sub-005-01, sub-005-02, sub-005-03 | ~120 |
| `bar_chart` | sub-001-02, sub-001-03, sub-004-03, sub-005-01, sub-005-02, sub-005-03 | ~100 |
| `area_chart` | sub-001-01, sub-004-03, sub-005-01 | ~45 |
| `heatmap` | sub-001-04, sub-005-01 | ~40 |
| `metric_card_group` | sub-002-02, sub-005-02, sub-005-03 | ~60 |
| `data_table` | sub-003-02 | ~50 |
| `property_card` | sub-004-01 | ~50 |
| `lead_profile_card` | sub-002-01 | ~50 |
| `communication_summary` | sub-003-01 | ~50 |
| `whatsapp_thread_viewer` | sub-003-03 | ~50 |
| `reminder_surface` | sub-003-04 | ~50 |
| `compact_alert_card` | sub-006-04 | ~50 |
| `action_queue` | sub-006-02 | ~50 |
| `calendar_view` | sub-006-01 | ~50 |
| `follow_up_plan` | sub-006-03 | ~50 |
| `availability_matrix` | sub-004-02 | ~50 |
| `inventory_comparison` | sub-004-04 | ~50 |
| `system_health_panel` | sub-005-04 | ~50 |
| `radar_chart`, `scatter_chart`, `funnel_chart`, others | various | ~135 |
---
## Example JSON Structure
Every example in every subchapter file follows this envelope:
```json
{
"example_id": "ex-0009",
"chapter_id": "ch-001",
"subchapter_id": "sub-001-01",
"title": "Component title string",
"quality_notes": "Human-readable note about this variant",
"is_canonical": true,
"template_name": "Subchapter Name — Template N",
"component_type": "line_chart",
"accepted_shapes": ["time_series"],
"example_json": {
"componentType": "line_chart",
"title": "...",
"subtitle": "...",
"dataSource": { ... },
"visualization": { ... },
"style": { ... },
"surfaceTargets": [ ... ]
}
}
```
The first example in each subchapter (`is_canonical: true`) is the recommended reference template for that subchapter.
---
## Design Language Compliance
All examples follow the established Velocity Oracle design language:
**Color palette** — All `accentColor` values come from the 10-color Velocity token set:
- `#2563EB` (primary blue), `#10B981` (emerald), `#F59E0B` (amber), `#EF4444` (red)
- `#8B5CF6` (violet), `#0EA5E9` (sky), `#EC4899` (pink), `#14B8A6` (teal)
- `#F97316` (orange), `#6366F1` (indigo)
**Semantic colors** — Status colors are fixed:
- Healthy / positive: `#10B981`
- Warning: `#F59E0B`
- Critical / negative: `#EF4444`
- Neutral / muted: `#94A3B8`
**Data source types** — Examples use only the contracted Oracle data source types:
- `inventory_aggregate`, `inventory_property`, `inventory_multi_property`
- `crm_lead`, `crm_aggregate`, `crm_engagement`, `crm_pipeline`, `crm_team_performance`
- `sentinel_qd`, `sentinel_live`, `sentinel_historical`
- `edge_communication_event`, `edge_memory_facts`
- `user_calendar_events`, `insight_recommendations`
- `nemoclaw_plan`, `catalyst_campaign`, `admin_health`, `competitive_intelligence`, `location_intelligence`
**Template variables** — Dynamic entity references use double-brace mustache syntax: `{{lead_id}}`, `{{property_id}}`, `{{agent_id}}`, `{{event_id}}`, `{{tenant_id}}`, `{{user_id}}`.
**Surface targets** — Every example declares `surfaceTargets` from the set: `webos`, `ipad`, `android_tablet`, `iphone_edge`, `android_phone_edge`.
---
## Permutation Logic
Each subchapter's 50 examples are generated by cycling through permutation combinations of:
- **District / developer / lead / agent names** — drawn from real Dubai market data (districts, developer names, nationality mix aligned to UAE CRM reality)
- **Time windows** — `7D`, `14D`, `30D`, `60D`, `90D`, `6M`, `12M`, `24M`, `YTD`, `QTD`
- **Chart types** — 46 types per subchapter appropriate to the data shape
- **Grouping dimensions** — e.g. by agent, district, property type, nationality
- **Layout variants** — e.g. `hero_with_stats`, `compact_card`, `list_row` for property cards
- **Action sets** — e.g. `accept / dismiss / snooze_1h` vs `call_now / send_whatsapp / dismiss`
- **Optional fields** — annotations, benchmarks, comparisons, sparklines toggled on/off across the 50
This means every subchapter has diverse examples covering different use cases while staying within the correct data contract for that component family.
---
## How to Ingest Into the Database
### Option 1 — Per-subchapter seed via Admin Surface
```bash
POST /api/oracle/component-templates/seed
Content-Type: application/json
{
"subchapter_id": "<uuid from oracle_template_subchapters>",
"examples": [ ... ] # paste the "examples" array from the per-subchapter file
}
```
### Option 2 — Bulk ingest via Kimi Synthetic Job
The master file (`oracle_template_seed_db_expanded.json`) is the correct input for `oracle_synthetic_generation_jobs`. Insert a job row referencing the template and let the background worker distribute examples into `oracle_template_seed_examples`.
### Option 3 — Direct SQL seed (dev/staging only)
```sql
INSERT INTO oracle_template_seed_examples
(template_id, chapter_id, subchapter_id, title, example_json, quality_notes, is_canonical)
VALUES
(...);
```
Map string chapter/subchapter IDs from the JSON against the UUID rows you insert via the migration in `schema_extension_v2.sql`.
---
## Known Caveats and Next Steps
- **`_meta.total_seed_examples` in v1 seed DB** — The original `oracle_template_seed_db.json` reports `36` in `_meta.total_seed_examples` but only contains 8 examples. This mismatch was noted in `delivery_log.md`. This expansion does not patch the v1 file; correct it separately before merging both corpora.
- **Kimi expansion** — These 1,200 examples are the **seed corpus**, not the synthetic expansion. Run `oracle_synthetic_generation_jobs` against published templates to generate the larger training/demo sets described in `KIMI_SYNTHETIC_DATA_DOWNSTREAM_PLAN.md`.
- **UUID mapping** — The `chapter_id` and `subchapter_id` fields in these files use the string keys from the v1 seed DB (`ch-001`, `sub-001-01`). Your migration script must map these to the PostgreSQL UUIDs inserted by `schema_extension_v2.sql`.
- **Template IDs** — `example_json.template_name` is a human label. Actual `template_id` UUIDs are assigned at ingestion time against `oracle_component_templates`.
---
## Generation Script
The generator script is included at `generate_examples.py` in the repo root (outside this zip). It is reproducible — re-running it with the same seed logic will produce the same 1,200 examples.
---
*Generated by Project Velocity platform tooling · 2026-04-19*

View File

@@ -0,0 +1,82 @@
# Oracle Canvas JSON Expansion Pack
This pack expands the current Oracle template seed library into a reviewable example set with **50 examples per subchapter** across all **24 subchapters**.
## What is inside
- `db/oracle_template_seed_db_expanded_v1.pretty.json`
Full expanded master DB with chapter taxonomy and all **1200** examples.
- `db/oracle_template_seed_db_expanded_v1.min.json`
Minified version of the same master DB.
- `examples/`
Chapter-by-chapter split files. Each subchapter file contains exactly **50** examples.
- `manifests/template_family_catalog.json`
Component families, accepted shapes, policy tags, and backend hints per subchapter.
- `manifests/subchapter_index.json`
Index of all generated files.
- `manifests/validation_report.json`
Validation summary for counts and ID uniqueness.
- `csv/subchapter_example_counts.csv`
Spreadsheet-friendly count manifest.
## Source alignment
This pack was generated against the current repo direction and constraints:
- FastAPI backend remains canonical.
- Oracle remains the analytical center.
- Mobile edge surfaces remain narrow, bounded control surfaces.
- Communication intelligence examples stay inside supported channels and provenance-aware capture modes.
- Admin examples only model bounded and auditable actions.
- The expanded examples follow the live-data-first / no-mock direction from the delivery log.
## Important correction carried forward
The source seed DB metadata currently reports `total_seed_examples: 36`, but the source file actually contains **8** canonical seed examples.
This expansion pack corrects the count in its own metadata and preserves the existing canonical examples inside the 50-example-per-subchapter allocation wherever they already existed.
## Design language used
Common policy tags applied through the pack:
- `backend_owned`
- `live_data_first`
- `no_mock_fallback`
- `surface_safe`
Additional policy tags appear per subchapter where relevant, including:
- `supported_channel_only`
- `provider_provenance_required`
- `bounded_admin_actions`
- `confirmation_required_for_writeback`
- `business_whatsapp_scope`
- `nemoclaw_suggested`
## Notes on IDs
The source taxonomy uses symbolic IDs such as `ch-001` and `sub-001-01`.
This pack preserves those symbolic IDs for review and lineage consistency.
Generated example IDs use deterministic `exg-*` identifiers. Existing canonical example IDs from the source file are preserved.
## Suggested use
1. Review examples subchapter-by-subchapter from `examples/`.
2. Use `template_family_catalog.json` to decide which component families should become formal reusable templates.
3. Use the master DB JSON once you are ready to merge the chosen examples into the Oracle seed library.
4. Keep the metadata notes about symbolic taxonomy IDs in mind when preparing any DB import step against UUID-backed SQL tables.
## Counts
- Chapters: 6
- Subchapters: 24
- Total examples: 1200
- Canonical carried forward: 8
- Generated additions: 1192

View File

@@ -0,0 +1,25 @@
chapter_id,chapter_name,subchapter_id,subchapter_name,example_count,file
ch-001,Market Intelligence,sub-001-01,Pricing Trends,50,examples/ch-001_market-intelligence/sub-001-01_pricing-trends.json
ch-001,Market Intelligence,sub-001-02,Demand Signals,50,examples/ch-001_market-intelligence/sub-001-02_demand-signals.json
ch-001,Market Intelligence,sub-001-03,Competitive Landscape,50,examples/ch-001_market-intelligence/sub-001-03_competitive-landscape.json
ch-001,Market Intelligence,sub-001-04,Location Index,50,examples/ch-001_market-intelligence/sub-001-04_location-index.json
ch-002,Lead Intelligence,sub-002-01,Lead Profile,50,examples/ch-002_lead-intelligence/sub-002-01_lead-profile.json
ch-002,Lead Intelligence,sub-002-02,QD Score,50,examples/ch-002_lead-intelligence/sub-002-02_qd-score.json
ch-002,Lead Intelligence,sub-002-03,Pipeline Health,50,examples/ch-002_lead-intelligence/sub-002-03_pipeline-health.json
ch-002,Lead Intelligence,sub-002-04,Engagement History,50,examples/ch-002_lead-intelligence/sub-002-04_engagement-history.json
ch-003,Communication Intelligence,sub-003-01,Call Summary,50,examples/ch-003_communication-intelligence/sub-003-01_call-summary.json
ch-003,Communication Intelligence,sub-003-02,Promise Tracker,50,examples/ch-003_communication-intelligence/sub-003-02_promise-tracker.json
ch-003,Communication Intelligence,sub-003-03,WhatsApp Thread,50,examples/ch-003_communication-intelligence/sub-003-03_whatsapp-thread.json
ch-003,Communication Intelligence,sub-003-04,Reminder Surface,50,examples/ch-003_communication-intelligence/sub-003-04_reminder-surface.json
ch-004,Inventory Analytics,sub-004-01,Property Card,50,examples/ch-004_inventory-analytics/sub-004-01_property-card.json
ch-004,Inventory Analytics,sub-004-02,Availability Matrix,50,examples/ch-004_inventory-analytics/sub-004-02_availability-matrix.json
ch-004,Inventory Analytics,sub-004-03,Absorption Rate,50,examples/ch-004_inventory-analytics/sub-004-03_absorption-rate.json
ch-004,Inventory Analytics,sub-004-04,Inventory Comparison,50,examples/ch-004_inventory-analytics/sub-004-04_inventory-comparison.json
ch-005,Operational Metrics,sub-005-01,Showroom Traffic,50,examples/ch-005_operational-metrics/sub-005-01_showroom-traffic.json
ch-005,Operational Metrics,sub-005-02,Team Performance,50,examples/ch-005_operational-metrics/sub-005-02_team-performance.json
ch-005,Operational Metrics,sub-005-03,Campaign Metrics,50,examples/ch-005_operational-metrics/sub-005-03_campaign-metrics.json
ch-005,Operational Metrics,sub-005-04,System Health,50,examples/ch-005_operational-metrics/sub-005-04_system-health.json
ch-006,Calendar and Follow-Up,sub-006-01,Calendar View,50,examples/ch-006_calendar-and-follow-up/sub-006-01_calendar-view.json
ch-006,Calendar and Follow-Up,sub-006-02,Action Queue,50,examples/ch-006_calendar-and-follow-up/sub-006-02_action-queue.json
ch-006,Calendar and Follow-Up,sub-006-03,Follow-Up Plan,50,examples/ch-006_calendar-and-follow-up/sub-006-03_follow-up-plan.json
ch-006,Calendar and Follow-Up,sub-006-04,Reminder Cards,50,examples/ch-006_calendar-and-follow-up/sub-006-04_reminder-cards.json
1 chapter_id chapter_name subchapter_id subchapter_name example_count file
2 ch-001 Market Intelligence sub-001-01 Pricing Trends 50 examples/ch-001_market-intelligence/sub-001-01_pricing-trends.json
3 ch-001 Market Intelligence sub-001-02 Demand Signals 50 examples/ch-001_market-intelligence/sub-001-02_demand-signals.json
4 ch-001 Market Intelligence sub-001-03 Competitive Landscape 50 examples/ch-001_market-intelligence/sub-001-03_competitive-landscape.json
5 ch-001 Market Intelligence sub-001-04 Location Index 50 examples/ch-001_market-intelligence/sub-001-04_location-index.json
6 ch-002 Lead Intelligence sub-002-01 Lead Profile 50 examples/ch-002_lead-intelligence/sub-002-01_lead-profile.json
7 ch-002 Lead Intelligence sub-002-02 QD Score 50 examples/ch-002_lead-intelligence/sub-002-02_qd-score.json
8 ch-002 Lead Intelligence sub-002-03 Pipeline Health 50 examples/ch-002_lead-intelligence/sub-002-03_pipeline-health.json
9 ch-002 Lead Intelligence sub-002-04 Engagement History 50 examples/ch-002_lead-intelligence/sub-002-04_engagement-history.json
10 ch-003 Communication Intelligence sub-003-01 Call Summary 50 examples/ch-003_communication-intelligence/sub-003-01_call-summary.json
11 ch-003 Communication Intelligence sub-003-02 Promise Tracker 50 examples/ch-003_communication-intelligence/sub-003-02_promise-tracker.json
12 ch-003 Communication Intelligence sub-003-03 WhatsApp Thread 50 examples/ch-003_communication-intelligence/sub-003-03_whatsapp-thread.json
13 ch-003 Communication Intelligence sub-003-04 Reminder Surface 50 examples/ch-003_communication-intelligence/sub-003-04_reminder-surface.json
14 ch-004 Inventory Analytics sub-004-01 Property Card 50 examples/ch-004_inventory-analytics/sub-004-01_property-card.json
15 ch-004 Inventory Analytics sub-004-02 Availability Matrix 50 examples/ch-004_inventory-analytics/sub-004-02_availability-matrix.json
16 ch-004 Inventory Analytics sub-004-03 Absorption Rate 50 examples/ch-004_inventory-analytics/sub-004-03_absorption-rate.json
17 ch-004 Inventory Analytics sub-004-04 Inventory Comparison 50 examples/ch-004_inventory-analytics/sub-004-04_inventory-comparison.json
18 ch-005 Operational Metrics sub-005-01 Showroom Traffic 50 examples/ch-005_operational-metrics/sub-005-01_showroom-traffic.json
19 ch-005 Operational Metrics sub-005-02 Team Performance 50 examples/ch-005_operational-metrics/sub-005-02_team-performance.json
20 ch-005 Operational Metrics sub-005-03 Campaign Metrics 50 examples/ch-005_operational-metrics/sub-005-03_campaign-metrics.json
21 ch-005 Operational Metrics sub-005-04 System Health 50 examples/ch-005_operational-metrics/sub-005-04_system-health.json
22 ch-006 Calendar and Follow-Up sub-006-01 Calendar View 50 examples/ch-006_calendar-and-follow-up/sub-006-01_calendar-view.json
23 ch-006 Calendar and Follow-Up sub-006-02 Action Queue 50 examples/ch-006_calendar-and-follow-up/sub-006-02_action-queue.json
24 ch-006 Calendar and Follow-Up sub-006-03 Follow-Up Plan 50 examples/ch-006_calendar-and-follow-up/sub-006-03_follow-up-plan.json
25 ch-006 Calendar and Follow-Up sub-006-04 Reminder Cards 50 examples/ch-006_calendar-and-follow-up/sub-006-04_reminder-cards.json

View File

@@ -0,0 +1,194 @@
[
{
"chapter_id": "ch-001",
"chapter_name": "Market Intelligence",
"subchapter_id": "sub-001-01",
"subchapter_name": "Pricing Trends",
"example_count": 50,
"file": "examples/ch-001_market-intelligence/sub-001-01_pricing-trends.json"
},
{
"chapter_id": "ch-001",
"chapter_name": "Market Intelligence",
"subchapter_id": "sub-001-02",
"subchapter_name": "Demand Signals",
"example_count": 50,
"file": "examples/ch-001_market-intelligence/sub-001-02_demand-signals.json"
},
{
"chapter_id": "ch-001",
"chapter_name": "Market Intelligence",
"subchapter_id": "sub-001-03",
"subchapter_name": "Competitive Landscape",
"example_count": 50,
"file": "examples/ch-001_market-intelligence/sub-001-03_competitive-landscape.json"
},
{
"chapter_id": "ch-001",
"chapter_name": "Market Intelligence",
"subchapter_id": "sub-001-04",
"subchapter_name": "Location Index",
"example_count": 50,
"file": "examples/ch-001_market-intelligence/sub-001-04_location-index.json"
},
{
"chapter_id": "ch-002",
"chapter_name": "Lead Intelligence",
"subchapter_id": "sub-002-01",
"subchapter_name": "Lead Profile",
"example_count": 50,
"file": "examples/ch-002_lead-intelligence/sub-002-01_lead-profile.json"
},
{
"chapter_id": "ch-002",
"chapter_name": "Lead Intelligence",
"subchapter_id": "sub-002-02",
"subchapter_name": "QD Score",
"example_count": 50,
"file": "examples/ch-002_lead-intelligence/sub-002-02_qd-score.json"
},
{
"chapter_id": "ch-002",
"chapter_name": "Lead Intelligence",
"subchapter_id": "sub-002-03",
"subchapter_name": "Pipeline Health",
"example_count": 50,
"file": "examples/ch-002_lead-intelligence/sub-002-03_pipeline-health.json"
},
{
"chapter_id": "ch-002",
"chapter_name": "Lead Intelligence",
"subchapter_id": "sub-002-04",
"subchapter_name": "Engagement History",
"example_count": 50,
"file": "examples/ch-002_lead-intelligence/sub-002-04_engagement-history.json"
},
{
"chapter_id": "ch-003",
"chapter_name": "Communication Intelligence",
"subchapter_id": "sub-003-01",
"subchapter_name": "Call Summary",
"example_count": 50,
"file": "examples/ch-003_communication-intelligence/sub-003-01_call-summary.json"
},
{
"chapter_id": "ch-003",
"chapter_name": "Communication Intelligence",
"subchapter_id": "sub-003-02",
"subchapter_name": "Promise Tracker",
"example_count": 50,
"file": "examples/ch-003_communication-intelligence/sub-003-02_promise-tracker.json"
},
{
"chapter_id": "ch-003",
"chapter_name": "Communication Intelligence",
"subchapter_id": "sub-003-03",
"subchapter_name": "WhatsApp Thread",
"example_count": 50,
"file": "examples/ch-003_communication-intelligence/sub-003-03_whatsapp-thread.json"
},
{
"chapter_id": "ch-003",
"chapter_name": "Communication Intelligence",
"subchapter_id": "sub-003-04",
"subchapter_name": "Reminder Surface",
"example_count": 50,
"file": "examples/ch-003_communication-intelligence/sub-003-04_reminder-surface.json"
},
{
"chapter_id": "ch-004",
"chapter_name": "Inventory Analytics",
"subchapter_id": "sub-004-01",
"subchapter_name": "Property Card",
"example_count": 50,
"file": "examples/ch-004_inventory-analytics/sub-004-01_property-card.json"
},
{
"chapter_id": "ch-004",
"chapter_name": "Inventory Analytics",
"subchapter_id": "sub-004-02",
"subchapter_name": "Availability Matrix",
"example_count": 50,
"file": "examples/ch-004_inventory-analytics/sub-004-02_availability-matrix.json"
},
{
"chapter_id": "ch-004",
"chapter_name": "Inventory Analytics",
"subchapter_id": "sub-004-03",
"subchapter_name": "Absorption Rate",
"example_count": 50,
"file": "examples/ch-004_inventory-analytics/sub-004-03_absorption-rate.json"
},
{
"chapter_id": "ch-004",
"chapter_name": "Inventory Analytics",
"subchapter_id": "sub-004-04",
"subchapter_name": "Inventory Comparison",
"example_count": 50,
"file": "examples/ch-004_inventory-analytics/sub-004-04_inventory-comparison.json"
},
{
"chapter_id": "ch-005",
"chapter_name": "Operational Metrics",
"subchapter_id": "sub-005-01",
"subchapter_name": "Showroom Traffic",
"example_count": 50,
"file": "examples/ch-005_operational-metrics/sub-005-01_showroom-traffic.json"
},
{
"chapter_id": "ch-005",
"chapter_name": "Operational Metrics",
"subchapter_id": "sub-005-02",
"subchapter_name": "Team Performance",
"example_count": 50,
"file": "examples/ch-005_operational-metrics/sub-005-02_team-performance.json"
},
{
"chapter_id": "ch-005",
"chapter_name": "Operational Metrics",
"subchapter_id": "sub-005-03",
"subchapter_name": "Campaign Metrics",
"example_count": 50,
"file": "examples/ch-005_operational-metrics/sub-005-03_campaign-metrics.json"
},
{
"chapter_id": "ch-005",
"chapter_name": "Operational Metrics",
"subchapter_id": "sub-005-04",
"subchapter_name": "System Health",
"example_count": 50,
"file": "examples/ch-005_operational-metrics/sub-005-04_system-health.json"
},
{
"chapter_id": "ch-006",
"chapter_name": "Calendar and Follow-Up",
"subchapter_id": "sub-006-01",
"subchapter_name": "Calendar View",
"example_count": 50,
"file": "examples/ch-006_calendar-and-follow-up/sub-006-01_calendar-view.json"
},
{
"chapter_id": "ch-006",
"chapter_name": "Calendar and Follow-Up",
"subchapter_id": "sub-006-02",
"subchapter_name": "Action Queue",
"example_count": 50,
"file": "examples/ch-006_calendar-and-follow-up/sub-006-02_action-queue.json"
},
{
"chapter_id": "ch-006",
"chapter_name": "Calendar and Follow-Up",
"subchapter_id": "sub-006-03",
"subchapter_name": "Follow-Up Plan",
"example_count": 50,
"file": "examples/ch-006_calendar-and-follow-up/sub-006-03_follow-up-plan.json"
},
{
"chapter_id": "ch-006",
"chapter_name": "Calendar and Follow-Up",
"subchapter_id": "sub-006-04",
"subchapter_name": "Reminder Cards",
"example_count": 50,
"file": "examples/ch-006_calendar-and-follow-up/sub-006-04_reminder-cards.json"
}
]

View File

@@ -0,0 +1,931 @@
[
{
"chapter_id": "ch-001",
"chapter_name": "Market Intelligence",
"subchapter_id": "sub-001-01",
"subchapter_name": "Pricing Trends",
"component_types": [
"area_chart",
"benchmark_band_chart",
"dual_axis_chart",
"line_chart",
"sparkline_metric"
],
"accepted_shapes": [
"comparative_time_series",
"district_benchmark",
"dual_metric_time_series",
"segment_snapshot",
"time_series"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "oracle",
"primary_tables": [
"oracle_component_templates",
"inventory_properties"
]
},
{
"chapter_id": "ch-001",
"chapter_name": "Market Intelligence",
"subchapter_id": "sub-001-02",
"subchapter_name": "Demand Signals",
"component_types": [
"bar_chart",
"funnel_chart",
"heatmap",
"line_chart",
"metric_card_group"
],
"accepted_shapes": [
"categorical_count",
"conversion_funnel",
"demand_snapshot",
"intent_time_series",
"zone_time_matrix"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "oracle",
"primary_tables": [
"oracle_component_templates",
"inventory_properties"
]
},
{
"chapter_id": "ch-001",
"chapter_name": "Market Intelligence",
"subchapter_id": "sub-001-03",
"subchapter_name": "Competitive Landscape",
"component_types": [
"bar_chart",
"comparison_table",
"grouped_bar_chart",
"matrix_grid",
"scorecard_panel"
],
"accepted_shapes": [
"competitive_matrix",
"competitive_scorecard",
"developer_benchmark",
"developer_pipeline",
"unit_mix_distribution"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "oracle",
"primary_tables": [
"oracle_component_templates",
"inventory_properties"
]
},
{
"chapter_id": "ch-001",
"chapter_name": "Market Intelligence",
"subchapter_id": "sub-001-04",
"subchapter_name": "Location Index",
"component_types": [
"data_table",
"map_score_card",
"radar_chart",
"scorecard_panel",
"timeline_chart"
],
"accepted_shapes": [
"district_ranking",
"infrastructure_readiness",
"location_index",
"location_map_summary",
"proximity_profile"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "oracle",
"primary_tables": [
"oracle_component_templates",
"inventory_properties"
]
},
{
"chapter_id": "ch-002",
"chapter_name": "Lead Intelligence",
"subchapter_id": "sub-002-01",
"subchapter_name": "Lead Profile",
"component_types": [
"affinity_card",
"cluster_card",
"lead_profile_card",
"metric_card_group",
"summary_strip"
],
"accepted_shapes": [
"district_affinity",
"lead_preferences",
"lead_profile",
"lead_summary",
"persona_cluster"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "crm",
"primary_tables": [
"leads",
"sentinel_scores"
]
},
{
"chapter_id": "ch-002",
"chapter_name": "Lead Intelligence",
"subchapter_id": "sub-002-02",
"subchapter_name": "QD Score",
"component_types": [
"bar_chart",
"gauge_stack",
"line_chart",
"matrix_grid",
"metric_card_group"
],
"accepted_shapes": [
"qd_matrix",
"qd_peer_benchmark",
"qd_score_breakdown",
"qd_snapshot",
"qd_trend"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "crm",
"primary_tables": [
"leads",
"sentinel_scores"
]
},
{
"chapter_id": "ch-002",
"chapter_name": "Lead Intelligence",
"subchapter_id": "sub-002-03",
"subchapter_name": "Pipeline Health",
"component_types": [
"data_table",
"funnel_chart",
"heatmap",
"metric_card_group",
"stacked_bar_chart"
],
"accepted_shapes": [
"pipeline_distribution",
"pipeline_forecast",
"pipeline_probability_matrix",
"pipeline_stalls",
"pipeline_velocity"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "crm",
"primary_tables": [
"leads",
"sentinel_scores"
]
},
{
"chapter_id": "ch-002",
"chapter_name": "Lead Intelligence",
"subchapter_id": "sub-002-04",
"subchapter_name": "Engagement History",
"component_types": [
"data_table",
"heatmap",
"interaction_timeline",
"line_chart",
"metric_card_group"
],
"accepted_shapes": [
"channel_preference_trend",
"content_interaction_log",
"engagement_heatmap",
"engagement_snapshot",
"engagement_timeline"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "crm",
"primary_tables": [
"leads",
"sentinel_scores"
]
},
{
"chapter_id": "ch-003",
"chapter_name": "Communication Intelligence",
"subchapter_id": "sub-003-01",
"subchapter_name": "Call Summary",
"component_types": [
"communication_summary",
"data_table",
"metric_card_group",
"next_best_action_card",
"transcript_highlight_card"
],
"accepted_shapes": [
"call_follow_up_snapshot",
"call_outcome_snapshot",
"speaker_highlights",
"transcript_segments",
"transcript_summary"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe",
"provider_provenance_required",
"supported_channel_only"
],
"route_family": "mobile-edge",
"primary_tables": [
"edge_communication_events",
"edge_communication_memory_facts",
"edge_transcription_jobs",
"edge_transcript_segments"
]
},
{
"chapter_id": "ch-003",
"chapter_name": "Communication Intelligence",
"subchapter_id": "sub-003-02",
"subchapter_name": "Promise Tracker",
"component_types": [
"checklist_board",
"compact_alert_card",
"data_table",
"matrix_grid",
"summary_card"
],
"accepted_shapes": [
"communication_facts",
"decision_maker_summary",
"follow_up_checklist",
"overdue_commitments",
"promise_confidence_matrix"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe",
"reviewable_writebacks",
"communication_memory"
],
"route_family": "mobile-edge",
"primary_tables": [
"edge_communication_events",
"edge_communication_memory_facts",
"edge_transcription_jobs",
"edge_transcript_segments"
]
},
{
"chapter_id": "ch-003",
"chapter_name": "Communication Intelligence",
"subchapter_id": "sub-003-03",
"subchapter_name": "WhatsApp Thread",
"component_types": [
"data_table",
"line_chart",
"message_thread_summary",
"metric_card_group",
"summary_card"
],
"accepted_shapes": [
"message_action_queue",
"message_sentiment_timeline",
"operator_handover",
"thread_sla_snapshot",
"whatsapp_thread"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe",
"supported_channel_only",
"business_whatsapp_scope"
],
"route_family": "mobile-edge",
"primary_tables": [
"edge_communication_events",
"edge_communication_memory_facts",
"edge_transcription_jobs",
"edge_transcript_segments"
]
},
{
"chapter_id": "ch-003",
"chapter_name": "Communication Intelligence",
"subchapter_id": "sub-003-04",
"subchapter_name": "Reminder Surface",
"component_types": [
"action_strip",
"alert_queue",
"compact_alert_card",
"matrix_grid",
"next_best_action_card"
],
"accepted_shapes": [
"calendar_suggestion",
"insight_recommendation",
"next_best_action",
"recommendation_confidence",
"reminder_queue"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe",
"bounded_actions",
"nemoclaw_suggested"
],
"route_family": "mobile-edge",
"primary_tables": [
"edge_communication_events",
"edge_communication_memory_facts",
"edge_transcription_jobs",
"edge_transcript_segments"
]
},
{
"chapter_id": "ch-004",
"chapter_name": "Inventory Analytics",
"subchapter_id": "sub-004-01",
"subchapter_name": "Property Card",
"component_types": [
"bar_chart",
"cta_card",
"metric_card_group",
"property_card",
"summary_card"
],
"accepted_shapes": [
"inventory_property",
"property_cta",
"property_media_summary",
"property_pricing_snapshot",
"unit_mix_distribution"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "inventory",
"primary_tables": [
"inventory_properties",
"inventory_media_assets",
"inventory_import_batches"
]
},
{
"chapter_id": "ch-004",
"chapter_name": "Inventory Analytics",
"subchapter_id": "sub-004-02",
"subchapter_name": "Availability Matrix",
"component_types": [
"data_table",
"heatmap",
"matrix_grid",
"metric_card_group",
"stacked_bar_chart"
],
"accepted_shapes": [
"availability_heatmap",
"availability_matrix",
"bed_type_snapshot",
"price_band_grid",
"release_phase_distribution"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "inventory",
"primary_tables": [
"inventory_properties",
"inventory_media_assets",
"inventory_import_batches"
]
},
{
"chapter_id": "ch-004",
"chapter_name": "Inventory Analytics",
"subchapter_id": "sub-004-03",
"subchapter_name": "Absorption Rate",
"component_types": [
"bar_chart",
"dual_axis_chart",
"line_chart",
"matrix_grid",
"sparkline_metric"
],
"accepted_shapes": [
"developer_velocity_ranking",
"handover_absorption_matrix",
"rolling_velocity_snapshot",
"sales_velocity",
"velocity_supply_overlay"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "inventory",
"primary_tables": [
"inventory_properties",
"inventory_media_assets",
"inventory_import_batches"
]
},
{
"chapter_id": "ch-004",
"chapter_name": "Inventory Analytics",
"subchapter_id": "sub-004-04",
"subchapter_name": "Inventory Comparison",
"component_types": [
"comparison_table",
"metric_card_group",
"radar_chart",
"side_by_side_comparison",
"summary_strip"
],
"accepted_shapes": [
"amenity_comparison",
"inventory_comparison",
"operator_choice_summary",
"property_metric_comparison",
"sales_readiness_comparison"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "inventory",
"primary_tables": [
"inventory_properties",
"inventory_media_assets",
"inventory_import_batches"
]
},
{
"chapter_id": "ch-005",
"chapter_name": "Operational Metrics",
"subchapter_id": "sub-005-01",
"subchapter_name": "Showroom Traffic",
"component_types": [
"bar_chart",
"dual_axis_chart",
"heatmap",
"metric_card_group",
"summary_strip"
],
"accepted_shapes": [
"live_traffic_snapshot",
"peak_hour_distribution",
"visitor_flow_overlay",
"zone_summary",
"zone_time_matrix"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "admin-surface",
"primary_tables": [
"surface_sessions",
"admin_action_events",
"oracle_synthetic_generation_jobs",
"inventory_import_batches",
"edge_transcription_jobs"
]
},
{
"chapter_id": "ch-005",
"chapter_name": "Operational Metrics",
"subchapter_id": "sub-005-02",
"subchapter_name": "Team Performance",
"component_types": [
"compact_alert_card",
"dual_axis_chart",
"leaderboard_table",
"matrix_grid",
"metric_card_group"
],
"accepted_shapes": [
"activity_conversion_overlay",
"agent_leaderboard",
"follow_up_compliance_matrix",
"quality_drift_alert",
"team_performance_snapshot"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "admin-surface",
"primary_tables": [
"surface_sessions",
"admin_action_events",
"oracle_synthetic_generation_jobs",
"inventory_import_batches",
"edge_transcription_jobs"
]
},
{
"chapter_id": "ch-005",
"chapter_name": "Operational Metrics",
"subchapter_id": "sub-005-03",
"subchapter_name": "Campaign Metrics",
"component_types": [
"bar_chart",
"line_chart",
"metric_card_group",
"scatter_plot",
"summary_card"
],
"accepted_shapes": [
"campaign_attribution",
"campaign_efficiency",
"campaign_roas_trend",
"campaign_snapshot",
"channel_comparison"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe"
],
"route_family": "admin-surface",
"primary_tables": [
"campaign_metrics",
"lead_events"
]
},
{
"chapter_id": "ch-005",
"chapter_name": "Operational Metrics",
"subchapter_id": "sub-005-04",
"subchapter_name": "System Health",
"component_types": [
"action_panel",
"data_table",
"line_chart",
"metric_card_group",
"system_health_panel"
],
"accepted_shapes": [
"bounded_admin_actions",
"latency_time_series",
"queue_status",
"surface_session_snapshot",
"system_health_snapshot"
],
"surface_targets": [
"webos"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe",
"bounded_admin_actions",
"audit_ready"
],
"route_family": "admin-surface",
"primary_tables": [
"admin_action_events",
"oracle_synthetic_generation_jobs",
"inventory_import_batches",
"edge_transcription_jobs",
"surface_sessions"
]
},
{
"chapter_id": "ch-006",
"chapter_name": "Calendar and Follow-Up",
"subchapter_id": "sub-006-01",
"subchapter_name": "Calendar View",
"component_types": [
"calendar_agenda",
"calendar_heatmap",
"data_table",
"donut_chart",
"summary_strip"
],
"accepted_shapes": [
"calendar_density",
"calendar_mix",
"calendar_strip",
"calendar_suggestions",
"user_calendar_agenda"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe",
"user_calendar_scope",
"confirmation_required_for_writeback"
],
"route_family": "mobile-edge",
"primary_tables": [
"user_calendar_events",
"insight_recommendations",
"edge_communication_events"
]
},
{
"chapter_id": "ch-006",
"chapter_name": "Calendar and Follow-Up",
"subchapter_id": "sub-006-02",
"subchapter_name": "Action Queue",
"component_types": [
"action_strip",
"bar_chart",
"donut_chart",
"matrix_grid",
"prioritized_task_list"
],
"accepted_shapes": [
"action_status_mix",
"action_type_distribution",
"agent_action_queue",
"edge_action_strip",
"queue_urgency_matrix"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe",
"bounded_actions",
"operator_queue"
],
"route_family": "mobile-edge",
"primary_tables": [
"user_calendar_events",
"insight_recommendations",
"edge_communication_events"
]
},
{
"chapter_id": "ch-006",
"chapter_name": "Calendar and Follow-Up",
"subchapter_id": "sub-006-03",
"subchapter_name": "Follow-Up Plan",
"component_types": [
"compact_alert_card",
"data_table",
"structured_plan_card",
"summary_card",
"timeline_chart"
],
"accepted_shapes": [
"escalation_plan",
"follow_up_cadence",
"follow_up_edge_card",
"follow_up_plan",
"follow_up_timeline"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe",
"nemoclaw_suggested",
"confirmation_required_for_writeback"
],
"route_family": "mobile-edge",
"primary_tables": [
"user_calendar_events",
"insight_recommendations",
"edge_communication_events"
]
},
{
"chapter_id": "ch-006",
"chapter_name": "Calendar and Follow-Up",
"subchapter_id": "sub-006-04",
"subchapter_name": "Reminder Cards",
"component_types": [
"compact_alert_card",
"kanban_board",
"matrix_grid",
"stacked_reminder_cards",
"summary_strip"
],
"accepted_shapes": [
"insight_recommendation",
"reminder_priority_matrix",
"reminder_snooze_board",
"reminder_stack",
"reminder_strip"
],
"surface_targets": [
"webos",
"ipad",
"android_tablet",
"iphone_edge",
"android_phone_edge"
],
"policy_tags": [
"backend_owned",
"live_data_first",
"no_mock_fallback",
"surface_safe",
"bounded_actions",
"surface_agnostic"
],
"route_family": "mobile-edge",
"primary_tables": [
"user_calendar_events",
"insight_recommendations",
"edge_communication_events"
]
}
]

View File

@@ -0,0 +1,38 @@
{
"status": "ok",
"checks": {
"total_examples": 1200,
"unique_example_ids": 1200,
"subchapters_with_50_examples": true,
"chapters": 6,
"subchapters": 24,
"source_seed_examples_reported": 36,
"source_seed_examples_actual": 8
},
"per_subchapter": {
"sub-001-01": 50,
"sub-001-02": 50,
"sub-001-03": 50,
"sub-001-04": 50,
"sub-002-01": 50,
"sub-002-02": 50,
"sub-002-03": 50,
"sub-002-04": 50,
"sub-003-01": 50,
"sub-003-02": 50,
"sub-003-03": 50,
"sub-003-04": 50,
"sub-004-01": 50,
"sub-004-02": 50,
"sub-004-03": 50,
"sub-004-04": 50,
"sub-005-01": 50,
"sub-005-02": 50,
"sub-005-03": 50,
"sub-005-04": 50,
"sub-006-01": 50,
"sub-006-02": 50,
"sub-006-03": 50,
"sub-006-04": 50
}
}

View File

@@ -19,9 +19,11 @@
- Compose app shell
- Navigation graph
- Dashboard, Inventory, Oracle, Sentinel, and Settings feature stubs
- Added iPhone edge app scaffold under `iOS/velocity-edge-phone/`
- SwiftUI app entry
- Added dedicated iPhone edge app source tree under `iOS/velocity-iphone/`
- SwiftUI app entry and tab shell
- Shared Velocity-styled phone UI tokens and cards
- Alerts, Lead Summary, Communications, Notes, Transcriptions, Settings
- Live backend auth, notes, transcript, memory, alerts, and heartbeat wiring
- Added Android phone edge scaffold under `android-edge-phone/`
- Compose app shell
- Alerts, Lead Summary, Communications, Notes, Transcriptions, Settings
@@ -57,14 +59,38 @@
- `iOS/velocity/velocity/Features/Calendar/CalendarView.swift`
- `iOS/velocity/velocity/Core/Networking/VelocityAPIClient.swift`
- `iOS/velocity/velocity/Core/Config/AppConfig.swift`
- Completed a broader iOS production hardening pass:
- `iOS/velocity/velocity/Core/Config/AppConfig.swift` now defaults to `https://api.desineuron.in` instead of a stale instance IP
- `iOS/velocity/velocity/Core/Networking/VelocityAPIClient.swift` now reads live inventory summaries in addition to leads, events, alerts, and calendar
- `iOS/velocity/velocity/Core/State/AppStore.swift` no longer seeds fabricated dashboard, visitor, chat, or oracle state and now hydrates a live shared snapshot
- `iOS/velocity/velocity/Features/Dashboard/DashboardView.swift` now renders live lead, inventory, calendar, and alert posture instead of synthetic KPIs and AI chat
- `iOS/velocity/velocity/Features/Oracle/OracleView.swift` now uses live pipeline, live communication timelines, and live calendar events; unavailable Oracle modes are shown truthfully instead of mock canvases
- `iOS/velocity/velocity/Features/Sentinel/SentinelView.swift` now disables visitor analytics until a real Sentinel feed exists and shows live operator urgency instead of fake biometric metrics
- `iOS/velocity/velocity/Features/Settings/SettingsView.swift` now reflects real backend/auth/runtime state
- `iOS/velocity/velocity/Features/Inventory/InventoryView.swift` no longer renders the simulator-only fake AR sun overlay in the active production path
- Completed the Android mobile hardening pass:
- `android-tablet/` now includes a live backend client, Gradle runtime config fields, internet permission, and live-backed Dashboard, Inventory, Oracle, Sentinel, and Settings surfaces
- `android-edge-phone/` now includes a live mobile-edge client, Gradle runtime config fields, internet permission, and live-backed Alerts, Lead Summary, Communications, Notes, Transcriptions, and Settings surfaces
- Android Notes now writes to the real `POST /api/mobile-edge/notes` route when credentials are configured
- Android Sentinel surfaces are now explicitly truthful about missing production biometric feeds instead of implying live analytics
- Completed the dedicated iPhone edge production source pass:
- `iOS/velocity-iphone/` now supersedes the earlier lightweight edge scaffold and is the single iPhone source of truth
- Added a dedicated standalone Xcode project at `iOS/velocity-iphone/velocity-iphone.xcodeproj` so the phone app can now be opened and tested directly in Xcode
- Added `iOS/velocity-iphone/Assets.xcassets` with app icon and accent color catalog plumbing required by the standalone target
- `iOS/velocity-iphone/Core/Networking/VelocityEdgeAPIClient.swift` now uses live `/api/auth/login`, `/api/leads`, `/api/mobile-edge/alerts`, `/api/mobile-edge/events`, `/api/mobile-edge/memory`, `/api/mobile-edge/notes`, `/api/mobile-edge/transcripts/{eventId}`, and `/api/mobile-edge/session`
- `iOS/velocity-iphone/Core/State/EdgeAppStore.swift` now manages live shared phone state for the edge surface and refreshes note, transcript, and alert context after writes
- `iOS/velocity-iphone/Core/UI/EdgeTheme.swift` now preserves the iPad Velocity styling language while adapting cards and layout to narrow iPhone width
- `iOS/velocity-iphone/Features/Alerts/EdgeAlertsView.swift` now uses an adaptive metric grid suitable for phone resolution instead of a tablet-like fixed row
- iPhone edge screens now auto-refresh live state periodically so the app behaves like an active operator surface instead of a one-shot fetch
- `backend/api/routes_mobile_edge.py` session heartbeat handling was fixed so `iphone_edge` and other mobile surfaces update an active session window instead of creating redundant session rows on every heartbeat
- Removed the superseded `iOS/velocity-edge-phone/` scaffold after confirming nothing useful remained outside the stronger `velocity-iphone` source tree
### MVP limits still in place
- Android projects are scaffolds only; they are not yet wired to shared API clients, auth, or install registration.
- The iPhone edge scaffold is source-first and does not yet include a dedicated `.xcodeproj` target.
- The `iOS/velocity-iphone/` source tree now includes a dedicated standalone `.xcodeproj`, but full `xcodebuild` verification still depends on a machine with full Xcode, simulator runtimes, signing configuration, and live credentials.
- The WebOS admin page is mounted into the live Vite shell and can now stage bounded actions against the backend audit trail; auto-execution remains intentionally out of scope.
- The iPad Communications and Calendar views read live backend data, but the broader iPad app still contains other legacy mock-backed modules outside this residual slice.
- The active WebOS runtime path has been hardened away from mock/demo behavior, but deeper non-WebOS workstream items remain out of scope for this pass, especially legacy iPad modules and simulator-only inventory/AR helpers on iOS.
- The iPad production shell no longer uses fabricated dashboard/oracle/sentinel state, but some specialist inventory helpers remain beyond the current source hardening pass; on iPhone, the main remaining gaps are host-side build verification, signing, and supplying final production app-icon artwork rather than feature wiring.
- Android mobile surfaces now read live data, but full device verification still requires Gradle on the host and real credentials in Gradle properties.
- Oracle template seed metadata needs correction: `_meta.total_seed_examples` does not match the actual seed example count in `backend/oracle/oracle_template_seed_db.json`.
- Sprint-1 documentation artifacts called for in the delivery pack are still missing as committed repo outputs, including the residual audit artifact and contract/package documentation.
@@ -75,7 +101,15 @@
- SQL parse sanity check on extension migration
- `npx tsc --noEmit` for the WebOS app after live-auth and no-mock hardening changes
- `python3 -m py_compile` for backend auth and route modules after role/session hardening
- `swiftc -parse` over the active iPad Swift source set after mobile production hardening
- `swiftc -parse` over the complete `iOS/velocity-iphone/` source tree
- `plutil -lint` for `iOS/velocity-iphone/Info.plist`
- `plutil -lint` for `iOS/velocity-iphone/velocity-iphone.xcodeproj/project.pbxproj`
- `python3 -m py_compile backend/api/routes_mobile_edge.py` after fixing surface session heartbeat behavior
- XML validation for both Android manifests
- Kotlin delimiter sanity checks over `android-tablet` and `android-edge-phone` source trees
- Full `xcodebuild` and Gradle assembly were not run on this host because full Xcode and Gradle are not installed here
### Recommended next implementation step
WebOS is now the strongest completed surface. The next implementation step should move fully to iOS and Android completion while preserving the live backend/auth patterns established here.
WebOS and the primary iPad/Android/iPhone source surfaces are now aligned around live backend truthfulness. The next implementation step should be dedicated device/build-host verification across the standalone `iOS/velocity-iphone/velocity-iphone.xcodeproj`, the existing iPad target, and the Android builds with live credentials.

View File

@@ -0,0 +1,324 @@
# Sprint 1 Fact Table — Updated 2026-04-21
> **Purpose**: Track what's done vs. what's left across all Project Velocity modules.
> **Last Audit Date**: 2026-04-21 (full codebase review)
> **Previous Version**: Sprint 1 Fact Table - 2026-04-12 (marked many items "Missing" that are now implemented)
---
## Executive Summary
| Metric | Value |
|--------|-------|
| **Total Backend Route Files** | 10 (`routes_crm.py`, `routes_crm_imports.py`, `routes_oracle.py`, `routes_oracle_templates.py`, `routes_catalyst.py`, `routes_inventory.py`, `routes_mobile_edge.py`, `routes_runtime_llm.py`, `routes_admin_surface.py`, `routes_weaver.py`) |
| **Total Backend Services** | 5 (aggregation_service, ingest_service, ad_network_service, nemoclaw_runtime, runtime_llm_service) |
| **Frontend Modules (React)** | 7 (Dashboard, Oracle, Sentinel, Inventory, Catalyst, CRM, Settings) + Admin page |
| **iOS Apps** | 2 (velocity iPad app, velocity-iphone Edge app) |
| **Infrastructure Layers** | 4 (aws_scale, blackbox_local, desineuron_ingress, ops_control_plane) |
| **Test Coverage** | 10 test files across backend |
### Status Legend
-**Done** — Fully implemented, functional code exists
- 🔶 **Partial** — Core logic exists but needs refinement/completion
-**Missing** — No implementation found in current codebase
- 📋 **Planned** — Documented in specs but not yet coded
---
## User Story Rollup
### US-01: FastAPI Neural Core (Unified Backend)
| Item | Status | Evidence |
|------|--------|----------|
| FastAPI app with auth middleware | ✅ Done | `backend/auth/``get_current_user`, `UserPrincipal` |
| PostgreSQL connection pooling | ✅ Done | All routes use `request.app.state.db_pool` |
| WebSocket support | 🔶 Partial | `useVelocitySocket` hook exists in frontend; backend WS layer not confirmed in current scan |
| Auth (login/logout/session) | ✅ Done | `getVelocityMe`, `clearVelocityToken`, token validation in `App.tsx` |
| Role-based access (admin/superadmin) | ✅ Done | `routes_admin_surface.py` enforces `ADMIN_ROLES`; `isAdminRole()` guard in frontend |
**Verdict**: ✅ **Done** — Core backend is production-ready.
---
### US-02: CRM — Canonical Layer
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| `POST/GET /crm/imports` (CSV upload + lifecycle) | ✅ Done | [`routes_crm_imports.py`](backend/api/routes_crm_imports.py:102) — 799 lines | Full import pipeline: upload → parse → infer mapping → proposals → review → commit |
| `POST/GET /crm/contacts` | ✅ Done | [`routes_crm_imports.py`](backend/api/routes_crm_imports.py:429) | CRUD for `crm_people` |
| `GET /crm/client-360/{id}` | ✅ Done | [`routes_crm_imports.py`](backend/api/routes_crm_imports.py:527) | Joins across 8 canonical tables via [`aggregation_service.py`](backend/services/client_graph/aggregation_service.py:102) |
| `GET /crm/opportunities` | ✅ Done | [`routes_crm_imports.py`](backend/api/routes_crm_imports.py:544) | Full pipeline list with stage/probability/value |
| `GET/POST /crm/tasks` | ✅ Done | [`routes_crm_imports.py`](backend/api/routes_crm_imports.py:603) | Reminder/inbox system |
| `GET /crm/kanban` | ✅ Done | [`routes_crm_imports.py`](backend/api/routes_crm_imports.py:697) | Kanban board from canonical data |
| `GET /crm/qd/{id}` (Quantum Dynamics scores) | ✅ Done | [`routes_crm_imports.py`](backend/api/routes_crm_imports.py:752) | QD score summary + timeseries |
| CSV import column mapping heuristics | ✅ Done | [`ingest_service.py`](backend/services/imports/ingest_service.py:30) — 40+ canonical mappings | Confidence scoring, review_required flags |
| CRM Frontend — Contacts view | ✅ Done | [`CRM.tsx`](app/src/components/modules/CRM.tsx:89) — ContactListView with search/filter/pagination |
| CRM Frontend — Kanban view | ✅ Done | [`CRM.tsx`](app/src/components/modules/CRM.tsx:282) — PipelineView with drag-ready columns |
| CRM Frontend — Opportunities view | ✅ Done | [`CRM.tsX`](app/src/components/modules/CRM.tsx:363) — Deal pipeline table |
| CRM Frontend — Tasks view | ✅ Done | [`CRM.tsx`](app/src/components/modules/CRM.tsx:448) — Priority-ordered task list |
| CRM Frontend — Import view | ✅ Done | [`CRM.tsx`](app/src/components/modules/CRM.tsx:518) — File picker with live upload |
| CRM Frontend — Client 360 panel | ✅ Done | [`CRM.tsx`](app/src/components/modules/CRM.tsx:550) — Slide-over dossier with QD bars, risk flags, recommended actions |
| Canonical schema (`schema_crm_canonical.sql`) | ✅ Done | 709 lines — 25+ tables across CRM Core, Intel Graph, Inventory Domain, Workflow Governance |
**Verdict**: ✅ **Done** — CRM is the most complete module. Both backend and frontend are fully implemented with canonical data model.
---
### US-03: CRM — Legacy Layer (routes_crm.py)
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| `GET/POST /leads` | ✅ Done | [`routes_crm.py`](backend/api/routes_crm.py:227) — 631 lines | Legacy leads table (separate from canonical) |
| `PUT/DELETE /leads/{id}` | ✅ Done | Same file | Full CRUD |
| `POST /leads/seed-synthetic` | ✅ Done | Generates 100 synthetic leads with chat logs |
| `GET /chat-logs` | ✅ Done | Chat log endpoints functional |
| `GET /kanban/board` | ✅ Done | Legacy kanban board |
| `GET /leads/demographics` | ✅ Done | Demographics analytics |
| WebSocket CRM events | 🔶 Partial | `_broadcast_crm_event()` helper exists (line 60) but WS server not confirmed |
**Verdict**: 🔶 **Partial** — Fully coded but legacy. Should be deprecated in favor of canonical layer. Two parallel CRM surfaces exist (`routes_crm.py` vs `routes_crm_imports.py`).
---
### US-04: Oracle Canvas System
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| Oracle canvas API (`routes_oracle.py`) | ✅ Done | 107 lines — health, MCP tools, workflow preview, actions/writeback | Mounted router with `persona_service`, `mcp_registry`, `nemoclaw_runtime` |
| Oracle template catalog (`routes_oracle_templates.py`) | ✅ Done | 405 lines — chapters, subchapters, component templates, seed examples, synthetic jobs | Full taxonomy CRUD |
| Oracle frontend page | ✅ Done | [`app/oracle/page.tsx`](app/oracle/page.tsx) — Full canvas viewport |
| Oracle components (BranchBar, CanvasViewport, ComponentRegistry, PromptRail) | ✅ Done | 10+ React components in `oracle/components/` |
| Oracle renderers (9 types) | ✅ Done | ActivityStream, BarChart, ErrorNotice, GeoMap, KpiTile, LineChart, PipelineBoard, Table, TextCanvas, Timeline |
| Oracle hooks (`useOracleExecution`, `useOraclePage`) | ✅ Done | Execution and page state management |
| Oracle canvas TypeScript types | ✅ Done | `oracle/types/canvas.ts` — Full type definitions |
| Oracle collaboration service | 🔶 Partial | Test file exists (`test_collaboration_service.py`) but production code not confirmed |
| Oracle policy service | 🔶 Partial | Test file exists (`test_policy_service.py`) but production code not confirmed |
**Verdict**: 🔶 **Partial** — Core canvas API and template system are done. Collaboration and policy services need confirmation of production readiness.
---
### US-05: The Catalyst (Marketing Automation)
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| Meta Marketing API integration | ✅ Done | [`routes_catalyst.py`](backend/api/routes_catalyst.py:134) — 513 lines | Campaigns, creative sync, insights, budget/bid, lookalike audiences |
| `POST /auth/meta` (OAuth token exchange) | ✅ Done | Meta OAuth flow endpoint |
| Google Ads platform support | 🔶 Partial | Platform mappers exist but Google is simulated (not live) |
| Campaign Command frontend | ✅ Done | [`Catalyst.tsx`](app/src/components/modules/Catalyst.tsx:537) — KPI cards, spend chart, campaign list |
| The Studio (ComfyUI workflow input) | ✅ Done | Ground Truth picker, reference slots, image/video toggle |
| Intelligence & ROI tab | ✅ Done | CPA trend chart, ad-set performance bars |
| War Room (Meta Graph settings) | ✅ Done | API credential forms, business asset links, required scopes |
| Marketing tab | ✅ Done | [`CatalystMarketingTab.tsx`](app/src/components/modules/CatalystMarketingTab.tsx) |
| Live Optimization Feed | ✅ Done | Real-time event stream with 6 event types |
| Meta SDK integration | ✅ Done | `facebook_business` SDK for live API calls |
**Verdict**: 🔶 **Partial** — Meta integration is fully functional. Google Ads is simulated. Production Meta credentials required for full operation.
---
### US-06: Inventory Pipeline
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| Import batches API | ✅ Done | [`routes_inventory.py`](backend/api/routes_inventory.py:95) — 400 lines | CRUD for `inventory_import_batches` |
| Properties CRUD | ✅ done | Same file — create, list, get, patch, delete |
| Media assets management | ✅ Done | Attach/list/delete media to properties |
| Inventory frontend | ✅ Done | [`Inventory.tsx`](app/src/components/modules/Inventory.tsx:829) — Grid/list views, 3D viewer, blueprint studio |
| 3D model viewer (React Three Fiber) | ✅ Done | GLTF loading, orbit controls, auto-fit |
| Blueprint Studio (zoom/pan) | ✅ Done | Wheel zoom, drag pan, fit-to-height |
| Unit detail modal | ✅ Done | Full property details with payment plans |
| Google Maps embed | ✅ Done | Right-pane map integration |
**Verdict**: ✅ **Done** — Inventory is fully implemented with rich frontend.
---
### US-07: Mobile Edge API
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| Communication events CRUD | ✅ Done | [`routes_mobile_edge.py`](backend/api/routes_mobile_edge.py:133) — 659 lines | All channels (PSTN, WhatsApp, email, FB, IG, VoIP) |
| Memory facts (edge_communication_memory_facts) | ✅ Done | List endpoint at line 211 |
| Operator-assisted import | ✅ Done | Creates events + triggers transcription jobs |
| Quick notes | ✅ Done | Direct fact insertion |
| Calendar CRUD | ✅ Done | Full calendar event management |
| Transcript retrieval | ✅ Done | Joins `edge_transcription_jobs``edge_transcript_segments` |
| Insights (recommendations) | ✅ Done | List + act/dismiss endpoints |
| Alerts (combined view) | ✅ Done | Aggregates pending insights, upcoming events, pending transcriptions |
| Session heartbeat | ✅ Done | Surface session tracking with screen sequence |
| iOS Oracle view | ✅ Done | Pipeline, timeline, calendar canvases |
| iOS Sentinel view | ✅ Done | Posture cards (pending insights, transcript queue, upcoming 24h) |
| iOS Edge apps (iPhone + iPad) | ✅ Done | `velocity-iphone/` — Alerts, Communications, LeadSummary, Notes, Transcriptions |
**Verdict**: ✅ **Done** — Mobile edge API is comprehensive. Both backend and iOS clients are functional.
---
### US-08: Runtime LLM Service
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| Provider listing | ✅ Done | [`routes_runtime_llm.py`](backend/api/routes_runtime_llm.py:53) — `GET /providers` |
| Chat completion | ✅ Done | `POST /chat` with provider/model routing |
| Batch job submission | ✅ Done | `POST /batch` with persisted job tracking |
| Job status/results | ✅ Done | `GET /jobs/{id}` and `GET /jobs/{id}/results` |
| `runtime_llm_service.py` | ✅ Done | Service layer with provider abstraction |
**Verdict**: ✅ **Done** — Runtime LLM surface is complete.
---
### US-09: Admin Control Plane
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| System health overview | ✅ Done | [`routes_admin_surface.py`](backend/api/routes_admin_surface.py:86) — DB latency, queue depths, session counts |
| Queue visibility | ✅ Done | Transcription, synthetic, inventory, admin action queues |
| Install/surface overview | ✅ Done | Surface type + app version breakdown |
| Admin actions (audit trail) | ✅ Done | 13 action types with idempotency keys |
| Audit log | ✅ Done | `oracle_audit_events` query surface |
| Template admin (publish/archive) | ✅ Done | Full template lifecycle management |
| Synthetic job admin | ✅ Done | List + cancel synthetic generation jobs |
| Admin frontend page | ✅ Done | [`app/admin/page.tsx`](app/admin/page.tsx) |
**Verdict**: ✅ **Done** — Admin control plane is fully implemented.
---
### US-10: Dream Weaver (ComfyUI Engine)
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| ComfyUI workflows | ✅ Done | 8 workflow JSON files in `comfy_engine/workflows/` |
| Test inputs (20+ images) | ✅ Done | Diverse test set across room types |
| Dream Weaver spec | ✅ Done | `docs/DREAMWEAVER_TECHNICAL_SPEC.md` |
| `routes_weaver.py` | ❌ Missing | File exists but is **empty** (0 bytes) |
| Weaver gateway (`dw_gateway_v2_min.py`) | 🔶 Partial | Root-level file exists — needs review for integration status |
**Verdict**: 🔶 **Partial** — ComfyUI engine has workflows and test data. Routes file is empty; gateway file needs integration review.
---
### US-11: Sentinel (Biometric Intelligence)
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| Sentinel overview frontend | ✅ Done | [`Sentinel.tsx`](app/src/modules/Sentinel.tsx:321) — Visitor counts, sentiment, dwell time, alerts |
| Journey River component | ✅ Done | `components/sentinel/JourneyRiver/` — Path, inspector panel |
| Live Session component | ✅ Done | `SentinelLiveSession.tsx` |
| Perception player | ✅ Done | `PerceptionPlayer.tsx` |
| iOS Sentinel view | 🔶 Partial | Shows posture cards from mobile-edge backend; explicitly notes "No mock feed" — real Sentinel stream route needed |
| MediaPipe hooks | 🔶 Partial | `useMediapipeFaceLandmarker` hook exists in frontend |
| QD scoring (nemoclaw) | ✅ Done | `nemoclaw_runtime.py` + test file exist |
| Auto-mode matcher | ✅ Done | `auto_mode_matcher.py` service |
| Sentinel backend routes | ❌ Missing | No dedicated Sentinel API routes found in `backend/api/` |
**Verdict**: 🔶 **Partial** — Frontend is rich and functional. iOS shows real data from mobile-edge. Backend biometric stream route is missing.
---
### US-12: iOS Time & Light Engine
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| AR Sun Overlay | 🔶 Partial | `ARSunOverlayView.swift` exists in both iPad and iPhone apps |
| Sunseeker ViewModel | ✅ Done | `SunseekerViewModel.swift` — Solar position calculations |
| Simulator Sun overlay | ✅ Done | `SimulatorSunOverlayView.swift` fallback |
| Inventory AR features | 🔶 Partial | Connected to Inventory module but needs real-time sun data pipeline |
**Verdict**: 🔶 **Partial** — Core components exist. Real-time sun data integration needed.
---
### US-13: Infrastructure & Deployment
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| AWS ingress (t4g.micro) | 🔶 Partial | `infrastructure/aws_scale/` directory exists |
| GPU workers (g6.12xlarge) | 🔶 Partial | Referenced in docs but IaC not confirmed |
| Caddy reverse proxy | 🔶 Partial | `infrastructure/blackbox_local/` — needs review |
| Rathole tunnels | 🔶 Partial | `infrastructure/desineuron_ingress/` — needs review |
| Ops control plane | 🔶 Partial | `infrastructure/ops_control_plane/` — needs review |
| NVMe-first deployment | 🔶 Partial | `monitor_nvme.py` exists at root |
| Deploy scripts | 🔶 Partial | `patch_nemoclaw_service_20260401.sh`, `.oracle_deploy_stage.tar` |
**Verdict**: 🔶 **Partial** — Infrastructure artifacts exist but need consolidation and review.
---
### US-14: Synthetic Data & Testing
| Item | Status | Evidence | Notes |
|------|--------|----------|-------|
| Synthetic CRM v1 dataset | ✅ Done | `db assets/synthetic_crm_v1/` — 360 snapshots, mapping manifest, relationships, transcripts |
| Test suite (10 files) | ✅ Done | `backend/tests/` — catalyst, crm, websocket, nemoclaw, oracle, vault tests |
| Oracle sub-tests | ✅ Done | canvas_service, collaboration_service, persona_service, policy_service, prompt_orchestrator |
**Verdict**: ✅ **Done** — Testing and synthetic data are comprehensive.
---
## Cross-Reference: Old Fact Table vs Current Codebase
| Claim in Old Fact Table (2026-04-12) | Current Reality | Delta |
|---------------------------------------|-----------------|-------|
| `backend/api/routes_crm.py` = 0 bytes | **631 lines** — full CRUD + seed + demographics + kanban | ✅ Now Done |
| `/api/leads` = Missing | **Fully implemented** in both legacy and canonical layers | ✅ Now Done |
| `/api/chat-logs` = Missing | **Fully implemented** with synthetic data generation | ✅ Now Done |
| Kanban board = Missing | **Implemented in both** `routes_crm.py` (legacy) and `routes_crm_imports.py` (canonical) | ✅ Now Done |
| `backend/api/routes_oracle.py` = 0 bytes | **107 lines** — health, MCP, workflow preview, actions | ✅ Now Done |
| Oracle canvas = Missing | **Fully implemented** with 10+ frontend components + template system | ✅ Now Done |
| CRM imports = Missing | **799-line canonical import pipeline** with CSV parsing, mapping, proposals | ✅ Now Done |
| Inventory API = Partial | **400-line full CRUD** with media assets | ✅ Now Done |
| Mobile edge = Partial | **659-line comprehensive API** with events, calendar, transcripts, insights | ✅ Now Done |
---
## What's Left (Sprint 2+ Priorities)
### BLOCKERS (Must complete before production)
1. **Sentinel biometric stream route** — No dedicated backend endpoint for live CCTV/face detection pipeline
2. **Dream Weaver routes**`routes_weaver.py` is empty; ComfyUI gateway needs integration
3. **WebSocket server confirmation** — WS layer exists in hooks but backend WS server not confirmed
### HIGH PRIORITY
4. **Google Ads platform** — Currently simulated; needs live Google Ads API integration
5. **Oracle collaboration service** — Test exists, production code unconfirmed
6. **Oracle policy service** — Test exists, production code unconfirmed
7. **Infrastructure consolidation** — 4 infrastructure directories need review and unified deployment
### MEDIUM PRIORITY
8. **Legacy CRM deprecation** — Two parallel CRM surfaces (`routes_crm.py` vs `routes_crm_imports.py`) create maintenance burden
9. **iOS AR sun data pipeline** — Real-time solar position integration needed
10. **CI/CD pipeline** — No build/deploy automation found
### LOW PRIORITY (Nice to have)
11. **Multi-tenant isolation** — Current code uses `user.role` as tenant_id; needs proper tenant separation
12. **Rate limiting** — No rate limiting middleware found
13. **API documentation** — No OpenAPI/Swagger docs generated
---
## Module Health Matrix
| Module | Backend | Frontend | iOS | Tests | Overall |
|--------|---------|----------|-----|-------|---------|
| CRM (Canonical) | ✅ Done | ✅ Done | 🔶 Partial | ✅ Done | ✅ **Done** |
| CRM (Legacy) | ✅ Done | N/A | N/A | ✅ Done | 🔶 **Partial** |
| Oracle Canvas | ✅ Done | ✅ Done | ✅ Done | ✅ Done | ✅ **Done** |
| Catalyst | ✅ Done | ✅ Done | N/A | ✅ Done | 🔶 **Partial** |
| Inventory | ✅ Done | ✅ Done | N/A | N/A | ✅ **Done** |
| Mobile Edge | ✅ Done | N/A | ✅ Done | ✅ Done | ✅ **Done** |
| Runtime LLM | ✅ Done | N/A | N/A | ✅ Done | ✅ **Done** |
| Admin Control | ✅ Done | ✅ Done | N/A | ✅ Done | ✅ **Done** |
| Dream Weaver | ❌ Missing | N/A | N/A | N/A | 🔶 **Partial** |
| Sentinel | ❌ Missing | ✅ Done | 🔶 Partial | ✅ Done | 🔶 **Partial** |
| Time & Light | N/A | N/A | 🔶 Partial | N/A | 🔶 **Partial** |
| Infrastructure | 🔶 Partial | N/A | N/A | N/A | 🔶 **Partial** |
---
## Code Quality Notes
### [BLOCKER]
- **Dual CRM surfaces**: Both `routes_crm.py` (legacy) and `routes_crm_imports.py` (canonical) handle leads. Plan deprecation of legacy layer.
### [SUGGESTION]
- **SQL injection risk in dynamic WHERE clauses**: [`routes_inventory.py`](backend/api/routes_inventory.py:209-231) and [`routes_mobile_edge.py`](backend/api/routes_mobile_edge.py:334-356) build WHERE clauses with f-strings. Parameterized values are safe, but column names are interpolated — ensure no user input reaches these.
- **Hardcoded tenant ID**: [`routes_oracle_templates.py`](backend/api/routes_oracle_templates.py:36) uses `os.getenv("ORACLE_DEFAULT_TENANT_ID", "tenant_velocity")` — consider making this a request-scoped value.
### [NIT]
- **Import organization**: Several files use inline `import json` inside functions rather than at module level.
- **Magic numbers**: Threshold values (e.g., `30 minutes` in session heartbeat) should be constants.
---
*Fact table generated by Chanakya (Review Mode) on 2026-04-21 after full codebase audit.*

View File

@@ -0,0 +1,63 @@
name: Production Readiness
on:
pull_request:
push:
branches:
- main
- master
jobs:
backend-contracts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install backend dependencies
run: |
python -m pip install --upgrade pip
pip install -r backend/requirements.txt
pip install pytest
- name: Run backend contract tests
run: |
PYTHONPATH="$PWD" python -m pytest \
backend/tests/test_auth_tenant_contract.py \
backend/tests/test_canonical_crm_auth.py \
backend/tests/test_canonical_crm_tenant_scoping.py \
backend/tests/test_dream_weaver_gateway_auth.py \
backend/tests/test_migrations_and_observability.py \
backend/tests/test_surface_route_tenant_scoping.py
webos-typecheck:
runs-on: ubuntu-latest
defaults:
run:
working-directory: app
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: app/package-lock.json
- name: Install WebOS dependencies
run: npm ci
- name: Typecheck WebOS
run: npx tsc --noEmit
ipad-parse:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Parse active iPad Swift sources
run: |
swiftc -frontend -parse \
iOS/velocity-ipad/velocity/App/ContentView.swift \
iOS/velocity-ipad/velocity/Features/Clients/ClientsView.swift \
iOS/velocity-ipad/velocity/Features/Imports/ImportsView.swift \
iOS/velocity-ipad/velocity/Core/Networking/VelocityAPIClient.swift \
iOS/velocity-ipad/velocity/Core/State/AppStore.swift \
iOS/velocity-ipad/velocityTests/VelocitySmokeTests.swift

4
.gitignore vendored
View File

@@ -171,6 +171,10 @@ docker-compose.override.yml
*.pem
*.mp4
*.zip
.local-dev/
runtime-snapshots/
local-dev-bundles/
scripts/tmp/
models/
comfy_engine/test_outputs/

BIN
.oracle_deploy_stage.tar Normal file

Binary file not shown.

View File

@@ -0,0 +1,167 @@
/**
* ComponentRegistry — maps CanvasComponent.type to renderer implementations.
* Supports lazy loading for expensive renderers (GeoMap, Table, PipelineBoard).
* Falls back to ErrorNoticeRenderer for unknown or revoked types.
*/
import { lazy, Suspense } from 'react';
import type { CanvasComponent } from '../types/canvas';
// ── Eager renderers (lightweight) ─────────────────────────────────────────────
import { KpiTileRenderer } from './renderers/KpiTileRenderer';
import { ErrorNoticeRenderer } from './renderers/ErrorNoticeRenderer';
import { TimelineRenderer } from './renderers/TimelineRenderer';
import { TextCanvasRenderer } from './renderers/TextCanvasRenderer';
// ── Lazy renderers (heavier) ──────────────────────────────────────────────────
const BarChartRenderer = lazy(() => import('./renderers/BarChartRenderer').then((m) => ({ default: m.BarChartRenderer })));
const LineChartRenderer = lazy(() => import('./renderers/LineChartRenderer').then((m) => ({ default: m.LineChartRenderer })));
const GeoMapRenderer = lazy(() => import('./renderers/GeoMapRenderer').then((m) => ({ default: m.GeoMapRenderer })));
const TableRenderer = lazy(() => import('./renderers/TableRenderer').then((m) => ({ default: m.TableRenderer })));
const PipelineBoardRenderer = lazy(() => import('./renderers/PipelineBoardRenderer').then((m) => ({ default: m.PipelineBoardRenderer })));
const ActivityStreamRenderer = lazy(() => import('./renderers/ActivityStreamRenderer').then((m) => ({ default: m.ActivityStreamRenderer })));
// ── Render context ────────────────────────────────────────────────────────────
export interface ComponentRenderContext {
tenantId: string;
actorRole: string;
showLineageBadges: boolean;
density: 'compact' | 'comfortable';
isSelected?: boolean;
onSelect?: (componentId: string) => void;
}
// ── Skeleton ──────────────────────────────────────────────────────────────────
function ComponentSkeleton({ variant }: { variant: string }) {
const heights: Record<string, number> = {
chart: 280, map: 380, table: 300, kpi: 120, pipeline: 360, timeline: 300, generic: 240,
};
const h = heights[variant] ?? 240;
return (
<div
className="rounded-2xl animate-pulse"
style={{
height: h,
background: 'rgba(255,255,255,0.03)',
border: '1px solid rgba(255,255,255,0.06)',
}}
/>
);
}
// ── Registry resolver ─────────────────────────────────────────────────────────
interface RegistryRendererProps {
component: CanvasComponent;
ctx: ComponentRenderContext;
}
export function ComponentRegistry({ component, ctx }: RegistryRendererProps) {
const skeleton = <ComponentSkeleton variant={component.renderingHints.skeletonVariant} />;
if (component.lifecycleState === 'revoked') {
return (
<ErrorNoticeRenderer
component={{
...component,
title: 'Component Revoked',
visualizationParameters: {
errorCode: 'component_revoked',
message: 'This component has been revoked and can no longer be rendered.',
},
}}
ctx={ctx}
/>
);
}
switch (component.type) {
case 'textCanvas':
return <TextCanvasRenderer component={component} ctx={ctx} />;
case 'kpiTile':
return <KpiTileRenderer component={component} ctx={ctx} />;
case 'errorNotice':
return <ErrorNoticeRenderer component={component} ctx={ctx} />;
case 'timeline':
return <TimelineRenderer component={component} ctx={ctx} />;
case 'barChart':
return (
<Suspense fallback={skeleton}>
<BarChartRenderer component={component} ctx={ctx} />
</Suspense>
);
case 'lineChart':
case 'forecastChart':
return (
<Suspense fallback={skeleton}>
<LineChartRenderer component={component} ctx={ctx} />
</Suspense>
);
case 'geoMap':
case 'heatmap':
return (
<Suspense fallback={skeleton}>
<GeoMapRenderer component={component} ctx={ctx} />
</Suspense>
);
case 'table':
return (
<Suspense fallback={skeleton}>
<TableRenderer component={component} ctx={ctx} />
</Suspense>
);
case 'pipelineBoard':
return (
<Suspense fallback={skeleton}>
<PipelineBoardRenderer component={component} ctx={ctx} />
</Suspense>
);
case 'activityStream':
return (
<Suspense fallback={skeleton}>
<ActivityStreamRenderer component={component} ctx={ctx} />
</Suspense>
);
case 'scatterPlot':
case 'customMLVisualization':
// Phase 2 renderers — show a meaningful placeholder with the right visual treatment
return (
<ErrorNoticeRenderer
component={{
...component,
visualizationParameters: {
errorCode: 'renderer_pending',
message: `The ${component.type} renderer is scheduled for Phase 2 synthesis. Data has been captured and is available.`,
severity: 'info',
},
}}
ctx={ctx}
/>
);
default:
return (
<ErrorNoticeRenderer
component={{
...component,
visualizationParameters: {
errorCode: 'unknown_type',
message: `Unknown component type: ${String(component.type)}`,
},
}}
ctx={ctx}
/>
);
}
}

View File

@@ -0,0 +1,35 @@
import type { CanvasComponent } from '../../types/canvas';
import { RendererWrapper, type ComponentRenderContext } from './RendererWrapper';
interface Props {
component: CanvasComponent;
ctx: ComponentRenderContext;
}
export function TextCanvasRenderer({ component, ctx }: Props) {
const params = component.visualizationParameters as {
content?: string;
};
const content = String(params.content ?? '').trim();
const paragraphs = content
.split(/\n{2,}/)
.map((block) => block.trim())
.filter(Boolean);
return (
<RendererWrapper component={component} ctx={ctx} minHeight={180}>
<div className="flex h-full flex-col gap-3 text-sm leading-7 text-zinc-200">
{paragraphs.length ? (
paragraphs.map((paragraph, index) => (
<p key={`${component.componentId}-${index}`} className="whitespace-pre-wrap text-zinc-300">
{paragraph}
</p>
))
) : (
<p className="text-zinc-500">No planning notes were generated for this prompt.</p>
)}
</div>
</RendererWrapper>
);
}

View File

@@ -0,0 +1,489 @@
/**
* Oracle Canvas — Canonical TypeScript Contracts
* Mirrors the JSON Schema from Section 6.2 of the Oracle Architecture Document v1.0
* These types replace the temporary OracleQueryResult contract.
*/
// ── Enums ─────────────────────────────────────────────────────────────────────
export type OracleRole =
| 'junior_broker'
| 'senior_broker'
| 'sales_director'
| 'marketing_operator'
| 'data_steward'
| 'compliance_reviewer'
| 'platform_admin';
export type ComponentType =
| 'textCanvas'
| 'kpiTile'
| 'barChart'
| 'lineChart'
| 'scatterPlot'
| 'geoMap'
| 'table'
| 'pipelineBoard'
| 'timeline'
| 'heatmap'
| 'forecastChart'
| 'activityStream'
| 'customMLVisualization'
| 'errorNotice';
export type ComponentLifecycleState = 'draft' | 'active' | 'superseded' | 'archived' | 'revoked';
export type PrivacyTier = 'standard' | 'restricted' | 'sensitive';
export type SourceType = 'postgres' | 'warehouse' | 'api' | 'materialized_view' | 'derived_dataset' | 'inline';
export type CachePolicyMode = 'none' | 'ttl' | 'revision_scoped';
export type IntentClass = 'analytical' | 'operational' | 'mixed';
export type ExecutionStatus =
| 'received'
| 'planning'
| 'validated'
| 'executing'
| 'completed'
| 'failed'
| 'clarification_required';
export type PageType = 'main' | 'fork';
export type ForkStatus = 'active' | 'merged' | 'closed';
export type MergeRequestStatus = 'open' | 'changes_requested' | 'approved' | 'merged' | 'closed';
export type TemplateStatus = 'catalog_active' | 'tenant_draft' | 'tenant_active' | 'archived' | 'revoked';
export type TemplateOrigin = 'premade' | 'synthesized' | 'cloned';
export type ShareMode = 'private' | 'direct_fork_only';
export type WidthMode = 'full' | 'half' | 'third';
export type VisibilityScope = 'private' | 'shared_fork' | 'tenant_team';
export type ComponentOriginType = 'catalog' | 'prompt_generated' | 'cloned' | 'merged' | 'edited';
export type PlacementMode =
| 'append_after_last_visible_component'
| 'insert_after_component'
| 'replace_component'
| 'group_under_section';
export type ActorType = 'user' | 'service' | 'ai';
export type LineageSourceKind =
| 'table'
| 'view'
| 'materialization'
| 'prompt'
| 'component'
| 'template'
| 'merge_request';
export type ValidationStatus = 'validated' | 'rejected' | 'needs_review';
export type CommitKind = 'prompt' | 'merge' | 'rollback' | 'manual_edit';
// ── Sub-objects ───────────────────────────────────────────────────────────────
export interface CachePolicy {
mode: CachePolicyMode;
ttlSeconds?: number;
}
export interface DataSourceDescriptor {
descriptorId: string;
sourceType: SourceType;
connectorId: string;
dataset: string;
authContextRef: string;
queryTemplate: string;
queryParameters: Record<string, unknown>;
rowLimit: number;
freshnessSlaSeconds?: number;
cachePolicy?: CachePolicy;
privacyTier: PrivacyTier;
lineageRefs?: string[];
}
export interface DataBindings {
dimensions: string[];
measures: string[];
series: string[];
filters: Array<{
field: string;
operator: string;
value: unknown;
}>;
}
export interface ComponentProvenance {
originType: ComponentOriginType;
templateId?: string;
promptExecutionId?: string;
sourceComponentId?: string;
sourceBranchId?: string;
mergeRequestId?: string;
createdBy: string;
createdAt: string;
}
export interface RenderingHints {
estimatedHeightPx: number;
skeletonVariant: 'chart' | 'map' | 'table' | 'kpi' | 'pipeline' | 'timeline' | 'generic';
virtualizationPriority: number;
}
export interface ComponentLayout {
orderIndex: number;
sectionId: string;
widthMode: WidthMode;
minHeightPx: number;
stickyHeader: boolean;
}
export interface AccessControls {
visibilityScope: VisibilityScope;
allowedRoles: OracleRole[];
redactionPolicy: string;
}
export interface StyleSignature {
theme: string;
paletteToken: string;
motionProfile: string;
density: 'compact' | 'comfortable';
radiusScale: string;
typographyScale: string;
}
export interface ValidationState {
schema: 'pass' | 'fail';
policy: 'pass' | 'fail';
a11y: 'pass' | 'fail';
performance: 'pass' | 'fail';
status: ValidationStatus;
}
// ── Core Entities ─────────────────────────────────────────────────────────────
export interface CanvasComponent {
componentId: string;
type: ComponentType;
title: string;
description?: string;
dataSourceDescriptor: DataSourceDescriptor;
visualizationParameters: Record<string, unknown>;
dataBindings: DataBindings;
version: number;
lifecycleState?: ComponentLifecycleState;
provenance: ComponentProvenance;
renderingHints: RenderingHints;
layout: ComponentLayout;
accessControls: AccessControls;
styleSignature: StyleSignature;
validationState: ValidationState;
auditLog: string[];
// Runtime-only: actual data rows fetched for this component
dataRows?: Record<string, unknown>[];
}
export interface ForkRecord {
forkId: string;
sourcePageId: string;
sourceBranchId: string;
sourceRevision: number;
forkPageId: string;
forkBranchId: string;
recipientUserId: string;
createdBy: string;
createdAt: string;
status: ForkStatus;
}
export interface LineageRecord {
lineageRecordId: string;
tenantId: string;
sourceKind: LineageSourceKind;
sourceId: string;
transformationType: string;
transformationSpecHash?: string;
producedKind: string;
producedId: string;
policySnapshotId?: string;
createdAt: string;
}
export interface SharingPolicy {
shareMode: ShareMode;
allowReshare: boolean;
defaultForkVisibility: 'private' | 'team';
}
export interface PagePresence {
activeViewers: number;
activeEditors: number;
lastPresenceAt: string;
}
export interface PageAuditSummary {
lastAuditEventId: string;
eventCount: number;
}
export interface CanvasPage {
pageId: string;
tenantId: string;
ownerId: string;
branchId: string;
branchName: string;
pageType: PageType;
title: string;
createdAt: string;
updatedAt: string;
isShared: boolean;
forks: ForkRecord[];
mainBranchPointer: {
pageId: string;
branchId: string;
revision: number;
};
baseRevision: number;
headRevision: number;
sharingPolicy: SharingPolicy;
presence: PagePresence;
lineage: LineageRecord[];
audit: PageAuditSummary;
components: CanvasComponent[];
}
export interface PromptExecution {
executionId: string;
tenantId: string;
pageId: string;
branchId: string;
actorId: string;
prompt: string;
intentClass: IntentClass;
status: ExecutionStatus;
modelRuntime: string;
semanticModelVersion: string;
retrievalPlan?: Record<string, unknown>;
visualizationPlan?: Record<string, unknown>;
warnings: string[];
summary?: string;
componentsCreated?: string[];
createdAt: string;
completedAt?: string;
}
export interface ComponentTemplate {
templateId: string;
tenantId: string;
name: string;
category: string;
status: TemplateStatus;
origin: TemplateOrigin;
version: string;
acceptedShapes: string[];
styleSignature?: StyleSignature;
validationState?: ValidationState;
provenance?: ComponentProvenance;
createdAt: string;
updatedAt: string;
}
export interface ConflictRecord {
conflictId: string;
conflictClass:
| 'component_content_conflict'
| 'query_descriptor_conflict'
| 'layout_slot_conflict'
| 'access_policy_conflict'
| 'delete_edit_conflict'
| 'safe_append'
| 'safe_reorder';
componentId: string;
field?: string;
sourceValue?: unknown;
targetValue?: unknown;
description: string;
}
export interface DiffSummary {
componentsAdded: number;
componentsEdited: number;
componentsReordered: number;
componentsDeleted: number;
}
export interface MergeRequest {
mergeRequestId: string;
tenantId: string;
sourcePageId: string;
sourceBranchId: string;
sourceHeadRevision: number;
targetPageId: string;
targetBranchId: string;
targetBaseRevision: number;
title: string;
description?: string;
status: MergeRequestStatus;
conflicts: ConflictRecord[];
diffSummary?: DiffSummary;
createdBy: string;
reviewedBy?: string;
createdAt: string;
updatedAt: string;
}
export interface AuditEvent {
auditEventId: string;
tenantId: string;
entityType: string;
entityId: string;
action: string;
actorId: string;
actorType: ActorType;
correlationId: string;
executionId?: string;
createdAt: string;
details: Record<string, unknown>;
}
export interface UserProfile {
userId: string;
tenantId: string;
email: string;
displayName: string;
role: OracleRole;
timezone: string;
locale: string;
defaultPageId: string;
canvasPreferences: {
defaultDensity: 'compact' | 'comfortable';
defaultPlacementMode: PlacementMode;
showLineageBadges: boolean;
};
policyProfileId: string;
createdAt: string;
updatedAt: string;
}
// ── API Request/Response contracts ────────────────────────────────────────────
export interface PromptSubmitRequest {
clientRequestId: string;
branchId: string;
prompt: string;
conversationContext?: Array<{ role: 'user' | 'assistant'; content: string }>;
placementMode?: PlacementMode;
}
export interface PromptSubmitResponse {
executionId: string;
status: ExecutionStatus;
pageId: string;
branchId: string;
headRevision: number;
componentsCreated: string[];
components: CanvasComponent[];
summary: string;
warnings: string[];
}
export interface CanvasPageRevision {
revisionId: string;
pageId: string;
tenantId: string;
revisionNumber: number;
commitKind: CommitKind;
commitSummary?: string;
actorId: string;
executionId?: string;
mergeRequestId?: string;
createdAt: string;
}
export interface ForkCreateRequest {
recipientUserId: string;
sourceRevision: number;
visibility: 'private' | 'team';
message?: string;
}
export interface ForkCreateResponse {
forkId: string;
forkPageId: string;
forkBranchId: string;
status: ForkStatus;
sourceRevision: number;
}
export interface MergeRequestCreateRequest {
sourcePageId: string;
sourceBranchId: string;
targetPageId: string;
targetBranchId: string;
title: string;
description?: string;
}
export interface MergeReviewRequest {
decision: 'approve' | 'reject' | 'changes_requested';
comment?: string;
resolutions?: Array<{
conflictId: string;
resolutionType: 'source_wins' | 'target_wins' | 'manual_composite';
resolvedPayloadHash?: string;
comment?: string;
}>;
}
// ── WebSocket event types ─────────────────────────────────────────────────────
export type OracleWSEventType =
| 'oracle.prompt.received'
| 'oracle.prompt.validated'
| 'oracle.prompt.failed'
| 'oracle.page.revision.committed'
| 'oracle.page.rollback.committed'
| 'oracle.fork.created'
| 'oracle.merge_request.opened'
| 'oracle.merge_request.updated'
| 'oracle.merge_request.merged'
| 'oracle.component.template.promoted'
| 'oracle.presence.updated';
export interface OracleWSMessage {
type: OracleWSEventType;
tenantId: string;
pageId?: string;
branchId?: string;
correlationId: string;
timestamp: string;
payload: Record<string, unknown>;
}
// ── API Error envelope ────────────────────────────────────────────────────────
export interface OracleAPIError {
error: {
code: string;
message: string;
retryable: boolean;
correlationId: string;
details?: Record<string, unknown>;
};
}
export interface OracleEnvelope<T> {
status: 'ok';
data: T;
meta?: Record<string, unknown>;
}

View File

@@ -0,0 +1,106 @@
from __future__ import annotations
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel, Field
from backend.oracle.action_service import oracle_action_service
from backend.oracle.persona_service import persona_service
from backend.services.mcp_registry import mcp_registry
from backend.services.nemoclaw_runtime import nemoclaw_runtime
from backend.services.runtime_llm_service import runtime_llm_service
router = APIRouter()
class WorkflowPreviewRequest(BaseModel):
prompt: str = Field(..., min_length=1, max_length=4096)
tenant_id: str = "tenant_velocity"
actor_role: str = "sales_director"
class MCPExecuteRequest(BaseModel):
tool_name: str = Field(..., min_length=1, max_length=128)
query: str = Field(..., min_length=1, max_length=1024)
class OracleWritebackRequest(BaseModel):
action_id: str
tenant_id: str = "tenant_velocity"
actor_id: str = "oracle_operator"
target_entity_type: str = Field(..., min_length=1, max_length=64)
target_entity_id: str = Field(..., min_length=1, max_length=128)
action_type: str = Field(default="lead_writeback", min_length=1, max_length=128)
writeback_payload: dict = Field(default_factory=dict)
@router.get("/health")
async def oracle_health() -> dict:
return {
"status": "ok",
"persona": await persona_service.health(),
"mcp_tools": mcp_registry.list_tools(),
"runtime_llm": await runtime_llm_service.list_providers(),
}
@router.get("/mcp/tools")
async def oracle_mcp_tools() -> dict:
return {"status": "ok", "data": mcp_registry.list_tools()}
@router.post("/mcp/execute")
async def oracle_mcp_execute(request: Request, payload: MCPExecuteRequest) -> dict:
pool = getattr(request.app.state, "db_pool", None)
result = await mcp_registry.execute(payload.tool_name, payload.query, crm_pool=pool)
return {"status": "ok", "data": result}
@router.post("/workflow/preview")
async def workflow_preview(payload: WorkflowPreviewRequest) -> dict:
persona_plan = await persona_service.plan_for_prompt(
prompt=payload.prompt,
tenant_id=payload.tenant_id,
actor_role=payload.actor_role,
)
return {
"status": "ok",
"data": {
"persona_plan": persona_plan,
"workflow": nemoclaw_runtime.build_workflow_dispatch(
prompt=payload.prompt,
tenant_id=payload.tenant_id,
actor_role=payload.actor_role,
component_templates=persona_plan["recommendedTemplates"],
),
},
}
@router.get("/actions")
async def list_oracle_actions(status: str | None = None, limit: int = 50) -> dict:
actions = await oracle_action_service.list_actions(status=status, limit=limit)
return {"status": "ok", "data": actions, "meta": {"count": len(actions)}}
@router.get("/actions/{action_id}")
async def get_oracle_action(action_id: str) -> dict:
action = await oracle_action_service.get_action(action_id)
if not action:
raise HTTPException(status_code=404, detail=f"Oracle action '{action_id}' not found.")
return {"status": "ok", "data": action}
@router.post("/actions/writeback")
async def apply_oracle_writeback(request: Request, payload: OracleWritebackRequest) -> dict:
result = await oracle_action_service.apply_writeback(payload.model_dump())
if hasattr(request.app.state, "broadcast_crm_event"):
await request.app.state.broadcast_crm_event(
{
"type": "oracle_writeback",
"entity": payload.target_entity_type,
"entity_id": payload.target_entity_id,
"action_id": payload.action_id,
"payload": result["resultPayload"],
}
)
return {"status": "ok", "data": result}

View File

@@ -0,0 +1,404 @@
"""
routes_oracle_templates.py
──────────────────────────
Oracle Template Catalog API
Extends the existing Oracle route surface with template taxonomy and seeding.
Endpoints:
GET /oracle/template-chapters — list chapters
POST /oracle/template-chapters — create a chapter
GET /oracle/template-subchapters — list subchapters (optionally filtered)
POST /oracle/template-subchapters — create a subchapter
GET /oracle/component-templates — list templates (filterable)
POST /oracle/component-templates — create a template
GET /oracle/component-templates/{id} — get a template
POST /oracle/component-templates/{id}/seed — add a seed example
GET /oracle/component-templates/{id}/seed — list seed examples for a template
POST /oracle/component-templates/synthetic-jobs — trigger a Kimi synthetic job
"""
from __future__ import annotations
import json
import logging
import os
from typing import Any, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from pydantic import BaseModel, Field
from backend.auth.dependencies import get_current_user
logger = logging.getLogger("velocity.oracle_templates")
router = APIRouter()
_DEFAULT_TENANT_ID = os.getenv("ORACLE_DEFAULT_TENANT_ID", "tenant_velocity")
# ── Helpers ───────────────────────────────────────────────────────────────────
def _pool(request: Request):
pool = request.app.state.db_pool
if pool is None:
raise HTTPException(503, "Database unavailable.")
return pool
def _tenant_id() -> str:
return _DEFAULT_TENANT_ID
# ── Models ────────────────────────────────────────────────────────────────────
class ChapterCreate(BaseModel):
name: str
description: Optional[str] = None
sort_order: int = 0
class SubchapterCreate(BaseModel):
chapter_id: str
name: str
description: Optional[str] = None
sort_order: int = 0
class TemplateCreate(BaseModel):
name: str
category: str
chapter_id: Optional[str] = None
subchapter_id: Optional[str] = None
component_type: Optional[str] = None
accepted_shapes: list[str] = Field(default_factory=list)
json_template: Optional[dict] = None
description: Optional[str] = None
origin: str = "premade"
version: str = "1.0.0"
class SeedExampleCreate(BaseModel):
title: str
example_json: dict
quality_notes: Optional[str] = None
chapter_id: Optional[str] = None
subchapter_id: Optional[str] = None
is_canonical: bool = False
class SyntheticJobCreate(BaseModel):
template_id: str
chapter_id: Optional[str] = None
subchapter_id: Optional[str] = None
model: str = "kimi"
requested_count: int = Field(10, ge=1, le=500)
# ── Template Chapters ─────────────────────────────────────────────────────────
@router.get("/template-chapters", summary="List Oracle template chapters")
async def list_template_chapters(
request: Request,
include_inactive: bool = Query(False),
user=Depends(get_current_user),
):
pool = _pool(request)
async with pool.acquire() as conn:
where = "WHERE ch.tenant_id=$1" + ("" if include_inactive else " AND ch.is_active=TRUE")
rows = await conn.fetch(
f"""
SELECT ch.chapter_id, ch.name, ch.description, ch.sort_order, ch.is_active,
COUNT(sub.subchapter_id) FILTER (WHERE sub.is_active=TRUE) as subchapter_count,
COUNT(t.template_id) as template_count
FROM oracle_template_chapters ch
LEFT JOIN oracle_template_subchapters sub ON sub.chapter_id = ch.chapter_id
LEFT JOIN oracle_component_templates t ON t.chapter_id = ch.chapter_id
AND t.status != 'archived'
{where}
GROUP BY ch.chapter_id
ORDER BY ch.sort_order ASC
""",
_tenant_id(),
)
return {"chapters": [dict(r) for r in rows]}
@router.post("/template-chapters", status_code=status.HTTP_201_CREATED,
summary="Create a template chapter")
async def create_template_chapter(
request: Request,
body: ChapterCreate,
user=Depends(get_current_user),
):
pool = _pool(request)
async with pool.acquire() as conn:
row = await conn.fetchrow(
"""
INSERT INTO oracle_template_chapters (tenant_id, name, description, sort_order)
VALUES ($1,$2,$3,$4)
RETURNING chapter_id, created_at
""",
_tenant_id(), body.name, body.description, body.sort_order,
)
return {"chapter_id": str(row["chapter_id"]), "created_at": str(row["created_at"])}
# ── Template Subchapters ──────────────────────────────────────────────────────
@router.get("/template-subchapters", summary="List Oracle template subchapters")
async def list_template_subchapters(
request: Request,
chapter_id: Optional[str] = Query(None),
include_inactive: bool = Query(False),
user=Depends(get_current_user),
):
pool = _pool(request)
async with pool.acquire() as conn:
where = "WHERE sub.tenant_id=$1"
params: list[Any] = [_tenant_id()]
idx = 2
if not include_inactive:
where += " AND sub.is_active=TRUE"
if chapter_id:
where += f" AND sub.chapter_id=${idx}"; params.append(chapter_id); idx += 1
rows = await conn.fetch(
f"""
SELECT sub.subchapter_id, sub.chapter_id, ch.name as chapter_name,
sub.name, sub.description, sub.sort_order, sub.is_active,
COUNT(t.template_id) as template_count
FROM oracle_template_subchapters sub
JOIN oracle_template_chapters ch ON ch.chapter_id = sub.chapter_id
LEFT JOIN oracle_component_templates t ON t.subchapter_id = sub.subchapter_id
AND t.status != 'archived'
{where}
GROUP BY sub.subchapter_id, ch.name
ORDER BY sub.chapter_id, sub.sort_order ASC
""",
*params,
)
return {"subchapters": [dict(r) for r in rows]}
@router.post("/template-subchapters", status_code=status.HTTP_201_CREATED,
summary="Create a template subchapter")
async def create_template_subchapter(
request: Request,
body: SubchapterCreate,
user=Depends(get_current_user),
):
pool = _pool(request)
async with pool.acquire() as conn:
# Verify chapter exists and belongs to tenant
ch_exists = await conn.fetchval(
"SELECT 1 FROM oracle_template_chapters WHERE chapter_id=$1 AND tenant_id=$2",
body.chapter_id, _tenant_id(),
)
if not ch_exists:
raise HTTPException(404, "Chapter not found")
row = await conn.fetchrow(
"""
INSERT INTO oracle_template_subchapters
(chapter_id, tenant_id, name, description, sort_order)
VALUES ($1,$2,$3,$4,$5)
RETURNING subchapter_id, created_at
""",
body.chapter_id, _tenant_id(), body.name, body.description, body.sort_order,
)
return {"subchapter_id": str(row["subchapter_id"]), "created_at": str(row["created_at"])}
# ── Component Templates ───────────────────────────────────────────────────────
@router.get("/component-templates", summary="List Oracle component templates")
async def list_component_templates(
request: Request,
chapter_id: Optional[str] = Query(None),
subchapter_id: Optional[str] = Query(None),
status_filter: Optional[str] = Query(None, alias="status"),
search: Optional[str] = Query(None),
limit: int = Query(50, ge=1, le=200),
offset: int = Query(0, ge=0),
user=Depends(get_current_user),
):
pool = _pool(request)
where = "WHERE t.tenant_id=$1"
params: list[Any] = [_tenant_id()]
idx = 2
if chapter_id:
where += f" AND t.chapter_id=${idx}"; params.append(chapter_id); idx += 1
if subchapter_id:
where += f" AND t.subchapter_id=${idx}"; params.append(subchapter_id); idx += 1
if status_filter:
where += f" AND t.status=${idx}"; params.append(status_filter); idx += 1
if search:
where += f" AND (t.name ILIKE ${idx} OR t.description ILIKE ${idx})"
params.append(f"%{search}%"); idx += 1
async with pool.acquire() as conn:
rows = await conn.fetch(
f"""
SELECT t.template_id, t.name, t.category, t.status, t.origin, t.version,
t.accepted_shapes, t.use_count, t.chapter_id, t.subchapter_id,
t.description, ch.name as chapter_name, sub.name as subchapter_name,
t.created_at, t.updated_at
FROM oracle_component_templates t
LEFT JOIN oracle_template_chapters ch ON ch.chapter_id = t.chapter_id
LEFT JOIN oracle_template_subchapters sub ON sub.subchapter_id = t.subchapter_id
{where}
ORDER BY t.updated_at DESC
LIMIT ${idx} OFFSET ${idx+1}
""",
*params, limit, offset,
)
total = await conn.fetchval(
f"SELECT COUNT(*) FROM oracle_component_templates t {where}", *params,
)
return {"total": total, "limit": limit, "offset": offset, "templates": [dict(r) for r in rows]}
@router.post("/component-templates", status_code=status.HTTP_201_CREATED,
summary="Create a component template")
async def create_component_template(
request: Request,
body: TemplateCreate,
user=Depends(get_current_user),
):
pool = _pool(request)
async with pool.acquire() as conn:
row = await conn.fetchrow(
"""
INSERT INTO oracle_component_templates (
tenant_id, name, category, chapter_id, subchapter_id,
accepted_shapes, json_template, description, origin, version, status
) VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8,$9,$10,'draft')
RETURNING template_id, created_at
""",
_tenant_id(), body.name, body.category, body.chapter_id, body.subchapter_id,
body.accepted_shapes,
json.dumps(body.json_template) if body.json_template else None,
body.description, body.origin, body.version,
)
return {"template_id": str(row["template_id"]), "created_at": str(row["created_at"])}
@router.get("/component-templates/{template_id}", summary="Get a component template")
async def get_component_template(
template_id: str,
request: Request,
user=Depends(get_current_user),
):
pool = _pool(request)
async with pool.acquire() as conn:
row = await conn.fetchrow(
"""
SELECT t.*, ch.name as chapter_name, sub.name as subchapter_name
FROM oracle_component_templates t
LEFT JOIN oracle_template_chapters ch ON ch.chapter_id = t.chapter_id
LEFT JOIN oracle_template_subchapters sub ON sub.subchapter_id = t.subchapter_id
WHERE t.template_id=$1 AND t.tenant_id=$2
""",
template_id, _tenant_id(),
)
if not row:
raise HTTPException(404, "Template not found")
return dict(row)
# ── Seed Examples ─────────────────────────────────────────────────────────────
@router.post("/component-templates/{template_id}/seed", status_code=status.HTTP_201_CREATED,
summary="Add a seed example to a template")
async def add_seed_example(
template_id: str,
request: Request,
body: SeedExampleCreate,
user=Depends(get_current_user),
):
pool = _pool(request)
async with pool.acquire() as conn:
exists = await conn.fetchval(
"SELECT 1 FROM oracle_component_templates WHERE template_id=$1 AND tenant_id=$2",
template_id, _tenant_id(),
)
if not exists:
raise HTTPException(404, "Template not found")
row = await conn.fetchrow(
"""
INSERT INTO oracle_template_seed_examples (
template_id, chapter_id, subchapter_id, title, example_json,
quality_notes, is_canonical
) VALUES ($1,$2,$3,$4,$5::jsonb,$6,$7)
RETURNING example_id, created_at
""",
template_id, body.chapter_id, body.subchapter_id, body.title,
json.dumps(body.example_json), body.quality_notes, body.is_canonical,
)
return {"example_id": str(row["example_id"]), "created_at": str(row["created_at"])}
@router.get("/component-templates/{template_id}/seed", summary="List seed examples for a template")
async def list_seed_examples(
template_id: str,
request: Request,
user=Depends(get_current_user),
):
pool = _pool(request)
async with pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT example_id, title, example_json, quality_notes, is_canonical, created_at
FROM oracle_template_seed_examples
WHERE template_id=$1
ORDER BY is_canonical DESC, created_at ASC
""",
template_id,
)
return {"examples": [dict(r) for r in rows]}
# ── Synthetic Jobs ────────────────────────────────────────────────────────────
@router.post("/component-templates/synthetic-jobs", status_code=status.HTTP_201_CREATED,
summary="Trigger a Kimi synthetic data generation job")
async def trigger_synthetic_job(
request: Request,
body: SyntheticJobCreate,
user=Depends(get_current_user),
):
"""
Queues a Kimi synthetic data expansion job for a template.
The job will be picked up by the background synthetic generation worker.
"""
pool = _pool(request)
async with pool.acquire() as conn:
exists = await conn.fetchval(
"SELECT 1 FROM oracle_component_templates WHERE template_id=$1 AND tenant_id=$2",
body.template_id, _tenant_id(),
)
if not exists:
raise HTTPException(404, "Template not found")
row = await conn.fetchrow(
"""
INSERT INTO oracle_synthetic_generation_jobs (
tenant_id, template_id, chapter_id, subchapter_id,
model, requested_count, created_by
) VALUES ($1,$2,$3,$4,$5,$6,$7)
RETURNING job_id, status, created_at
""",
_tenant_id(), body.template_id, body.chapter_id, body.subchapter_id,
body.model, body.requested_count, user.user_id,
)
logger.info(
"Synthetic job queued: %s for template %s (%d examples)",
row["job_id"], body.template_id, body.requested_count,
)
return {
"job_id": str(row["job_id"]),
"status": row["status"],
"created_at": str(row["created_at"]),
}

View File

@@ -0,0 +1,140 @@
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel, Field
from backend.auth.dependencies import UserPrincipal, get_current_user
from backend.services.runtime_llm_service import runtime_llm_service
router = APIRouter()
class ChatMessage(BaseModel):
role: str = Field(..., pattern="^(system|user|assistant)$")
content: str = Field(..., min_length=1)
class RuntimeChatRequest(BaseModel):
provider: str | None = None
model: str | None = None
system_prompt: str | None = None
messages: list[ChatMessage]
temperature: float = Field(default=0.2, ge=0.0, le=2.0)
response_format: str | None = Field(default=None, pattern="^(json|text)$")
metadata: dict[str, Any] = Field(default_factory=dict)
class BatchItemRequest(BaseModel):
request_id: str
messages: list[ChatMessage]
system_prompt: str | None = None
temperature: float = Field(default=0.2, ge=0.0, le=2.0)
response_format: str | None = Field(default=None, pattern="^(json|text)$")
metadata: dict[str, Any] = Field(default_factory=dict)
class RuntimeBatchRequest(BaseModel):
provider: str | None = None
model: str | None = None
job_type: str = Field(..., min_length=1, max_length=128)
metadata: dict[str, Any] = Field(default_factory=dict)
items: list[BatchItemRequest] = Field(..., min_length=1, max_length=128)
def _normalize_user(user: UserPrincipal) -> dict[str, str]:
return {
"user_id": user.user_id,
"role": user.role,
}
@router.get("/providers", summary="List configured runtime LLM providers and models")
async def list_runtime_providers(_: UserPrincipal = Depends(get_current_user)) -> dict:
return {"status": "ok", "data": await runtime_llm_service.list_providers()}
@router.post("/chat", summary="Execute a single runtime LLM chat completion")
async def runtime_chat(
payload: RuntimeChatRequest,
user: UserPrincipal = Depends(get_current_user),
) -> dict:
response = await runtime_llm_service.chat(
provider_id=payload.provider,
model=payload.model,
system_prompt=payload.system_prompt,
messages=[message.model_dump() for message in payload.messages],
temperature=payload.temperature,
response_format=payload.response_format,
metadata={
**payload.metadata,
"requested_by": _normalize_user(user),
},
)
return {"status": "ok", "data": response}
@router.post("/batch", status_code=status.HTTP_202_ACCEPTED, summary="Submit a persisted runtime LLM batch job")
async def runtime_batch(
payload: RuntimeBatchRequest,
request: Request,
user: UserPrincipal = Depends(get_current_user),
) -> dict:
pool = getattr(request.app.state, "db_pool", None)
result = await runtime_llm_service.submit_batch(
provider_id=payload.provider,
model=payload.model,
job_type=payload.job_type,
items=[item.model_dump() for item in payload.items],
metadata={
**payload.metadata,
"requested_by": _normalize_user(user),
},
pool=pool,
actor_id=user.user_id,
)
return {"status": "ok", "data": result}
@router.get("/jobs/{job_id}", summary="Get runtime LLM batch job status")
async def get_runtime_job(
job_id: str,
request: Request,
_: UserPrincipal = Depends(get_current_user),
) -> dict:
pool = getattr(request.app.state, "db_pool", None)
job = await runtime_llm_service.get_job(job_id, pool=pool)
if not job:
raise HTTPException(status_code=404, detail=f"Runtime LLM job '{job_id}' not found.")
return {
"status": "ok",
"data": {
"job_id": job["job_id"],
"status": job["status"],
"provider": job["provider"],
"model": job["model"],
"job_type": job["job_type"],
"submitted_count": job["submitted_count"],
"completed_count": job["completed_count"],
"failed_count": job["failed_count"],
"created_at": job["created_at"],
"started_at": job["started_at"],
"completed_at": job["completed_at"],
"metadata": job.get("metadata") or {},
},
}
@router.get("/jobs/{job_id}/results", summary="Get runtime LLM batch job item results")
async def get_runtime_job_results(
job_id: str,
request: Request,
_: UserPrincipal = Depends(get_current_user),
) -> dict:
pool = getattr(request.app.state, "db_pool", None)
results = await runtime_llm_service.list_job_results(job_id, pool=pool)
if results is None:
raise HTTPException(status_code=404, detail=f"Runtime LLM job '{job_id}' not found.")
return {"status": "ok", "data": results, "meta": {"count": len(results)}}

View File

@@ -0,0 +1,411 @@
"""
Velocity — Unified FastAPI Backend
Covers: Catalyst (Meta Marketing), Sentinel (QD Engine), Vault (Trackable Links), Auth
GPU partitioning on AWS:
- NemoClaw / Ollama → CUDA devices 0, 1 (enforced in nemoclaw.service systemd unit)
- ComfyUI / Wan 2.2 → CUDA devices 2, 3 (enforced in comfyui.service systemd unit)
"""
import os
import json
import asyncio
import logging
import re
from contextlib import asynccontextmanager
from datetime import UTC, datetime
from pathlib import Path
from typing import Set
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from dotenv import load_dotenv
def _load_velocity_env() -> None:
repo_root = Path(__file__).resolve().parent.parent
backend_root = repo_root / "backend"
explicit_env = os.getenv("VELOCITY_ENV_FILE", "").strip()
candidate_paths = []
if explicit_env:
candidate_paths.append(Path(explicit_env))
candidate_paths.extend(
[
backend_root / ".env",
repo_root / ".env",
]
)
loaded_any = False
seen: set[Path] = set()
for candidate in candidate_paths:
resolved = candidate.resolve()
if resolved in seen or not candidate.exists():
continue
load_dotenv(candidate, override=not loaded_any)
loaded_any = True
seen.add(resolved)
if not loaded_any:
load_dotenv()
_load_velocity_env()
from backend.api.routes_catalyst import router as catalyst_router
from backend.api.routes_crm import crm_router, analytics_router
from backend.api.routes_oracle import router as oracle_helper_router
from backend.api.routes_mobile_edge import router as mobile_edge_router
from backend.api.routes_inventory import router as inventory_router
from backend.api.routes_admin_surface import router as admin_surface_router
from backend.api.routes_oracle_templates import router as oracle_templates_router
from backend.api.routes_crm_imports import router as crm_imports_router
from backend.api.routes_runtime_llm import router as runtime_llm_router
from backend.auth.dependencies import (
create_access_token, verify_password, get_current_user, UserPrincipal
)
from backend.db.pool import create_pool, close_pool
from backend.oracle.router_v1 import router as oracle_v1_router
from backend.routers.cctv import router as cctv_router
from backend.routers.scenes import router as scenes_router
from backend.routers.videos import router as videos_router
from backend.routers.vault import router as vault_router
from backend.routers.sentinel import router as sentinel_router, broadcast_sentinel_event
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("velocity.main")
# ── Lifespan: DB pool init / teardown ─────────────────────────────────────────
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
try:
app.state.db_pool = await create_pool()
logger.info("asyncpg pool created")
except Exception as exc:
logger.error("Failed to create DB pool: %s", exc)
app.state.db_pool = None
app.state.broadcast_sentinel_event = broadcast_sentinel_event
yield
# Shutdown
await close_pool()
logger.info("asyncpg pool closed")
# ── App ───────────────────────────────────────────────────────────────────────
app = FastAPI(
title="Velocity — Neural Core",
description="Unified backend: Catalyst, Sentinel QD Engine, Vault, Oracle, Auth.",
version="2.0.0",
lifespan=lifespan,
)
# ── CORS ──────────────────────────────────────────────────────────────────────
origins = [o.strip() for o in os.getenv("CORS_ORIGINS", "http://localhost:5173").split(",")]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ── Static asset serving (Vault files) ───────────────────────────────────────
ASSET_DIR = os.getenv("VELOCITY_ASSET_DIR", "/opt/dlami/nvme/assets")
if os.path.isdir(ASSET_DIR):
app.mount("/assets", StaticFiles(directory=ASSET_DIR), name="assets")
def _sanitize_filename(value: str) -> str:
cleaned = re.sub(r"[^A-Za-z0-9._-]+", "_", value).strip("._")
return cleaned or "upload"
# ── Routers ───────────────────────────────────────────────────────────────────
app.include_router(catalyst_router, prefix="/api/catalyst", tags=["Catalyst"])
app.include_router(crm_router, prefix="/api", tags=["CRM"])
app.include_router(analytics_router, prefix="/api/analytics", tags=["Analytics"])
app.include_router(oracle_helper_router, prefix="/api/oracle", tags=["Oracle"])
app.include_router(oracle_v1_router, prefix="/api/oracle/v1", tags=["Oracle V1"])
app.include_router(oracle_templates_router, prefix="/api/oracle", tags=["Oracle Templates"])
app.include_router(sentinel_router, prefix="/api/sentinel", tags=["Sentinel"])
app.include_router(cctv_router, prefix="/api/cctv", tags=["CCTV"])
app.include_router(scenes_router, prefix="/api/scenes", tags=["Scenes"])
app.include_router(videos_router, prefix="/api/videos", tags=["Videos"])
app.include_router(vault_router, prefix="/api/vault", tags=["Vault"])
app.include_router(mobile_edge_router, prefix="/api/mobile-edge", tags=["Mobile Edge"])
app.include_router(inventory_router, prefix="/api/inventory", tags=["Inventory"])
app.include_router(admin_surface_router, prefix="/api/admin-surface", tags=["Admin Surface"])
app.include_router(crm_imports_router, prefix="/api", tags=["CRM Canonical"])
app.include_router(runtime_llm_router, prefix="/api/runtime/llm", tags=["Runtime LLM"])
# Public vault link (no /api prefix — shared externally with prospects)
from backend.routers.vault import router as public_vault_router
app.include_router(public_vault_router, prefix="/vault", tags=["Vault Public"])
# ── Auth endpoint ─────────────────────────────────────────────────────────────
from fastapi import HTTPException, status
from pydantic import BaseModel
class LoginRequest(BaseModel):
email: str
password: str
@app.post("/api/auth/login", tags=["Auth"])
async def login(body: LoginRequest):
"""
Authenticate a user and return a JWT.
Credentials are verified against the users_and_roles table.
"""
from backend.db.pool import get_pool
from fastapi import Request
pool = app.state.db_pool
if pool is None:
raise HTTPException(status_code=503, detail="Database unavailable.")
async with pool.acquire() as conn:
row = await conn.fetchrow(
"SELECT id::text, role, password_hash FROM users_and_roles WHERE email = $1 AND is_active = TRUE",
body.email,
)
if not row or not verify_password(body.password, row["password_hash"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid email or password.",
)
token = create_access_token(user_id=row["id"], role=row["role"])
return {"access_token": token, "token_type": "bearer", "expires_in": 28800}
@app.get("/api/auth/me", tags=["Auth"])
async def me(user: UserPrincipal = Depends(get_current_user)):
pool = app.state.db_pool
if pool is None:
raise HTTPException(status_code=503, detail="Database unavailable.")
async with pool.acquire() as conn:
row = await conn.fetchrow(
"""
SELECT full_name, email, avatar_url
FROM users_and_roles
WHERE id = $1::uuid
""",
user.user_id,
)
return {
"user_id": user.user_id,
"role": user.role,
"full_name": row["full_name"] if row else None,
"email": row["email"] if row else None,
"avatar_url": row["avatar_url"] if row else None,
}
@app.get("/api/auth/users", tags=["Auth"])
async def list_auth_users(_: UserPrincipal = Depends(get_current_user)):
pool = app.state.db_pool
if pool is None:
raise HTTPException(status_code=503, detail="Database unavailable.")
async with pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT
id::text AS user_id,
role,
full_name,
email,
avatar_url
FROM users_and_roles
WHERE is_active = TRUE
ORDER BY
COALESCE(NULLIF(full_name, ''), email, id::text) ASC
"""
)
return [
{
"user_id": row["user_id"],
"role": row["role"],
"full_name": row["full_name"],
"email": row["email"],
"avatar_url": row["avatar_url"],
}
for row in rows
]
@app.post("/api/auth/profile/avatar", tags=["Auth"])
async def upload_profile_avatar(
file: UploadFile = File(...),
user: UserPrincipal = Depends(get_current_user),
):
pool = app.state.db_pool
if pool is None:
raise HTTPException(status_code=503, detail="Database unavailable.")
allowed = {"image/png", "image/jpeg", "image/jpg", "image/webp"}
if file.content_type not in allowed:
raise HTTPException(status_code=400, detail="Unsupported avatar format.")
extension = Path(file.filename or "avatar.png").suffix.lower() or ".png"
if extension not in {".png", ".jpg", ".jpeg", ".webp"}:
extension = ".png"
avatar_dir = Path(ASSET_DIR) / "profile_avatars"
avatar_dir.mkdir(parents=True, exist_ok=True)
filename = f"{user.user_id}_{_sanitize_filename(Path(file.filename or 'avatar').stem)}_{int(datetime.now(UTC).timestamp())}{extension}"
destination = avatar_dir / filename
contents = await file.read()
destination.write_bytes(contents)
avatar_url = f"/assets/profile_avatars/{filename}"
async with pool.acquire() as conn:
await conn.execute(
"""
UPDATE users_and_roles
SET avatar_url = $2
WHERE id = $1::uuid
""",
user.user_id,
avatar_url,
)
return {"avatar_url": avatar_url}
# ── Catalyst WebSocket (preserved from v1) ────────────────────────────────────
class _CatalystManager:
def __init__(self) -> None:
self.active: Set[WebSocket] = set()
async def connect(self, ws: WebSocket) -> None:
await ws.accept()
self.active.add(ws)
def disconnect(self, ws: WebSocket) -> None:
self.active.discard(ws)
async def broadcast(self, payload: dict) -> None:
dead: Set[WebSocket] = set()
for ws in self.active:
try:
await ws.send_text(json.dumps(payload))
except Exception:
dead.add(ws)
self.active -= dead
_catalyst_mgr = _CatalystManager()
class _CRMManager:
def __init__(self) -> None:
self.active: Set[WebSocket] = set()
async def connect(self, ws: WebSocket) -> None:
await ws.accept()
self.active.add(ws)
def disconnect(self, ws: WebSocket) -> None:
self.active.discard(ws)
async def broadcast(self, payload: dict) -> None:
dead: Set[WebSocket] = set()
for ws in self.active:
try:
await ws.send_text(json.dumps(payload))
except Exception:
dead.add(ws)
self.active -= dead
_crm_mgr = _CRMManager()
@app.websocket("/ws/catalyst")
async def catalyst_ws(ws: WebSocket) -> None:
await _catalyst_mgr.connect(ws)
try:
while True:
data = await ws.receive_text()
await ws.send_text(json.dumps({"type": "ack", "data": data}))
except WebSocketDisconnect:
_catalyst_mgr.disconnect(ws)
@app.websocket("/ws/crm")
async def crm_ws(ws: WebSocket) -> None:
await _crm_mgr.connect(ws)
await _crm_mgr.broadcast(
{
"type": "crm_presence",
"connected_clients": len(_crm_mgr.active),
"timestamp": datetime.now(UTC).isoformat(),
}
)
try:
while True:
message = await ws.receive_text()
await ws.send_text(json.dumps({"type": "crm_ack", "data": message}))
except WebSocketDisconnect:
_crm_mgr.disconnect(ws)
async def broadcast_live_event(event_type, message, campaign_name=None, value=None):
payload = {
"type": event_type,
"message": message,
"campaignName": campaign_name,
"value": value,
"timestamp": datetime.now(UTC).isoformat(),
}
await _catalyst_mgr.broadcast(payload)
app.state.broadcast_live_event = broadcast_live_event
async def broadcast_crm_event(payload: dict) -> None:
enriched = {
**payload,
"timestamp": datetime.now(UTC).isoformat(),
}
await _crm_mgr.broadcast(enriched)
app.state.broadcast_crm_event = broadcast_crm_event
# ── Health ─────────────────────────────────────────────────────────────────────
@app.get("/health", tags=["Health"])
async def health() -> dict:
pool = app.state.db_pool
db_ok = pool is not None
return {
"status": "ok",
"service": "velocity-backend",
"version": "2.0.0",
"db_pool": "connected" if db_ok else "unavailable",
"timestamp": datetime.now(UTC).isoformat(),
}

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