Skill: wwbwin/basic-data-analysis (version 1.0.1)

--- name: 保险Excel自动数据分析工具 description: | 保险Excel自动数据分析工具，是基于保险从业人员常用的excel数据字段进行筛选和分析的AI自动数据分析skills。 触发词：数据分析、分析数据、生成图表、制作图表、数据报告、EDA、探索性分析、可视化分析、统计报告。 当用户提供数据文件并要求分析、生成图表、制作报告时，自动触发本 Skill。 metadata: version: "1.0.1" author: "WuWenBin-BeiJing-ST" license: "MIT" --- # 保险Excel自动数据分析工具 V1.0.1 > **作者**: WuWenBin-BeiJing-ST > > 2026 年，保险行业的数据分析涉及到数据清洗和数据整理，这些工作应该交给 Skills 自动完成。本 Skill 专为保险从业人员设计，针对保险Excel数据字段进行智能分析，从原始数据到专业报告，一键搞定。 ## 前言 保险行业的数据分析涉及到数据清洗和数据整理，2026 年，这些基础工作应该交给 Skills。本 Skill 专为保险从业人员设计，能够自动识别保险常用字段（如保单号、险种、保费、理赔金额、客户信息等），自动制作图表（柱状图、饼状图、带数据标记的折线图、条形图），数据分析能力具备： 1. **标准数据分析流程**（EDA → 清洗 → 分析 → 可视化 → 洞察 → 输出）执行，同时处理常见文件格式（CSV、Excel、JSON） 2. Agent 在 Skill 里自动调用 Kimi/DeepSeek 生成自然语言洞察部分 3. 具备导出 Word 报告的能力 ## 能力概览 本 Skill 提供端到端数据分析能力，涵盖从原始数据到专业报告的完整流程： 1. **数据加载** — 支持 CSV、Excel (.xlsx/.xls)、JSON 格式，自动识别编码 2. **EDA 探索** — 数据概览、缺失值分析、类型推断、描述性统计 3. **数据清洗** — 删除空行/重复行、缺失值填充、异常值标记 4. **统计分析** — 数值列统计、分类列频次、相关性分析 5. **可视化** — 自动生成柱状图、饼状图、折线图、条形图（PNG 格式） 6. **AI 洞察** — 调用 Kimi/DeepSeek 生成自然语言业务洞察 7. **报告导出** — 一键生成专业 Word 分析报告 ## 快速开始 ```bash # Step 1: 执行数据分析（EDA + 清洗 + 统计 + 图表） python3 scripts/analyze.py <数据文件路径> --output-dir ./output # Step 2: 生成 Word 报告（洞察文本由 Agent 调用 Kimi/DeepSeek 生成） python3 scripts/export_report.py ./output/summary.json "<AI洞察文本>" --output ./报告.docx ``` ## 标准工作流 ``` 用户上传数据文件 ↓ [1] 加载数据（CSV/Excel/JSON） ↓ [2] EDA 探索性分析 ↓ [3] 数据清洗（自动 + 日志记录） ↓ [4] 统计分析（数值 + 分类） ↓ [5] 可视化（自动生成四类图表） ↓ [6] AI 洞察生成（调用 Kimi/DeepSeek） ↓ [7] 导出 Word 报告 ↓ 返回报告文件给用户 ``` 详细流程说明见 [references/workflow.md](references/workflow.md)。 ## 脚本说明 ### `scripts/analyze.py` **功能**: 数据分析核心脚本，执行 EDA、清洗、统计、图表生成。 **用法**: ```bash python3 scripts/analyze.py <数据文件> [--output-dir <输出目录>] ``` **输出**: - `summary.json` — 完整分析结果（EDA、清洗日志、统计、图表路径） - `charts/*.png` — 自动生成的可视化图表 **依赖**: pandas, matplotlib, numpy, openpyxl ### `scripts/export_report.py` **功能**: 生成 Word 格式数据分析报告。 **用法**: ```bash python3 scripts/export_report.py <summary.json路径> "<洞察文本>" [--output <报告路径>] ``` **输出**: 专业排版的 Word 文档，包含封面、数据概览、清洗记录、统计分析、图表、AI 洞察、结论建议。 **依赖**: python-docx ## AI 洞察生成 本 Skill 需 Agent 调用外部大模型（Kimi 或 DeepSeek）生成自然语言洞察。 详细调用方式、Prompt 模板、环境变量配置见 [references/insight_generation.md](references/insight_generation.md)。 **关键配置**: ```bash # Kimi API Key export KIMI_API_KEY="your-kimi-api-key" # DeepSeek API Key export DEEPSEEK_API_KEY="your-deepseek-api-key" ``` ## 图表类型 | 图表类型 | 适用场景 | 自动生成条件 | |---------|---------|-------------| | 柱状图 | 数值分布、分类频次 | 数值列最多 3，分类列最多 2 | | 饼状图 | 分类占比 | 分类列唯一值 ≥ 2，最多 2 列 | | 折线图 | 数值趋势（带数据标记） | 数值列数据点 ≥ 2，最多 3 列 | | 条形图 | 分类排名（水平） | 分类列唯一值 ≥ 2，最多 2 列 | 所有图表自动适配中文字体（PingFang SC / SimHei / Microsoft YaHei 等）。 ## 支持的数据格式 | 格式 | 扩展名 | 编码处理 | |------|--------|---------| | CSV | .csv | 自动尝试 utf-8 / utf-8-sig / gbk | | Excel | .xlsx, .xls | 自动读取，支持多 Sheet（默认第一个） | | JSON | .json | 支持数组格式、对象数组、键值对映射 | ## Agent 执行指南 当用户触发本 Skill 时，按以下步骤执行： 1. **确认数据文件路径** — 用户上传或指定路径 2. **执行 analyze.py** — 生成 `summary.json` 和图表 3. **读取 summary.json** — 提取关键信息构建 Prompt 4. **调用 Kimi/DeepSeek** — 生成自然语言洞察 5. **执行 export_report.py** — 生成 Word 报告 6. **返回报告文件** — 提供下载路径或直接发送给用户 **错误处理**: - 缺少依赖 → 提示用户运行 `pip3 install pandas matplotlib numpy openpyxl python-docx` - 缺少 API Key → 提示配置环境变量 - 数据格式错误 → 提示用户检查文件格式 ## 参考文档 - [工作流详解](references/workflow.md) — 标准流程、判断逻辑、常见问题 - [洞察生成指南](references/insight_generation.md) — Kimi/DeepSeek 调用方式、Prompt 模板

