| export type ModuleId = 'trade' | 'career' | 'partner' |
| export type EngineId = 'universal' |
|
|
| export type OutcomeDistribution = { |
| winWin: number |
| neutral: number |
| mismatch: number |
| } |
|
|
| export type SuccessPanel = { |
| success: number |
| neutral: number |
| failure: number |
| stages: { |
| currentNegotiation: number |
| afterConcession: number |
| fulfillment: number |
| } |
| stageModels: { |
| currentNegotiation: { |
| model: 'bargaining-scorecard' |
| factors: { label: string; value: number; coefficient: number; contribution: number }[] |
| } |
| afterConcession: { |
| model: 'mechanism-counterfactual' |
| concessionGain: number |
| concessionCost: number |
| paretoImprovement: number |
| factors: { label: string; value: number; coefficient: number; contribution: number }[] |
| } |
| fulfillment: { |
| model: 'bayesian-survival' |
| baseSurvival: number |
| hazard: number |
| factors: { label: string; value: number; coefficient: number; contribution: number }[] |
| } |
| } |
| grade: 'advantage' | 'balanced' | 'danger' |
| formulaVersion: string |
| drivers: { |
| positive: { label: string; value: number; impact: number }[] |
| negative: { label: string; value: number; impact: number }[] |
| } |
| } |
|
|
| export type EngineFeature = { |
| key: string |
| label: string |
| category: string |
| source: 'base' | 'open' |
| value: number |
| confidence: number |
| weight: number |
| evidence: string[] |
| missing: boolean |
| } |
|
|
| export type DynamicFactorInput = { |
| key?: string |
| label: string |
| category: string |
| value: number |
| confidence: number |
| weight: number |
| userWeight?: number |
| otherWeight?: number |
| riskWeight?: number |
| refWeight?: number |
| evidence?: string[] |
| question?: string |
| } |
|
|
| export type ContractAnalysis = { |
| moduleId: EngineId |
| moduleName: string |
| verdict: string |
| decision: string |
| score: number |
| formulas: { |
| generalizedDistribution: string |
| referenceContract: string |
| bilateralMatching: string |
| } |
| features: EngineFeature[] |
| factorCoverage: { |
| total: number |
| observed: number |
| coverage: number |
| missingHighWeightFactors: string[] |
| openFactorCount: number |
| } |
| requiredFactors: { |
| total: number |
| observed: number |
| missing: string[] |
| blocking: boolean |
| } |
| informationSufficiency: { |
| level: 'insufficient' | 'preliminary' | 'sufficient' |
| score: number |
| blocking: boolean |
| reasons: string[] |
| userOnlyMissing: string[] |
| searchableMissing: string[] |
| userQuestions: string[] |
| searchQuestions: string[] |
| } |
| complianceReview: { |
| status: 'pass' | 'warning' | 'blocking' |
| score: number |
| missing: string[] |
| risks: string[] |
| checklist: { |
| item: string |
| status: 'observed' | 'missing' | 'risk' |
| evidence: string[] |
| }[] |
| } |
| distribution: OutcomeDistribution |
| successPanel: SuccessPanel |
| generalizedDistribution: { |
| expectedUtility: number |
| variance: number |
| downsideProbability: number |
| cvarLoss: number |
| stateUtilities: { state: string; probability: number; utility: number }[] |
| } |
| referenceContract: { |
| referenceAlignment: number |
| expectationGap: number |
| holdUpRisk: number |
| renegotiationRisk: number |
| fairnessReference: number |
| } |
| controlRights: { |
| controlAlignment: number |
| residualControlRisk: number |
| investmentIncentiveFit: number |
| stateContingencyReadiness: number |
| recommendedAllocation: string |
| missingControlQuestions: string[] |
| } |
| bayesianReliability: { |
| prior: number |
| posterior: number |
| evidenceCount: number |
| positiveEvidence: string[] |
| negativeEvidence: string[] |
| reliabilityLevel: 'high' | 'medium' | 'low' |
| interpretation: string |
| } |
| bilateralMatching: { |
| userUtility: number |
| counterpartyUtility: number |
| outsideOptionPressure: number |
| totalSurplus: number |
| nashProduct: number |
| matchingEquilibrium: number |
| } |
| missingQuestions: string[] |
| modelPayload: { |
| engineVersion: string |
| theoryBasis: string[] |
| constraints: string[] |
| } |
| } |
|
|
| type FactorSpec = { |
| key: string |
| label: string |
| category: string |
| base: number |
| weight: number |
| userWeight: number |
| otherWeight: number |
| riskWeight: number |
| refWeight: number |
| positive: string[] |
| negative: string[] |
| question: string |
| required?: boolean |
| gapType?: 'user-only' | 'searchable' | 'hybrid' |
| } |
|
|
| const universalFactorSpecs: FactorSpec[] = [ |
| { |
| key: 'user_core_need', |
| label: '用户最核心需求', |
| category: '需求画像/必要条件', |
| base: 0.46, |
| weight: 1, |
| userWeight: 1, |
| otherWeight: 0.45, |
| riskWeight: 0.35, |
| refWeight: 1, |
| positive: ['成长', '赚钱', '收入', '稳定', '安全', '自由', '自主权', '履历', '平台', '实战', '家庭压力', '长期', '短期', 'AI产品', '热爱', '想要', '最重要', 'growth', 'money', 'income', 'stability', 'autonomy', 'resume value', 'family pressure', 'long-term', 'short-term'], |
| negative: ['不知道想要什么', '纠结', '都想要', '不确定', 'unclear', 'not sure'], |
| question: '你这次决策最想优先满足什么:成长、赚钱、稳定、自由、履历、关系、时间,还是别的?请按重要性排个顺序。', |
| required: true, |
| gapType: 'user-only', |
| }, |
| { |
| key: 'need_priority_order', |
| label: '需求优先级/取舍顺序', |
| category: '需求画像/必要条件', |
| base: 0.45, |
| weight: 1, |
| userWeight: 1, |
| otherWeight: 0.4, |
| riskWeight: 0.45, |
| refWeight: 1, |
| positive: ['优先', '第一', '更重要', '取舍', '排序', '底线', '可以接受', '不能接受', '宁愿', '相比', 'priority', 'tradeoff', 'prefer', 'rather', 'bottom line'], |
| negative: ['都可以', '没想好', '不知道怎么选', '无法取舍'], |
| question: '如果不能同时满足所有目标,你愿意牺牲什么,绝不能牺牲什么?', |
| required: true, |
| gapType: 'user-only', |
| }, |
| { |
| key: 'time_horizon', |
| label: '短期/长期时间视角', |
| category: '需求画像/必要条件', |
| base: 0.48, |
| weight: 0.94, |
| userWeight: 1, |
| otherWeight: 0.45, |
| riskWeight: 0.5, |
| refWeight: 0.8, |
| positive: ['短期', '长期', '一年', '两年', '三年', '五年', '未来', '现在', '阶段', '职业初期', 'long-term', 'short-term', '1 year', '2 years', '3 years', 'future', 'horizon'], |
| negative: ['只看眼前', '没想过以后'], |
| question: '你更看重未来几个月的确定收益,还是未来一到三年的成长和选择权?', |
| required: true, |
| gapType: 'user-only', |
| }, |
| { |
| key: 'capability_fit', |
| label: '能力/兴趣/资源匹配', |
| category: '需求画像/必要条件', |
| base: 0.5, |
| weight: 0.94, |
| userWeight: 1, |
| otherWeight: 0.55, |
| riskWeight: 0.45, |
| refWeight: 0.75, |
| positive: ['技能', '能力', '擅长', '匹配', '兴趣', '专业', '经验', '资源', '适合', 'skill', 'fit', 'ability', 'interest', 'experience', 'major'], |
| negative: ['不适合', '没经验', '不会', '吃力', '排斥'], |
| question: '这件事和你的技能、兴趣、经验、资源是否匹配?哪些地方你有优势,哪些地方明显短板?', |
| required: true, |
| gapType: 'user-only', |
| }, |
| { |
| key: 'constraint_floor', |
| label: '现实约束/最低底线', |
| category: '需求画像/必要条件', |
| base: 0.47, |
| weight: 0.92, |
| userWeight: 1, |
| otherWeight: 0.45, |
| riskWeight: 0.9, |
| refWeight: 0.75, |
| positive: ['底线', '最低', '房租', '现金流', '家庭', '父母', '贷款', '存款', '不能承受', '最坏结果', 'red line', 'runway', 'cash flow', 'family', 'worst case'], |
| negative: ['没有退路', '承受不了', '撑不住'], |
| question: '你的现实底线是什么:最低收入、最大亏损、最晚时间、家庭压力、最坏结果能不能承受?', |
| required: true, |
| gapType: 'user-only', |
| }, |
| ] |
|
|
| export const legacyScenarioFactorBank: Record<ModuleId, FactorSpec[]> = { |
| trade: [ |
| { key: 'price_gap', label: '价格/可接受区间', category: '必要条件', base: 0.52, weight: 1, userWeight: 1, otherWeight: 0.7, riskWeight: 0.5, refWeight: 1, positive: ['预算', '报价', '心理价', '底价', '区间', '万', '元'], negative: ['差太多', '超预算', '虚高'], question: '双方真实报价、预算、底价和可接受区间分别是多少?', required: true }, |
| { key: 'liquidity_need', label: '交易时间/急迫性', category: '必要条件', base: 0.45, weight: 0.86, userWeight: 0.6, otherWeight: 1, riskWeight: 0.25, refWeight: 0.55, positive: ['急', '月底', '马上', '尽快', '时间', '周期', '交付', '换房', '换新', '周转', '现金流'], negative: ['不急', '慢慢卖'], question: '双方期望交易时间、最晚成交时间和真实急迫性是什么?', required: true }, |
| { key: 'buyer_execution', label: '买方履约能力', category: '履约能力', base: 0.48, weight: 0.9, userWeight: 0.8, otherWeight: 1, riskWeight: 0.55, refWeight: 0.45, positive: ['全款', '贷款批了', '首付', '资金到位', '马上签', '可当天'], negative: ['贷款不确定', '资金紧张', '审批'], question: '你的付款方式、资金到位时间和贷款确定性如何?' }, |
| { key: 'asset_quality', label: '标的质量', category: '信息质量', base: 0.5, weight: 1, userWeight: 1, otherWeight: 0.55, riskWeight: 1, refWeight: 0.9, positive: ['保养记录', '无事故', '房龄新', '设备齐全', '流水真实', '检测'], negative: ['车况', '事故', '水泡', '火烧', '房龄', '漏水', '设备老化', '流水一般'], question: '标的质量有哪些可验证材料或第三方检测?' }, |
| { key: 'title_clearance', label: '权属清晰度', category: '权属/法律事实', base: 0.52, weight: 1, userWeight: 0.9, otherWeight: 0.75, riskWeight: 1, refWeight: 0.85, positive: ['无抵押', '产权清晰', '可过户', '房东同意', '证照齐全'], negative: ['抵押', '查封', '贷款未结清', '房东不同意', '产权不清'], question: '是否存在抵押、查封、未解押、房东不同意或证照不全?' }, |
| { key: 'transfer_friction', label: '交付与验收安排', category: '履约流程', base: 0.5, weight: 0.82, userWeight: 0.75, otherWeight: 0.75, riskWeight: 0.8, refWeight: 0.6, positive: ['过户', '交付', '移交', '交接', '监管', '尾款', '验收', '节点'], negative: ['迁入', '排放', '审批', '拖延', '手续复杂', '交付不清', '移交不清'], question: '交付、验收、尾款和后续交接节点如何安排?' }, |
| { key: 'risk_allocation', label: '风险分配工具', category: '契约设计', base: 0.42, weight: 1, userWeight: 0.9, otherWeight: 0.85, riskWeight: 1, refWeight: 0.75, positive: ['检测', '押金', '尾款', '违约', '监管', '对赌', '退款', '合同'], negative: ['口头', '没合同', '不能检测', '不退'], question: '风险能否用检测、尾款、监管、违约或退款条款分配?' }, |
| { key: 'residual_control', label: '后续控制权/最终拍板权', category: '控制权配置', base: 0.43, weight: 1, userWeight: 0.9, otherWeight: 0.85, riskWeight: 1, refWeight: 0.9, positive: ['最终决定', '书面确认', '共同确认', '验收标准', '监管账户', '第三方', '审批权'], negative: ['口头', '先签', '后面再说', '单方决定', '拒绝写', '说了算'], question: '未来合同没写清或发生争议时,谁拥有最终决定权?哪些事项需要双方共同确认?' }, |
| { key: 'state_control_transfer', label: '状态触发控制权转移', category: '控制权配置', base: 0.42, weight: 0.9, userWeight: 0.86, otherWeight: 0.82, riskWeight: 0.95, refWeight: 0.82, positive: ['触发', '逾期', '不达标', '验收不通过', '暂停', '解除', '转移', '托管'], negative: ['没有退出', '无法停止', '继续付款', '不能暂停'], question: '逾期、不达标、验收失败、付款异常等状态出现时,控制权是否会自动暂停、转移或共同决策?' }, |
| { key: 'market_anchor', label: '当前市场情况', category: '必要条件', base: 0.48, weight: 0.78, userWeight: 0.8, otherWeight: 0.65, riskWeight: 0.35, refWeight: 1, positive: ['市场价', '公允价值', '同小区', '同款', '行情', '均价', '挂牌', '成交价', '租售比', '多个', '多家', '竞争', 'offer'], negative: ['不清楚行情', '没比较'], question: '请补充可查的市场参照:同类标的/岗位/服务的近期成交价、挂牌价、薪酬水平、报价区间、租售比、竞争强度或替代收益。', required: true, gapType: 'searchable' }, |
| { key: 'outside_option', label: '双方替代选择/BATNA', category: '必要条件', base: 0.46, weight: 0.76, userWeight: 0.85, otherWeight: 0.85, riskWeight: 0.35, refWeight: 0.65, positive: ['还有别的', '备选', '可替代', '其他买家', '其他房源', '交易失败', '没有损失', 'BATNA'], negative: ['唯一', '没选择', '只能'], question: '请补充双方替代选择:你还有哪些备选对象/offer/买家/供应商,对方还有哪些替代对象;交易失败双方各自损失什么。', required: true, gapType: 'hybrid' }, |
| { key: 'trust_signal', label: '可信信号', category: '信息质量', base: 0.48, weight: 0.72, userWeight: 0.7, otherWeight: 0.65, riskWeight: 0.75, refWeight: 0.6, positive: ['透明', '记录', '发票', '证明', '可查', '第三方'], negative: ['隐瞒', '含糊', '不透明', '拒绝提供'], question: '关键材料是否透明、可查、可第三方验证?' }, |
| { key: 'post_deal_cost', label: '成交后隐性成本', category: '长期结果', base: 0.5, weight: 0.72, userWeight: 0.9, otherWeight: 0.45, riskWeight: 0.75, refWeight: 0.55, positive: ['保修', '质保', '维修基金', '已保养'], negative: ['维修', '税费', '装修', '年检', '续租', '涨租'], question: '成交后税费、维修、装修、续租或维护成本是多少?' }, |
| { key: 'relationship_continuity', label: '后续协作需要', category: '关系/协作', base: 0.45, weight: 0.58, userWeight: 0.55, otherWeight: 0.55, riskWeight: 0.45, refWeight: 0.45, positive: ['后续配合', '售后', '交接', '协助'], negative: ['一次性', '不配合', '失联'], question: '成交后是否还需要对方协助交接、售后或配合?' }, |
| ], |
| career: [ |
| { key: 'pay_return', label: '薪酬/绩效回报', category: '经济结果', base: 0.5, weight: 1, userWeight: 1, otherWeight: 0.65, riskWeight: 0.75, refWeight: 1, positive: ['涨薪', '奖金', '绩效', '兑现', '明确'], negative: ['没涨薪', '没有涨薪', '绩效没有', '只说以后'], question: '投入是否换来了明确薪酬、绩效或奖金兑现?' }, |
| { key: 'growth_asset', label: '成长资产', category: '长期收益', base: 0.5, weight: 0.98, userWeight: 1, otherWeight: 0.7, riskWeight: 0.35, refWeight: 0.75, positive: ['成长', '核心', '项目', '能力', '学习', '行业资源'], negative: ['重复', '杂活', '学不到'], question: '岗位提供了哪些可迁移能力和行业资源?' }, |
| { key: 'recognition', label: '组织认可', category: '参照点', base: 0.46, weight: 0.85, userWeight: 0.9, otherWeight: 0.7, riskWeight: 0.55, refWeight: 0.95, positive: ['认可', '晋升', '明确', '授权', '核心'], negative: ['口头', '画饼', '不明确', '忽视'], question: '领导认可是否对应到权限、评级、晋升或资源?' }, |
| { key: 'resource_support', label: '资源支持', category: '履约能力', base: 0.48, weight: 0.8, userWeight: 0.8, otherWeight: 0.85, riskWeight: 0.6, refWeight: 0.6, positive: ['预算', '人手', '资源', '支持', '授权'], negative: ['资源少', '没人', '没预算', '孤军'], question: '组织给了多少预算、人手、授权和跨部门支持?' }, |
| { key: 'residual_control', label: '后续控制权/最终拍板权', category: '控制权配置', base: 0.42, weight: 1, userWeight: 0.95, otherWeight: 0.75, riskWeight: 1, refWeight: 0.95, positive: ['最终决定权', '授权', '拍板', '否决权', '负责人', '汇报线', '决策权', '书面确认'], negative: ['只负责', '没权限', '无授权', '领导拍板', '背锅', '口头', '不明确'], question: '你承担责任后,预算、人事、技术路线、跨部门协调或结果验收的最终拍板权归谁?' }, |
| { key: 'state_control_transfer', label: '状态触发控制权转移', category: '控制权配置', base: 0.42, weight: 0.9, userWeight: 0.92, otherWeight: 0.7, riskWeight: 0.95, refWeight: 0.85, positive: ['目标调整', '升级机制', '暂停', '复盘', '阶段目标', '不达标', '授权升级'], negative: ['目标不变', '资源不变', '责任全背', '无法升级', '不能调整'], question: '当资源不到位、目标变化、跨部门不配合时,责任和控制权是否能重新分配?' }, |
| { key: 'workload_cost', label: '工作负荷/身心成本', category: '成本损耗', base: 0.58, weight: 0.95, userWeight: 1, otherWeight: 0.35, riskWeight: 1, refWeight: 0.75, positive: ['可控', '稳定', '弹性'], negative: ['加班', '消耗', '身体', '压力', '熬夜', '焦虑'], question: '损耗是短期波动,还是长期稳定常态?' }, |
| { key: 'promotion_path', label: '晋升路径清晰度', category: '参照点', base: 0.45, weight: 0.85, userWeight: 0.9, otherWeight: 0.65, riskWeight: 0.55, refWeight: 1, positive: ['晋升时间表', '路径', '标准', '明确'], negative: ['不清晰', '以后', '再说'], question: '晋升标准、时间表和负责人是否明确?' }, |
| { key: 'outside_market', label: '外部机会', category: '外部选择', base: 0.48, weight: 0.8, userWeight: 0.9, otherWeight: 0.45, riskWeight: 0.4, refWeight: 0.65, positive: ['跳槽', '面试', 'offer', '外部机会', '市场'], negative: ['没机会', '不好找'], question: '外部机会在薪酬、成长和确定性上是否更好?' }, |
| { key: 'political_risk', label: '组织政治/背责风险', category: '风险', base: 0.56, weight: 0.78, userWeight: 0.85, otherWeight: 0.45, riskWeight: 0.95, refWeight: 0.65, positive: ['责任清晰', '流程清楚'], negative: ['背锅', '甩锅', '权责不清', '抢功', '内耗'], question: '功劳、责任、权限是否对等和清晰?' }, |
| { key: 'manager_contract', label: '领导契约稳定性', category: '关系契约', base: 0.48, weight: 0.76, userWeight: 0.75, otherWeight: 0.7, riskWeight: 0.7, refWeight: 0.85, positive: ['稳定', '兑现', '信任', '支持'], negative: ['变卦', '画饼', '压榨', '反复'], question: '领导过去承诺的兑现率如何?' }, |
| { key: 'role_fit', label: '角色/能力匹配度', category: '匹配', base: 0.5, weight: 0.75, userWeight: 0.8, otherWeight: 0.8, riskWeight: 0.35, refWeight: 0.5, positive: ['擅长', '匹配', '兴趣', '优势', '适配'], negative: ['不适合', '痛苦', '排斥', '不匹配'], question: '这份安排与你的能力、资源、兴趣和长期方向匹配吗?' }, |
| ], |
| partner: [ |
| { key: 'resource_reciprocity', label: '资源互惠', category: '经济结果', base: 0.48, weight: 1, userWeight: 1, otherWeight: 0.85, riskWeight: 0.45, refWeight: 0.85, positive: ['客户', '资源', '帮助', '回馈', '互相'], negative: ['只索取', '不回馈', '占便宜'], question: '对方实际贡献了哪些可验证资源,频率是否稳定?' }, |
| { key: 'profit_fairness', label: '收益分配公平', category: '参照点', base: 0.5, weight: 1, userWeight: 1, otherWeight: 0.85, riskWeight: 0.8, refWeight: 1, positive: ['分配透明', '规则', '按贡献', '结算'], negative: ['不透明', '少分', '拖款', '抢利益'], question: '收益分配规则是否提前说清并稳定执行?' }, |
| { key: 'commitment_reliability', label: '承诺兑现率', category: '履约能力', base: 0.48, weight: 0.95, userWeight: 0.9, otherWeight: 0.85, riskWeight: 0.9, refWeight: 0.95, positive: ['兑现', '准时', '稳定', '说到做到'], negative: ['变更', '拖', '临时', '反复', '失约'], question: '承诺落空是偶发,还是已经成为合作模式?' }, |
| { key: 'responsibility_sharing', label: '责任共担', category: '风险', base: 0.52, weight: 0.95, userWeight: 1, otherWeight: 0.75, riskWeight: 1, refWeight: 0.85, positive: ['一起承担', '共担', '对外一起', '负责'], negative: ['背锅', '推责任', '让我解释', '甩锅'], question: '风险和责任是否长期由你单方面承担?' }, |
| { key: 'information_transparency', label: '信息透明', category: '信息质量', base: 0.48, weight: 0.85, userWeight: 0.9, otherWeight: 0.75, riskWeight: 0.85, refWeight: 0.8, positive: ['透明', '同步', '公开', '可查'], negative: ['隐瞒', '不说', '不透明', '信息差'], question: '关键客户、成本、收益、风险信息是否透明?' }, |
| { key: 'control_dependency', label: '关键依赖风险', category: '外部选择', base: 0.52, weight: 0.75, userWeight: 0.85, otherWeight: 0.45, riskWeight: 0.9, refWeight: 0.55, positive: ['可替代', '备选', '可退出'], negative: ['离不开', '绑定', '唯一', '被卡'], question: '你是否过度依赖对方的客户、渠道、账户或关键资源?' }, |
| { key: 'residual_control', label: '后续控制权/最终拍板权', category: '控制权配置', base: 0.4, weight: 1, userWeight: 1, otherWeight: 0.9, riskWeight: 1, refWeight: 1, positive: ['共同决策', '否决权', '账号归属', '客户归属', '数据归属', 'IP归属', '最终决定权', '董事会', '投票权'], negative: ['单方决定', '对方说了算', '账号被卡', '客户被抢', '无否决权', '控制在对方'], question: '合同未覆盖的新情况出现时,账号、客户、数据、IP、预算和方向的最终控制权归谁?' }, |
| { key: 'state_control_transfer', label: '状态触发控制权转移', category: '控制权配置', base: 0.42, weight: 0.9, userWeight: 0.92, otherWeight: 0.88, riskWeight: 0.95, refWeight: 0.9, positive: ['退出机制', '回购', '清算', '不达标', '违约', '触发', '暂停合作', '转让'], negative: ['无法退出', '长期绑定', '没有退出', '不清算', '继续投入'], question: '一方不投入、违约、收益不达标或合作破裂时,控制权如何回收、转移或清算?' }, |
| { key: 'long_term_stability', label: '长期稳定性', category: '长期结果', base: 0.48, weight: 0.82, userWeight: 0.82, otherWeight: 0.82, riskWeight: 0.65, refWeight: 0.7, positive: ['一年', '半年', '长期', '稳定'], negative: ['反复', '波动', '临时', '突然'], question: '这种合作模式持续了多久,稳定性如何?' }, |
| { key: 'boundary_clarity', label: '边界清晰度', category: '契约设计', base: 0.44, weight: 0.78, userWeight: 0.85, otherWeight: 0.75, riskWeight: 0.8, refWeight: 0.75, positive: ['合同', '规则', '边界', '节点', '清楚'], negative: ['口头', '模糊', '没说清'], question: '贡献、责任、收益和退出边界是否说清?' }, |
| { key: 'reputation_signal', label: '信誉信号', category: '信息质量', base: 0.48, weight: 0.68, userWeight: 0.7, otherWeight: 0.7, riskWeight: 0.55, refWeight: 0.45, positive: ['口碑', '信誉', '介绍', '记录'], negative: ['投诉', '失信', '骗', '黑历史'], question: '对方过往合作口碑和履约记录如何?' }, |
| ], |
| } |
|
|
| const contractCoreFactorSpecs: FactorSpec[] = [ |
| { key: 'price_gap', label: '价格/成本可接受区间', category: '必要条件', base: 0.52, weight: 1, userWeight: 1, otherWeight: 0.7, riskWeight: 0.5, refWeight: 1, positive: ['预算', '报价', '价格', '费用', '成本', '底价', '区间', '万', '元', '薪酬', '分成', '佣金'], negative: ['差太多', '超预算', '虚高', '过低', '压价'], question: '双方真实价格、成本、预算、底价和可接受区间分别是多少?', required: true }, |
| { key: 'liquidity_need', label: '时间/期限/急迫性', category: '必要条件', base: 0.45, weight: 0.86, userWeight: 0.65, otherWeight: 1, riskWeight: 0.3, refWeight: 0.55, positive: ['急', '月底', '马上', '尽快', '时间', '周期', '期限', '交付', '上线', '截止', '现金流', '周转'], negative: ['不急', '慢慢', '可延期'], question: '双方期望时间、最晚期限、交付周期和真实急迫性是什么?', required: true }, |
| { key: 'counterparty_execution', label: '对方履约能力', category: '履约能力', base: 0.48, weight: 0.92, userWeight: 0.82, otherWeight: 1, riskWeight: 0.62, refWeight: 0.55, positive: ['资金到位', '案例', '团队', '项目经理', '资源', '证据', '记录', '可验证', '正规', '合同'], negative: ['没案例', '不确定', '资金紧张', '人手不足', '口头', '拖延', '不透明'], question: '对方是否有可验证的资金、能力、团队、案例或履约记录?' }, |
| { key: 'subject_quality', label: '标的/方案质量', category: '信息质量', base: 0.5, weight: 1, userWeight: 1, otherWeight: 0.55, riskWeight: 1, refWeight: 0.85, positive: ['质量', '成熟', '清单', '方案', '检测', '验收', '样品', 'demo', '案例', '标准'], negative: ['模糊', '不清楚', '质量风险', '缺陷', '返工', '不成熟'], question: '这份标的、方案、服务或安排的质量如何验证?' }, |
| { key: 'ownership_clearance', label: '权属/归属清晰度', category: '权属/归属', base: 0.52, weight: 1, userWeight: 0.92, otherWeight: 0.75, riskWeight: 1, refWeight: 0.85, positive: ['归属', '所有权', '知识产权', 'IP', '账号', '数据', '代码', '客户', '产权清晰', '授权'], negative: ['归属不清', '无授权', '被卡', '抢客户', '代码失控', '数据失控', '产权不清'], question: '关键资产、账号、数据、客户、代码、知识产权或标的权属归谁?' }, |
| { key: 'transfer_friction', label: '交付与验收安排', category: '履约流程', base: 0.5, weight: 0.82, userWeight: 0.75, otherWeight: 0.75, riskWeight: 0.8, refWeight: 0.6, positive: ['交付', '移交', '交接', '监管', '尾款', '验收', '节点', '里程碑', '过户'], negative: ['审批', '拖延', '手续复杂', '交付不清', '移交不清', '验收失败'], question: '交付、验收、尾款和后续交接节点如何安排?' }, |
| { key: 'risk_allocation', label: '风险分配工具', category: '契约设计', base: 0.42, weight: 1, userWeight: 0.9, otherWeight: 0.85, riskWeight: 1, refWeight: 0.75, positive: ['检测', '押金', '尾款', '违约', '监管', '对赌', '退款', '合同', '验收', '暂停', '解除'], negative: ['口头', '没合同', '不退', '无违约', '无法暂停', '无法退出'], question: '风险能否用检测、尾款、监管、违约、退款、暂停或解除条款分配?' }, |
| { key: 'residual_control', label: '后续控制权/最终拍板权', category: '控制权配置', base: 0.43, weight: 1, userWeight: 0.92, otherWeight: 0.85, riskWeight: 1, refWeight: 0.9, positive: ['最终决定', '书面确认', '共同确认', '验收标准', '审批权', '否决权', '拍板', '授权'], negative: ['口头', '先签', '后面再说', '单方决定', '拒绝写', '说了算', '没权限'], question: '未来合同没写清或发生争议时,谁拥有最终决定权?哪些事项需要双方共同确认?' }, |
| { key: 'state_control_transfer', label: '状态触发控制权转移', category: '控制权配置', base: 0.42, weight: 0.9, userWeight: 0.88, otherWeight: 0.82, riskWeight: 0.95, refWeight: 0.82, positive: ['触发', '逾期', '不达标', '验收不通过', '暂停', '解除', '转移', '托管', '退出'], negative: ['没有退出', '无法停止', '继续付款', '不能暂停', '长期绑定'], question: '逾期、不达标、验收失败、付款异常或不投入时,控制权是否会暂停、转移或共同决策?' }, |
| { key: 'market_anchor', label: '当前市场情况', category: '必要条件', base: 0.48, weight: 0.78, userWeight: 0.8, otherWeight: 0.65, riskWeight: 0.35, refWeight: 1, positive: ['市场价', '公允价值', '行情', '均价', '成交价', '报价区间', '多个', '多家', '竞争', 'offer', '同类'], negative: ['不清楚行情', '没比较'], question: '请补充可查的市场参照:同类标的、服务、岗位、资源或方案的近期价格、成交条件、竞争强度或替代收益。', required: true, gapType: 'searchable' }, |
| { key: 'outside_option', label: '双方替代选择/BATNA', category: '必要条件', base: 0.46, weight: 0.76, userWeight: 0.85, otherWeight: 0.85, riskWeight: 0.35, refWeight: 0.65, positive: ['还有别的', '备选', '可替代', '替代', '交易失败', '合作失败', '没有损失', 'BATNA'], negative: ['唯一', '没选择', '只能'], question: '双方各自还有哪些替代对象、替代方案或退出选择?如果不成交/不合作,各自损失什么?', required: true, gapType: 'hybrid' }, |
| { key: 'trust_signal', label: '可信信号', category: '信息质量', base: 0.48, weight: 0.72, userWeight: 0.7, otherWeight: 0.65, riskWeight: 0.75, refWeight: 0.6, positive: ['透明', '记录', '发票', '证明', '可查', '第三方', '合同', '案例', '介绍'], negative: ['隐瞒', '含糊', '不透明', '拒绝提供', '只口头'], question: '关键材料、承诺、履约记录是否透明、可查、可第三方验证?' }, |
| { key: 'post_deal_cost', label: '签约/履约后隐性成本', category: '长期结果', base: 0.5, weight: 0.72, userWeight: 0.9, otherWeight: 0.45, riskWeight: 0.75, refWeight: 0.55, positive: ['保修', '质保', '维护', '售后', '续约', '长期服务'], negative: ['维修', '税费', '维护费', '续费', '涨价', '返工', '额外费用'], question: '签约或履约后还有哪些维护、返工、续费、税费、迁移或协作成本?' }, |
| { key: 'relationship_continuity', label: '后续协作需要', category: '关系/协作', base: 0.45, weight: 0.58, userWeight: 0.55, otherWeight: 0.55, riskWeight: 0.45, refWeight: 0.45, positive: ['后续配合', '售后', '交接', '协助', '长期维护', '持续合作'], negative: ['一次性', '不配合', '失联', '被卡'], question: '成交或签约后是否还需要对方协助交接、售后、维护或长期配合?' }, |
| ] |
|
|
| const complianceChecklist: Record<ModuleId, { item: string; positive: string[]; negative: string[]; question: string }[]> = { |
| trade: [ |
| { item: '合同主体与身份真实', positive: ['身份证', '主体', '本人', '授权', '委托书', '营业执照'], negative: ['代签', '不露面', '身份不明'], question: '交易双方主体、身份、授权文件是否真实完整?' }, |
| { item: '权属/处分权清晰', positive: ['产权清晰', '无抵押', '无查封', '可过户', '房东同意', '证照齐全'], negative: ['抵押', '查封', '贷款未结清', '产权不清', '无权处分'], question: '标的是否存在抵押、查封、未解押、共有权人不同意或无权处分?' }, |
| { item: '标的描述明确', positive: ['面积', '房号', '车架号', '里程', '设备清单', '附属设施'], negative: ['模糊', '口头', '说不清'], question: '标的物位置、编号、面积/型号、附属物和交付状态是否写清?' }, |
| { item: '价款与付款节点明确', positive: ['定金', '首付', '尾款', '监管', '付款节点', '价格'], negative: ['口头付款', '私下转账', '不写'], question: '价款、定金、首付、尾款、资金监管和付款节点是否明确?' }, |
| { item: '交付/过户/验收节点明确', positive: ['交付', '过户', '验收', '检测', '交接', '节点'], negative: ['拖延', '不配合', '无法过户'], question: '交付、过户、验收、检测和资料移交的时间节点是否明确?' }, |
| { item: '违约责任与解除条件明确', positive: ['违约', '赔偿', '退款', '解除', '逾期', '定金罚则'], negative: ['没违约', '不退', '口头承诺'], question: '逾期、无法过户、质量问题、资金不到位时的违约责任和解除条件是什么?' }, |
| { item: '强制性规定/限购限贷/税费合规', positive: ['限购', '贷款', '税费', '资质', '备案', '合规'], negative: ['规避', '阴阳合同', '逃税', '借名'], question: '是否涉及限购限贷、税费、备案、资质、阴阳合同或借名交易风险?' }, |
| { item: '证据留存与争议解决', positive: ['书面', '聊天记录', '录音', '收据', '发票', '争议解决', '管辖'], negative: ['只口头', '无凭证'], question: '关键沟通、付款凭证、验收证据、争议解决和管辖条款是否留存?' }, |
| ], |
| career: [ |
| { item: '劳动/合作主体清晰', positive: ['劳动合同', '公司', '主体', '岗位'], negative: ['外包不明', '主体不清'], question: '用工主体、岗位、汇报关系是否清晰?' }, |
| { item: '薪酬绩效书面明确', positive: ['薪酬', '绩效', '奖金', '提成', '书面'], negative: ['口头', '画饼'], question: '薪酬、奖金、绩效、提成和晋升承诺是否有书面依据?' }, |
| { item: '工时加班与休假合规', positive: ['加班费', '调休', '工时', '休假'], negative: ['长期加班', '无加班费'], question: '工时、加班、调休、休假是否符合劳动规则和公司制度?' }, |
| { item: '权责边界与成果归属', positive: ['职责', '权限', '成果', '归属', '授权'], negative: ['权责不清', '背锅'], question: '职责、权限、成果归属和责任边界是否明确?' }, |
| { item: '离职/竞业/保密风险', positive: ['保密', '竞业', '离职', '交接'], negative: ['限制不清', '违约金'], question: '是否涉及保密、竞业限制、违约金、离职交接或知识产权归属?' }, |
| ], |
| partner: [ |
| { item: '合作主体与授权', positive: ['合同', '主体', '授权', '公司', '个人'], negative: ['代签', '主体不清'], question: '合作主体、授权和签约身份是否明确?' }, |
| { item: '贡献/分工/交付物明确', positive: ['分工', '交付', '节点', '贡献', '责任'], negative: ['口头', '模糊'], question: '各方贡献、分工、交付物和验收节点是否明确?' }, |
| { item: '收益分配与结算规则明确', positive: ['分配', '结算', '比例', '账期', '透明'], negative: ['不透明', '拖款'], question: '收益分配、成本承担、结算周期和账目透明规则是否明确?' }, |
| { item: '风险承担和违约退出机制', positive: ['违约', '退出', '赔偿', '止损', '解除'], negative: ['背锅', '无法退出'], question: '违约、亏损、责任承担、退出和清算机制是否明确?' }, |
| { item: '知识产权/客户资源/数据归属', positive: ['知识产权', '客户', '数据', '账号', '归属'], negative: ['抢客户', '账号被卡'], question: '客户资源、账号、数据、知识产权和后续使用权归谁?' }, |
| { item: '证据留存与争议解决', positive: ['书面', '记录', '收据', '管辖', '仲裁'], negative: ['只口头', '无凭证'], question: '关键沟通、交付证据、账目和争议解决条款是否留存?' }, |
| ], |
| } |
|
|
| function clamp(value: number, min = 0, max = 1) { |
| return Math.min(max, Math.max(min, value)) |
| } |
|
|
| function round(value: number, digits = 3) { |
| const base = 10 ** digits |
| return Math.round(value * base) / base |
| } |
|
|
| function findEvidence(text: string, words: string[]) { |
| return words.filter((word) => text.includes(word)) |
| } |
|
|
| function extractNumbers(text: string) { |
| return [...text.matchAll(/(\d+(?:\.\d+)?)/g)].map((match) => Number(match[1])).filter(Number.isFinite) |
| } |
|
|
| function softmax(values: number[]) { |
| const max = Math.max(...values) |
| const exps = values.map((value) => Math.exp(value - max)) |
| const sum = exps.reduce((total, value) => total + value, 0) |
| return exps.map((value) => value / sum) |
| } |
|
|
| function weightedAverage(features: EngineFeature[], weightSelector: (factor: EngineFeature) => number) { |
| const totalWeight = features.reduce((sum, item) => sum + weightSelector(item), 0) |
| if (totalWeight === 0) return 0.5 |
| return features.reduce((sum, item) => sum + item.value * weightSelector(item), 0) / totalWeight |
| } |
|
|
| function normalizeWeights<T extends Record<string, number>>(weights: T): T { |
| const total = Object.values(weights).reduce((sum, value) => sum + Math.max(value, 0.0001), 0) |
| return Object.fromEntries(Object.entries(weights).map(([key, value]) => [key, round(Math.max(value, 0.0001) / total)])) as T |
| } |
|
|
| function bucketImportance(features: EngineFeature[], predicate: (item: EngineFeature) => boolean) { |
| const bucket = features.filter(predicate) |
| if (bucket.length === 0) return 0.5 |
| return round(clamp(bucket.reduce((sum, item) => sum + item.weight * (0.55 + item.confidence * 0.45), 0) / bucket.length)) |
| } |
|
|
| function deriveContractFormulaWeights(features: EngineFeature[]) { |
| const textOf = (item: EngineFeature) => featureText(item) |
| const needImportance = bucketImportance(features, (item) => /需求|匹配|收益|benefit|matching|need|utility|价值/i.test(textOf(item))) |
| const riskImportance = bucketImportance(features, (item) => /风险|损失|底线|下行|不确定|risk|loss|floor|uncertain|downside/i.test(textOf(item))) |
| const controlImportance = bucketImportance(features, isControlFeature) |
| const reliabilityImportance = bucketImportance(features, isReliabilityFeature) |
| const referenceImportance = bucketImportance(features, (item) => /参照|公允|市场|预期|reference|market|anchor|expectation/i.test(textOf(item))) |
| const surplusImportance = bucketImportance(features, (item) => /共同|剩余|互补|双赢|surplus|win|reciprocal|互惠/i.test(textOf(item))) |
|
|
| return { |
| matching: normalizeWeights({ |
| nash: 0.32 * (0.72 + needImportance * 0.56), |
| reference: 0.2 * (0.72 + referenceImportance * 0.56), |
| surplus: 0.16 * (0.72 + surplusImportance * 0.56), |
| outsideOption: 0.12, |
| antiHoldUp: 0.1 * (0.72 + riskImportance * 0.56), |
| control: 0.1 * (0.72 + controlImportance * 0.56), |
| }), |
| holdUp: normalizeWeights({ |
| risk: 0.38 * (0.72 + riskImportance * 0.56), |
| gap: 0.2 * (0.72 + referenceImportance * 0.56), |
| missing: 0.16, |
| control: 0.14 * (0.72 + controlImportance * 0.56), |
| reliability: 0.12 * (0.72 + reliabilityImportance * 0.56), |
| }), |
| score: normalizeWeights({ |
| winWin: 0.42 * (0.72 + surplusImportance * 0.56), |
| matching: 0.24 * (0.72 + needImportance * 0.56), |
| reference: 0.16 * (0.72 + referenceImportance * 0.56), |
| coverage: 0.1, |
| surplus: 0.08 * (0.72 + surplusImportance * 0.56), |
| }), |
| } |
| } |
|
|
| function scoreFactor(spec: FactorSpec, text: string, moduleId: ModuleId): EngineFeature { |
| const positive = findEvidence(text, spec.positive) |
| const negative = findEvidence(text, spec.negative) |
| const observed = positive.length + negative.length |
| let value = spec.base + positive.length * 0.105 - negative.length * 0.13 |
|
|
| if (moduleId === 'trade' && spec.key === 'price_gap') { |
| const numbers = extractNumbers(text) |
| if (numbers.length >= 2) { |
| const gap = Math.abs(numbers[0] - numbers[1]) / Math.max(numbers[0], numbers[1], 1) |
| value = 1 - gap * 2.2 |
| } |
| } |
|
|
| if (moduleId === 'trade' && spec.key === 'post_deal_cost' && negative.length === 0) { |
| value -= 0.05 |
| } |
|
|
| if (moduleId === 'career' && spec.key === 'workload_cost') { |
| value = spec.base + positive.length * 0.09 - negative.length * 0.16 |
| } |
|
|
| if (moduleId === 'partner' && spec.key === 'control_dependency') { |
| value = spec.base + positive.length * 0.11 - negative.length * 0.15 |
| } |
|
|
| const confidence = clamp(0.28 + observed * 0.18 + Math.min(text.length / 360, 0.28)) |
|
|
| return { |
| key: spec.key, |
| label: spec.label, |
| category: spec.category, |
| source: 'base', |
| value: round(clamp(value)), |
| confidence: round(confidence), |
| weight: spec.weight, |
| evidence: [...positive, ...negative].length > 0 ? [...positive, ...negative] : ['未显式出现,保留先验并计入信息缺口'], |
| missing: observed === 0, |
| } |
| } |
|
|
| function normalizeDynamicFactor(input: DynamicFactorInput, index: number): EngineFeature { |
| const safeLabel = input.label || `开放参数 ${index + 1}` |
| const safeCategory = input.category || '开放因素' |
| return { |
| key: input.key || `open_${index + 1}_${safeLabel.slice(0, 12)}`, |
| label: safeLabel, |
| category: safeCategory, |
| source: 'open', |
| value: round(clamp(Number(input.value))), |
| confidence: round(clamp(Number(input.confidence || 0.62))), |
| weight: round(clamp(Number(input.weight || 0.68), 0.1, 1)), |
| evidence: input.evidence && input.evidence.length > 0 ? input.evidence : ['开放参数抽取器识别'], |
| missing: false, |
| } |
| } |
|
|
| function dynamicWeight(input: DynamicFactorInput | undefined, kind: 'user' | 'other' | 'risk' | 'ref') { |
| if (!input) return 1 |
| if (kind === 'user') return input.userWeight ?? 1 |
| if (kind === 'other') return input.otherWeight ?? 1 |
| if (kind === 'risk') return input.riskWeight ?? 0.65 |
| return input.refWeight ?? 0.85 |
| } |
|
|
| function featureText(item: EngineFeature) { |
| return `${item.key} ${item.label} ${item.category} ${item.evidence.join(' ')}` |
| } |
|
|
| function isControlFeature(item: EngineFeature) { |
| return /控制权|理论2|拍板|决定权|否决权|验收权|退出权|暂停|解除|转移|托管|授权|权限|归属|账号|数据|代码|IP|知识产权|付款节点|尾款|交付|验收|职责边界|资源调度|最终决定|共同确认|control|rights|decision|veto|approval|ownership|authority|permission|handover|acceptance|payment|milestone|escrow|exit|terminate|pause/i.test(featureText(item)) |
| } |
|
|
| function isStateTriggerFeature(item: EngineFeature) { |
| return /状态|触发|逾期|不达标|未达标|验收不通过|验收失败|验收不合格|付款异常|资源不到位|融资失败|融资|现金流|资金|无法转岗|无法转AI|不能保证|不保证|职责扩大|职责泛化|延期|暂停|解除|退出|退款|退货|尾款|里程碑|cliff|vesting|回购|清算|转移|共同决策|state|trigger|contingency|delay|overdue|fail|failure|refund|return|vesting|buyback|liquidation|fallback|walkaway|funding|financing|runway|cashflow|cannot guarantee|not guaranteed/i.test(featureText(item)) |
| } |
|
|
| function isReliabilityFeature(item: EngineFeature) { |
| return /理论4|可信|履约|信任|信誉|证据|案例|书面|合同|透明|可查|第三方|发票|授权|质保|SLA|验收标准|项目经理|正规|历史|记录|资金|融资|现金流|口头|画饼|不确定|拒绝写|不透明|无案例|没案例|reliability|trust|credible|evidence|case|written|contract|transparent|verifiable|third|invoice|warranty|runway|funding|financing|verbal|uncertain|refuse/i.test(featureText(item)) |
| } |
|
|
| function buildControlRights(features: EngineFeature[], specs: FactorSpec[]) { |
| const controlFeatures = features.filter(isControlFeature) |
| const controlSignalFeatures = controlFeatures.filter((item) => item.source === 'open' || !item.missing) |
| const controlBasis = controlSignalFeatures.length > 0 ? controlSignalFeatures : controlFeatures |
| const controlAlignment = round(controlBasis.length > 0 ? weightedAverage(controlBasis, (item) => item.weight * (0.7 + item.confidence * 0.3)) : 0.42) |
| const residualControlRisk = round(clamp(1 - controlAlignment)) |
| const specificInvestment = round( |
| weightedAverage( |
| features.filter((item) => /专用|资产|投入|资源|客户|数据|账号|技术|装修|项目|成果|成长|品牌|股权|IP|知识产权/.test(`${item.label}${item.category}${item.evidence.join('')}`)), |
| (item) => item.weight * (0.7 + item.confidence * 0.3), |
| ), |
| ) |
| const investmentIncentiveFit = round(clamp(0.58 * controlAlignment + 0.42 * (1 - Math.abs(specificInvestment - controlAlignment)))) |
| const stateControlFeatures = features.filter((item) => item.key === 'state_control_transfer' || isStateTriggerFeature(item)) |
| const stateSignalFeatures = stateControlFeatures.filter((item) => item.source === 'open' || !item.missing) |
| const stateBasis = stateSignalFeatures.length > 0 ? stateSignalFeatures : stateControlFeatures |
| const stateContingencyReadiness = round( |
| stateBasis.length > 0 |
| ? weightedAverage(stateBasis, (item) => item.weight * (0.65 + item.confidence * 0.35)) |
| : 0.35, |
| ) |
| const missingControlQuestions = specs |
| .filter((spec) => spec.category.includes('控制权')) |
| .filter((spec) => !features.some((item) => item.key === spec.key && !item.missing)) |
| .map((spec) => spec.question) |
| const uniqueMissingControlQuestions = [...new Set(missingControlQuestions)].slice(0, 3) |
| const recommendedAllocation = residualControlRisk >= 0.58 |
| ? '存在后续被锁定风险:凡涉及付款、验收、账号、客户、数据、IP、预算、方向、资源投入和责任承担的事项,都需要分项配置控制权,并设置逾期、不达标、违约或不投入时的暂停、转移、解除和退出机制。' |
| : stateContingencyReadiness < 0.5 |
| ? '基础控制权可谈,但状态变化机制不足:需要补充逾期、不达标、验收失败、资源不到位时的暂停、转移、解除或共同决策条款。' |
| : '控制权配置相对可接受:继续把关键事项写成分项决策权、触发条件和证据留存。' |
|
|
| return { |
| controlAlignment, |
| residualControlRisk, |
| investmentIncentiveFit, |
| stateContingencyReadiness, |
| recommendedAllocation, |
| missingControlQuestions: uniqueMissingControlQuestions, |
| } |
| } |
|
|
| function buildBayesianReliability(text: string, features: EngineFeature[] = []) { |
| const prior = 0.52 |
| const positiveSignals = [ |
| { word: '书面', likelihood: 1.45 }, |
| { word: '透明', likelihood: 1.35 }, |
| { word: '可查', likelihood: 1.28 }, |
| { word: '第三方', likelihood: 1.28 }, |
| { word: '资金到位', likelihood: 1.38 }, |
| { word: '贷款已预批', likelihood: 1.32 }, |
| { word: '定金', likelihood: 1.22 }, |
| { word: '共同确认', likelihood: 1.3 }, |
| { word: '明确', likelihood: 1.22 }, |
| { word: '按时', likelihood: 1.3 }, |
| { word: '兑现', likelihood: 1.36 }, |
| { word: '后台数据', likelihood: 1.28 }, |
| { word: '愿意写', likelihood: 1.42 }, |
| { word: '验收标准', likelihood: 1.24 }, |
| { word: '违约责任', likelihood: 1.2 }, |
| { word: 'written', likelihood: 1.42 }, |
| { word: 'transparent', likelihood: 1.32 }, |
| { word: 'verifiable', likelihood: 1.28 }, |
| { word: 'third party', likelihood: 1.26 }, |
| { word: 'preapproved', likelihood: 1.32 }, |
| { word: 'deposit', likelihood: 1.22 }, |
| { word: 'clear terms', likelihood: 1.3 }, |
| { word: 'acceptance criteria', likelihood: 1.24 }, |
| { word: 'penalty clause', likelihood: 1.2 }, |
| ] |
| const negativeSignals = [ |
| { word: '口头', likelihood: 0.66 }, |
| { word: '画饼', likelihood: 0.58 }, |
| { word: '不透明', likelihood: 0.52 }, |
| { word: '拒绝写', likelihood: 0.42 }, |
| { word: '后面再说', likelihood: 0.55 }, |
| { word: '先签', likelihood: 0.62 }, |
| { word: '拖延', likelihood: 0.65 }, |
| { word: '变卦', likelihood: 0.48 }, |
| { word: '反复', likelihood: 0.6 }, |
| { word: '结算慢', likelihood: 0.58 }, |
| { word: '无承诺', likelihood: 0.56 }, |
| { word: '没有承诺', likelihood: 0.56 }, |
| { word: '不愿提供', likelihood: 0.5 }, |
| { word: '最终决策都由他', likelihood: 0.5 }, |
| { word: '单方决定', likelihood: 0.48 }, |
| { word: '账号归属没有写清', likelihood: 0.5 }, |
| { word: 'verbal', likelihood: 0.62 }, |
| { word: 'refuses to write', likelihood: 0.42 }, |
| { word: 'refuse to write', likelihood: 0.42 }, |
| { word: 'later', likelihood: 0.6 }, |
| { word: 'not transparent', likelihood: 0.52 }, |
| { word: 'delayed settlement', likelihood: 0.58 }, |
| { word: 'unclear ownership', likelihood: 0.5 }, |
| { word: 'single-sided decision', likelihood: 0.48 }, |
| { word: 'no veto right', likelihood: 0.52 }, |
| ] |
| const hasPositiveSignal = (word: string) => { |
| if (!text.includes(word)) return false |
| if (word === '透明' && text.includes('不透明')) return false |
| if (word === '书面' && /拒绝.{0,4}书面|不愿.{0,4}书面/.test(text)) return false |
| if (text.includes(`not ${word}`) || text.includes(`no ${word}`) || text.includes(`without ${word}`)) return false |
| return true |
| } |
|
|
| let odds = prior / (1 - prior) |
| const positiveEvidence: string[] = [] |
| const negativeEvidence: string[] = [] |
|
|
| for (const signal of positiveSignals) { |
| if (hasPositiveSignal(signal.word)) { |
| odds *= signal.likelihood |
| positiveEvidence.push(signal.word) |
| } |
| } |
|
|
| for (const signal of negativeSignals) { |
| if (text.includes(signal.word)) { |
| odds *= signal.likelihood |
| negativeEvidence.push(signal.word) |
| } |
| } |
|
|
| for (const item of features.filter(isReliabilityFeature)) { |
| const strength = item.weight * (0.55 + item.confidence * 0.45) |
| const evidenceLabel = item.evidence[0] || item.label |
| if (item.value >= 0.62) { |
| odds *= 1 + (item.value - 0.5) * strength |
| positiveEvidence.push(evidenceLabel) |
| } else if (item.value <= 0.42) { |
| odds *= Math.max(0.35, 1 - (0.5 - item.value) * strength * 1.45) |
| negativeEvidence.push(evidenceLabel) |
| } |
| } |
|
|
| const posterior = round(clamp(odds / (1 + odds))) |
| const reliabilityLevel = posterior >= 0.68 ? 'high' : posterior >= 0.45 ? 'medium' : 'low' |
| const interpretation = reliabilityLevel === 'high' |
| ? '对方可信履约信号较强,可以继续推进,但仍需把关键承诺写成可验证条件。' |
| : reliabilityLevel === 'medium' |
| ? '对方可信履约能力尚未充分验证,适合继续谈,但必须用书面条件、验收标准和退出机制降低不确定性。' |
| : '对方可信履约信号偏弱,不宜依赖口头承诺,应先要求书面确认、履约担保或降低投入。' |
|
|
| return { |
| prior, |
| posterior, |
| evidenceCount: positiveEvidence.length + negativeEvidence.length, |
| positiveEvidence, |
| negativeEvidence, |
| reliabilityLevel, |
| interpretation, |
| } as const |
| } |
|
|
| function buildSuccessPanel(input: { |
| matchingEquilibrium: number |
| totalSurplus: number |
| outsideOptionPressure: number |
| coverage: number |
| holdUpRisk: number |
| expectationGap: number |
| downsideProbability: number |
| controlAlignment: number |
| stateContingencyReadiness: number |
| reliability: number |
| }) { |
| const weightedSum = (terms: { label: string; value: number; coefficient: number }[]) => |
| terms.reduce((sum, term) => sum + term.value * term.coefficient, 0) |
| const withContribution = (terms: { label: string; value: number; coefficient: number }[]) => |
| terms.map((term) => ({ ...term, value: round(term.value), contribution: round(term.value * term.coefficient) })) |
| const sigmoid = (value: number) => 1 / (1 + Math.exp(-value)) |
| const terms = [ |
| { label: '当前匹配质量', value: input.matchingEquilibrium, weight: 0.24, direction: 1 }, |
| { label: '双方总剩余', value: input.totalSurplus, weight: 0.16, direction: 1 }, |
| { label: '对方可信履约', value: input.reliability, weight: 0.18, direction: 1 }, |
| { label: '控制权配置', value: input.controlAlignment, weight: 0.14, direction: 1 }, |
| { label: '信息完整度', value: input.coverage, weight: 0.09, direction: 1 }, |
| { label: '替代选择压力', value: input.outsideOptionPressure, weight: 0.07, direction: 1 }, |
| { label: '状态触发机制', value: input.stateContingencyReadiness, weight: 0.08, direction: 1 }, |
| { label: '被卡住风险', value: input.holdUpRisk, weight: 0.16, direction: -1 }, |
| { label: '预期缺口', value: input.expectationGap, weight: 0.11, direction: -1 }, |
| { label: '下行损失风险', value: input.downsideProbability, weight: 0.1, direction: -1 }, |
| ] |
| const positiveScore = terms |
| .filter((term) => term.direction === 1) |
| .reduce((sum, term) => sum + term.weight * term.value, 0) |
| const negativeScore = terms |
| .filter((term) => term.direction === -1) |
| .reduce((sum, term) => sum + term.weight * term.value, 0) |
| const successRaw = positiveScore - negativeScore |
| const neutralRaw = 0.38 + 0.32 * input.coverage + 0.22 * (1 - Math.abs(0.5 - input.matchingEquilibrium)) - 0.18 * Math.abs(positiveScore - negativeScore) |
| const failureRaw = negativeScore + 0.22 * (1 - input.reliability) + 0.16 * (1 - input.controlAlignment) - positiveScore * 0.45 |
| const [success, neutral, failure] = softmax([successRaw * 4.2, neutralRaw * 2.4, failureRaw * 4.2]).map((value) => round(value)) |
| const bargainingFactors = [ |
| { label: '双方总剩余', value: input.totalSurplus, coefficient: 1.05 }, |
| { label: '当前匹配质量', value: input.matchingEquilibrium, coefficient: 1 }, |
| { label: '替代选择压力', value: input.outsideOptionPressure, coefficient: 0.62 }, |
| { label: '对方可信履约', value: input.reliability, coefficient: 0.54 }, |
| { label: '信息完整度', value: input.coverage, coefficient: 0.48 }, |
| { label: '预期缺口', value: input.expectationGap, coefficient: -0.8 }, |
| { label: '被卡住风险', value: input.holdUpRisk, coefficient: -0.62 }, |
| ] |
| const currentNegotiation = round(clamp(sigmoid(-1.05 + weightedSum(bargainingFactors)))) |
| const concessionGainFactors = [ |
| { label: '高价值低成本交换空间', value: input.outsideOptionPressure, coefficient: 0.38 }, |
| { label: '预期缺口可修复度', value: 1 - input.expectationGap, coefficient: 0.32 }, |
| { label: '状态触发机制可设计度', value: input.stateContingencyReadiness, coefficient: 0.24 }, |
| { label: '控制权可锁定度', value: input.controlAlignment, coefficient: 0.22 }, |
| { label: '对方可信履约', value: input.reliability, coefficient: 0.18 }, |
| ] |
| const concessionCostFactors = [ |
| { label: '下行损失风险', value: input.downsideProbability, coefficient: 0.32 }, |
| { label: '被卡住风险', value: input.holdUpRisk, coefficient: 0.28 }, |
| { label: '控制权不足导致的让步损耗', value: 1 - input.controlAlignment, coefficient: 0.22 }, |
| { label: '对方可信不足导致的让步损耗', value: 1 - input.reliability, coefficient: 0.18 }, |
| ] |
| const concessionGain = round(clamp(weightedSum(concessionGainFactors))) |
| const concessionCost = round(clamp(weightedSum(concessionCostFactors))) |
| const paretoImprovement = round(clamp(concessionGain - concessionCost + 0.5)) |
| const afterConcession = round(clamp( |
| currentNegotiation + |
| 0.42 * paretoImprovement * (1 - currentNegotiation) - |
| 0.16 * concessionCost, |
| )) |
| const survivalFactors = [ |
| { label: '对方可信履约', value: input.reliability, coefficient: 0.34 }, |
| { label: '控制权配置', value: input.controlAlignment, coefficient: 0.24 }, |
| { label: '状态触发机制', value: input.stateContingencyReadiness, coefficient: 0.22 }, |
| { label: '信息完整度', value: input.coverage, coefficient: 0.1 }, |
| { label: '抗下行损失能力', value: 1 - input.downsideProbability, coefficient: 0.1 }, |
| ] |
| const hazardFactors = [ |
| { label: '被卡住风险', value: input.holdUpRisk, coefficient: 0.34 }, |
| { label: '预期缺口', value: input.expectationGap, coefficient: 0.24 }, |
| { label: '下行损失风险', value: input.downsideProbability, coefficient: 0.22 }, |
| { label: '可信履约不足', value: 1 - input.reliability, coefficient: 0.2 }, |
| ] |
| const baseSurvival = round(clamp(weightedSum(survivalFactors))) |
| const hazard = round(clamp(weightedSum(hazardFactors))) |
| const fulfillment = round(clamp(baseSurvival * Math.exp(-0.72 * hazard))) |
| const grade = success >= 0.62 && failure <= 0.22 ? 'advantage' : failure >= 0.42 ? 'danger' : 'balanced' |
| const positive = terms |
| .filter((term) => term.direction === 1) |
| .map((term) => ({ label: term.label, value: round(term.value), impact: round(term.weight * term.value) })) |
| .sort((a, b) => b.impact - a.impact) |
| .slice(0, 5) |
| const negative = terms |
| .filter((term) => term.direction === -1) |
| .map((term) => ({ label: term.label, value: round(term.value), impact: round(term.weight * term.value) })) |
| .sort((a, b) => b.impact - a.impact) |
| .slice(0, 5) |
|
|
| return { |
| success, |
| neutral, |
| failure, |
| stages: { |
| currentNegotiation, |
| afterConcession, |
| fulfillment, |
| }, |
| stageModels: { |
| currentNegotiation: { |
| model: 'bargaining-scorecard', |
| factors: withContribution(bargainingFactors), |
| }, |
| afterConcession: { |
| model: 'mechanism-counterfactual', |
| concessionGain, |
| concessionCost, |
| paretoImprovement, |
| factors: [...withContribution(concessionGainFactors), ...withContribution(concessionCostFactors.map((term) => ({ ...term, coefficient: -term.coefficient })))], |
| }, |
| fulfillment: { |
| model: 'bayesian-survival', |
| baseSurvival, |
| hazard, |
| factors: [...withContribution(survivalFactors), ...withContribution(hazardFactors.map((term) => ({ ...term, coefficient: -term.coefficient })))], |
| }, |
| }, |
| grade, |
| formulaVersion: 'success-panel-deterministic-1.2', |
| drivers: { |
| positive, |
| negative, |
| }, |
| } satisfies SuccessPanel |
| } |
|
|
| function buildQuestions(features: EngineFeature[], specs: FactorSpec[]) { |
| const requiredMissingKeys = specs |
| .filter((spec) => spec.required) |
| .filter((spec) => !requiredIsSatisfied(spec, features)) |
| .map((spec) => spec.key) |
| const missingKeys = features |
| .filter((item) => item.missing) |
| .sort((a, b) => b.weight - a.weight) |
| .map((item) => item.key) |
| const questions = [...requiredMissingKeys, ...missingKeys.filter((key) => !requiredMissingKeys.includes(key))] |
| .map((key) => specs.find((spec) => spec.key === key)?.question) |
| .filter((question): question is string => Boolean(question)) |
| return [...new Set(questions)].slice(0, 5) |
| } |
|
|
| function missingSpecQuestions(features: EngineFeature[], specs: FactorSpec[], gapType: FactorSpec['gapType']) { |
| return specs |
| .filter((spec) => spec.gapType === gapType || (gapType === 'user-only' && spec.gapType === 'hybrid')) |
| .filter((spec) => !requiredIsSatisfied(spec, features)) |
| .sort((a, b) => b.weight - a.weight) |
| .map((spec) => spec.question) |
| } |
|
|
| function hasObservedFeature(features: EngineFeature[], key: string) { |
| return features.some((item) => item.key === key && !item.missing) |
| } |
|
|
| function buildInformationSufficiency(text: string, features: EngineFeature[], specs: FactorSpec[], coverage: number) { |
| const openFeatures = features.filter((item) => item.source === 'open' && !item.missing) |
| const openText = openFeatures.flatMap((item) => [item.key, item.label, item.category, ...item.evidence]).join(' ') |
| const combinedText = `${text} ${openText}` |
| const hasOpenDecisionMaterial = openFeatures.length >= 3 |
| const concreteArrangement = (text.length >= 30 || hasOpenDecisionMaterial) && /offer|报价|价格|年薪|薪酬|合作|合伙|买|卖|签|外包|入职|试用|房|车|项目|供应商|服务商|承包商|代理|渠道|平台|资源方|需求方|承诺方|交易对手|合同|岗位|公司|团队|客户|付款|交付|验收|股权|期权|分成|返点|佣金|授权|控制权|条件包|路径|安排|标的|候选|方案|A|B|C|D|对方|买家|卖家|candidate|supplier|vendor|contractor|partner|proposal|payment|delivery|acceptance|equity|option|budget|deadline|IP|data|code|control/i.test(combinedText) |
| const exchangeTerms = /报价|价格|年薪|薪酬|付款|交付|验收|股权|期权|职责|岗位|负责|期限|现金流|融资|提成|分成|返点|佣金|预算|首付|贷款|让价|降价|万|元|资源|授权|控制权|退出|区域|账号|数据|代码|IP|客户归属|quote|price|salary|payment|delivery|acceptance|budget|deadline|runway|funding|financing|scope|responsibility|ownership|rights|exit/i.test(combinedText) |
| const userNeedSignals = [ |
| 'user_core_need', |
| 'need_priority_order', |
| 'time_horizon', |
| 'capability_fit', |
| 'constraint_floor', |
| ].filter((key) => hasObservedFeature(features, key)).length |
| const controlSignals = hasObservedFeature(features, 'residual_control') || hasObservedFeature(features, 'state_control_transfer') |
| const marketSignals = hasObservedFeature(features, 'market_anchor') || /市场|行情|同类|竞品|同行|成交|挂牌|薪酬|报价|替代|备选|多家|多个|竞争|搜索补充|公开资料|公开信息|quote|price|market|alternative|competition|benchmark/i.test(combinedText) |
| const reliabilitySignals = /书面|透明|可查|证明|案例|记录|口碑|兑现|拖延|不透明|融资|现金流|贷款|首付|履约|违约|written|contract|case|record|reliable|funding|runway|financing|breach/i.test(combinedText) |
| const userQuestions = [...new Set(missingSpecQuestions(features, specs, 'user-only'))].slice(0, 3) |
| const searchQuestions = [...new Set(missingSpecQuestions(features, specs, 'searchable'))].slice(0, 3) |
| const hybridQuestions = [...new Set(missingSpecQuestions(features, specs, 'hybrid'))].slice(0, 2) |
| const userOnlyMissing = specs |
| .filter((spec) => spec.gapType === 'user-only' || spec.gapType === 'hybrid') |
| .filter((spec) => !requiredIsSatisfied(spec, features, text)) |
| .map((spec) => spec.label) |
| const searchableMissing = specs |
| .filter((spec) => spec.gapType === 'searchable' || spec.gapType === 'hybrid') |
| .filter((spec) => !requiredIsSatisfied(spec, features, text)) |
| .map((spec) => spec.label) |
|
|
| let score = 0 |
| if (concreteArrangement) score += 0.28 |
| if (exchangeTerms) score += 0.16 |
| score += Math.min(userNeedSignals, 3) * 0.12 |
| if (marketSignals) score += 0.11 |
| if (controlSignals) score += 0.1 |
| if (reliabilitySignals) score += 0.08 |
| score += Math.min(coverage, 1) * 0.15 |
| score = round(clamp(score), 3) |
|
|
| const reasons = [ |
| concreteArrangement ? '已经有具体安排或候选对象' : '还没有具体安排或候选对象', |
| exchangeTerms ? '已经有核心交换条件' : '缺少价格、薪酬、付款、交付、职责或期限等核心条件', |
| userNeedSignals > 0 ? `已有${userNeedSignals}类用户需求/约束线索` : '缺少用户本人目标、取舍或底线', |
| marketSignals ? '已有市场/替代选择线索,或可由 GPT 搜索补足' : '缺少市场情况或替代选择参照', |
| controlSignals ? '已有控制权或状态变化线索' : '缺少关键事项谁说了算、出问题如何处理', |
| ] |
|
|
| const blocking = !hasOpenDecisionMaterial && (!concreteArrangement || (!exchangeTerms && userNeedSignals === 0)) |
| const level = blocking ? 'insufficient' : score >= 0.72 ? 'sufficient' : 'preliminary' |
|
|
| return { |
| level, |
| score, |
| blocking, |
| reasons, |
| userOnlyMissing: [...new Set(userOnlyMissing)].slice(0, 5), |
| searchableMissing: [...new Set(searchableMissing)].slice(0, 5), |
| userQuestions: [...new Set([...userQuestions, ...hybridQuestions])].slice(0, 3), |
| searchQuestions, |
| } as const |
| } |
|
|
| function hasComputableDecisionSignal( |
| dynamicFeatures: EngineFeature[], |
| coverage: number, |
| metrics: { |
| winWin: number |
| mismatch: number |
| currentNegotiation: number |
| fulfillment: number |
| userUtility: number |
| counterpartyUtility: number |
| totalSurplus: number |
| }, |
| ) { |
| const observedOpenFeatures = dynamicFeatures.filter((item) => !item.missing) |
| if (observedOpenFeatures.length < 2) return false |
|
|
| const dynamicText = observedOpenFeatures |
| .flatMap((item) => [item.key, item.label, item.category, ...item.evidence]) |
| .join(' ') |
|
|
| const hasExchangeSignal = /报价|价格|年薪|薪酬|付款|交付|验收|职责|岗位|负责|期限|现金流|融资|股权|期权|分成|预算|资源|客户|代码|数据|IP|授权|控制权|退出|quote|price|salary|payment|delivery|acceptance|budget|deadline|funding|financing|scope|responsibility|ownership|rights|exit/i.test(dynamicText) |
| const hasRiskOrValueSignal = /风险|收益|价值|能力|案例|履约|可信|控制权|不确定|成本|底线|替代|市场|匹配|risk|value|capability|case|reliable|control|uncertain|cost|floor|alternative|market|fit/i.test(dynamicText) |
| const hasMetricSeparation = |
| metrics.winWin >= 0.52 |
| || metrics.mismatch >= 0.34 |
| || metrics.currentNegotiation >= 0.6 |
| || metrics.fulfillment <= 0.42 |
| || metrics.userUtility >= 0.55 |
| || metrics.counterpartyUtility >= 0.55 |
| || metrics.totalSurplus >= 0.54 |
|
|
| return (hasExchangeSignal && hasRiskOrValueSignal) || (coverage >= 0.45 && hasMetricSeparation) || observedOpenFeatures.length >= 4 |
| } |
|
|
| function requiredIsSatisfied(spec: FactorSpec, features: EngineFeature[], rawText = '') { |
| if (features.some((item) => item.key === spec.key && !item.missing)) return true |
| const featureText = features |
| .filter((item) => item.source === 'open') |
| .flatMap((item) => [item.label, item.category, ...item.evidence]) |
| .join(' ') |
| const text = `${rawText} ${featureText}` |
|
|
| if (spec.key === 'user_core_need') return /成长|赚钱|收入|稳定|安全|自由|自主权|履历|平台|实战|家庭压力|长期|短期|AI产品|热爱|想要|最重要|growth|money|income|stability|autonomy|resume value|family pressure|long-term|short-term/i.test(text) |
| if (spec.key === 'need_priority_order') return /优先|第一|更重要|取舍|排序|底线|可以接受|不能接受|宁愿|相比|priority|tradeoff|prefer|rather|bottom line/i.test(text) |
| if (spec.key === 'time_horizon') return /短期|长期|一年|两年|三年|五年|未来|现在|阶段|职业初期|long-term|short-term|1 year|2 years|3 years|future|horizon/i.test(text) |
| if (spec.key === 'capability_fit') return /技能|能力|擅长|匹配|兴趣|专业|经验|资源|适合|skill|fit|ability|interest|experience|major/i.test(text) |
| if (spec.key === 'constraint_floor') return /底线|最低|房租|现金流|家庭|父母|贷款|存款|不能承受|最坏结果|red line|runway|cash flow|family|worst case/i.test(text) |
| if (spec.key === 'price_gap') return /报价|价格|预算|底价|可接受|万|元/.test(text) |
| if (spec.key === 'liquidity_need') return /时间|急|周期|截止|月底|交付|让步|时间优势/.test(text) |
| if (spec.key === 'market_anchor') return /市场|公允|行情|成交价|挂牌|租售比|银行|性价比|同小区|片区/.test(text) |
| if (spec.key === 'outside_option') return /替代|备选|BATNA|交易失败|没有损失|其他买家|其他房源|时间站在/.test(text) |
| return false |
| } |
|
|
| function getUniversalSpecs() { |
| return [ |
| ...universalFactorSpecs.map((spec) => ({ sourceModule: 'trade' as ModuleId, spec })), |
| ...contractCoreFactorSpecs.map((spec) => ({ sourceModule: 'trade' as ModuleId, spec: { ...spec, required: spec.required ?? false } })), |
| ] |
| } |
|
|
| function buildComplianceReview(text: string) { |
| const rules = Object.values(complianceChecklist).flat() |
| const checklist = rules.map((rule) => { |
| const positive = findEvidence(text, rule.positive) |
| const negative = findEvidence(text, rule.negative) |
| const status: 'observed' | 'missing' | 'risk' = negative.length > 0 ? 'risk' : positive.length > 0 ? 'observed' : 'missing' |
|
|
| return { |
| item: rule.item, |
| status, |
| evidence: [...positive, ...negative].length > 0 ? [...positive, ...negative] : [rule.question], |
| } |
| }) |
| const missing = checklist.filter((item) => item.status === 'missing').map((item) => item.item) |
| const risks = checklist.filter((item) => item.status === 'risk').map((item) => item.item) |
| const observed = checklist.filter((item) => item.status === 'observed').length |
| const score = Math.round(((observed + risks.length * 0.35) / checklist.length) * 100) |
| const status = risks.length > 0 ? 'blocking' : missing.length > Math.ceil(checklist.length / 2) ? 'warning' : 'pass' |
|
|
| return { |
| status, |
| score, |
| missing, |
| risks, |
| checklist, |
| } as const |
| } |
|
|
| export function analyzeContract(text: string, openFactors: DynamicFactorInput[] = []): ContractAnalysis { |
| const moduleId: EngineId = 'universal' |
| const universalSpecs = getUniversalSpecs() |
| const specs = universalSpecs.map(({ spec }) => spec) |
| const baseFeatures = universalSpecs.map(({ sourceModule, spec }) => scoreFactor(spec, text, sourceModule)) |
| const dynamicFeatures = openFactors.map(normalizeDynamicFactor) |
| const observedBaseFeatures = baseFeatures.filter((item) => !item.missing || item.category.includes('控制权') || item.category.includes('需求画像')) |
| const calculationFeatures = [...observedBaseFeatures, ...dynamicFeatures] |
| const features = calculationFeatures.length > 0 ? calculationFeatures : baseFeatures |
| const specByIdentity = new Map(specs.map((spec) => [`${spec.key}::${spec.label}`, spec])) |
| const specForFeature = (item: EngineFeature) => specByIdentity.get(`${item.key}::${item.label}`) |
| const dynamicByKey = new Map(dynamicFeatures.map((feature, index) => [feature.key, openFactors[index]])) |
| const observed = observedBaseFeatures.length + dynamicFeatures.length |
| const totalAvailable = baseFeatures.length + dynamicFeatures.length |
| const coverage = round(observed / totalAvailable) |
| const missingHighWeightFactors = baseFeatures |
| .filter((item) => item.missing && item.weight >= 0.85) |
| .map((item) => item.label) |
| const requiredSpecs = specs.filter((spec) => spec.required) |
| const missingRequiredSpecs = requiredSpecs.filter((spec) => !requiredIsSatisfied(spec, features, text)) |
| const informationSufficiency = buildInformationSufficiency(text, features, specs, coverage) |
| const complianceReview = buildComplianceReview(text) |
| const controlRights = buildControlRights(features, specs) |
| const bayesianReliability = buildBayesianReliability(text, features) |
| const formulaWeights = deriveContractFormulaWeights(features) |
|
|
| const factorWeight = (item: EngineFeature) => item.weight * (0.7 + item.confidence * 0.3) |
| const reliabilityAdjustment = 0.75 + bayesianReliability.posterior * 0.5 |
| const userUtility = round(weightedAverage(features, (item) => factorWeight(item) * (specForFeature(item)?.userWeight ?? dynamicWeight(dynamicByKey.get(item.key), 'user')))) |
| const counterpartyUtility = round(clamp(weightedAverage(features, (item) => factorWeight(item) * (specForFeature(item)?.otherWeight ?? dynamicWeight(dynamicByKey.get(item.key), 'other'))) * reliabilityAdjustment)) |
| const riskExposure = round(clamp(1 - weightedAverage(features, (item) => factorWeight(item) * (specForFeature(item)?.riskWeight ?? dynamicWeight(dynamicByKey.get(item.key), 'risk'))) + (1 - bayesianReliability.posterior) * 0.12)) |
| const referenceAlignment = round(weightedAverage(features, (item) => factorWeight(item) * (specForFeature(item)?.refWeight ?? dynamicWeight(dynamicByKey.get(item.key), 'ref')))) |
| const informationPenalty = round((1 - Math.min(coverage + dynamicFeatures.length * 0.035, 1)) * 0.18) |
| const expectationGap = round(clamp(1 - referenceAlignment + informationPenalty)) |
| const fairnessReference = round(clamp(0.5 * referenceAlignment + 0.3 * userUtility + 0.2 * counterpartyUtility)) |
| const holdUpRisk = round(clamp( |
| formulaWeights.holdUp.risk * riskExposure |
| + formulaWeights.holdUp.gap * expectationGap |
| + formulaWeights.holdUp.missing * (1 - coverage) |
| + formulaWeights.holdUp.control * controlRights.residualControlRisk |
| + formulaWeights.holdUp.reliability * (1 - bayesianReliability.posterior), |
| )) |
| const renegotiationRisk = round(clamp(0.38 * expectationGap + 0.29 * riskExposure + 0.18 * (1 - fairnessReference) + 0.15 * (1 - controlRights.stateContingencyReadiness))) |
| const outsideOptionPressure = round(clamp(1 - Math.abs(userUtility - counterpartyUtility))) |
| const totalSurplus = round((userUtility + counterpartyUtility) / 2) |
| const nashProduct = round(Math.sqrt(Math.max(userUtility, 0) * Math.max(counterpartyUtility, 0))) |
| const matchingEquilibrium = round( |
| clamp( |
| formulaWeights.matching.nash * nashProduct |
| + formulaWeights.matching.reference * referenceAlignment |
| + formulaWeights.matching.surplus * totalSurplus |
| + formulaWeights.matching.outsideOption * outsideOptionPressure |
| + formulaWeights.matching.antiHoldUp * (1 - holdUpRisk) |
| + formulaWeights.matching.control * controlRights.investmentIncentiveFit, |
| ), |
| ) |
|
|
| const [winWin, neutral, mismatch] = softmax([ |
| 2.9 * matchingEquilibrium + 1.0 * totalSurplus + 0.58 * coverage + 0.48 * controlRights.controlAlignment + 0.42 * bayesianReliability.posterior - 1.45 * holdUpRisk, |
| 1.2 * coverage + 0.9 * referenceAlignment - 0.3 * Math.abs(userUtility - counterpartyUtility), |
| 2.5 * holdUpRisk + 1.65 * renegotiationRisk + 1.15 * expectationGap - 1.25 * totalSurplus, |
| ]).map((value) => round(value)) |
|
|
| const stateUtilities = [ |
| { state: '双赢匹配', probability: winWin, utility: round(1.2 * totalSurplus + 0.5 * matchingEquilibrium) }, |
| { state: '中性波动', probability: neutral, utility: round(0.25 * totalSurplus - 0.1 * renegotiationRisk) }, |
| { state: '错配损耗', probability: mismatch, utility: round(-1 * (0.7 + holdUpRisk + riskExposure)) }, |
| ] |
| const expectedUtility = round(stateUtilities.reduce((sum, item) => sum + item.probability * item.utility, 0)) |
| const variance = round(stateUtilities.reduce((sum, item) => sum + item.probability * (item.utility - expectedUtility) ** 2, 0)) |
| const cvarLoss = round(mismatch * Math.abs(stateUtilities[2].utility)) |
| const successPanel = buildSuccessPanel({ |
| matchingEquilibrium, |
| totalSurplus, |
| outsideOptionPressure, |
| coverage, |
| holdUpRisk, |
| expectationGap, |
| downsideProbability: mismatch, |
| controlAlignment: controlRights.controlAlignment, |
| stateContingencyReadiness: controlRights.stateContingencyReadiness, |
| reliability: bayesianReliability.posterior, |
| }) |
|
|
| const rawScore = Math.round(clamp( |
| formulaWeights.score.winWin * winWin |
| + formulaWeights.score.matching * matchingEquilibrium |
| + formulaWeights.score.reference * referenceAlignment |
| + formulaWeights.score.coverage * coverage |
| + formulaWeights.score.surplus * totalSurplus, |
| ) * 100) |
| const computableDecisionSignal = hasComputableDecisionSignal(dynamicFeatures, coverage, { |
| winWin, |
| mismatch, |
| currentNegotiation: successPanel.stages.currentNegotiation, |
| fulfillment: successPanel.stages.fulfillment, |
| userUtility, |
| counterpartyUtility, |
| totalSurplus, |
| }) |
| const requiredBlocking = informationSufficiency.blocking && !computableDecisionSignal |
| const adjustedInformationSufficiency = requiredBlocking |
| ? informationSufficiency |
| : { |
| ...informationSufficiency, |
| blocking: false, |
| level: informationSufficiency.level === 'insufficient' ? 'preliminary' as const : informationSufficiency.level, |
| reasons: informationSufficiency.blocking |
| ? [...informationSufficiency.reasons, '开放参数已经足以形成当前初步判断,剩余信息用于校验、排序和优化条件'] |
| : informationSufficiency.reasons, |
| } |
| const score = requiredBlocking ? Math.min(rawScore, 68) : rawScore |
| const verdict = requiredBlocking |
| ? '必要信息不足,不能下最终结论' |
| : winWin >= 0.55 && mismatch < 0.25 ? '可优化双赢匹配' : mismatch >= 0.38 ? '高风险错配' : '信息不足或中性波动' |
| const decision = requiredBlocking |
| ? '先补齐价格、时间、当前市场情况、替代选择等必要条件,再做最终验算' |
| : verdict === '可优化双赢匹配' |
| ? '可以继续推进,但必须先把关键利益、责任、控制权和退出条件结构化' |
| : verdict === '高风险错配' |
| ? '暂停按原条件推进,除非关键风险能通过书面条件、分阶段验证或退出机制消除' |
| : '优先补齐高权重缺失因素,再做最终决策' |
|
|
| return { |
| moduleId, |
| moduleName: '全量因子决策引擎', |
| verdict, |
| decision, |
| score, |
| formulas: { |
| generalizedDistribution: 'F(U)=Σ p_s·1(U_s<=u); E[U]=Σp_s·U_s; Var(U)=Σp_s·(U_s-E[U])²; CVaR_loss=P(loss)·|U_loss|', |
| referenceContract: 'A_ref=Σ(w_ref_i·v_i·conf_i)/Σw_ref_i; Gap=1-A_ref+InfoPenalty; HoldUp=0.52·Risk+0.28·Gap+0.20·Missing', |
| bilateralMatching: 'M=0.36·sqrt(U_user·U_other)+0.24·A_ref+0.18·Surplus+0.12·OutsideOption+0.10·(1-HoldUp)', |
| }, |
| features, |
| factorCoverage: { |
| total: totalAvailable, |
| observed, |
| coverage, |
| missingHighWeightFactors, |
| openFactorCount: dynamicFeatures.length, |
| }, |
| requiredFactors: { |
| total: requiredSpecs.length, |
| observed: requiredSpecs.length - missingRequiredSpecs.length, |
| missing: missingRequiredSpecs.map((spec) => spec.label), |
| blocking: requiredBlocking, |
| }, |
| informationSufficiency: adjustedInformationSufficiency, |
| complianceReview, |
| distribution: { winWin, neutral, mismatch }, |
| successPanel, |
| generalizedDistribution: { |
| expectedUtility, |
| variance, |
| downsideProbability: mismatch, |
| cvarLoss, |
| stateUtilities, |
| }, |
| referenceContract: { |
| referenceAlignment, |
| expectationGap, |
| holdUpRisk, |
| renegotiationRisk, |
| fairnessReference, |
| }, |
| controlRights, |
| bayesianReliability, |
| bilateralMatching: { |
| userUtility, |
| counterpartyUtility, |
| outsideOptionPressure, |
| totalSurplus, |
| nashProduct, |
| matchingEquilibrium, |
| }, |
| missingQuestions: buildQuestions(features, specs), |
| modelPayload: { |
| engineVersion: 'contract-engine-universal-factor-matrix-1.1', |
| theoryBasis: ['广义分布函数模型', '不完全参照点契约', '双边匹配均衡', '贝叶斯后验更新', '开放式不定参数矩阵'], |
| constraints: ['大模型只能解释和表达计算结果', '不得替代后台概率分布自行主观判断', '信息不足时必须优先追问高权重缺失因素'], |
| }, |
| } |
| } |
|
|