Files
group_fqcd_jr/app/api/controllers/offsite_fund.py
T

251 lines
9.7 KiB
Python
Raw Normal View History

"""场外基金运营接口。"""
from typing import Any
from fastapi import APIRouter, Depends, Query, Response
from fastapi.responses import FileResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.dependencies.auth import build_request_context
from app.api.dependencies.database import get_session
from app.api.schemas.offsite_fund import (
OffsiteConfirmRequest,
OffsiteMailboxRecoveryRequest,
OffsiteMailDeletionRequest,
OffsiteNl2SqlCorrectionRequest,
OffsiteNotificationRequest,
OffsiteNotificationSendRequest,
OffsiteRecalculateRequest,
OffsiteRecognitionCorrectionRequest,
OffsiteRecognitionRetryRequest,
OffsiteRuleRecalculationRequest,
OffsiteTriggerNl2SqlRequest,
)
from app.core.contracts import RequestContext
from app.service.offsite_fund_service import OffsiteFundService
router = APIRouter(prefix="/api/v1/offsite-fund", tags=["offsite-fund"])
operation_router = APIRouter(prefix="/api", tags=["offsite-fund"])
@router.get("/mails")
async def list_mails(
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
sender: str | None = Query(default=None, min_length=3, max_length=255),
status: str | None = Query(default=None, min_length=1, max_length=32),
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
return await OffsiteFundService(session).list_mails(
context, page, page_size, sender, status
)
@router.get("/mails/{mail_id}")
async def get_mail(
mail_id: str,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
return await OffsiteFundService(session).get_mail(mail_id, context)
@router.post("/mails/{mail_id}/deletions")
async def delete_mail(
mail_id: str,
payload: OffsiteMailDeletionRequest,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
"""物理删除邮件及其关联业务数据,保留删除审计。"""
return await OffsiteFundService(session).delete_mail(
mail_id, payload.operator_id, context
)
@router.get("/mails/{mail_id}/recognition-fields")
async def get_mail_recognition_fields(
mail_id: str,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
"""读取邮件内全部附件的 OCR 识别字段:只读查询,不触发重新识别。"""
return await OffsiteFundService(session).mail_recognition_fields(mail_id, context)
@router.put("/mails/{mail_id}/recognition-fields")
async def save_mail_recognition_fields(
mail_id: str,
payload: OffsiteRecognitionCorrectionRequest,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
"""保存 OCR 识别字段的人工修正值:只新增修正记录,不覆盖 Agent 原始识别值。"""
return await OffsiteFundService(session).save_recognition_corrections(
mail_id,
[(item.attachment_id, item.fields) for item in payload.attachments],
payload.operator_id,
context,
)
@router.get("/documents/{task_id}/nl2sql-fields")
async def get_document_nl2sql_fields(
task_id: str,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
"""读取单据核对使用的 NL2SQL 返回字段:只读查询,不触发新的查询。"""
return await OffsiteFundService(session).nl2sql_fields(task_id, context)
@router.put("/documents/{task_id}/nl2sql-fields")
async def save_document_nl2sql_fields(
task_id: str,
payload: OffsiteNl2SqlCorrectionRequest,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
"""保存 NL2SQL 返回字段的人工修正值:只新增修正记录,不改写查询记录。"""
return await OffsiteFundService(session).save_nl2sql_corrections(
task_id, payload.fields, payload.operator_id, context
)
@router.get("/documents/{task_id}/rule-results")
async def get_document_rule_results(
task_id: str,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
"""读取单据规则判定结果:只读查询,不触发查询或重新判定。"""
return await OffsiteFundService(session).rule_results(task_id, context)
@router.post("/documents/{task_id}/rule-results/recalculations")
async def recalculate_document_rule_results(
task_id: str,
payload: OffsiteRuleRecalculationRequest,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
"""用已落库的识别字段和查询结果重新判定规则:不重新识别、不重新调用 NL2SQL。"""
return await OffsiteFundService(session).recalculate_rule_results(
task_id, payload.operator_id, context
)
@router.get("/mailbox-status")
async def mailbox_status(
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
"""收件箱状态:游标锁死时前端据此弹出告警。"""
return await OffsiteFundService(session).mailbox_status(context)
@router.post("/mailbox-status/recoveries")
async def recover_mailbox(
payload: OffsiteMailboxRecoveryRequest,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
"""解除收件游标阻塞,保留失败 UID 并由 Worker 重新处理。"""
return await OffsiteFundService(session).recover_mailbox(
payload.operator_id, context
)
@router.get("/attachments/{attachment_id}/file", response_model=None)
async def open_attachment_file(
attachment_id: str,
disposition: str = Query(default="inline", pattern="^(inline|attachment)$"),
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> Response | dict[str, Any]:
"""按附件编号返回原始文件:PDF/图片默认内联预览,其余类型转为下载。"""
result = await OffsiteFundService(session).open_attachment_file(
attachment_id, disposition, context
)
if isinstance(result, dict):
return result
return FileResponse(
result.path,
media_type=result.media_type,
filename=result.filename,
content_disposition_type=result.disposition,
headers={"X-Content-Type-Options": "nosniff"},
)
@router.post("/documents/{task_id}/confirmations")
async def confirm_document(
task_id: str,
payload: OffsiteConfirmRequest,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
return await OffsiteFundService(session).confirm_document(
task_id, payload.decision, payload.operator_id, context)
@router.post("/documents/{task_id}/recognition-retries")
async def retry_document_recognition(
task_id: str,
payload: OffsiteRecognitionRetryRequest,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
return await OffsiteFundService(session).retry_document_recognition(
task_id, payload.operator_id, context
)
@router.post("/documents/{task_id}/notifications")
async def create_notification(
task_id: str,
payload: OffsiteNotificationRequest,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
return await OffsiteFundService(session).create_notification(
task_id, payload.notification_type, payload.operator_id, context)
@router.post("/notifications/{notification_id}/send")
async def send_notification(
notification_id: int,
payload: OffsiteNotificationSendRequest,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
return await OffsiteFundService(session).send_notification(
notification_id,
payload.operator_id,
payload.operator_confirmed,
payload.final_content,
context,
)
@router.post("/settlement-statistics/recalculate")
async def recalculate_statistics(
payload: OffsiteRecalculateRequest,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
return await OffsiteFundService(session).recalculate_statistics(
payload.fund_code, payload.application_date, context)
@operation_router.post("/tasks/{task_id}/trigger-agent-nl2sql")
async def trigger_agent_nl2sql(
task_id: str,
payload: OffsiteTriggerNl2SqlRequest,
context: RequestContext = Depends(build_request_context), # noqa: B008
session: AsyncSession = Depends(get_session), # noqa: B008
) -> dict[str, Any]:
return await OffsiteFundService(session).trigger_agent_nl2sql(
task_id, payload.operator_id, payload.manual_confirmed, context)