path: _meta.json
content: ownerId: kn7cq99t9kzkxb4g587an3gyw5850tc0
slug: basic-data-analysis
version: 1.0.0
publishedAt: 1776781098410

path: references/insight_generation.md
content: # Kimi/DeepSeek 自然语言洞察生成指南

## 调用方式

在数据分析 Skill 中，Agent 应在图表生成后调用 Kimi 或 DeepSeek API 生成自然语言洞察。

### 推荐模型

| 模型 | 适用场景 | API 端点 |
|------|---------|---------|
| Kimi (moonshot-v1-8k) | 中文洞察、业务解读 | https://api.moonshot.cn/v1/chat/completions |
| DeepSeek (deepseek-chat) | 中文洞察、深度分析 | https://api.deepseek.com/v1/chat/completions |

### Prompt 模板

```
你是一位资深数据分析师。请根据以下数据分析结果，生成简洁、专业的自然语言洞察报告。

【数据概览】
- 行数: {shape[0]}
- 列数: {shape[1]}
- 数值列: {numeric_columns}
- 分类列: {categorical_columns}

【描述性统计】
{numeric_stats_json}

【分类列 Top5】
{categorical_stats_json}

【数据清洗记录】
{clean_log}

【图表描述】
{charts_description}

请输出：
1. 数据质量评估（缺失值、异常值、重复值）
2. 核心发现（3-5 条关键洞察）
3. 分布特征（数值列分布、分类列占比）
4. 异常点识别（离群值、极端值）
5. 业务建议（基于数据的行动建议）

要求：语言简洁专业，避免过度技术化，突出业务价值。
```

### 调用示例（Python）

```python
import os
import json
import requests

def generate_insight(summary: dict, model: str = "kimi") -> str:
    """调用 Kimi/DeepSeek 生成洞察"""
    [REDACTED_SECRET]"KIMI_API_KEY") if model == "kimi" else os.environ.get("DEEPSEEK_API_KEY")
    endpoint = "https://api.moonshot.cn/v1/chat/completions" if model == "kimi" else "https://api.deepseek.com/v1/chat/completions"
    
    prompt = build_prompt(summary)  # 使用上述模板
    
    resp = requests.post(
        endpoint,
        headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
        json={
            "model": "moonshot-v1-8k" if model == "kimi" else "deepseek-chat",
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.7,
        },
        timeout=60,
    )
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]
```

