@huicai/cli 2026.814.0 → 2026.1021.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/LICENSE +391 -32
  2. package/README.md +139 -8
  3. package/bin/huicai.js +155 -74
  4. package/bin/update.js +97 -0
  5. package/package.json +27 -8
  6. package/skills/ab-testing/trigger-cases.json +30 -0
  7. package/skills/academic-writing/trigger-cases.json +30 -0
  8. package/skills/accounting-ops/trigger-cases.json +30 -0
  9. package/skills/ai-agent/trigger-cases.json +30 -0
  10. package/skills/ai-aiops/trigger-cases.json +30 -0
  11. package/skills/ai-coding/SKILL.md +2 -0
  12. package/skills/ai-coding/trigger-cases.json +30 -0
  13. package/skills/ai-compliance/trigger-cases.json +30 -0
  14. package/skills/ai-eval/trigger-cases.json +30 -0
  15. package/skills/ai-llm-app/trigger-cases.json +30 -0
  16. package/skills/ai-mlops/trigger-cases.json +30 -0
  17. package/skills/ai-multimodal/trigger-cases.json +30 -0
  18. package/skills/ai-prompt/trigger-cases.json +30 -0
  19. package/skills/ai-rag/trigger-cases.json +30 -0
  20. package/skills/anomaly-detection/trigger-cases.json +30 -0
  21. package/skills/api-design/trigger-cases.json +31 -0
  22. package/skills/api-integration/trigger-cases.json +31 -0
  23. package/skills/arch-design/trigger-cases.json +12 -0
  24. package/skills/arch-diagram/SKILL.md +118 -0
  25. package/skills/arch-diagram/trigger-cases.json +30 -0
  26. package/skills/arch-distributed/trigger-cases.json +30 -0
  27. package/skills/arch-multitenant/trigger-cases.json +30 -0
  28. package/skills/audio-brief/SKILL.md +65 -0
  29. package/skills/audio-brief/trigger-cases.json +30 -0
  30. package/skills/audit-assist/trigger-cases.json +30 -0
  31. package/skills/audit-chain/trigger-cases.json +31 -0
  32. package/skills/banking-credit/trigger-cases.json +30 -0
  33. package/skills/brand-design/trigger-cases.json +30 -0
  34. package/skills/business-plan/trigger-cases.json +30 -0
  35. package/skills/capital-ops/trigger-cases.json +30 -0
  36. package/skills/cicd-pipeline/trigger-cases.json +31 -0
  37. package/skills/code-archaeology/SKILL.md +55 -0
  38. package/skills/code-archaeology/trigger-cases.json +9 -0
  39. package/skills/code-quality/SKILL.md +138 -0
  40. package/skills/code-quality/references/languages.md +107 -0
  41. package/skills/code-quality/scripts/__pycache__/code_battery.cpython-312.pyc +0 -0
  42. package/skills/code-quality/scripts/__pycache__/code_check.cpython-312.pyc +0 -0
  43. package/skills/code-quality/scripts/__pycache__/code_check.cpython-313.pyc +0 -0
  44. package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-312.pyc +0 -0
  45. package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-313.pyc +0 -0
  46. package/skills/code-quality/scripts/__pycache__/code_fix.cpython-312.pyc +0 -0
  47. package/skills/code-quality/scripts/__pycache__/code_fix.cpython-313.pyc +0 -0
  48. package/skills/code-quality/scripts/__pycache__/code_java.cpython-312.pyc +0 -0
  49. package/skills/code-quality/scripts/__pycache__/code_java.cpython-313.pyc +0 -0
  50. package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-312.pyc +0 -0
  51. package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-313.pyc +0 -0
  52. package/skills/code-quality/scripts/__pycache__/code_lens.cpython-312.pyc +0 -0
  53. package/skills/code-quality/scripts/__pycache__/code_lens.cpython-313.pyc +0 -0
  54. package/skills/code-quality/scripts/__pycache__/code_python.cpython-312.pyc +0 -0
  55. package/skills/code-quality/scripts/__pycache__/code_python.cpython-313.pyc +0 -0
  56. package/skills/code-quality/scripts/__pycache__/code_testplan.cpython-312.pyc +0 -0
  57. package/skills/code-quality/scripts/code_battery.py +1072 -0
  58. package/skills/code-quality/scripts/code_check.py +1269 -0
  59. package/skills/code-quality/scripts/code_csharp.py +371 -0
  60. package/skills/code-quality/scripts/code_fix.py +201 -0
  61. package/skills/code-quality/scripts/code_java.py +695 -0
  62. package/skills/code-quality/scripts/code_jsts.py +313 -0
  63. package/skills/code-quality/scripts/code_lens.py +1079 -0
  64. package/skills/code-quality/scripts/code_python.py +536 -0
  65. package/skills/code-quality/scripts/code_testplan.py +239 -0
  66. package/skills/code-quality/trigger-cases.json +45 -0
  67. package/skills/code-review/SKILL.md +24 -0
  68. package/skills/code-review/trigger-cases.json +35 -0
  69. package/skills/commit-and-pr/trigger-cases.json +31 -0
  70. package/skills/content-creation/trigger-cases.json +30 -0
  71. package/skills/corporate-governance/trigger-cases.json +30 -0
  72. package/skills/crawl-chain/trigger-cases.json +31 -0
  73. package/skills/customer-service/trigger-cases.json +30 -0
  74. package/skills/data-analysis/trigger-cases.json +30 -0
  75. package/skills/data-chain/SKILL.md +1 -1
  76. package/skills/data-chain/trigger-cases.json +31 -0
  77. package/skills/data-governance/trigger-cases.json +30 -0
  78. package/skills/data-report-pro/trigger-cases.json +30 -0
  79. package/skills/data-streaming/trigger-cases.json +30 -0
  80. package/skills/data-wrangling/SKILL.md +1 -1
  81. package/skills/data-wrangling/trigger-cases.json +30 -0
  82. package/skills/db-es/trigger-cases.json +30 -0
  83. package/skills/db-migration/trigger-cases.json +30 -0
  84. package/skills/db-mysql/trigger-cases.json +30 -0
  85. package/skills/db-postgres/trigger-cases.json +30 -0
  86. package/skills/db-redis/trigger-cases.json +30 -0
  87. package/skills/debug-root-cause/trigger-cases.json +30 -0
  88. package/skills/dependency-audit/SKILL.md +56 -0
  89. package/skills/dependency-audit/trigger-cases.json +7 -0
  90. package/skills/dependency-upgrade/trigger-cases.json +31 -0
  91. package/skills/dev-cpp/trigger-cases.json +30 -0
  92. package/skills/dev-cross-platform/trigger-cases.json +30 -0
  93. package/skills/dev-csharp/SKILL.md +23 -0
  94. package/skills/dev-csharp/trigger-cases.json +30 -0
  95. package/skills/dev-frontend/SKILL.md +19 -0
  96. package/skills/dev-frontend/trigger-cases.json +30 -0
  97. package/skills/dev-go/trigger-cases.json +30 -0
  98. package/skills/dev-harmony/trigger-cases.json +30 -0
  99. package/skills/dev-java/SKILL.md +20 -0
  100. package/skills/dev-java/trigger-cases.json +30 -0
  101. package/skills/dev-mobile/trigger-cases.json +30 -0
  102. package/skills/dev-node/trigger-cases.json +30 -0
  103. package/skills/dev-php/trigger-cases.json +30 -0
  104. package/skills/dev-python/SKILL.md +21 -0
  105. package/skills/dev-python/trigger-cases.json +30 -0
  106. package/skills/dev-rust/trigger-cases.json +30 -0
  107. package/skills/dev-toolchain/trigger-cases.json +31 -0
  108. package/skills/doc-chain/trigger-cases.json +31 -0
  109. package/skills/doc-compare/trigger-cases.json +30 -0
  110. package/skills/docx-author/SKILL.md +127 -25
  111. package/skills/docx-author/scripts/__pycache__/docx_battery.cpython-312.pyc +0 -0
  112. package/skills/docx-author/scripts/__pycache__/docx_build.cpython-312.pyc +0 -0
  113. package/skills/docx-author/scripts/__pycache__/docx_check.cpython-312.pyc +0 -0
  114. package/skills/docx-author/scripts/__pycache__/docx_layout.cpython-312.pyc +0 -0
  115. package/skills/docx-author/scripts/__pycache__/zh_prose.cpython-312.pyc +0 -0
  116. package/skills/docx-author/scripts/docx_battery.py +586 -0
  117. package/skills/docx-author/scripts/docx_build.py +995 -0
  118. package/skills/docx-author/scripts/docx_check.py +1306 -0
  119. package/skills/docx-author/scripts/docx_doc.py +14 -154
  120. package/skills/docx-author/scripts/docx_layout.py +490 -0
  121. package/skills/docx-author/scripts/zh_prose.py +639 -0
  122. package/skills/docx-author/trigger-cases.json +58 -0
  123. package/skills/echo-brief/SKILL.md +84 -0
  124. package/skills/echo-brief/references/echo-format.md +68 -0
  125. package/skills/echo-brief/trigger-cases.json +40 -0
  126. package/skills/ecommerce-ops/trigger-cases.json +30 -0
  127. package/skills/education-training/trigger-cases.json +30 -0
  128. package/skills/ehs-safety/trigger-cases.json +30 -0
  129. package/skills/email-writing/trigger-cases.json +30 -0
  130. package/skills/eng-ai/trigger-cases.json +30 -0
  131. package/skills/eng-architecture/trigger-cases.json +31 -0
  132. package/skills/eng-automation/trigger-cases.json +31 -0
  133. package/skills/eng-collaboration/trigger-cases.json +30 -0
  134. package/skills/eng-complex-env/trigger-cases.json +30 -0
  135. package/skills/eng-data-platform/trigger-cases.json +31 -0
  136. package/skills/eng-database/trigger-cases.json +31 -0
  137. package/skills/eng-develop/SKILL.md +9 -1
  138. package/skills/eng-develop/references/dod-checklist.md +3 -0
  139. package/skills/eng-develop/references/engineering-workflow.md +37 -0
  140. package/skills/eng-develop/trigger-cases.json +30 -0
  141. package/skills/eng-docs/SKILL.md +24 -0
  142. package/skills/eng-docs/trigger-cases.json +30 -0
  143. package/skills/eng-efficiency/trigger-cases.json +30 -0
  144. package/skills/eng-infra/trigger-cases.json +30 -0
  145. package/skills/eng-integration/trigger-cases.json +31 -0
  146. package/skills/eng-legacy/trigger-cases.json +31 -0
  147. package/skills/eng-observability/trigger-cases.json +31 -0
  148. package/skills/eng-ops/trigger-cases.json +31 -0
  149. package/skills/eng-performance/trigger-cases.json +31 -0
  150. package/skills/eng-planning/trigger-cases.json +31 -0
  151. package/skills/eng-quality/SKILL.md +8 -0
  152. package/skills/eng-quality/trigger-cases.json +31 -0
  153. package/skills/eng-release/trigger-cases.json +31 -0
  154. package/skills/eng-reliability/trigger-cases.json +31 -0
  155. package/skills/eng-requirements/trigger-cases.json +12 -0
  156. package/skills/eng-research/trigger-cases.json +30 -0
  157. package/skills/eng-security/trigger-cases.json +31 -0
  158. package/skills/eng-ux/trigger-cases.json +30 -0
  159. package/skills/engineering-chain/trigger-cases.json +30 -0
  160. package/skills/env-offline/trigger-cases.json +31 -0
  161. package/skills/env-private-deploy/trigger-cases.json +31 -0
  162. package/skills/env-xinchuang/trigger-cases.json +31 -0
  163. package/skills/equipment-tpm/trigger-cases.json +30 -0
  164. package/skills/esg-report/trigger-cases.json +30 -0
  165. package/skills/event-management/trigger-cases.json +30 -0
  166. package/skills/excel-analysis-chain/SKILL.md +17 -4
  167. package/skills/excel-analysis-chain/trigger-cases.json +31 -0
  168. package/skills/explain-codebase/trigger-cases.json +11 -0
  169. package/skills/file-ingest/trigger-cases.json +30 -0
  170. package/skills/finance-analysis/trigger-cases.json +31 -0
  171. package/skills/finance-chain/trigger-cases.json +31 -0
  172. package/skills/financial-modeling/trigger-cases.json +31 -0
  173. package/skills/find-simplifications/SKILL.md +43 -0
  174. package/skills/find-simplifications/trigger-cases.json +23 -0
  175. package/skills/foreign-trade/trigger-cases.json +30 -0
  176. package/skills/funnel-cohort/trigger-cases.json +30 -0
  177. package/skills/gov-document/trigger-cases.json +30 -0
  178. package/skills/gui-change-evidence/SKILL.md +47 -0
  179. package/skills/gui-change-evidence/trigger-cases.json +23 -0
  180. package/skills/hr-chain/trigger-cases.json +31 -0
  181. package/skills/hr-operations/trigger-cases.json +30 -0
  182. package/skills/info-disclosure/trigger-cases.json +31 -0
  183. package/skills/infra-gateway/trigger-cases.json +31 -0
  184. package/skills/infra-k8s/trigger-cases.json +31 -0
  185. package/skills/infra-mq/trigger-cases.json +30 -0
  186. package/skills/insurance-advisory/trigger-cases.json +30 -0
  187. package/skills/internal-audit/trigger-cases.json +30 -0
  188. package/skills/ip-management/trigger-cases.json +31 -0
  189. package/skills/ipo-listing/trigger-cases.json +31 -0
  190. package/skills/it-audit/trigger-cases.json +31 -0
  191. package/skills/itsm-ops/trigger-cases.json +31 -0
  192. package/skills/knowledge-sop/trigger-cases.json +31 -0
  193. package/skills/legal-chain/trigger-cases.json +31 -0
  194. package/skills/legal-contract/trigger-cases.json +30 -0
  195. package/skills/litigation-support/trigger-cases.json +31 -0
  196. package/skills/logistics-warehouse/trigger-cases.json +30 -0
  197. package/skills/long-doc-analysis/trigger-cases.json +31 -0
  198. package/skills/management-accounting/trigger-cases.json +30 -0
  199. package/skills/manufacturing-chain/trigger-cases.json +31 -0
  200. package/skills/manufacturing-quality/trigger-cases.json +30 -0
  201. package/skills/marketing-analytics/trigger-cases.json +31 -0
  202. package/skills/marketing-chain/trigger-cases.json +31 -0
  203. package/skills/meeting-minutes/trigger-cases.json +30 -0
  204. package/skills/metrics-system/trigger-cases.json +31 -0
  205. package/skills/negotiation-prep/trigger-cases.json +30 -0
  206. package/skills/obs-prometheus/trigger-cases.json +31 -0
  207. package/skills/office-automation/trigger-cases.json +31 -0
  208. package/skills/okr-review/trigger-cases.json +30 -0
  209. package/skills/ops-fleet/trigger-cases.json +31 -0
  210. package/skills/ops-linux/trigger-cases.json +31 -0
  211. package/skills/optimization-solver/trigger-cases.json +30 -0
  212. package/skills/patent-disclosure/SKILL.md +105 -0
  213. package/skills/patent-disclosure/references/figures.md +110 -0
  214. package/skills/patent-disclosure/references/outline.md +162 -0
  215. package/skills/patent-disclosure/scripts/patent_docx.py +230 -0
  216. package/skills/patent-disclosure/trigger-cases.json +31 -0
  217. package/skills/patent-drafting/SKILL.md +108 -0
  218. package/skills/patent-drafting/trigger-cases.json +31 -0
  219. package/skills/patent-portfolio/SKILL.md +81 -0
  220. package/skills/patent-portfolio/trigger-cases.json +31 -0
  221. package/skills/pdf-toolkit/trigger-cases.json +31 -0
  222. package/skills/perf-optimize/trigger-cases.json +31 -0
  223. package/skills/pivot-crosstab/trigger-cases.json +30 -0
  224. package/skills/policy-analysis/trigger-cases.json +31 -0
  225. package/skills/portfolio-analysis/trigger-cases.json +31 -0
  226. package/skills/ppt-chain/SKILL.md +14 -6
  227. package/skills/ppt-chain/trigger-cases.json +31 -0
  228. package/skills/pptx-author/SKILL.md +86 -33
  229. package/skills/pptx-author/scripts/__pycache__/pptx_check.cpython-312.pyc +0 -0
  230. package/skills/pptx-author/scripts/__pycache__/pptx_deck.cpython-312.pyc +0 -0
  231. package/skills/pptx-author/scripts/__pycache__/pptx_layout.cpython-312.pyc +0 -0
  232. package/skills/pptx-author/scripts/pptx_battery.py +178 -0
  233. package/skills/pptx-author/scripts/pptx_build.py +119 -0
  234. package/skills/pptx-author/scripts/pptx_check.py +1182 -0
  235. package/skills/pptx-author/scripts/pptx_deck.py +736 -145
  236. package/skills/pptx-author/scripts/pptx_layout.py +384 -0
  237. package/skills/pptx-author/trigger-cases.json +15 -0
  238. package/skills/pr-comms/trigger-cases.json +30 -0
  239. package/skills/procurement-supply/trigger-cases.json +31 -0
  240. package/skills/product-design/trigger-cases.json +31 -0
  241. package/skills/production-planning/trigger-cases.json +31 -0
  242. package/skills/project-management/trigger-cases.json +31 -0
  243. package/skills/project-proposal/trigger-cases.json +31 -0
  244. package/skills/prose-standard/SKILL.md +53 -0
  245. package/skills/prose-standard/trigger-cases.json +23 -0
  246. package/skills/qa-api-automation/trigger-cases.json +31 -0
  247. package/skills/qa-mobile-automation/trigger-cases.json +31 -0
  248. package/skills/qa-ui-automation/trigger-cases.json +31 -0
  249. package/skills/quant-analysis/trigger-cases.json +31 -0
  250. package/skills/rag-triage/SKILL.md +64 -0
  251. package/skills/rag-triage/evals.json +57 -0
  252. package/skills/rag-triage/references/triage-playbook.md +75 -0
  253. package/skills/rag-triage/trigger-cases.json +31 -0
  254. package/skills/real-estate-property/trigger-cases.json +30 -0
  255. package/skills/refactor-safely/SKILL.md +1 -1
  256. package/skills/refactor-safely/trigger-cases.json +30 -0
  257. package/skills/regression-forecast/trigger-cases.json +31 -0
  258. package/skills/release-checklist/trigger-cases.json +31 -0
  259. package/skills/report-craft/SKILL.md +76 -0
  260. package/skills/report-craft/references/feasibility.md +118 -0
  261. package/skills/report-craft/references/review-checklist.md +51 -0
  262. package/skills/report-craft/references/visuals.md +114 -0
  263. package/skills/report-craft/references/writing.md +80 -0
  264. package/skills/report-craft/scripts/__pycache__/chart_render.cpython-312.pyc +0 -0
  265. package/skills/report-craft/scripts/__pycache__/fin_model.cpython-312.pyc +0 -0
  266. package/skills/report-craft/scripts/__pycache__/project_facts.cpython-312.pyc +0 -0
  267. package/skills/report-craft/scripts/chart_render.py +1222 -0
  268. package/skills/report-craft/scripts/fin_model.py +602 -0
  269. package/skills/report-craft/scripts/project_facts.py +580 -0
  270. package/skills/report-craft/scripts/report_battery.py +349 -0
  271. package/skills/report-craft/trigger-cases.json +15 -0
  272. package/skills/retail-store-ops/trigger-cases.json +30 -0
  273. package/skills/risk-compliance/trigger-cases.json +31 -0
  274. package/skills/scope-creep/SKILL.md +45 -0
  275. package/skills/scope-creep/trigger-cases.json +7 -0
  276. package/skills/sec-auth/trigger-cases.json +31 -0
  277. package/skills/sec-compliance/trigger-cases.json +31 -0
  278. package/skills/securities-chain/trigger-cases.json +31 -0
  279. package/skills/securities-research/trigger-cases.json +31 -0
  280. package/skills/security-audit/trigger-cases.json +31 -0
  281. package/skills/slides-html/SKILL.md +25 -10
  282. package/skills/slides-html/trigger-cases.json +11 -0
  283. package/skills/sql-analysis/SKILL.md +110 -0
  284. package/skills/sql-analysis/trigger-cases.json +34 -0
  285. package/skills/sql-tuning/trigger-cases.json +31 -0
  286. package/skills/statistical-testing/trigger-cases.json +31 -0
  287. package/skills/strategy-planning/trigger-cases.json +30 -0
  288. package/skills/supply-chain/trigger-cases.json +31 -0
  289. package/skills/tax-compliance/trigger-cases.json +30 -0
  290. package/skills/tech-doc/SKILL.md +262 -0
  291. package/skills/tech-doc/scripts/__pycache__/archive_battery.cpython-312.pyc +0 -0
  292. package/skills/tech-doc/scripts/__pycache__/archive_build.cpython-312.pyc +0 -0
  293. package/skills/tech-doc/scripts/__pycache__/archive_check.cpython-312.pyc +0 -0
  294. package/skills/tech-doc/scripts/__pycache__/archive_spec.cpython-312.pyc +0 -0
  295. package/skills/tech-doc/scripts/__pycache__/doc_templates.cpython-312.pyc +0 -0
  296. package/skills/tech-doc/scripts/archive_battery.py +860 -0
  297. package/skills/tech-doc/scripts/archive_build.py +599 -0
  298. package/skills/tech-doc/scripts/archive_check.py +1307 -0
  299. package/skills/tech-doc/scripts/archive_spec.py +499 -0
  300. package/skills/tech-doc/scripts/doc_templates.py +624 -0
  301. package/skills/tech-doc/trigger-cases.json +57 -0
  302. package/skills/tech-research/trigger-cases.json +31 -0
  303. package/skills/tender-bidding/trigger-cases.json +31 -0
  304. package/skills/translation-pro/trigger-cases.json +30 -0
  305. package/skills/treasury-mgmt/trigger-cases.json +31 -0
  306. package/skills/trim-cot-leakage/SKILL.md +58 -0
  307. package/skills/trim-cot-leakage/trigger-cases.json +23 -0
  308. package/skills/web-craft/SKILL.md +103 -0
  309. package/skills/web-craft/references/craft-checklist.md +75 -0
  310. package/skills/web-craft/references/design-direction.md +122 -0
  311. package/skills/web-craft/references/h5-mobile.md +100 -0
  312. package/skills/web-craft/references/motion.md +136 -0
  313. package/skills/web-craft/references/recipes.md +74 -0
  314. package/skills/web-craft/templates/wedding-invitation.html +1180 -0
  315. package/skills/web-craft/trigger-cases.json +14 -0
  316. package/skills/web-scraping/trigger-cases.json +34 -0
  317. package/skills/web-search/SKILL.md +11 -10
  318. package/skills/web-search/trigger-cases.json +31 -0
  319. package/skills/weekly-report/SKILL.md +134 -0
  320. package/skills/weekly-report/trigger-cases.json +11 -0
  321. package/skills/write-tests/trigger-cases.json +31 -0
  322. package/skills/xlsx-analysis/SKILL.md +143 -77
  323. package/skills/xlsx-analysis/scripts/__pycache__/data_profile.cpython-312.pyc +0 -0
  324. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_build.cpython-312.pyc +0 -0
  325. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_check.cpython-312.pyc +0 -0
  326. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_layout.cpython-312.pyc +0 -0
  327. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_toolkit.cpython-312.pyc +0 -0
  328. package/skills/xlsx-analysis/scripts/data_profile.py +391 -0
  329. package/skills/xlsx-analysis/scripts/xlsx_battery.py +389 -0
  330. package/skills/xlsx-analysis/scripts/xlsx_build.py +522 -0
  331. package/skills/xlsx-analysis/scripts/xlsx_check.py +924 -0
  332. package/skills/xlsx-analysis/scripts/xlsx_layout.py +235 -0
  333. package/skills/xlsx-analysis/scripts/xlsx_style.py +28 -66
  334. package/skills/xlsx-analysis/scripts/xlsx_toolkit.py +61 -5
  335. package/skills/xlsx-analysis/trigger-cases.json +59 -0
  336. package/squads/hc-defense.md +47 -0
  337. package/squads/hc-rdarchive.md +110 -0
  338. package/squads/hc-redteam.md +50 -0
  339. package/squads/hc-weekly.md +125 -0
  340. package/src/account.js +1 -1
  341. package/src/acp.js +1 -0
  342. package/src/advisor.js +1 -1
  343. package/src/agent.js +1 -1
  344. package/src/api.js +1 -1
  345. package/src/archaeology.js +1 -0
  346. package/src/ask-ui.js +1 -0
  347. package/src/assets-import.js +1 -1
  348. package/src/autofix.js +1 -1
  349. package/src/automation-tools.js +1 -1
  350. package/src/automation.js +1 -1
  351. package/src/autorun.js +1 -1
  352. package/src/banner.js +1 -1
  353. package/src/capability-probe.js +1 -0
  354. package/src/checkpoints.js +1 -1
  355. package/src/code-tools.js +1 -0
  356. package/src/codeindex.js +1 -1
  357. package/src/codemode.js +1 -0
  358. package/src/command-bridge.js +1 -0
  359. package/src/command-ui.js +1 -0
  360. package/src/commands.js +1 -1
  361. package/src/compaction.js +1 -1
  362. package/src/computer.js +1 -1
  363. package/src/config.js +1 -1
  364. package/src/consult.js +1 -1
  365. package/src/context-report.js +1 -1
  366. package/src/context-window.js +1 -0
  367. package/src/context.js +1 -1
  368. package/src/crew.js +1 -0
  369. package/src/crush.js +1 -0
  370. package/src/daemon.js +1 -1
  371. package/src/decisions.js +1 -1
  372. package/src/depaudit.js +1 -0
  373. package/src/device.js +1 -1
  374. package/src/diagram-draw.js +1 -0
  375. package/src/diagram-layouts.js +1 -0
  376. package/src/diagram-raster.js +1 -0
  377. package/src/diagram.js +1 -0
  378. package/src/dictation.js +1 -0
  379. package/src/diffview.js +1 -1
  380. package/src/doctor.js +1 -1
  381. package/src/docx-tools.js +1 -0
  382. package/src/engineering.js +1 -0
  383. package/src/errkind.js +1 -1
  384. package/src/evidence.js +1 -0
  385. package/src/executors.js +1 -1
  386. package/src/expert-catalog.js +1 -1
  387. package/src/experts-store.js +1 -1
  388. package/src/experts.js +1 -1
  389. package/src/external-agents.js +1 -0
  390. package/src/forks.js +1 -1
  391. package/src/fs-observe.js +1 -0
  392. package/src/gitflow.js +1 -1
  393. package/src/goalround.js +1 -0
  394. package/src/hooks.js +1 -1
  395. package/src/ide.js +1 -1
  396. package/src/input.js +1 -1
  397. package/src/insights.js +1 -1
  398. package/src/jobs.js +1 -0
  399. package/src/kbsync.js +1 -1
  400. package/src/lsp.js +1 -0
  401. package/src/mcp-apps.js +1 -0
  402. package/src/mcp.js +1 -1
  403. package/src/memory.js +1 -1
  404. package/src/notify.js +1 -1
  405. package/src/permissions.js +1 -1
  406. package/src/plan-limits.js +1 -0
  407. package/src/policy.js +1 -0
  408. package/src/pptx-tools.js +1 -0
  409. package/src/presence.js +1 -1
  410. package/src/privatedir.js +1 -0
  411. package/src/pyruntime.js +1 -1
  412. package/src/radar.js +1 -0
  413. package/src/render.js +1 -1
  414. package/src/repl.js +1 -1
  415. package/src/report-tools.js +1 -0
  416. package/src/routing.js +1 -0
  417. package/src/sandbox-probe.js +1 -0
  418. package/src/sandbox-win-launcher.mjs +156 -0
  419. package/src/sandbox.js +1 -0
  420. package/src/scan.js +1 -1
  421. package/src/schedule.js +1 -1
  422. package/src/scopecreep.js +1 -0
  423. package/src/sdk.js +1 -0
  424. package/src/selection.js +1 -0
  425. package/src/session-events.js +1 -0
  426. package/src/session.js +1 -1
  427. package/src/sessionindex.js +1 -0
  428. package/src/sessionlog.js +1 -0
  429. package/src/skill-catalog.js +1 -0
  430. package/src/skill-lint.js +1 -0
  431. package/src/skill-optimize.js +1 -0
  432. package/src/skill-scan.js +1 -0
  433. package/src/skills.js +1 -1
  434. package/src/spill.js +1 -0
  435. package/src/squad.js +1 -1
  436. package/src/subagent.js +1 -1
  437. package/src/surface-state.js +1 -0
  438. package/src/tapd.js +1 -0
  439. package/src/terminal-select.js +1 -0
  440. package/src/terminal.js +1 -0
  441. package/src/test-runner.js +1 -0
  442. package/src/todo.js +1 -1
  443. package/src/toolpipe.js +1 -0
  444. package/src/tools.js +1 -1
  445. package/src/toon.js +1 -0
  446. package/src/transcript.js +1 -1
  447. package/src/tui/app.js +1 -1
  448. package/src/tui/command-menu.js +1 -0
  449. package/src/tui/progress.js +1 -0
  450. package/src/tui/scroll.js +1 -0
  451. package/src/tui/welcome.js +1 -0
  452. package/src/tui/workbench.js +1 -0
  453. package/src/ui-spec.js +1 -0
  454. package/src/uitree.js +1 -0
  455. package/src/uninstall.js +1 -1
  456. package/src/vision.js +1 -1
  457. package/src/visual.js +1 -1
  458. package/src/voice-form.js +1 -0
  459. package/src/voice.js +1 -0
  460. package/src/watch.js +1 -1
  461. package/src/web-audit.js +1 -0
  462. package/src/web-cdp.js +1 -0
  463. package/src/web-inspect.js +1 -0
  464. package/src/websearch.js +1 -0
  465. package/src/weekly.js +1 -0
  466. package/src/workflow.js +1 -0
  467. package/src/worklog.js +1 -0
  468. package/src/xlsx-tools.js +1 -0
  469. package/ui/command-picker.js +79 -0
  470. package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
  471. package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
  472. package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
  473. package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
  474. package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
  475. package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
  476. package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
  477. package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
  478. package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
  479. package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
  480. package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
  481. package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
  482. package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
  483. package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
  484. package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
  485. package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
  486. package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
  487. package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
  488. package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
  489. package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
  490. package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
  491. package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
  492. package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
  493. package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -0
