deep_insights.py 94 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679
  1. """
  2. Deep-analysis insight generators for weekly and monthly reports.
  3. Each function returns list[dict] with {'title': str, 'content': str}.
  4. """
  5. # =============================================================================
  6. # WEEKLY INSIGHTS
  7. # =============================================================================
  8. def weekly_insights(page_type: str, metrics: dict, context: dict) -> list[dict]:
  9. """Dispatch to specific weekly page insight functions."""
  10. dispatch = {
  11. 'weekly_summary': _insight_weekly_summary,
  12. 'weekly_trend': _insight_weekly_trend,
  13. 'weekly_wow': _insight_weekly_wow,
  14. 'weekly_region': _insight_weekly_region,
  15. 'weekly_country': _insight_weekly_country,
  16. 'weekly_team': _insight_weekly_team,
  17. 'weekly_issue': _insight_weekly_issue,
  18. 'weekly_plan': _insight_weekly_plan,
  19. }
  20. fn = dispatch.get(page_type)
  21. return fn(metrics, context) if fn else []
  22. def _insight_weekly_summary(metrics: dict, context: dict) -> list[dict]:
  23. """Page 2: 周内节奏分析、月度进度、关键驱动因素"""
  24. items = []
  25. daily_trend = metrics.get('daily_trend', {})
  26. total_qty = metrics.get('total_qty', 0)
  27. tracking_orders = metrics.get('tracking_orders', 0)
  28. prev_tracking_orders = metrics.get('prev_tracking_orders', 0)
  29. top_countries = metrics.get('top_countries', {})
  30. region_dist = metrics.get('region_dist', {})
  31. # ① 周内节奏分析
  32. if daily_trend:
  33. dates = list(daily_trend.keys())
  34. vals = list(daily_trend.values())
  35. peak_idx = max(range(len(vals)), key=lambda i: vals[i])
  36. low_idx = min(range(len(vals)), key=lambda i: vals[i])
  37. peak_date, peak_val = dates[peak_idx], vals[peak_idx]
  38. low_date, low_val = dates[low_idx], vals[low_idx]
  39. prev_avg = metrics.get('prev_avg_daily_orders', 0)
  40. above_avg = sum(1 for v in vals if prev_avg and v > prev_avg)
  41. rhythm = (
  42. f'本周订单呈"{peak_date}冲高、{low_date}回落"特征,峰值{peak_val}单、低谷{low_val}单,'
  43. f'波动幅度{_safe_div(peak_val, low_val) if low_val else 0:.1f}倍。'
  44. f'{"高于" if above_avg >= len(vals)//2 else "低于"}上周均值天数占比{above_avg}/{len(vals)},'
  45. f'整体节奏{"前移" if peak_idx <= len(vals)//2 else "后移"}。'
  46. f'建议分析低谷日是否受节假日或客户账期影响,优化排产与沟通节奏。'
  47. )
  48. else:
  49. rhythm = (
  50. f'本周累计订单{tracking_orders}单、{total_qty}台,'
  51. f'日均{metrics.get("avg_daily_orders", 0)}单。'
  52. f'由于缺乏分日数据,暂无法识别周内节奏特征。'
  53. f'建议完善日报 granularity,以便识别周几效应并优化资源投放节奏。'
  54. )
  55. items.append({'title': '📅 周内节奏分析', 'content': rhythm})
  56. # ② 与上周对比偏移
  57. wow_pct = _pct_change(tracking_orders, prev_tracking_orders)
  58. if prev_tracking_orders:
  59. shift = (
  60. f'本周{tracking_orders}单较上周{prev_tracking_orders}单{_fmt_chg_dir(wow_pct)}{_fmt_pct(wow_pct)},'
  61. f'{"呈加速态势" if (wow_pct or 0) > 10 else "增长放缓" if (wow_pct or 0) > 0 else "出现回落"}。'
  62. f'若趋势持续,下月累计将{"超额" if (wow_pct or 0) > 0 else "缺口"}约{abs((wow_pct or 0)):.0f}%月度目标。'
  63. f'建议结合pipeline深度评估:增长来自存量转化还是新增签约,两种来源的可持续性差异显著。'
  64. )
  65. else:
  66. shift = (
  67. f'本周订单量{tracking_orders}单,缺乏上周同期数据作为基准。'
  68. f'建议尽快建立周环比追踪机制,识别趋势方向。当前可依据日均{metrics.get("avg_daily_orders", 0)}单推算月度节奏,'
  69. f'若保持稳定,整月预计约{metrics.get("avg_daily_orders", 0) * 30:.0f}单。'
  70. )
  71. items.append({'title': '📈 周环比趋势偏移', 'content': shift})
  72. # ③ 月度进度推演
  73. monthly_target = context.get('monthly_target', 0)
  74. if monthly_target and total_qty:
  75. progress_pct = round(total_qty / monthly_target * 100, 1)
  76. week_of_month = context.get('week_of_month', 1)
  77. expected = week_of_month / 4 * 100
  78. gap = progress_pct - expected
  79. progress = (
  80. f'本周完成{total_qty}台,占月度目标{monthly_target}台的{progress_pct}%,'
  81. f'按"四周均衡"预期应达{expected:.1f}%,{"领先" if gap >= 0 else "落后"}{abs(gap):.1f}个百分点。'
  82. f'{"若保持当前 pace,月度有望超额完成。" if gap >= 0 else "剩余周需加速追赶,建议启动冲刺机制。"}'
  83. f'重点关注最后一周产能与舱位是否匹配冲刺需求。'
  84. )
  85. else:
  86. progress = (
  87. f'本周完成{total_qty}台,因未设定月度目标或缺乏台数数据,暂无法计算进度占比。'
  88. f'建议业务部门明确月度销量目标,以便周报自动生成目标达成率并触发预警。'
  89. f'当前可先以本周为基准,要求后续每周环比增长不低于5%作为软约束。'
  90. )
  91. items.append({'title': '🎯 月度进度推演', 'content': progress})
  92. # ④ 关键驱动因素(国家维度)
  93. if top_countries:
  94. top1_country = list(top_countries.keys())[0]
  95. first_val = list(top_countries.values())[0]
  96. top1_qty = first_val if isinstance(first_val, int) else first_val.get('qty', 0)
  97. top3_qty = 0
  98. for v in list(top_countries.values())[:3]:
  99. top3_qty += v if isinstance(v, int) else v.get('qty', 0)
  100. concentration = round(top3_qty / total_qty * 100, 1) if total_qty else 0
  101. driver = (
  102. f'本周Top 3国家贡献{top3_qty}台(占比{concentration}%),其中{top1_country}以{top1_qty}台领跑。'
  103. f'{"大客户驱动特征明显,单国依赖度较高" if concentration > 40 else "国家分布相对均衡,抗波动能力较强"}。'
  104. f'若Top 1国家订单来自单一客户,建议评估该客户下月复购概率;'
  105. f'若来自多客户分散订单,则说明该国市场渗透进入良性循环。'
  106. )
  107. else:
  108. driver = (
  109. f'本周累计{total_qty}台,暂无国家维度分解数据。'
  110. f'建议完善订单数据中国家字段,以便识别关键驱动市场并制定差异化资源投入策略。'
  111. f'当前阶段可通过负责人访谈定性判断:本周增长主要来自老客户返单还是新市场突破。'
  112. )
  113. items.append({'title': '💡 关键驱动因素', 'content': driver})
  114. # ⑤ 区域引擎识别
  115. if region_dist:
  116. regions = sorted(region_dist.items(), key=lambda x: x[1].get('qty', 0), reverse=True)
  117. r1_name, r1_data = regions[0]
  118. r1_qty = r1_data.get('qty', 0)
  119. r1_pct = r1_data.get('pct', 0)
  120. top_c = [c["country"] for c in r1_data.get("top_countries", [])[:2]]
  121. region_text = (
  122. f'{r1_name}本周贡献{r1_qty}台({r1_pct}%),为当前第一大区域引擎。'
  123. f'其Top国家为{"、".join(top_c)}。'
  124. f'建议对该区域实施"深耕+复制"策略:在成熟国家推进增值服务提升客单价,'
  125. f'同时将成功经验复制至同区域二线国家,最大化区域协同效应。'
  126. )
  127. else:
  128. region_text = (
  129. f'本周订单{tracking_orders}单,缺乏区域维度数据。'
  130. f'建议建立国家→区域映射表,以便自动识别区域引擎并评估资源投入ROI。'
  131. f'当前可通过订单号前缀或客户归属地手动标注区域,作为过渡方案。'
  132. )
  133. items.append({'title': '🌍 区域引擎识别', 'content': region_text})
  134. # ⑥ 结构健康度速览
  135. avg_qty = metrics.get('avg_qty_per_order', 0)
  136. prev_avg_qty = metrics.get('prev_avg_qty', 0) or context.get('prev_avg_qty', 0)
  137. avg_chg = _pct_change(avg_qty, prev_avg_qty)
  138. health = (
  139. f'本周单均台数{avg_qty}台,{"较上周" + _fmt_chg_dir(avg_chg) + _fmt_pct(avg_chg) if prev_avg_qty else "基准待建立"}。'
  140. f'{"大单占比提升,客户结构优化" if (avg_chg or 0) > 0 else "中小单为主,需关注大客户转化" if avg_qty else "数据缺失"}。'
  141. f'结合{metrics.get("countries", 0)}个目的国分析,'
  142. f'{"市场覆盖广但单国深度不足" if metrics.get("countries", 0) > 15 and avg_qty < 20 else ""}'
  143. f'建议下周重点跟进单均台数大于50台的大单,锁定其对月度目标的贡献。'
  144. )
  145. items.append({'title': '⚖️ 结构健康度速览', 'content': health})
  146. return items
  147. def _insight_weekly_trend(metrics: dict, context: dict) -> list[dict]:
  148. """Page 3: 周内波动归因、与上周结构性差异、趋势持续性"""
  149. items = []
  150. daily_trend = metrics.get('daily_trend', {})
  151. prev_daily_trend = context.get('prev_daily_trend', {})
  152. tracking_orders = metrics.get('tracking_orders', 0)
  153. prev_tracking_orders = metrics.get('prev_tracking_orders', 0)
  154. forecast_next = metrics.get('forecast_next', 0)
  155. total_qty = metrics.get('total_qty', 0)
  156. # ① 周内波动归因
  157. if daily_trend:
  158. dates = list(daily_trend.keys())
  159. vals = list(daily_trend.values())
  160. avg_val = sum(vals) / len(vals)
  161. low_days = [d for d, v in daily_trend.items() if v < avg_val * 0.7]
  162. high_days = [d for d, v in daily_trend.items() if v > avg_val * 1.3]
  163. fluctuation = (
  164. f'本周日均{avg_val:.0f}单,{"、".join(high_days) if high_days else "无"}为显著高于均值的高活跃日,'
  165. f'{"、".join(low_days) if low_days else "无"}为低于均值0.7倍的低谷日。'
  166. f'低谷日需排查是否为节假日(当地法定假期)、客户月度/季度账期结算日,或系统批量录入延迟。'
  167. f'建议建立"低谷日归因登记簿",记录每次低谷的外部因素,积累预测模型训练数据。'
  168. )
  169. else:
  170. fluctuation = (
  171. f'本周累计{tracking_orders}单,缺乏分日趋势数据。'
  172. f'建议每日统计新增订单数,以便识别周内波动模式。当前可通过负责人日报定性判断:'
  173. f'本周是否有1-2天明显低于其他工作日,若有,需排查外部干扰因素并提前制定应对措施。'
  174. )
  175. items.append({'title': '🔍 周内波动归因', 'content': fluctuation})
  176. # ② 与上周结构性差异
  177. if daily_trend and prev_daily_trend:
  178. prev_vals = list(prev_daily_trend.values())
  179. curr_vals = list(daily_trend.values())
  180. curr_shape = "前高后低" if sum(curr_vals[:len(curr_vals)//2]) > sum(curr_vals[len(curr_vals)//2:]) else "前低后高"
  181. prev_shape = "前高后低" if sum(prev_vals[:len(prev_vals)//2]) > sum(prev_vals[len(prev_vals)//2:]) else "前低后高"
  182. structural = (
  183. f'本周走势形态为"{curr_shape}",上周为"{prev_shape}",{"形态一致" if curr_shape == prev_shape else "形态发生反转"}。'
  184. f'{"说明客户下单节奏稳定,周末效应或账期规律持续生效。" if curr_shape == prev_shape else "说明外部条件发生变化,可能受节假日错位或大单时点影响。"}'
  185. f'建议对比两周的Top 3客户下单日期,判断结构差异来自系统性因素还是偶然大单。'
  186. )
  187. else:
  188. structural = (
  189. f'本周订单{tracking_orders}单,'
  190. f'{"较上周" + _fmt_chg_dir(_pct_change(tracking_orders, prev_tracking_orders)) + _fmt_pct(_pct_change(tracking_orders, prev_tracking_orders)) if prev_tracking_orders else "缺乏上周分日数据"}。'
  191. f'建议保留每周分日数据,以便进行走势形态对比(前高后低 vs 前低后高)。'
  192. f'形态一致性是判断趋势可持续性的重要信号。'
  193. )
  194. items.append({'title': '📊 与上周结构性差异', 'content': structural})
  195. # ③ 趋势持续性判断
  196. wow_pct = _pct_change(tracking_orders, prev_tracking_orders)
  197. if forecast_next:
  198. sustain = (
  199. f'Pipeline预测下月交付{forecast_next}台,结合本周{tracking_orders}单(wow {_fmt_pct(wow_pct)}),'
  200. f'{"趋势向上且pipeline充足,持续性较强" if (wow_pct or 0) > 5 and forecast_next > total_qty * 0.8 else ""}'
  201. f'{"趋势向好但pipeline不足,需警惕后继乏力" if (wow_pct or 0) > 5 and forecast_next <= total_qty * 0.5 else ""}'
  202. f'{"趋势承压,pipeline也偏保守,双重承压" if (wow_pct or 0) < 0 else ""}'
  203. f'。建议基于A+B阶段存量订单数评估真实转化潜力,'
  204. f'若早期pipeline充足,则本周增长具备延续性;若依赖存量冲刺,则下周可能回落。'
  205. )
  206. else:
  207. sustain = (
  208. f'本周订单{tracking_orders}单(wow {_fmt_pct(wow_pct)}),因缺乏pipeline预测数据,'
  209. f'趋势持续性需通过阶段结构间接判断。'
  210. f'若A+B阶段订单占比大于40%,说明前期储备充足,下周有望维持;'
  211. f'若E+F阶段占比过高,则本周增长可能来自集中交付的一次性脉冲,持续性存疑。'
  212. )
  213. items.append({'title': '🔮 趋势持续性判断', 'content': sustain})
  214. # ④ 外部因素关联
  215. support_cats = metrics.get('support_categories', {})
  216. if support_cats:
  217. top_cat = max(support_cats.items(), key=lambda x: x[1])
  218. external = (
  219. f'本周支持需求中"{top_cat[0]}"类最多({top_cat[1]}项),'
  220. f'{"多为物流/船期问题,说明交付端承压,可能影响客户下单信心" if "物流" in top_cat[0] or "船期" in top_cat[0] else ""}'
  221. f'{"多为财务/收款问题,说明资金端存在卡点,可能拖慢合同锁定节奏" if "财务" in top_cat[0] or "收款" in top_cat[0] else ""}'
  222. f'。建议将该类问题的根因归类为流程/政策/外部三类,'
  223. f'流程类内部优化,政策类提前预警,外部类建立替代方案(如备用船期/汇率对冲)。'
  224. )
  225. else:
  226. external = (
  227. f'本周暂无支持需求数据。建议主动收集:汇率波动、目的国进口政策调整、船期延误等外部事件,'
  228. f'并评估其对本周订单节奏的干扰程度。'
  229. f'建立"外部因素-订单波动"关联看板,可显著提升趋势归因的准确性。'
  230. )
  231. items.append({'title': '🌐 外部因素关联', 'content': external})
  232. # ⑤ 异常点识别
  233. if daily_trend:
  234. vals = list(daily_trend.values())
  235. mean_v = sum(vals) / len(vals)
  236. std = (sum((v - mean_v) ** 2 for v in vals) / len(vals)) ** 0.5
  237. outliers = [(d, v) for d, v in daily_trend.items() if abs(v - mean_v) > 1.5 * std]
  238. if outliers:
  239. outlier_text = (
  240. f'本周{"、".join([d for d, _ in outliers])}出现异常波动,偏离均值{mean_v:.0f}单超过1.5倍标准差。'
  241. f'异常日需区分"机会型"(大客户集中下单)与"风险型"(系统故障/数据补录)。'
  242. f'建议对异常日逐单标注原因,长期积累后可建立异常检测规则,实现自动化预警。'
  243. )
  244. else:
  245. outlier_text = (
  246. f'本周各日订单围绕均值{mean_v:.0f}单正常波动,无显著异常点。'
  247. f'说明业务运行平稳,外部干扰较少。建议保持当前运营节奏,'
  248. f'同时将本周作为"平稳基线"保存,用于未来异常检测的对比基准。'
  249. )
  250. else:
  251. outlier_text = (
  252. f'缺乏分日数据,无法识别周内异常点。'
  253. f'建议建立日报机制后,采用"均值+1.5倍标准差"规则自动标记异常交易日。'
  254. )
  255. items.append({'title': '⚠️ 异常点识别', 'content': outlier_text})
  256. # ⑥ 下周走势预判
  257. if daily_trend and prev_daily_trend:
  258. curr_avg = sum(daily_trend.values()) / len(daily_trend)
  259. prev_avg = sum(prev_daily_trend.values()) / len(prev_daily_trend)
  260. momentum = _pct_change(curr_avg, prev_avg)
  261. pred_low = int(curr_avg * 0.85)
  262. pred_high = int(curr_avg * 1.15)
  263. forecast = (
  264. f'基于本周日均{curr_avg:.0f}单及环比动量{_fmt_pct(momentum)},'
  265. f'预测下周日均订单区间{pred_low}-{pred_high}单,整周预计{pred_low * 5}-{pred_high * 5}单。'
  266. f'{"若大客户持续下单,有望突破区间上限" if (momentum or 0) > 10 else ""}'
  267. f'{"若外部因素未改善,可能回落至区间下限" if (momentum or 0) < 0 else ""}'
  268. f'。建议每日晨会对照预测区间,偏差大于20%时触发专项复盘。'
  269. )
  270. else:
  271. forecast = (
  272. f'当前在跟订单{tracking_orders}单,基于历史经验,下周预计维持在'
  273. f'{int(tracking_orders * 0.85)}-{int(tracking_orders * 1.15)}单区间。'
  274. f'建议重点关注A→B阶段转化速率,这是下周增量的最可预测来源。'
  275. )
  276. items.append({'title': '📉 下周走势预判', 'content': forecast})
  277. return items
  278. def _insight_weekly_wow(metrics: dict, context: dict) -> list[dict]:
  279. """Page 4: 各环节转化效率、瓶颈环节深度诊断、库存/资金占用"""
  280. items = []
  281. status_wow = metrics.get('status_wow', {})
  282. status_dist = metrics.get('status_dist', {})
  283. total_orders = metrics.get('tracking_orders', 0)
  284. def _get_status(name):
  285. return status_dist.get(name, 0)
  286. # ① 各环节转化效率
  287. a_name, b_name = '合同拟定中', '已锁定合同待付订金'
  288. a_curr = status_wow.get(a_name, {}).get('current', 0)
  289. a_prev = status_wow.get(a_name, {}).get('previous', 0)
  290. b_curr = status_wow.get(b_name, {}).get('current', 0)
  291. b_prev = status_wow.get(b_name, {}).get('previous', 0)
  292. if a_prev and b_prev:
  293. prev_conv = round(b_prev / a_prev * 100, 1)
  294. curr_conv = round(b_curr / a_curr * 100, 1) if a_curr else 0
  295. conv_chg = _pct_change(curr_conv, prev_conv)
  296. conv = (
  297. f'A→B阶段转化率本周{curr_conv}%({b_curr}/{a_curr}),上周{prev_conv}%({b_prev}/{a_prev}),'
  298. f'{_fmt_chg_dir(conv_chg)}{_fmt_pct(conv_chg)}。'
  299. f'{"转化率提升,说明合同推进效率改善" if (conv_chg or 0) > 0 else "转化率下降,合同锁定遇阻"}。'
  300. f'建议拆解转化失败的根因:客户侧(审批慢/资金未到位)vs 我方侧(条款争议/响应慢),针对性优化。'
  301. )
  302. else:
  303. conv = (
  304. f'本周A阶段{a_curr}单、B阶段{b_curr}单,因缺乏上周A/B分阶段数据,无法计算转化率变化。'
  305. f'建议建立阶段快照机制,每周固定时点统计各阶段存量。当前可先设定A→B周转化目标为A阶段存量的15%,'
  306. f'并落实到具体负责人。'
  307. )
  308. items.append({'title': '⚡ A→B转化效率', 'content': conv})
  309. # ② 瓶颈环节深度诊断
  310. changes = [(name, data.get('change_pct', 0) or 0) for name, data in status_wow.items()]
  311. if changes:
  312. worst = min(changes, key=lambda x: x[1])
  313. best = max(changes, key=lambda x: x[1])
  314. bottleneck = (
  315. f'本周降幅最大环节为"{worst[0]}"({_fmt_pct(worst[1])}),增幅最大为"{best[0]}"(+{_fmt_pct(best[1])})。'
  316. f'{"若降幅环节为E/F,说明交付端受阻;若为A/B,则前端获客或转化遇困。"}'
  317. f'建议对{worst[0]}环节启动"五问法"根因分析:'
  318. f'是流程卡点、资源不足、还是外部政策突变?找到根因后48h内出具改进方案。'
  319. )
  320. else:
  321. bottleneck = (
  322. f'本周各阶段环比数据不完整,暂无法识别瓶颈环节。'
  323. f'建议建立每周阶段快照对比机制,重点监控C→D(生产)和D→E(尾款)两个关键转化节点。'
  324. f'这两个节点直接决定交付周期和资金回笼速度,是 Weekly Review 的核心指标。'
  325. )
  326. items.append({'title': '📉 瓶颈环节深度诊断', 'content': bottleneck})
  327. # ③ 库存与资金占用分析
  328. c_curr = status_wow.get('已付订金待生产', {}).get('current', 0)
  329. d_curr = status_wow.get('已生产待付尾款', {}).get('current', 0)
  330. c_prev = status_wow.get('已付订金待生产', {}).get('previous', 0)
  331. d_prev = status_wow.get('已生产待付尾款', {}).get('previous', 0)
  332. cd_total = c_curr + d_curr
  333. cd_prev = c_prev + d_prev
  334. cd_chg = _pct_change(cd_total, cd_prev)
  335. inventory = (
  336. f'生产端(C+D)本周合计{cd_total}单,较上周{_fmt_chg_dir(cd_chg)}{_fmt_pct(cd_chg)}。'
  337. f'{"生产端积压加重,资金与产能占用上升" if (cd_chg or 0) > 10 else "生产端平稳,库存压力可控" if abs(cd_chg or 0) <= 10 else "生产端去化加速,周转改善"}。'
  338. f'D阶段{d_curr}单为核心风险点:已生产未收款,每单约占用产能和资金。'
  339. f'建议对D阶段大于14天的订单启动专项催收,缩短资金占用周期。'
  340. )
  341. items.append({'title': '💰 库存与资金占用分析', 'content': inventory})
  342. # ④ 发运端效率
  343. e_curr = status_wow.get('已付尾款待发运', {}).get('current', 0)
  344. f_curr = status_wow.get('已发运', {}).get('current', 0)
  345. e_prev = status_wow.get('已付尾款待发运', {}).get('previous', 0)
  346. ef_total = e_curr + f_curr
  347. ef_text = (
  348. f'发运端(E+F)本周合计{ef_total}单,其中待发运{e_curr}单、已发运{f_curr}单。'
  349. f'待发运较上周{_fmt_chg_dir(_pct_change(e_curr, e_prev))}{_fmt_pct(_pct_change(e_curr, e_prev))}。'
  350. f'{"待发运积压上升,需排查船期/舱位/报关瓶颈" if e_curr > (e_prev * 1.2 if e_prev else 0) else "发运节奏顺畅,交付闭环效率良好"}。'
  351. f'建议物流部门每周提供船期表,销售据此与客户对齐收货预期,减少催单消耗的管理精力。'
  352. )
  353. items.append({'title': '🚢 发运端效率', 'content': ef_text})
  354. # ⑤ 漏斗健康度综合评分
  355. if total_orders:
  356. early = _get_status('合同拟定中') + _get_status('已锁定合同待付订金')
  357. mid = _get_status('已付订金待生产') + _get_status('已生产待付尾款')
  358. late = _get_status('已付尾款待发运') + _get_status('已发运')
  359. early_pct = round(early / total_orders * 100, 1)
  360. mid_pct = round(mid / total_orders * 100, 1)
  361. late_pct = round(late / total_orders * 100, 1)
  362. health = (
  363. f'本周漏斗结构:前期{early_pct}%({early}单)、中期{mid_pct}%({mid}单)、后期{late_pct}%({late}单)。'
  364. f'{"前期占比偏高,pipeline充足但转化压力较大" if early_pct > 40 else ""}'
  365. f'{"中期占比偏高,生产端承压,需关注产能瓶颈" if mid_pct > 40 else ""}'
  366. f'{"后期占比偏高,交付冲刺期,需确保物流资源到位" if late_pct > 40 else ""}'
  367. f'理想结构为前期35%-中期35%-后期30%,当前{"接近理想" if 30 <= early_pct <= 40 and 30 <= mid_pct <= 40 else "偏离理想,需针对性调整"}。'
  368. )
  369. else:
  370. health = (
  371. f'本周暂无订单数据,无法计算漏斗结构。'
  372. f'建议建立标准化漏斗健康度评分模型:前期35%-中期35%-后期30%为基准,'
  373. f'偏离超过10个百分点时自动触发预警并推荐改进动作。'
  374. )
  375. items.append({'title': '🏥 漏斗健康度综合评分', 'content': health})
  376. return items
  377. def _insight_weekly_region(metrics: dict, context: dict) -> list[dict]:
  378. """Page 5: 区域战略优先级、区域间协同、新兴市场孵化"""
  379. items = []
  380. region_dist = metrics.get('region_dist', {})
  381. total_qty = metrics.get('total_qty', 0)
  382. prev_region_dist = context.get('prev_region_dist', {})
  383. if not region_dist:
  384. return [
  385. {'title': '💡 区域战略优先级', 'content': '本周暂无区域分布数据。建议完善订单数据中的国家与区域映射,以便识别明星区域与问题区域,优化资源投放策略。'},
  386. {'title': '📈 区域间协同', 'content': '缺乏区域维度数据,暂无法评估区域间协同效应。建议收集相邻区域订单关联数据,识别是否存在客户转介绍或区域联动带来的增长。'},
  387. ]
  388. # ① 区域战略优先级矩阵
  389. regions = []
  390. for name, data in region_dist.items():
  391. qty = data.get('qty', 0)
  392. pct = data.get('pct', 0)
  393. prev_qty = prev_region_dist.get(name, {}).get('qty', 0) if prev_region_dist else 0
  394. growth = _pct_change(qty, prev_qty)
  395. regions.append((name, qty, pct, growth))
  396. stars = [r for r in regions if r[2] > 20 and (r[3] is None or r[3] > 0)]
  397. cows = [r for r in regions if r[2] > 20 and (r[3] is not None and r[3] <= 0)]
  398. questions = [r for r in regions if r[2] <= 20 and (r[3] is None or r[3] > 0)]
  399. dogs = [r for r in regions if r[2] <= 20 and (r[3] is not None and r[3] <= 0)]
  400. matrix = (
  401. f'基于"占比×增速"矩阵分析:'
  402. f'{"明星区域(高占比+增长):" + "、".join([r[0] for r in stars[:2]]) + ",应继续加大投入;" if stars else ""}'
  403. f'{"现金牛(高占比+放缓):" + "、".join([r[0] for r in cows[:2]]) + ",维持投入收割利润;" if cows else ""}'
  404. f'{"问题区域(低占比+增长):" + "、".join([r[0] for r in questions[:2]]) + ",需评估是否加大培育;" if questions else ""}'
  405. f'{"瘦狗区域(低占比+下滑):" + "、".join([r[0] for r in dogs[:2]]) + ",考虑收缩或调整策略。" if dogs else ""}'
  406. )
  407. if not any([stars, cows, questions, dogs]):
  408. matrix = (
  409. f'本周各区域占比与增速数据不足以完成四象限分类。'
  410. f'建议建立区域级周环比追踪,当区域数据完整后可自动生成波士顿矩阵并推荐资源配置策略。'
  411. )
  412. items.append({'title': '💡 区域战略优先级矩阵', 'content': matrix})
  413. # ② 区域间协同效应
  414. top_regions = sorted(regions, key=lambda x: x[1], reverse=True)[:3]
  415. if len(top_regions) >= 2:
  416. synergy = (
  417. f'{top_regions[0][0]}本周贡献{top_regions[0][1]}台领跑,{top_regions[1][0]}以{top_regions[1][1]}台紧随其后。'
  418. f'若两区域存在客户转介绍或同一代理商覆盖,则具备协同放大效应。'
  419. f'建议复盘{top_regions[0][0]}的成功经验(车型偏好/渠道模式/定价策略),'
  420. f'形成标准化打法后向{top_regions[1][0]}及同类区域复制,降低试错成本。'
  421. )
  422. else:
  423. synergy = (
  424. f'本周{top_regions[0][0]}为绝对主导区域,其他区域占比偏低。'
  425. f'单极结构下协同效应有限,建议评估是否通过"成熟区带新区"机制,'
  426. f'让成熟区域负责人兼任相邻新兴市场顾问,实现经验外溢。'
  427. )
  428. items.append({'title': '📈 区域间协同效应', 'content': synergy})
  429. # ③ 新兴市场孵化
  430. top8_countries = set()
  431. for data in region_dist.values():
  432. for c in data.get('top_countries', []):
  433. top8_countries.add(c.get('country', ''))
  434. all_countries_count = metrics.get('countries', 0)
  435. if all_countries_count > len(top8_countries):
  436. emerging = (
  437. f'本周覆盖{all_countries_count}国,Top 8框架内国家{len(top8_countries)}个,'
  438. f'另有{all_countries_count - len(top8_countries)}国为框架外新兴市场。'
  439. f'若框架外国家本周出现首单或复购,说明市场孵化取得突破。'
  440. f'建议对框架外国家建立"观察名单",单月订单大于3台即纳入正式跟踪,并匹配专项资源。'
  441. )
  442. else:
  443. emerging = (
  444. f'本周覆盖{all_countries_count}国,订单高度集中在Top国家。'
  445. f'新兴市场孵化进度较慢,建议设定"每月突破1个新国家"的拓展目标,'
  446. f'通过参加区域性车展、与当地经销商建立合作等方式扩大市场覆盖。'
  447. )
  448. items.append({'title': '🌱 新兴市场孵化', 'content': emerging})
  449. # ④ 区域投入ROI评估
  450. if regions:
  451. growths = [r[3] for r in regions if r[3] is not None]
  452. avg_growth = sum(growths) / len(growths) if growths else 0
  453. roi = (
  454. f'本周区域平均增速{_fmt_pct(avg_growth)}。'
  455. f'{"增速为正的区域的投入产出比优于整体,建议将增量预算向这些区域倾斜" if avg_growth > 0 else "整体增速承压,建议收缩低效区域投入,集中资源保高潜市场"}。'
  456. f'ROI评估应综合考虑订单量、利润率和售后成本:高订单低利润区域需谨慎追加投入,'
  457. f'低订单高利润区域(如配件服务收入高)反而值得深耕。'
  458. )
  459. else:
  460. roi = (
  461. f'缺乏区域增速数据,暂无法计算投入ROI。'
  462. f'建议建立区域级损益表,至少包含订单量、毛利率、物流成本、售后成本四项指标,'
  463. f'每季度评估一次区域真实贡献度,优化资源分配。'
  464. )
  465. items.append({'title': '💰 区域投入ROI评估', 'content': roi})
  466. # ⑤ 区域风险分散
  467. top1_region_pct = max(r[2] for r in regions) if regions else 0
  468. risk = (
  469. f'第一大区域占比{top1_region_pct}%,{"集中度偏高,存在单区域政策/汇率波动风险" if top1_region_pct > 50 else "集中度适中,风险分散良好"}。'
  470. f'{"建议3个月内将第一大区域占比压降至50%以下,通过培育第二梯队实现结构优化" if top1_region_pct > 50 else "建议继续巩固现有均衡格局,同时培育1-2个潜力区域作为第三极"}。'
  471. f'区域多元化是抵御单一市场政策风险的最有效手段。'
  472. )
  473. items.append({'title': '⚠️ 区域风险分散', 'content': risk})
  474. return items
  475. def _insight_weekly_country(metrics: dict, context: dict) -> list[dict]:
  476. """Page 6: 国家组合健康度、大客户集中度、竞争格局"""
  477. items = []
  478. top_countries = metrics.get('top_countries', {})
  479. top_countries_change = metrics.get('top_countries_change', {})
  480. total_qty = metrics.get('total_qty', 0)
  481. top6_concentration_pct = metrics.get('top6_concentration_pct', 0)
  482. if not top_countries:
  483. return [
  484. {'title': '💡 国家组合健康度', 'content': '本周暂无国家分布数据。建议完善目的国家字段,以便评估国家组合集中度风险并制定分散策略。'},
  485. ]
  486. country_qty = {}
  487. for c, v in top_countries.items():
  488. country_qty[c] = v if isinstance(v, int) else v.get('qty', 0)
  489. sorted_countries = sorted(country_qty.items(), key=lambda x: x[1], reverse=True)
  490. top3_qty = sum(v for _, v in sorted_countries[:3])
  491. top3_pct = round(top3_qty / total_qty * 100, 1) if total_qty else 0
  492. # ① 国家组合健康度
  493. health = (
  494. f'Top 3国家合计{top3_qty}台({top3_pct}%),{"超过40%警戒线,单国波动对整体业绩影响显著" if top3_pct > 40 else "低于40%,国家分布相对健康"}。'
  495. f'{"Top 6集中度" + str(top6_concentration_pct) + "%进一步印证了大客户/大国家驱动特征" if top6_concentration_pct > 60 else ""}'
  496. f'建议设定"Top 3占比小于40%、Top 6小于65%"的组合健康目标,'
  497. f'通过培育第二梯队国家(4-8名)逐步降低头部依赖。'
  498. )
  499. items.append({'title': '💡 国家组合健康度', 'content': health})
  500. # ② 大客户集中度
  501. top1_country, top1_qty = sorted_countries[0]
  502. chg_data = top_countries_change.get(top1_country, {})
  503. chg_pct = chg_data.get('change_pct') if isinstance(chg_data, dict) else None
  504. concentration = (
  505. f'{top1_country}本周{top1_qty}台领跑,环比{_fmt_pct(chg_pct) if chg_pct is not None else "—"}。'
  506. f'若该国订单来自单一客户,则存在极大客户依赖风险,该客户流失将导致月度目标大幅缺口。'
  507. f'建议对Top 1国家进行客户拆解:单一客户占比大于50%则触发红色预警,需立即启动新客户开发计划。'
  508. )
  509. items.append({'title': '👤 大客户集中度风险', 'content': concentration})
  510. # ③ 国家增速梯队
  511. if top_countries_change:
  512. growing = [(c, d['change_pct']) for c, d in top_countries_change.items() if d.get('change_pct', 0) and d['change_pct'] > 0]
  513. declining = [(c, d['change_pct']) for c, d in top_countries_change.items() if d.get('change_pct', 0) and d['change_pct'] < 0]
  514. growing.sort(key=lambda x: x[1], reverse=True)
  515. declining.sort(key=lambda x: x[1])
  516. tier = (
  517. f'国家增速梯队:上升最快{"为" + "、".join([c for c, _ in growing[:2]]) if growing else "暂无"},'
  518. f'下滑最快{"为" + "、".join([c for c, _ in declining[:2]]) if declining else "暂无"}。'
  519. f'上升国家需分析是政策红利还是客户拓展见效,判断可持续性;'
  520. f'下滑国家需区分暂时性因素(节假日/账期)还是结构性因素(竞争加剧/需求萎缩),对症下药。'
  521. )
  522. else:
  523. tier = (
  524. f'本周缺乏国家维度环比数据,无法划分增速梯队。'
  525. f'建议每周固定输出Top 10国家环比变化表,识别"黑马"与"掉队"国家,'
  526. f'为资源动态调配提供数据依据。'
  527. )
  528. items.append({'title': '📊 国家增速梯队', 'content': tier})
  529. # ④ 竞争格局判断
  530. competition = (
  531. f'若{top1_country}市场出现价格竞争或交付周期压缩,说明竞品正在渗透。'
  532. f'建议建立"竞争信号"监测机制:客户询盘时提及竞品频次、询盘转化率变化、订单周期拉长等。'
  533. f'本周若Top 1国家订单增速放缓但询盘量上升,可能是竞品截流信号,需立即调整报价或服务策略。'
  534. )
  535. items.append({'title': '🏁 竞争格局判断', 'content': competition})
  536. # ⑤ 国家生命周期策略
  537. if len(sorted_countries) >= 2:
  538. mid = sorted_countries[len(sorted_countries)//2][1] if sorted_countries else 0
  539. mature = [c for c, v in sorted_countries if v >= mid * 1.2]
  540. emerging = [c for c, v in sorted_countries if v < mid * 0.8]
  541. lifecycle = (
  542. f'成熟市场({"、".join(mature[:3])})订单稳定,建议主推高毛利车型和金融服务提升客单价;'
  543. f'新兴市场({"、".join(emerging[:3])})处于首单突破期,当前应以保交付口碑为核心,'
  544. f'建立标杆案例后通过客户转介绍实现低成本获客。两种市场的KPI应差异化设置。'
  545. )
  546. else:
  547. lifecycle = (
  548. f'当前国家数量不足以划分生命周期梯队。'
  549. f'建议积累更多国家数据后,按"订单量+复购率"双维度将国家分为导入期、成长期、成熟期、衰退期,'
  550. f'匹配差异化的产品、定价和服务策略。'
  551. )
  552. items.append({'title': '📈 国家生命周期策略', 'content': lifecycle})
  553. return items
  554. def _insight_weekly_team(metrics: dict, context: dict) -> list[dict]:
  555. """Page 7: 人均产出趋势、区域专注度、协作效率"""
  556. items = []
  557. team = metrics.get('team', {})
  558. team_wow = metrics.get('team_wow', {})
  559. per_capita_orders = metrics.get('per_capita_orders', 0)
  560. prev_per_capita = context.get('prev_per_capita_orders', 0)
  561. countries = metrics.get('countries', 0)
  562. if isinstance(team, dict) and 'owners' in team:
  563. owners = team['owners']
  564. qty_map = team.get('qty', {})
  565. else:
  566. owners = {k: v.get('orders', 0) for k, v in team.items()} if team else {}
  567. qty_map = {k: v.get('qty', 0) for k, v in team.items()} if team else {}
  568. if not owners:
  569. return [
  570. {'title': '💡 人均产出趋势', 'content': '本周暂无负责人数据。建议完善订单归属字段,以便评估团队人效并优化负载分配。'},
  571. {'title': '⚖️ 区域专注度', 'content': '缺乏团队数据,无法评估区域专注度。建议建立人均覆盖国家数指标,超过8个时触发精力分散预警。'},
  572. ]
  573. n_members = len(owners)
  574. total_orders = sum(owners.values())
  575. sorted_owners = sorted(owners.items(), key=lambda x: x[1], reverse=True)
  576. top_owner, top_val = sorted_owners[0]
  577. # ① 人均产出趋势
  578. pc_chg = _pct_change(per_capita_orders, prev_per_capita)
  579. productivity = (
  580. f'本周团队{n_members}人,人均{per_capita_orders:.1f}单,'
  581. f'{"较上周" + _fmt_chg_dir(pc_chg) + _fmt_pct(pc_chg) if prev_per_capita else "基准待建立"}。'
  582. f'{"人均产出提升,团队效率改善" if (pc_chg or 0) > 0 else "人均产出下滑,需排查是人员增加稀释还是订单总量下降"}。'
  583. f'建议将人均产出纳入周会Review,连续两周下滑时启动专项诊断。'
  584. )
  585. items.append({'title': '💡 人均产出趋势', 'content': productivity})
  586. # ② 头部与尾部差距
  587. tail_vals = [v for _, v in sorted_owners[-3:]]
  588. tail_avg = sum(tail_vals) / len(tail_vals) if tail_vals else 0
  589. gap = _safe_div(top_val, tail_avg) if tail_avg else 0
  590. disparity = (
  591. f'团队头部{top_owner}本周{top_val}单,尾部3人均约{tail_avg:.1f}单,'
  592. f'头尾差距{gap:.1f}倍,{"差距较大,存在激励或能力分化" if gap > 3 else "差距适中,团队相对均衡"}。'
  593. f'若差距来自能力差异,建议安排头部带教;若来自资源分配不均(如国家/客户分配),'
  594. f'建议重新盘点客户池,向低负载负责人倾斜高潜客户。'
  595. )
  596. items.append({'title': '📊 头部与尾部差距', 'content': disparity})
  597. # ③ 区域专注度
  598. if countries and n_members:
  599. avg_countries = countries / n_members
  600. focus = (
  601. f'团队人均覆盖{avg_countries:.1f}个国家,'
  602. f'{"超过8个,精力高度分散,单国深度不足" if avg_countries > 8 else "在合理范围内,兼顾广度与深度"}。'
  603. f'{"建议将国家按优先级分为A/B/C类,A类由专人深耕,B类共享覆盖,C类交给代理商" if avg_countries > 8 else ""}'
  604. f'{"建议对重点国家实施双人backup机制,避免单点依赖" if avg_countries <= 5 else ""}'
  605. f'。国家覆盖数与订单量呈倒U型关系,过多或过少均不利。'
  606. )
  607. else:
  608. focus = (
  609. f'缺乏国家数或团队人数数据,无法计算人均覆盖。'
  610. f'建议建立"人均覆盖国家数"指标,警戒线为8个,超过时触发区域重新划分流程。'
  611. )
  612. items.append({'title': '⚖️ 区域专注度', 'content': focus})
  613. # ④ 协作效率
  614. cross_orders = context.get('cross_owner_orders', 0)
  615. if cross_orders:
  616. ratio = round(cross_orders / total_orders * 100, 1) if total_orders else 0
  617. collaboration = (
  618. f'本周跨负责人协作订单{cross_orders}单,占总量{ratio}%。'
  619. f'{"协作占比偏低,团队呈单兵作战状态,知识共享不足" if ratio < 5 else "协作机制运行良好,团队具备协同作战能力"}。'
  620. f'建议对大型项目(单均大于50台)强制要求双负责人制,既分散风险又促进经验交流。'
  621. )
  622. else:
  623. collaboration = (
  624. f'本周暂无跨负责人协作订单数据。建议建立协作订单标记机制,'
  625. f'识别需要多区域/多客户类型协同的复杂项目。'
  626. f'协作效率是团队从"单兵"向"集团军"转型的关键指标,应纳入季度考核。'
  627. )
  628. items.append({'title': '🤝 协作效率', 'content': collaboration})
  629. # ⑤ 人员稳定性
  630. if team_wow:
  631. churn = [o for o, d in team_wow.items() if d.get('previous', 0) > 0 and d.get('current', 0) == 0]
  632. new_rise = [o for o, d in team_wow.items() if d.get('previous', 0) == 0 and d.get('current', 0) > 0]
  633. stability = (
  634. f'团队变动:{"上周有产出但本周挂零:" + "、".join(churn[:2]) if churn else "无流失风险信号"};'
  635. f'{"本周新涌现:" + "、".join(new_rise[:2]) if new_rise else "无新人冒头"}。'
  636. f'{"需关注挂零负责人的客户状态,是否离职/调岗/休假导致订单断档,必要时启动客户交接保护机制" if churn else "团队人员稳定,无异常流失或新增涌现,建议保持当前激励机制并关注长期职业发展路径"}'
  637. f'{"新涌现负责人值得复盘其成功经验,快速复制至团队" if new_rise else ""}'
  638. )
  639. else:
  640. stability = (
  641. f'缺乏上周团队对比数据,无法评估人员稳定性。'
  642. f'建议建立人员周环比追踪,连续两周挂零或大幅波动时触发主管一对一沟通。'
  643. f'人员稳定性是业务连续性的基础,突然变动往往导致客户流失和知识断层。'
  644. )
  645. items.append({'title': '👥 人员稳定性', 'content': stability})
  646. return items
  647. def _insight_weekly_issue(metrics: dict, context: dict) -> list[dict]:
  648. """Page 8: 问题根因分类、影响面量化、重复性问题、解决进度"""
  649. items = []
  650. issues = metrics.get('issues', [])
  651. support_categories = metrics.get('support_categories', {})
  652. tracking_orders = metrics.get('tracking_orders', 0)
  653. total_qty = metrics.get('total_qty', 0)
  654. # ① 问题根因分类
  655. if issues:
  656. root_causes = {'技术': 0, '流程': 0, '外部': 0}
  657. for issue in issues:
  658. detail = issue.get('detail', '') + issue.get('title', '')
  659. if any(k in detail for k in ['系统', 'IT', '技术', '质量', '检测']):
  660. root_causes['技术'] += 1
  661. elif any(k in detail for k in ['流程', '审批', '协调', '内部']):
  662. root_causes['流程'] += 1
  663. else:
  664. root_causes['外部'] += 1
  665. dominant = max(root_causes.items(), key=lambda x: x[1])
  666. root = (
  667. f'本周{len(issues)}项问题按根因分类:技术类{root_causes["技术"]}项、流程类{root_causes["流程"]}项、外部类{root_causes["外部"]}项。'
  668. f'{"技术类最多,说明产品质量或系统稳定性存在短板,需产研部门介入" if dominant[0] == "技术" else ""}'
  669. f'{"流程类最多,说明内部协作存在断点,建议梳理跨部门SOP" if dominant[0] == "流程" else ""}'
  670. f'{"外部类最多,说明问题主要来自客户/政策/物流等不可控因素,需建立预案库" if dominant[0] == "外部" else ""}'
  671. f'。根因分类是预防性管理的基础,建议每次问题上报时强制选择根因类型。'
  672. )
  673. else:
  674. root = (
  675. f'本周暂无显式问题记录。建议建立"无问题也是一种信号"的视角:'
  676. f'若支持需求总量上升但问题记录为零,可能是问题未被识别或归类。'
  677. f'建议每周五由负责人提交本周卡点,即使已自行解决也记录备案,积累组织知识。'
  678. )
  679. items.append({'title': '🔍 问题根因分类', 'content': root})
  680. # ② 影响面量化
  681. if issues:
  682. est_orders = max(len(issues), sum(1 for i in issues if i.get('severity') == '高'))
  683. est_qty = est_orders * 30
  684. est_amount = est_orders * 150
  685. impact = (
  686. f'本周问题预计影响订单{est_orders}单、车辆约{est_qty}台、金额约¥{est_amount}万。'
  687. f'其中高严重度问题{"占比高,需立即升级至管理层" if est_orders > 3 else "可控,按常规流程处理即可"}。'
  688. f'建议建立"问题影响面"必填字段:阻断单数、预计台数、预计金额,便于后续按损失金额排序处理优先级。'
  689. )
  690. else:
  691. impact = (
  692. f'本周无显式问题,预计影响面为零。建议将节省的管理精力转向'
  693. f'"支持需求前置化处理",把潜在问题消灭在萌芽阶段。'
  694. f'支持需求中若出现高频关键词,应视为早期预警信号。'
  695. )
  696. items.append({'title': '💰 影响面量化', 'content': impact})
  697. # ③ 重复性问题识别
  698. if support_categories:
  699. top_cat = max(support_categories.items(), key=lambda x: x[1])
  700. recurring = (
  701. f'本周支持需求中"{top_cat[0]}"类出现{top_cat[1]}次,为最高频类别。'
  702. f'若该类别连续两周及以上位居榜首,则可判定为重复性问题。'
  703. f'建议对{top_cat[0]}类问题启动"根治计划":统计发生场景→提炼标准化处理模板→培训一线人员→设置系统校验规则,'
  704. f'目标是将该类问题发生频率压降50%以上。'
  705. )
  706. else:
  707. recurring = (
  708. f'本周无支持需求分类数据,无法识别重复性问题。'
  709. f'建议积累4周以上数据后,按"月度出现频次大于3次且连续两周上榜"定义重复性问题,'
  710. f'并建立专项改进小组负责根治。'
  711. )
  712. items.append({'title': '🔄 重复性问题识别', 'content': recurring})
  713. # ④ 上周问题解决率
  714. prev_resolved = context.get('prev_week_resolved_issues', 0)
  715. prev_total = context.get('prev_week_total_issues', 0)
  716. if prev_total:
  717. resolve_rate = round(prev_resolved / prev_total * 100, 1)
  718. resolution = (
  719. f'上周问题{prev_total}项,已解决{prev_resolved}项,解决率{resolve_rate}%。'
  720. f'{"解决率大于80%,问题闭环效率高" if resolve_rate > 80 else "解决率小于80%,存在遗留问题堆积风险"}。'
  721. f'未解决问题应滚动至本周继续跟踪,避免问题"报而不决"形成管理债务。'
  722. f'建议建立问题看板,按"待处理/处理中/待验证/已关闭"四状态管理。'
  723. )
  724. else:
  725. resolution = (
  726. f'缺乏上周问题解决率数据。建议建立问题生命周期管理:从上报到关闭全流程记录,'
  727. f'每周计算"本周关闭率"和"平均关闭时长"。目标:关闭率大于85%、平均时长小于5个工作日。'
  728. )
  729. items.append({'title': '✅ 上周问题解决率', 'content': resolution})
  730. # ⑤ 问题预防机制
  731. prevention = (
  732. f'建议建立三层预防机制:第一层"系统校验"(如合同超14天自动标红)、'
  733. f'第二层"流程卡点"(如大额订单必须经法务预审核)、'
  734. f'第三层"文化驱动"(如每月评选"零问题周"并给予团队奖励)。'
  735. f'从"救火"转向"防火",是问题管理的终极目标。'
  736. )
  737. items.append({'title': '🛡️ 问题预防机制', 'content': prevention})
  738. return items
  739. def _insight_weekly_plan(metrics: dict, context: dict) -> list[dict]:
  740. """Page 9: 目标拆解逻辑、资源匹配、里程碑、风险对冲"""
  741. items = []
  742. next_week_goals = metrics.get('next_week_goals', [])
  743. monthly_target = context.get('monthly_target', 0)
  744. total_qty = metrics.get('total_qty', 0)
  745. tracking_orders = metrics.get('tracking_orders', 0)
  746. # ① 目标拆解逻辑
  747. if next_week_goals and monthly_target:
  748. total_goal = sum(g.get('number', 0) for g in next_week_goals)
  749. breakdown = (
  750. f'下周目标合计{total_goal}项任务,源自月度目标{monthly_target}台的四分之一拆解({monthly_target/4:.0f}台/周)。'
  751. f'本周实际完成{total_qty}台,{"高于" if total_qty > monthly_target/4 else "低于"}周均目标,'
  752. f'下周需{"保持惯性" if total_qty > monthly_target/4 else "加速追赶"}。'
  753. f'目标拆解应遵循"存量转化保底+新增拓展增量"双轨逻辑,避免仅靠单一来源支撑。'
  754. )
  755. else:
  756. breakdown = (
  757. f'下周目标共{len(next_week_goals)}项,因缺乏月度目标或本周台数数据,暂无法做比例拆解。'
  758. f'建议业务部门输入月度目标后,系统自动按"四周均衡"或"前低后高冲刺"模式生成周目标。'
  759. f'当前可先基于本周{tracking_orders}单设定下周增长5%-10%的软目标。'
  760. )
  761. items.append({'title': '🎯 目标拆解逻辑', 'content': breakdown})
  762. # ② 资源匹配
  763. n_goals = len(next_week_goals)
  764. n_owners = len(metrics.get('team', {}).get('owners', {})) if isinstance(metrics.get('team'), dict) and 'owners' in metrics.get('team', {}) else len(metrics.get('team', {}))
  765. pending_ship = metrics.get('pending_shipment', 0)
  766. pending_pay = metrics.get('pending_payment', 0)
  767. resource = (
  768. f'下周需完成{n_goals}项目标,当前团队{n_owners}人,人均承担{n_goals/n_owners if n_owners else 0:.1f}项目标。'
  769. f'待发运{pending_ship}单、待收款{pending_pay}单为资源消耗大头,需提前协调物流舱位和财务催收人力。'
  770. f'若目标增量超过团队当前负载20%,建议申请临时支援或外包非核心环节。'
  771. )
  772. items.append({'title': '⚙️ 资源匹配', 'content': resource})
  773. # ③ 里程碑
  774. milestones = []
  775. for g in next_week_goals[:2]:
  776. milestones.append(f'{g.get("id", "")}:{g.get("title", "")}({g.get("number", 0)})')
  777. milestone_text = (
  778. f'下周必须达成节点:{";".join(milestones) if milestones else "暂无具体里程碑"}。'
  779. f'里程碑应满足SMART原则:具体到单数/台数、可量化、可验证。'
  780. f'建议每周一晨会公开承诺里程碑,周五复盘完成率,未完成项需在当日24:00前提交原因和改进措施。'
  781. )
  782. items.append({'title': '🚩 里程碑节点', 'content': milestone_text})
  783. # ④ 风险对冲(Plan B)
  784. risks = context.get('next_week_risks', [])
  785. if risks:
  786. plan_b = (
  787. f'下周已识别风险:{"、".join([r.get("title", "") for r in risks[:3]])}。'
  788. f'Plan B原则:若A方案因外部因素受阻,48h内切换至备用方案。'
  789. f'例如:若主船期延误,提前锁定备用船公司;若大客户延迟下单,启动备选客户清单。'
  790. f'建议每项高风险目标均配置Plan B,并在周初完成资源预置。'
  791. )
  792. else:
  793. plan_b = (
  794. f'下周风险清单尚未建立。建议基于本周问题和支持需求,推演下周可能出现的3个最大风险场景:'
  795. f'(1)物流延误导致E阶段积压、(2)大客户账期延迟导致D→E转化受阻、(3)政策变动导致A阶段合同搁置。'
  796. f'针对每类场景提前设计应对预案,确保目标韧性。'
  797. )
  798. items.append({'title': '🛡️ 风险对冲(Plan B)', 'content': plan_b})
  799. # ⑤ 关键依赖项
  800. dependencies = context.get('next_week_dependencies', [])
  801. if dependencies:
  802. dep_text = (
  803. f'下周目标达成依赖于:{"、".join(dependencies[:3])}。'
  804. f'关键依赖应提前一周确认状态,避免临时发现资源不到位导致目标悬空。'
  805. f'建议建立"依赖项红绿灯"机制:周一全绿确认、周三黄灯预警、周五必须全绿放行。'
  806. )
  807. else:
  808. dep_text = (
  809. f'下周关键依赖项尚未明确。建议梳理:物流舱位确认书、财务收款截止时间、'
  810. f'生产排期锁定函等关键外部确认,作为目标达成的先决条件。'
  811. f'内部依赖(如跨部门协作)也应落实到具体责任人和交付时间。'
  812. )
  813. items.append({'title': '🔗 关键依赖项', 'content': dep_text})
  814. return items
  815. # =============================================================================
  816. # MONTHLY INSIGHTS
  817. # =============================================================================
  818. def monthly_insights(page_type: str, metrics: dict, context: dict) -> list[dict]:
  819. """Dispatch to specific monthly page insight functions."""
  820. dispatch = {
  821. 'monthly_overview': _insight_monthly_overview,
  822. 'monthly_funnel': _insight_monthly_funnel,
  823. 'monthly_region': _insight_monthly_region,
  824. 'monthly_country': _insight_monthly_country,
  825. 'monthly_trend': _insight_monthly_trend,
  826. 'monthly_team': _insight_monthly_team,
  827. 'monthly_support': _insight_monthly_support,
  828. 'monthly_plan': _insight_monthly_plan,
  829. }
  830. fn = dispatch.get(page_type)
  831. return fn(metrics, context) if fn else []
  832. def _insight_monthly_overview(metrics: dict, context: dict) -> list[dict]:
  833. """Page 3: 月度节奏、目标达成率、季节性、年度进度"""
  834. items = []
  835. total_contracts = metrics.get('total_contracts', 0)
  836. total_qty = metrics.get('total_qty', 0)
  837. shipped_orders = metrics.get('shipped_orders', 0)
  838. shipped_qty = metrics.get('shipped_qty', 0)
  839. trend_by_period = metrics.get('trend_by_period', {})
  840. monthly_target = context.get('monthly_target', 0)
  841. yoy_total_qty = metrics.get('yoy_total_qty', 0)
  842. annual_target = context.get('annual_target', 0)
  843. # ① 月度节奏(上中下旬)
  844. if trend_by_period:
  845. early = trend_by_period.get('early', 0)
  846. mid = trend_by_period.get('mid', 0)
  847. late = trend_by_period.get('late', 0)
  848. total_period = early + mid + late
  849. if total_period:
  850. early_pct = round(early / total_period * 100, 1)
  851. mid_pct = round(mid / total_period * 100, 1)
  852. late_pct = round(late / total_period * 100, 1)
  853. rhythm = (
  854. f'本月订单节奏:上旬{early_pct}%({early:.1f}单/日)、中旬{mid_pct}%({mid:.1f}单/日)、下旬{late_pct}%({late:.1f}单/日)。'
  855. f'{"下旬冲刺特征明显,说明团队具备收官能力但前期储备不足" if late > early and late > mid else ""}'
  856. f'{"上旬开门红,中下旬平稳,月度节奏健康" if early >= mid and mid >= late * 0.8 else ""}'
  857. f'{"中旬平台期过长,存在阶段性懈怠,需加强月中跟踪" if mid < early * 0.7 else ""}'
  858. f'。理想节奏为上旬35%-中旬30%-下旬35%,当前{"接近理想" if 25 <= early_pct <= 45 and 25 <= mid_pct <= 40 else "需调整"}。'
  859. )
  860. else:
  861. rhythm = f'本月分旬数据异常,无法计算节奏占比。建议检查日报数据完整性。'
  862. else:
  863. rhythm = (
  864. f'本月累计{total_contracts}单,缺乏上中下旬分旬数据。'
  865. f'建议将月度按自然旬拆分,识别"月初开门红/月中平台期/月末冲刺"的典型模式。'
  866. f'节奏分析是预测下月走势和优化资源投放时点的重要依据。'
  867. )
  868. items.append({'title': '📅 月度节奏分析', 'content': rhythm})
  869. # ② 目标达成率
  870. if monthly_target and total_qty:
  871. achievement = round(total_qty / monthly_target * 100, 1)
  872. gap = total_qty - monthly_target
  873. target_text = (
  874. f'本月实际完成{total_qty}台,目标{monthly_target}台,达成率{achievement}%,'
  875. f'{"超额" if gap >= 0 else "缺口"}{abs(gap)}台。'
  876. f'{"达成率超过100%,建议复盘成功因素并固化为标准打法" if achievement >= 100 else ""}'
  877. f'{"达成率90%-100%,基本达标但无冗余,需确保最后几天无退单" if 90 <= achievement < 100 else ""}'
  878. f'{"达成率低于90%,缺口较大,需启动紧急补单或下调下月目标" if achievement < 90 else ""}'
  879. f'。目标达成率应分解到周,每周偏差大于10%时即触发预警。'
  880. )
  881. else:
  882. target_text = (
  883. f'本月完成{total_qty}台,因未设定月度目标,无法计算达成率。'
  884. f'建议业务部门在月初输入目标值,系统将自动按周拆解并生成进度看板。'
  885. f'当前可先以历史月均值为隐性目标,评估相对表现。'
  886. )
  887. items.append({'title': '🎯 目标达成率', 'content': target_text})
  888. # ③ 季节性分析(同比)
  889. if yoy_total_qty:
  890. yoy_chg = _pct_change(total_qty, yoy_total_qty)
  891. seasonal = (
  892. f'本月{total_qty}台,去年同期{yoy_total_qty}台,同比{_fmt_chg_dir(yoy_chg)}{_fmt_pct(yoy_chg)}。'
  893. f'{"同比加速增长,市场景气度上行或我司市占率提升" if (yoy_chg or 0) > 10 else ""}'
  894. f'{"同比小幅增长,与行业大盘同步" if 0 <= (yoy_chg or 0) <= 10 else ""}'
  895. f'{"同比下滑,需警惕行业下行或竞争加剧" if (yoy_chg or 0) < 0 else ""}'
  896. f'。建议结合行业报告判断:若我司增速高于行业,说明策略有效;若低于行业,则需重新审视产品/定价/渠道。'
  897. )
  898. else:
  899. seasonal = (
  900. f'本月完成{total_qty}台,缺乏去年同期数据。'
  901. f'建议建立年度数据档案,以便进行同比分析。同比是剔除季节性干扰的最佳方式,'
  902. f'尤其在受春节、海外斋月等影响的月份,环比可能失真,同比更具参考价值。'
  903. )
  904. items.append({'title': '📊 季节性分析(同比)', 'content': seasonal})
  905. # ④ 年度进度
  906. if annual_target and total_qty:
  907. annual_progress = round(total_qty / annual_target * 100, 1)
  908. month = context.get('month', 1)
  909. expected_annual = month / 12 * 100
  910. annual_gap = annual_progress - expected_annual
  911. annual_text = (
  912. f'本月贡献{total_qty}台,占年度目标{annual_target}台的{annual_progress}%,'
  913. f'按时间进度应达{expected_annual:.1f}%,{"领先" if annual_gap >= 0 else "落后"}{abs(annual_gap):.1f}个百分点。'
  914. f'{"年度进度超前,建议适当储备Q4项目避免年末透支" if annual_gap > 5 else ""}'
  915. f'{"年度进度滞后,剩余月份需月均追赶" + str(abs(int(annual_gap/100*annual_target/(12-month)))) + "台" if annual_gap < -5 and month < 12 else ""}'
  916. f'。年度进度是战略资源配置的北极星指标,每季度应做一次资源再平衡。'
  917. )
  918. else:
  919. annual_text = (
  920. f'本月完成{total_qty}台,因未设定年度目标,无法计算年度进度。'
  921. f'建议年初输入年度目标,系统自动按月分解并生成进度条。'
  922. f'年度目标的合理性直接影响月度策略:过高导致团队透支,过低导致资源闲置。'
  923. )
  924. items.append({'title': '🗓️ 年度进度', 'content': annual_text})
  925. # ⑤ 交付闭环效率
  926. if total_contracts:
  927. ship_pct = round(shipped_orders / total_contracts * 100, 1)
  928. delivery = (
  929. f'本月已发运{shipped_orders}单({shipped_qty}台),占月内合同{ship_pct}%。'
  930. f'{"发运占比高,交付闭环效率高" if ship_pct > 60 else "发运占比偏低,大量订单滞留中后期阶段"}。'
  931. f'需区分"本月新签本月发运"(效率极高)与"历史存量本月发运"(清理旧账),'
  932. f'前者反映流程效率,后者反映历史包袱。建议分别统计两类占比。'
  933. )
  934. else:
  935. delivery = (
  936. f'本月暂无合同数据,无法计算交付闭环效率。'
  937. f'建议建立"当月签约-当月发运"比率作为流程效率核心指标,目标值大于30%。'
  938. )
  939. items.append({'title': '🚢 交付闭环效率', 'content': delivery})
  940. return items
  941. def _insight_monthly_funnel(metrics: dict, context: dict) -> list[dict]:
  942. """Page 4: 各环节转化率、瓶颈诊断、历史最优、改进路线图"""
  943. items = []
  944. status_funnel = metrics.get('status_funnel', {})
  945. stage_analysis = metrics.get('stage_analysis', {})
  946. total_contracts = metrics.get('total_contracts', 0)
  947. if not status_funnel or not total_contracts:
  948. return [
  949. {'title': '💡 漏斗结构诊断', 'content': '本月暂无漏斗数据。建议完善订单状态字段,以便计算各阶段转化率并识别瓶颈。'},
  950. ]
  951. # ① 各环节转化率分析
  952. names = ['合同拟定中', '已锁定合同待付订金', '已付订金待生产', '已生产待付尾款', '已付尾款待发运', '已发运']
  953. prev_funnel = context.get('prev_status_funnel', {})
  954. conv_text = '本月漏斗各阶段占比:'
  955. parts = []
  956. for name in names:
  957. pct = status_funnel.get(name, {}).get('pct', 0)
  958. prev_pct = prev_funnel.get(name, {}).get('pct', 0) if prev_funnel else 0
  959. chg = _pct_change(pct, prev_pct)
  960. parts.append(f'{name}{pct}%({_fmt_chg_dir(chg)}{_fmt_pct(chg)})')
  961. conv_text += '、'.join(parts[:4]) + '。'
  962. conv_text += '转化率变化反映流程效率波动,建议锁定两个关键转化节点重点监控:A→B(合同锁定)和D→E(尾款回收)。'
  963. items.append({'title': '💡 各环节转化率分析', 'content': conv_text})
  964. # ② 瓶颈环节深度诊断
  965. early = stage_analysis.get('early', {}).get('pct', 0)
  966. mid = stage_analysis.get('mid', {}).get('pct', 0)
  967. late = stage_analysis.get('late', {}).get('pct', 0)
  968. bottleneck = (
  969. f'阶段结构:前期{early}%(合同+锁定)、中期{mid}%(生产)、后期{late}%(待发运+已发运)。'
  970. f'{"前期占比过高,新增pipeline充足但转化效率低,需重点突破合同审批和定金催收" if early > 40 else ""}'
  971. f'{"中期占比过高,生产端积压严重,需排查产能瓶颈或生产计划失衡" if mid > 40 else ""}'
  972. f'{"后期占比过高,交付压力大,需确保物流和报关资源到位" if late > 40 else ""}'
  973. f'。与行业benchmark对比:理想状态为前期30%-中期35%-后期35%,当前{"接近理想" if 25 <= early <= 40 and 25 <= mid <= 45 else "偏离理想,需专项改进"}。'
  974. )
  975. items.append({'title': '📉 瓶颈环节深度诊断', 'content': bottleneck})
  976. # ③ 历史最优水平对比
  977. hist_best = context.get('hist_best_conversion', {})
  978. if hist_best:
  979. best_text = (
  980. f'历史最优A→B转化率{hist_best.get("a_to_b", "—")}%、D→E转化率{hist_best.get("d_to_e", "—")}%。'
  981. f'本月表现与最优水平对比,可量化当前流程效率损失。'
  982. f'差距大于10个百分点时,说明流程存在显著退化,需启动流程再造;'
  983. f'差距小于5个百分点时,可通过微调优化逼近最优。建议每季度更新历史最优基准。'
  984. )
  985. else:
  986. best_text = (
  987. f'本月A→B及D→E转化率数据已记录,但历史最优基准尚未建立。'
  988. f'建议追溯过去12个月数据,提取各阶段转化率峰值作为"历史最优"标杆。'
  989. f'历史最优不仅是目标,更是证明"我们曾做到过"的证据,对团队信心建设至关重要。'
  990. )
  991. items.append({'title': '🏆 历史最优水平对比', 'content': best_text})
  992. # ④ 改进路线图
  993. roadmap = (
  994. f'下月漏斗改进重点:'
  995. f'(1)A→B转化:优化合同模板,将标准合同审批周期从5天压缩至3天;'
  996. f'(2)C→D转化:建立生产进度可视化看板,让客户实时追踪车辆生产状态,减少催单焦虑;'
  997. f'(3)D→E转化:财务前置介入,车辆下线前7天启动尾款提醒,而非传统下线后催收。'
  998. f'每项改进需设定量化目标、责任人和验收标准,月底复盘执行效果。'
  999. )
  1000. items.append({'title': '🛣️ 改进路线图', 'content': roadmap})
  1001. # ⑤ 资金占用与周转
  1002. pending_pay = metrics.get('pending_payment', {}).get('orders', 0)
  1003. pending_pay_qty = metrics.get('pending_payment', {}).get('qty', 0)
  1004. pending_ship = metrics.get('pending_shipment', {}).get('orders', 0)
  1005. capital = (
  1006. f'本月D阶段(已生产待付尾款){pending_pay}单(约{pending_pay_qty}台),是资金占用核心环节。'
  1007. f'假设单台均价15万,则D阶段资金占用约¥{pending_pay_qty * 15:,}万。'
  1008. f'若D阶段平均滞留天数超过14天,建议对超期订单启动"财务+销售"联合催收机制,'
  1009. f'目标是将D阶段平均周转天数压降至10天以内。'
  1010. )
  1011. items.append({'title': '💰 资金占用与周转', 'content': capital})
  1012. return items
  1013. def _insight_monthly_region(metrics: dict, context: dict) -> list[dict]:
  1014. """Page 5: 区域战略优先级矩阵、资源投入ROI、订单结构差异"""
  1015. items = []
  1016. region_dist = metrics.get('region_dist', {})
  1017. prev_region_dist = context.get('prev_region_dist', {})
  1018. if not region_dist:
  1019. return [
  1020. {'title': '💡 区域战略优先级矩阵', 'content': '本月暂无区域分布数据。建议完善国家-区域映射表,以便按区域维度分析市场规模与增速矩阵。'},
  1021. ]
  1022. # ① 区域战略优先级矩阵(市场规模×增速)
  1023. regions = []
  1024. for name, data in region_dist.items():
  1025. qty = data.get('qty', 0)
  1026. pct = data.get('pct', 0)
  1027. prev_qty = prev_region_dist.get(name, {}).get('qty', 0) if prev_region_dist else 0
  1028. growth = _pct_change(qty, prev_qty)
  1029. regions.append((name, qty, pct, growth))
  1030. stars = [r for r in regions if r[2] > 20 and (r[3] is None or r[3] > 0)]
  1031. cows = [r for r in regions if r[2] > 20 and (r[3] is not None and r[3] <= 0)]
  1032. questions = [r for r in regions if r[2] <= 20 and (r[3] is None or r[3] > 0)]
  1033. dogs = [r for r in regions if r[2] <= 20 and (r[3] is not None and r[3] <= 0)]
  1034. matrix = (
  1035. f'基于"市场规模×增速"四象限分析:'
  1036. f'{"明星(高规模+高增长):" + "、".join([r[0] for r in stars[:2]]) + ",应追加资源扩大优势;" if stars else ""}'
  1037. f'{"现金牛(高规模+低增长):" + "、".join([r[0] for r in cows[:2]]) + ",维持投入收割利润;" if cows else ""}'
  1038. f'{"问题(低规模+高增长):" + "、".join([r[0] for r in questions[:2]]) + ",需判断是真潜力还是虚假繁荣;" if questions else ""}'
  1039. f'{"瘦狗(低规模+低增长):" + "、".join([r[0] for r in dogs[:2]]) + ",收缩资源或调整模式。" if dogs else ""}'
  1040. )
  1041. items.append({'title': '💡 区域战略优先级矩阵', 'content': matrix})
  1042. # ② 资源投入ROI
  1043. roi_text = (
  1044. f'区域ROI评估应超越订单量,引入利润率、售后成本、物流复杂度三维度。'
  1045. f'例如:某区域订单量大但物流成本占比高(偏远国/内陆国),其真实ROI可能低于订单量小的近海区域。'
  1046. f'建议每季度输出区域损益表,按"净利润=订单毛利-物流成本-售后成本-人力分摊"公式计算,'
  1047. f'将资源向高ROI区域倾斜,低ROI区域探索代理商模式降低成本。'
  1048. )
  1049. items.append({'title': '💰 资源投入ROI', 'content': roi_text})
  1050. # ③ 区域间订单结构差异
  1051. if len(regions) >= 2:
  1052. r1_name, r1_data = max(region_dist.items(), key=lambda x: x[1].get('qty', 0))
  1053. r1_top = [c['country'] for c in r1_data.get('top_countries', [])[:2]]
  1054. structure = (
  1055. f'{r1_name}为最大区域,其Top国家{r1_top}的车型偏好可能与其他区域存在显著差异。'
  1056. f'例如:亚洲市场偏好紧凑型电动车,非洲市场偏好皮卡/商用车,拉美市场对SUV需求旺盛。'
  1057. f'区域间车型差异反映市场需求本质不同,建议按区域定制产品组合和营销话术,'
  1058. f'避免全球统一策略导致的水土不服。'
  1059. )
  1060. else:
  1061. structure = (
  1062. f'本月区域数据不足,无法分析订单结构差异。'
  1063. f'建议积累多区域数据后,按"区域×车型"交叉分析,识别区域专属需求特征。'
  1064. )
  1065. items.append({'title': '📊 区域间订单结构差异', 'content': structure})
  1066. # ④ 区域培育策略
  1067. if questions:
  1068. q_names = [r[0] for r in questions[:2]]
  1069. nurture = (
  1070. f'问题区域({"、".join(q_names)})增速快但规模小,处于市场培育期。'
  1071. f'培育策略:前期以"样板工程"为核心,投入1-2个标杆客户确保极致交付体验;'
  1072. f'中期通过客户转介绍和本地车展扩大知名度;后期引入本地代理商实现轻资产扩张。'
  1073. f'培育期通常需要6-12个月,需设定阶段性里程碑避免过早放弃。'
  1074. )
  1075. else:
  1076. nurture = (
  1077. f'本月暂无高增长低基数的问题区域。建议审视现有区域增速:'
  1078. f'若有区域增速超过50%但占比仍低于10%,应立即纳入问题区域清单并给予专项资源。'
  1079. )
  1080. items.append({'title': '🌱 区域培育策略', 'content': nurture})
  1081. return items
  1082. def _insight_monthly_country(metrics: dict, context: dict) -> list[dict]:
  1083. """Page 6: 国家组合健康度、大客户集中度、新国家孵化、竞争态势"""
  1084. items = []
  1085. top_countries = metrics.get('top_countries', {})
  1086. top_countries_change = metrics.get('top_countries_change', {})
  1087. total_qty = metrics.get('total_qty', 0)
  1088. if not top_countries:
  1089. return [
  1090. {'title': '💡 国家组合健康度', 'content': '本月暂无国家分布数据。建议完善目的国家字段,以便评估国家组合健康度并制定分散策略。'},
  1091. ]
  1092. country_qty = {}
  1093. country_orders = {}
  1094. for c, v in top_countries.items():
  1095. if isinstance(v, int):
  1096. country_qty[c] = v
  1097. country_orders[c] = 0
  1098. else:
  1099. country_qty[c] = v.get('qty', 0)
  1100. country_orders[c] = v.get('orders', 0)
  1101. sorted_countries = sorted(country_qty.items(), key=lambda x: x[1], reverse=True)
  1102. top3_qty = sum(v for _, v in sorted_countries[:3])
  1103. top3_pct = round(top3_qty / total_qty * 100, 1) if total_qty else 0
  1104. # ① 国家组合健康度评分
  1105. health_score = 100
  1106. if top3_pct > 50:
  1107. health_score -= 30
  1108. elif top3_pct > 40:
  1109. health_score -= 15
  1110. if len(sorted_countries) < 5:
  1111. health_score -= 20
  1112. health = (
  1113. f'国家组合健康度评分{health_score}/100(Top 3集中度{top3_pct}%、覆盖国家数{len(sorted_countries)})。'
  1114. f'{"评分优秀,国家分布均衡,抗风险能力强" if health_score >= 85 else ""}'
  1115. f'{"评分良好,存在优化空间,建议培育第二梯队" if 70 <= health_score < 85 else ""}'
  1116. f'{"评分偏低,头部依赖严重或覆盖不足,需立即启动分散计划" if health_score < 70 else ""}'
  1117. f'。评分规则:Top 3集中度每超10%扣15分,覆盖国家不足5个扣20分。'
  1118. )
  1119. items.append({'title': '💡 国家组合健康度评分', 'content': health})
  1120. # ② 大客户集中度风险
  1121. top1_country, top1_qty = sorted_countries[0]
  1122. top1_orders = country_orders.get(top1_country, 0)
  1123. risk = (
  1124. f'{top1_country}本月{top1_qty}台({top1_orders}单)领跑。'
  1125. f'若该国最大单一客户占比超过50%,则触发红色预警:该客户流失将导致月度目标缺口{round(top1_qty * 0.5)}台。'
  1126. f'建议对该国客户结构进行"金字塔"分层:底部散户(小于5台)占60%、腰部客户(5-20台)占30%、顶部大客户(大于20台)不超过10%,'
  1127. f'确保任何单一客户流失不会动摇基本盘。'
  1128. )
  1129. items.append({'title': '👤 大客户集中度风险', 'content': risk})
  1130. # ③ 新国家孵化进展
  1131. new_countries = context.get('new_countries_this_month', [])
  1132. if new_countries:
  1133. new_text = (
  1134. f'本月新开拓国家:{"、".join(new_countries[:5])}。'
  1135. f'新国家首单是0到1的突破,意义重大但风险较高。建议对新国家实施"护航计划":'
  1136. f'首单交付全程由资深负责人跟进,确保零差错;交付后30天内回访客户,收集反馈并建立口碑案例;'
  1137. f'若首单反馈良好,第2-3单可逐步放宽管控,交由本地负责人常规跟进。'
  1138. )
  1139. else:
  1140. new_text = (
  1141. f'本月无新国家突破。建议审视"新国家孵化 pipeline":是否有正在洽谈的潜在市场?'
  1142. f'若连续3个月无新国家,需反思是市场选择过于保守还是孵化流程存在断点。'
  1143. f'建议每季度设定"新国家突破"为团队OKR之一,激励市场拓展。'
  1144. )
  1145. items.append({'title': '🌱 新国家孵化进展', 'content': new_text})
  1146. # ④ 竞争态势
  1147. competitive = (
  1148. f'竞争态势监测建议:对Top 3国家建立"竞品对标"机制,跟踪维度包括:'
  1149. f'(1)价格带:竞品是否发起价格战、促销力度如何;'
  1150. f'(2)交付周期:竞品从签约到发运的时长是否短于我方;'
  1151. f'(3)服务网络:竞品是否在当地设立配件仓或服务站。'
  1152. f'本月若某国订单增速放缓但询盘量上升,可能是竞品截流信号,需立即调整策略。'
  1153. )
  1154. items.append({'title': '🏁 竞争态势', 'content': competitive})
  1155. return items
  1156. def _insight_monthly_trend(metrics: dict, context: dict) -> list[dict]:
  1157. """Page 7: 阶段性特征、异常波动归因、外部因素、下月预测"""
  1158. items = []
  1159. trend_by_period = metrics.get('trend_by_period', {})
  1160. daily_trend = metrics.get('daily_trend', {})
  1161. peak_dates = metrics.get('peak_dates', [])
  1162. forecast_next = metrics.get('forecast_next', 0)
  1163. total_qty = metrics.get('total_qty', 0)
  1164. total_contracts = metrics.get('total_contracts', 0)
  1165. # ① 阶段性特征
  1166. if trend_by_period:
  1167. early = trend_by_period.get('early', 0)
  1168. mid = trend_by_period.get('mid', 0)
  1169. late = trend_by_period.get('late', 0)
  1170. late_chg = trend_by_period.get('late_change_pct')
  1171. phase = (
  1172. f'本月三旬日均:上旬{early:.1f}单、中旬{mid:.1f}单、下旬{late:.1f}单。'
  1173. f'{"上旬开门红,开局强势" if early > mid and early > late else ""}'
  1174. f'{"中旬平台期,需警惕懈怠" if mid < early * 0.8 and mid < late * 0.8 else ""}'
  1175. f'{"下旬冲刺,收官能力强" if late > early and late > mid else ""}'
  1176. f'下旬较中旬{_fmt_chg_dir(late_chg)}{_fmt_pct(late_chg)}。'
  1177. f'阶段性特征反映了团队的节奏控制能力,理想状态为"高开稳走",避免过度依赖月末冲刺。'
  1178. )
  1179. else:
  1180. phase = (
  1181. f'本月累计{total_contracts}单,缺乏上中下旬分旬数据。'
  1182. f'建议将月度按自然旬拆分,识别"月初开门红/月中平台期/月末冲刺"的典型模式。'
  1183. f'阶段特征分析有助于优化资源投放时点:上旬重签约、中旬重生产、下旬重交付。'
  1184. )
  1185. items.append({'title': '📅 阶段性特征', 'content': phase})
  1186. # ② 异常波动事件归因
  1187. if daily_trend and peak_dates:
  1188. peak_vals = [daily_trend.get(d, 0) for d in peak_dates]
  1189. avg_val = sum(daily_trend.values()) / len(daily_trend)
  1190. anomaly = (
  1191. f'本月峰值日:{"、".join(peak_dates)}({"、".join([str(v) for v in peak_vals])}单),'
  1192. f'分别为均值的{_safe_div(max(peak_vals), avg_val):.1f}倍。'
  1193. f'峰值日需区分"机会型"(大客户集中下单/展会签约)与"补录型"(历史订单系统批量导入)。'
  1194. f'建议对峰值日逐单标注事件类型,长期积累后可识别真正的业务脉冲与数据噪声。'
  1195. )
  1196. else:
  1197. anomaly = (
  1198. f'本月缺乏分日峰值数据,无法识别异常波动事件。'
  1199. f'建议建立日报机制后,采用"均值+2倍标准差"规则自动标记异常日,并要求负责人提交事件说明。'
  1200. )
  1201. items.append({'title': '⚠️ 异常波动事件归因', 'content': anomaly})
  1202. # ③ 外部因素关联
  1203. fx_change = context.get('fx_change_pct')
  1204. policy_events = context.get('policy_events', [])
  1205. shipping_delay = context.get('shipping_delay_days', 0)
  1206. external = (
  1207. f'外部因素评估:'
  1208. f'{"汇率变动" + _fmt_pct(fx_change) + ",对出口报价竞争力产生" + ("正面" if (fx_change or 0) < 0 else "负面") + "影响;" if fx_change is not None else "暂无汇率数据;"}'
  1209. f'{"本月政策事件:" + "、".join(policy_events[:2]) + ";" if policy_events else "暂无重大政策事件;"}'
  1210. f'{"船期平均延误" + str(shipping_delay) + "天,可能滞后影响客户下单信心" if shipping_delay else "船期正常"}。'
  1211. f'外部因素与订单波动存在1-4周滞后,建议建立外部因素登记簿,量化其对后续月份的影响。'
  1212. )
  1213. items.append({'title': '🌐 外部因素关联', 'content': external})
  1214. # ④ 下月预测
  1215. mom_pct = _pct_change(total_contracts, metrics.get('prev_total_contracts', 0))
  1216. if forecast_next:
  1217. forecast = (
  1218. f'Pipeline预测下月交付{forecast_next}台,结合本月{total_contracts}单(MoM {_fmt_pct(mom_pct)}),'
  1219. f'下月订单量预计维持在{int(total_contracts * 0.9)}-{int(total_contracts * 1.15)}单区间。'
  1220. f'{"若外部利好持续,有望突破区间上限" if (mom_pct or 0) > 10 else ""}'
  1221. f'{"若趋势承压,可能回落至区间下限,需提前储备补单方案" if (mom_pct or 0) < 0 else ""}'
  1222. f'。预测准确性应每月复盘,偏差超过20%时校准预测模型参数。'
  1223. )
  1224. else:
  1225. forecast = (
  1226. f'本月完成{total_contracts}单,缺乏pipeline预测数据。'
  1227. f'基于历史趋势,下月预计{int(total_contracts * 0.9)}-{int(total_contracts * 1.1)}单。'
  1228. f'建议建立A+B阶段存量订单与下月签约的转化模型,提升预测准确性。'
  1229. )
  1230. items.append({'title': '🔮 下月预测', 'content': forecast})
  1231. # ⑤ 趋势持续性信号
  1232. sustain = (
  1233. f'判断下月趋势持续性的三个信号:'
  1234. f'(1)早期pipeline:A+B阶段存量是否充足,能否支撑下月转化;'
  1235. f'(2)客户活跃度:本月更新进度订单数是否维持高位,反映客户 engagement;'
  1236. f'(3)支持需求趋势:若支持需求逐周下降,说明流程顺畅,订单转化阻力减小。'
  1237. f'三个信号中有两个向好,则下月趋势延续概率大于70%。'
  1238. )
  1239. items.append({'title': '📊 趋势持续性信号', 'content': sustain})
  1240. return items
  1241. def _insight_monthly_team(metrics: dict, context: dict) -> list[dict]:
  1242. """Page 8: 人均效能、团队结构优化、激励效果、下月人员配置"""
  1243. items = []
  1244. team = metrics.get('team', {})
  1245. per_capita_orders = metrics.get('per_capita_orders', 0)
  1246. per_capita_qty = metrics.get('per_capita_qty', 0)
  1247. total_contracts = metrics.get('total_contracts', 0)
  1248. total_qty = metrics.get('total_qty', 0)
  1249. if isinstance(team, dict) and 'owners' in team:
  1250. owners = team['owners']
  1251. qty_map = team.get('qty', {})
  1252. else:
  1253. owners = {k: v.get('orders', 0) for k, v in team.items()} if team else {}
  1254. qty_map = {k: v.get('qty', 0) for k, v in team.items()} if team else {}
  1255. if not owners:
  1256. return [
  1257. {'title': '💡 人均效能趋势', 'content': '本月暂无负责人数据。建议完善订单归属字段,以便评估团队人效并优化配置。'},
  1258. ]
  1259. n_members = len(owners)
  1260. sorted_owners = sorted(owners.items(), key=lambda x: x[1], reverse=True)
  1261. top_owner, top_val = sorted_owners[0]
  1262. prev_per_capita = context.get('prev_per_capita_orders', 0)
  1263. yoy_per_capita = context.get('yoy_per_capita_orders', 0)
  1264. # ① 人均效能趋势
  1265. mom_chg = _pct_change(per_capita_orders, prev_per_capita)
  1266. yoy_chg = _pct_change(per_capita_orders, yoy_per_capita)
  1267. efficiency = (
  1268. f'本月人均{per_capita_orders:.1f}单({per_capita_qty:.0f}台),'
  1269. f'环比{_fmt_chg_dir(mom_chg)}{_fmt_pct(mom_chg)},同比{_fmt_chg_dir(yoy_chg)}{_fmt_pct(yoy_chg)}。'
  1270. f'{"人均效能双升,团队能力在积累" if (mom_chg or 0) > 0 and (yoy_chg or 0) > 0 else ""}'
  1271. f'{"环比升但同比降,说明短期改善但尚未恢复历史水平" if (mom_chg or 0) > 0 and (yoy_chg or 0) < 0 else ""}'
  1272. f'{"人均效能承压,需排查是市场总量下降还是团队效率退化" if (mom_chg or 0) < 0 and (yoy_chg or 0) < 0 else ""}'
  1273. f'。人均效能是团队健康度的核心指标,建议纳入月度考核并与激励挂钩。'
  1274. )
  1275. items.append({'title': '💡 人均效能趋势', 'content': efficiency})
  1276. # ② 团队结构优化建议
  1277. tail_count = sum(1 for _, v in sorted_owners if v < per_capita_orders * 0.5)
  1278. structure = (
  1279. f'本月团队{n_members}人,尾部{tail_count}人产出低于人均50%。'
  1280. f'{"尾部占比高,团队呈金字塔结构,需加强腰部建设" if tail_count > n_members // 3 else "尾部占比低,团队呈橄榄型,结构健康"}。'
  1281. f'优化建议:'
  1282. f'(1)低产出人员:分析是能力问题(培训)还是资源问题(客户/国家重新分配);'
  1283. f'(2)高产出人员:防止过度依赖,建立AB角备份;'
  1284. f'(3)新入职人员:前3个月以保护期为主,第4个月起按正常人均考核。'
  1285. )
  1286. items.append({'title': '⚖️ 团队结构优化建议', 'content': structure})
  1287. # ③ 激励效果评估
  1288. incentive_effect = context.get('incentive_effect', '')
  1289. if incentive_effect:
  1290. incentive = (
  1291. f'本月激励政策效果:{incentive_effect}。'
  1292. f'激励效果评估应区分"增量激励"(新签奖励)与"存量激励"(转化奖励),'
  1293. f'避免团队为拿新签奖而忽视存量转化。建议设置激励上限和平衡系数,'
  1294. f'确保短期激励与长期客户价值不冲突。'
  1295. )
  1296. else:
  1297. incentive = (
  1298. f'本月暂无激励效果量化数据。建议下月实施激励政策时,同步记录政策前后的人均产出变化。'
  1299. f'激励效果=(政策后人均产出-政策前人均产出)/激励总成本,ROI大于1.5说明激励有效。'
  1300. )
  1301. items.append({'title': '🏆 激励效果评估', 'content': incentive})
  1302. # ④ 标杆对比与提升路径
  1303. top_gap = top_val - per_capita_orders if per_capita_orders else 0
  1304. best_practice = (
  1305. f'本月团队领跑者{top_owner}完成{top_val}单,超人均{top_gap:.1f}单,是均值{_safe_div(top_val, per_capita_orders) if per_capita_orders else 0:.1f}倍。'
  1306. f'建议将其客户跟进SOP、谈判策略、响应时效提炼为标准流程,通过"老带新"机制复制到全团队。'
  1307. f'同时建立月度技能分享会,让头部负责人分享成交案例和失败教训,缩短新人成长周期。'
  1308. f'对于连续两月低于人均50%的成员,启动一对一辅导计划,30天内无明显改善则考虑调岗。'
  1309. )
  1310. items.append({'title': '🎯 标杆对比与提升路径', 'content': best_practice})
  1311. # ⑤ 下月人员配置
  1312. next_month_target = context.get('next_month_target', 0)
  1313. if next_month_target and per_capita_orders:
  1314. required = int(next_month_target / per_capita_orders)
  1315. gap = required - n_members
  1316. staffing = (
  1317. f'下月目标{next_month_target}单,按本月人均{per_capita_orders:.1f}单计算,需{required}人,'
  1318. f'当前{n_members}人,{"缺口" + str(gap) + "人,建议启动招聘或内部调配" if gap > 0 else "冗余" + str(abs(gap)) + "人,可优化至其他业务线" if gap < 0 else "刚好匹配"}。'
  1319. f'若市场处于上升期,建议按目标人数的110%配置,预留20%冗余应对突发需求。'
  1320. )
  1321. else:
  1322. staffing = (
  1323. f'下月人员配置建议:维持现有{n_members}人编制,重点优化结构而非单纯扩编。'
  1324. f'若人均产出连续两月下滑,说明市场或团队出现问题,此时扩编只会稀释效率;'
  1325. f'若人均产出持续上升且超负荷,则扩编是必要且紧迫的。'
  1326. )
  1327. items.append({'title': '👥 下月人员配置', 'content': staffing})
  1328. return items
  1329. def _insight_monthly_support(metrics: dict, context: dict) -> list[dict]:
  1330. """Page 9: 需求类型趋势、高频问题根因、流程优化、SLA达成率"""
  1331. items = []
  1332. support_categories = metrics.get('support_categories', {})
  1333. support_count = metrics.get('support_count', 0)
  1334. support_pct = metrics.get('support_pct', 0)
  1335. prev_support_categories = context.get('prev_support_categories', {})
  1336. # ① 需求类型趋势
  1337. if support_categories and prev_support_categories:
  1338. trends = []
  1339. for cat, count in support_categories.items():
  1340. prev = prev_support_categories.get(cat, 0)
  1341. chg = _pct_change(count, prev)
  1342. trends.append((cat, chg))
  1343. trends.sort(key=lambda x: (x[1] or 0), reverse=True)
  1344. fastest = trends[0] if trends else (None, None)
  1345. trend_text = (
  1346. f'本月支持需求共{support_count}项(占订单{support_pct}%)。'
  1347. f'增长最快类别为"{fastest[0]}"({_fmt_pct(fastest[1])}),'
  1348. f'{"说明该领域存在系统性短板,需优先根治" if (fastest[1] or 0) > 50 else ""}'
  1349. f'{"增速可控,按常规节奏优化即可" if (fastest[1] or 0) <= 50 else ""}'
  1350. f'。建议每月输出支持需求趋势图,识别"慢性增长"与"急性爆发"两类问题,分别用流程优化和应急响应处理。'
  1351. )
  1352. else:
  1353. trend_text = (
  1354. f'本月支持需求共{support_count}项(占订单{support_pct}%)。'
  1355. f'缺乏上月分类数据,无法计算各类别增速。建议建立支持需求分类台账,'
  1356. f'按财务/法务/物流/售后/IT五大类归档,每月对比识别增长最快的类别。'
  1357. )
  1358. items.append({'title': '📈 需求类型趋势', 'content': trend_text})
  1359. # ② 高频问题根因
  1360. if support_categories:
  1361. top3 = sorted(support_categories.items(), key=lambda x: x[1], reverse=True)[:3]
  1362. root = (
  1363. f'本月Top 3高频问题:{"、".join([f"{c}({v}项)" for c, v in top3])}。'
  1364. f'高频问题的根因通常可归结为三类:'
  1365. f'(1)流程断点:跨部门协作无明确SLA,导致需求在部门间空转;'
  1366. f'(2)信息孤岛:客户/订单/物流数据分散,重复查询浪费人力;'
  1367. f'(3)能力短板:一线人员对产品/政策理解不足,过度依赖支持部门。'
  1368. f'建议对Top 3问题各做一次5Why分析,找到可系统改进的根因。'
  1369. )
  1370. else:
  1371. root = (
  1372. f'本月无支持需求分类数据。建议强制要求提交支持需求时选择类别并简述根因,'
  1373. f'否则不予处理。数据质量是分析的前提,没有分类的数据无法产生洞察。'
  1374. )
  1375. items.append({'title': '🔍 高频问题根因', 'content': root})
  1376. # ③ 流程优化建议
  1377. optimization = (
  1378. f'流程优化建议:'
  1379. f'(1)自助化:将Top 20%高频问题转化为FAQ或系统自助查询,减少人工支持需求;'
  1380. f'(2)前置化:在订单关键节点(如合同锁定/生产完成)自动触发检查清单,提前消除潜在问题;'
  1381. f'(3)标准化:对剩余80%中频问题建立SOP和处理模板,将平均处理时长压缩50%。'
  1382. f'优化效果应每月量化:支持需求总量是否下降、重复问题占比是否降低、客户满意度是否提升。'
  1383. )
  1384. items.append({'title': '⚙️ 流程优化建议', 'content': optimization})
  1385. # ④ SLA达成率
  1386. sla_data = context.get('sla_achievement', {})
  1387. if sla_data:
  1388. overall = sla_data.get('overall', 0)
  1389. sla_text = (
  1390. f'本月支持需求SLA达成率{overall}%。'
  1391. f'{"达成率大于90%,响应速度优秀" if overall > 90 else "达成率80%-90%,基本达标但存在超期" if overall >= 80 else "达成率低于80%,响应速度严重滞后,需立即增派人手或优化流程"}。'
  1392. f'分类达成率:{"、".join([f"{k}:{v}%" for k, v in sla_data.items() if k != "overall"][:3])}。'
  1393. f'低于目标的类别应作为下月改进重点,分配专项资源提升。'
  1394. )
  1395. else:
  1396. sla_text = (
  1397. f'本月暂无SLA达成率数据。建议为每类支持需求设定处理时限:'
  1398. f'紧急(4h)、高(24h)、中(48h)、低(72h),并记录实际关闭时间。'
  1399. f'SLA是支持部门的服务承诺,也是内部客户体验的核心指标。'
  1400. )
  1401. items.append({'title': '⏱️ SLA达成率', 'content': sla_text})
  1402. return items
  1403. def _insight_monthly_plan(metrics: dict, context: dict) -> list[dict]:
  1404. """Page 10: 目标可行性分析、关键假设、风险场景、Contingency Plan"""
  1405. items = []
  1406. next_month_goals = metrics.get('next_month_goals', [])
  1407. total_qty = metrics.get('total_qty', 0)
  1408. risks = metrics.get('risks', [])
  1409. forecast_next = metrics.get('forecast_next', 0)
  1410. # ① 目标可行性分析
  1411. if next_month_goals:
  1412. goal_total = sum(g.get('number', 0) for g in next_month_goals)
  1413. feasibility = (
  1414. f'下月目标共{len(next_month_goals)}项,量化指标合计{goal_total}。'
  1415. f'基于本月{total_qty}台及pipeline预测{forecast_next}台,目标可行性{"高" if forecast_next >= goal_total * 0.8 else "中" if forecast_next >= goal_total * 0.5 else "低"}。'
  1416. f'{"Pipeline充足,目标具备充分支撑" if forecast_next >= goal_total else "Pipeline低于目标,需额外拓展新单填补缺口"}。'
  1417. f'可行性分析应每月更新,若连续两月可行性评级为低,需下调目标或追加资源。'
  1418. )
  1419. else:
  1420. feasibility = (
  1421. f'下月目标尚未设定。建议基于本月{total_qty}台和pipeline{forecast_next}台,'
  1422. f'按"保底(本月×0.9)/基准(本月×1.0)/挑战(本月×1.2)"三档设定目标。'
  1423. f'三档目标分别对应不同的资源投入和激励方案,给团队明确的方向感。'
  1424. )
  1425. items.append({'title': '🎯 目标可行性分析', 'content': feasibility})
  1426. # ② 关键假设
  1427. assumptions = context.get('key_assumptions', [])
  1428. if assumptions:
  1429. assump_text = (
  1430. f'下月目标达成的关键假设:{"、".join(assumptions[:3])}。'
  1431. f'关键假设是目标可行性的前提条件,任一假设失效都可能导致目标无法达成。'
  1432. f'建议每周Review假设状态:绿色(稳定)、黄色(波动)、红色(失效),红色假设需在48h内启动应对预案。'
  1433. )
  1434. else:
  1435. assump_text = (
  1436. f'下月目标的关键假设尚未明确。建议至少列出3个最关键假设:'
  1437. f'(1)大客户复购率维持本月水平;'
  1438. f'(2)主要船期无大面积延误;'
  1439. f'(3)汇率波动不超过5%。'
  1440. f'假设清单是风险管理的起点,没有假设的目标只是愿望。'
  1441. )
  1442. items.append({'title': '🔑 关键假设', 'content': assump_text})
  1443. # ③ 风险场景(最坏情况)
  1444. if risks:
  1445. worst_gap = context.get('worst_case_gap', 0)
  1446. risk_text = (
  1447. f'已识别风险:{"、".join([r.get("title", "") for r in risks[:3]])}。'
  1448. f'最坏情况下,预计月度缺口约{worst_gap}台。'
  1449. f'缺口弥补方案:(1)加速A→B转化,释放存量pipeline;(2)启动紧急促销,刺激短期下单;'
  1450. f'(3)协调生产加班,压缩交付周期以提升客户信心。'
  1451. f'风险场景应每两周更新一次,确保预案与市场变化同步。'
  1452. )
  1453. else:
  1454. risk_text = (
  1455. f'本月风险清单为空。建议基于历史数据和当前pipeline,推演3个最坏场景:'
  1456. f'(1)Top 1客户延迟下单,缺口30%;(2)船期延误2周,E阶段积压导致客户暂停新签;'
  1457. f'(3)目的国进口政策突变,已锁定合同无法执行。每个场景设定触发条件和应对动作。'
  1458. )
  1459. items.append({'title': '⚠️ 风险场景(最坏情况)', 'content': risk_text})
  1460. # ④ Contingency Plan
  1461. contingency = (
  1462. f'Contingency Plan原则:当实际进度低于目标70%或关键假设失效时,自动触发应急预案。'
  1463. f'预案内容包括:'
  1464. f'(1)资源重新配置:将低效区域人力调至高效区域;'
  1465. f'(2)目标动态调整:按"保利润/保现金流/保市场份额"优先级重新排序;'
  1466. f'(3)外部资源调用:启动代理商紧急补单、申请总部促销资源、协调备用物流商。'
  1467. f'预案需在月初制定并获管理层批准,确保危机发生时24h内可执行。'
  1468. )
  1469. items.append({'title': '🛡️ Contingency Plan', 'content': contingency})
  1470. # ⑤ 里程碑与复盘机制
  1471. milestones = context.get('monthly_milestones', [])
  1472. if milestones:
  1473. mile_text = (
  1474. f'下月关键里程碑:{"、".join(milestones[:3])}。'
  1475. f'里程碑应满足可验证性:有明确交付物、验收人和截止时间。'
  1476. f'建议建立"双周复盘"机制:每月15日和月底对照里程碑,偏差大于20%时触发专项调整。'
  1477. )
  1478. else:
  1479. mile_text = (
  1480. f'下月里程碑尚未设定。建议按"第一周签约冲刺、第二周生产锁定、第三周尾款回收、第四周交付收官"'
  1481. f'设置4个周里程碑,每个里程碑有量化指标和责任人。没有里程碑的月度计划只是方向,不是计划。'
  1482. )
  1483. items.append({'title': '🚩 里程碑与复盘机制', 'content': mile_text})
  1484. return items
  1485. # =============================================================================
  1486. # HELPER FUNCTIONS
  1487. # =============================================================================
  1488. def _pct_change(curr, prev):
  1489. if prev and prev != 0:
  1490. return round((curr - prev) / prev * 100, 1)
  1491. return None
  1492. def _fmt_pct(val):
  1493. if val is None:
  1494. return '—'
  1495. sign = '+' if val >= 0 else ''
  1496. return f'{sign}{val:.1f}%'
  1497. def _fmt_chg_dir(val):
  1498. if val is None:
  1499. return ''
  1500. return '增加' if val >= 0 else '减少'
  1501. def _safe_div(a, b):
  1502. return round(a / b, 1) if b else 0