refactor(server): user_agent/steward/ocr 等服务重构并适配关联任务
- user_agent 拆分 application/locations/knowledge/response/review 四个子模块,接入申请位置语义与关联草稿分支 - steward planner/runtime/slot/plan_builder 决策链路重构,travel_reimbursement_calculator/orchestrator_expense_query 适配 - ocr/document_preview/document_intelligence/receipt_folder 复用预览与资产缓存,expense_claim_draft_flow/application_handoff 适配 - pyproject.toml 新增依赖,paddleocr bootstrap 脚本与 server_start.sh 调整 - 更新差旅/交通/通信等财务规则表,同步 document_intelligence/ocr/receipt_folder/user_agent 等测试
This commit is contained in:
@@ -8,20 +8,25 @@ from sqlalchemy import or_, select
|
||||
|
||||
from app.api.deps import CurrentUserContext
|
||||
from app.models.financial_record import ExpenseClaim
|
||||
from app.schemas.reimbursement import TravelReimbursementCalculatorRequest
|
||||
from app.schemas.user_agent import (
|
||||
UserAgentDraftPayload,
|
||||
UserAgentRequest,
|
||||
UserAgentResponse,
|
||||
UserAgentSuggestedAction,
|
||||
)
|
||||
from app.schemas.reimbursement import TravelReimbursementCalculatorRequest
|
||||
from app.services.expense_claim_access_policy import ExpenseClaimAccessPolicy
|
||||
from app.services.expense_claim_risk_stage import with_risk_business_stage
|
||||
from app.services.travel_reimbursement_calculator import TravelReimbursementCalculatorService
|
||||
from app.services.application_location_semantics import (
|
||||
strip_route_location_prefix_with_jieba,
|
||||
validate_application_location_text,
|
||||
)
|
||||
from app.services.application_system_estimate import apply_application_system_estimate_to_facts
|
||||
from app.services.document_numbering import (
|
||||
build_document_number,
|
||||
generate_unique_expense_claim_no,
|
||||
)
|
||||
from app.services.expense_claim_access_policy import ExpenseClaimAccessPolicy
|
||||
from app.services.expense_claim_risk_stage import with_risk_business_stage
|
||||
from app.services.travel_reimbursement_calculator import TravelReimbursementCalculatorService
|
||||
from app.services.user_agent_application_dates import (
|
||||
expand_application_time_with_days,
|
||||
resolve_application_date_range,
|
||||
@@ -33,7 +38,6 @@ from app.services.user_agent_application_summary import (
|
||||
build_application_summary_table,
|
||||
resolve_application_time_label,
|
||||
)
|
||||
from app.services.application_system_estimate import apply_application_system_estimate_to_facts
|
||||
|
||||
APPLICATION_CONTEXT_VALUES = {
|
||||
"application",
|
||||
@@ -182,6 +186,17 @@ class UserAgentApplicationSlotMixin:
|
||||
if not str(facts.get(field) or "").strip()
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _resolve_application_validation_issues(facts: dict[str, str]) -> list[dict[str, str]]:
|
||||
issues: list[dict[str, str]] = []
|
||||
location_error = validate_application_location_text(facts.get("location", ""))
|
||||
if location_error:
|
||||
issues.append({
|
||||
"field": "location",
|
||||
"message": location_error,
|
||||
})
|
||||
return issues
|
||||
|
||||
def _resolve_application_missing_fields(self, facts: dict[str, str]) -> list[str]:
|
||||
return [
|
||||
*self._resolve_application_missing_base_fields(facts),
|
||||
@@ -391,7 +406,11 @@ class UserAgentApplicationSlotMixin:
|
||||
if re.fullmatch(r"(?:去|到|前往)?[\u4e00-\u9fa5]{1,8}出差(?P<days>\d+|[一二两三四五六七八九十]{1,3})?天?", text):
|
||||
return ""
|
||||
|
||||
text = re.sub(r"^.*?(?:出差|前往|去|到|赴)[\u4e00-\u9fa5]{1,8}(?:出差)?(?P<days>\d+|[一二两三四五六七八九十]{1,3})?天?[,,\s]*", "", text)
|
||||
tokenized = strip_route_location_prefix_with_jieba(text)
|
||||
if tokenized != text:
|
||||
text = tokenized
|
||||
else:
|
||||
text = re.sub(r"^.*?(?:出差|前往|去|到|赴)[\u4e00-\u9fa5]{1,8}(?:出差)?(?P<days>\d+|[一二两三四五六七八九十]{1,3})?天?[,,\s]*", "", text)
|
||||
text = re.sub(r"^(?:出差|申请|费用申请|业务|本次|去|到|前往)\s*", "", text)
|
||||
text = text.strip(" ::,,。;;")
|
||||
if not text:
|
||||
@@ -537,8 +556,16 @@ class UserAgentApplicationSlotMixin:
|
||||
step: str,
|
||||
facts: dict[str, str],
|
||||
) -> list[UserAgentSuggestedAction]:
|
||||
if step == "ask_missing":
|
||||
missing_fields = self._resolve_application_missing_fields(facts)
|
||||
if step in {"ask_missing", "ask_invalid"}:
|
||||
missing_fields = (
|
||||
self._resolve_application_missing_fields(facts)
|
||||
if step == "ask_missing"
|
||||
else [
|
||||
issue.get("field", "")
|
||||
for issue in self._resolve_application_validation_issues(facts)
|
||||
if issue.get("field")
|
||||
]
|
||||
)
|
||||
return [
|
||||
UserAgentSuggestedAction(
|
||||
label="一次性补充申请信息",
|
||||
@@ -1209,7 +1236,22 @@ class UserAgentApplicationMixin(UserAgentApplicationSlotMixin, UserAgentApplicat
|
||||
"我已按「费用申请 / 事前审批」来处理这条内容。",
|
||||
"已识别信息:\n" + recognized_table,
|
||||
f"当前还需要补充:{missing_text}。",
|
||||
"请一次性补齐上述字段,我会继续生成申请核对结果并让你确认是否提交。",
|
||||
"请一次性补齐上述字段,我会继续生成申请核对结果,并请您确认是否提交。",
|
||||
]
|
||||
)
|
||||
|
||||
if step == "ask_invalid":
|
||||
issue_messages = [
|
||||
item["message"]
|
||||
for item in self._resolve_application_validation_issues(facts)
|
||||
if str(item.get("message") or "").strip()
|
||||
]
|
||||
return "\n\n".join(
|
||||
[
|
||||
"我已识别到申请信息里有需要先修正的字段。",
|
||||
"已识别信息:\n" + recognized_table,
|
||||
*issue_messages,
|
||||
"请把地点改为真实出差地点,业务事项放在事由中;修正后我再帮您提交申请。",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1473,7 +1515,7 @@ class UserAgentApplicationMixin(UserAgentApplicationSlotMixin, UserAgentApplicat
|
||||
pick("applicationType", "application_type")
|
||||
),
|
||||
"time": pick("time", "timeRange", "time_range"),
|
||||
"location": pick("location"),
|
||||
"location": normalize_application_location(pick("location")),
|
||||
"reason": reason,
|
||||
"days": pick("days"),
|
||||
"transport_mode": pick("transportMode", "transport_mode"),
|
||||
@@ -1507,6 +1549,8 @@ class UserAgentApplicationMixin(UserAgentApplicationSlotMixin, UserAgentApplicat
|
||||
payload: UserAgentRequest,
|
||||
facts: dict[str, str],
|
||||
) -> str:
|
||||
if self._resolve_application_validation_issues(facts):
|
||||
return "ask_invalid"
|
||||
if self._is_application_save_draft_action(payload):
|
||||
return "draft"
|
||||
if self._resolve_application_missing_base_fields(facts):
|
||||
@@ -1516,4 +1560,3 @@ class UserAgentApplicationMixin(UserAgentApplicationSlotMixin, UserAgentApplicat
|
||||
if self._is_application_submit_confirmation(payload):
|
||||
return "submitted"
|
||||
return "preview"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user