#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""六人年度目标责任书达成 — 8页PPT（自包含，无外部依赖）。"""
import json, os
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION
from pptx.oxml.ns import qn
from lxml import etree

ROOT = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
DATA = json.load(open(os.path.join(ROOT, '数据/年度目标责任书_六人达成_20261108.json'), encoding='utf-8'))
OUT = os.path.join(ROOT, '经营分析/月报/年度目标责任书达成_六人_20261108.pptx')
LOGO = '/Users/liuxinyuan/work/公司资料/logo_transparent.png'

# Brand colors
NAVY = RGBColor(0x0B, 0x2A, 0x4A)
BLUE = RGBColor(0x1F, 0x6F, 0xEB)
LIGHT = RGBColor(0xEA, 0xF2, 0xFB)
PALE = RGBColor(0xF5, 0xF8, 0xFC)
GREY = RGBColor(0x5B, 0x6B, 0x7F)
MIDGREY = RGBColor(0x8A, 0x99, 0xAA)
RED = RGBColor(0xC0, 0x39, 0x2B)
AMBER = RGBColor(0xB7, 0x79, 0x1F)
GREEN = RGBColor(0x1E, 0x84, 0x49)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)


def set_run(run, size=10, color=NAVY, bold=False, italic=False):
    run.font.size = Pt(size)
    run.font.bold = bold
    run.font.italic = italic
    run.font.color.rgb = color
    # Latin font Times New Roman, East Asian 宋体
    run.font.name = 'Times New Roman'
    rPr = run._r.get_or_add_rPr()
    ea = rPr.find(qn('a:ea'))
    if ea is None:
        ea = etree.SubElement(rPr, qn('a:ea'))
    ea.set('typeface', '宋体')
    cs = rPr.find(qn('a:cs'))
    if cs is None:
        cs = etree.SubElement(rPr, qn('a:cs'))
    cs.set('typeface', '宋体')


def add_text(slide, x, y, w, h, text, size=10, color=NAVY, bold=False,
             align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, line_spacing=1.15):
    box = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
    tf = box.text_frame
    tf.word_wrap = True
    tf.margin_left = 0
    tf.margin_right = 0
    tf.margin_top = 0
    tf.margin_bottom = 0
    tf.vertical_anchor = anchor
    lines = text.split('\n') if isinstance(text, str) else text
    for i, line in enumerate(lines):
        p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
        p.alignment = align
        p.line_spacing = line_spacing
        r = p.add_run()
        r.text = line
        set_run(r, size, color, bold)
    return box


def add_rect(slide, x, y, w, h, fill, line=None, radius=False):
    shape_type = MSO_SHAPE.ROUNDED_RECTANGLE if radius else MSO_SHAPE.RECTANGLE
    shp = slide.shapes.add_shape(shape_type, Inches(x), Inches(y), Inches(w), Inches(h))
    shp.fill.solid()
    shp.fill.fore_color.rgb = fill
    if line:
        shp.line.color.rgb = line
        shp.line.width = Pt(0.75)
    else:
        shp.line.fill.background()
    if radius:
        try:
            shp.adjustments[0] = 0.08
        except Exception:
            pass
    return shp


def add_header(slide, title, subtitle, page):
    # thin top rule
    add_rect(slide, 0, 0, 13.333, 0.06, BLUE)
    if os.path.exists(LOGO):
        slide.shapes.add_picture(LOGO, Inches(0.48), Inches(0.22), height=Inches(0.34))
    add_text(slide, 0.48, 0.64, 9.8, 0.46, title, size=20, color=NAVY, bold=True)
    add_text(slide, 0.48, 1.08, 12.3, 0.3, subtitle, size=9.5, color=GREY)
    add_text(slide, 11.8, 0.26, 1.05, 0.24, f'{page:02d}', size=10, color=MIDGREY, align=PP_ALIGN.RIGHT)


def add_footer(slide, page, note):
    add_rect(slide, 0.48, 7.02, 12.35, 0.012, RGBColor(0xD9, 0xE2, 0xEE))
    add_text(slide, 0.48, 7.10, 9.9, 0.22, note, size=7.2, color=MIDGREY)
    add_text(slide, 10.55, 7.10, 2.25, 0.22, '守护生命健康，缔造美好生活', size=7.5, color=BLUE, bold=True, align=PP_ALIGN.RIGHT)


