feat(server): 新增附件关联/关联报销草稿后台任务与申请位置语义
- attachment_association_jobs:从票据夹批量关联附件到报销单,识别城市/日期并创建明细项,内存态 job 跟踪 - linked_reimbursement_draft_jobs:基于申请单异步生成关联报销草稿,调用 Orchestrator 编排,区分 succeeded/failed 终态 - application_location_semantics:抽取差旅出发/到达城市、判断具体地址/业务动作等位置语义,供申请单校验复用 - router 注册两个 job 端点,新增对应 job/语义单元测试
This commit is contained in:
40
server/src/app/schemas/attachment_association_job.py
Normal file
40
server/src/app/schemas/attachment_association_job.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class AttachmentAssociationJobCreate(BaseModel):
|
||||
receipt_ids: list[str] = Field(default_factory=list, description="票据夹持久化票据 ID。")
|
||||
prompt: str = Field(default="", max_length=1000, description="用户发送时的上下文说明。")
|
||||
conversation_id: str = Field(default="", max_length=120, description="前端会话 ID,用于状态恢复。")
|
||||
|
||||
@field_validator("receipt_ids")
|
||||
@classmethod
|
||||
def validate_receipt_ids(cls, value: list[str]) -> list[str]:
|
||||
receipt_ids = [
|
||||
str(item or "").strip()
|
||||
for item in list(value or [])
|
||||
if str(item or "").strip()
|
||||
]
|
||||
if not receipt_ids:
|
||||
raise ValueError("请先完成附件 OCR 识别,再发起自动关联。")
|
||||
return list(dict.fromkeys(receipt_ids))
|
||||
|
||||
|
||||
class AttachmentAssociationJobRead(BaseModel):
|
||||
job_id: str
|
||||
status: str
|
||||
message: str = ""
|
||||
receipt_ids: list[str] = Field(default_factory=list)
|
||||
claim_id: str = ""
|
||||
claim_no: str = ""
|
||||
uploaded_count: int = 0
|
||||
skipped_count: int = 0
|
||||
error: str = ""
|
||||
prompt: str = ""
|
||||
conversation_id: str = ""
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
32
server/src/app/schemas/linked_reimbursement_draft_job.py
Normal file
32
server/src/app/schemas/linked_reimbursement_draft_job.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class LinkedReimbursementDraftJobCreate(BaseModel):
|
||||
message: str = Field(min_length=1, max_length=3000, description="生成报销草稿的原始助手请求。")
|
||||
context_json: dict[str, Any] = Field(default_factory=dict, description="复用 Orchestrator 的上下文。")
|
||||
conversation_id: str = Field(default="", max_length=120, description="前端会话 ID,用于状态恢复。")
|
||||
|
||||
@field_validator("message")
|
||||
@classmethod
|
||||
def validate_message(cls, value: str) -> str:
|
||||
normalized = str(value or "").strip()
|
||||
if not normalized:
|
||||
raise ValueError("请先选择要关联的申请单。")
|
||||
return normalized
|
||||
|
||||
|
||||
class LinkedReimbursementDraftJobRead(BaseModel):
|
||||
job_id: str
|
||||
status: str
|
||||
message: str = ""
|
||||
error: str = ""
|
||||
run_id: str = ""
|
||||
conversation_id: str = ""
|
||||
draft_payload: dict[str, Any] | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
Reference in New Issue
Block a user