### 环境变量配置

Agent 应提示用户配置 API Key：

```bash
# Kimi
export KIMI_API_KEY="your-kimi-api-key"

# DeepSeek
export DEEPSEEK_API_KEY="your-deepseek-api-key"
```

或在 `~/.zshrc` / `~/.bashrc` 中永久配置。

## 洞察输出格式

生成的洞察应为 Markdown 格式，包含：

```markdown
### 一、数据质量评估
...

### 二、核心发现
1. ...
2. ...

### 三、分布特征
...

### 四、异常点识别
...

### 五、业务建议
...
```

此内容将直接插入 Word 报告的「AI 数据洞察」章节。


path: references/workflow.md
content: # 数据分析 Skill 工作流详解

## 标准流程（EDA → 清洗 → 分析 → 可视化 → 洞察 → 输出）

### Step 1: EDA（探索性数据分析）

**目标**: 快速了解数据全貌，识别数据质量问题。

**执行**:
```bash
python3 scripts/analyze.py <数据文件> --output-dir ./output
```

**输出**: `summary.json` 包含：
- 数据形状（行数、列数）
- 列类型分布（数值列、分类列、时间列）
- 缺失值统计
- 重复行统计
- 数值列描述性统计（count/mean/std/min/25%/50%/75%/max）
- 分类列 Top5 频次

**判断逻辑**:
- 缺失率 > 30% 的列 → 提示用户是否删除
- 重复行 > 5% → 提示用户确认清洗策略
- 数值列唯一值 < 5 → 可能是分类变量，建议转换

### Step 2: 数据清洗

**自动清洗规则**:
1. 删除全空行/列
2. 删除重复行（记录日志）
3. 数值列缺失值 → 用中位数填充
4. 分类列缺失值 → 用 "未知" 填充

**清洗日志**: 记录每一步操作，写入 `summary.json` 的 `clean_log` 字段。

### Step 3: 统计分析

**数值列**:
- 描述性统计（均值、标准差、四分位数）
- 相关性矩阵（如有多列）
- 分布偏度/峰度（可选）

**分类列**:
- 唯一值数量
- Top N 频次分布
- 占比计算

### Step 4: 可视化

**自动图表生成规则**:

| 图表类型 | 适用列 | 生成条件 | 数量限制 |
|---------|--------|---------|---------|
| 柱状图 | 数值列/分类列 | 自动 | 数值列最多 3，分类列最多 2 |
| 饼状图 | 分类列 | 唯一值 ≥ 2 | 最多 2 列 |
| 折线图 | 数值列 | 数据点 ≥ 2 | 最多 3 列 |
| 条形图 | 分类列 | 唯一值 ≥ 2 | 最多 2 列 |

**图表保存**: `output/charts/` 目录，PNG 格式，150 DPI。

**中文字体**: 自动检测系统字体（PingFang SC / SimHei / Microsoft YaHei 等），确保中文正常显示。

### Step 5: AI 洞察生成

**调用时机**: 图表生成完成后。

**调用方式**: 参考 `references/insight_generation.md`，调用 Kimi 或 DeepSeek API。

**Prompt 构建**:
- 注入 `summary.json` 关键信息
- 注入图表描述（类型、列名、特征）
- 要求输出 5 个章节：数据质量评估、核心发现、分布特征、异常点识别、业务建议

**输出**: Markdown 格式洞察文本。

### Step 6: 导出 Word 报告

**执行**:
```bash
python3 scripts/export_report.py ./output/summary.json "<洞察文本>" --output ./数据分析报告.docx
```

**报告结构**:
1. 封面（标题、生成时间、数据文件名）
2. 数据概览（基本信息表、列信息表）
3. 数据清洗记录（操作日志）
4. 统计分析（数值列统计表、分类列统计表）
5. 可视化图表（嵌入 PNG 图片）
6. AI 数据洞察（Markdown 渲染）
7. 结论与建议

**Word 样式**:
- 标题：Heading 1-4
- 表格：Table Grid 样式，表头蓝色背景
- 图表：宽度 5.5 英寸，居中

## 完整示例

```bash
# 1. 分析数据
python3 scripts/analyze.py sales_2024.xlsx --output-dir ./output

# 2. 生成洞察（Agent 调用 Kimi/DeepSeek）
insight=$(python3 -c "
import os, json, requests
# ... 调用 API 生成洞察 ...
print(insight_text)
")

# 3. 导出报告
python3 scripts/export_report.py ./output/summary.json "$insight" --output ./销售数据分析报告.docx
```