def status_color(rate):
    if isinstance(rate, (int, float)):
        if rate >= 80:
            return GREEN
        if rate >= 60:
            return AMBER
        return RED
    return MIDGREY


def add_table(slide, x, y, w, h, headers, rows, widths, font_size=9.8, header_size=9.2,
              rate_cols=(), center_cols=(), row_height=0.38):
    nrows, ncols = len(rows) + 1, len(headers)
    shape = slide.shapes.add_table(nrows, ncols, Inches(x), Inches(y), Inches(w), Inches(h))
    table = shape.table
    table.first_row = True
    table.horz_banding = False
    for j, wd in enumerate(widths):
        table.columns[j].width = Inches(wd)
    table.rows[0].height = Inches(0.34)
    for i in range(1, nrows):
        table.rows[i].height = Inches(row_height)

    for j, htext in enumerate(headers):
        cell = table.cell(0, j)
        cell.fill.solid(); cell.fill.fore_color.rgb = NAVY
        cell.margin_left = Inches(0.05); cell.margin_right = Inches(0.04)
        cell.margin_top = Inches(0.02); cell.margin_bottom = Inches(0.02)
        cell.vertical_anchor = MSO_ANCHOR.MIDDLE
        tf = cell.text_frame; tf.word_wrap = True
        p = tf.paragraphs[0]
        p.alignment = PP_ALIGN.CENTER if j in center_cols or j == 0 else PP_ALIGN.LEFT
        r = p.add_run(); r.text = htext
        set_run(r, header_size, WHITE, True)

    for i, row in enumerate(rows, start=1):
        fill = PALE if i % 2 == 0 else WHITE
        for j, val in enumerate(row):
            cell = table.cell(i, j)
            cell.fill.solid(); cell.fill.fore_color.rgb = fill
            cell.margin_left = Inches(0.05); cell.margin_right = Inches(0.04)
            cell.margin_top = Inches(0.02); cell.margin_bottom = Inches(0.02)
            cell.vertical_anchor = MSO_ANCHOR.MIDDLE
            tf = cell.text_frame; tf.word_wrap = True
            p = tf.paragraphs[0]
            p.alignment = PP_ALIGN.CENTER if j in center_cols or j == 0 else PP_ALIGN.LEFT
            r = p.add_run(); r.text = str(val) if val is not None else '—'
            color = NAVY
            if j in rate_cols and isinstance(val, str) and val.endswith('%'):
                try:
                    color = status_color(float(val.strip('%').split('（')[0]))
                except Exception:
                    color = NAVY
            set_run(r, font_size, color, j == 0)
    return table


def kpi_cards(slide, target, actual, rate, gap, y=1.48):
    items = [
        ('年度目标', f'{target:,.2f}' if isinstance(target, (int, float)) else str(target), BLUE),
        ('1-10月实际', f'{actual:,.2f}' if isinstance(actual, (int, float)) else str(actual), NAVY),
        ('年度达成率', f'{rate:.1f}%' if isinstance(rate, (int, float)) else str(rate), status_color(rate)),
        ('年度缺口', f'{gap:,.2f}' if isinstance(gap, (int, float)) else str(gap), RED),
    ]
    xs = [0.55, 3.55, 6.55, 9.55]
    for (label, value, color), x in zip(items, xs):
        add_rect(slide, x, y, 2.75, 0.86, PALE, line=RGBColor(0xDD, 0xE7, 0xF2), radius=True)
        add_text(slide, x + 0.18, y + 0.10, 2.4, 0.2, label, size=8.2, color=GREY)
        add_text(slide, x + 0.18, y + 0.33, 2.4, 0.34, value, size=17, color=color, bold=True)


def source_note(extra=''):
    base = '目标=目标基础数据2026；实际=KPI看板1-10月；开票额=ERP含税代理；单位：万元。'
    return base + (' ' + extra if extra else '')


def slide_title(slide, title, subtitle, page):
    add_header(slide, title, subtitle, page)


prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
blank = prs.slide_layouts[6]

# ============ P1 overview ============
s = prs.slides.add_slide(blank)
slide_title(s, '六人年度目标达成总览', '2026年1-10月 ｜ 诊断原料销售部 + 生命科学销售部', 1)

rows = []
for p in DATA['people']:
    rows.append([p['name'], p['role'], f"{p['target']:,.0f}", f"{p['actual']:,.2f}", f"{p['rate']:.1f}%", f"{p['gap']:,.2f}"])
