2026-05-18 16:32:42 +08:00
|
|
|
"""Define API routes for compliance."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-05-14 15:07:34 +08:00
|
|
|
import asyncio
|
2026-05-18 16:32:42 +08:00
|
|
|
import json
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import AsyncGenerator
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, File, UploadFile
|
|
|
|
|
from fastapi.responses import StreamingResponse
|
|
|
|
|
|
2026-05-14 15:07:34 +08:00
|
|
|
from app.schemas.compliance import (
|
|
|
|
|
AnalyzeResponse,
|
|
|
|
|
ComplianceChatRequest,
|
|
|
|
|
)
|
|
|
|
|
from app.services.mock_data import (
|
|
|
|
|
generate_task_id,
|
|
|
|
|
get_mock_compliance_result,
|
|
|
|
|
get_mock_compliance_chat_response,
|
|
|
|
|
)
|
2026-05-18 16:32:42 +08:00
|
|
|
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
|
|
|
|
|
2026-05-14 15:07:34 +08:00
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/compliance", tags=["合规分析"])
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
2026-05-14 15:07:34 +08:00
|
|
|
tasks_store: dict[str, dict] = {}
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
# Store uploaded compliance files inside the local backend data directory.
|
|
|
|
|
RAW_DATA_DIR = Path(__file__).resolve().parents[3] / "data" / "raw"
|
|
|
|
|
|
2026-05-14 15:07:34 +08:00
|
|
|
|
|
|
|
|
@router.post("/analyze", response_model=AnalyzeResponse)
|
|
|
|
|
async def analyze_document(file: UploadFile = File(...)):
|
2026-05-18 16:32:42 +08:00
|
|
|
"""Handle analyze document."""
|
|
|
|
|
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
2026-05-14 15:07:34 +08:00
|
|
|
task_id = generate_task_id()
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
|
|
|
|
RAW_DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
file_path = RAW_DATA_DIR / f"compliance_{task_id}_{file.filename}"
|
2026-05-14 15:07:34 +08:00
|
|
|
|
|
|
|
|
content = await file.read()
|
2026-05-18 16:32:42 +08:00
|
|
|
with file_path.open("wb") as f:
|
2026-05-14 15:07:34 +08:00
|
|
|
f.write(content)
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
2026-05-14 15:07:34 +08:00
|
|
|
tasks_store[task_id] = {
|
|
|
|
|
"task_id": task_id,
|
2026-05-18 16:32:42 +08:00
|
|
|
"file_path": str(file_path),
|
2026-05-14 15:07:34 +08:00
|
|
|
"status": "processing",
|
|
|
|
|
"result": None,
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
|
|
|
|
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
2026-05-14 15:07:34 +08:00
|
|
|
tasks_store[task_id]["status"] = "completed"
|
|
|
|
|
tasks_store[task_id]["result"] = get_mock_compliance_result(task_id)
|
|
|
|
|
|
|
|
|
|
return AnalyzeResponse(task_id=task_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/result/{task_id}")
|
|
|
|
|
async def get_result(task_id: str):
|
2026-05-18 16:32:42 +08:00
|
|
|
"""Return result."""
|
2026-05-14 15:07:34 +08:00
|
|
|
if task_id not in tasks_store:
|
2026-05-18 16:32:42 +08:00
|
|
|
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
2026-05-14 15:07:34 +08:00
|
|
|
return get_mock_compliance_result(task_id)
|
|
|
|
|
|
|
|
|
|
task = tasks_store[task_id]
|
|
|
|
|
|
|
|
|
|
if task["status"] == "processing":
|
|
|
|
|
return {"status": "processing", "message": "分析进行中"}
|
|
|
|
|
|
|
|
|
|
return task["result"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/chat/{segment_id}")
|
|
|
|
|
async def compliance_chat(segment_id: int, request: ComplianceChatRequest):
|
2026-05-18 16:32:42 +08:00
|
|
|
"""Handle compliance chat."""
|
|
|
|
|
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
2026-05-14 15:07:34 +08:00
|
|
|
intent_map = {
|
|
|
|
|
1: "车身结构设计",
|
|
|
|
|
2: "动力系统配置",
|
|
|
|
|
3: "安全配置设计",
|
|
|
|
|
}
|
|
|
|
|
intent = intent_map.get(segment_id, "车身结构设计")
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
async def generate() -> AsyncGenerator[str, None]:
|
|
|
|
|
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
|
|
|
|
"""Handle generate."""
|
2026-05-14 15:07:34 +08:00
|
|
|
response = get_mock_compliance_chat_response(intent, request.query)
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
2026-05-14 15:07:34 +08:00
|
|
|
sentences = response.split("\n\n")
|
|
|
|
|
for sentence in sentences:
|
|
|
|
|
if sentence.strip():
|
|
|
|
|
chunks = sentence.split("\n")
|
|
|
|
|
for chunk in chunks:
|
|
|
|
|
if chunk.strip():
|
|
|
|
|
await asyncio.sleep(0.05)
|
2026-05-18 16:32:42 +08:00
|
|
|
yield (
|
|
|
|
|
"event: message\n"
|
|
|
|
|
f"data: {json.dumps({'type': 'chunk', 'text': chunk + chr(10)}, ensure_ascii=False)}\n\n"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
yield f"event: message\ndata: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
|
|
|
|
|
|
|
|
|
|
return StreamingResponse(
|
|
|
|
|
generate(),
|
|
|
|
|
media_type="text/event-stream",
|
|
|
|
|
headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"},
|
|
|
|
|
)
|