## 常见问题

### Q: 图表中文乱码？
A: 脚本自动检测中文字体，如仍乱码，检查系统是否安装 PingFang SC / SimHei / Microsoft YaHei。

### Q: 数值列被识别为字符串？
A: 检查数据源格式，CSV 可能因编码问题导致类型识别错误。脚本会尝试 utf-8 / utf-8-sig / gbk 三种编码。

### Q: 洞察生成失败？
A: 检查 API Key 配置（KIMI_API_KEY / DEEPSEEK_API_KEY 环境变量），确认网络可访问 API 端点。

### Q: Word 报告格式错乱？
A: 确保 python-docx 版本 >= 0.8.11，旧版本样式支持不完整。


path: scripts/analyze.py
content: #!/usr/bin/env python3
"""
数据分析核心脚本
支持 CSV / Excel / JSON 输入
输出：图表（PNG）+ 统计摘要（JSON）
用法：python3 analyze.py <数据文件路径> [--output-dir <输出目录>]
"""

import sys
import os
import json
import argparse
import warnings
warnings.filterwarnings("ignore")

# ── 依赖检查 ──────────────────────────────────────────────────────────────────
REQUIRED = ["pandas", "matplotlib", "numpy", "openpyxl"]
missing = []
for pkg in REQUIRED:
    try:
        __import__(pkg)
    except ImportError:
        missing.append(pkg)
if missing:
    print(f"[ERROR] 缺少依赖，请先运行: pip3 install {' '.join(missing)}")
    sys.exit(1)

import pandas as pd
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

# ── 中文字体配置 ──────────────────────────────────────────────────────────────
def setup_chinese_font():
    """尝试加载系统中文字体，失败则使用英文回退"""
    candidates = [
        "PingFang SC", "Heiti SC", "STHeiti", "SimHei",
        "Microsoft YaHei", "WenQuanYi Micro Hei", "Noto Sans CJK SC"
    ]
    available = {f.name for f in fm.fontManager.ttflist}
    for font in candidates:
        if font in available:
            plt.rcParams["font.family"] = font
            plt.rcParams["axes.unicode_minus"] = False
            return font
    plt.rcParams["axes.unicode_minus"] = False
    return None

FONT = setup_chinese_font()

# ── 数据加载 ──────────────────────────────────────────────────────────────────
def load_data(path: str) -> pd.DataFrame:
    ext = os.path.splitext(path)[1].lower()
    if ext == ".csv":
        for enc in ["utf-8", "utf-8-sig", "gbk", "gb2312"]:
            try:
                return pd.read_csv(path, encoding=enc)
            except UnicodeDecodeError:
                continue
        raise ValueError("CSV 编码无法识别")
    elif ext in (".xlsx", ".xls"):
        return pd.read_excel(path)
    elif ext == ".json":
        with open(path, "r", encoding="utf-8") as f:
            raw = json.load(f)
        if isinstance(raw, list):
            return pd.DataFrame(raw)
        elif isinstance(raw, dict):
            return pd.DataFrame([raw]) if not any(isinstance(v, list) for v in raw.values()) else pd.DataFrame(raw)
        raise ValueError("JSON 格式不支持")
    else:
        raise ValueError(f"不支持的文件格式: {ext}")

# ── EDA ───────────────────────────────────────────────────────────────────────
def eda(df: pd.DataFrame) -> dict:
    numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
    cat_cols = df.select_dtypes(include=["object", "category"]).columns.tolist()
    datetime_cols = df.select_dtypes(include=["datetime"]).columns.tolist()

    summary = {
        "shape": list(df.shape),
        "columns": df.columns.tolist(),
        "dtypes": {c: str(t) for c, t in df.dtypes.items()},
        "numeric_columns": numeric_cols,
        "categorical_columns": cat_cols,
        "datetime_columns": datetime_cols,
        "missing_values": df.isnull().sum().to_dict(),
        "missing_pct": (df.isnull().mean() * 100).round(2).to_dict(),
        "duplicates": int(df.duplicated().sum()),
    }

    if numeric_cols:
        desc = df[numeric_cols].describe().round(4)
        summary["numeric_stats"] = desc.to_dict()

    if cat_cols:
        summary["categorical_stats"] = {
            c: {"unique": int(df[c].nunique()), "top5": df[c].value_counts().head(5).to_dict()}
            for c in cat_cols
        }

    return summary