add_table(s, 0.55, 1.55, 8.35, 3.15,
          ['姓名', '岗位', '年度目标', '1-10月实际', '达成率', '缺口'],
          rows, widths=[1.0, 2.3, 1.25, 1.4, 1.0, 1.4], font_size=9.6, header_size=8.8,
          rate_cols=(4,), center_cols=(0, 2, 3, 4, 5), row_height=0.43)

# right insight card
add_rect(s, 9.15, 1.55, 3.63, 3.15, LIGHT, line=RGBColor(0xD4, 0xE2, 0xF4), radius=True)
add_text(s, 9.35, 1.70, 3.2, 0.26, '三条判断', size=10.5, color=BLUE, bold=True)
judgements = [
    '仅刘欣团队 75.7%；其余五人年度达成率均低于 60%。',
    '诊断原料缺口 2,821 万：大客户 1,439、拓展 826、科研 556。',
    '生命科学区域合计 42.59 / 600 = 7.1%；团队 60% 达成 0/4、0/5。',
]
add_text(s, 9.35, 2.10, 3.18, 2.3, ['• ' + x for x in judgements], size=9.0, color=NAVY, line_spacing=1.35)

# chart
chart_data = CategoryChartData()
chart_data.categories = [p['name'] for p in DATA['people']]
chart_data.add_series('年度目标', [p['target'] for p in DATA['people']])
chart_data.add_series('1-10月实际', [p['actual'] for p in DATA['people']])
chart = s.shapes.add_chart(XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(0.55), Inches(4.98), Inches(12.25), Inches(1.78), chart_data).chart
chart.has_title = True
chart.chart_title.text_frame.text = '年度目标 vs 1-10月实际（万元）'
for p in chart.chart_title.text_frame.paragraphs:
    for r in p.runs:
        set_run(r, 9.5, NAVY, True)
chart.has_legend = True
chart.legend.position = XL_LEGEND_POSITION.BOTTOM
chart.legend.include_in_layout = False
chart.font.size = Pt(8.5); chart.font.name = 'Times New Roman'
chart.plots[0].gap_width = 60
chart.plots[0].overlap = -10
chart.series[0].format.fill.solid(); chart.series[0].format.fill.fore_color.rgb = RGBColor(0xB8, 0xC7, 0xD9)
chart.series[1].format.fill.solid(); chart.series[1].format.fill.fore_color.rgb = BLUE
chart.value_axis.has_major_gridlines = True
chart.value_axis.major_gridlines.format.line.color.rgb = RGBColor(0xE6, 0xEC, 0xF3)
chart.value_axis.tick_labels.font.size = Pt(8)
chart.category_axis.tick_labels.font.size = Pt(8.5)
add_footer(s, 1, '口径：刘新元为部门合计；吴云/韩远怀为区域团队；其余为个人。目标与实际均含年度累计，未年化。')

# ============ P2 刘新元 ============
p = DATA['people'][0]
s = prs.slides.add_slide(blank)
slide_title(s, '刘新元｜诊断原料销售部总监', '部门合计口径 ｜ 2026年1-10月', 2)
kpi_cards(s, p['target'], p['actual'], p['rate'], p['gap'])
rows = [[d['name'], d['weight'], f"{d['target']:,.2f}" if isinstance(d['target'], (int,float)) else d['target'],
         f"{d['actual']:,.2f}" if isinstance(d['actual'], (int,float)) else d['actual'],
         f"{d['rate']:.1f}%" if isinstance(d['rate'], (int,float)) else d['rate'], d['coeff']] for d in p['dimensions']]
add_table(s, 0.55, 2.62, 8.55, 3.95,
          ['目标维度', '权重', '年度目标', '1-10月实际', '达成率', '系数/状态'],
          rows, widths=[2.75, 0.95, 1.35, 1.35, 0.95, 1.0], font_size=9.2, header_size=8.7,
          rate_cols=(4,), center_cols=(1, 2, 3, 4, 5), row_height=0.50)
