完整修改内容: - 拆分 TravelReimbursementCreateView:提取审核面板纯模型、消息操作、建议动作处理、生命周期 watcher/UI 映射、小财管家运行时、续办流程和运行时文本模型,减少主组件继续堆叠业务分支。 - 调整申请预览链路:新增本地申请意图 gate,完善复杂差旅申请的大模型复核判断、交通方式缺失/候选识别、规则中心交通费用预估合并和申请冲突处理。 - 优化小财管家流程:抽出 steward typewriter 分段策略,避免 Markdown 表格逐字闪烁;补齐跨助手 carry、字段补齐续办、文本确认提交和行程规划推荐动作。 - 调整消息与样式:移除申请预览日期 chip 样式,收敛申请卡片/报销草稿消息的展示与复制、朗读、反馈入口逻辑。 - 更新测试:将源码锚点迁移到新模块,覆盖申请预览、提交确认、小财管家续办、引导流和审核抽屉相关断言。 验证: - node --check web/src/views/scripts/TravelReimbursementCreateView.js 及新增拆分模块 - npm --prefix web run build - node --test web/tests/expense-application-fast-preview.test.mjs web/tests/expense-application-submit-rich-confirm.test.mjs web/tests/travel-reimbursement-guided-flow.test.mjs 说明: - 后端/规则/容器配置/Audit 页面等工作区已有改动未纳入本提交。 - 容器内后端定向 pytest 曾运行 timeout 180s /tmp/x-financial-server-venv/bin/pytest -q <相关后端测试>,180 秒超时且超时前已有失败标记,未作为通过项记录。 - TravelReimbursementCreateView 当前仍超过 800 行,后续仍需继续拆分;本提交先把新增职责模块控制在 800 行内,阻止主类/主模块继续膨胀。
94 lines
3.5 KiB
JavaScript
94 lines
3.5 KiB
JavaScript
const APPLICATION_SESSION_TYPE = 'application'
|
|
const APPLICATION_ASSISTANT_ECHO_PATTERN = /(?:这是)?费用申请核对结果|申请核对结果|请核对上述信息|确认无误后.*提交至审批流程/
|
|
const APPLICATION_QUERY_PATTERN = /查询|状态|进度|列表|有哪些|材料清单|需要哪些|制度|标准|规则|怎么规定/
|
|
const APPLICATION_CREATE_PATTERN = /申请|发起|提交|创建|新建|事前|前置|出差|差旅|采购|会务|会议|培训/
|
|
const APPLICATION_REASON_PATTERN = /支撑|支持|部署|上线|实施|驻场|拜访|验收|培训|协助|处理|办理|参加|服务/
|
|
|
|
export function evaluateLocalApplicationIntentGate(rawText, options = {}) {
|
|
const compact = compactText(rawText)
|
|
if (String(options.sessionType || '').trim() !== APPLICATION_SESSION_TYPE) {
|
|
return block('out_of_scope', 0.98, '不是申请会话。')
|
|
}
|
|
if (options.systemGenerated || options.reviewAction || Number(options.attachmentCount || 0) > 0) {
|
|
return block('out_of_scope', 0.96, '系统生成、审核动作或附件场景不走本地申请预览。')
|
|
}
|
|
if (!compact) {
|
|
return block('chitchat_or_noise', 0.95, '输入为空。')
|
|
}
|
|
if (APPLICATION_ASSISTANT_ECHO_PATTERN.test(compact)) {
|
|
return block('assistant_echo', 0.96, '用户输入是助手申请核对文案回显。')
|
|
}
|
|
if (APPLICATION_QUERY_PATTERN.test(compact)) {
|
|
return block('ask_question', 0.86, '用户更像是在查询或询问。')
|
|
}
|
|
|
|
const fields = collectLocalApplicationIntentFields(rawText)
|
|
const fieldCount = Object.keys(fields).length
|
|
const hasCreateSignal = APPLICATION_CREATE_PATTERN.test(compact)
|
|
if (hasCreateSignal && fieldCount >= 2) {
|
|
return allow('create_application', 0.78, '申请动作和结构化申请事实同时存在。', fields)
|
|
}
|
|
if (hasCreateSignal && fieldCount === 1 && hasStrongBusinessSignal(compact)) {
|
|
return allow('create_application', 0.72, '申请动作和明确业务事实同时存在。', fields)
|
|
}
|
|
return block(
|
|
hasCreateSignal ? 'unknown' : 'chitchat_or_noise',
|
|
hasCreateSignal ? 0.58 : 0.82,
|
|
'未识别到足够的新建申请意图。',
|
|
fields
|
|
)
|
|
}
|
|
|
|
function allow(intent, confidence, reason, fields = {}) {
|
|
return {
|
|
intent,
|
|
allowed: true,
|
|
confidence,
|
|
reason,
|
|
fields
|
|
}
|
|
}
|
|
|
|
function block(intent, confidence, reason, fields = {}) {
|
|
return {
|
|
intent,
|
|
allowed: false,
|
|
confidence,
|
|
reason,
|
|
fields
|
|
}
|
|
}
|
|
|
|
function collectLocalApplicationIntentFields(rawText) {
|
|
const text = String(rawText || '')
|
|
const compact = compactText(text)
|
|
const fields = {}
|
|
if (/(?:20\d{2}[-/.年]\d{1,2}[-/.月]\d{1,2}|[1-9]\d?月[1-3]?\d日?)/.test(compact)) {
|
|
fields.time = 'time_text'
|
|
}
|
|
if (/(?:\d+|[一二两三四五六七八九十]{1,3})天/.test(compact)) {
|
|
fields.days = 'days_text'
|
|
}
|
|
if (/\d+(?:\.\d+)?\s*(?:万|千|k|K|元|块|人民币)/.test(text)) {
|
|
fields.amount = 'amount_text'
|
|
}
|
|
if (/(?:飞机|机票|航班|火车|高铁|动车|轮船|船票|客轮|渡轮)/.test(compact)) {
|
|
fields.transportMode = 'transport_text'
|
|
}
|
|
if (/(?:去|到|赴|前往)[\u4e00-\u9fa5]{1,24}(?:出差|支撑|支持|部署|开会|培训|拜访|验收|项目|客户|$)/.test(compact)) {
|
|
fields.location = 'location_text'
|
|
}
|
|
if (APPLICATION_REASON_PATTERN.test(compact)) {
|
|
fields.reason = 'reason_text'
|
|
}
|
|
return fields
|
|
}
|
|
|
|
function hasStrongBusinessSignal(compactTextValue) {
|
|
return APPLICATION_REASON_PATTERN.test(compactTextValue) || /出差|差旅/.test(compactTextValue)
|
|
}
|
|
|
|
function compactText(value) {
|
|
return String(value || '').replace(/\s+/g, '')
|
|
}
|