# ── 数据清洗 ──────────────────────────────────────────────────────────────────
def clean_data(df: pd.DataFrame) -> tuple[pd.DataFrame, list]:
    log = []
    original_shape = df.shape

    # 删除全空行/列
    df = df.dropna(how="all")
    df = df.dropna(axis=1, how="all")

    # 删除重复行
    dup_count = df.duplicated().sum()
    if dup_count > 0:
        df = df.drop_duplicates()
        log.append(f"删除重复行: {dup_count} 行")

    # 数值列填充缺失值（中位数）
    numeric_cols = df.select_dtypes(include=[np.number]).columns
    for col in numeric_cols:
        n_missing = df[col].isnull().sum()
        if n_missing > 0:
            median_val = df[col].median()
            df[col] = df[col].fillna(median_val)
            log.append(f"列 '{col}' 缺失值 {n_missing} 个，用中位数 {median_val:.4g} 填充")

    # 字符串列填充缺失值
    cat_cols = df.select_dtypes(include=["object"]).columns
    for col in cat_cols:
        n_missing = df[col].isnull().sum()
        if n_missing > 0:
            df[col] = df[col].fillna("未知")
            log.append(f"列 '{col}' 缺失值 {n_missing} 个，用 '未知' 填充")

    log.append(f"清洗完成: {original_shape} → {df.shape}")
    return df, log

# ── 图表生成 ──────────────────────────────────────────────────────────────────
COLORS = ["#4C72B0", "#DD8452", "#55A868", "#C44E52", "#8172B2",
          "#937860", "#DA8BC3", "#8C8C8C", "#CCB974", "#64B5CD"]

def _save(fig, path):
    fig.tight_layout()
    fig.savefig(path, dpi=150, bbox_inches="tight")
    plt.close(fig)
    return path

def plot_bar(df, col, output_dir, top_n=15):
    """柱状图：数值列分布 or 分类列频次"""
    path = os.path.join(output_dir, f"bar_{col}.png")
    fig, ax = plt.subplots(figsize=(10, 5))

    if df[col].dtype in [np.float64, np.int64, float, int]:
        # 数值列 → 直方图风格柱状图
        counts, bins = np.histogram(df[col].dropna(), bins=min(20, df[col].nunique()))
        ax.bar(bins[:-1], counts, width=np.diff(bins), color=COLORS[0], edgecolor="white", align="edge")
        ax.set_xlabel(col)
        ax.set_ylabel("频次")
        ax.set_title(f"{col} 分布（柱状图）")
        for rect in ax.patches:
            h = rect.get_height()
            if h > 0:
                ax.text(rect.get_x() + rect.get_width() / 2, h + 0.3, str(int(h)),
                        ha="center", va="bottom", fontsize=8)
    else:
        vc = df[col].value_counts().head(top_n)
        bars = ax.bar(range(len(vc)), vc.values, color=COLORS[:len(vc)], edgecolor="white")
        ax.set_xticks(range(len(vc)))
        ax.set_xticklabels(vc.index, rotation=30, ha="right")
        ax.set_ylabel("频次")
        ax.set_title(f"{col} Top{top_n} 频次（柱状图）")
        for bar, val in zip(bars, vc.values):
            ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.3,
                    str(val), ha="center", va="bottom", fontsize=9)

    return _save(fig, path)

def plot_pie(df, col, output_dir, top_n=8):
    """饼状图：分类列占比"""
    path = os.path.join(output_dir, f"pie_{col}.png")
    vc = df[col].value_counts().head(top_n)
    if len(vc) < 2:
        return None

    fig, ax = plt.subplots(figsize=(8, 8))
    wedges, texts, autotexts = ax.pie(
        vc.values, labels=vc.index, autopct="%1.1f%%",
        colors=COLORS[:len(vc)], startangle=140,
        wedgeprops={"edgecolor": "white", "linewidth": 1.5}
    )
    for at in autotexts:
        at.set_fontsize(9)
    ax.set_title(f"{col} 占比（饼状图）")
    return _save(fig, path)

