Files
MapMil/centers/analytics/api/app/routers/parser_builder.py
T
gitrusprusandCursor 699e9be503 Add parser builder: one-shot DeepSeek profile for Telegram extract_mode=profile.
Generate static HeuristicProfile in CA admin, preview and run without LLM on each post via shared interpreter in CP batch and listener.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 14:14:18 +03:00

109 lines
3.2 KiB
Python

"""Admin API: construct static telegram parsers from a sample post."""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from contracts.heuristic_profile import HeuristicProfile
from contracts.sources import TelegramSourceConfig
from ..database import get_db
from ..models import ParseJob
from ..schemas import ParseJobRead
from ..services.jobs import enqueue_job
from ..services import parser_builder as builder
router = APIRouter(prefix="/admin/parser-builder", tags=["parser-builder"])
class GenerateRequest(BaseModel):
sample_post: str = Field(min_length=1)
class GenerateResponse(BaseModel):
profile: dict[str, Any]
class PreviewRequest(BaseModel):
sample_post: str = Field(min_length=1)
profile: dict[str, Any]
class PreviewResponse(BaseModel):
fields: dict[str, str]
class CreateProfileJobRequest(BaseModel):
channel: str = Field(min_length=1)
profile: dict[str, Any]
sample_post: str | None = None
limit: int = Field(default=100, ge=1, le=1000)
interval_seconds: int = Field(default=3600, ge=60, le=604800)
is_active: bool = True
@router.get("/target-fields")
def target_fields():
return {"fields": builder.get_target_fields()}
@router.post("/generate", response_model=GenerateResponse)
async def generate_profile(payload: GenerateRequest):
if not builder.deepseek_enabled():
raise HTTPException(
status_code=503,
detail="DEEPSEEK_API_KEY is not set on ca-api. Add it to .env for parser generation.",
)
try:
profile = await builder.generate_profile(payload.sample_post)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except Exception as exc:
raise HTTPException(
status_code=502,
detail=f"DeepSeek generate failed: {exc}",
) from exc
return GenerateResponse(profile=profile.model_dump())
@router.post("/preview", response_model=PreviewResponse)
def preview_profile(payload: PreviewRequest):
try:
HeuristicProfile.model_validate(payload.profile)
fields = builder.preview_with_profile(payload.sample_post, payload.profile)
except Exception as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return PreviewResponse(fields=fields)
@router.post("/jobs", response_model=ParseJobRead, status_code=201)
def create_profile_job(payload: CreateProfileJobRequest, db: Session = Depends(get_db)):
try:
cfg = TelegramSourceConfig(
channel=payload.channel.strip(),
limit=payload.limit,
extract_mode="profile",
heuristic_profile=payload.profile,
sample_post=payload.sample_post,
)
except Exception as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
job = ParseJob(
source_type="telegram",
source_config=cfg.model_dump(),
interval_seconds=payload.interval_seconds,
is_active=payload.is_active,
status="queued",
)
db.add(job)
db.commit()
db.refresh(job)
enqueue_job(job.id, job.source_type, job.source_config)
return job