forked from sagnik/Project_Velocity
#24 WebOS Completion Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local> Reviewed-on: sagnik/Project_Velocity#25
399 lines
15 KiB
Python
399 lines
15 KiB
Python
"""
|
|
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
|
|
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()
|
|
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
def _pool(request: Request):
|
|
pool = request.app.state.db_pool
|
|
if pool is None:
|
|
raise HTTPException(503, "Database unavailable.")
|
|
return pool
|
|
|
|
|
|
# ── 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
|
|
""",
|
|
user.role,
|
|
)
|
|
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
|
|
""",
|
|
user.role, 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] = [user.role]
|
|
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, user.role,
|
|
)
|
|
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, user.role, 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] = [user.role]
|
|
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
|
|
""",
|
|
user.role, 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, user.role,
|
|
)
|
|
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, user.role,
|
|
)
|
|
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, user.role,
|
|
)
|
|
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
|
|
""",
|
|
user.role, 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"]),
|
|
}
|