match-engine.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. #!/usr/bin/env node
  2. /**
  3. * 小牛看房 — 房源智能匹配引擎 v1.0
  4. *
  5. * 用法:
  6. * node match-engine.js # 匹配全部客户
  7. * node match-engine.js --buyer buyer_001 # 匹配单个客户
  8. * node match-engine.js --buyer buyer_001 --top 3 # 输出 Top-3
  9. */
  10. const fs = require('fs');
  11. const path = require('path');
  12. // ============================================================
  13. // 一、数据加载
  14. // ============================================================
  15. function loadData() {
  16. const buyers = JSON.parse(
  17. fs.readFileSync(path.join(__dirname, 'buyers.json'), 'utf8')
  18. ).clients;
  19. const properties = JSON.parse(
  20. fs.readFileSync(path.join(__dirname, 'properties.json'), 'utf8')
  21. ).properties;
  22. return { buyers, properties };
  23. }
  24. // ============================================================
  25. // 二、五类客户权重模板(来源:经纪人访谈 + 客户画像分析)
  26. // ============================================================
  27. const WEIGHT_TEMPLATES = {
  28. '婚房刚需型': {
  29. priceAdvantage: 0.30,
  30. layoutMatch: 0.15,
  31. areaMatch: 0.10,
  32. decoration: 0.10,
  33. schoolMatch: 0.05,
  34. transport: 0.10,
  35. community: 0.05,
  36. floorMatch: 0.05,
  37. orientation: 0.05,
  38. surrounding: 0.05,
  39. },
  40. '学区焦虑型': {
  41. priceAdvantage: 0.15,
  42. layoutMatch: 0.10,
  43. areaMatch: 0.05,
  44. decoration: 0.05,
  45. schoolMatch: 0.45,
  46. transport: 0.10,
  47. community: 0.05,
  48. floorMatch: 0.02,
  49. orientation: 0.02,
  50. surrounding: 0.01,
  51. },
  52. '置换改善型': {
  53. priceAdvantage: 0.20,
  54. layoutMatch: 0.20,
  55. areaMatch: 0.20,
  56. decoration: 0.15,
  57. schoolMatch: 0.05,
  58. transport: 0.05,
  59. community: 0.10,
  60. floorMatch: 0.03,
  61. orientation: 0.02,
  62. surrounding: 0.00,
  63. },
  64. '隐性需求型': {
  65. priceAdvantage: 0.20,
  66. layoutMatch: 0.10,
  67. areaMatch: 0.10,
  68. decoration: 0.25,
  69. schoolMatch: 0.05,
  70. transport: 0.10,
  71. community: 0.10,
  72. floorMatch: 0.05,
  73. orientation: 0.03,
  74. surrounding: 0.02,
  75. },
  76. '刚需升级型': {
  77. priceAdvantage: 0.25,
  78. layoutMatch: 0.15,
  79. areaMatch: 0.15,
  80. decoration: 0.20,
  81. schoolMatch: 0.05,
  82. transport: 0.05,
  83. community: 0.05,
  84. floorMatch: 0.05,
  85. orientation: 0.03,
  86. surrounding: 0.02,
  87. },
  88. };
  89. // ============================================================
  90. // 三、卖点标签 → 客户类型加分映射(来源:经纪人访谈提炼)
  91. // ============================================================
  92. const HIGHLIGHT_BONUS = {
  93. '豪装': { '隐性需求型': 3, '刚需升级型': 3 },
  94. '精装': { '隐性需求型': 2, '刚需升级型': 2, '置换改善型': 1 },
  95. '满五唯一': { '婚房刚需型': 2 },
  96. '业主急售': { '婚房刚需型': 2, '置换改善型': -1 },
  97. '急售': { '婚房刚需型': 2, '置换改善型': -1 },
  98. '南北通透': { '婚房刚需型': 1, '置换改善型': 1, '刚需升级型': 1, '隐性需求型': 1, '学区焦虑型': 1 },
  99. '明厨明卫': { '置换改善型': 2 },
  100. '近地铁': { '婚房刚需型': 2 },
  101. '地铁房': { '婚房刚需型': 2 },
  102. '人车分流': { '置换改善型': 2 },
  103. '近公园': { '置换改善型': 1, '隐性需求型': 1 },
  104. '次新房': { '婚房刚需型': 1, '置换改善型': 1, '刚需升级型': 1 },
  105. '有车位': { '置换改善型': 1 },
  106. '总价低': { '婚房刚需型': 2, '刚需升级型': 1 },
  107. '品牌开发商': { '置换改善型': 1, '刚需升级型': 1 },
  108. '婚装': { '隐性需求型': 2 },
  109. };
  110. // ============================================================
  111. // 四、客户心理 → 房源加分(来源:七步法 + 访谈)
  112. // ============================================================
  113. function calcPsychologyBonus(buyer, property) {
  114. let bonus = 0;
  115. const style = buyer.decisionStyle;
  116. const type = buyer.type;
  117. // 对比型 — 性价比突出
  118. if (style === '对比型' && property.priceAdvantage >= 7) bonus += 2;
  119. // 谨慎型 — 无硬伤、产权清晰
  120. if (style === '谨慎型' && property.isFiveYearOnly && !property.isGroundFloor && !property.isTopFloor) {
  121. bonus += 2;
  122. }
  123. // 感性/冲动型(隐性需求、刚需升级)— 装修亮眼
  124. if ((style === '冲动型' || type === '隐性需求型') && ['豪装'].some(t => property.highlightTags.includes(t))) {
  125. bonus += 3;
  126. }
  127. if ((style === '冲动型' || type === '刚需升级型') && ['豪装', '婚装'].some(t => property.highlightTags.includes(t))) {
  128. bonus += 2;
  129. }
  130. // 置换改善 — 人车分流+绿化好
  131. if (type === '置换改善型' && property.highlightTags.includes('人车分流') && property.communityQuality >= 8) {
  132. bonus += 1;
  133. }
  134. return Math.min(bonus, 3);
  135. }
  136. // ============================================================
  137. // 五、阶段 1:硬约束过滤
  138. // ============================================================
  139. function hardFilter(buyer, properties) {
  140. // 反推真实预算上限
  141. const loanCoefficient = 180; // 简化:30年贷款系数
  142. const maxLoan = buyer.monthlyPaymentCapacity * loanCoefficient / 10000;
  143. const realBudgetMax = Math.max(
  144. buyer.budgetMax,
  145. (buyer.downPayment + maxLoan) * 0.9
  146. );
  147. return properties.filter(p => {
  148. // F1: 预算上限(真实上限×1.1)
  149. if (p.totalPrice > realBudgetMax * 1.1) return false;
  150. // F2: 区域不匹配
  151. const districtMatch = buyer.targetDistricts.some(d => p.district.startsWith(d));
  152. if (!districtMatch) return false;
  153. // F3: 房龄超标
  154. if (p.buildingAge > buyer.buildingAgeMax) return false;
  155. // F4: 必须学区
  156. if (buyer.schoolDistrictRequired && !p.isSchoolDistrict) return false;
  157. if (buyer.schoolDistrictRequired && buyer.targetSchools && !buyer.targetSchools.includes(p.schoolName)) return false;
  158. // F5: 排斥项
  159. if (buyer.resistFactors.includes('底层') && p.isGroundFloor) return false;
  160. if (buyer.resistFactors.includes('顶楼') && p.isTopFloor) return false;
  161. if (buyer.resistFactors.includes('临街') && (p.highlightTags.includes('临街') || p.communityQuality <= 3)) return false;
  162. // F6: 特殊需求
  163. if (buyer.specialRequirements.includes('车位或可租车位') && p.parking === '无') return false;
  164. if (buyer.specialRequirements.includes('电梯') && !p.highlightTags.includes('电梯') && p.floorLevel !== '低') {
  165. // 简单判断:如果是低楼层且没有电梯标签,可能无电梯
  166. }
  167. if (buyer.specialRequirements.includes('人车分流') && !p.highlightTags.includes('人车分流')) return false;
  168. // F7: 面积硬下限
  169. if (p.area < buyer.areaMin * 0.85) return false;
  170. return true;
  171. });
  172. }
  173. // ============================================================
  174. // 六、阶段 2:加权评分
  175. // ============================================================
  176. function scoreDimension(buyer, property, weights) {
  177. const scores = {};
  178. // D1: 价格优势
  179. scores.priceAdvantage = property.priceAdvantage / 10;
  180. // D2: 户型匹配
  181. const layoutExact = buyer.preferredLayouts.includes(property.layout);
  182. const layoutPartial = buyer.preferredLayouts.some(pl => {
  183. const pMatch = pl.match(/(\d+)室(\d+)厅/);
  184. const propMatch = property.layout.match(/(\d+)室(\d+)厅/);
  185. if (pMatch && propMatch) {
  186. const pRooms = parseInt(pMatch[1]);
  187. const propRooms = parseInt(propMatch[1]);
  188. return pRooms === propRooms; // 室数相同算部分匹配
  189. }
  190. return false;
  191. });
  192. scores.layoutMatch = layoutExact ? 1.0 : layoutPartial ? 0.6 : 0.3;
  193. // D3: 面积匹配
  194. if (property.area >= buyer.areaMin && property.area <= buyer.areaMax) {
  195. scores.areaMatch = 1.0;
  196. } else if (property.area >= buyer.areaMin * 0.85 && property.area <= buyer.areaMax * 1.15) {
  197. const center = (buyer.areaMin + buyer.areaMax) / 2;
  198. const deviation = Math.abs(property.area - center) / center;
  199. scores.areaMatch = Math.max(0.3, 1 - deviation * 2);
  200. } else {
  201. scores.areaMatch = 0.2;
  202. }
  203. // D4: 装修品质
  204. const decorationMap = { '豪装': 1.0, '精装': 0.75, '简装': 0.4, '毛坯': 0.2 };
  205. const requiredDecoMap = { '豪装': 1.0, '精装': 0.75, '简装': 0.4, '不限': 0.0 };
  206. const requiredLevel = requiredDecoMap[buyer.decorationRequirement] || 0.5;
  207. const actualLevel = decorationMap[property.decoration] || 0.5;
  208. scores.decoration = actualLevel >= requiredLevel ? 1.0 : actualLevel / Math.max(requiredLevel, 0.1);
  209. // D5: 学区匹配
  210. if (!buyer.schoolDistrictRequired) {
  211. scores.schoolMatch = 1.0; // 不在乎学区,不影响评分
  212. } else {
  213. if (buyer.targetSchools && buyer.targetSchools.includes(property.schoolName)) {
  214. scores.schoolMatch = 1.0;
  215. } else if (property.isSchoolDistrict) {
  216. scores.schoolMatch = 0.5;
  217. } else {
  218. scores.schoolMatch = 0.0;
  219. }
  220. }
  221. // D6: 交通便利
  222. scores.transport = property.transportScore / 10;
  223. // D7: 小区品质
  224. scores.community = property.communityQuality / 10;
  225. // D8: 楼层匹配
  226. const floorMap = { '低': 0, '中': 1, '高': 2 };
  227. const buyerFloor = floorMap[buyer.floorPreference] !== undefined ? floorMap[buyer.floorPreference] : 1;
  228. const propFloor = floorMap[property.floorLevel] !== undefined ? floorMap[property.floorLevel] : 1;
  229. const floorDiff = Math.abs(buyerFloor - propFloor);
  230. scores.floorMatch = floorDiff === 0 ? 1.0 : floorDiff === 1 ? 0.6 : 0.3;
  231. // D9: 朝向匹配
  232. if (buyer.preferredOrientations.includes('不限')) {
  233. scores.orientation = 0.8;
  234. } else {
  235. const orientExact = buyer.preferredOrientations.some(o => property.orientation.includes(o));
  236. const orientPartial = buyer.preferredOrientations.some(o => property.orientation.includes(o.replace('南北通透', '南').replace('南向', '南')));
  237. scores.orientation = orientExact ? 1.0 : orientPartial ? 0.5 : 0.2;
  238. }
  239. // D10: 周边配套
  240. scores.surrounding = property.surroundingScore / 10;
  241. // 加权汇总
  242. let totalScore = 0;
  243. let totalWeight = 0;
  244. for (const [key, weight] of Object.entries(weights)) {
  245. if (scores[key] !== undefined) {
  246. totalScore += scores[key] * weight;
  247. totalWeight += weight;
  248. }
  249. }
  250. return {
  251. totalScore: totalWeight > 0 ? (totalScore / totalWeight) * 100 : 0,
  252. breakdown: scores,
  253. };
  254. }
  255. // ============================================================
  256. // 七、阶段 3:智能择优(卖点 + 心理加分)
  257. // ============================================================
  258. function calcHighlightBonus(buyerType, property) {
  259. let bonus = 0;
  260. for (const tag of property.highlightTags) {
  261. const tagBonus = HIGHLIGHT_BONUS[tag];
  262. if (tagBonus && tagBonus[buyerType]) {
  263. bonus += tagBonus[buyerType];
  264. }
  265. }
  266. return Math.min(bonus, 5);
  267. }
  268. // ============================================================
  269. // 八、主匹配函数
  270. // ============================================================
  271. function matchBuyer(buyer, properties, topN = 5) {
  272. const weights = WEIGHT_TEMPLATES[buyer.type] || WEIGHT_TEMPLATES['婚房刚需型'];
  273. // 阶段 1: 硬约束过滤
  274. const candidates = hardFilter(buyer, properties);
  275. // 阶段 2: 加权评分
  276. const scored = candidates.map(p => {
  277. const { totalScore, breakdown } = scoreDimension(buyer, p, weights);
  278. return { property: p, stage2Score: totalScore, breakdown };
  279. });
  280. // 阶段 3: 智能择优
  281. const final = scored.map(s => {
  282. const highlightBonus = calcHighlightBonus(buyer.type, s.property);
  283. const psychBonus = calcPsychologyBonus(buyer, s.property);
  284. const finalScore = s.stage2Score + highlightBonus + psychBonus;
  285. return {
  286. ...s,
  287. highlightBonus,
  288. psychBonus,
  289. finalScore,
  290. level: finalScore >= 90 ? '强烈推荐' : finalScore >= 80 ? '推荐' : finalScore >= 70 ? '备选' : '不推荐',
  291. };
  292. });
  293. // 排序
  294. final.sort((a, b) => b.finalScore - a.finalScore);
  295. return final.slice(0, topN);
  296. }
  297. // ============================================================
  298. // 九、沟通策略生成
  299. // ============================================================
  300. const STRATEGY_TEMPLATES = {
  301. '婚房刚需型': {
  302. style: '财务顾问+生活规划师',
  303. keyScript: (p, buyer) => `这套${p.layout}总价${p.totalPrice}万,首付约${Math.round(p.totalPrice*0.2)}万,月供大概${Math.round(p.totalPrice*0.8*0.0045*10000)}元。两居的客厅够大,以后改成三居也完全没问题。`,
  304. precautions: ['帮客户算清"多花10万首付=未来5年不换房"这笔账', '关注女方感受,感性决策占比大'],
  305. followUp: '首次推荐后2天发对比分析,1周内约带看',
  306. },
  307. '学区焦虑型': {
  308. style: '政策专家+数据提供者',
  309. keyScript: (p, buyer) => `这套对口${p.schoolName},近3年划片都没有变动,入学年限满足要求,学位也未被占用。这是去年该小区划片文件和升学率数据。`,
  310. precautions: ['必须准备书面证据:划片文件、学位占用情况', '客户严谨较真,不要口头承诺'],
  311. followUp: '首次推荐后立即发学区资料,3天内约实地看房',
  312. },
  313. '置换改善型': {
  314. style: '全流程管家',
  315. keyScript: (p, buyer) => `这套${p.layout}目前在售。您的老房子预计能卖${buyer.oldHouseEstimatedValue || 120}万,这套${p.totalPrice}万,贷款${p.totalPrice - (buyer.oldHouseEstimatedValue || 120)}万左右。如果两边节奏配合好,可以实现无缝衔接。`,
  316. precautions: ['提供"卖+买"一体化时间线方案', '决策链长,尽量约全家人一起看'],
  317. followUp: '了解老房子挂牌进展,同步推送新上房源',
  318. },
  319. '隐性需求型': {
  320. style: '需求翻译官+引导者',
  321. keyScript: (p, buyer) => `不用急着定,我们先多看几套对比一下。这套装修是亮点,您看这个阳台,以后周末在这里喝喝茶,感觉很舒服的。`,
  322. precautions: ['同一天带看2套差异大的房源做对比', '看房后引导客户说出喜欢/不喜欢哪里'],
  323. followUp: '每次带看后现场复盘感受,逐步收敛需求',
  324. },
  325. '刚需升级型': {
  326. style: '品质推手+性价比计算器',
  327. keyScript: (p, buyer) => `这套虽然比您预算多了点,但多一个独立书房和主卧套间。多花10万首付,月供只多500块,未来5年不用换房。`,
  328. precautions: ['先推达标房源再推品质房源,形成对比', '让客户看到"价值差"而不是"价格差"'],
  329. followUp: '先推一套80万,再推120万,让客户自己感受差异',
  330. },
  331. };
  332. function generateStrategy(buyer, property) {
  333. const template = STRATEGY_TEMPLATES[buyer.type] || STRATEGY_TEMPLATES['婚房刚需型'];
  334. return {
  335. style: template.style,
  336. openingScript: template.keyScript(property, buyer),
  337. precautions: template.precautions,
  338. followUp: template.followUp,
  339. };
  340. }
  341. // ============================================================
  342. // 十、格式化输出
  343. // ============================================================
  344. // 生成坦诚缺点(来自蓝领岗位匹配的启发:坦诚 > 隐瞒)
  345. function generateHonestCons(property) {
  346. const cons = [];
  347. if (property.buildingAge >= 20) cons.push('房龄较老,需关注管道老化和渗水情况');
  348. if (property.buildingAge >= 25) cons.push('房龄超过25年,贷款年限可能受限');
  349. if (property.decoration === '简装') cons.push('装修简单,入住前可能需翻新');
  350. if (property.decoration === '毛坯') cons.push('毛坯房,需额外准备装修预算约10-15万');
  351. if (property.isGroundFloor) cons.push('底层,需关注防潮和隐私问题');
  352. if (property.isTopFloor) cons.push('顶楼,夏季较热,需关注防水');
  353. if (property.parking === '无') cons.push('无车位,周边停车可能不便');
  354. if (property.communityQuality <= 4) cons.push('小区品质一般,绿化物业配套有限');
  355. if (property.transportScore <= 5) cons.push('交通便利度一般,公交/地铁覆盖较少');
  356. if (property.surroundingScore <= 5) cons.push('周边商业配套有限,生活便利度较低');
  357. if (!property.isFiveYearOnly && property.buildingAge < 5) cons.push('不满五,交易税费较高');
  358. if (property.floorLevel === '低' && !property.isGroundFloor) cons.push('低楼层,采光可能受遮挡影响');
  359. if (property.floorLevel === '高' && !property.isTopFloor && property.communityQuality <= 5) cons.push('高楼层,需确认电梯运行状况');
  360. if (!property.isSchoolDistrict && property.district.includes('天宁区-文化宫')) cons.push('非学区房,天宁区核心学区客户需注意');
  361. if (property.priceDropSpace <= 5) cons.push('业主议价空间较小,谈价弹性有限');
  362. return cons.length > 0 ? cons : ['无明显硬伤,整体较为均衡'];
  363. }
  364. function printResults(buyer, results) {
  365. console.log(`\n${'='.repeat(70)}`);
  366. console.log(`🎯 客户:${buyer.name}(${buyer.type})`);
  367. console.log(` 口述预算:${buyer.surfaceBudget}万 | 首付:${buyer.downPayment}万 | 月供承受:${buyer.monthlyPaymentCapacity}元`);
  368. console.log(` 目标区域:${buyer.targetDistricts.join('、')}`);
  369. console.log(` 核心关注:${buyer.coreConcerns.join(' > ')}`);
  370. console.log(`${'='.repeat(70)}`);
  371. if (results.length === 0) {
  372. console.log(`\n⚠️ 无匹配房源。建议:`);
  373. if (buyer.budgetMax < 100) console.log(` → 预算偏低,考虑提高预算或扩大区域`);
  374. if (buyer.schoolDistrictRequired) console.log(` → 学区要求严格,建议扩大目标学校范围`);
  375. return;
  376. }
  377. results.forEach((r, i) => {
  378. const p = r.property;
  379. const s = generateStrategy(buyer, p);
  380. console.log(`\n${'─'.repeat(70)}`);
  381. console.log(`🏠 #${i + 1} [${r.level}] ${p.community} · ${p.layout} · ${p.totalPrice}万`);
  382. console.log(` 综合分:${r.finalScore.toFixed(1)} (基础${r.stage2Score.toFixed(1)} + 卖点${r.highlightBonus} + 心理${r.psychBonus})`);
  383. console.log(` 区域:${p.district} | 面积:${p.area}㎡ | 楼层:${p.floor} | 朝向:${p.orientation}`);
  384. console.log(` 装修:${p.decoration} | 房龄:${p.buildingAge}年 | 学区:${p.isSchoolDistrict ? p.schoolName : '无'}`);
  385. console.log(` 亮点:${p.highlightTags.join(' · ')}`);
  386. console.log(` 业主:${p.ownerSituation} | 议价空间:${p.priceDropSpace}万`);
  387. // 坦诚缺点(来自蓝领匹配启发)
  388. const cons = generateHonestCons(p);
  389. console.log(` ⚠️ 坦诚提醒:${cons.join(';')}`);
  390. // 评分分解
  391. const bd = r.breakdown;
  392. console.log(` 📊 维度分解:价格${(bd.priceAdvantage*100).toFixed(0)} | 户型${(bd.layoutMatch*100).toFixed(0)} | 面积${(bd.areaMatch*100).toFixed(0)} | 装修${(bd.decoration*100).toFixed(0)} | 交通${(bd.transport*100).toFixed(0)} | 品质${(bd.community*100).toFixed(0)} | 楼层${(bd.floorMatch*100).toFixed(0)} | 朝向${(bd.orientation*100).toFixed(0)}`);
  393. console.log(`\n 💬 沟通策略(${s.style}):`);
  394. console.log(` "${s.openingScript}"`);
  395. console.log(` ⚠️ 注意事项:${s.precautions.join(';')}`);
  396. console.log(` 📅 跟进节奏:${s.followUp}`);
  397. });
  398. console.log(`\n${'─'.repeat(70)}`);
  399. console.log(`📊 筛选统计:全量${30}套 → 硬约束过滤后${results.length > 0 ? '匹配' : '0'}套 → Top-${results.length}`);
  400. console.log(`${'='.repeat(70)}\n`);
  401. }
  402. // ============================================================
  403. // 十一、入口
  404. // ============================================================
  405. function main() {
  406. const { buyers, properties } = loadData();
  407. // 命令行参数
  408. const args = process.argv.slice(2);
  409. const buyerId = args.includes('--buyer') ? args[args.indexOf('--buyer') + 1] : null;
  410. const topIdx = args.includes('--top') ? args.indexOf('--top') : -1;
  411. const topN = topIdx >= 0 ? parseInt(args[topIdx + 1]) || 5 : 5;
  412. const targets = buyerId
  413. ? buyers.filter(b => b.id === buyerId)
  414. : buyers;
  415. if (targets.length === 0) {
  416. console.error(`❌ 未找到客户 ${buyerId}`);
  417. console.error(` 可用客户ID:${buyers.map(b => b.id).join(', ')}`);
  418. process.exit(1);
  419. }
  420. targets.forEach(buyer => {
  421. const results = matchBuyer(buyer, properties, topN);
  422. printResults(buyer, results);
  423. });
  424. }
  425. if (require.main === module) {
  426. main();
  427. }
  428. // 导出供外部调用
  429. module.exports = { matchBuyer, generateStrategy, hardFilter, WEIGHT_TEMPLATES, STRATEGY_TEMPLATES };