add_rect(s, 9.35, 2.62, 3.43, 3.95, LIGHT, line=RGBColor(0xD4, 0xE2, 0xF4), radius=True)
add_text(s, 9.55, 2.78, 3.0, 0.25, '缺口与 Q4 动作', size=10.5, color=BLUE, bold=True)
add_text(s, 9.55, 3.12, 3.0, 3.2,
         ['• 大客户缺口 1,439 万：天隆、佰奥、海尔施回补；丽珠守住 99.9%。',
          '• 拓展缺口 826 万：刘欣团队 TOP5 客户 + KA 补缺。',
          '• 科研缺口 556 万：11-12 月“二单冲刺”，按周跟踪。',
          '• 责任书：2026 版未提供，权重/系数按 2025 版参考。'],
         size=8.8, color=NAVY, line_spacing=1.32)
add_footer(s, 2, source_note('2026版责任书未找到；挑战目标待补。'))

# ============ P3 刘欣 ============
p = DATA['people'][1]
s = prs.slides.add_slide(blank)
slide_title(s, '刘欣｜销售拓展部经理', '销售拓展部团队口径 ｜ 2026年1-10月', 3)
kpi_cards(s, p['target'], p['actual'], p['rate'], p['gap'])
rows = [[d['name'], d['weight'], f"{d['target']:,.2f}" if isinstance(d['target'], (int,float)) else d['target'],
         f"{d['actual']:,.2f}" if isinstance(d['actual'], (int,float)) else d['actual'],
         f"{d['rate']:.1f}%" if isinstance(d['rate'], (int,float)) else d['rate'], d['coeff']] for d in p['dimensions']]
add_table(s, 0.55, 2.62, 8.55, 3.95,
          ['目标维度', '权重', '年度目标', '1-10月实际', '达成率', '系数/状态'],
          rows, widths=[2.75, 0.95, 1.35, 1.35, 0.95, 1.0], font_size=9.0, header_size=8.7,
          rate_cols=(4,), center_cols=(1, 2, 3, 4, 5), row_height=0.50)
add_rect(s, 9.35, 2.62, 3.43, 3.95, LIGHT, line=RGBColor(0xD4, 0xE2, 0xF4), radius=True)
add_text(s, 9.55, 2.78, 3.0, 0.25, '关键卡点', size=10.5, color=BLUE, bold=True)
add_text(s, 9.55, 3.12, 3.0, 3.2,
         ['• 团队 8/11 人达到 60% 目标，E=72.7%，过 70% 线。',
          '• KA 46/85；按 5 万+ 口径 81 家，接近目标。',
          '• NGS 客户增长未取到可靠口径，标“待补”。',
          '• Q4：TOP5 客户、KA 补缺、NGS 增量三张清单。'],
         size=8.8, color=NAVY, line_spacing=1.32)
add_footer(s, 3, source_note('团队目标取部门3400；责任书团队销售金额栏为空。'))

# ============ P4 赵云浩 ============
p = DATA['people'][2]
s = prs.slides.add_slide(blank)
slide_title(s, '赵云浩｜大客户部销售工程师', '个人口径 ｜ 2026年1-10月', 4)
kpi_cards(s, p['target'], p['actual'], p['rate'], p['gap'])
rows = [[d['name'], d['weight'], f"{d['target']:,.2f}" if isinstance(d['target'], (int,float)) else d['target'],
         f"{d['actual']:,.2f}" if isinstance(d['actual'], (int,float)) else d['actual'],
         f"{d['rate']:.1f}%" if isinstance(d['rate'], (int,float)) else d['rate'], d['coeff']] for d in p['dimensions']]
add_table(s, 0.55, 2.62, 8.55, 3.45,
          ['目标维度', '权重', '年度目标', '1-10月实际', '达成率', '系数/状态'],
          rows, widths=[2.75, 0.95, 1.35, 1.35, 0.95, 1.0], font_size=9.8, header_size=8.7,
          rate_cols=(4,), center_cols=(1, 2, 3, 4, 5), row_height=0.50)
add_rect(s, 9.35, 2.62, 3.43, 3.45, LIGHT, line=RGBColor(0xD4, 0xE2, 0xF4), radius=True)
add_text(s, 9.55, 2.78, 3.0, 0.25, '责任书维度', size=10.5, color=BLUE, bold=True)
add_text(s, 9.55, 3.12, 3.0, 2.9,
         ['• 正常 1,350 / 底线 810 / 挑战 1,755。',
          '• Y1：15 个有销售集团，丢 4 个，系数 0.5。',
          '• Z：圣湘+万孚 47.2%，系数 0.5。',
          '• D：河南客户 125.5%，系数 1.0。',
          '• Q4：圣湘/明德/中道/华之源回补；万孚保增长。'],
         size=8.5, color=NAVY, line_spacing=1.28)
