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>
109 lines
3.2 KiB
Python
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
|