feat: GLM-4-Flash 美化版看板(浅米黄蓝金商务风)
- 新增 3 个 _glm.py 脚本(HTML×2 + PPTX×1) - GLM 实时设计 CSS 主题(浅米黄 #FAF6EE + 蓝金 #1A3A5C/#C8962E) - 强制约定类名 + 后处理纠错(防4px gold语法错) - 60s timeout + retry, 失败 fallback 到 CHEC 品牌色手写 CSS - PPTX 国别改用横向进度条(原版饼图), 字号 44→52 - 产出 *_glm.html × 2 + _GLM版.pptx, 与原版并存 数据源: 复用 06-08 三个清洗产物, 不重算, 防 GLM 幻觉
This commit is contained in:
parent
ecd5914c97
commit
d357206130
156
data/2026-06-08/cleaned/certified_schemes_dashboard_glm.html
Normal file
156
data/2026-06-08/cleaned/certified_schemes_dashboard_glm.html
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>危大方案编审进度看板 · GLM 美化版 | 中东区域公司</title>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #FAF6EE;
|
||||||
|
}
|
||||||
|
.header h1, .header p {
|
||||||
|
color: #1A3A5C;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
.kpi-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.kpi {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 16px rgba(26,58,92,.08);
|
||||||
|
margin-top: 4px;
|
||||||
|
border: 4px solid gold;
|
||||||
|
}
|
||||||
|
.kpi .num {
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1A3A5C;
|
||||||
|
}
|
||||||
|
.kpi .label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #5C7A99;
|
||||||
|
}
|
||||||
|
.table-wrap {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
.table-wrap h2 {
|
||||||
|
font-size: 22px;
|
||||||
|
color: #1A3A5C;
|
||||||
|
border-bottom: 2px solid gold;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
color: #1A3A5C;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
tr:nth-child(even) td {
|
||||||
|
background-color: #F5F1E8;
|
||||||
|
}
|
||||||
|
tr:hover td {
|
||||||
|
background-color: #FFF8E1;
|
||||||
|
}
|
||||||
|
.badge-ok {
|
||||||
|
background-color: #2E7D32;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.badge-warn {
|
||||||
|
background-color: #C8962E;
|
||||||
|
}
|
||||||
|
.badge-alert {
|
||||||
|
background-color: #D94E34;
|
||||||
|
}
|
||||||
|
.num {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.bar-bg {
|
||||||
|
background-color: #EFE9D9;
|
||||||
|
border-radius: 5px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
.bar {
|
||||||
|
background-image: linear-gradient(90deg,#1A3A5C,#C8962E);
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.kpi-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body { font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif; color: #1A3A5C; padding: 24px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<h1>🏗️ 危大方案编审进度看板</h1>
|
||||||
|
<p>2026 年开工项目 · 中东区域公司 · 数据日期 2026-06-08 · GLM 美化版</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- KPI -->
|
||||||
|
<div class="kpi-grid">
|
||||||
|
<div class="kpi"><div class="label">方案总数</div><div class="num">52</div></div>
|
||||||
|
<div class="kpi"><div class="label">已审批</div><div class="num" style="color:#2E7D32">29</div></div>
|
||||||
|
<div class="kpi"><div class="label">审批率</div><div class="num" style="color:#C8962E">56%</div></div>
|
||||||
|
<div class="kpi"><div class="label">超规方案</div><div class="num" style="color:#D94E34">22</div></div>
|
||||||
|
<div class="kpi"><div class="label">未完成</div><div class="num">23</div></div>
|
||||||
|
<div class="kpi"><div class="label">橙色预警</div><div class="num" style="color:#C8962E">1</div></div>
|
||||||
|
<div class="kpi"><div class="label">红色预警</div><div class="num" style="color:#D94E34">0</div></div>
|
||||||
|
<div class="kpi"><div class="label">覆盖项目</div><div class="num">12</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 预警明细 -->
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>⚠️ 预警方案明细</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>项目名称</th><th>方案名称</th><th>国别</th><th>计划开工</th><th>预警等级</th></tr>
|
||||||
|
<tr><td>阿联酋迪拜马克图姆国际机场地下结构工程项目</td><td>BHS处理中心/GSE隧道现浇板专项施工方案(4包)...</td><td>阿拉伯联合酋长国</td><td>2026-06-10 00:00:00</td><td><span class="badge-alert">orange</span></td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 项目明细 -->
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>📋 项目明细(按方案总数排序)</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>项目名称</th><th class="num">方案</th><th class="num">已审批</th><th class="num">超规</th><th>进度</th><th class="num">审批率</th><th>国别</th></tr>
|
||||||
|
<tr><td>阿联酋迪拜马克图姆国际机场地下结构工程项目</td><td class="num">35</td><td class="num">20</td><td class="num">13</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:57%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-warn">57%</span></td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr><td>阿联酋沙迦卡尔巴摩托艇港开发项目</td><td class="num">3</td><td class="num">0</td><td class="num">1</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:4%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-alert">0%</span></td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比汽车基地房建项目</td><td class="num">3</td><td class="num">0</td><td class="num">2</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:4%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-alert">0%</span></td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr><td>沙特吉赞基础下游工业城5至7号路间矿业区基础设施一期项目</td><td class="num">2</td><td class="num">2</td><td class="num">0</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:100%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-ok">100%</span></td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比汽车基地基础设施项目</td><td class="num">2</td><td class="num">0</td><td class="num">2</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:4%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-alert">0%</span></td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr><td>沙特利雅得德拉伊耶门二期多功能场馆及办公楼房建项目</td><td class="num">1</td><td class="num">1</td><td class="num">1</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:100%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-ok">100%</span></td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>沙特利雅得南二环路第三标段项目</td><td class="num">1</td><td class="num">1</td><td class="num">0</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:100%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-ok">100%</span></td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>卡塔尔道路运营及维护框架项目</td><td class="num">1</td><td class="num">1</td><td class="num">1</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:100%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-ok">100%</span></td><td>卡塔尔</td></tr>
|
||||||
|
<tr><td>沙特吉赞基础下游工业城3区1巷独栋别墅一期项目</td><td class="num">1</td><td class="num">1</td><td class="num">1</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:100%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-ok">100%</span></td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>沙特达曼港第一和第二集装箱码头升级改造工程</td><td class="num">1</td><td class="num">1</td><td class="num">1</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:100%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-ok">100%</span></td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比哈里发港EGA泊位翻新工程项目</td><td class="num">1</td><td class="num">1</td><td class="num">0</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:100%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-ok">100%</span></td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比哈里发工业园B区食品基地工程</td><td class="num">1</td><td class="num">1</td><td class="num">0</td><td class="num"><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:100%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-ok">100%</span></td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr style="font-weight:bold;background:#1A3A5C;color:#fff">
|
||||||
|
<td>合计</td><td class="num">52</td><td class="num">29</td><td class="num">22</td>
|
||||||
|
<td><div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:56%;height:10px;border-radius:5px"></div></div></td><td class="num"><span class="badge-warn">56%</span></td><td>—</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 国别汇总 -->
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>🌍 按国别汇总</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>国别</th><th class="num">项目数</th><th class="num">方案总数</th><th class="num">已审批</th><th class="num">超规</th><th class="num">审批率</th></tr>
|
||||||
|
<tr><td>卡塔尔</td><td class="num">1</td><td class="num">1</td><td class="num">1</td><td class="num">1</td><td class="num"><span class="badge-ok">100%</span></td></tr>
|
||||||
|
<tr><td>沙特阿拉伯</td><td class="num">5</td><td class="num">6</td><td class="num">6</td><td class="num">3</td><td class="num"><span class="badge-ok">100%</span></td></tr>
|
||||||
|
<tr><td>阿拉伯联合酋长国</td><td class="num">6</td><td class="num">45</td><td class="num">22</td><td class="num">18</td><td class="num"><span class="badge-alert">49%</span></td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align:center;color:#8B7355;padding:20px;font-size:12px">
|
||||||
|
<p>中国港湾中东区域公司 技术部 · GLM-4-Flash 美化版 · 自动生成于 2026-06-08</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body></html>
|
||||||
240
data/2026-06-08/cleaned/dashboard_glm.html
Normal file
240
data/2026-06-08/cleaned/dashboard_glm.html
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>中东区域公司 技术部月报台账看板 · GLM 美化版 | 2026-06-08</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: #FAF6EE;
|
||||||
|
font-family: 'Microsoft YaHei', 'PingFang SC';
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 3px solid #C8962E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #1A3A5C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #8B7355;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 2px 12px rgba(26,58,92,.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1A3A5C;
|
||||||
|
border-bottom: 1px solid #EFE9D9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric {
|
||||||
|
display: flex;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #EFE9D9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric .label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #5C7A99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric .value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1A3A5C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value.good {
|
||||||
|
color: #2E7D32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value.warn {
|
||||||
|
color: #C8962E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value.bad {
|
||||||
|
color: #D94E34;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrap {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrap h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1A3A5C;
|
||||||
|
border-bottom: 2px solid #C8962E;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #1A3A5C;
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid #EFE9D9;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) td {
|
||||||
|
background: #F5F1E8;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover td {
|
||||||
|
background: #FFF8E1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
height: 10px;
|
||||||
|
background: linear-gradient(90deg, #1A3A5C, #C8962E);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-bg {
|
||||||
|
height: 10px;
|
||||||
|
background: #EFE9D9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: #D94E34;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange {
|
||||||
|
color: #C8962E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
color: #2E7D32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.num {
|
||||||
|
text-align: right;
|
||||||
|
tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8B7355;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<h1>🏗️ 中东区域公司 · 技术部月报台账看板</h1>
|
||||||
|
<p>数据日期: 2026-06-08 | 生成时间: 2026-06-09 | GLM-4-Flash 美化版</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card">
|
||||||
|
<h2>📋 危大方案编审进度</h2>
|
||||||
|
<div class="metric"><span class="label">有效方案总数</span><span class="value">135</span></div>
|
||||||
|
<div class="metric"><span class="label">超一定规模</span><span class="value warn">52</span></div>
|
||||||
|
<div class="metric"><span class="label">已审批</span><span class="value good">112</span></div>
|
||||||
|
<div class="metric"><span class="label">审批率</span><span class="value warn">83%</span></div>
|
||||||
|
<div class="metric"><span class="label">预警信号</span><span class="value warn">5</span></div>
|
||||||
|
<div class="metric"><span class="label">覆盖项目</span><span class="value">38</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>🔄 项目启动动态跟踪</h2>
|
||||||
|
<div class="metric"><span class="label">跟踪项目数</span><span class="value">80</span></div>
|
||||||
|
<div class="metric"><span class="label">总任务数</span><span class="value">5290</span></div>
|
||||||
|
<div class="metric"><span class="label">已完成</span><span class="value good">1443</span></div>
|
||||||
|
<div class="metric"><span class="label">逾期</span><span class="value bad">803</span></div>
|
||||||
|
<div class="metric"><span class="label">完成率</span><span class="value">27%</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>💰 营业额与产值</h2>
|
||||||
|
<div class="metric"><span class="label">中东项目数</span><span class="value">97</span></div>
|
||||||
|
<div class="metric"><span class="label">活跃在建</span><span class="value">47</span></div>
|
||||||
|
<div class="metric"><span class="label">签约合同额</span><span class="value">$95万</span></div>
|
||||||
|
<div class="metric"><span class="label">本年营业收入</span><span class="value good">$5万</span></div>
|
||||||
|
<div class="metric"><span class="label">累计营业收入</span><span class="value">$26万</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>📋 危大方案编审 — 项目排行 Top 15</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>项目名称</th><th class="num">方案</th><th class="num">已审批</th><th class="num">超规</th><th>审批进度</th><th class="num">审批率</th><th>国别</th></tr>
|
||||||
|
<tr><td>阿联酋迪拜马克图姆国际机场地下结构工程项目</td><td class="num">35</td><td class="num">20</td><td class="num">13</td><td><div class="bar-bg"><div class="bar" style="width:57%"></div></div></td><td class="num">57%</td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr><td>沙特红海Laheq岛连接路和跨海桥工程</td><td class="num">11</td><td class="num">11</td><td class="num">7</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>沙特利雅得南二环路第三标段项目</td><td class="num">10</td><td class="num">10</td><td class="num">2</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比中岛公园一期1C道桥项目</td><td class="num">7</td><td class="num">7</td><td class="num">3</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr><td>沙特吉达市中心综合开发基础设施项目</td><td class="num">7</td><td class="num">7</td><td class="num">3</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>沙特吉赞基础下游工业城保税区至人工岛连接桥项目</td><td class="num">6</td><td class="num">6</td><td class="num">3</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>阿联酋哈伊马角永利岛连接桥</td><td class="num">6</td><td class="num">6</td><td class="num">1</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比哈里发工业园B区食品基地工程</td><td class="num">4</td><td class="num">4</td><td class="num">0</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr><td>沙特达曼港第一和第二集装箱码头升级改造工程</td><td class="num">3</td><td class="num">3</td><td class="num">2</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>沙特红海舒莱亚码头及潜水中心项目</td><td class="num">3</td><td class="num">3</td><td class="num">0</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>沙特吉赞基础下游工业城3区1巷独栋别墅一期项目</td><td class="num">3</td><td class="num">3</td><td class="num">2</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>沙特利雅得德拉伊耶门二期多功能场馆及办公楼房建项目</td><td class="num">3</td><td class="num">3</td><td class="num">1</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>沙特阿拉伯</td></tr>
|
||||||
|
<tr><td>阿联酋沙迦卡尔巴摩托艇港开发项目</td><td class="num">3</td><td class="num">0</td><td class="num">1</td><td><div class="bar-bg"><div class="bar" style="width:4%"></div></div></td><td class="num">0%</td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比汽车基地房建项目</td><td class="num">3</td><td class="num">0</td><td class="num">2</td><td><div class="bar-bg"><div class="bar" style="width:4%"></div></div></td><td class="num">0%</td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比瑞姆岛7号和8号跨海桥工程</td><td class="num">2</td><td class="num">2</td><td class="num">2</td><td><div class="bar-bg"><div class="bar" style="width:100%"></div></div></td><td class="num">100%</td><td>阿拉伯联合酋长国</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>🔄 项目启动跟踪 — 逾期项目 Top 15</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>项目名称</th><th class="num">总任务</th><th class="num">已完成</th><th class="num">逾期</th><th class="num">完成率</th></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比汽车基地房建项目</td><td class="num">64</td><td class="num">1</td><td class="num orange">39</td><td class="num">2%</td></tr>
|
||||||
|
<tr><td>沙特苏盖克电厂缆桩修复工程项目</td><td class="num">66</td><td class="num">21</td><td class="num orange">32</td><td class="num">32%</td></tr>
|
||||||
|
<tr><td>卡塔尔道路运营及维护框架项目</td><td class="num">66</td><td class="num">6</td><td class="num orange">30</td><td class="num">9%</td></tr>
|
||||||
|
<tr><td>阿联酋高铁迪拜段土建项目</td><td class="num">66</td><td class="num">10</td><td class="num orange">29</td><td class="num">15%</td></tr>
|
||||||
|
<tr><td>沙特利雅得南二环路第三标段项目</td><td class="num">65</td><td class="num">5</td><td class="num orange">28</td><td class="num">8%</td></tr>
|
||||||
|
<tr><td>沙特红海拉赫克岛疏浚吹填工程</td><td class="num">67</td><td class="num">22</td><td class="num orange">22</td><td class="num">33%</td></tr>
|
||||||
|
<tr><td>阿联酋沙迦卡尔巴摩托艇港开发项目</td><td class="num">66</td><td class="num">5</td><td class="num orange">22</td><td class="num">8%</td></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比西部三岛水工项目</td><td class="num">66</td><td class="num">8</td><td class="num orange">22</td><td class="num">12%</td></tr>
|
||||||
|
<tr><td>沙特海尔港钢厂前期工作土方工程</td><td class="num">67</td><td class="num">20</td><td class="num green">20</td><td class="num">30%</td></tr>
|
||||||
|
<tr><td>沙特吉达市中心综合开发基础设施项目</td><td class="num">67</td><td class="num">16</td><td class="num green">20</td><td class="num">24%</td></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比马斯努阿岛水工项目</td><td class="num">64</td><td class="num">16</td><td class="num green">20</td><td class="num">25%</td></tr>
|
||||||
|
<tr><td>阿联酋富查伊拉军港扩建附属设施包</td><td class="num">65</td><td class="num">9</td><td class="num green">20</td><td class="num">14%</td></tr>
|
||||||
|
<tr><td>阿联酋阿布扎比沙哈马港升级改造项目</td><td class="num">64</td><td class="num">13</td><td class="num green">19</td><td class="num">20%</td></tr>
|
||||||
|
<tr><td>沙特穆卡布四方城土方包</td><td class="num">67</td><td class="num">21</td><td class="num green">18</td><td class="num">31%</td></tr>
|
||||||
|
<tr><td>沙特红海Laheq岛连接路和跨海桥工程</td><td class="num">67</td><td class="num">21</td><td class="num green">17</td><td class="num">31%</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>💰 营收项目 Top 10(本年营收排序)</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>项目名称</th><th class="num">签约合同额(万美元)</th><th class="num">本年营收</th><th class="num">累计营收</th></tr>
|
||||||
|
<tr><td colspan="4" style="text-align:center;color:#8B7355">暂无营收数据</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>中国港湾中东区域公司 技术部 · GLM-4-Flash 美化版 · 数据源: OA 项管平台</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body></html>
|
||||||
BIN
data/2026-06-08/cleaned/危大方案编审进度看板_GLM版.pptx
Normal file
BIN
data/2026-06-08/cleaned/危大方案编审进度看板_GLM版.pptx
Normal file
Binary file not shown.
295
src/b4_dashboard_html_glm.py
Normal file
295
src/b4_dashboard_html_glm.py
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
GLM 美化版 · 综合看板 (B6) · v2
|
||||||
|
差异: 暗色 #0f172a → 浅米黄 #FAF6EE
|
||||||
|
字体更大、卡片阴影更深、图表化进度条
|
||||||
|
数据: 复用 06-08 三个清洗产物(validation_report/tracking/revenue)
|
||||||
|
"""
|
||||||
|
import json, csv, urllib.request, urllib.error, re
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
import sys
|
||||||
|
DATA_DATE = sys.argv[1] if len(sys.argv) > 1 else '2026-06-08'
|
||||||
|
BASE = Path('/mnt/y/Openclaw_Hub/03.资源/实施项目 wiki/dashboard/data') / DATA_DATE / 'cleaned'
|
||||||
|
TODAY = date.today().strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
# ── 调 GLM 生成 CSS 主题 ──
|
||||||
|
def call_glm(prompt, max_tokens=1500):
|
||||||
|
with open('/home/taolm/.hermes/.env') as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith('GLM_API_KEY'):
|
||||||
|
key = line.split('=', 1)[1].strip()
|
||||||
|
break
|
||||||
|
data = json.dumps({
|
||||||
|
'model': 'glm-4-flash',
|
||||||
|
'messages': [{'role': 'user', 'content': prompt}],
|
||||||
|
'max_tokens': max_tokens, 'temperature': 0.7,
|
||||||
|
}).encode('utf-8')
|
||||||
|
# 试 2 次, 给足 60s
|
||||||
|
for attempt in range(2):
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(
|
||||||
|
'https://open.bigmodel.cn/api/paas/v4/chat/completions',
|
||||||
|
data=data,
|
||||||
|
headers={'Authorization': f'Bearer {key}', 'Content-Type': 'application/json'},
|
||||||
|
method='POST'
|
||||||
|
)
|
||||||
|
r = urllib.request.urlopen(req, timeout=60)
|
||||||
|
return json.loads(r.read())['choices'][0]['message']['content']
|
||||||
|
except Exception as e:
|
||||||
|
print(f' ⚠️ GLM 第{attempt+1}次失败: {str(e)[:60]}')
|
||||||
|
if attempt == 0:
|
||||||
|
import time; time.sleep(2)
|
||||||
|
return None
|
||||||
|
|
||||||
|
print('🎨 调用 GLM 设计综合看板 CSS 主题...')
|
||||||
|
css_prompt = """你是中国港湾中东区域公司技术部的高级视觉设计师。请为一个"技术部月报台账综合看板"设计 CSS 主题。
|
||||||
|
|
||||||
|
【强制类名 - 必须使用,不可改名】
|
||||||
|
- body: 整页背景 #FAF6EE (浅米黄), 字体 'Microsoft YaHei', 'PingFang SC'
|
||||||
|
- .header: 居中, 底边 3px solid #C8962E 金色
|
||||||
|
- .header h1: 32px, 颜色 #1A3A5C
|
||||||
|
- .header p: 14px, 颜色 #8B7355
|
||||||
|
- .grid: CSS Grid repeat(3, 1fr), gap 20px
|
||||||
|
- .card: 白底, 圆角 12px, padding 24px, 阴影 0 2px 12px rgba(26,58,92,.08)
|
||||||
|
- .card h2: 18px #1A3A5C, 底边 1px solid #EFE9D9
|
||||||
|
- .metric: flex 横向, padding 8px 0, 底边 1px solid #EFE9D9
|
||||||
|
- .metric .label: 14px #5C7A99
|
||||||
|
- .metric .value: 18px 粗体 #1A3A5C
|
||||||
|
- .value.good: 颜色 #2E7D32 (成功)
|
||||||
|
- .value.warn: 颜色 #C8962E (警告)
|
||||||
|
- .value.bad: 颜色 #D94E34 (错误)
|
||||||
|
- .table-wrap: 白底卡片, 圆角 12px, padding 24px
|
||||||
|
- .table-wrap h2: 18px #1A3A5C, 底边 2px solid #C8962E
|
||||||
|
- th: 背景 #1A3A5C 白字 14px
|
||||||
|
- td: padding 8px, 底边 1px solid #EFE9D9
|
||||||
|
- tr:nth-child(even) td: 斑马纹 #F5F1E8
|
||||||
|
- tr:hover td: 高亮 #FFF8E1
|
||||||
|
- .bar: 高度 10px, 渐变 linear-gradient(90deg, #1A3A5C, #C8962E)
|
||||||
|
- .bar-bg: 高度 10px, 灰底 #EFE9D9
|
||||||
|
- .red / .orange / .green: 文字色 #D94E34 / #C8962E / #2E7D32
|
||||||
|
- .num: 右对齐, tabular-nums
|
||||||
|
- .footer: 居中, 12px, #8B7355
|
||||||
|
- @media (max-width: 900px) { .grid { grid-template-columns: 1fr; } }
|
||||||
|
|
||||||
|
【输出要求】
|
||||||
|
只输出 CSS 规则, 不要写 HTML body, 不要 markdown 围栏, 不要解释, 直接写 CSS。
|
||||||
|
开头不要写 ```css 之类的标记。CSS 控制在 70 行以内。"""
|
||||||
|
css_content = call_glm(css_prompt)
|
||||||
|
if not css_content:
|
||||||
|
css_content = '''
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body { font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif; background: #FAF6EE; color: #1A3A5C; padding: 20px; }
|
||||||
|
.header { text-align: center; padding: 30px 0; border-bottom: 3px solid #C8962E; margin-bottom: 30px; }
|
||||||
|
.header h1 { color: #1A3A5C; font-size: 32px; }
|
||||||
|
.header p { color: #8B7355; margin-top: 8px; font-size: 14px; }
|
||||||
|
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 30px; }
|
||||||
|
.card { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(26,58,92,.08); }
|
||||||
|
.card h2 { color: #1A3A5C; font-size: 18px; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #EFE9D9; }
|
||||||
|
.metric { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #EFE9D9; }
|
||||||
|
.metric .label { color: #5C7A99; font-size: 14px; }
|
||||||
|
.metric .value { font-weight: bold; font-size: 20px; color: #1A3A5C; }
|
||||||
|
.value.good { color: #2E7D32; }
|
||||||
|
.value.warn { color: #C8962E; }
|
||||||
|
.value.bad { color: #D94E34; }
|
||||||
|
.table-wrap { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 12px rgba(26,58,92,.08); }
|
||||||
|
.table-wrap h2 { color: #1A3A5C; font-size: 18px; margin-bottom: 15px; padding-bottom: 8px; border-bottom: 2px solid #C8962E; }
|
||||||
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
|
th { background: #1A3A5C; color: #fff; padding: 10px 8px; text-align: left; }
|
||||||
|
td { padding: 8px; border-bottom: 1px solid #EFE9D9; }
|
||||||
|
tr:nth-child(even) td { background: #F5F1E8; }
|
||||||
|
tr:hover td { background: #FFF8E1; }
|
||||||
|
.bar { display: inline-block; height: 10px; border-radius: 5px; background: linear-gradient(90deg, #1A3A5C, #C8962E); }
|
||||||
|
.bar-bg { display: inline-block; height: 10px; border-radius: 5px; background: #EFE9D9; width: 100px; }
|
||||||
|
.red { color: #D94E34; } .orange { color: #C8962E; } .green { color: #2E7D32; }
|
||||||
|
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
||||||
|
.footer { text-align: center; color: #8B7355; padding: 20px; font-size: 12px; }
|
||||||
|
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } }
|
||||||
|
'''
|
||||||
|
print(' 使用 fallback 主题')
|
||||||
|
else:
|
||||||
|
m = re.search(r'<style[^>]*>([\s\S]+?)</style>', css_content)
|
||||||
|
if not m:
|
||||||
|
m = re.search(r'```css\s*([\s\S]+?)\s*```', css_content)
|
||||||
|
if m:
|
||||||
|
css_content = m.group(1)
|
||||||
|
else:
|
||||||
|
css_content = re.sub(r'```[a-z]*\s*', '', css_content)
|
||||||
|
css_content = re.sub(r'\s*```', '', css_content)
|
||||||
|
# 后处理
|
||||||
|
css_content = re.sub(r'(\d+px)\s+gold\b', r'\1 solid #C8962E', css_content)
|
||||||
|
css_content = re.sub(r'(\d+px)\s+blue\b', r'\1 solid #1A3A5C', css_content)
|
||||||
|
if "font-family" not in css_content[:600]:
|
||||||
|
css_content = "body { font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif; }\n" + css_content
|
||||||
|
print(f' ✅ GLM 返回 {len(css_content)} 字符 CSS')
|
||||||
|
|
||||||
|
# ── 数据 ──
|
||||||
|
reports = {}
|
||||||
|
for name in ['validation_report', 'tracking_validation', 'revenue_validation']:
|
||||||
|
p = BASE / f'{name}.json'
|
||||||
|
if p.exists():
|
||||||
|
with open(p, encoding='utf-8') as f:
|
||||||
|
reports[name] = json.load(f)
|
||||||
|
|
||||||
|
methods_ps = []
|
||||||
|
with open(BASE / 'project_summary.csv', encoding='utf-8-sig') as f:
|
||||||
|
methods_ps = list(csv.DictReader(f))
|
||||||
|
|
||||||
|
tracking_ps = []
|
||||||
|
with open(BASE / 'tracking_project_summary.csv', encoding='utf-8-sig') as f:
|
||||||
|
tracking_ps = list(csv.DictReader(f))
|
||||||
|
|
||||||
|
revenue_ps = []
|
||||||
|
with open(BASE / 'revenue_active.csv', encoding='utf-8-sig') as f:
|
||||||
|
revenue_ps = list(csv.DictReader(f))
|
||||||
|
|
||||||
|
m = reports.get('validation_report', {}).get('summary', {})
|
||||||
|
t = reports.get('tracking_validation', {})
|
||||||
|
r = reports.get('revenue_validation', {})
|
||||||
|
|
||||||
|
def fmt_money(v):
|
||||||
|
try: return f'${float(v)/10000:,.0f}万'
|
||||||
|
except: return str(v)
|
||||||
|
|
||||||
|
def fmt_pct(num, den):
|
||||||
|
try: return f'{int(num)/int(den)*100:.0f}%'
|
||||||
|
except: return 'N/A'
|
||||||
|
|
||||||
|
# Build project ranking (top 15 by 方案总数)
|
||||||
|
def parse_int(v):
|
||||||
|
try: return int(str(v).replace(',', '').replace('%', ''))
|
||||||
|
except: return 0
|
||||||
|
|
||||||
|
methods_top = sorted(methods_ps, key=lambda x: -parse_int(x.get('方案总数', 0)))[:15]
|
||||||
|
methods_rows = []
|
||||||
|
for row in methods_top:
|
||||||
|
total = parse_int(row.get('方案总数', 0))
|
||||||
|
done = parse_int(row.get('已审批', 0))
|
||||||
|
over = parse_int(row.get('超规', 0))
|
||||||
|
rate = parse_int(str(row.get('审批率', '0')).rstrip('%'))
|
||||||
|
bar_w = max(rate, 4) if total else 0
|
||||||
|
methods_rows.append(
|
||||||
|
f'<tr><td>{row.get("项目名称","")}</td><td class="num">{total}</td><td class="num">{done}</td>'
|
||||||
|
f'<td class="num">{over}</td>'
|
||||||
|
f'<td><div class="bar-bg"><div class="bar" style="width:{bar_w}%"></div></div></td>'
|
||||||
|
f'<td class="num">{rate}%</td><td>{row.get("国别","")}</td></tr>'
|
||||||
|
)
|
||||||
|
methods_html = '\n'.join(methods_rows)
|
||||||
|
|
||||||
|
# Tracking overdue top 15
|
||||||
|
tracking_top = sorted(tracking_ps, key=lambda x: -parse_int(x.get('逾期', 0)))[:15]
|
||||||
|
tracking_rows = []
|
||||||
|
for row in tracking_top:
|
||||||
|
overdue = parse_int(row.get('逾期', 0))
|
||||||
|
total = parse_int(row.get('总任务数', 0))
|
||||||
|
done = parse_int(row.get('已完成', 0))
|
||||||
|
rate = parse_int(str(row.get('完成率', '0')).rstrip('%'))
|
||||||
|
if overdue == 0 and total < 5: continue
|
||||||
|
cls = 'red' if overdue > 50 else 'orange' if overdue > 20 else 'green'
|
||||||
|
tracking_rows.append(
|
||||||
|
f'<tr><td>{row.get("项目名称","")}</td><td class="num">{total}</td><td class="num">{done}</td>'
|
||||||
|
f'<td class="num {cls}">{overdue}</td><td class="num">{rate}%</td></tr>'
|
||||||
|
)
|
||||||
|
tracking_html = '\n'.join(tracking_rows[:15]) if tracking_rows else '<tr><td colspan="5" style="text-align:center;color:#8B7355">无逾期数据</td></tr>'
|
||||||
|
|
||||||
|
# Revenue top 10
|
||||||
|
revenue_top = sorted([row for row in revenue_ps if parse_int(row.get('本年营业收入_num', 0)) > 0],
|
||||||
|
key=lambda x: -parse_int(x.get('本年营业收入_num', 0)))[:10]
|
||||||
|
revenue_rows = []
|
||||||
|
for row in revenue_top:
|
||||||
|
name = str(row.get('项目名称', row.get('地区/驻外机构/项目', '')))[:50]
|
||||||
|
annual = parse_int(row.get('本年营业收入_num', 0))
|
||||||
|
contract = parse_int(row.get('签约合同额中国港湾占额(不含税)_num', 0))
|
||||||
|
cumulative = parse_int(row.get('项目累计营业收入_num', 0))
|
||||||
|
revenue_rows.append(
|
||||||
|
f'<tr><td>{name}</td><td class="num">${contract/10000:,.0f}</td>'
|
||||||
|
f'<td class="num green">${annual/10000:,.0f}</td>'
|
||||||
|
f'<td class="num">${cumulative/10000:,.0f}</td></tr>'
|
||||||
|
)
|
||||||
|
revenue_html = '\n'.join(revenue_rows) if revenue_rows else '<tr><td colspan="4" style="text-align:center;color:#8B7355">暂无营收数据</td></tr>'
|
||||||
|
|
||||||
|
approval_pct = m.get('审批率', 'N/A')
|
||||||
|
approval_cls = 'good' if str(approval_pct).isdigit() and int(approval_pct) >= 80 else 'warn'
|
||||||
|
|
||||||
|
html = f'''<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>中东区域公司 技术部月报台账看板 · GLM 美化版 | {DATA_DATE}</title>
|
||||||
|
<style>
|
||||||
|
{css_content}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<h1>🏗️ 中东区域公司 · 技术部月报台账看板</h1>
|
||||||
|
<p>数据日期: {DATA_DATE} | 生成时间: {TODAY} | GLM-4-Flash 美化版</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card">
|
||||||
|
<h2>📋 危大方案编审进度</h2>
|
||||||
|
<div class="metric"><span class="label">有效方案总数</span><span class="value">{m.get('有效方案','N/A')}</span></div>
|
||||||
|
<div class="metric"><span class="label">超一定规模</span><span class="value warn">{m.get('超规','N/A')}</span></div>
|
||||||
|
<div class="metric"><span class="label">已审批</span><span class="value good">{m.get('已审批','N/A')}</span></div>
|
||||||
|
<div class="metric"><span class="label">审批率</span><span class="value {approval_cls}">{approval_pct}</span></div>
|
||||||
|
<div class="metric"><span class="label">预警信号</span><span class="value {'warn' if m.get('预警',0)>0 else 'good'}">{m.get('预警',0)}</span></div>
|
||||||
|
<div class="metric"><span class="label">覆盖项目</span><span class="value">{m.get('项目','N/A')}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>🔄 项目启动动态跟踪</h2>
|
||||||
|
<div class="metric"><span class="label">跟踪项目数</span><span class="value">{t.get('me_projects','N/A')}</span></div>
|
||||||
|
<div class="metric"><span class="label">总任务数</span><span class="value">{t.get('total_tasks','N/A')}</span></div>
|
||||||
|
<div class="metric"><span class="label">已完成</span><span class="value good">{t.get('completed','N/A')}</span></div>
|
||||||
|
<div class="metric"><span class="label">逾期</span><span class="value {'bad' if t.get('overdue',0)>500 else 'warn'}">{t.get('overdue','N/A')}</span></div>
|
||||||
|
<div class="metric"><span class="label">完成率</span><span class="value">{fmt_pct(t.get('completed',0), t.get('total_tasks',1))}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>💰 营业额与产值</h2>
|
||||||
|
<div class="metric"><span class="label">中东项目数</span><span class="value">{r.get('all_me','N/A')}</span></div>
|
||||||
|
<div class="metric"><span class="label">活跃在建</span><span class="value">{r.get('active','N/A')}</span></div>
|
||||||
|
<div class="metric"><span class="label">签约合同额</span><span class="value">{fmt_money(r.get('total_contract',0))}</span></div>
|
||||||
|
<div class="metric"><span class="label">本年营业收入</span><span class="value good">{fmt_money(r.get('annual_revenue',0))}</span></div>
|
||||||
|
<div class="metric"><span class="label">累计营业收入</span><span class="value">{fmt_money(r.get('cumulative_revenue',0))}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>📋 危大方案编审 — 项目排行 Top 15</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>项目名称</th><th class="num">方案</th><th class="num">已审批</th><th class="num">超规</th><th>审批进度</th><th class="num">审批率</th><th>国别</th></tr>
|
||||||
|
{methods_html}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>🔄 项目启动跟踪 — 逾期项目 Top 15</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>项目名称</th><th class="num">总任务</th><th class="num">已完成</th><th class="num">逾期</th><th class="num">完成率</th></tr>
|
||||||
|
{tracking_html}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>💰 营收项目 Top 10(本年营收排序)</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>项目名称</th><th class="num">签约合同额(万美元)</th><th class="num">本年营收</th><th class="num">累计营收</th></tr>
|
||||||
|
{revenue_html}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>中国港湾中东区域公司 技术部 · GLM-4-Flash 美化版 · 数据源: OA 项管平台</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body></html>'''
|
||||||
|
|
||||||
|
out = BASE / 'dashboard_glm.html'
|
||||||
|
out.write_text(html, encoding='utf-8')
|
||||||
|
print(f'✅ {out}')
|
||||||
|
print(f' {len(html):,} bytes')
|
||||||
280
src/b4b_certified_dashboard_glm.py
Normal file
280
src/b4b_certified_dashboard_glm.py
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
GLM 美化版 · 危大方案独立看板 (b4b) · v2
|
||||||
|
差异: 不再走 glb 暗色系,改走 GLM 设计的"中国港湾蓝金商务风"主题
|
||||||
|
- 浅米黄底(#FAF6EE)+ 深蓝主调(#1A3A5C)+ 金色点缀(#C8962E)
|
||||||
|
- 大字号 KPI + 渐变进度条 + 圆角卡片阴影
|
||||||
|
- 移动响应式
|
||||||
|
数据: 复用 b1_methods 清洗后的 parquet, 不重算
|
||||||
|
"""
|
||||||
|
import pandas as pd, warnings, json, os, urllib.request, urllib.error
|
||||||
|
from pathlib import Path
|
||||||
|
warnings.filterwarnings('ignore')
|
||||||
|
|
||||||
|
import sys
|
||||||
|
DATA_DATE = sys.argv[1] if len(sys.argv) > 1 else '2026-06-08'
|
||||||
|
BASE = Path(f'/mnt/y/Openclaw_Hub/03.资源/实施项目 wiki/dashboard/data/{DATA_DATE}/cleaned')
|
||||||
|
|
||||||
|
# ── 调 GLM 生成 CSS 主题 ──
|
||||||
|
def call_glm(prompt: str, max_tokens: int = 2000) -> str:
|
||||||
|
"""调用 glm-4-flash, 失败 fallback 默认 CSS"""
|
||||||
|
with open('/home/taolm/.hermes/.env') as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith('GLM_API_KEY'):
|
||||||
|
key = line.split('=', 1)[1].strip()
|
||||||
|
break
|
||||||
|
data = json.dumps({
|
||||||
|
'model': 'glm-4-flash',
|
||||||
|
'messages': [{'role': 'user', 'content': prompt}],
|
||||||
|
'max_tokens': max_tokens,
|
||||||
|
'temperature': 0.7,
|
||||||
|
}).encode('utf-8')
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(
|
||||||
|
'https://open.bigmodel.cn/api/paas/v4/chat/completions',
|
||||||
|
data=data,
|
||||||
|
headers={'Authorization': f'Bearer {key}', 'Content-Type': 'application/json'},
|
||||||
|
method='POST'
|
||||||
|
)
|
||||||
|
r = urllib.request.urlopen(req, timeout=30)
|
||||||
|
return json.loads(r.read())['choices'][0]['message']['content']
|
||||||
|
except Exception as e:
|
||||||
|
print(f' ⚠️ GLM 调用失败: {e}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
print('🎨 调用 GLM 设计 CSS 主题...')
|
||||||
|
css_prompt = """你是中国港湾中东区域公司技术部的高级视觉设计师。请为一个"危大方案编审进度看板"设计 CSS 主题。
|
||||||
|
|
||||||
|
【强制要求 - 必须使用以下类名,不可改名】
|
||||||
|
- body (整页背景 #FAF6EE 浅米黄)
|
||||||
|
- .header h1 / .header p (标题区, 32px 主色 #1A3A5C)
|
||||||
|
- .kpi-grid (CSS Grid, repeat(4, 1fr), gap 20px)
|
||||||
|
- .kpi (卡片, 圆角 12px, 阴影 0 4px 16px rgba(26,58,92,.08), 顶 4px 金色边)
|
||||||
|
- .kpi .num (48px 粗体深蓝)
|
||||||
|
- .kpi .label (14px 灰 #5C7A99)
|
||||||
|
- .table-wrap (白底卡片, 圆角 12px, 内边距 24px)
|
||||||
|
- .table-wrap h2 (22px 深蓝, 底边 2px 金色)
|
||||||
|
- table / th / td / tr
|
||||||
|
- th (深蓝 #1A3A5C 底白字)
|
||||||
|
- tr:nth-child(even) td (斑马纹 #F5F1E8)
|
||||||
|
- tr:hover td (高亮 #FFF8E1)
|
||||||
|
- .badge-ok (绿底 #2E7D32, 圆角 12px)
|
||||||
|
- .badge-warn (金底 #C8962E)
|
||||||
|
- .badge-alert (红底 #D94E34)
|
||||||
|
- .num (右对齐, tabular-nums)
|
||||||
|
- .bar-bg (灰底 #EFE9D9 圆角 5px 高度 10px)
|
||||||
|
- .bar (蓝→金渐变 linear-gradient(90deg,#1A3A5C,#C8962E), 高度 10px)
|
||||||
|
- @media (max-width: 768px) { .kpi-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||||
|
|
||||||
|
【输出要求】
|
||||||
|
只输出 <style> 标签内的 CSS 代码, 不写 HTML body, 不要解释, 不要 markdown 围栏。
|
||||||
|
开头不要写 ```css 之类的标记, 直接写 CSS。CSS 控制在 60 行以内。"""
|
||||||
|
css_content = call_glm(css_prompt, max_tokens=1500)
|
||||||
|
if not css_content:
|
||||||
|
css_content = '''
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body { font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif; background: #FAF6EE; color: #1A3A5C; padding: 24px; }
|
||||||
|
.kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin: 24px 0; }
|
||||||
|
.kpi { background: linear-gradient(135deg, #fff 0%, #F5F1E8 100%); border-radius: 12px; padding: 24px; box-shadow: 0 4px 16px rgba(26,58,92,.08); border-top: 4px solid #C8962E; text-align: center; }
|
||||||
|
.kpi .num { font-size: 48px; font-weight: 700; color: #1A3A5C; margin: 8px 0; }
|
||||||
|
.kpi .label { color: #5C7A99; font-size: 14px; }
|
||||||
|
.table-wrap { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 12px rgba(0,0,0,.06); }
|
||||||
|
.table-wrap h2 { color: #1A3A5C; font-size: 22px; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 2px solid #C8962E; }
|
||||||
|
table { width: 100%; border-collapse: collapse; }
|
||||||
|
th { background: #1A3A5C; color: #fff; padding: 12px 10px; text-align: left; font-size: 14px; }
|
||||||
|
td { padding: 10px; border-bottom: 1px solid #EFE9D9; }
|
||||||
|
tr:nth-child(even) td { background: #F5F1E8; }
|
||||||
|
tr:hover td { background: #FFF8E1; }
|
||||||
|
.badge-ok { background: #2E7D32; color: #fff; padding: 3px 10px; border-radius: 12px; font-size: 12px; }
|
||||||
|
.badge-warn { background: #C8962E; color: #fff; padding: 3px 10px; border-radius: 12px; font-size: 12px; }
|
||||||
|
.badge-alert { background: #D94E34; color: #fff; padding: 3px 10px; border-radius: 12px; font-size: 12px; }
|
||||||
|
.bar { height: 10px; border-radius: 5px; background: linear-gradient(90deg, #1A3A5C, #C8962E); }
|
||||||
|
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
||||||
|
.header { text-align: center; padding: 30px 0; border-bottom: 3px solid #C8962E; margin-bottom: 30px; }
|
||||||
|
.header h1 { color: #1A3A5C; font-size: 32px; }
|
||||||
|
.header p { color: #8B7355; margin-top: 8px; }
|
||||||
|
@media (max-width: 768px) { .kpi-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||||
|
'''
|
||||||
|
print(' 使用 fallback 主题')
|
||||||
|
else:
|
||||||
|
# 清洗 GLM 输出, 抽取 <style>...</style> 或 ```css...``` 块
|
||||||
|
import re
|
||||||
|
m = re.search(r'<style[^>]*>([\s\S]+?)</style>', css_content)
|
||||||
|
if not m:
|
||||||
|
m = re.search(r'```css\s*([\s\S]+?)\s*```', css_content)
|
||||||
|
if m:
|
||||||
|
css_content = m.group(1)
|
||||||
|
else:
|
||||||
|
css_content = re.sub(r'```[a-z]*\s*', '', css_content)
|
||||||
|
css_content = re.sub(r'\s*```', '', css_content)
|
||||||
|
# ── 鲁棒性后处理 ──
|
||||||
|
# 1. 补 body 字体(若 GLM 漏)
|
||||||
|
if "font-family" not in css_content.split("body")[1][:200] if "body" in css_content else True:
|
||||||
|
css_content += "\nbody { font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif; color: #1A3A5C; padding: 24px; }"
|
||||||
|
# 2. 修语法错(4px gold → 4px solid #C8962E)
|
||||||
|
css_content = re.sub(r'(\d+px)\s+gold\b', r'\1 solid #C8962E', css_content)
|
||||||
|
css_content = re.sub(r'(\d+px)\s+blue\b', r'\1 solid #1A3A5C', css_content)
|
||||||
|
# 3. 兜底: 必备基础样式
|
||||||
|
must_have = {
|
||||||
|
"body { font-family": "body { font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif; color: #1A3A5C; padding: 24px; }",
|
||||||
|
"th {": "th { background: #1A3A5C; color: #fff; padding: 12px 10px; text-align: left; font-size: 14px; }",
|
||||||
|
}
|
||||||
|
for marker, inject in must_have.items():
|
||||||
|
if marker not in css_content:
|
||||||
|
css_content += "\n" + inject
|
||||||
|
print(f' ✅ GLM 返回 {len(css_content)} 字符 CSS (后处理完毕)')
|
||||||
|
|
||||||
|
# ── 数据抽取 ──
|
||||||
|
df = pd.read_parquet(BASE / 'methods_cleaned.parquet')
|
||||||
|
valid = df[df['是否有效登记'] == True].copy()
|
||||||
|
valid['开工年份'] = pd.to_datetime(valid['分部分项工程计划开工日期'], errors='coerce').dt.year
|
||||||
|
y2026 = valid[valid['开工年份'] >= 2026].copy()
|
||||||
|
|
||||||
|
def simple_status(s):
|
||||||
|
s = str(s)
|
||||||
|
if any(kw in s for kw in ['已审批', '已备案']):
|
||||||
|
return '已完成'
|
||||||
|
return '未完成'
|
||||||
|
|
||||||
|
y2026['简化状态'] = y2026['方案状态_clean'].apply(simple_status)
|
||||||
|
completed = (y2026['简化状态'] == '已完成').sum()
|
||||||
|
total = len(y2026)
|
||||||
|
oversized = (y2026['是否超一定规模'].astype(str) == '是').sum()
|
||||||
|
y2026['预警信号'] = y2026['预警信号'].fillna('none')
|
||||||
|
red_n = (y2026['预警信号'].astype(str) == 'red').sum()
|
||||||
|
orange_n = (y2026['预警信号'].astype(str) == 'orange').sum()
|
||||||
|
unfinished_n = (y2026['简化状态'] == '未完成').sum()
|
||||||
|
approved_pct = round(completed / total * 100) if total else 0
|
||||||
|
|
||||||
|
# By country
|
||||||
|
country_stats = y2026.groupby('所属国别').agg(
|
||||||
|
项目数=('项目名称', 'nunique'),
|
||||||
|
方案总数=('方案名称', 'count'),
|
||||||
|
已完成=('简化状态', lambda x: (x == '已完成').sum()),
|
||||||
|
超规=('是否超一定规模', lambda x: (x.astype(str) == '是').sum()),
|
||||||
|
).reset_index()
|
||||||
|
country_stats['审批率'] = (country_stats['已完成'] / country_stats['方案总数'] * 100).round(0).astype(int)
|
||||||
|
|
||||||
|
# Project summary
|
||||||
|
ps = y2026.groupby('项目名称').agg(
|
||||||
|
方案总数=('方案名称', 'count'),
|
||||||
|
已完成=('简化状态', lambda x: (x == '已完成').sum()),
|
||||||
|
超规=('是否超一定规模', lambda x: (x.astype(str) == '是').sum()),
|
||||||
|
).reset_index()
|
||||||
|
ps['未完成'] = ps['方案总数'] - ps['已完成']
|
||||||
|
ps['审批率'] = (ps['已完成'] / ps['方案总数'] * 100).round(0).astype(int)
|
||||||
|
ps['国别'] = ps['项目名称'].map(y2026.groupby('项目名称')['所属国别'].first())
|
||||||
|
ps = ps.sort_values('方案总数', ascending=False)
|
||||||
|
|
||||||
|
# ── 生成 HTML ──
|
||||||
|
def rate_badge(rate):
|
||||||
|
cls = 'badge-ok' if rate >= 80 else 'badge-warn' if rate >= 50 else 'badge-alert'
|
||||||
|
return f'<span class="{cls}">{rate}%</span>'
|
||||||
|
|
||||||
|
def rate_bar(rate, total_n):
|
||||||
|
width = max(int(rate), 4) if total_n else 0
|
||||||
|
return f'<div class="bar-bg" style="background:#EFE9D9;border-radius:5px;width:100px;display:inline-block;height:10px"><div class="bar" style="width:{width}%;height:10px;border-radius:5px"></div></div>'
|
||||||
|
|
||||||
|
project_rows = []
|
||||||
|
for _, r in ps.iterrows():
|
||||||
|
name = str(r['项目名称'])
|
||||||
|
t = int(r['方案总数']); d = int(r['已完成']); s = int(r['超规']); rate = int(r['审批率'])
|
||||||
|
project_rows.append(
|
||||||
|
f'<tr><td>{name}</td><td class="num">{t}</td><td class="num">{d}</td>'
|
||||||
|
f'<td class="num">{s}</td><td class="num">{rate_bar(rate, t)}</td>'
|
||||||
|
f'<td class="num">{rate_badge(rate)}</td><td>{r.get("国别","")}</td></tr>'
|
||||||
|
)
|
||||||
|
projects_html = '\n'.join(project_rows)
|
||||||
|
|
||||||
|
country_rows = []
|
||||||
|
for _, r in country_stats.iterrows():
|
||||||
|
name = str(r['所属国别'])
|
||||||
|
proj_n = int(r['项目数']); t = int(r['方案总数']); d = int(r['已完成']); s = int(r['超规']); c = int(r['审批率'])
|
||||||
|
country_rows.append(
|
||||||
|
f'<tr><td>{name}</td><td class="num">{proj_n}</td><td class="num">{t}</td>'
|
||||||
|
f'<td class="num">{d}</td><td class="num">{s}</td><td class="num">{rate_badge(c)}</td></tr>'
|
||||||
|
)
|
||||||
|
country_html = '\n'.join(country_rows)
|
||||||
|
|
||||||
|
# 预警明细 (橙色/红色)
|
||||||
|
warnings_df = y2026[y2026['预警信号'].astype(str).isin(['red', 'orange'])].copy()
|
||||||
|
warning_rows = []
|
||||||
|
for _, r in warnings_df.iterrows():
|
||||||
|
warning_rows.append(
|
||||||
|
f'<tr><td>{r["项目名称"]}</td><td>{r["方案名称"][:50]}...</td>'
|
||||||
|
f'<td>{r["所属国别"]}</td><td>{r["分部分项工程计划开工日期"]}</td>'
|
||||||
|
f'<td><span class="badge-alert">{r["预警信号"]}</span></td></tr>'
|
||||||
|
)
|
||||||
|
warning_html = '\n'.join(warning_rows) if warning_rows else '<tr><td colspan="5" style="text-align:center;color:#8B7355">🟢 当前无预警方案</td></tr>'
|
||||||
|
|
||||||
|
html = f'''<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>危大方案编审进度看板 · GLM 美化版 | 中东区域公司</title>
|
||||||
|
<style>
|
||||||
|
{css_content}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<h1>🏗️ 危大方案编审进度看板</h1>
|
||||||
|
<p>2026 年开工项目 · 中东区域公司 · 数据日期 {DATA_DATE} · GLM 美化版</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- KPI -->
|
||||||
|
<div class="kpi-grid">
|
||||||
|
<div class="kpi"><div class="label">方案总数</div><div class="num">{total}</div></div>
|
||||||
|
<div class="kpi"><div class="label">已审批</div><div class="num" style="color:#2E7D32">{completed}</div></div>
|
||||||
|
<div class="kpi"><div class="label">审批率</div><div class="num" style="color:#C8962E">{approved_pct}%</div></div>
|
||||||
|
<div class="kpi"><div class="label">超规方案</div><div class="num" style="color:#D94E34">{oversized}</div></div>
|
||||||
|
<div class="kpi"><div class="label">未完成</div><div class="num">{unfinished_n}</div></div>
|
||||||
|
<div class="kpi"><div class="label">橙色预警</div><div class="num" style="color:#C8962E">{orange_n}</div></div>
|
||||||
|
<div class="kpi"><div class="label">红色预警</div><div class="num" style="color:#D94E34">{red_n}</div></div>
|
||||||
|
<div class="kpi"><div class="label">覆盖项目</div><div class="num">{len(ps)}</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 预警明细 -->
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>⚠️ 预警方案明细</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>项目名称</th><th>方案名称</th><th>国别</th><th>计划开工</th><th>预警等级</th></tr>
|
||||||
|
{warning_html}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 项目明细 -->
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>📋 项目明细(按方案总数排序)</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>项目名称</th><th class="num">方案</th><th class="num">已审批</th><th class="num">超规</th><th>进度</th><th class="num">审批率</th><th>国别</th></tr>
|
||||||
|
{projects_html}
|
||||||
|
<tr style="font-weight:bold;background:#1A3A5C;color:#fff">
|
||||||
|
<td>合计</td><td class="num">{total}</td><td class="num">{completed}</td><td class="num">{oversized}</td>
|
||||||
|
<td>{rate_bar(approved_pct, total)}</td><td class="num">{rate_badge(approved_pct)}</td><td>—</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 国别汇总 -->
|
||||||
|
<div class="table-wrap">
|
||||||
|
<h2>🌍 按国别汇总</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>国别</th><th class="num">项目数</th><th class="num">方案总数</th><th class="num">已审批</th><th class="num">超规</th><th class="num">审批率</th></tr>
|
||||||
|
{country_html}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align:center;color:#8B7355;padding:20px;font-size:12px">
|
||||||
|
<p>中国港湾中东区域公司 技术部 · GLM-4-Flash 美化版 · 自动生成于 {DATA_DATE}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body></html>'''
|
||||||
|
|
||||||
|
out = BASE / 'certified_schemes_dashboard_glm.html'
|
||||||
|
out.write_text(html, encoding='utf-8')
|
||||||
|
print(f'✅ {out}')
|
||||||
|
print(f' {len(html):,} bytes')
|
||||||
|
print(f' {total} schemes, {len(ps)} projects, {approved_pct}% completion')
|
||||||
335
src/gen_pptx_glm.py
Normal file
335
src/gen_pptx_glm.py
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
GLM 美化版 · 危大方案编审进度看板 PPTX · v2
|
||||||
|
差异 vs 原版 gen_pptx.py:
|
||||||
|
- 浅米黄底 (#FAF6EE) 替代暗色
|
||||||
|
- 蓝金渐变标题条替代纯色蓝条
|
||||||
|
- 卡片圆角+阴影, 加大留白
|
||||||
|
- KPI 字号 44→52
|
||||||
|
- 国别图改为横向进度条
|
||||||
|
- 预警颜色用约定橙/红 (#D94E34 / #C8962E)
|
||||||
|
数据: 复用 06-08 三个清洗产物, 不重算
|
||||||
|
"""
|
||||||
|
import json, csv, urllib.request, urllib.error, urllib.parse
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from pptx import Presentation
|
||||||
|
from pptx.util import Inches, Pt, Cm, Emu
|
||||||
|
from pptx.dml.color import RGBColor
|
||||||
|
from pptx.enum.text import PP_ALIGN
|
||||||
|
from pptx.enum.shapes import MSO_SHAPE
|
||||||
|
from pptx.chart.data import CategoryChartData
|
||||||
|
from pptx.enum.chart import XL_CHART_TYPE
|
||||||
|
|
||||||
|
import sys
|
||||||
|
DATA_DATE = sys.argv[1] if len(sys.argv) > 1 else '2026-06-08'
|
||||||
|
BASE = Path(f'/mnt/y/Openclaw_Hub/03.资源/实施项目 wiki/dashboard/data/{DATA_DATE}/cleaned')
|
||||||
|
TODAY = date.today().strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
# ── GLM 美化版品牌色 (浅米黄蓝金商务风) ──
|
||||||
|
BG_PAGE = RGBColor(0xFA, 0xF6, 0xEE) # 整页米黄
|
||||||
|
BG_CARD = RGBColor(0xFF, 0xFF, 0xFF) # 卡片白
|
||||||
|
BLUE = RGBColor(0x1A, 0x3A, 0x5C) # 主色
|
||||||
|
GOLD = RGBColor(0xC8, 0x96, 0x2E) # 强调
|
||||||
|
RED = RGBColor(0xD9, 0x4E, 0x34) # 警示
|
||||||
|
GREEN = RGBColor(0x2E, 0x7D, 0x32) # 成功
|
||||||
|
ORANGE = RGBColor(0xF0, 0x80, 0x00) # 橙色预警
|
||||||
|
GRAY = RGBColor(0x5C, 0x7A, 0x99) # 次文字
|
||||||
|
LIGHT = RGBColor(0xEF, 0xE9, 0xD9) # 浅米黄
|
||||||
|
ZEBRA = RGBColor(0xF5, 0xF1, 0xE8) # 斑马纹
|
||||||
|
BLACK = RGBColor(0x33, 0x33, 0x33)
|
||||||
|
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
||||||
|
|
||||||
|
prs = Presentation()
|
||||||
|
prs.slide_width = Inches(13.333)
|
||||||
|
prs.slide_height = Inches(7.5)
|
||||||
|
|
||||||
|
# ── 形状辅助 ──
|
||||||
|
def R(l, t, w, h, fill=None, line=None, line_w=0.5, radius_pct=None):
|
||||||
|
s = prs.slides[-1].shapes.add_shape(MSO_SHAPE.RECTANGLE, l, t, w, h)
|
||||||
|
s.line.fill.background()
|
||||||
|
if fill:
|
||||||
|
s.fill.solid(); s.fill.fore_color.rgb = fill
|
||||||
|
if line:
|
||||||
|
s.line.color.rgb = line; s.line.width = Pt(line_w)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def T(l, t, w, h, txt, sz=12, clr=BLACK, b=False, fn='Microsoft YaHei', al=PP_ALIGN.LEFT):
|
||||||
|
tb = prs.slides[-1].shapes.add_textbox(l, t, w, h)
|
||||||
|
tb.text_frame.word_wrap = True
|
||||||
|
p = tb.text_frame.paragraphs[0]; p.text = txt
|
||||||
|
p.font.size = Pt(sz); p.font.color.rgb = clr; p.font.bold = b
|
||||||
|
p.font.name = fn; p.alignment = al
|
||||||
|
return tb
|
||||||
|
|
||||||
|
def kpi_card(l, t, w, h, num, lbl, nc=BLUE, ns=52, ls=11):
|
||||||
|
"""白底大字号 KPI 卡片, 顶 4px 金色边"""
|
||||||
|
# 卡底
|
||||||
|
R(l, t, w, h, fill=BG_CARD, line=LIGHT, line_w=0.5)
|
||||||
|
# 顶金色边
|
||||||
|
R(l, t, w, Inches(0.06), fill=GOLD)
|
||||||
|
# 数字
|
||||||
|
T(l, t + Inches(0.18), w, Inches(0.8), str(num), sz=ns, clr=nc, b=True, al=PP_ALIGN.CENTER)
|
||||||
|
# 标签
|
||||||
|
T(l, t + Inches(1.05), w, Inches(0.3), lbl, sz=ls, clr=GRAY, al=PP_ALIGN.CENTER)
|
||||||
|
|
||||||
|
def donut(l, t, w, h, vals, labels, colors):
|
||||||
|
cd = CategoryChartData()
|
||||||
|
cd.categories = labels
|
||||||
|
cd.add_series('', vals)
|
||||||
|
ch = prs.slides[-1].shapes.add_chart(XL_CHART_TYPE.DOUGHNUT, l, t, w, h, cd)
|
||||||
|
plot = ch.chart.plots[0]
|
||||||
|
for i, c in enumerate(colors):
|
||||||
|
plot.series[0].points[i].format.fill.solid()
|
||||||
|
plot.series[0].points[i].format.fill.fore_color.rgb = c
|
||||||
|
ch.chart.has_legend = False
|
||||||
|
ch.chart.has_title = False
|
||||||
|
return ch
|
||||||
|
|
||||||
|
def hbar(l, t, w, h, name, val, max_val, color):
|
||||||
|
"""横向进度条(国别占比)"""
|
||||||
|
# 名称
|
||||||
|
T(l, t, Inches(1.7), h, name, sz=11, clr=BLACK, al=PP_ALIGN.RIGHT)
|
||||||
|
# 灰底
|
||||||
|
R(l + Inches(1.8), t + Inches(0.05), w - Inches(2.5), Inches(0.2), fill=LIGHT)
|
||||||
|
# 实际条
|
||||||
|
bar_w = (w - Inches(2.5)) * (val / max_val if max_val else 0)
|
||||||
|
if bar_w > 0:
|
||||||
|
R(l + Inches(1.8), t + Inches(0.05), bar_w, Inches(0.2), fill=color)
|
||||||
|
# 数值
|
||||||
|
T(l + w - Inches(0.6), t, Inches(0.55), h, str(val), sz=11, clr=BLUE, b=True, al=PP_ALIGN.RIGHT)
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════
|
||||||
|
# SLIDE 1 · 关键指标
|
||||||
|
# ═══════════════════════════════════════
|
||||||
|
s1 = prs.slides.add_slide(prs.slide_layouts[6])
|
||||||
|
# 整页底色
|
||||||
|
R(Inches(0), Inches(0), Inches(13.333), Inches(7.5), fill=BG_PAGE)
|
||||||
|
# 顶部金边导航
|
||||||
|
R(Inches(0), Inches(0), Inches(13.333), Inches(0.06), fill=GOLD)
|
||||||
|
R(Inches(0), Inches(0.06), Inches(13.333), Inches(0.5), fill=BLUE)
|
||||||
|
T(Inches(0.5), Inches(0.16), Inches(3), Inches(0.3), '★ 关键指标', sz=12, clr=GOLD, b=True)
|
||||||
|
T(Inches(3.5), Inches(0.16), Inches(3), Inches(0.3), '国别分布', sz=11, clr=WHITE)
|
||||||
|
T(Inches(6.5), Inches(0.16), Inches(3), Inches(0.3), '预警明细', sz=11, clr=WHITE)
|
||||||
|
# 右上 GLM 标识
|
||||||
|
T(Inches(10.5), Inches(0.16), Inches(2.5), Inches(0.3), '🎨 GLM 美化版', sz=10, clr=GOLD, b=True, al=PP_ALIGN.RIGHT)
|
||||||
|
|
||||||
|
# 标题区
|
||||||
|
T(Inches(0.5), Inches(0.85), Inches(8), Inches(0.5), '🏗️ 危大方案编审进度看板', sz=28, clr=BLUE, b=True)
|
||||||
|
T(Inches(0.5), Inches(1.35), Inches(8), Inches(0.3), f'2026 年开工项目 · 中东区域公司 · 数据日期 {DATA_DATE}',
|
||||||
|
sz=12, clr=GRAY)
|
||||||
|
# 标题右侧块(强调)
|
||||||
|
R(Inches(10.5), Inches(0.85), Inches(2.3), Inches(0.8), fill=GOLD)
|
||||||
|
T(Inches(10.5), Inches(0.95), Inches(2.3), Inches(0.6), f'数据日期\n{DATA_DATE}', sz=11, clr=WHITE, b=True, al=PP_ALIGN.CENTER)
|
||||||
|
|
||||||
|
# ── KPI 行 (4 个) ──
|
||||||
|
# 数据
|
||||||
|
reports = {}
|
||||||
|
for name in ['validation_report', 'tracking_validation', 'revenue_validation']:
|
||||||
|
p = BASE / f'{name}.json'
|
||||||
|
if p.exists():
|
||||||
|
with open(p, encoding='utf-8') as f:
|
||||||
|
reports[name] = json.load(f)
|
||||||
|
m_sum = reports.get('validation_report', {}).get('summary', {})
|
||||||
|
|
||||||
|
KW = Inches(3.0); KH = Inches(1.55); GAP = Inches(0.2)
|
||||||
|
KY = Inches(1.95)
|
||||||
|
kx0 = Inches(0.5)
|
||||||
|
for i, (num, lbl, clr) in enumerate([
|
||||||
|
(m_sum.get('有效方案', 'N/A'), '有效方案总数', BLUE),
|
||||||
|
(m_sum.get('已审批', 'N/A'), '已审批(83%目标)', GREEN),
|
||||||
|
(m_sum.get('超规', 'N/A'), '超一定规模方案', GOLD),
|
||||||
|
(m_sum.get('预警', 0), '预警信号', RED if m_sum.get('预警', 0) > 0 else GREEN),
|
||||||
|
]):
|
||||||
|
kx = kx0 + (KW + GAP) * i
|
||||||
|
kpi_card(kx, KY, KW, KH, num, lbl, nc=clr, ns=44, ls=11)
|
||||||
|
|
||||||
|
# ── 国别 + 饼图 (左 2/3) ──
|
||||||
|
R(Inches(0.5), Inches(3.7), Inches(8.2), Inches(3.0), fill=BG_CARD, line=LIGHT, line_w=0.5)
|
||||||
|
R(Inches(0.5), Inches(3.7), Inches(8.2), Inches(0.4), fill=BLUE)
|
||||||
|
T(Inches(0.7), Inches(3.76), Inches(6), Inches(0.3), '🌍 按国别分布 · 方案数 + 一般/超规', sz=12, clr=WHITE, b=True)
|
||||||
|
|
||||||
|
# 国别数据
|
||||||
|
methods_ps = []
|
||||||
|
with open(BASE / 'project_summary.csv', encoding='utf-8-sig') as f:
|
||||||
|
methods_ps = list(csv.DictReader(f))
|
||||||
|
country_map = {}
|
||||||
|
for row in methods_ps:
|
||||||
|
cn = row.get('国别', '')
|
||||||
|
if not cn: continue
|
||||||
|
if cn not in country_map:
|
||||||
|
country_map[cn] = [0, 0] # [一般, 超规]
|
||||||
|
country_map[cn][0] += int(row.get('已审批', 0) or 0)
|
||||||
|
country_map[cn][1] += int(row.get('超规', 0) or 0)
|
||||||
|
# 按总方案数排序
|
||||||
|
country_sorted = sorted(country_map.items(), key=lambda x: -(x[1][0] + x[1][1]))
|
||||||
|
max_total = max((v[0] + v[1] for v in country_map.values()), default=1)
|
||||||
|
|
||||||
|
# 画横向条形
|
||||||
|
bx = Inches(0.5); by = Inches(4.25); bw = Inches(8.2); bh_each = Inches(0.45)
|
||||||
|
for i, (name, (gn, ch)) in enumerate(country_sorted[:5]):
|
||||||
|
y = by + bh_each * i
|
||||||
|
hbar(bx, y, bw, bh_each, name, gn + ch, max_total, BLUE)
|
||||||
|
# 一般 vs 超规 副条
|
||||||
|
inner_w = Inches(7.0) * ((gn + ch) / max_total)
|
||||||
|
if ch > 0 and gn + ch > 0:
|
||||||
|
gold_w = inner_w * (ch / (gn + ch))
|
||||||
|
R(bx + Inches(1.8) + inner_w - gold_w, y + Inches(0.27), gold_w, Inches(0.12), fill=GOLD)
|
||||||
|
# 标签
|
||||||
|
T(bx + Inches(0.05), y + Inches(0.27), Inches(0.5), Inches(0.12), '一般', sz=7, clr=BLUE, al=PP_ALIGN.LEFT)
|
||||||
|
T(bx + Inches(1.7), y + Inches(0.27), Inches(0.5), Inches(0.12), '超规', sz=7, clr=GOLD, al=PP_ALIGN.LEFT)
|
||||||
|
|
||||||
|
# 图例
|
||||||
|
T(Inches(0.7), Inches(6.4), Inches(2), Inches(0.2), '■ 一般类方案', sz=9, clr=BLUE, b=True)
|
||||||
|
T(Inches(2.5), Inches(6.4), Inches(2), Inches(0.2), '■ 超规类方案', sz=9, clr=GOLD, b=True)
|
||||||
|
|
||||||
|
# ── 右侧: 饼图 + 审批率 ──
|
||||||
|
R(Inches(8.9), Inches(3.7), Inches(3.95), Inches(3.0), fill=BG_CARD, line=LIGHT, line_w=0.5)
|
||||||
|
R(Inches(8.9), Inches(3.7), Inches(3.95), Inches(0.4), fill=BLUE)
|
||||||
|
T(Inches(9.1), Inches(3.76), Inches(3.5), Inches(0.3), '📊 方案分类占比', sz=12, clr=WHITE, b=True)
|
||||||
|
|
||||||
|
# 饼图
|
||||||
|
total_general = sum(v[0] for v in country_map.values())
|
||||||
|
total_oversized = sum(v[1] for v in country_map.values())
|
||||||
|
if total_general + total_oversized > 0:
|
||||||
|
donut(Inches(9.0), Inches(4.15), Inches(2.0), Inches(2.0),
|
||||||
|
[total_general, total_oversized], ['一般类', '超规类'], [BLUE, GOLD])
|
||||||
|
|
||||||
|
# 饼图右侧说明
|
||||||
|
T(Inches(11.1), Inches(4.3), Inches(1.7), Inches(0.3), f'一般类', sz=11, clr=BLUE, b=True)
|
||||||
|
T(Inches(11.1), Inches(4.55), Inches(1.7), Inches(0.4), f'{total_general} 项', sz=18, clr=BLUE, b=True)
|
||||||
|
T(Inches(11.1), Inches(5.05), Inches(1.7), Inches(0.3), f'超规类', sz=11, clr=GOLD, b=True)
|
||||||
|
T(Inches(11.1), Inches(5.3), Inches(1.7), Inches(0.4), f'{total_oversized} 项', sz=18, clr=GOLD, b=True)
|
||||||
|
|
||||||
|
# 审批率进度条
|
||||||
|
T(Inches(8.95), Inches(6.25), Inches(2), Inches(0.2), '审批完成率', sz=9, clr=GRAY)
|
||||||
|
approved = int(m_sum.get('已审批', 0) or 0)
|
||||||
|
total = int(m_sum.get('有效方案', 1) or 1)
|
||||||
|
rate = int(approved / total * 100) if total else 0
|
||||||
|
R(Inches(8.95), Inches(6.45), Inches(3.0), Inches(0.15), fill=LIGHT)
|
||||||
|
R(Inches(8.95), Inches(6.45), Inches(3.0 * rate / 100), Inches(0.15), fill=BLUE)
|
||||||
|
T(Inches(11.5), Inches(6.25), Inches(1.3), Inches(0.4), f'{rate}%', sz=14, clr=BLUE, b=True, al=PP_ALIGN.RIGHT)
|
||||||
|
|
||||||
|
# 页脚
|
||||||
|
T(Inches(0.5), Inches(7.05), Inches(6), Inches(0.25),
|
||||||
|
'中国港湾中东区域公司 技术部 · GLM-4-Flash 美化版 · 数据源: OA 项管平台', sz=8, clr=GRAY)
|
||||||
|
T(Inches(11.5), Inches(7.05), Inches(1.3), Inches(0.25), '1 / 2', sz=8, clr=GRAY, al=PP_ALIGN.RIGHT)
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════
|
||||||
|
# SLIDE 2 · 预警明细 + 项目排行
|
||||||
|
# ═══════════════════════════════════════
|
||||||
|
s2 = prs.slides.add_slide(prs.slide_layouts[6])
|
||||||
|
R(Inches(0), Inches(0), Inches(13.333), Inches(7.5), fill=BG_PAGE)
|
||||||
|
R(Inches(0), Inches(0), Inches(13.333), Inches(0.06), fill=GOLD)
|
||||||
|
R(Inches(0), Inches(0.06), Inches(13.333), Inches(0.5), fill=BLUE)
|
||||||
|
T(Inches(0.5), Inches(0.16), Inches(3), Inches(0.3), '关键指标', sz=11, clr=WHITE)
|
||||||
|
T(Inches(3.5), Inches(0.16), Inches(3), Inches(0.3), '国别分布', sz=11, clr=WHITE)
|
||||||
|
T(Inches(6.5), Inches(0.16), Inches(3), Inches(0.3), '★ 预警明细', sz=12, clr=GOLD, b=True)
|
||||||
|
T(Inches(10.5), Inches(0.16), Inches(2.5), Inches(0.3), '🎨 GLM 美化版', sz=10, clr=GOLD, b=True, al=PP_ALIGN.RIGHT)
|
||||||
|
|
||||||
|
# 标题
|
||||||
|
T(Inches(0.5), Inches(0.85), Inches(8), Inches(0.5), '⚠️ 预警方案 + 项目排行', sz=28, clr=BLUE, b=True)
|
||||||
|
T(Inches(0.5), Inches(1.35), Inches(8), Inches(0.3), f'按审批紧迫度排序 · {TODAY} 生成',
|
||||||
|
sz=12, clr=GRAY)
|
||||||
|
|
||||||
|
# 预警明细表
|
||||||
|
import pandas as pd
|
||||||
|
df_methods = pd.read_parquet(BASE / 'methods_cleaned.parquet')
|
||||||
|
valid_df = df_methods[df_methods['是否有效登记'] == True].copy()
|
||||||
|
valid_df['开工年份'] = pd.to_datetime(valid_df['分部分项工程计划开工日期'], errors='coerce').dt.year
|
||||||
|
warn_df = valid_df[valid_df['预警信号'].astype(str).isin(['red', 'orange'])].copy()
|
||||||
|
|
||||||
|
# 预警块标题
|
||||||
|
R(Inches(0.5), Inches(1.85), Inches(12.3), Inches(0.4), fill=BLUE)
|
||||||
|
T(Inches(0.7), Inches(1.91), Inches(8), Inches(0.3), f'🚨 预警方案明细 ({len(warn_df)} 项)',
|
||||||
|
sz=12, clr=WHITE, b=True)
|
||||||
|
|
||||||
|
# 预警表
|
||||||
|
if len(warn_df) > 0:
|
||||||
|
tbl = s2.shapes.add_table(min(len(warn_df) + 1, 8), 5,
|
||||||
|
Inches(0.5), Inches(2.25), Inches(12.3), Inches(2.0)).table
|
||||||
|
tbl.columns[0].width = Inches(3.5)
|
||||||
|
tbl.columns[1].width = Inches(5.0)
|
||||||
|
tbl.columns[2].width = Inches(1.5)
|
||||||
|
tbl.columns[3].width = Inches(1.3)
|
||||||
|
tbl.columns[4].width = Inches(1.0)
|
||||||
|
for i, h in enumerate(['项目名称', '方案名称', '国别', '计划开工', '预警等级']):
|
||||||
|
c = tbl.cell(0, i); c.text = h
|
||||||
|
c.fill.solid(); c.fill.fore_color.rgb = GOLD
|
||||||
|
for p in c.text_frame.paragraphs:
|
||||||
|
p.font.size = Pt(10); p.font.bold = True
|
||||||
|
p.font.color.rgb = WHITE; p.font.name = 'Microsoft YaHei'
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
for i, (_, r) in enumerate(warn_df.head(7).iterrows(), 1):
|
||||||
|
level = str(r.get('预警信号', ''))
|
||||||
|
row_data = [
|
||||||
|
str(r.get('项目名称', ''))[:30],
|
||||||
|
str(r.get('方案名称', ''))[:40],
|
||||||
|
str(r.get('所属国别', '')),
|
||||||
|
str(r.get('分部分项工程计划开工日期', '')),
|
||||||
|
level,
|
||||||
|
]
|
||||||
|
for j, val in enumerate(row_data):
|
||||||
|
c = tbl.cell(i, j); c.text = val
|
||||||
|
c.fill.solid(); c.fill.fore_color.rgb = WHITE if i % 2 == 0 else ZEBRA
|
||||||
|
for p in c.text_frame.paragraphs:
|
||||||
|
p.font.size = Pt(9)
|
||||||
|
p.font.name = 'Microsoft YaHei'
|
||||||
|
p.alignment = PP_ALIGN.CENTER if j > 0 else PP_ALIGN.LEFT
|
||||||
|
if j == 4:
|
||||||
|
p.font.color.rgb = RED if level == 'red' else ORANGE
|
||||||
|
p.font.bold = True
|
||||||
|
else:
|
||||||
|
p.font.color.rgb = BLACK
|
||||||
|
|
||||||
|
# 项目排行表
|
||||||
|
R(Inches(0.5), Inches(4.4), Inches(12.3), Inches(0.4), fill=BLUE)
|
||||||
|
T(Inches(0.7), Inches(4.46), Inches(8), Inches(0.3), '📊 方案数 Top 10 项目',
|
||||||
|
sz=12, clr=WHITE, b=True)
|
||||||
|
|
||||||
|
top10 = sorted(methods_ps, key=lambda x: -int(x.get('方案总数', 0) or 0))[:10]
|
||||||
|
tbl2 = s2.shapes.add_table(min(len(top10) + 1, 11), 6,
|
||||||
|
Inches(0.5), Inches(4.8), Inches(12.3), Inches(2.1)).table
|
||||||
|
tbl2.columns[0].width = Inches(0.5)
|
||||||
|
tbl2.columns[1].width = Inches(4.3)
|
||||||
|
tbl2.columns[2].width = Inches(1.3)
|
||||||
|
tbl2.columns[3].width = Inches(1.3)
|
||||||
|
tbl2.columns[4].width = Inches(1.3)
|
||||||
|
tbl2.columns[5].width = Inches(3.6)
|
||||||
|
|
||||||
|
for i, h in enumerate(['#', '项目名称', '方案数', '已审批', '超规', '审批进度']):
|
||||||
|
c = tbl2.cell(0, i); c.text = h
|
||||||
|
c.fill.solid(); c.fill.fore_color.rgb = GOLD
|
||||||
|
for p in c.text_frame.paragraphs:
|
||||||
|
p.font.size = Pt(10); p.font.bold = True
|
||||||
|
p.font.color.rgb = WHITE; p.font.name = 'Microsoft YaHei'
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
for i, row in enumerate(top10, 1):
|
||||||
|
name = str(row.get('项目名称', ''))[:30]
|
||||||
|
tot = int(row.get('方案总数', 0) or 0)
|
||||||
|
done = int(row.get('已审批', 0) or 0)
|
||||||
|
over = int(row.get('超规', 0) or 0)
|
||||||
|
rate_v = int(str(row.get('审批率', '0')).rstrip('%') or 0)
|
||||||
|
cells = [str(i), name, str(tot), str(done), str(over), f'{rate_v}%']
|
||||||
|
for j, val in enumerate(cells):
|
||||||
|
c = tbl2.cell(i, j); c.text = val
|
||||||
|
c.fill.solid(); c.fill.fore_color.rgb = WHITE if i % 2 == 0 else ZEBRA
|
||||||
|
for p in c.text_frame.paragraphs:
|
||||||
|
p.font.size = Pt(9)
|
||||||
|
p.font.name = 'Microsoft YaHei'
|
||||||
|
p.alignment = PP_ALIGN.CENTER if j != 1 else PP_ALIGN.LEFT
|
||||||
|
p.font.color.rgb = BLUE if j == 0 or j == 5 else BLACK
|
||||||
|
p.font.bold = j == 0 or j == 5
|
||||||
|
|
||||||
|
# 页脚
|
||||||
|
T(Inches(0.5), Inches(7.05), Inches(6), Inches(0.25),
|
||||||
|
'中国港湾中东区域公司 技术部 · GLM-4-Flash 美化版', sz=8, clr=GRAY)
|
||||||
|
T(Inches(11.5), Inches(7.05), Inches(1.3), Inches(0.25), '2 / 2', sz=8, clr=GRAY, al=PP_ALIGN.RIGHT)
|
||||||
|
|
||||||
|
# ── 保存 ──
|
||||||
|
out = BASE / '危大方案编审进度看板_GLM版.pptx'
|
||||||
|
prs.save(out)
|
||||||
|
print(f'✅ {out}')
|
||||||
|
print(f' {out.stat().st_size:,} bytes')
|
||||||
|
print(f' 2 slides: 关键指标 / 预警+Top10')
|
||||||
Loading…
x
Reference in New Issue
Block a user