add_footer(s, 4, source_note('2026责任书已收到；D按河南客户名代理，附件清单待核。'))

# ============ P5 刘子研 ============
p = DATA['people'][3]
s = prs.slides.add_slide(blank)
slide_title(s, '刘子研｜大客户部销售工程师', '个人口径 ｜ 2026年1-10月', 5)
kpi_cards(s, p['target'], p['actual'], p['rate'], p['gap'])
rows = [[d['name'], d['weight'], f"{d['target']:,.2f}" if isinstance(d['target'], (int,float)) else d['target'],
         f"{d['actual']:,.2f}" if isinstance(d['actual'], (int,float)) else d['actual'],
         f"{d['rate']:.1f}%" if isinstance(d['rate'], (int,float)) else d['rate'], d['coeff']] for d in p['dimensions']]
add_table(s, 0.55, 2.62, 8.55, 3.55,
          ['目标维度', '权重', '年度目标', '1-10月实际', '达成率', '系数/状态'],
          rows, widths=[2.75, 0.95, 1.35, 1.35, 0.95, 1.0], font_size=9.2, header_size=8.7,
          rate_cols=(4,), center_cols=(1, 2, 3, 4, 5), row_height=0.55)
add_rect(s, 9.35, 2.62, 3.43, 3.55, LIGHT, line=RGBColor(0xD4, 0xE2, 0xF4), radius=True)
add_text(s, 9.55, 2.78, 3.0, 0.25, '三个考核项', size=10.5, color=BLUE, bold=True)
add_text(s, 9.55, 3.12, 3.0, 2.9,
         ['• Y1：11 个 2025 大客户集团，丢 5 个，系数 0.5。',
          '• Z：天隆+丽珠 75.3%，系数 0.5。',
          '• E：之江 3.45 万已完成，艾德未达 1.5 万，1/2，系数 1.5。',
          '• Q4：天隆/佰奥/海尔施回补；艾德 NGS 部门继续攻。'],
         size=8.8, color=NAVY, line_spacing=1.32)
add_footer(s, 5, source_note('Y1按大客户1清单集团归集；Z按天隆+丽珠；E按之江、艾德。'))

# ============ P6 吴云 ============
p = DATA['people'][4]
s = prs.slides.add_slide(blank)
slide_title(s, '吴云｜生命科学浙江区域经理', '区域团队口径 ｜ 2026年1-10月', 6)
kpi_cards(s, p['target'], p['actual'], p['rate'], p['gap'])
rows = [[d['name'], d['weight'], str(d['target']) if isinstance(d['target'], str) else f"{d['target']:,.2f}",
         str(d['actual']) if isinstance(d['actual'], str) else f"{d['actual']:,.2f}",
         f"{d['rate']:.1f}%" if isinstance(d['rate'], (int,float)) else d['rate'], d['coeff']] for d in p['dimensions']]
add_table(s, 0.55, 2.62, 8.55, 3.95,
          ['目标维度', '权重', '年度目标', '1-10月实际', '达成率', '系数/状态'],
          rows, widths=[2.75, 0.95, 1.35, 1.35, 0.95, 1.0], font_size=8.9, header_size=8.6,
          rate_cols=(4,), center_cols=(1, 2, 3, 4, 5), row_height=0.50)
add_rect(s, 9.35, 2.62, 3.43, 3.95, LIGHT, line=RGBColor(0xD4, 0xE2, 0xF4), radius=True)
add_text(s, 9.55, 2.78, 3.0, 0.25, '关键卡点', size=10.5, color=BLUE, bold=True)
add_text(s, 9.55, 3.12, 3.0, 3.2,
         ['• 区域团队 28.50/300，9.5%；底线 240 也未到。',
          '• 课题组 42/320，Y1=0.8。',
          '• 团队 60% 达成 0/4，Z1=0.5。',
          '• 责任书权重合计 110%，需 HR 确认。'],
         size=8.8, color=NAVY, line_spacing=1.32)
add_footer(s, 6, source_note('区域团队实际按订单销售员归属；课题组按成交客户名代理。'))