@@ -0,0 +1,391 @@
1
+ # 数据体检:分析动手之前,先把这份数据「哪里不能信」查清楚。
2
+ #
3
+ # 为什么必须有:数据分析出错误答案,绝大多数不是算法错,是**读进来的东西不是你以为的东西**——
4
+ # 金额是文本(SUM 得 0)、订单号被读成数字(前导零没了)、日期是 Excel 序列号、
5
+ # 明细里混着「合计」行(一汇总就翻倍)、两个「北京」差一个空格没合并、主键其实不唯一。
6
+ # 这些在代码里都不报错,只是把数字算错,而算错的数字看起来和算对的一模一样。
7
+ #
8
+ # 与 xlsx_toolkit 的分工:那边管**把脏表读进来**(表头识别、金额救回、日期归一),
9
+ # 这边管**读进来之后告诉你它还有哪里不对**。先 smart_read,再 profile。
10
+ #
11
+ # 用法:
12
+ # import data_profile as P
13
+ # P.report(P.profile(df, '应收明细')) # 单表体检
14
+ # P.report_file('收款单.xlsx') # 整本工作簿逐 Sheet 体检
15
+ # 或 CLI:python data_profile.py 数据.xlsx [--sheet 名] [--json]
16
+
17
+ import json
18
+ import re
19
+ import sys
20
+ import warnings
21
+ from collections import Counter
22
+
23
+ warnings.simplefilter('ignore')
24
+ import numpy as np # noqa: E402
25
+ import pandas as pd # noqa: E402
26
+
27
+ ID_HINTS = ('编码', '编号', '代码', '号', 'id', 'code', 'no', '卡号', '工号', '身份证', '邮编')
28
+ AMOUNT_HINTS = ('金额', '贷方', '借方', '余额', '税额', '价税', '收入', '成本', '费用',
29
+ '单价', '工资', '总额', '应收', '应付', '预收', '预付', '销售额', '利润')
30
+ DATE_HINTS = ('日期', '时间', '月份', '期间', '账期', '年月')
31
+ SUBTOTAL_MARKS = ('合计', '小计', '总计', '合 计', '小 计', '总 计', '本页合计', '累计', 'total', 'subtotal')
32
+ INVISIBLE = '  ​\t\r\n'
33
+ NUM_RE = re.compile(r'^\s*[((]?\s*[-+]?[¥¥$]?\s*[\d,,]+(\.\d+)?\s*[%元]?\s*[))]?\s*$')
34
+
35
+
36
+ def _issue(level, kind, where, msg, detail='', suggest=''):
37
+ return {'level': level, 'kind': kind, 'where': where, 'msg': msg,
38
+ 'detail': detail, 'suggest': suggest}
39
+
40
+
41
+ def _sample(s, n=3):
42
+ v = s.dropna()
43
+ if v.empty:
44
+ return []
45
+ return [str(x)[:24] for x in v.head(n).tolist()]
46
+
47
+
48
+ def _looks_number(x):
49
+ if isinstance(x, (int, float, np.number)) and not isinstance(x, bool):
50
+ return True
51
+ return bool(NUM_RE.match(str(x)))
52
+
53
+
54
+ def _looks_date(x):
55
+ s = str(x).strip()
56
+ if re.fullmatch(r'\d{4}[-/.年]\d{1,2}([-/.月]\d{1,2}日?)?', s):
57
+ return True
58
+ return bool(re.fullmatch(r'\d{4}[-/]\d{1,2}[-/]\d{1,2}[ T].*', s))
59
+
60
+
61
+ def _norm_cat(x):
62
+ """分类值归一:去空白与不可见字符、全半角、大小写——用于找「同物异名」。"""
63
+ t = str(x)
64
+ for ch in INVISIBLE:
65
+ t = t.replace(ch, '')
66
+ return t.strip().lower().replace('(', '(').replace(')', ')')
67
+
68
+
69
+ # ── 单列体检 ───────────────────────────────────────────────────────────────
70
+ def profile_column(s, name, n_rows):
71
+ col = {'name': name, 'dtype': str(s.dtype), 'nulls': int(s.isna().sum()),
72
+ 'null_rate': round(float(s.isna().mean()), 4) if n_rows else 0.0,
73
+ 'unique': int(s.nunique(dropna=True)), 'sample': _sample(s)}
74
+ issues = []
75
+ nn = s.dropna()
76
+ col['inferred'] = 'empty'
77
+ if nn.empty:
78
+ issues.append(_issue('warn', 'empty-col', name, '整列为空',
79
+ '', '确认是否漏读了列,或直接删掉这一列'))
80
+ return col, issues
81
+
82
+ is_obj = s.dtype == object or pd.api.types.is_string_dtype(s)
83
+ if pd.api.types.is_numeric_dtype(s) and not pd.api.types.is_bool_dtype(s):
84
+ col['inferred'] = 'number'
85
+ elif pd.api.types.is_datetime64_any_dtype(s):
86
+ col['inferred'] = 'date'
87
+ elif pd.api.types.is_bool_dtype(s):
88
+ col['inferred'] = 'bool'
89
+ else:
90
+ col['inferred'] = 'text'
91
+
92
+ if is_obj:
93
+ # **按值的真实类型判,不按列 dtype 判**:pandas 用 dtype=object 读进来的列里
94
+ # 装的往往是货真价实的 int/float(smart_read 就是这么读的)。
95
+ # 早先按 dtype 判,这类列一律被判成「数值被存成文本」——在最常用的读表路径上
96
+ # 大面积误报,而误报比漏报更快让人关掉检查。
97
+ head = nn.head(500)
98
+ n = len(head)
99
+ strs = [v for v in head if isinstance(v, str)]
100
+ reals = [v for v in head if isinstance(v, (int, float, np.number)) and not isinstance(v, bool)]
101
+ num_like = sum(1 for v in strs if NUM_RE.match(v)) / max(n, 1)
102
+ date_like = sum(1 for v in strs if _looks_date(v)) / max(n, 1)
103
+ if reals and len(reals) >= n * 0.9:
104
+ col['inferred'] = 'number' # object 列,但装的是真数字
105
+ # ① 数字存成了文本——SUM 会得 0,是「对账对不平」的头号原因
106
+ if num_like >= 0.9 and not any(k in str(name) for k in ID_HINTS):
107
+ col['inferred'] = 'number-as-text'
108
+ issues.append(_issue('error', 'num-as-text', name,
109
+ f'数值被存成文本({num_like*100:.0f}% 的值形如数字)',
110
+ f'示例 {col["sample"]}',
111
+ '用 to_amount() 转成 float 再算;直接 sum 会得 0 或字符串拼接'))
112
+ elif date_like >= 0.9:
113
+ col['inferred'] = 'date-as-text'
114
+ issues.append(_issue('warn', 'date-as-text', name,
115
+ f'日期被存成文本({date_like*100:.0f}% 形如日期)',
116
+ f'示例 {col["sample"]}', '用 to_period()/to_datetime() 转换后再排序或分组'))
117
+ # ② 一列里数字与文本混——排序、分组、比较全都会出鬼
118
+ elif reals and strs and 0.15 < len(strs) / max(n, 1) < 0.85:
119
+ issues.append(_issue('warn', 'mixed-type', name,
120
+ f'同一列里真数字与文本混杂({len(reals)} 数字 / {len(strs)} 文本)',
121
+ f'示例 {col["sample"]}', '先统一类型:要么全转数字,要么全按文本处理'))
122
+ # ③ 首尾空白与不可见字符——「北京 」和「北京」分组会分成两组
123
+ dirty = int(sum(v != v.strip() or any(ch in v for ch in INVISIBLE)
124
+ for v in nn.head(2000) if isinstance(v, str)))
125
+ if dirty:
126
+ issues.append(_issue('warn', 'whitespace', name,
127
+ f'{dirty} 个值带首尾空白或不可见字符(含全角空格/换行)',
128
+ '', '.str.strip() 并清掉 \\u00a0/\\u3000,否则分组会分成两组'))
129
+ # ④ 同物异名:归一化后能合并的组
130
+ if 1 < col['unique'] <= 2000 and col['inferred'] not in ('number', 'number-as-text'):
131
+ groups = Counter(_norm_cat(v) for v in nn.head(2000))
132
+ merged = [k for k, c in groups.items() if c > 0]
133
+ if len(merged) < col['unique']:
134
+ issues.append(_issue('warn', 'alias', name,
135
+ f'归一化后取值从 {col["unique"]} 个降到 {len(merged)} 个,存在同物异名',
136
+ '', '先 normalize_key() 再分组/关联,否则会算成不同的实体'))
137
+ # ⑤ ID 列被读成数字 → 前导零丢了、超 15 位精度丢了
138
+ if col['inferred'] == 'number' and any(k in str(name).lower() for k in ID_HINTS):
139
+ mx = float(pd.to_numeric(nn, errors='coerce').abs().max() or 0)
140
+ issues.append(_issue('warn' if mx < 1e15 else 'error', 'id-as-number', name,
141
+ '编号类列被读成了数字',
142
+ f'最大值 {mx:.0f}' + ('(已超 15 位,末位精度可能已丢)' if mx >= 1e15 else ''),
143
+ 'read_excel(dtype={"%s": str}),否则前导零会消失、长号会变科学计数法' % name))
144
+
145
+ if col['inferred'] == 'number':
146
+ v = pd.to_numeric(nn, errors='coerce').dropna()
147
+ if len(v):
148
+ q1, q3 = float(v.quantile(.25)), float(v.quantile(.75))
149
+ iqr = q3 - q1
150
+ out = int(((v < q1 - 3 * iqr) | (v > q3 + 3 * iqr)).sum()) if iqr > 0 else 0
151
+ col['stats'] = {'min': float(v.min()), 'max': float(v.max()),
152
+ 'mean': round(float(v.mean()), 4), 'median': float(v.median()),
153
+ 'zeros': int((v == 0).sum()), 'negatives': int((v < 0).sum()),
154
+ 'outliers': out}
155
+ if out and out / len(v) < 0.05:
156
+ issues.append(_issue('info', 'outlier', name,
157
+ f'{out} 个极端值(超出 3 倍四分位距)',
158
+ f'范围 {v.min():.2f} ~ {v.max():.2f}', '确认是真实极值还是录入错误'))
159
+ if col['inferred'] == 'date':
160
+ v = nn
161
+ try:
162
+ col['stats'] = {'min': str(v.min())[:10], 'max': str(v.max())[:10]}
163
+ future = int((v > pd.Timestamp.now()).sum())
164
+ if future:
165
+ issues.append(_issue('warn', 'future-date', name,
166
+ f'{future} 个未来日期', f'最大 {str(v.max())[:10]}',
167
+ '确认是计划日期还是录入错误'))
168
+ months = pd.Series(v).dt.to_period('M').nunique()
169
+ span = (v.max() - v.min()).days if len(v) > 1 else 0
170
+ if span > 62 and months and months < span / 31 * 0.7:
171
+ issues.append(_issue('info', 'gap-month', name,
172
+ f'时间跨度 {span} 天但只覆盖 {months} 个月,中间有断月',
173
+ '', '按月汇总时缺月会被跳过,画趋势图要先 reindex 补齐'))
174
+ except Exception:
175
+ pass
176
+ return col, issues
177
+
178
+
179
+ # ── 整表体检 ───────────────────────────────────────────────────────────────
180
+ def profile(df, name='', key_hint=None):
181
+ """单表体检。返回 {name, shape, columns[], issues[], keys[]}。"""
182
+ n_rows, n_cols = df.shape
183
+ res = {'name': name, 'rows': int(n_rows), 'cols': int(n_cols),
184
+ 'memory_mb': round(float(df.memory_usage(deep=True).sum()) / 1048576, 2),
185
+ 'columns': [], 'issues': [], 'keys': []}
186
+ if n_rows == 0:
187
+ res['issues'].append(_issue('error', 'empty', name or '表', '表里没有数据行',
188
+ '', '确认表头行是否识别对了(smart_read 可手动传 header=N)'))
189
+ return res
190
+ # 列名问题
191
+ names = [str(c) for c in df.columns]
192
+ unnamed = [c for c in names if c.lower().startswith('unnamed') or re.fullmatch(r'列\d+', c)]
193
+ if unnamed:
194
+ res['issues'].append(_issue('warn', 'unnamed-col', '列名',
195
+ f'{len(unnamed)} 个列没有名字:{unnamed[:5]}',
196
+ '', '多半是表头行认错了——用 detect_header 复核,或手动指定 header=N'))
197
+ dup = [c for c, k in Counter(names).items() if k > 1]
198
+ if dup:
199
+ res['issues'].append(_issue('error', 'dup-col', '列名', f'重名列:{dup[:5]}',
200
+ '', '重名列取值时会拿到 DataFrame 而不是 Series,后续全错'))
201
+ messy = [c for c in names if c != c.strip() or any(ch in c for ch in INVISIBLE)]
202
+ if messy:
203
+ res['issues'].append(_issue('warn', 'messy-col', '列名',
204
+ f'{len(messy)} 个列名带空白/换行:{messy[:3]}',
205
+ '', 'df.columns = df.columns.str.strip(),否则 df["销售额"] 会 KeyError'))
206
+
207
+ # 重复行
208
+ try:
209
+ dups = int(df.duplicated().sum())
210
+ if dups:
211
+ res['issues'].append(_issue('warn', 'dup-row', '全表',
212
+ f'{dups} 行完全重复(占 {dups/n_rows*100:.1f}%)',
213
+ '', '确认是真实重复业务还是导出重复;汇总前要不要去重'))
214
+ except Exception:
215
+ pass
216
+
217
+ # 「合计」行混在明细里——一汇总就双计,是最隐蔽的错误来源
218
+ sub = _subtotal_rows(df)
219
+ if sub:
220
+ res['issues'].append(_issue('error', 'subtotal-row', '全表',
221
+ f'{len(sub)} 行疑似「合计/小计」混在明细中(行号 {sub[:6]})',
222
+ '', '分析前剔除,否则求和会翻倍;合计用 pivot_table(margins=True) 自己生成'))
223
+
224
+ # 全空行/列
225
+ blank_rows = int(df.isna().all(axis=1).sum())
226
+ if blank_rows:
227
+ res['issues'].append(_issue('info', 'blank-row', '全表', f'{blank_rows} 行整行为空',
228
+ '', 'dropna(how="all") 清掉'))
229
+
230
+ # 主键候选:先单列,再两列组合(组合只在单列都不唯一时找,且列数不多时才试)
231
+ res['keys'] = _find_keys(df, key_hint)
232
+ if not res['keys']:
233
+ res['issues'].append(_issue('info', 'no-key', '全表',
234
+ '没有找到唯一标识列(单列或两列组合)',
235
+ '', '关联前要确认粒度:主键不唯一时 merge 会产生笛卡尔积把行数放大'))
236
+
237
+ for c in df.columns:
238
+ col, iss = profile_column(df[c], str(c), n_rows)
239
+ res['columns'].append(col)
240
+ res['issues'].extend(iss)
241
+ return res
242
+
243
+
244
+ def _subtotal_rows(df, max_scan=20000):
245
+ hits = []
246
+ for c in df.columns:
247
+ s = df[c]
248
+ if s.dtype != object and not pd.api.types.is_string_dtype(s):
249
+ continue
250
+ # .str 访问器会把 NaN 原样传下去(不是 'nan'),所以必须 fillna 后再判,
251
+ # 否则 lambda 会收到 float nan 直接 TypeError
252
+ v = s.head(max_scan).astype(str).str.strip().str.lower().fillna('')
253
+ m = v.apply(lambda x: bool(x) and len(x) <= 12 and any(k in x for k in SUBTOTAL_MARKS))
254
+ hits.extend(list(df.index[:len(m)][m.fillna(False)]))
255
+ return sorted(set(int(i) for i in hits if isinstance(i, (int, np.integer))))[:50]
256
+
257
+
258
+ def _find_keys(df, key_hint=None, max_cols=12):
259
+ n = len(df)
260
+ single = [str(c) for c in df.columns
261
+ if df[c].notna().all() and df[c].nunique(dropna=True) == n]
262
+ if single:
263
+ return single[:3]
264
+ cand = [c for c in df.columns if df[c].notna().all() and 1 < df[c].nunique(dropna=True) < n]
265
+ if key_hint:
266
+ cand = [c for c in cand if str(c) in key_hint] or cand
267
+ cand = cand[:max_cols]
268
+ for i, a in enumerate(cand):
269
+ for b in cand[i + 1:]:
270
+ try:
271
+ if not df.duplicated(subset=[a, b]).any():
272
+ return [f'{a} + {b}']
273
+ except Exception:
274
+ continue
275
+ return []
276
+
277
+
278
+ def profile_file(path, sheet=None, smart=True):
279
+ """整本工作簿逐 Sheet 体检。smart=True 时优先用 xlsx_toolkit 的稳读(治表头不在首行)。"""
280
+ reader = None
281
+ if smart:
282
+ try:
283
+ import os
284
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
285
+ import xlsx_toolkit as T
286
+ reader = T
287
+ except Exception:
288
+ reader = None
289
+ out = []
290
+ if str(path).lower().endswith('.csv'):
291
+ df = pd.read_csv(path, dtype=object, encoding='utf-8-sig') # 吃掉 BOM,否则首列名带 \ufeff
292
+ out.append(profile(df, '(csv)'))
293
+ return out
294
+ xf = None
295
+ if reader is not None:
296
+ xf, why = reader.open_workbook(path)
297
+ if xf is None:
298
+ return [{'name': str(path), 'rows': 0, 'cols': 0, 'columns': [], 'keys': [],
299
+ 'issues': [_issue('error', 'unreadable', str(path), f'打不开:{why}')]}]
300
+ else:
301
+ xf = pd.ExcelFile(path)
302
+ names = [sheet] if sheet else xf.sheet_names
303
+ for s in names:
304
+ try:
305
+ df = reader.smart_read(path, s) if reader is not None else xf.parse(s)
306
+ except Exception as e:
307
+ out.append({'name': s, 'rows': 0, 'cols': 0, 'columns': [], 'keys': [],
308
+ 'issues': [_issue('error', 'unreadable', s, f'读取失败:{type(e).__name__}: {e}')]})
309
+ continue
310
+ out.append(profile(df, str(s)))
311
+ return out
312
+
313
+
314
+ # ── 报告 ───────────────────────────────────────────────────────────────────
315
+ LEVEL = {'error': '必修', 'warn': '建议', 'info': '提示'}
316
+ ORDER = {'error': 0, 'warn': 1, 'info': 2}
317
+
318
+
319
+ def report(res, max_cols=40):
320
+ if isinstance(res, list):
321
+ return '\n\n'.join(report(r, max_cols) for r in res)
322
+ lines = [f"【数据体检】{res.get('name') or '表'} · {res['rows']} 行 × {res['cols']} 列"
323
+ + (f" · {res['memory_mb']}MB" if res.get('memory_mb') else '')]
324
+ if res.get('keys'):
325
+ lines.append(f" 唯一标识:{'、'.join(res['keys'])}")
326
+ cnt = Counter(i['level'] for i in res['issues'])
327
+ lines.append(f" 结论:必修 {cnt.get('error',0)} · 建议 {cnt.get('warn',0)} · 提示 {cnt.get('info',0)}")
328
+ for it in sorted(res['issues'], key=lambda x: ORDER[x['level']]):
329
+ tail = f" — {it['detail']}" if it['detail'] else ''
330
+ lines.append(f" [{LEVEL[it['level']]}] {it['where']}:{it['msg']}{tail}")
331
+ if it['suggest']:
332
+ lines.append(f" → {it['suggest']}")
333
+ if res['columns']:
334
+ lines.append(' 列清单(类型 / 缺失 / 唯一值 / 示例):')
335
+ for c in res['columns'][:max_cols]:
336
+ st = c.get('stats') or {}
337
+ extra = ''
338
+ if 'min' in st and 'mean' in st:
339
+ extra = f" | {st['min']:g}~{st['max']:g} 均{st['mean']:g}"
340
+ elif 'min' in st:
341
+ extra = f" | {st['min']}~{st['max']}"
342
+ lines.append(f" · {c['name'][:22]:<22} {c['inferred']:<15} "
343
+ f"缺{c['null_rate']*100:.0f}% 唯{c['unique']}{extra} {c['sample'][:2]}")
344
+ if len(res['columns']) > max_cols:
345
+ lines.append(f" · …另有 {len(res['columns']) - max_cols} 列")
346
+ return '\n'.join(lines)
347
+
348
+
349
+ def self_test():
350
+ df = pd.DataFrame({
351
+ '订单编号': ['001', '002', '003', '004', '合计'],
352
+ '客户': ['北京公司', '北京公司 ', '上海公司', '上海公司', ''],
353
+ '金额': ['1,200.00', '3,400.50', '2,000', '900', '7,500.50'],
354
+ '数量': [1, 2, 3, 4, 10],
355
+ '备注': [None, None, None, None, None],
356
+ })
357
+ r = profile(df, '订单明细')
358
+ ks = {i['kind'] for i in r['issues']}
359
+ assert 'num-as-text' in ks, ks # 金额是带千分位的文本
360
+ assert 'subtotal-row' in ks, ks # 「合计」行混在明细里
361
+ assert 'whitespace' in ks or 'alias' in ks, ks # 「北京公司 」带尾空格
362
+ assert 'empty-col' in ks, ks # 备注整列空
363
+ obj_int = pd.DataFrame({'序列': pd.Series([1, 2, 3, 4], dtype=object),
364
+ '名称': ['甲', '乙', '丙', '丁']})
365
+ ks_obj = {i['kind'] for i in profile(obj_int, 'object列真数字')['issues']}
366
+ assert 'num-as-text' not in ks_obj, ks_obj
367
+ clean = pd.DataFrame({'编号': ['A1', 'A2', 'A3'], '销售额': [1.0, 2.0, 3.0],
368
+ '日期': pd.to_datetime(['2026-01-01', '2026-02-01', '2026-03-01'])})
369
+ r2 = profile(clean, '干净表')
370
+ assert not [i for i in r2['issues'] if i['level'] == 'error'], r2['issues']
371
+ assert r2['keys'], '应识别出「编号」是唯一标识'
372
+ assert [c for c in r2['columns'] if c['name'] == '销售额'][0]['inferred'] == 'number'
373
+ print(f"[data_profile] self-test OK · 脏表命中 {len(ks)} 类、干净表零必修")
374
+
375
+
376
+ def main(argv):
377
+ args = [a for a in argv if not a.startswith('--')]
378
+ if not args:
379
+ self_test()
380
+ return 0
381
+ sheet = None
382
+ for a in argv:
383
+ if a.startswith('--sheet='):
384
+ sheet = a.split('=', 1)[1]
385
+ res = profile_file(args[0], sheet)
386
+ print(json.dumps(res, ensure_ascii=False, default=str) if '--json' in argv else report(res))
387
+ return 0
388
+
389
+
390
+ if __name__ == '__main__':
391
+ sys.exit(main(sys.argv[1:]))