def plot_line(df, col, output_dir):
    """带数据标记的折线图：数值列按索引趋势"""
    path = os.path.join(output_dir, f"line_{col}.png")
    series = df[col].dropna().reset_index(drop=True)
    if len(series) < 2:
        return None

    # 采样（超过200点时）
    if len(series) > 200:
        step = len(series) // 100
        series = series.iloc[::step]

    fig, ax = plt.subplots(figsize=(12, 5))
    ax.plot(series.index, series.values, color=COLORS[0], linewidth=1.8,
            marker="o", markersize=4, markerfacecolor=COLORS[1])

    # 标注最大/最小值
    idx_max = series.idxmax()
    idx_min = series.idxmin()
    ax.annotate(f"最大: {series[idx_max]:.4g}", xy=(idx_max, series[idx_max]),
                xytext=(idx_max, series[idx_max] * 1.02),
                fontsize=8, color="red", ha="center")
    ax.annotate(f"最小: {series[idx_min]:.4g}", xy=(idx_min, series[idx_min]),
                xytext=(idx_min, series[idx_min] * 0.98),
                fontsize=8, color="blue", ha="center")

    ax.set_xlabel("索引")
    ax.set_ylabel(col)
    ax.set_title(f"{col} 趋势（带标记折线图）")
    ax.grid(True, alpha=0.3)
    return _save(fig, path)

def plot_barh(df, col, output_dir, top_n=15):
    """条形图（水平）：分类列频次排名"""
    path = os.path.join(output_dir, f"barh_{col}.png")
    vc = df[col].value_counts().head(top_n).sort_values()
    if len(vc) < 2:
        return None

    fig, ax = plt.subplots(figsize=(10, max(4, len(vc) * 0.45)))
    bars = ax.barh(range(len(vc)), vc.values, color=COLORS[:len(vc)], edgecolor="white")
    ax.set_yticks(range(len(vc)))
    ax.set_yticklabels(vc.index)
    ax.set_xlabel("频次")
    ax.set_title(f"{col} Top{top_n} 排名（条形图）")
    for bar, val in zip(bars, vc.values):
        ax.text(bar.get_width() + 0.1, bar.get_y() + bar.get_height() / 2,
                str(val), va="center", fontsize=9)
    return _save(fig, path)

def generate_charts(df: pd.DataFrame, output_dir: str) -> list:
    """自动为合适的列生成四类图表"""
    os.makedirs(output_dir, exist_ok=True)
    charts = []
    numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
    cat_cols = df.select_dtypes(include=["object", "category"]).columns.tolist()

    # 柱状图：数值列（最多3列）+ 分类列（最多2列）
    for col in numeric_cols[:3]:
        p = plot_bar(df, col, output_dir)
        if p:
            charts.append({"type": "bar", "column": col, "path": p})

    for col in cat_cols[:2]:
        p = plot_bar(df, col, output_dir)
        if p:
            charts.append({"type": "bar", "column": col, "path": p})

    # 饼状图：分类列（最多2列）
    for col in cat_cols[:2]:
        p = plot_pie(df, col, output_dir)
        if p:
            charts.append({"type": "pie", "column": col, "path": p})

    # 折线图：数值列（最多3列）
    for col in numeric_cols[:3]:
        p = plot_line(df, col, output_dir)
        if p:
            charts.append({"type": "line", "column": col, "path": p})

    # 条形图：分类列（最多2列）
    for col in cat_cols[:2]:
        p = plot_barh(df, col, output_dir)
        if p:
            charts.append({"type": "barh", "column": col, "path": p})

    return charts

# ── 主流程 ────────────────────────────────────────────────────────────────────
def main():
    parser = argparse.ArgumentParser(description="数据分析脚本")
    parser.add_argument("file", help="数据文件路径（CSV/Excel/JSON）")
    parser.add_argument("--output-dir", default="./analysis_output", help="输出目录")
    args = parser.parse_args()

    os.makedirs(args.output_dir, exist_ok=True)
    print(f"[1/5] 加载数据: {args.file}")
    df = load_data(args.file)
    print(f"      → 形状: {df.shape}")

    print("[2/5] EDA 探索性分析...")
    eda_result = eda(df)

    print("[3/5] 数据清洗...")
    df_clean, clean_log = clean_data(df)
    for msg in clean_log:
        print(f"      → {msg}")

    print("[4/5] 生成图表...")
    charts_dir = os.path.join(args.output_dir, "charts")
    charts = generate_charts(df_clean, charts_dir)
    print(f"      → 生成 {len(charts)} 张图表")

    # 保存摘要 JSON
    result = {
        "file": args.file,
        "eda": eda_result,
        "clean_log": clean_log,
        "charts": charts,
    }
    summary_path = os.path.join(args.output_dir, "summary.json")
    with open(summary_path, "w", encoding="utf-8") as f:
        json.dump(result, f, ensure_ascii=False, indent=2, default=str)

    print(f"[5/5] 完成！摘要已保存至: {summary_path}")
    print(json.dumps(result, ensure_ascii=False, indent=2, default=str))