# ============ P7 韩远怀 ============
p = DATA['people'][5]
s = prs.slides.add_slide(blank)
slide_title(s, '韩远怀｜生命科学广东区域经理', '区域团队口径 ｜ 2026年1-10月', 7)
kpi_cards(s, p['target'], p['actual'], p['rate'], p['gap'])
rows = [[d['name'], d['weight'], str(d['target']) if isinstance(d['target'], str) else f"{d['target']:,.2f}",
         str(d['actual']) if isinstance(d['actual'], str) else f"{d['actual']:,.2f}",
         f"{d['rate']:.1f}%" if isinstance(d['rate'], (int,float)) else d['rate'], d['coeff']] for d in p['dimensions']]
add_table(s, 0.55, 2.62, 8.55, 3.95,
          ['目标维度', '权重', '年度目标', '1-10月实际', '达成率', '系数/状态'],
          rows, widths=[2.75, 0.95, 1.35, 1.35, 0.95, 1.0], font_size=8.9, header_size=8.6,
          rate_cols=(4,), center_cols=(1, 2, 3, 4, 5), row_height=0.50)
add_rect(s, 9.35, 2.62, 3.43, 3.95, LIGHT, line=RGBColor(0xD4, 0xE2, 0xF4), radius=True)
add_text(s, 9.55, 2.78, 3.0, 0.25, '关键卡点', size=10.5, color=BLUE, bold=True)
add_text(s, 9.55, 3.12, 3.0, 3.2,
         ['• 区域团队 14.09/300，4.7%；底线 240 也未到。',
          '• 课题组 40/400，Y1=0.8。',
          '• 团队 60% 达成 0/5，Z1=0.5。',
          '• 责任书权重合计 110%，需 HR 确认。'],
         size=8.8, color=NAVY, line_spacing=1.32)
add_footer(s, 7, source_note('区域团队实际按订单销售员归属；课题组按成交客户名代理。'))

# ============ P8 actions ============
s = prs.slides.add_slide(blank)
slide_title(s, '缺口与 11-12 月动作', '按人、按维度拆缺口；owner 唯一、时间到 12/31', 8)
actions = [
    ['刘新元', '6,650 / 3,828.70 / 57.6%', '2,821.30', '大客户1,439；拓展826；科研556', '大客户周跟踪；科研二单冲刺', '刘新元 / 12-31'],
    ['刘欣', '3,400 / 2,573.71 / 75.7%', '826.29', 'KA 46/85；NGS待补', 'TOP5客户 + KA补缺 + NGS清单', '刘欣 / 12-31'],
    ['赵云浩', '1,350 / 532.67 / 39.5%', '817.33', '年度销售X=39.5%', '万孚/圣湘/明日达回补', '赵云浩 / 12-31'],
    ['刘子研', '1,300 / 675.81 / 52.0%', '624.19', 'Y1丢失5；Z 75.3%；E 1/2', '天隆/佰奥/海尔施回补；艾德Q4', '刘子研 / 12-31'],
    ['吴云', '300 / 28.50 / 9.5%', '271.50', '课题组42/320；团队60% 0/4', '课题组周跟踪；团队60%补位', '吴云 / 12-31'],
    ['韩远怀', '300 / 14.09 / 4.7%', '285.91', '课题组40/400；团队60% 0/5', '课题组周跟踪；周会补动作', '韩远怀 / 12-31'],
]
add_table(s, 0.55, 1.55, 12.25, 3.75,
          ['姓名', '年度目标/实际/达成', '缺口', '关键缺口维度', '11-12月动作', 'Owner/时间'],
          actions, widths=[0.95, 2.1, 1.05, 2.7, 3.4, 1.7], font_size=8.6, header_size=8.3,
          center_cols=(0, 2, 5), row_height=0.54)
add_rect(s, 0.55, 5.55, 12.25, 1.18, LIGHT, line=RGBColor(0xD4, 0xE2, 0xF4), radius=True)
add_text(s, 0.75, 5.68, 11.8, 0.25, '结论', size=10.5, color=BLUE, bold=True)
add_text(s, 0.75, 5.98, 11.8, 0.65,
         ['• 六人中仅刘欣团队 75.7%；其余五人年度销售达成率低于 60%，Q4 需要按缺口倒排。',
          '• 诊断原料缺口集中在刘新元所辖大客户与科研；生命科学两个区域团队均不足 10%，课题组与团队 60% 是共同短板。'],
         size=8.8, color=NAVY, line_spacing=1.25)
add_footer(s, 8, source_note('缺口=年度目标-1-10月实际；动作 owner 为建议责任人，需在周会确认。'))

prs.save(OUT)
print('saved', OUT)
print('slides', len(prs.slides))
