Skip to content
Back to skills

Law Firm Worklog

ASecurity

律所工时月报生成(通用版,多数据源)。从用户已配置的任务管理工具(滴答清单 / Notion / Microsoft To Do / 飞书任务模块,任选其一)拉取指定日期范围内已完成的"业务工作"任务,按用户首次配置的工时模板(列名、固定值、姓名)和项目案号注册表(项目名、案号、关键词)自动归类并生成律所工时系统可导入的 Excel(默认 .xlsx)或 CSV。首次运行会引导用户完成一次性配置(数据源选择 + 工作单元白名单、姓名/律所、列模板、项目案号清单),后续可随时增删项目。当用户提到"工时""工作日志""月度工时""填工时""导出工时""律所工时""X月工时"等时触发,不论是否提及具体律所或工具。

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 25, 2026
toolspythonbashapidatabase

Works with

  • cursor
  • api
  • mcp

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 8 files and shows the line behind each finding

Scanned September 25, 2026

npx -y skills add CSlawyer1985/legal-skillhub --skill law-firm-worklog --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Law Firm Worklog?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Law Firm Worklog
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/cslawyer1985-law-firm-worklog/badge)](https://www.skillsdirectory.com/skills/cslawyer1985-law-firm-worklog)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: law-firm-worklog
description: 律所工时月报生成(通用版,多数据源)。从用户已配置的任务管理工具(滴答清单 / Notion / Microsoft To Do / 飞书任务模块,任选其一)拉取指定日期范围内已完成的"业务工作"任务,按用户首次配置的工时模板(列名、固定值、姓名)和项目案号注册表(项目名、案号、关键词)自动归类并生成律所工时系统可导入的 Excel(默认 .xlsx)或 CSV。首次运行会引导用户完成一次性配置(数据源选择 + 工作单元白名单、姓名/律所、列模板、项目案号清单),后续可随时增删项目。当用户提到"工时""工作日志""月度工时""填工时""导出工时""律所工时""X月工时"等时触发,不论是否提及具体律所或工具。
---

# 律所工时月报生成 Skill(通用版,多数据源)

把"已完成任务"转成"律所工时系统 Excel / CSV"的工作流,适用于任何律所、使用以下任一任务工具的律师:

| 数据源 | 适配器文件 |
|--------|----------|
| 滴答清单 / TickTick | `references/sources/dida365.md` |
| Notion | `references/sources/notion.md` |
| Microsoft To Do | `references/sources/todo.md` |
| 飞书 / Lark | `references/sources/feishu.md` |

主流程(本文件)数据源无关:所有源在 B3 出口都归一化成 `{date, title, project_key, is_all_day}` 四元组,下游 B4/B5 统一处理。源专属细节(MCP 工具名、字段映射、时区处理、首次配置追问脚本)都封装在对应 `references/sources/<source>.md` 中。

## 总览

本 skill 把工作流拆成两类调用:

- **首次配置**(user-config 目录为空):引导用户选数据源、录入律所工时表头、姓名、固定值、工作单元白名单、项目案号注册表。
- **生成工时**(已配置过):读取配置,按 source 字段加载适配器,拉任务,归类,生成 Excel(默认)或 CSV。

另外随时支持:
- **新增/删减项目** — 用户说"新签了 X 项目,案号 …"或"删掉某项目"时,原地编辑 `user-config/projects.csv`。
- **修改个人信息 / 工作单元白名单** — 用户说"换律所了""改姓名""新增工作 list/database/project"时,编辑 `user-config/profile.md`,源专属字段格式见对应 `references/sources/<source>.md`。

## 起手判断

进入 skill 后按以下决策树走,不跳步:

```
user-config/profile.md 存在且完整?
├─ 否 → 流程 A(首次配置);其中第二组确定 source。
└─ 是 → 读取 profile.md 的 `source` 字段(缺省 = dida365,向后兼容老配置)
         → 加载 references/sources/<source>.md
         → 按其"MCP 工具集"小节验证工具可见性
            ├─ 工具不可见 → 输出该 adapter 的"工具不可见时的提示文案",停
            ├─ 工具可见但鉴权失败(401/403)→ 输出该 adapter 的鉴权失败提示,停
            └─ 工具就绪 → 流程 B(生成工时)
```

绝不在此阶段假设默认姓名、默认列名、默认 source——没有用户认可的配置就不能生成任何输出文件。

---

## 流程 A:首次配置

详细脚本见 `references/setup.md`。要点:

1. 一次性向用户收齐以下信息(一组一组问,不要一次甩一长串)。**能用规则推断的字段不要主动问用户,只在用户 review 时纠正**——这条仅适用于工时模板的列分类启发式,**不适用于数据源 schema**(见下方约束)。

   - **第一组:工时模板** — 列名 + 启发式分类(保留不变;启发式分类详见 `references/setup.md` 的步骤 1-B 表格)。
   - **第二组:数据源 + 工作单元白名单** — 先问用户用什么任务工具(`dida365` / `notion` / `todo` / `feishu`),然后**加载 `references/sources/<source>.md` 中的"首次配置脚本"小节**走源专属问答。每个源的"工作单元"概念不同:dida 是 project,notion 是 database,todo 是 task list,feishu 是任务容器(任务列表或全部)。
   - **第三组:项目案号注册表** — 项目名 + 案号(关键词默认 = 项目名本身),数据源无关。

2. 把收集到的信息写入 `user-config/` 下三个文件:
   - `profile.md` — 包含 `source:` 字段、工作单元白名单(按 source 不同格式不同)、列分类表(启发式分类结果 + A 类各列固定值,含律师姓名)
   - `template.csv` 或 `template.xlsx` — 用户律所工时系统的表头(用户给什么格式就存什么格式)
   - `projects.csv` — 项目案号注册表,列固定为:`项目名,案号,客户,关键词`

3. 写完后输出"配置已保存到 …,下次直接说'生成 X 月工时'即可"。

**关键约束**:
- 工时模板列名启发式分类**允许**(用户 review 时纠正)。
- 数据源 schema **不允许**启发式(Notion / 飞书的 database / 多维表格 schema 用户自定义,必须显式问清"哪个字段是状态、哪个是完成时间、状态等于什么值算完成",绝不靠列名猜)。
- 项目案号绝不虚构。关键词允许默认(项目名本身),不允许凭空编造其他词。

---

## 流程 B:生成工时

### B1. 确认日期范围

向用户确认起止日(如 `2026-05-01` 到 `2026-05-24`)。如果用户只说"5月工时",按当月 1 日到今天(或当月末,看上下文)处理,并复述给用户确认。

### B1.5 检查输出文件是否已存在

生成前检查默认输出路径(`YYYY-MM-工作日志.xlsx`):

- **文件不存在** → 继续 B2,正常生成
- **文件已存在** → 提示用户:"已存在 `YYYY-MM-工作日志.xlsx`(上次生成于 <文件修改时间>)。是否覆盖?还是生成带时间戳的新文件(如 `YYYY-MM-工作日志-v2.xlsx`)?"

不要静默覆盖——用户可能已经在旧文件里手填了小时数。

### B2. 拉指定日期范围内已完成任务

按 `profile.md` 的 `source` 字段加载 `references/sources/<source>.md`,执行其中的 **"B2 数据拉取"** 小节。

跨源共同约束(每个 adapter 都必须遵守):

- **绝不全量拉取再筛选**。必须在 API 层用工作单元 ID(projectIds / database_id / list_id / table_id)过滤。理由:用户的"个人/学习/还款"任务混在同一账户里,全量拉会把婚礼、还款日、课程都拖进工时;大库还会触发分页/限速,慢且容易漏。
- **分页**:返回结果如带 `hasMore` / `has_more` / `nextCursor` / `next_cursor` / `@odata.nextLink` 等标记,必须补拉至全部,再进入 B3。不要静默丢弃超出首页的任务。

如果用户说"我新建了一个工作单元(项目/list/database/table)"或拉回来的数据明显少了,先按 adapter 的"流程 C"小节复核白名单,必要时让用户确认后更新 `profile.md`。

### B3. 字段归一化

按 `profile.md` 的 `source` 字段加载 `references/sources/<source>.md`,执行其中的 **"B3 字段归一化"** 小节。

每条任务必须归一化成以下统一四元组(下游 B4/B5 只读这层,不再触及源专属字段):

```json
{
  "date":        "YYYY-MM-DD",   // 北京时间(UTC+8)日期
  "title":       "任务标题",
  "project_key": "<source 内的工作单元 ID 或子分类值>",
  "is_all_day":  true | false
}
```

各源对时区、全天事件的处理差异详见对应 adapter 文件,本文件不重复。**注意滴答清单的全天任务 UTC 日期 +1 天的坑由 `dida365.md` 处理**——主流程不需要知道。

> **设计原理**:完整的架构决策说明见末尾"关于为什么这样设计"段落。

### B4. 按项目案号归类

读取 `user-config/projects.csv`,按以下顺序为每条任务分配项目和案号:

1. **关键词匹配** — 任务标题/描述中包含某项目的任一关键词 → 命中该项目。多个项目同时命中时按以下优先级链裁决:

   1. **完全匹配优先**:关键词完整出现在标题中(如标题含"新能源项目",关键词也是"新能源项目")> 仅部分匹配(如关键词是"新能源",仅命中标题中的子串)
   2. **匹配到的关键词字符数多者优先**(减少单字/短词误命中)
   3. **仍冲突 → 问用户**"以下任务同时命中 [项目A] 和 [项目B] 的关键词,请确认归属"

   例:任务"新能源项目 — 审阅EPC合同"同时命中项目 A(关键词"新能源项目",5字)和项目 B(关键词"新能源",3字)→ 项目 A 胜出。
2. **匹配不上** — 不要瞎猜,列出来让用户裁定("以下 N 条无法自动归类,请告知归属项目")。

   **兜底处理**:
   - 如果用户在合理时间内未回应:将该任务标记为"待确认",列在输出摘要末尾,并在文件名中加 `(含待确认)` 后缀。
   - 如果用户明确说"随便归到 X":按用户说的填,但在输出摘要中注明"[已手动指定]"。
   - 如果用户说"这条不算":跳过该任务,不出现在输出文件中。

### B5. 生成输出文件

> **设计原理**:完整的架构决策说明见末尾"关于为什么这样设计"段落。

**默认生成 Excel(`.xlsx`)**,用户也可以说"导出 CSV"来要 CSV。

#### 输出路径

文件名默认 `YYYY-MM-工作日志.xlsx`(或 `.csv`),保存到当前工作目录。

#### 输出格式选择

- 用户说"生成 X 月工时"不加后缀 → 输出 `.xlsx`
- 用户说"导出 CSV"或"生成 CSV" → 输出 `.csv`
- `user-config/` 下只有 `template.csv`(没有 `.xlsx`)且用户没指定格式 → 输出 `.csv`(尊重历史配置)

**表头来源不受模板格式限制**:无论 `user-config/` 存的是 `template.xlsx` 还是 `template.csv`,都能从中提取列名数组。所以用户给 Excel 模板、要求导出 CSV(或反过来)完全没问题——列名从模板读,输出格式按用户指令选。

#### Excel 生成(`.xlsx`)

调用 skill 自带的 `scripts/generate_worklog.py`,不要手写 openpyxl:

```bash
python <skill-dir>/scripts/generate_worklog.py \
  --input /tmp/worklog_data.json \
  --output "YYYY-MM-工作日志.xlsx" \
  --template "<user-config/template.xlsx 的路径(如有)>"
```

其中 `/tmp/worklog_data.json` 的结构为:

```json
{
  "headers": ["列1", "列2", ...],
  "rows": [["值1", "值2", ...], ...],
  "column_widths": {"日期": 12, "描述": 50}
}
```

- `headers`:按模板列顺序的列名数组
- `rows`:按通用取值规则填充的数据行(二维数组)
- `column_widths`:可选,指定列宽(key 为列名,value 为宽度数值);不传则脚本用内置默认值(日期 ~12、描述 ~50、其他 ~15)

脚本会自动处理:加粗灰底表头、细线边框、冻结首行、自动筛选。如果提供了 `--template`,列宽优先从模板继承。依赖 `openpyxl`,首次运行前提示用户 `pip install openpyxl`。

#### CSV 生成(`.csv`)

按 `user-config/template.csv` 的表头顺序写每行。编码 **UTF-8 with BOM**,Excel/WPS 双击不乱码。

#### 通用取值规则(Excel 和 CSV 共用)

按首次配置时的列分类取值:
- **A 类(固定值列)**:直接填 profile.md 里配置好的固定值
- **B 类(任务衍生列)**:
  - 日期列 → B3 归一化后的 `date`(北京时间,格式 `YYYY-MM-DD`)
  - 描述/标题列 → B3 归一化后的 `title`
- **C 类(项目映射衍生列)**:用 B4 归类结果(项目名 / 案号 / 客户名)填入
- **D 类(留空待手填列)**:保持空白。理由:用户最了解每条任务实际花了多少时间或其他主观字段,自动估计反而误导;让用户在 Excel 里手填更准。

### B6. 输出摘要

写完后给用户:
- 文件绝对路径及格式(`.xlsx` / `.csv`)
- 总行数(不含表头)
- 项目分布表(项目 / 案号 / 条数)
- 需用户确认的边界条目清单:未匹配上的任务、关键词命中多个项目的任务

---

## 流程 C:增删项目 / 修改个人信息 / 增删工作单元

用户说"新签 X 项目,案号 …,关键词 …" → 追加一行到 `user-config/projects.csv`,让用户确认后保存。
用户说"删掉 X 项目" → 删行后保存。
用户说"改姓名/换律所" → 修改 `profile.md` 的列分类表中对应 A 类列的固定值。
用户说"新增/删除一个工作单元(项目 / list / database / table)" → 加载 `references/sources/<source>.md` 的"流程 C"小节,按源专属格式修改 `profile.md` 的工作单元白名单。

每次修改后回显"已更新,当前共 N 个项目(或 N 个工作单元)"。

---

## 重要约束

1. **绝不全量拉再筛选** —— 所有数据源都必须在 API 层用工作单元 ID 过滤。理由跨源通用:用户的"个人/学习/还款"任务混在同一账户/workspace 里,全量拉会污染工时。
2. **绝不虚构案号** —— 不在 `user-config/projects.csv` 中的项目,先问用户要新案号再写入注册表,再生成输出文件。
3. **绝不在首次配置完成前生成输出文件** —— 没有用户认可的列模板/姓名/案号/source,任何 Excel/CSV 都不能直接用。
4. **绝不对数据源 schema 启发式推断** —— Notion 的 status property、飞书多维表格的状态列、MS To Do 的 list 含义都必须由用户在首次配置时显式给出。工时模板的列名启发式分类是允许的(行业惯例统一),但数据源 schema 完全自定义,不能猜。
5. 不强行预估"自报小时"(若列模板中有此列),保留空白让用户手填。

## 维护

- 用户新签项目/收到新案号 → 追加到 `user-config/projects.csv`
- 用户新增工作单元(任意 source)→ 按对应 adapter 的"流程 C"小节修改 `user-config/profile.md`

## 参考文件

- `references/setup.md` — 首次配置详细脚本(一问一答模板、示例)
- `references/sources/<source>.md` — 各数据源的专属配置脚本与数据拉取细节
- `scripts/generate_worklog.py` — Excel 生成脚本(`openpyxl`),已打包,B5 直接调用,不要手写 openpyxl

## 常见问题排查

| 症状 | 可能原因 | 处理 |
|------|---------|------|
| 任务拉不到 / 数量明显偏少 | 工作单元白名单过时(新建了 project/list/database/table 但 profile.md 没更新) | 按当前 source 的 adapter 列出可选工作单元,让用户确认后更新白名单 |
| 拉不到 / 报鉴权错 | MCP token / OAuth 过期 | 按当前 source 的 adapter 输出鉴权失败提示文案 |
| 多条任务无法自动归类 | projects.csv 缺少该项目或关键词覆盖不足 | 展示未匹配任务列表,问用户归属;确认后追加到 projects.csv |
| Excel 打开乱码 | CSV 忘了加 BOM | 确认写入时用了 `utf-8-sig` 编码(或直接改用 .xlsx 输出,无编码问题) |
| openpyxl 报 `ModuleNotFoundError` | 环境没装 | 提示用户 `pip install openpyxl` |
| 任务日期不对(差一天) | 全天任务时区没正确处理 | 看当前 source 的 adapter 中 B3 节是怎么算 all-day 的(典型如 `dida365.md` 的 UTC+1 天规则) |

## 关于"为什么这样设计"

- **配置和工作流分离**:列模板、姓名、案号都是律所/律师强相关的"私有信息",写在 user-config 里而非 SKILL.md 里,是为了让一个 skill 文件能服务任何律所,配置由用户自己管理。
- **数据源适配器分离**:每个 source 的 MCP 工具名、字段语义、时区/全天事件处理差异巨大,但工作流的骨架(列分类、关键词匹配、Excel 生成)完全数据源无关。把源耦合点抽到 `references/sources/<source>.md`,主流程保持薄一层,新增源 = 加一个 reference 文件。
- **不预设默认值**:律所工时表头差异极大(有的 5 列、有的 8 列、有的有"客户"列没有"项目"列),任何"默认表头"都会让用户拿到的文件导不进系统。宁可第一次多问几个问题,也不要默认值导致后续返工。同理:用户的 Notion database / 飞书多维表格 schema 也千差万别,绝不替用户猜状态/完成时间字段。
- **首跑严格走完配置**:相比"边用边补",一次性把配置走完,后续每月只需一句"生成 5 月工时"就能完成,体验更稳。

Files in this skill

  • SKILL.md16.1 KB
  • evals/evals.json1.3 KB
  • references/setup.md6.9 KB
  • references/sources/dida365.md2.9 KB
  • references/sources/feishu.md3.2 KB
  • references/sources/notion.md2.8 KB
  • references/sources/todo.md2.4 KB
  • scripts/generate_worklog.py4.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…