# 开发日记:从一份资费表到可复用 dsh 插件 > 说明:本文按原始可见对话顺序记录项目开发过程。保留用户提示、AI 回复、关键命令和用户回传的终端结果;不包含系统提示、内部推理和面向运行环境的工具调用细节。 --- ## 第 1 轮:认识 DeepSeek Harness **用户:** > 我是一名应用统计学研究生,现在想要学习 deepseek-harness,请你查看 git 链接 https://github.com/deepseek-ai/deepseek-harness,给我学习指导 **AI 助手:** > deepseek-harness(`dsh`)不是统计/机器学习训练框架,而是一个可组合的 AI Agent 运行框架:用模型、工具、会话日志、审批与沙箱等插件拼装出智能体。它的核心设计是“一切皆插件”,底层采用 Cordis;模型适配器、工具、Agent loop 本身都可替换。 > > 对应用统计方向,最值得学习的是将统计分析流程封装为“可调用、可审计、可复现”的 Agent 工具。学习路径是:先运行 Web UI,再学习 Cordis 的 plugin、ctx、service、event、effect,随后实现一个本地 CSV 描述统计工具,最后理解 Agent 生命周期与工具执行流水线。 --- ## 第 2 轮:确定最小课题目标 **用户:** > 这是一份数据,我现在想要做一个用 dsh 做一个“CSV 描述统计 + 可复现报告”的本地 Agent 工具链。请你给我一份指南,手把手教我如何做,给我列出详细步骤。 **AI 助手:** > 目标拆为五步: > > 1. 创建 dsh 源码环境与 Python 虚拟环境; > 2. 将 Excel 转为稳定编码的本地 CSV; > 3. 用 Python 生成描述统计、数据质量表、分组统计与 `manifest.json`; > 4. 用 TypeScript/Cordis 将 Python 脚本注册为 `tariff_describe` 工具; > 5. 在 Web UI 调用工具并用输入哈希、依赖版本和输出哈希验证可复现性。 **AI 助手:** > 先定边界:原始数据只在本地由 Python 读取;dsh 只获取脱敏后的汇总结果;报告需要记录输入哈希、代码版本、统计表和 Markdown 说明。 --- ## 第 3 轮:检查数据结构 **AI 助手:** > 已读取主表“数据”:11,729 行、19 字段,当前无缺失值。字段包括用户标识、入网日期、品牌系列、套餐名称、套餐费用、用户属地、话费、流量使用、套餐内总流量、语音资源、流量/语音饱和度、套外费用、客户等级、价值合约、异网主卡和宽带。 **用户:** > 已完成:创建 `tariff-agent` 目录和 Python 虚拟环境,安装了 pandas、openpyxl,并在 `.gitignore` 中忽略了 `data/`、`reports/` 和 `.venv/`。下一步做什么? --- ## 第 4 轮:Excel 转 CSV **AI 助手:** > 下一步是把 Excel 转为本地 CSV,并确认转换结果。先创建 `tariff-agent/scripts/xlsx_to_csv.py`,使用 `pandas.read_excel()` 读取“数据”工作表,再以 UTF-8 BOM 编码写入 CSV。运行: ```bash source tariff-agent/.venv/bin/activate python tariff-agent/scripts/xlsx_to_csv.py \ --input '/Users/chaoyi/Documents/dsh/老旧资费特征 copy.xlsx' \ --output tariff-agent/data/old_tariff.csv ``` **用户:** ```text 已写入:tariff-agent/data/old_tariff.csv 行数:11729;列数:19 字段: ['用户标识', '入网日期', '品牌系列', '套餐名称', '套餐费用(元)', '用户属地', '话费(元)', '流量使用(MB)', '资费套餐总流量(MB)', '本月套外语音费用', '本月套内语音资源(分钟)', '流量饱和度(百分比单位)', '套外流量费用', '语音饱和度(百分比单位)', '全球通等级', '套外流量单价(元/GB)', '价值合约(1:是,0:不是)', '异网主卡(1:是,0:不是)', '宽带(1:是,0:不是)'] -rw-r--r-- 1 chaoyi staff 1.4M Aug 31 15:20 tariff-agent/data/old_tariff.csv ``` **AI 助手:** > 转换成功:CSV 约 1.4 MB,行列数正确。下一步创建描述统计与可复现报告脚本。 --- ## 第 5 轮:描述统计与报告脚本 **AI 助手:** > 脚本 `descriptive_report.py` 需要输出: > > - `data_quality.csv`:字段类型、缺失数、缺失率、唯一值数; > - `numeric_summary.csv`:count、mean、std、min、p25、median、p75、max; > - `categorical_distribution.csv`:品牌、套餐、属地、等级的频数和占比; > - `binary_feature_summary.csv`:价值合约、异网主卡、宽带的正例数和占比; > - `manifest.json`:输入 SHA-256、行列数、环境版本和日期范围; > - `report.md`:报告索引。 > > 目录名使用 `输入文件名-SHA256前12位`,以保证同一输入可定位到同一报告目录。 **用户:** ```text { "report_dir": "/Users/chaoyi/Documents/dsh/deepseek-harness/tariff-agent/reports/old_tariff-89825d7c0ab3", "row_count": 11729, "input_sha256": "89825d7c0ab35b2803e16dc6672f8693f6809165c81d42225dbacba9cb20d30a" } ``` **AI 助手:** > 原始报告已生成。下一步将脚本封装为 dsh 工具。 --- ## 第 6 轮:封装为 Cordis/dsh 工具 **AI 助手:** > 在 `tariff-tool.ts` 中导出 `name`、`inject = ['tools']` 和 `apply(ctx, config)`。通过 `defineTool()` 注册 `tariff_describe`;参数只接受 `dataDir` 下相对路径的 CSV,使用 `resolve()`、`relative()` 和扩展名检查阻止越界读文件。工具通过 `spawn()` 调用固定虚拟环境里的 Python 脚本,并将 `exec.signal` 传给子进程。 > `cordis.yml` 中配置 `dataDir`、`outputDir`、`pythonPath` 和 `scriptPath`,随后执行: ```bash pnpm dsh web --patch ./tariff-agent/scratch-plugin/cordis.yml ``` **用户:** > 在自动打开的 Web UI 中输入: > > 请调用 `tariff_describe` 工具分析 `old_tariff.csv`。只基于聚合结果总结样本规模、数据质量、套餐费用、话费、流量使用、套餐构成和合约/宽带占比;不要输出任何用户级信息。 **用户:** > dsh 已完成整体描述:样本量 11,729、19 字段、所有字段无缺失;套餐费用中位数 18 元,话费中位数约 99 元,流量使用高度右偏;同时输出套餐、品牌、属地与合约/宽带聚合占比,未涉及用户级信息。 --- ## 第 7 轮:验证可复现性 **AI 助手:** > 先对输出文件计算 SHA-256,再重跑脚本、重新计算哈希,并用 `diff -u` 比较: ```bash PYTHON="$PWD/tariff-agent/.venv/bin/python" REPORT_DIR='tariff-agent/reports/old_tariff-89825d7c0ab3' ``` **用户:** > 首次比较时只有 `manifest.json` 哈希变化。 **AI 助手:** > 原因是本次终端只有 `(base)`,没有使用原来的虚拟环境;`manifest.json` 会记录 Python 与 pandas 版本。统计表和报告未变化,说明统计计算一致,清单正确暴露了环境漂移。应显式使用 `tariff-agent/.venv/bin/python`。 **用户:** ```text Python 3.12.12 pandas = 3.0.5 { "report_dir": ".../old_tariff-89825d7c0ab3", "row_count": 11729, "input_sha256": "89825d7c0ab35..." } ``` **AI 助手:** > 第二次 `diff` 没有输出,表明在相同输入、脚本与环境下,报告文件完全可复现。 --- ## 第 8 轮:增加分组统计 **AI 助手:** > 新增: ```python GROUP_COLUMNS = ["套餐名称", "用户属地", "品牌系列"] GROUP_METRICS = ["套餐费用(元)", "话费(元)", "流量使用(MB)", "套外流量费用", "流量饱和度(百分比单位)"] MIN_GROUP_SIZE = 30 ``` > 分组结果计算样本量、均值、中位数、p25 和 p75;样本量小于 30 的组不进入输出。结果写入 `grouped_numeric_summary.csv`。 **用户:** ```text -rw-r--r-- 1 chaoyi staff 31K Aug 31 15:44 tariff-agent/reports/old_tariff-89825d7c0ab3/grouped_numeric_summary.csv ``` **AI 助手:** > 分组统计运行成功。无需重启 dsh,因为 `tariff_describe` 在每次调用时重新执行 Python 脚本。 --- ## 第 9 轮:用 Agent 解释分群结果 **用户:** > 请调用 `tariff_describe` 工具分析 `old_tariff.csv`,然后读取报告目录的 `grouped_numeric_summary.csv`;分别比较套餐名称、用户属地、品牌系列的套餐费用、话费、流量使用与套外流量费用;每条比较报告样本量、中位数和四分位区间;只做描述性比较,不输出用户级信息。 **用户:** > dsh 读取了 46 个套餐、9 个属地和 4 个品牌系列的分组聚合结果,并使用中位数与四分位区间解释右偏的流量和套外费用。 **AI 助手:** > 解释整体合格,但需区分完整样本统计与“样本量至少 30 的分组”统计:完整样本套餐费用范围是 9~588 元,而纳入分组统计的套餐范围是 9~338 元。另需确认“流量饱和度(百分比单位)”的业务口径后再换算百分比。 --- ## 第 10 轮:确认最初目标完成 **用户:** > 这是否可以说明已经完成了“描述统计 + 可复现报告”这个最初目标? **AI 助手:** > 是。已完成 Excel→CSV、本地描述统计、聚合报告、输入哈希、依赖锁定、重复运行哈希验证,以及 dsh 本地工具调用。为便于长期复现,代码与依赖文件应提交到 Git;原始数据和虚拟环境不应混入代码提交。 --- ## 第 11 轮:从课题脚本转为可复用插件 **用户:** > 我现在正是需要将这个 dsh 插件上传到我的 git 主页,并且将我也你协作创建这个插件的过程也一起上传到 git。 **AI 助手:** > 目标应调整为独立、可安装的 dsh bundle,而非把修改直接推送到 DeepSeek 官方仓库。bundle 需要 `package.json` 的 `dsh.bundle` 声明和 `cordis.patch.yml`;示例数据、真实聚合报告与开发日记置于 `example/`。README 同时面向插件使用者与 HR/面试官。 **用户:** > 我的 GitHub 主页是 `https://github.com/SUFE-Chaoyi`;仓库公开;不加署名;数据字段说明、少量数据值示例和真实聚合报告需要上传;开发过程需要作为日记上传。 **AI 助手:** > 最终项目命名为 `dsh-plugin-csv-report`,代码根目录是可复用插件,`example/` 集中放置数据样例、真实聚合报告和运行配置。建议 Topics:`dsh`、`dsh-plugin`、`deepseek-harness`、`ai-agent`、`python`、`descriptive-statistics`、`reproducible-research`,首个 Git tag 为 `v0.1.0`。 --- ## 第 12 轮:本次发布 **用户:** > 请直接执行。README、description 等描述/简介用中文。直接开始上传我的 git 吧,我的 git cli 已经登陆。我的主页:`https://github.com/SUFE-Chaoyi`,仓库名我取名为:`dsh-plugin-csv-report`。 **AI 助手:** > 开始将课题代码重构为可安装 dsh bundle,归档完整可见对话到开发日记,保留真实聚合报告于 `example/`,并创建和推送 `SUFE-Chaoyi/dsh-plugin-csv-report`。 --- ## 第 13 轮:从单一描述统计工具升级为资费分析准备链路(v0.2.0) **用户:** > 套餐资费数据来自老旧资费特征表。希望以 dsh 制作一个数据分析 Bot:它应完成数据分析和用户运营决策之前的流程,为决策者提供数据支撑,而不自动做业务决定。 **AI 助手:** > 将插件目标调整为“本地资费数据准备与描述性分析 Bot”。它负责输入文件校验、字段标准化、数据质量检查、描述统计和可复现报告;输出事实、异常和聚合结果,运营动作与业务决策仍由使用者完成。 ### 需求澄清 **用户:** > 是否可以增加图表输出,例如柱状图、折线图、透视表图等? **AI 助手:** > 可以实现,但图表会增加依赖、输出格式、图表口径和 dsh 展示方式的复杂度。当前版本优先完成可复现的数据准备与结构化统计输出;透视式分组统计 CSV 可作为后续图表插件的稳定输入。 **用户:** > 个人电脑使用,允许输出用户级分析结果;入网日期只用于入网批次分析,不计算当前在网时长。 **AI 助手:** > 明确记录该边界:用户标识仍不进入默认聚合报告;入网日期仅保留用于批次分析和日期范围概览,不计算在网时长。 ### v0.2.0 设计 插件由单工具升级为两个串联工具: ```text tariff_prepare -> 标准化 CSV、字段字典、准备清单 -> tariff_describe -> 数据质量、描述统计、分类分布、二元汇总、分组统计、报告清单 ``` 新增的关键设计包括: - 将 19 个字段、字段类型、枚举值与质量规则集中到 `python/tariff_schema.py`; - 新增 `python/prepare_tariff.py`,支持 XLSX/XLSM/CSV 输入和字段说明工作表; - 将 TypeScript 插件拆分为配置校验、路径策略、Python 运行器和两个工具注册模块; - 限制工具只读取 `dataDir` 内的相对路径,阻止绝对路径、`..` 穿越和符号链接越界; - 在上下游使用 SHA-256 串联原始文件、标准化 CSV 和统计报告; - 将工具返回限制为路径、哈希、质量状态和聚合元数据,不在模型可见结果中返回用户级记录。 ### 本地验证结果 对本地资费表执行准备流程,得到: ```text 样本量:11,729 字段数:19 准备状态:warning 错误数:0 警告:套外流量单价(元/GB)为常量列 ``` 随后对标准化 CSV 生成描述统计报告,得到: ```text 描述统计状态:pass 数据质量状态:warning 样本量:11,729 字段数:19 ``` 常量列警告被保留在质量报告中,不阻断描述性分析。 ### 测试 新增 Python 单元测试,覆盖: - 有效 CSV 的标准化输出; - XLSX 主数据与字段说明工作表读取; - 非法二元值; - 重复用户标识; - 缺失主数据工作表; - 描述统计产物完整性、质量状态、分组字段格式、重复运行一致性和清单参数。 新增 TypeScript/Vitest 单元测试,覆盖: - `dataDir` 路径边界、文件类型、文件大小和符号链接逃逸; - Python 子进程参数传递、失败信息、非法 JSON 与超大输出保护; - 插件配置合法性与危险路径拒绝; - `tariff_prepare` 与 `tariff_describe` 的 dsh 工具注册与参数契约。 最终验证命令: ```bash pnpm test ``` 测试结果: ```text TypeScript:15 个测试通过 Python:10 个测试通过 ``` ### 文档更新 v0.2.0 新增并更新了以下文档: - `docs/DATA_SCHEMA.zh-CN.md`:19 个字段口径和质量规则; - `docs/OUTPUT_CONTRACT.zh-CN.md`:工具参数、返回值、输出文件与哈希链路; - `docs/ARCHITECTURE.zh-CN.md`:准备阶段与描述阶段架构; - `docs/REPRODUCIBILITY.zh-CN.md`:XLSX 到标准化 CSV 再到统计报告的复现方法; - `README.md`:安装、配置、两阶段调用和测试说明。