if __name__ == "__main__":
    main()


path: scripts/export_report.py
content: #!/usr/bin/env python3
"""
Word 报告生成脚本
读取 analyze.py 输出的 summary.json + 图表，生成完整 Word 报告
用法：python3 export_report.py <summary.json路径> <洞察文本> [--output <报告路径>]
"""

import sys
import os
import json
import argparse
from datetime import datetime

REQUIRED = ["python-docx"]
missing = []
try:
    import docx
except ImportError:
    missing.append("python-docx")

if missing:
    print(f"[ERROR] 缺少依赖，请先运行: pip3 install {' '.join(missing)}")
    sys.exit(1)

from docx import Document
from docx.shared import Inches, Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement


def set_cell_bg(cell, hex_color: str):
    """设置表格单元格背景色"""
    tc = cell._tc
    tcPr = tc.get_or_add_tcPr()
    shd = OxmlElement("w:shd")
    shd.set(qn("w:val"), "clear")
    shd.set(qn("w:color"), "auto")
    shd.set(qn("w:fill"), hex_color)
    tcPr.append(shd)


def add_heading(doc, text, level=1):
    p = doc.add_heading(text, level=level)
    p.alignment = WD_ALIGN_PARAGRAPH.LEFT
    return p


def add_kv_table(doc, data: dict, title: str = None):
    """添加键值对表格"""
    if title:
        doc.add_paragraph(title, style="Heading 3")
    table = doc.add_table(rows=1, cols=2)
    table.style = "Table Grid"
    hdr = table.rows[0].cells
    hdr[0].text = "字段"
    hdr[1].text = "值"
    for cell in hdr:
        set_cell_bg(cell, "4472C4")
        for para in cell.paragraphs:
            for run in para.runs:
                run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
                run.font.bold = True

    for k, v in data.items():
        row = table.add_row().cells
        row[0].text = str(k)
        row[1].text = str(v)
    doc.add_paragraph()


