/**
 * Persona Feature Registry(标签注册表)— 单一真理源(代码,git/PR review)
 *
 * 画像的核心是「标签化」。每个标签按业务整理好的「标签卡」形式定义(对齐 CDP 标签中台 / OneModel):
 *   标签值 / 数据来源 / 数据字段 / 标签释义 / 计算算法 + 时间语义 / 层级 / owner / version。
 *
 * - 存储:**代码常量**(本文件),不落库;DB(persona_features)只存每患者每标签的实例值。
 * - 时间语义:每个标签自己声明(snapshot 当前态 / window 窗口 / lifetime 全史 / trend 趋势 / mixed)。
 * - 收口:producer 产出的 key 必须在此登记(CI 防漂移,见 persona-design-v2.md §二)。
 * - 来源可切:同一标签现可从 PAC 事实层自算,宿主 CDP 报表给出后切宿主值,口径(算法)不变。
 */
export type FeatureTier = 'rule' | 'statistical' | 'model' | 'llm';
export type FeatureTimeSemantics = 'snapshot' | 'window' | 'lifetime' | 'trend' | 'mixed';

export interface PersonaFeatureSpec {
  key: string; // PersonaFeatureKey
  nameZh: string; // 标签名
  tier: FeatureTier; // 层级:规则 / 统计 / 模型 / LLM
  timeSemantics: FeatureTimeSemantics; // 时间语义
  labelValues?: string[]; // 标签值(枚举型标签)
  dataSource: string; // 数据来源(现状 + 未来切换)
  dataFields: string[]; // 数据字段
  meaning: string; // 标签释义
  algorithm: string; // 计算算法(口径,人读;真理实现在 extractor)
  owner: string;
  version: number;
}

