#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Generate DataPointer White Paper PPT with screenshots.""" import 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 io import BytesIO from PIL import Image # Config SCREENSHOT_DIR = '/Users/nathan/Work/DataPointer/prop-data-guard/screenshots' OUTPUT_PATH = '/Users/nathan/Work/DataPointer/DataPointer产品介绍白皮书.pptx' SLIDE_WIDTH = Inches(13.333) SLIDE_HEIGHT = Inches(7.5) # Color theme COLOR_PRIMARY = RGBColor(0x1A, 0x56, 0xDB) # Deep blue COLOR_SECONDARY = RGBColor(0x10, 0xB9, 0x81) # Green COLOR_DARK = RGBColor(0x1E, 0x29, 0x3B) # Dark slate COLOR_TEXT = RGBColor(0x37, 0x41, 0x51) # Gray text COLOR_LIGHT = RGBColor(0xF8, 0xFA, 0xFC) # Light bg COLOR_ACCENT = RGBColor(0xF5, 0x9E, 0x0B) # Orange accent def add_title_slide(prs, title, subtitle): slide = prs.slides.add_slide(prs.slide_layouts[6]) # blank slide.shapes._spTree.remove(slide.shapes._spTree[0]) if len(slide.shapes._spTree) > 0 else None # Background shape bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SLIDE_WIDTH, SLIDE_HEIGHT) bg.fill.solid() bg.fill.fore_color.rgb = COLOR_DARK bg.line.fill.background() # Accent bar bar = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0.8), Inches(3.0), Inches(0.15), Inches(2.2)) bar.fill.solid() bar.fill.fore_color.rgb = COLOR_PRIMARY bar.line.fill.background() # Title title_box = slide.shapes.add_textbox(Inches(1.2), Inches(2.8), Inches(10), Inches(1.5)) tf = title_box.text_frame tf.word_wrap = True p = tf.paragraphs[0] p.text = title p.font.size = Pt(48) p.font.bold = True p.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF) # Subtitle sub_box = slide.shapes.add_textbox(Inches(1.2), Inches(4.4), Inches(10), Inches(0.8)) tf = sub_box.text_frame p = tf.paragraphs[0] p.text = subtitle p.font.size = Pt(22) p.font.color.rgb = RGBColor(0xA0, 0xAE, 0xC0) # Bottom info info_box = slide.shapes.add_textbox(Inches(1.2), Inches(6.5), Inches(10), Inches(0.5)) tf = info_box.text_frame p = tf.paragraphs[0] p.text = "Property Insurance Data Classification Platform | 2026" p.font.size = Pt(12) p.font.color.rgb = RGBColor(0x71, 0x80, 0x96) return slide def add_section_slide(prs, section_num, section_title): slide = prs.slides.add_slide(prs.slide_layouts[6]) bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SLIDE_WIDTH, SLIDE_HEIGHT) bg.fill.solid() bg.fill.fore_color.rgb = COLOR_PRIMARY bg.line.fill.background() num_box = slide.shapes.add_textbox(Inches(0.8), Inches(2.5), Inches(2), Inches(1.5)) tf = num_box.text_frame p = tf.paragraphs[0] p.text = f"0{section_num}" if section_num < 10 else str(section_num) p.font.size = Pt(72) p.font.bold = True p.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF) p.font.opacity = 0.3 title_box = slide.shapes.add_textbox(Inches(0.8), Inches(4.0), Inches(10), Inches(1.0)) tf = title_box.text_frame p = tf.paragraphs[0] p.text = section_title p.font.size = Pt(40) p.font.bold = True p.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF) return slide def add_content_slide(prs, title, bullets, image_path=None): slide = prs.slides.add_slide(prs.slide_layouts[6]) # Light background bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SLIDE_WIDTH, SLIDE_HEIGHT) bg.fill.solid() bg.fill.fore_color.rgb = COLOR_LIGHT bg.line.fill.background() # Send to back spTree = slide.shapes._spTree spTree.insert(2, bg._element) # Top bar top_bar = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SLIDE_WIDTH, Inches(0.12)) top_bar.fill.solid() top_bar.fill.fore_color.rgb = COLOR_PRIMARY top_bar.line.fill.background() # Title title_box = slide.shapes.add_textbox(Inches(0.6), Inches(0.35), Inches(12), Inches(0.6)) tf = title_box.text_frame p = tf.paragraphs[0] p.text = title p.font.size = Pt(28) p.font.bold = True p.font.color.rgb = COLOR_DARK # Content if image_path and os.path.exists(image_path): # Image on right, text on left content_box = slide.shapes.add_textbox(Inches(0.6), Inches(1.2), Inches(4.5), Inches(5.5)) tf = content_box.text_frame tf.word_wrap = True for i, bullet in enumerate(bullets): if i == 0: p = tf.paragraphs[0] else: p = tf.add_paragraph() p.text = f"• {bullet}" p.font.size = Pt(16) p.font.color.rgb = COLOR_TEXT p.space_after = Pt(12) # Add image img = Image.open(image_path) orig_w, orig_h = img.size max_w = Inches(7.0) max_h = Inches(5.5) ratio = min(max_w / orig_w, max_h / orig_h) img_w = orig_w * ratio img_h = orig_h * ratio left = SLIDE_WIDTH - img_w - Inches(0.6) top = Inches(1.0) + (max_h - img_h) / 2 slide.shapes.add_picture(image_path, left, top, width=img_w, height=img_h) else: content_box = slide.shapes.add_textbox(Inches(0.6), Inches(1.2), Inches(12), Inches(5.5)) tf = content_box.text_frame tf.word_wrap = True for i, bullet in enumerate(bullets): if i == 0: p = tf.paragraphs[0] else: p = tf.add_paragraph() p.text = f"• {bullet}" p.font.size = Pt(18) p.font.color.rgb = COLOR_TEXT p.space_after = Pt(14) return slide def add_image_slide(prs, title, image_path, caption=""): slide = prs.slides.add_slide(prs.slide_layouts[6]) bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SLIDE_WIDTH, SLIDE_HEIGHT) bg.fill.solid() bg.fill.fore_color.rgb = RGBColor(0xFF, 0xFF, 0xFF) bg.line.fill.background() spTree = slide.shapes._spTree spTree.insert(2, bg._element) top_bar = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SLIDE_WIDTH, Inches(0.12)) top_bar.fill.solid() top_bar.fill.fore_color.rgb = COLOR_PRIMARY top_bar.line.fill.background() title_box = slide.shapes.add_textbox(Inches(0.6), Inches(0.35), Inches(12), Inches(0.5)) tf = title_box.text_frame p = tf.paragraphs[0] p.text = title p.font.size = Pt(26) p.font.bold = True p.font.color.rgb = COLOR_DARK if os.path.exists(image_path): img = Image.open(image_path) orig_w, orig_h = img.size max_w = Inches(11.5) max_h = Inches(5.8) ratio = min(max_w / orig_w, max_h / orig_h) img_w = orig_w * ratio img_h = orig_h * ratio left = (SLIDE_WIDTH - img_w) / 2 top = Inches(1.0) slide.shapes.add_picture(image_path, left, top, width=img_w, height=img_h) if caption: cap_box = slide.shapes.add_textbox(Inches(0.6), Inches(6.9), Inches(12), Inches(0.4)) tf = cap_box.text_frame p = tf.paragraphs[0] p.text = caption p.font.size = Pt(12) p.font.color.rgb = RGBColor(0x71, 0x80, 0x96) p.alignment = PP_ALIGN.CENTER return slide def add_summary_slide(prs): slide = prs.slides.add_slide(prs.slide_layouts[6]) bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SLIDE_WIDTH, SLIDE_HEIGHT) bg.fill.solid() bg.fill.fore_color.rgb = COLOR_DARK bg.line.fill.background() title_box = slide.shapes.add_textbox(Inches(0.8), Inches(1.5), Inches(11.5), Inches(1.0)) tf = title_box.text_frame p = tf.paragraphs[0] p.text = "DataPointer 产品优势" p.font.size = Pt(36) p.font.bold = True p.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF) items = [ ("全生命周期数据安全", "从数据采集、分类分级、风险评估到持续监控,覆盖数据安全全生命周期"), ("智能自动化引擎", "基于规则+AI的智能分类引擎,自动识别敏感数据,效率提升80%"), ("精细化权限管控", "RBAC 角色权限模型,支持数据隔离,满足多租户场景"), ("保险行业深度适配", "内置保险行业数据分类分级标准,覆盖客户、保单、理赔等核心领域"), ("可视化数据资产", "仪表盘+报表多维展示,数据资产状况一目了然"), ] y = Inches(2.8) for title, desc in items: box = slide.shapes.add_textbox(Inches(0.8), y, Inches(11.5), Inches(0.8)) tf = box.text_frame tf.word_wrap = True p = tf.paragraphs[0] p.text = f"▎ {title}" p.font.size = Pt(18) p.font.bold = True p.font.color.rgb = COLOR_SECONDARY p = tf.add_paragraph() p.text = f" {desc}" p.font.size = Pt(14) p.font.color.rgb = RGBColor(0xA0, 0xAE, 0xC0) y += Inches(0.85) return slide def add_end_slide(prs): slide = prs.slides.add_slide(prs.slide_layouts[6]) bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SLIDE_WIDTH, SLIDE_HEIGHT) bg.fill.solid() bg.fill.fore_color.rgb = COLOR_PRIMARY bg.line.fill.background() title_box = slide.shapes.add_textbox(Inches(0), Inches(2.8), SLIDE_WIDTH, Inches(1.0)) tf = title_box.text_frame p = tf.paragraphs[0] p.text = "谢谢观看" p.font.size = Pt(52) p.font.bold = True p.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF) p.alignment = PP_ALIGN.CENTER sub_box = slide.shapes.add_textbox(Inches(0), Inches(4.0), SLIDE_WIDTH, Inches(0.6)) tf = sub_box.text_frame p = tf.paragraphs[0] p.text = "DataPointer - 让数据安全触手可及" p.font.size = Pt(20) p.font.color.rgb = RGBColor(0xA0, 0xAE, 0xC0) p.alignment = PP_ALIGN.CENTER return slide def main(): prs = Presentation() prs.slide_width = SLIDE_WIDTH prs.slide_height = SLIDE_HEIGHT # 1. Cover add_title_slide(prs, "DataPointer", "数据安全分级及风险管理平台产品介绍白皮书") # 2. TOC add_content_slide(prs, "目录", [ "产品概述与定位", "系统架构与技术栈", "核心功能模块详解", "产品优势与价值", "应用场景与最佳实践" ]) # 3. Section: Overview add_section_slide(prs, 1, "产品概述") add_content_slide(prs, "DataPointer 产品定位", [ "DataPointer 是面向保险行业的数据安全分级及风险管理平台", "基于国家及行业数据分类分级标准,帮助企业实现数据资产的自动化识别与分级", "支持从数据源接入、元数据采集、智能分类、人工复核到报表输出的完整工作流", "内置保险行业专属分类标准,覆盖客户、保单、理赔、财务、渠道、监管等核心领域", "提供多维度数据资产视图,助力企业满足数据安全合规要求" ]) # 4. Section: Architecture add_section_slide(prs, 2, "系统架构") add_content_slide(prs, "技术架构", [ "前端:Vue 3.4 + Vite + TypeScript + Element Plus 2.7 + ECharts 5.4", "后端:FastAPI 0.111 + SQLAlchemy 2.0 + PostgreSQL 16 + Redis 7 + Celery 5.4", "部署:容器化部署,支持 Docker Compose / Kubernetes", "安全:JWT 认证 + RBAC 权限控制 + 数据隔离,满足等保要求", "扩展:模块化设计,支持自定义分类标准与规则引擎扩展" ]) # 5. Section: Features add_section_slide(prs, 3, "核心功能") add_image_slide(prs, "安全登录与权限管控", os.path.join(SCREENSHOT_DIR, "01_login.png"), "支持用户名密码登录,JWT Token 认证,会话安全") add_image_slide(prs, "仪表盘总览", os.path.join(SCREENSHOT_DIR, "02_dashboard.png"), "实时展示数据源、表、字段、分级结果等核心指标,支持敏感数据分布可视化") add_content_slide(prs, "数据源管理", [ "支持多种数据库类型接入:MySQL、PostgreSQL、Oracle、SQL Server 等", "自动扫描数据库元数据,采集表结构与字段信息", "支持数据源连接测试与状态监控", "批量导入导出数据源配置,提升运维效率" ], os.path.join(SCREENSHOT_DIR, "03_datasource.png")) add_content_slide(prs, "数据资产管理", [ "自动生成数据资产目录,展示数据库、表、字段的层级关系", "支持字段级元数据查看,包括数据类型、注释、采样数据等", "资产变更追踪,记录元数据的增删改历史", "与分类分级结果联动,直观展示各资产的安全等级" ], os.path.join(SCREENSHOT_DIR, "04_metadata.png")) add_content_slide(prs, "分类分级标准", [ "内置保险行业五级分类标准:公开级(L1)、内部级(L2)、敏感级(L3)、重要级(L4)、核心级(L5)", "支持自定义分类标准与分级规则,灵活适配不同业务场景", "规则引擎支持正则匹配、关键字匹配、语义识别等多种检测方式", "标准模板管理,支持一键导入导出行业标准模板" ], os.path.join(SCREENSHOT_DIR, "05_category.png")) add_content_slide(prs, "项目管理", [ "支持创建多个分类分级项目,独立管理与进度跟踪", "项目维度统计:自动识别、人工标注、已复核数量", "支持批量触发自动分类任务,提升处理效率", "项目经理可查看负责项目的全部进度与成果" ], os.path.join(SCREENSHOT_DIR, "06_project.png")) add_content_slide(prs, "任务管理", [ "自动拆解项目为标注/复核任务,分配给标注员与审核员", "任务状态追踪:待处理、处理中、已完成、已驳回", "支持任务领取与批量处理,提升团队协作效率", "数据隔离:普通用户仅能查看和处理分配给自己的任务" ], os.path.join(SCREENSHOT_DIR, "07_task.png")) add_content_slide(prs, "分类分级结果", [ "展示所有字段的分类分级结果,支持多维筛选与搜索", "结果包含:字段名称、所属表/库、分类标准、安全等级、识别方式", "支持结果的批量导出,便于合规审计与报告生成", "人工复核机制,确保分类分级的准确性与权威性" ], os.path.join(SCREENSHOT_DIR, "08_classification.png")) add_content_slide(prs, "报表统计", [ "多维统计报表:按等级分布、类别分布、数据源分布等维度展示", "支持 Word 报告一键生成,包含完整的数据资产与分级详情", "可视化图表直观展示敏感数据分布与趋势", "满足监管报送与内部审计的数据安全报告需求" ], os.path.join(SCREENSHOT_DIR, "09_report.png")) add_content_slide(prs, "系统管理", [ "用户管理:支持用户增删改查、角色分配、部门归属", "角色权限:基于 RBAC 模型,支持 admin、project_manager、labeler、reviewer、guest 等角色", "数据隔离:严格的数据权限控制,确保跨部门数据安全", "操作日志:完整记录用户操作行为,支持安全审计" ], os.path.join(SCREENSHOT_DIR, "10_system.png")) # 6. Section: Advantages add_section_slide(prs, 4, "产品优势") add_summary_slide(prs) # 7. Section: Scenarios add_section_slide(prs, 5, "应用场景") add_content_slide(prs, "典型应用场景", [ "数据安全合规:帮助企业满足《数据安全法》《个人信息保护法》等法规要求", "等保测评:提供完整的数据分类分级证据链,支撑等级保护测评", "数据资产盘点:全面梳理企业数据资产,建立统一的数据资产目录", "敏感数据发现:自动识别客户身份证号、银行卡号、保单信息等敏感字段", "数据分级保护:根据分级结果制定差异化的数据安全保护策略" ]) # 8. End add_end_slide(prs) prs.save(OUTPUT_PATH) print(f"PPT saved to: {OUTPUT_PATH}") if __name__ == "__main__": main()