def generate_report(summary_path: str, insight: str, output_path: str):
    with open(summary_path, "r", encoding="utf-8") as f:
        summary = json.load(f)

    doc = Document()

    # ── 标题页 ────────────────────────────────────────────────────────────────
    title = doc.add_heading("数据分析报告", 0)
    title.alignment = WD_ALIGN_PARAGRAPH.CENTER

    sub = doc.add_paragraph(f"生成时间：{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    sub.alignment = WD_ALIGN_PARAGRAPH.CENTER
    sub = doc.add_paragraph(f"数据文件：{os.path.basename(summary.get('file', '未知'))}")
    sub.alignment = WD_ALIGN_PARAGRAPH.CENTER
    doc.add_page_break()

    # ── 1. 数据概览 ───────────────────────────────────────────────────────────
    add_heading(doc, "一、数据概览", 1)
    eda = summary.get("eda", {})
    shape = eda.get("shape", [0, 0])
    overview = {
        "总行数": shape[0],
        "总列数": shape[1],
        "数值列数": len(eda.get("numeric_columns", [])),
        "分类列数": len(eda.get("categorical_columns", [])),
        "重复行数": eda.get("duplicates", 0),
    }
    add_kv_table(doc, overview, "基本信息")

    # 列信息表
    doc.add_paragraph("列信息", style="Heading 3")
    cols = eda.get("columns", [])
    dtypes = eda.get("dtypes", {})
    missing_pct = eda.get("missing_pct", {})
    if cols:
        table = doc.add_table(rows=1, cols=3)
        table.style = "Table Grid"
        hdr = table.rows[0].cells
        for i, h in enumerate(["列名", "数据类型", "缺失率(%)"]):
            hdr[i].text = h
            set_cell_bg(hdr[i], "4472C4")
            for para in hdr[i].paragraphs:
                for run in para.runs:
                    run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
                    run.font.bold = True
        for col in cols:
            row = table.add_row().cells
            row[0].text = str(col)
            row[1].text = str(dtypes.get(col, ""))
            row[2].text = str(missing_pct.get(col, 0))
    doc.add_paragraph()

    # ── 2. 数据清洗记录 ───────────────────────────────────────────────────────
    add_heading(doc, "二、数据清洗记录", 1)
    clean_log = summary.get("clean_log", [])
    if clean_log:
        for item in clean_log:
            doc.add_paragraph(f"• {item}")
    else:
        doc.add_paragraph("数据质量良好，无需清洗。")
    doc.add_paragraph()

    # ── 3. 统计分析 ───────────────────────────────────────────────────────────
    add_heading(doc, "三、统计分析", 1)
    numeric_stats = eda.get("numeric_stats", {})
    if numeric_stats:
        doc.add_paragraph("数值列描述性统计", style="Heading 3")
        stat_keys = ["count", "mean", "std", "min", "25%", "50%", "75%", "max"]
        num_cols = list(numeric_stats.keys())
        table = doc.add_table(rows=1, cols=len(num_cols) + 1)
        table.style = "Table Grid"
        hdr = table.rows[0].cells
        hdr[0].text = "统计量"
        set_cell_bg(hdr[0], "4472C4")
        for para in hdr[0].paragraphs:
            for run in para.runs:
                run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
                run.font.bold = True
        for i, col in enumerate(num_cols):
            hdr[i + 1].text = str(col)
            set_cell_bg(hdr[i + 1], "4472C4")
            for para in hdr[i + 1].paragraphs:
                for run in para.runs:
                    run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
                    run.font.bold = True
        for stat in stat_keys:
            row = table.add_row().cells
            row[0].text = stat
            for i, col in enumerate(num_cols):
                val = numeric_stats[col].get(stat, "")
                row[i + 1].text = f"{val:.4g}" if isinstance(val, float) else str(val)
        doc.add_paragraph()

    cat_stats = eda.get("categorical_stats", {})
    if cat_stats:
        doc.add_paragraph("分类列统计", style="Heading 3")
        for col, info in cat_stats.items():
            doc.add_paragraph(f"列：{col}  |  唯一值数量：{info.get('unique', 0)}", style="Heading 4")
            top5 = info.get("top5", {})
            if top5:
                table = doc.add_table(rows=1, cols=2)
                table.style = "Table Grid"
                hdr = table.rows[0].cells
                hdr[0].text = "值"
                hdr[1].text = "频次"
                for cell in hdr:
                    set_cell_bg(cell, "70AD47")
                    for para in cell.paragraphs:
                        for run in para.runs:
                            run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
                            run.font.bold = True
                for k, v in top5.items():
                    row = table.add_row().cells
                    row[0].text = str(k)
                    row[1].text = str(v)
                doc.add_paragraph()

    # ── 4. 可视化图表 ─────────────────────────────────────────────────────────
    add_heading(doc, "四、可视化图表", 1)
    charts = summary.get("charts", [])
    type_names = {"bar": "柱状图", "pie": "饼状图", "line": "折线图", "barh": "条形图"}
    if charts:
        for chart in charts:
            chart_path = chart.get("path", "")
            if chart_path and os.path.exists(chart_path):
                chart_type = type_names.get(chart.get("type", ""), chart.get("type", ""))
                col_name = chart.get("column", "")
                doc.add_paragraph(f"{chart_type}：{col_name}", style="Heading 3")
                doc.add_picture(chart_path, width=Inches(5.5))
                doc.add_paragraph()
    else:
        doc.add_paragraph("未生成图表（数据列不足）。")

    # ── 5. AI 洞察 ────────────────────────────────────────────────────────────
    add_heading(doc, "五、AI 数据洞察", 1)
    if insight:
        for line in insight.strip().split("\n"):
            doc.add_paragraph(line)
    else:
        doc.add_paragraph("（洞察内容待生成）")

    # ── 6. 结论与建议 ─────────────────────────────────────────────────────────
    add_heading(doc, "六、结论与建议", 1)
    doc.add_paragraph("本报告由数据分析 Skill 自动生成，包含完整的 EDA、清洗记录、统计分析、可视化图表及 AI 洞察。")
    doc.add_paragraph("建议结合业务背景对以上洞察进行深入解读，并针对异常值、趋势变化制定相应的业务策略。")

    doc.save(output_path)
    print(f"[OK] Word 报告已保存至: {output_path}")
    return output_path


def main():
    parser = argparse.ArgumentParser(description="生成 Word 数据分析报告")
    parser.add_argument("summary", help="summary.json 路径")
    parser.add_argument("insight", help="AI 洞察文本（可为空字符串）")
    parser.add_argument("--output", default="./analysis_report.docx", help="输出 Word 文件路径")
    args = parser.parse_args()

    generate_report(args.summary, args.insight, args.output)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)