contract-decision-ai-beta / src /contractEngine.ts
binbin888's picture
Sync full project source without secrets
a3916fa verified
Raw
History Blame Contribute Delete
77.9 kB
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: ['大模型只能解释和表达计算结果', '不得替代后台概率分布自行主观判断', '信息不足时必须优先追问高权重缺失因素'],
},
}
}