跳到主要内容

专注时间与日程规划

收藏0

把一天的任务按专注工作与零碎事务分组,排出有缓冲的时间块,并可在本地记录专注时长。

让 AI 安装

github最新版本 0.1.1更新于 2026-10-21下载 2 次2 个版本

scripts/shallow_work_auditor.py

8.4 KB
1#!/usr/bin/env python3
2"""shallow_work_auditor.py — Classify tasks deep vs shallow and audit the shallow share against a budget.
3
4Encodes Cal Newport's shallow-work budget discipline:
5
6 1. Every task is classified DEEP or SHALLOW by keyword heuristics on its name:
7 shallow signals: email, inbox, slack, status, standup, meeting, expense, invoice,
8 scheduling, calendar, admin, paperwork, timesheet, errand, triage
9 deep signals: write, draft, design, code, build, research, analyze, study,
10 architect, prototype, prove, strategy, model
11 A tie (or no signal at all) counts as SHALLOW — depth must be claimed deliberately.
12 Override any task with an explicit :deep or :shallow suffix; the suffix always wins.
13 2. The shallow share of total hours is compared against --budget percent (default 50 —
14 the top of Newport's 30-50 percent band for most knowledge roles).
15 3. For every shallow item, the forcing question is printed:
16 "How long would it take to train a smart recent graduate to do this?"
17 Months of training -> it is probably deep; days or weeks -> it is shallow. Batch it,
18 delegate it, or say no to it.
19
20Verdicts:
21 WITHIN-BUDGET (exit 0) shallow share <= budget
22 OVER-BUDGET (exit 2) shallow share > budget — cut, batch, or delegate before planning the day
23
24NO LLM CALLS. Deterministic keyword scoring. Identical inputs, identical verdict.
25
26Exit codes:
27 0 WITHIN-BUDGET
28 1 usage / input error (bad task spec)
29 2 OVER-BUDGET
30
31Usage:
32 python shallow_work_auditor.py --task "Write investor update:60" \\
33 --task "Email triage:45" --task "Analyze churn cohort:90:deep" --budget 50
34 python shallow_work_auditor.py --sample
35 python shallow_work_auditor.py --sample --json
36"""
37
38import argparse
39import json
40import re
41import sys
42from typing import Any, Dict, List
43
44SHALLOW_KEYWORDS = [
45 "email", "inbox", "slack", "status", "standup", "meeting", "expense", "invoice",
46 "scheduling", "schedule", "calendar", "admin", "paperwork", "timesheet", "errand",
47 "triage",
48]
49DEEP_KEYWORDS = [
50 "write", "writing", "draft", "design", "code", "coding", "program", "build",
51 "research", "analyze", "analysis", "study", "architect", "prototype", "prove",
52 "strategy", "model",
53]
54
55FORCING_QUESTION = ('How long would it take to train a smart recent graduate to do "{name}"? '
56 "Days or weeks -> shallow: batch it, delegate it, or say no.")
57
58
59def parse_task(spec: str) -> Dict[str, Any]:
60 """Accepts "name:minutes" (heuristic classifies) or "name:minutes:deep|shallow" (override)."""
61 parts = spec.rsplit(":", 2)
62 override = None
63 if len(parts) == 3 and parts[2].strip().lower() in ("deep", "shallow"):
64 name, minutes_s, override = parts[0], parts[1], parts[2].strip().lower()
65 else:
66 parts = spec.rsplit(":", 1)
67 if len(parts) != 2:
68 raise ValueError(f'task must be "name:minutes[:deep|shallow]", got: {spec!r}')
69 name, minutes_s = parts
70 name, minutes_s = name.strip(), minutes_s.strip()
71 if not name:
72 raise ValueError(f"task name is empty in: {spec!r}")
73 if not minutes_s.isdigit() or int(minutes_s) <= 0:
74 raise ValueError(f"task minutes must be a positive integer in: {spec!r}")
75 return {"name": name, "minutes": int(minutes_s), "override": override}
76
77
78def classify(name: str) -> Dict[str, Any]:
79 lowered = name.lower()
80 deep_hits = [k for k in DEEP_KEYWORDS if re.search(rf"\b{re.escape(k)}\b", lowered)]
81 shallow_hits = [k for k in SHALLOW_KEYWORDS if re.search(rf"\b{re.escape(k)}\b", lowered)]
82 if len(deep_hits) > len(shallow_hits):
83 mode, why = "deep", f"deep signals: {', '.join(deep_hits)}"
84 elif shallow_hits:
85 mode, why = "shallow", f"shallow signals: {', '.join(shallow_hits)}"
86 else:
87 mode, why = "shallow", "no signal — unclassified counts as shallow; claim :deep explicitly if wrong"
88 return {"mode": mode, "why": why}
89
90
91def audit(tasks: List[Dict[str, Any]], budget_pct: float) -> Dict[str, Any]:
92 items = []
93 for t in tasks:
94 if t["override"]:
95 mode, why = t["override"], f"explicit :{t['override']} override"
96 else:
97 c = classify(t["name"])
98 mode, why = c["mode"], c["why"]
99 item = {"name": t["name"], "minutes": t["minutes"], "mode": mode, "why": why}
100 if mode == "shallow":
101 item["forcing_question"] = FORCING_QUESTION.format(name=t["name"])
102 items.append(item)
103
104 total = sum(i["minutes"] for i in items)
105 shallow_min = sum(i["minutes"] for i in items if i["mode"] == "shallow")
106 deep_min = total - shallow_min
107 share = round(100.0 * shallow_min / total, 1) if total else 0.0
108 verdict = "WITHIN-BUDGET" if share <= budget_pct else "OVER-BUDGET"
109 return {
110 "items": items,
111 "total_minutes": total,
112 "deep_minutes": deep_min,
113 "shallow_minutes": shallow_min,
114 "shallow_share_pct": share,
115 "budget_pct": budget_pct,
116 "verdict": verdict,
117 "headline": (
118 f"Shallow work is {share}% of the day against a {budget_pct:.0f}% budget. "
119 + ("Within budget — protect the deep hours you just earned."
120 if verdict == "WITHIN-BUDGET"
121 else "Over budget — cut, batch, or delegate shallow items before planning the day.")
122 ),
123 }
124
125
126def render_human(r: Dict[str, Any]) -> str:
127 out: List[str] = []
128 out.append("Shallow-Work Audit (deep vs shallow, share vs budget)")
129 out.append("=" * 64)
130 out.append("")
131 out.append("| Task | Min | Mode | Basis |")
132 out.append("|------|-----|------|-------|")
133 for i in r["items"]:
134 out.append(f"| {i['name']} | {i['minutes']} | {i['mode'].upper()} | {i['why']} |")
135 out.append("")
136 out.append(f" Deep {r['deep_minutes']} min · Shallow {r['shallow_minutes']} min · "
137 f"Shallow share {r['shallow_share_pct']}% vs budget {r['budget_pct']:.0f}%")
138 out.append("")
139 out.append(f" VERDICT: {r['verdict']}")
140 out.append(f" {r['headline']}")
141 shallow_items = [i for i in r["items"] if i["mode"] == "shallow"]
142 if shallow_items:
143 out.append("")
144 out.append(" The forcing question, per shallow item:")
145 for i in shallow_items:
146 out.append(f" - {i['forcing_question']}")
147 return "\n".join(out)
148
149
150SAMPLE_TASKS = [
151 "Write investor update:60",
152 "Analyze churn cohort:90:deep",
153 "Email triage:45",
154 "Slack catch-up:30",
155 "Expense report:20",
156 "Team scheduling:15",
157]
158
159
160def main(argv: List[str]) -> int:
161 p = argparse.ArgumentParser(
162 description="Classify tasks deep vs shallow and audit the shallow share against a budget.",
163 formatter_class=argparse.RawDescriptionHelpFormatter,
164 epilog=("Exit codes:\n"
165 " 0 WITHIN-BUDGET — shallow share <= --budget\n"
166 " 1 usage / input error\n"
167 " 2 OVER-BUDGET — shallow share > --budget; cut, batch, or delegate first"),
168 )
169 p.add_argument("--task", action="append", default=[],
170 help='Repeatable: "name:minutes" (heuristic) or "name:minutes:deep|shallow" (override)')
171 p.add_argument("--budget", type=float, default=50.0,
172 help="Shallow budget as percent of total time (default 50 — top of the 30-50 band)")
173 p.add_argument("--json", action="store_true", help="Emit JSON instead of the table")
174 p.add_argument("--sample", action="store_true", help="Run the embedded sample task list")
175 args = p.parse_args(argv)
176
177 if args.sample:
178 args.task = SAMPLE_TASKS
179 if not args.task:
180 p.print_help()
181 print("\nerror: at least one --task is required (or --sample)", file=sys.stderr)
182 return 1
183 if not 0 < args.budget <= 100:
184 print("error: --budget must be in (0, 100]", file=sys.stderr)
185 return 1
186 try:
187 tasks = [parse_task(s) for s in args.task]
188 except ValueError as exc:
189 print(f"error: {exc}", file=sys.stderr)
190 return 1
191
192 result = audit(tasks, args.budget)
193 if args.json:
194 print(json.dumps(result, indent=2))
195 else:
196 print(render_human(result))
197 return 0 if result["verdict"] == "WITHIN-BUDGET" else 2
198
199
200if __name__ == "__main__":
201 sys.exit(main(sys.argv[1:]))

文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。