export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
  // ── B.1.1 RFM 层级(业务整理好的 CDP 口径,直接采用)──
  rfm: {
    key: 'rfm',
    nameZh: '价值分群(RFM)',
    tier: 'statistical',
    timeSemantics: 'mixed', // R=snapshot(最近) F/M=lifetime
    labelValues: [
      '重要价值客户',
      '重要保持客户',
      '重要发展客户',
      '重要挽留客户',
      '一般价值客户',
      '一般保持客户',
      '一般发展客户',
      '低活跃客户',
    ],
    dataSource:
      '现:PAC 事实层自算(encounter/treatment(actual)/挂号 + payment/recharge/refund);未来:宿主 CDP「客户综合分析报表」直接给 R/F/M',
    dataFields: ['last_visit_time', 'visit_times', 'net_receipts_total'],
    meaning: '基于最近就诊时间(R)、就诊频次(F)、累计消费(M)三维度的客户价值分层',
    algorithm: [
      'Step1 R/F/M 得分(1-5):',
      '  R ≤540天=5 / 541-730=4 / 731-1095=3 / 1096-1460=2 / >1460=1',
      '  F ≥5次=5 / 3-4次=4 / 2次=2 / 1次=1',
      '  M 累计消费分位:TOP20%=5 / 20-40%=4 / 40-60%=3 / 60-80%=2 / BOTTOM20%=1(租户内分位)',
      'Step2 分层:',
      '  R≥4 F≥3 M≥4→重要价值 / R=3 F≥3 M≥4→重要保持 / R≥4 F=2 M≥4→重要发展 / R≤2 F≥3 M≥4→重要挽留',
      '  R≥4 F≥3 M<4→一般价值 / R=3 F≥3 M<4→一般保持 / R≥4 F=2 M<4→一般发展 / 其余(含 R≤2 任意)→低活跃',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── A.1.1 年龄段(业务 CDP 口径)──
  age_bracket: {
    key: 'age_bracket',
    nameZh: '年龄段',
    tier: 'rule',
    timeSemantics: 'snapshot', // 当下年龄(随时间增长,历史读版本流)
    labelValues: [
      '婴幼儿(0-3)',
      '学龄前(3-6)',
      '替牙期(7-11)',
      '青少年(12-17)',
      '青年(18-25)',
      '中青年(26-30)',
      '中年(31-45)',
      '中老年(46-55)',
      '老年(55+)',
    ],
    dataSource: '现:PAC patient.birthDate 自算;未来:宿主 CDP「客户综合分析报表」client_age 直接取',
    dataFields: ['client_age'],
    meaning: '按口腔治疗需求特征精细划分年龄层,匹配适龄项目与家庭决策模式',
    algorithm: [
      'client_age 直接取值(PAC 现从 birthDate 算周岁);区间(下界含,3/55 重叠归下一档,≥55→老年):',
      '0-2→婴幼儿 / 3-6→学龄前 / 7-11→替牙期 / 12-17→青少年 / 18-25→青年 /',
      '26-30→中青年 / 31-45→中年 / 46-54→中老年 / ≥55→老年',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── A.1.2 性别(业务 CDP 口径)──
  gender: {
    key: 'gender',
    nameZh: '性别',
    tier: 'rule',
    timeSemantics: 'snapshot',
    labelValues: ['男性', '女性', '未知'],
    dataSource: '现:PAC patient.gender 自映射;未来:宿主 CDP client_gender 直接取',
    dataFields: ['client_gender'],
    meaning: '客户生理性别,影响沟通话术与项目推荐',
    algorithm: 'client_gender 直接映射:"M"/"男"→男性;"F"/"女"→女性;其他→未知',
    owner: 'pac-algo',
    version: 1,
  },

  // ── A.2.1 获客渠道(业务 CDP 口径,L2 数仓已算)──
  acquisition_channel: {
    key: 'acquisition_channel',
    nameZh: '获客渠道',
    tier: 'rule',
    timeSemantics: 'snapshot', // 实为 static(不变的历史定格):初诊来源一经判定不改,既不可逆也不累计;退化场景归 snapshot
    labelValues: ['走入', '口碑客户', '集团销售渠道', '集团营销渠道', '地区营销渠道', '电商平台', '自媒体网络', '内部员工', '其他'],
    dataSource: 'DW fact_client_out.primary_category/sub_category(L2 数仓初诊来源)→ 摄入 PAC 副表 patient_profiles.acquisition_channel/sub(立柱标准枚举)',
    dataFields: ['primary_category', 'sub_category'],
    meaning: '客户首次到诊的获客来源,用于渠道价值分析。初诊来源一经判定不做二次改归类',
    algorithm: 'host primary_category 经 assembler enum_mapping 归一到 PAC 立柱标准(走入→walk_in 等);二级 sub_category 原值透传。冲突解决(数仓侧):推荐人 > 活动码/渠道码 > 客户类型',
    owner: 'pac-algo',
    version: 1,
  },

  // ── A.3.1 家庭构成(业务 CDP 口径;PAC 用 PatientRelation 反推)──
  family_structure: {
    key: 'family_structure',
    nameZh: '家庭构成',
    tier: 'rule',
    timeSemantics: 'snapshot',
    labelValues: ['单身家庭', '两口之家', '多口之家', '多代之家'],
    dataSource: '现:PAC PatientRelation 边表(摄入自 fact_customer_referee_out,混推荐人噪音);未来:宿主 CDP「社交关系维护」social_relations',
    dataFields: ['social_relations'],
    meaning: '基于直系亲属社交关系反推家庭结构,用于家庭套餐推荐与儿牙营销',
    algorithm: [
      '直系:spouse / child·grandchild / father·mother·grandparent;sibling/friend/other 不计(噪音)。',
      '有长辈→多代之家;否则有子女→多口之家;否则有配偶→两口之家;否则有非直系边→单身家庭;无边→不打标签。',
      '⚠️ 依赖关系边覆盖,样本稀疏 + 非全量时覆盖偏低。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── B.1.2 转介绍达人(业务 CDP 口径;v1 用 DW 预聚合)──
  referral_champion: {
    key: 'referral_champion',
    nameZh: '转介绍达人',
    tier: 'rule',
    timeSemantics: 'lifetime', // recommend_num/amount 是全史累计计数器(单调只增),非可逆当前态
    labelValues: ['家庭型', '社交型'],
    dataSource: 'DW fact_client_out.recommend_num/recommend_amount(DW 预聚合推荐人数+转化额)+ PatientRelation;未来宿主 CDP social_relations + settlement_records 逐人核',
    dataFields: ['recommend_num', 'recommend_amount', 'social_relations'],
    meaning: '识别有推荐能力且带来实质转化的客户;家庭型(带家人)推家庭套餐,社交型(推外人)给推荐奖励',
    algorithm: [
      '门槛:recommend_num ≥3 且 recommend_amount > 0(实质转化,v1 用 DW 聚合额作代理)。',
      '家庭型:直系家庭关系 ≥3(带家人);否则 社交型(推外人)。不满足→不打标签。',
      '⚠️ v1 用 DW 预聚合(逐个被推荐人"均有效转化"需其结算,跨患者+需被推荐人已摄入,留 v2)。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── B.1.3 权益身份(业务 CDP 口径;多标签)──
  entitlement_status: {
    key: 'entitlement_status',
    nameZh: '权益身份',
    tier: 'rule',
    timeSemantics: 'lifetime', // 遍历历史全部结算记录
    labelValues: ['高端保险直付', '银行私行权益', '储值会员', '儿牙会员', '医保客户'],
    dataSource: 'DW fact_settlement_mode_out 卡券名(card_type_name/card_name)+ insurance_name/channel → payment_record.content;recharge_record(储值)',
    dataFields: ['settlement_records(卡券名称、权益类型)'],
    meaning: '通过历史结算卡券反推权益身份,识别高净值客户、预付费会员及医保覆盖客户',
    algorithm: [
      '遍历历史结算,命中关键词即打标(允许并列):',
      '高端保险直付 Bupa/中间带/保险直付/商保;银行私行 招行私行/私人银行/白金卡/贵宾卡;',
      '储值会员 储值/预存(或有充值);儿牙会员 儿牙会员/乐牙卡/涂氟年卡;医保 医保。未命中→不打标签。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 2,
  },

  // ── B.1.4 生命周期(业务 CDP 口径;PAC 自算 + 修正顺序 bug)──
  lifecycle_stage: {
    key: 'lifecycle_stage',
    nameZh: '生命周期',
    tier: 'rule',
    timeSemantics: 'mixed', // 末诊 recency(window) + 消费斜率(trend,近1年 vs 历史年均)→ 复合,归 mixed
    labelValues: ['潜客', '新客', '成长客', '成熟客', '待激活', '沉睡客', '流失客'],
    dataSource: '现:PAC 自算(payment/recharge/refund + 就诊 fact + appointment);源表 fact_client_out 无 net_receipts_total/total_visit_times → PAC 算;宿主 CDP 报表给出后可切',
    dataFields: ['first_visit_time', 'last_visit_time', 'total_visit_times', 'net_receipts_this', 'net_receipts_total'],
    meaning: '客户在生命周期中的位置,决定运营策略匹配;成长客 vs 成熟客用消费斜率(近1年>历史年均×1.5)区分',
    algorithm: [
      'total=0 有预约→潜客;首诊≤180&就诊≤3→新客;',
      '末诊≤540&年均≥0.5&近1年>历史年均×1.5→成长客;同上但≤1.5→成熟客;',
      '末诊180-540&就诊≥2→待激活;末诊>730→流失客;末诊540-730&就诊≥2→沉睡客;否则→新客兜底。',
      '⚠️ 修正 spec 顺序 bug:原"沉睡>540"在"流失>730"前 → 流失永不触发;此处流失提前。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── C.3.1 治疗史(业务 CDP 口径;PAC 读 canonical category)──
  treatment_history: {
    key: 'treatment_history',
    nameZh: '治疗史',
    tier: 'rule',
    timeSemantics: 'lifetime',
    labelValues: ['种植史', '正畸史', '修复史', '牙周治疗史'],
    dataSource: 'PAC treatment_record.content.category(canonical 已归一;spec 的"病历处置/结算关键词"在摄入层已收敛成 category)',
    dataFields: ['medical_record_tag', 'settlement_records'],
    meaning: '客户历史接受过的主要治疗类型,影响后续推荐与维护策略;允许并列,仅保留核心治疗类型',
    algorithm: [
      'implant→种植史;orthodontic→正畸史;prosthodontic/cosmetic→修复史(冠桥/贴面/嵌体);periodontic→牙周治疗史。',
      '不标记 surgical(拔牙)/restorative(充填)/endodontic(根管)等基础治疗。仅 actual;全不命中→不打标签。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── D.1.2 时间偏好(业务 CDP 口径)──
  time_preference: {
    key: 'time_preference',
    nameZh: '时间偏好',
    tier: 'rule',
    timeSemantics: 'window', // 近2年预约
    labelValues: ['工作日偏好', '周末偏好', '上午偏好', '下午偏好', '晚间偏好'],
    dataSource: 'PAC appointment_record.planned_for(预约槽时刻;北京 TZ 下干净营业钟形)。occurred_at 钟点不可靠,用 planned_for',
    dataFields: ['appointment_records(日期+时段)', 'settlement_records(日期)'],
    meaning: '客户习惯就诊时间(星期+时段),用于优化推送时间与预约排班;允许并列',
    algorithm: [
      '近2年预约(记录≥2),北京时区:工作日占比≥60%→工作日偏好;周末≥50%→周末偏好;',
      '上午8-12≥50%→上午;下午12-18≥50%→下午;晚间18-21≥50%→晚间。无命中/记录<2→不打标签。',
      '⚠️ TZ 硬编北京(+8),多宿主应读 host 时区(follow-up)。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── D.1.3 折扣锚点(业务 CDP 口径)──
  discount_anchor: {
    key: 'discount_anchor',
    nameZh: '折扣锚点',
    tier: 'rule',
    timeSemantics: 'lifetime',
    labelValues: ['最低折扣率 + 对应结算时间/项目'],
    dataSource: 'PAC payment_record.content(amount_cents=应收/原价、discount_cents=4渠道折扣额和);DW 无 original_amount,折扣率自算',
    dataFields: ['actual_amount', 'original_amount', 'settlement_date'],
    meaning: '客户历史结算中享受过的最大折扣力度(最低折扣率),作为销售推优惠时的价格底线参考',
    algorithm: [
      '遍历有折扣的结算:折扣率 = 1 − discount_cents/amount_cents(DW 无原价字段;discount_*_rate 实为折扣金额)。',
      '取最小折扣率(力度最大)+ 保留日期/项目。无折扣→不打标签(业务:无锚点换推增值权益,不直接降价)。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── D.2.3 特别关注(业务 CDP 口径)──
  special_attention: {
    key: 'special_attention',
    nameZh: '特别关注',
    tier: 'rule',
    timeSemantics: 'window', // 近1年预约 + 当前标注
    labelValues: ['屡次爽约', '经常迟到', '免打扰', '不可等候'],
    dataSource: 'PAC appointment_record(status 履约/爽约 + arrived_at 迟到)+ patient_profile.doNotContact/notes/tags + emr_record',
    dataFields: ['appointment_records', 'customer_tags'],
    meaning: '客户行为特征中需一线服务特别注意的事项,直接影响排班与触达策略',
    algorithm: [
      '近1年:屡次爽约 履约率<50%且决定数≥3(到诊 completed/arrived;爽约 no_show);',
      '经常迟到 到店>预约+15min 占比≥50%且≥3次;免打扰 doNotContact;',
      '不可等候 notes/tags/病历含 不可等候/时间敏感/赶时间/不能等。全不命中→不打标签。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── D.2.2 治疗敏感(业务 CDP 口径;关键词精炼排假阳)──
  treatment_sensitivity: {
    key: 'treatment_sensitivity',
    nameZh: '治疗敏感',
    tier: 'rule',
    timeSemantics: 'lifetime',
    labelValues: ['看牙恐惧', '晕针', '晕血', '密闭恐惧'],
    dataSource: 'EMR emr_record.content(past_history 既往史/illness_desc 等)+ profile.notes/tags。宿主未给 health_profile,EMR 既往史已承载',
    dataFields: ['medical_record_tag(病历备注)', 'health_profile(健康档案)'],
    meaning: '客户在治疗过程中的特殊敏感反应,直接影响医生匹配、舒缓策略与预约排班',
    algorithm: [
      '扫病历自由文本,命中即打标(允许并列):看牙恐惧 恐惧/害怕看牙/牙科焦虑/看牙紧张;',
      '晕针 晕针/害怕打针;晕血 晕血/见血不适;密闭恐惧 幽闭/密闭/长时间张口不适。全不命中→不打标签。',
      '⚠️ 关键词按真实数据精炼排假阳:裸"紧张"=颏肌紧张(正畸)、裸"见血"=可见血凝块(临床)、裸"张口受限"=物理开口受限,均剔除。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── C.1.1 潜在治疗(转化机会;复用召回 gap 核心)──
  potential_treatment: {
    key: 'potential_treatment',
    nameZh: '潜在治疗',
    tier: 'rule',
    timeSemantics: 'snapshot', // 常态:有客观需求未完成(随诊断/治疗变化重算)
    labelValues: ['潜在种植', '潜在正畸', '潜在早矫', '潜在修复', '潜在牙周', '潜在根管', '潜在拔牙', '潜在补牙'],
    dataSource: '复用召回 gap 核心(PotentialTreatmentSelector → 共享 buildGapCore):诊断/建议 fact 未启动对应治疗,去召回时间门',
    dataFields: ['diagnosis_record', 'recommendation_record', 'treatment_record'],
    meaning: '客户"有客观需求但未完成治疗"的转化机会;以电子病历诊断/处置为核心,减少结算维度依赖。允许并列',
    algorithm: [
      '= 召回挖的"诊断了/建议了但没启动对应治疗"(牙位级 gap),去掉召回时间门(cooldown/预约/到诊)→ 常态属性。',
      'K 码→8 标签:种植←K08(>18)/补牙←K02/根管←K04/牙周←K05,K06/正畸←K07(>12≤40)/早矫←K07(3-12)/',
      '修复←K03(默认)/拔牙←K01+K03(残根残冠)。置信度=诊断1.0/建议0.8。⚠️非已丢单(sales_chance)PAC未摄入,省略。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── C.2.1 急迫等级(取最大;v1 仅潜在治疗路径)──
  urgency_level: {
    key: 'urgency_level',
    nameZh: '急迫等级',
    tier: 'rule',
    timeSemantics: 'snapshot',
    labelValues: ['紧急', '高', '中', '低'],
    dataSource: '潜在治疗(ctx.potentialGaps,复用召回 gap 核心)+ 末诊(同 lifecycle 口径)。已治疗复查路径待召回实现该场景',
    dataFields: ['is_pending', 'last_visit_time', 'settlement_records', 'sales_chance_records', 'medical_record_tag'],
    meaning: '客户当前所有待处理事项中最高的急迫程度,决定外呼/触达优先级。客户维度单一标签',
    algorithm: [
      'v1 仅潜在治疗路径(已治疗复查路径召回未实现,follow-up):有潜在待转 且',
      '末诊>90天→紧急 / 30-90→高 / <30 或新发现→中 / 美学预防→低(8 标签不含,暂不触发)。',
      '无潜在治疗→不打标签。Step2 取最大(多路径 MAX,现仅 1 路径)。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },

  // ── D.2.4 禁忌标签(v1 仅种植年龄;余 Layer C)──
  contraindication: {
    key: 'contraindication',
    nameZh: '禁忌标签',
    tier: 'rule', // v1 规则;完整版需 LLM(Layer C)
    timeSemantics: 'snapshot', // 年龄禁忌随年龄自动解除(validUntil=满19岁)
    labelValues: ['种植禁忌', '正畸禁忌', '麻醉禁忌', '手术禁忌'],
    dataSource: '现:patient.birthDate(年龄禁忌);完整版:EMR 既往史/诊断(需 LLM 抽取,Layer C)。宿主未给 health_profile',
    dataFields: ['health_profile', 'medical_record_tag(既往史+诊断)', 'emr_history'],
    meaning: '客户因健康/年龄不适合或需谨慎特定治疗;预约/方案前必须核查,影响可行性与安全预警。允许并列',
    algorithm: [
      'v1 仅实现【种植年龄禁忌:年龄≤18,骨骼发育未完全】(validUntil=满19岁自动解除)。',
      '⚠️ 其余禁忌(糖尿病控制不佳/过敏/抗凝/妊娠/放疗…)规则做不了:① 既往史否定泛滥(含"过敏"97%是"否认过敏",naive 匹配=医疗事故);',
      '② 量化条件无数据(HbA1c 全量仅10条/血压自由文本);③ 需控制状态/急性期判断+时效窗口 → 留 Layer C(LLM 抽取)。',
    ].join('\n'),
    owner: 'pac-algo',
    version: 1,
  },
};
