300 lines
11 KiB
Python
300 lines
11 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import re
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
EXPENSE_RULE_CODE_BLOCK_PATTERN = re.compile(r"```expense-rule\s*(\{.*?\})\s*```", re.DOTALL)
|
||
|
|
|
||
|
|
DOCUMENT_TYPE_LABELS = {
|
||
|
|
"flight_itinerary": "机票/航班行程单",
|
||
|
|
"train_ticket": "火车/高铁票",
|
||
|
|
"hotel_invoice": "酒店住宿票据",
|
||
|
|
"taxi_receipt": "出租车/网约车票据",
|
||
|
|
"parking_toll_receipt": "停车/通行费票据",
|
||
|
|
"meal_receipt": "餐饮票据",
|
||
|
|
"office_invoice": "办公用品票据",
|
||
|
|
"meeting_invoice": "会议/会务票据",
|
||
|
|
"training_invoice": "培训票据",
|
||
|
|
"vat_invoice": "增值税发票",
|
||
|
|
"receipt": "一般收据/凭证",
|
||
|
|
"other": "其他单据",
|
||
|
|
}
|
||
|
|
|
||
|
|
SCENE_LABELS = {
|
||
|
|
"travel": "差旅",
|
||
|
|
"hotel": "住宿",
|
||
|
|
"transport": "交通",
|
||
|
|
"meal": "餐饮",
|
||
|
|
"entertainment": "业务招待",
|
||
|
|
"office": "办公",
|
||
|
|
"meeting": "会务",
|
||
|
|
"training": "培训",
|
||
|
|
"communication": "通讯",
|
||
|
|
"welfare": "福利",
|
||
|
|
"other": "其他",
|
||
|
|
}
|
||
|
|
|
||
|
|
DEFAULT_SCENE_RULE_ASSET_CODE = "rule.expense.scene_submission_standard"
|
||
|
|
DEFAULT_TRAVEL_RULE_ASSET_CODE = "rule.expense.travel_risk_control_standard"
|
||
|
|
|
||
|
|
DEFAULT_SCENE_MATRIX_CONFIG: dict[str, Any] = {
|
||
|
|
"kind": "scene_matrix",
|
||
|
|
"version": 1,
|
||
|
|
"scenes": {
|
||
|
|
"travel": {
|
||
|
|
"label": "差旅费",
|
||
|
|
"location_required": True,
|
||
|
|
"min_attachment_count": 1,
|
||
|
|
"allowed_scene_codes": ["travel"],
|
||
|
|
"allowed_document_types": ["flight_itinerary", "train_ticket"],
|
||
|
|
"attachment_mismatch_severity": "high",
|
||
|
|
},
|
||
|
|
"hotel": {
|
||
|
|
"label": "住宿费",
|
||
|
|
"location_required": False,
|
||
|
|
"min_attachment_count": 1,
|
||
|
|
"allowed_scene_codes": ["hotel"],
|
||
|
|
"allowed_document_types": ["hotel_invoice", "vat_invoice", "receipt"],
|
||
|
|
"attachment_mismatch_severity": "high",
|
||
|
|
},
|
||
|
|
"transport": {
|
||
|
|
"label": "交通费",
|
||
|
|
"location_required": False,
|
||
|
|
"min_attachment_count": 1,
|
||
|
|
"allowed_scene_codes": ["transport"],
|
||
|
|
"allowed_document_types": ["taxi_receipt", "parking_toll_receipt", "vat_invoice", "receipt"],
|
||
|
|
"attachment_mismatch_severity": "high",
|
||
|
|
"item_amount_limit": {
|
||
|
|
"scope": "item_amount",
|
||
|
|
"warn_amount": "300.00",
|
||
|
|
"block_amount": "800.00",
|
||
|
|
"exception_keywords": ["跨城", "夜间", "应急", "无公共交通", "机场", "火车站", "超标说明"],
|
||
|
|
"metric_label": "单笔交通金额",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"meal": {
|
||
|
|
"label": "餐费",
|
||
|
|
"location_required": False,
|
||
|
|
"min_attachment_count": 1,
|
||
|
|
"allowed_scene_codes": ["meal"],
|
||
|
|
"allowed_document_types": ["meal_receipt", "vat_invoice", "receipt"],
|
||
|
|
"attachment_mismatch_severity": "high",
|
||
|
|
"claim_amount_limit": {
|
||
|
|
"scope": "claim_total",
|
||
|
|
"warn_amount": "300.00",
|
||
|
|
"block_amount": "800.00",
|
||
|
|
"exception_keywords": ["客户接待", "团队活动", "加班", "展会", "超标说明"],
|
||
|
|
"metric_label": "餐费合计",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"entertainment": {
|
||
|
|
"label": "业务招待费",
|
||
|
|
"location_required": True,
|
||
|
|
"min_attachment_count": 1,
|
||
|
|
"allowed_scene_codes": ["meal"],
|
||
|
|
"allowed_document_types": ["meal_receipt", "vat_invoice", "receipt"],
|
||
|
|
"attachment_mismatch_severity": "high",
|
||
|
|
"claim_amount_limit": {
|
||
|
|
"scope": "claim_total",
|
||
|
|
"warn_amount": "2000.00",
|
||
|
|
"block_amount": "5000.00",
|
||
|
|
"exception_keywords": ["重要客户", "商务宴请", "项目签约", "超标说明"],
|
||
|
|
"metric_label": "招待费合计",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"office": {
|
||
|
|
"label": "办公费",
|
||
|
|
"location_required": False,
|
||
|
|
"min_attachment_count": 1,
|
||
|
|
"allowed_scene_codes": ["office"],
|
||
|
|
"allowed_document_types": ["office_invoice", "vat_invoice", "receipt"],
|
||
|
|
"attachment_mismatch_severity": "high",
|
||
|
|
"claim_amount_limit": {
|
||
|
|
"scope": "claim_total",
|
||
|
|
"warn_amount": "1500.00",
|
||
|
|
"block_amount": "5000.00",
|
||
|
|
"exception_keywords": ["批量采购", "固定资产", "部门集中采购", "超标说明"],
|
||
|
|
"metric_label": "办公费合计",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"meeting": {
|
||
|
|
"label": "会务费",
|
||
|
|
"location_required": True,
|
||
|
|
"min_attachment_count": 1,
|
||
|
|
"allowed_scene_codes": ["meeting"],
|
||
|
|
"allowed_document_types": ["meeting_invoice", "vat_invoice", "receipt"],
|
||
|
|
"attachment_mismatch_severity": "high",
|
||
|
|
"claim_amount_limit": {
|
||
|
|
"scope": "claim_total",
|
||
|
|
"warn_amount": "5000.00",
|
||
|
|
"block_amount": "30000.00",
|
||
|
|
"exception_keywords": ["大型会议", "外部场地", "超标说明"],
|
||
|
|
"metric_label": "会务费合计",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"training": {
|
||
|
|
"label": "培训费",
|
||
|
|
"location_required": False,
|
||
|
|
"min_attachment_count": 1,
|
||
|
|
"allowed_scene_codes": ["training"],
|
||
|
|
"allowed_document_types": ["training_invoice", "vat_invoice", "receipt"],
|
||
|
|
"attachment_mismatch_severity": "high",
|
||
|
|
"claim_amount_limit": {
|
||
|
|
"scope": "claim_total",
|
||
|
|
"warn_amount": "3000.00",
|
||
|
|
"block_amount": "15000.00",
|
||
|
|
"exception_keywords": ["认证考试", "外部培训", "超标说明"],
|
||
|
|
"metric_label": "培训费合计",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"communication": {
|
||
|
|
"label": "通讯费",
|
||
|
|
"location_required": False,
|
||
|
|
"min_attachment_count": 1,
|
||
|
|
"allowed_scene_codes": ["other"],
|
||
|
|
"allowed_document_types": ["vat_invoice", "receipt"],
|
||
|
|
"attachment_mismatch_severity": "medium",
|
||
|
|
"claim_amount_limit": {
|
||
|
|
"scope": "claim_total",
|
||
|
|
"warn_amount": "300.00",
|
||
|
|
"block_amount": "1000.00",
|
||
|
|
"exception_keywords": ["国际漫游", "专项通信", "超标说明"],
|
||
|
|
"metric_label": "通讯费合计",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"welfare": {
|
||
|
|
"label": "福利费",
|
||
|
|
"location_required": False,
|
||
|
|
"min_attachment_count": 1,
|
||
|
|
"allowed_scene_codes": ["other"],
|
||
|
|
"allowed_document_types": ["vat_invoice", "receipt"],
|
||
|
|
"attachment_mismatch_severity": "medium",
|
||
|
|
"claim_amount_limit": {
|
||
|
|
"scope": "claim_total",
|
||
|
|
"warn_amount": "1000.00",
|
||
|
|
"block_amount": "5000.00",
|
||
|
|
"exception_keywords": ["节日福利", "团队活动", "员工关怀", "超标说明"],
|
||
|
|
"metric_label": "福利费合计",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"other": {
|
||
|
|
"label": "其他费用",
|
||
|
|
"location_required": False,
|
||
|
|
"min_attachment_count": 1,
|
||
|
|
"allowed_scene_codes": ["other"],
|
||
|
|
"allowed_document_types": ["vat_invoice", "receipt"],
|
||
|
|
"attachment_mismatch_severity": "medium",
|
||
|
|
"always_warn": True,
|
||
|
|
"always_warn_message": "其他费用默认进入人工重点复核,请补充清晰用途说明并由审批人重点确认。",
|
||
|
|
"claim_amount_limit": {
|
||
|
|
"scope": "claim_total",
|
||
|
|
"warn_amount": "1000.00",
|
||
|
|
"block_amount": "3000.00",
|
||
|
|
"exception_keywords": ["特殊事项", "临时采购", "超标说明"],
|
||
|
|
"metric_label": "其他费用合计",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
DEFAULT_TRAVEL_POLICY_CONFIG: dict[str, Any] = {
|
||
|
|
"kind": "travel_policy",
|
||
|
|
"version": 1,
|
||
|
|
"relevant_expense_types": ["travel", "hotel", "transport"],
|
||
|
|
"long_distance_document_types": ["flight_itinerary", "train_ticket"],
|
||
|
|
"route_exception_keywords": [
|
||
|
|
"中转",
|
||
|
|
"转机",
|
||
|
|
"经停",
|
||
|
|
"改签",
|
||
|
|
"多地出差",
|
||
|
|
"多城市",
|
||
|
|
"多站",
|
||
|
|
"异地返程",
|
||
|
|
"异地结束",
|
||
|
|
"临时变更",
|
||
|
|
"继续前往",
|
||
|
|
"第二站",
|
||
|
|
],
|
||
|
|
"standard_exception_keywords": [
|
||
|
|
"超标说明",
|
||
|
|
"无直达",
|
||
|
|
"展会高峰",
|
||
|
|
"会议高峰",
|
||
|
|
"协议酒店满房",
|
||
|
|
"客户指定",
|
||
|
|
"临时改签",
|
||
|
|
"行程变更",
|
||
|
|
"红眼航班",
|
||
|
|
"晚到店",
|
||
|
|
],
|
||
|
|
"band_labels": {
|
||
|
|
"junior": "P1-P3",
|
||
|
|
"mid": "P4-P5",
|
||
|
|
"senior": "P6-P7",
|
||
|
|
"manager": "M1-M2",
|
||
|
|
"executive": "M3及以上 / D序列",
|
||
|
|
},
|
||
|
|
"city_tiers": {
|
||
|
|
"北京": "tier_1",
|
||
|
|
"上海": "tier_1",
|
||
|
|
"广州": "tier_1",
|
||
|
|
"深圳": "tier_1",
|
||
|
|
"杭州": "tier_2",
|
||
|
|
"南京": "tier_2",
|
||
|
|
"苏州": "tier_2",
|
||
|
|
"武汉": "tier_2",
|
||
|
|
"成都": "tier_2",
|
||
|
|
"重庆": "tier_2",
|
||
|
|
"西安": "tier_2",
|
||
|
|
"天津": "tier_2",
|
||
|
|
"宁波": "tier_2",
|
||
|
|
"厦门": "tier_2",
|
||
|
|
"青岛": "tier_2",
|
||
|
|
"长沙": "tier_2",
|
||
|
|
"郑州": "tier_2",
|
||
|
|
"合肥": "tier_2",
|
||
|
|
"济南": "tier_2",
|
||
|
|
"沈阳": "tier_2",
|
||
|
|
"大连": "tier_2",
|
||
|
|
"福州": "tier_2",
|
||
|
|
"昆明": "tier_2",
|
||
|
|
"海口": "tier_2",
|
||
|
|
"三亚": "tier_2",
|
||
|
|
"无锡": "tier_2",
|
||
|
|
"东莞": "tier_2",
|
||
|
|
"佛山": "tier_2",
|
||
|
|
},
|
||
|
|
"hotel_limits": {
|
||
|
|
"junior": {"tier_1": "450.00", "tier_2": "380.00", "tier_3": "320.00"},
|
||
|
|
"mid": {"tier_1": "550.00", "tier_2": "480.00", "tier_3": "380.00"},
|
||
|
|
"senior": {"tier_1": "700.00", "tier_2": "620.00", "tier_3": "520.00"},
|
||
|
|
"manager": {"tier_1": "900.00", "tier_2": "820.00", "tier_3": "720.00"},
|
||
|
|
"executive": {"tier_1": "1200.00", "tier_2": "1000.00", "tier_3": "900.00"},
|
||
|
|
},
|
||
|
|
"transport_limits": {
|
||
|
|
"junior": {"flight": 1, "train": 1},
|
||
|
|
"mid": {"flight": 1, "train": 1},
|
||
|
|
"senior": {"flight": 2, "train": 2},
|
||
|
|
"manager": {"flight": 3, "train": 3},
|
||
|
|
"executive": {"flight": 4, "train": 3},
|
||
|
|
},
|
||
|
|
"flight_classes": [
|
||
|
|
{"keyword": "头等舱", "level": 4},
|
||
|
|
{"keyword": "公务舱", "level": 3},
|
||
|
|
{"keyword": "商务舱", "level": 3},
|
||
|
|
{"keyword": "超级经济舱", "level": 2},
|
||
|
|
{"keyword": "高端经济舱", "level": 2},
|
||
|
|
{"keyword": "明珠经济舱", "level": 2},
|
||
|
|
{"keyword": "经济舱", "level": 1},
|
||
|
|
],
|
||
|
|
"train_classes": [
|
||
|
|
{"keyword": "商务座", "level": 3},
|
||
|
|
{"keyword": "一等座", "level": 2},
|
||
|
|
{"keyword": "软卧", "level": 2},
|
||
|
|
{"keyword": "二等座", "level": 1},
|
||
|
|
{"keyword": "二等卧", "level": 1},
|
||
|
|
{"keyword": "硬卧", "level": 1},
|
||
|
|
],
|
||
|
|
}
|