#!/usr/bin/env python3 # -*- coding: utf-8 -*- """作业 9 · 生成 PPTX(python-pptx)。未安装则跳过,HTML 幻灯片仍可用。""" from __future__ import annotations from pathlib import Path from typing import Any import _bootstrap # noqa: F401 from 作业9_ppt_data import data_dir, fmt_int, fmt_pct, repo_root def write_pptx(deck: dict[str, Any]) -> Path | None: try: from pptx import Presentation from pptx.chart.data import CategoryChartData from pptx.dml.color import RGBColor from pptx.enum.chart import XL_CHART_TYPE from pptx.enum.shapes import MSO_SHAPE from pptx.enum.text import PP_ALIGN from pptx.util import Emu, Inches, Pt except ImportError: print("[WARN] 未安装 python-pptx,只产出 HTML。可执行: pip install python-pptx") return None prs = Presentation() prs.slide_width = Inches(13.333) prs.slide_height = Inches(7.5) blank = prs.slide_layouts[6] navy = RGBColor(0x0A, 0x1B, 0x30) white = RGBColor(0xF4, 0xF8, 0xFC) muted = RGBColor(0x8F, 0xB4, 0xD9) acc = RGBColor(0x3B, 0x9E, 0xFF) ok = RGBColor(0x3D, 0xD6, 0x8C) card = RGBColor(0x10, 0x2A, 0x46) def fill_slide(slide): shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height) shape.fill.solid() shape.fill.fore_color.rgb = navy shape.line.fill.background() def add_text(slide, l, t, w, h, text, size=18, color=white, bold=False): box = slide.shapes.add_textbox(Inches(l), Inches(t), Inches(w), Inches(h)) tf = box.text_frame tf.word_wrap = True p = tf.paragraphs[0] p.text = str(text) p.font.size = Pt(size) p.font.color.rgb = color p.font.bold = bold p.font.name = "Microsoft YaHei" return box def kpi_card(slide, l, t, title, value, sub): sh = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(l), Inches(t), Inches(2.9), Inches(1.55)) sh.fill.solid() sh.fill.fore_color.rgb = card sh.line.color.rgb = RGBColor(0x1E, 0x4A, 0x73) add_text(slide, l + 0.12, t + 0.12, 2.6, 0.35, title, 11, muted) add_text(slide, l + 0.12, t + 0.45, 2.6, 0.55, value, 22, white, True) add_text(slide, l + 0.12, t + 1.05, 2.6, 0.35, sub, 11, muted) core = deck.get("core") or {} biz = deck.get("business") or {} cost = deck.get("cost") or {} nxt = deck.get("next") or {} mom = core.get("mom") or {} # 封面 s = prs.slides.add_slide(blank) fill_slide(s) add_text(s, 0.8, 2.1, 11, 0.4, "%s · 管理层汇报模版" % deck.get("org", ""), 16, muted) add_text(s, 0.8, 2.6, 11, 1.0, deck.get("title") or "AI 平台经营汇报", 40, white, True) add_text(s, 0.8, 3.8, 11, 0.5, deck.get("period") or "", 22, acc) add_text(s, 0.8, 4.5, 11, 0.4, "汇报人:%s" % (deck.get("presenter") or ""), 16, muted) # 核心指标 s = prs.slides.add_slide(blank) fill_slide(s) add_text(s, 0.5, 0.28, 12, 0.5, "1. 平台核心指标(一页纸)", 26, white, True) kpi_card(s, 0.45, 0.95, "总请求量", fmt_int(core.get("total_requests") or 0), "环比 %s" % fmt_pct(mom.get("traces_wow_pct") or 0, True)) kpi_card(s, 3.55, 0.95, "Token 消耗", fmt_int(core.get("total_tokens") or 0), "环比 %s" % fmt_pct(mom.get("tokens_wow_pct") or 0, True)) kpi_card(s, 6.65, 0.95, "可用性", "%s%%" % core.get("availability_pct"), "目标 ≥ 99.9%") kpi_card(s, 9.75, 0.95, "延迟 P99", "%ss / %ss" % (core.get("ttft_p99"), core.get("full_p99")), "首 Token / 完整响应") daily = list(core.get("daily") or []) if daily: chart_data = CategoryChartData() chart_data.categories = [(d.get("date") or "")[5:] for d in daily] chart_data.add_series("日请求量", [int(d.get("traces") or 0) for d in daily]) chart = s.shapes.add_chart( XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(0.45), Inches(2.75), Inches(12.4), Inches(4.3), chart_data, ).chart chart.has_legend = False # 业务价值 s = prs.slides.add_slide(blank) fill_slide(s) add_text(s, 0.5, 0.28, 12, 0.5, "2. 业务价值(重点)", 26, white, True) items = [ ("客服效率提升", "+%s%%" % biz.get("cs_efficiency_pct"), biz.get("cs_note")), ("研发编码效率提升", "+%s%%" % biz.get("dev_efficiency_pct"), biz.get("dev_note")), ( "知识检索时间", "%s 分钟 → %s 秒" % (biz.get("kb_before_minutes"), biz.get("kb_after_seconds")), biz.get("kb_note"), ), ] for i, (title, val, note) in enumerate(items): x = 0.5 + i * 4.2 sh = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(x), Inches(1.2), Inches(3.95), Inches(5.4)) sh.fill.solid() sh.fill.fore_color.rgb = card sh.line.color.rgb = RGBColor(0x1E, 0x4A, 0x73) add_text(s, x + 0.25, 1.45, 3.5, 0.5, title, 16, muted) add_text(s, x + 0.25, 2.2, 3.5, 1.4, val, 28, ok, True) add_text(s, x + 0.25, 3.8, 3.5, 2.4, note or "", 14, white) # 成本 s = prs.slides.add_slide(blank) fill_slide(s) add_text(s, 0.5, 0.28, 12, 0.5, "3. 成本优化", 26, white, True) add_text(s, 0.5, 1.1, 12, 0.4, "%s通过量化 / 缓存 / 模型降级节省" % (cost.get("period_label") or ""), 16, muted) add_text(s, 0.5, 1.6, 12, 1.0, "¥ %s 万元" % cost.get("saved_wan"), 40, ok, True) add_text( s, 0.5, 3.0, 12, 2.5, "结构:量化 %s%% · 缓存 %s%% · 模型降级 %s%%\n\n%s" % (cost.get("quantize_pct"), cost.get("cache_pct"), cost.get("downgrade_pct"), cost.get("note") or ""), 18, white, ) # 规划 s = prs.slides.add_slide(blank) fill_slide(s) add_text(s, 0.5, 0.28, 12, 0.5, "4. 下一步规划", 26, white, True) cols = [ ("下季度目标", nxt.get("goals") or []), ("资源需求", nxt.get("resources") or []), ("风险预警", nxt.get("risks") or []), ] for i, (title, lines) in enumerate(cols): x = 0.45 + i * 4.25 sh = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(x), Inches(1.05), Inches(4.05), Inches(5.7)) sh.fill.solid() sh.fill.fore_color.rgb = card sh.line.color.rgb = RGBColor(0x1E, 0x4A, 0x73) add_text(s, x + 0.2, 1.2, 3.6, 0.45, title, 18, acc, True) add_text(s, x + 0.2, 1.8, 3.6, 4.6, "\n".join("• %s" % x for x in lines), 13, white) out = data_dir() / "AI平台经营汇报.pptx" prs.save(str(out)) copy = repo_root() / "docs" / "html_cursor" / "作业9_AI平台经营汇报.pptx" copy.parent.mkdir(parents=True, exist_ok=True) copy.write_bytes(out.read_bytes()) return out