@huicai/cli 2026.814.0 → 2026.1019.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 (485) hide show
  1. package/LICENSE +391 -32
  2. package/README.md +137 -8
  3. package/bin/huicai.js +146 -73
  4. package/package.json +27 -8
  5. package/skills/ab-testing/trigger-cases.json +30 -0
  6. package/skills/academic-writing/trigger-cases.json +30 -0
  7. package/skills/accounting-ops/trigger-cases.json +30 -0
  8. package/skills/ai-agent/trigger-cases.json +30 -0
  9. package/skills/ai-aiops/trigger-cases.json +30 -0
  10. package/skills/ai-coding/trigger-cases.json +30 -0
  11. package/skills/ai-compliance/trigger-cases.json +30 -0
  12. package/skills/ai-eval/trigger-cases.json +30 -0
  13. package/skills/ai-llm-app/trigger-cases.json +30 -0
  14. package/skills/ai-mlops/trigger-cases.json +30 -0
  15. package/skills/ai-multimodal/trigger-cases.json +30 -0
  16. package/skills/ai-prompt/trigger-cases.json +30 -0
  17. package/skills/ai-rag/trigger-cases.json +30 -0
  18. package/skills/anomaly-detection/trigger-cases.json +30 -0
  19. package/skills/api-design/trigger-cases.json +31 -0
  20. package/skills/api-integration/trigger-cases.json +31 -0
  21. package/skills/arch-design/trigger-cases.json +12 -0
  22. package/skills/arch-diagram/SKILL.md +118 -0
  23. package/skills/arch-diagram/trigger-cases.json +30 -0
  24. package/skills/arch-distributed/trigger-cases.json +30 -0
  25. package/skills/arch-multitenant/trigger-cases.json +30 -0
  26. package/skills/audio-brief/SKILL.md +65 -0
  27. package/skills/audio-brief/trigger-cases.json +30 -0
  28. package/skills/audit-assist/trigger-cases.json +30 -0
  29. package/skills/audit-chain/trigger-cases.json +31 -0
  30. package/skills/banking-credit/trigger-cases.json +30 -0
  31. package/skills/brand-design/trigger-cases.json +30 -0
  32. package/skills/business-plan/trigger-cases.json +30 -0
  33. package/skills/capital-ops/trigger-cases.json +30 -0
  34. package/skills/cicd-pipeline/trigger-cases.json +31 -0
  35. package/skills/code-archaeology/SKILL.md +55 -0
  36. package/skills/code-archaeology/trigger-cases.json +9 -0
  37. package/skills/code-quality/SKILL.md +138 -0
  38. package/skills/code-quality/references/languages.md +107 -0
  39. package/skills/code-quality/scripts/__pycache__/code_battery.cpython-312.pyc +0 -0
  40. package/skills/code-quality/scripts/__pycache__/code_check.cpython-312.pyc +0 -0
  41. package/skills/code-quality/scripts/__pycache__/code_check.cpython-313.pyc +0 -0
  42. package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-312.pyc +0 -0
  43. package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-313.pyc +0 -0
  44. package/skills/code-quality/scripts/__pycache__/code_fix.cpython-312.pyc +0 -0
  45. package/skills/code-quality/scripts/__pycache__/code_fix.cpython-313.pyc +0 -0
  46. package/skills/code-quality/scripts/__pycache__/code_java.cpython-312.pyc +0 -0
  47. package/skills/code-quality/scripts/__pycache__/code_java.cpython-313.pyc +0 -0
  48. package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-312.pyc +0 -0
  49. package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-313.pyc +0 -0
  50. package/skills/code-quality/scripts/__pycache__/code_lens.cpython-312.pyc +0 -0
  51. package/skills/code-quality/scripts/__pycache__/code_lens.cpython-313.pyc +0 -0
  52. package/skills/code-quality/scripts/__pycache__/code_python.cpython-312.pyc +0 -0
  53. package/skills/code-quality/scripts/__pycache__/code_python.cpython-313.pyc +0 -0
  54. package/skills/code-quality/scripts/__pycache__/code_testplan.cpython-312.pyc +0 -0
  55. package/skills/code-quality/scripts/code_battery.py +1072 -0
  56. package/skills/code-quality/scripts/code_check.py +1269 -0
  57. package/skills/code-quality/scripts/code_csharp.py +371 -0
  58. package/skills/code-quality/scripts/code_fix.py +201 -0
  59. package/skills/code-quality/scripts/code_java.py +695 -0
  60. package/skills/code-quality/scripts/code_jsts.py +313 -0
  61. package/skills/code-quality/scripts/code_lens.py +1079 -0
  62. package/skills/code-quality/scripts/code_python.py +536 -0
  63. package/skills/code-quality/scripts/code_testplan.py +239 -0
  64. package/skills/code-quality/trigger-cases.json +45 -0
  65. package/skills/code-review/SKILL.md +24 -0
  66. package/skills/code-review/trigger-cases.json +35 -0
  67. package/skills/commit-and-pr/trigger-cases.json +31 -0
  68. package/skills/content-creation/trigger-cases.json +30 -0
  69. package/skills/corporate-governance/trigger-cases.json +30 -0
  70. package/skills/crawl-chain/trigger-cases.json +31 -0
  71. package/skills/customer-service/trigger-cases.json +30 -0
  72. package/skills/data-analysis/trigger-cases.json +30 -0
  73. package/skills/data-chain/SKILL.md +1 -1
  74. package/skills/data-chain/trigger-cases.json +31 -0
  75. package/skills/data-governance/trigger-cases.json +30 -0
  76. package/skills/data-report-pro/trigger-cases.json +30 -0
  77. package/skills/data-streaming/trigger-cases.json +30 -0
  78. package/skills/data-wrangling/SKILL.md +1 -1
  79. package/skills/data-wrangling/trigger-cases.json +30 -0
  80. package/skills/db-es/trigger-cases.json +30 -0
  81. package/skills/db-migration/trigger-cases.json +30 -0
  82. package/skills/db-mysql/trigger-cases.json +30 -0
  83. package/skills/db-postgres/trigger-cases.json +30 -0
  84. package/skills/db-redis/trigger-cases.json +30 -0
  85. package/skills/debug-root-cause/trigger-cases.json +30 -0
  86. package/skills/dependency-audit/SKILL.md +56 -0
  87. package/skills/dependency-audit/trigger-cases.json +7 -0
  88. package/skills/dependency-upgrade/trigger-cases.json +31 -0
  89. package/skills/dev-cpp/trigger-cases.json +30 -0
  90. package/skills/dev-cross-platform/trigger-cases.json +30 -0
  91. package/skills/dev-csharp/SKILL.md +23 -0
  92. package/skills/dev-csharp/trigger-cases.json +30 -0
  93. package/skills/dev-frontend/SKILL.md +19 -0
  94. package/skills/dev-frontend/trigger-cases.json +30 -0
  95. package/skills/dev-go/trigger-cases.json +30 -0
  96. package/skills/dev-harmony/trigger-cases.json +30 -0
  97. package/skills/dev-java/SKILL.md +20 -0
  98. package/skills/dev-java/trigger-cases.json +30 -0
  99. package/skills/dev-mobile/trigger-cases.json +30 -0
  100. package/skills/dev-node/trigger-cases.json +30 -0
  101. package/skills/dev-php/trigger-cases.json +30 -0
  102. package/skills/dev-python/SKILL.md +21 -0
  103. package/skills/dev-python/trigger-cases.json +30 -0
  104. package/skills/dev-rust/trigger-cases.json +30 -0
  105. package/skills/dev-toolchain/trigger-cases.json +31 -0
  106. package/skills/doc-chain/trigger-cases.json +31 -0
  107. package/skills/doc-compare/trigger-cases.json +30 -0
  108. package/skills/docx-author/SKILL.md +127 -25
  109. package/skills/docx-author/scripts/__pycache__/docx_battery.cpython-312.pyc +0 -0
  110. package/skills/docx-author/scripts/__pycache__/docx_build.cpython-312.pyc +0 -0
  111. package/skills/docx-author/scripts/__pycache__/docx_check.cpython-312.pyc +0 -0
  112. package/skills/docx-author/scripts/__pycache__/docx_layout.cpython-312.pyc +0 -0
  113. package/skills/docx-author/scripts/__pycache__/zh_prose.cpython-312.pyc +0 -0
  114. package/skills/docx-author/scripts/docx_battery.py +586 -0
  115. package/skills/docx-author/scripts/docx_build.py +995 -0
  116. package/skills/docx-author/scripts/docx_check.py +1306 -0
  117. package/skills/docx-author/scripts/docx_doc.py +14 -154
  118. package/skills/docx-author/scripts/docx_layout.py +490 -0
  119. package/skills/docx-author/scripts/zh_prose.py +639 -0
  120. package/skills/docx-author/trigger-cases.json +58 -0
  121. package/skills/echo-brief/SKILL.md +84 -0
  122. package/skills/echo-brief/references/echo-format.md +68 -0
  123. package/skills/echo-brief/trigger-cases.json +40 -0
  124. package/skills/ecommerce-ops/trigger-cases.json +30 -0
  125. package/skills/education-training/trigger-cases.json +30 -0
  126. package/skills/ehs-safety/trigger-cases.json +30 -0
  127. package/skills/email-writing/trigger-cases.json +30 -0
  128. package/skills/eng-ai/trigger-cases.json +30 -0
  129. package/skills/eng-architecture/trigger-cases.json +31 -0
  130. package/skills/eng-automation/trigger-cases.json +31 -0
  131. package/skills/eng-collaboration/trigger-cases.json +30 -0
  132. package/skills/eng-complex-env/trigger-cases.json +30 -0
  133. package/skills/eng-data-platform/trigger-cases.json +31 -0
  134. package/skills/eng-database/trigger-cases.json +31 -0
  135. package/skills/eng-develop/trigger-cases.json +30 -0
  136. package/skills/eng-docs/SKILL.md +24 -0
  137. package/skills/eng-docs/trigger-cases.json +30 -0
  138. package/skills/eng-efficiency/trigger-cases.json +30 -0
  139. package/skills/eng-infra/trigger-cases.json +30 -0
  140. package/skills/eng-integration/trigger-cases.json +31 -0
  141. package/skills/eng-legacy/trigger-cases.json +31 -0
  142. package/skills/eng-observability/trigger-cases.json +31 -0
  143. package/skills/eng-ops/trigger-cases.json +31 -0
  144. package/skills/eng-performance/trigger-cases.json +31 -0
  145. package/skills/eng-planning/trigger-cases.json +31 -0
  146. package/skills/eng-quality/SKILL.md +8 -0
  147. package/skills/eng-quality/trigger-cases.json +31 -0
  148. package/skills/eng-release/trigger-cases.json +31 -0
  149. package/skills/eng-reliability/trigger-cases.json +31 -0
  150. package/skills/eng-requirements/trigger-cases.json +12 -0
  151. package/skills/eng-research/trigger-cases.json +30 -0
  152. package/skills/eng-security/trigger-cases.json +31 -0
  153. package/skills/eng-ux/trigger-cases.json +30 -0
  154. package/skills/engineering-chain/trigger-cases.json +30 -0
  155. package/skills/env-offline/trigger-cases.json +31 -0
  156. package/skills/env-private-deploy/trigger-cases.json +31 -0
  157. package/skills/env-xinchuang/trigger-cases.json +31 -0
  158. package/skills/equipment-tpm/trigger-cases.json +30 -0
  159. package/skills/esg-report/trigger-cases.json +30 -0
  160. package/skills/event-management/trigger-cases.json +30 -0
  161. package/skills/excel-analysis-chain/SKILL.md +17 -4
  162. package/skills/excel-analysis-chain/trigger-cases.json +31 -0
  163. package/skills/explain-codebase/trigger-cases.json +11 -0
  164. package/skills/file-ingest/trigger-cases.json +30 -0
  165. package/skills/finance-analysis/trigger-cases.json +31 -0
  166. package/skills/finance-chain/trigger-cases.json +31 -0
  167. package/skills/financial-modeling/trigger-cases.json +31 -0
  168. package/skills/find-simplifications/SKILL.md +43 -0
  169. package/skills/find-simplifications/trigger-cases.json +23 -0
  170. package/skills/foreign-trade/trigger-cases.json +30 -0
  171. package/skills/funnel-cohort/trigger-cases.json +30 -0
  172. package/skills/gov-document/trigger-cases.json +30 -0
  173. package/skills/gui-change-evidence/SKILL.md +47 -0
  174. package/skills/gui-change-evidence/trigger-cases.json +23 -0
  175. package/skills/hr-chain/trigger-cases.json +31 -0
  176. package/skills/hr-operations/trigger-cases.json +30 -0
  177. package/skills/info-disclosure/trigger-cases.json +31 -0
  178. package/skills/infra-gateway/trigger-cases.json +31 -0
  179. package/skills/infra-k8s/trigger-cases.json +31 -0
  180. package/skills/infra-mq/trigger-cases.json +30 -0
  181. package/skills/insurance-advisory/trigger-cases.json +30 -0
  182. package/skills/internal-audit/trigger-cases.json +30 -0
  183. package/skills/ip-management/trigger-cases.json +31 -0
  184. package/skills/ipo-listing/trigger-cases.json +31 -0
  185. package/skills/it-audit/trigger-cases.json +31 -0
  186. package/skills/itsm-ops/trigger-cases.json +31 -0
  187. package/skills/knowledge-sop/trigger-cases.json +31 -0
  188. package/skills/legal-chain/trigger-cases.json +31 -0
  189. package/skills/legal-contract/trigger-cases.json +30 -0
  190. package/skills/litigation-support/trigger-cases.json +31 -0
  191. package/skills/logistics-warehouse/trigger-cases.json +30 -0
  192. package/skills/long-doc-analysis/trigger-cases.json +31 -0
  193. package/skills/management-accounting/trigger-cases.json +30 -0
  194. package/skills/manufacturing-chain/trigger-cases.json +31 -0
  195. package/skills/manufacturing-quality/trigger-cases.json +30 -0
  196. package/skills/marketing-analytics/trigger-cases.json +31 -0
  197. package/skills/marketing-chain/trigger-cases.json +31 -0
  198. package/skills/meeting-minutes/trigger-cases.json +30 -0
  199. package/skills/metrics-system/trigger-cases.json +31 -0
  200. package/skills/negotiation-prep/trigger-cases.json +30 -0
  201. package/skills/obs-prometheus/trigger-cases.json +31 -0
  202. package/skills/office-automation/trigger-cases.json +31 -0
  203. package/skills/okr-review/trigger-cases.json +30 -0
  204. package/skills/ops-fleet/trigger-cases.json +31 -0
  205. package/skills/ops-linux/trigger-cases.json +31 -0
  206. package/skills/optimization-solver/trigger-cases.json +30 -0
  207. package/skills/patent-disclosure/SKILL.md +105 -0
  208. package/skills/patent-disclosure/references/figures.md +110 -0
  209. package/skills/patent-disclosure/references/outline.md +162 -0
  210. package/skills/patent-disclosure/scripts/patent_docx.py +230 -0
  211. package/skills/patent-disclosure/trigger-cases.json +31 -0
  212. package/skills/patent-drafting/SKILL.md +108 -0
  213. package/skills/patent-drafting/trigger-cases.json +31 -0
  214. package/skills/patent-portfolio/SKILL.md +81 -0
  215. package/skills/patent-portfolio/trigger-cases.json +31 -0
  216. package/skills/pdf-toolkit/trigger-cases.json +31 -0
  217. package/skills/perf-optimize/trigger-cases.json +31 -0
  218. package/skills/pivot-crosstab/trigger-cases.json +30 -0
  219. package/skills/policy-analysis/trigger-cases.json +31 -0
  220. package/skills/portfolio-analysis/trigger-cases.json +31 -0
  221. package/skills/ppt-chain/SKILL.md +14 -6
  222. package/skills/ppt-chain/trigger-cases.json +31 -0
  223. package/skills/pptx-author/SKILL.md +86 -33
  224. package/skills/pptx-author/scripts/__pycache__/pptx_check.cpython-312.pyc +0 -0
  225. package/skills/pptx-author/scripts/__pycache__/pptx_deck.cpython-312.pyc +0 -0
  226. package/skills/pptx-author/scripts/__pycache__/pptx_layout.cpython-312.pyc +0 -0
  227. package/skills/pptx-author/scripts/pptx_battery.py +178 -0
  228. package/skills/pptx-author/scripts/pptx_build.py +119 -0
  229. package/skills/pptx-author/scripts/pptx_check.py +1182 -0
  230. package/skills/pptx-author/scripts/pptx_deck.py +736 -145
  231. package/skills/pptx-author/scripts/pptx_layout.py +384 -0
  232. package/skills/pptx-author/trigger-cases.json +15 -0
  233. package/skills/pr-comms/trigger-cases.json +30 -0
  234. package/skills/procurement-supply/trigger-cases.json +31 -0
  235. package/skills/product-design/trigger-cases.json +31 -0
  236. package/skills/production-planning/trigger-cases.json +31 -0
  237. package/skills/project-management/trigger-cases.json +31 -0
  238. package/skills/project-proposal/trigger-cases.json +31 -0
  239. package/skills/prose-standard/SKILL.md +53 -0
  240. package/skills/prose-standard/trigger-cases.json +23 -0
  241. package/skills/qa-api-automation/trigger-cases.json +31 -0
  242. package/skills/qa-mobile-automation/trigger-cases.json +31 -0
  243. package/skills/qa-ui-automation/trigger-cases.json +31 -0
  244. package/skills/quant-analysis/trigger-cases.json +31 -0
  245. package/skills/rag-triage/SKILL.md +64 -0
  246. package/skills/rag-triage/evals.json +57 -0
  247. package/skills/rag-triage/references/triage-playbook.md +75 -0
  248. package/skills/rag-triage/trigger-cases.json +31 -0
  249. package/skills/real-estate-property/trigger-cases.json +30 -0
  250. package/skills/refactor-safely/SKILL.md +1 -1
  251. package/skills/refactor-safely/trigger-cases.json +30 -0
  252. package/skills/regression-forecast/trigger-cases.json +31 -0
  253. package/skills/release-checklist/trigger-cases.json +31 -0
  254. package/skills/report-craft/SKILL.md +76 -0
  255. package/skills/report-craft/references/feasibility.md +118 -0
  256. package/skills/report-craft/references/review-checklist.md +51 -0
  257. package/skills/report-craft/references/visuals.md +114 -0
  258. package/skills/report-craft/references/writing.md +80 -0
  259. package/skills/report-craft/scripts/__pycache__/chart_render.cpython-312.pyc +0 -0
  260. package/skills/report-craft/scripts/__pycache__/fin_model.cpython-312.pyc +0 -0
  261. package/skills/report-craft/scripts/__pycache__/project_facts.cpython-312.pyc +0 -0
  262. package/skills/report-craft/scripts/chart_render.py +1222 -0
  263. package/skills/report-craft/scripts/fin_model.py +602 -0
  264. package/skills/report-craft/scripts/project_facts.py +580 -0
  265. package/skills/report-craft/scripts/report_battery.py +349 -0
  266. package/skills/report-craft/trigger-cases.json +15 -0
  267. package/skills/retail-store-ops/trigger-cases.json +30 -0
  268. package/skills/risk-compliance/trigger-cases.json +31 -0
  269. package/skills/scope-creep/SKILL.md +45 -0
  270. package/skills/scope-creep/trigger-cases.json +7 -0
  271. package/skills/sec-auth/trigger-cases.json +31 -0
  272. package/skills/sec-compliance/trigger-cases.json +31 -0
  273. package/skills/securities-chain/trigger-cases.json +31 -0
  274. package/skills/securities-research/trigger-cases.json +31 -0
  275. package/skills/security-audit/trigger-cases.json +31 -0
  276. package/skills/slides-html/SKILL.md +25 -10
  277. package/skills/slides-html/trigger-cases.json +11 -0
  278. package/skills/sql-analysis/SKILL.md +110 -0
  279. package/skills/sql-analysis/trigger-cases.json +34 -0
  280. package/skills/sql-tuning/trigger-cases.json +31 -0
  281. package/skills/statistical-testing/trigger-cases.json +31 -0
  282. package/skills/strategy-planning/trigger-cases.json +30 -0
  283. package/skills/supply-chain/trigger-cases.json +31 -0
  284. package/skills/tax-compliance/trigger-cases.json +30 -0
  285. package/skills/tech-doc/SKILL.md +262 -0
  286. package/skills/tech-doc/scripts/__pycache__/archive_battery.cpython-312.pyc +0 -0
  287. package/skills/tech-doc/scripts/__pycache__/archive_build.cpython-312.pyc +0 -0
  288. package/skills/tech-doc/scripts/__pycache__/archive_check.cpython-312.pyc +0 -0
  289. package/skills/tech-doc/scripts/__pycache__/archive_spec.cpython-312.pyc +0 -0
  290. package/skills/tech-doc/scripts/__pycache__/doc_templates.cpython-312.pyc +0 -0
  291. package/skills/tech-doc/scripts/archive_battery.py +860 -0
  292. package/skills/tech-doc/scripts/archive_build.py +599 -0
  293. package/skills/tech-doc/scripts/archive_check.py +1307 -0
  294. package/skills/tech-doc/scripts/archive_spec.py +499 -0
  295. package/skills/tech-doc/scripts/doc_templates.py +624 -0
  296. package/skills/tech-doc/trigger-cases.json +57 -0
  297. package/skills/tech-research/trigger-cases.json +31 -0
  298. package/skills/tender-bidding/trigger-cases.json +31 -0
  299. package/skills/translation-pro/trigger-cases.json +30 -0
  300. package/skills/treasury-mgmt/trigger-cases.json +31 -0
  301. package/skills/trim-cot-leakage/SKILL.md +58 -0
  302. package/skills/trim-cot-leakage/trigger-cases.json +23 -0
  303. package/skills/web-craft/SKILL.md +103 -0
  304. package/skills/web-craft/references/craft-checklist.md +75 -0
  305. package/skills/web-craft/references/design-direction.md +122 -0
  306. package/skills/web-craft/references/h5-mobile.md +100 -0
  307. package/skills/web-craft/references/motion.md +136 -0
  308. package/skills/web-craft/references/recipes.md +74 -0
  309. package/skills/web-craft/templates/wedding-invitation.html +1180 -0
  310. package/skills/web-craft/trigger-cases.json +14 -0
  311. package/skills/web-scraping/trigger-cases.json +34 -0
  312. package/skills/web-search/SKILL.md +11 -10
  313. package/skills/web-search/trigger-cases.json +31 -0
  314. package/skills/weekly-report/SKILL.md +134 -0
  315. package/skills/weekly-report/trigger-cases.json +11 -0
  316. package/skills/write-tests/trigger-cases.json +31 -0
  317. package/skills/xlsx-analysis/SKILL.md +143 -77
  318. package/skills/xlsx-analysis/scripts/__pycache__/data_profile.cpython-312.pyc +0 -0
  319. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_build.cpython-312.pyc +0 -0
  320. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_check.cpython-312.pyc +0 -0
  321. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_layout.cpython-312.pyc +0 -0
  322. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_toolkit.cpython-312.pyc +0 -0
  323. package/skills/xlsx-analysis/scripts/data_profile.py +391 -0
  324. package/skills/xlsx-analysis/scripts/xlsx_battery.py +389 -0
  325. package/skills/xlsx-analysis/scripts/xlsx_build.py +522 -0
  326. package/skills/xlsx-analysis/scripts/xlsx_check.py +924 -0
  327. package/skills/xlsx-analysis/scripts/xlsx_layout.py +235 -0
  328. package/skills/xlsx-analysis/scripts/xlsx_style.py +28 -66
  329. package/skills/xlsx-analysis/scripts/xlsx_toolkit.py +61 -5
  330. package/skills/xlsx-analysis/trigger-cases.json +59 -0
  331. package/squads/hc-defense.md +47 -0
  332. package/squads/hc-rdarchive.md +110 -0
  333. package/squads/hc-redteam.md +50 -0
  334. package/squads/hc-weekly.md +125 -0
  335. package/src/account.js +1 -1
  336. package/src/acp.js +1 -0
  337. package/src/advisor.js +1 -1
  338. package/src/agent.js +1 -1
  339. package/src/api.js +1 -1
  340. package/src/archaeology.js +1 -0
  341. package/src/ask-ui.js +1 -0
  342. package/src/assets-import.js +1 -1
  343. package/src/autofix.js +1 -1
  344. package/src/automation-tools.js +1 -1
  345. package/src/automation.js +1 -1
  346. package/src/autorun.js +1 -1
  347. package/src/banner.js +1 -1
  348. package/src/capability-probe.js +1 -0
  349. package/src/checkpoints.js +1 -1
  350. package/src/code-tools.js +1 -0
  351. package/src/codeindex.js +1 -1
  352. package/src/codemode.js +1 -0
  353. package/src/command-bridge.js +1 -0
  354. package/src/command-ui.js +1 -0
  355. package/src/commands.js +1 -1
  356. package/src/compaction.js +1 -1
  357. package/src/computer.js +1 -1
  358. package/src/config.js +1 -1
  359. package/src/consult.js +1 -1
  360. package/src/context-report.js +1 -1
  361. package/src/context-window.js +1 -0
  362. package/src/context.js +1 -1
  363. package/src/crew.js +1 -0
  364. package/src/crush.js +1 -0
  365. package/src/daemon.js +1 -1
  366. package/src/decisions.js +1 -1
  367. package/src/depaudit.js +1 -0
  368. package/src/device.js +1 -1
  369. package/src/diagram-draw.js +1 -0
  370. package/src/diagram-layouts.js +1 -0
  371. package/src/diagram-raster.js +1 -0
  372. package/src/diagram.js +1 -0
  373. package/src/dictation.js +1 -0
  374. package/src/diffview.js +1 -1
  375. package/src/doctor.js +1 -1
  376. package/src/docx-tools.js +1 -0
  377. package/src/errkind.js +1 -1
  378. package/src/evidence.js +1 -0
  379. package/src/executors.js +1 -1
  380. package/src/expert-catalog.js +1 -1
  381. package/src/experts-store.js +1 -1
  382. package/src/experts.js +1 -1
  383. package/src/external-agents.js +1 -0
  384. package/src/forks.js +1 -1
  385. package/src/fs-observe.js +1 -0
  386. package/src/gitflow.js +1 -1
  387. package/src/goalround.js +1 -0
  388. package/src/hooks.js +1 -1
  389. package/src/input.js +1 -1
  390. package/src/insights.js +1 -1
  391. package/src/jobs.js +1 -0
  392. package/src/kbsync.js +1 -1
  393. package/src/lsp.js +1 -0
  394. package/src/mcp-apps.js +1 -0
  395. package/src/mcp.js +1 -1
  396. package/src/memory.js +1 -1
  397. package/src/notify.js +1 -1
  398. package/src/permissions.js +1 -1
  399. package/src/plan-limits.js +1 -0
  400. package/src/policy.js +1 -0
  401. package/src/pptx-tools.js +1 -0
  402. package/src/presence.js +1 -1
  403. package/src/privatedir.js +1 -0
  404. package/src/pyruntime.js +1 -1
  405. package/src/radar.js +1 -0
  406. package/src/render.js +1 -1
  407. package/src/repl.js +1 -1
  408. package/src/report-tools.js +1 -0
  409. package/src/routing.js +1 -0
  410. package/src/sandbox-probe.js +1 -0
  411. package/src/sandbox-win-launcher.mjs +156 -0
  412. package/src/sandbox.js +1 -0
  413. package/src/scan.js +1 -1
  414. package/src/schedule.js +1 -1
  415. package/src/scopecreep.js +1 -0
  416. package/src/sdk.js +1 -0
  417. package/src/selection.js +1 -0
  418. package/src/session-events.js +1 -0
  419. package/src/session.js +1 -1
  420. package/src/sessionindex.js +1 -0
  421. package/src/sessionlog.js +1 -0
  422. package/src/skill-catalog.js +1 -0
  423. package/src/skill-lint.js +1 -0
  424. package/src/skill-optimize.js +1 -0
  425. package/src/skill-scan.js +1 -0
  426. package/src/skills.js +1 -1
  427. package/src/spill.js +1 -0
  428. package/src/squad.js +1 -1
  429. package/src/subagent.js +1 -1
  430. package/src/surface-state.js +1 -0
  431. package/src/tapd.js +1 -0
  432. package/src/terminal-select.js +1 -0
  433. package/src/terminal.js +1 -0
  434. package/src/test-runner.js +1 -0
  435. package/src/todo.js +1 -1
  436. package/src/toolpipe.js +1 -0
  437. package/src/tools.js +1 -1
  438. package/src/toon.js +1 -0
  439. package/src/transcript.js +1 -1
  440. package/src/tui/app.js +1 -1
  441. package/src/tui/command-menu.js +1 -0
  442. package/src/tui/progress.js +1 -0
  443. package/src/tui/scroll.js +1 -0
  444. package/src/tui/welcome.js +1 -0
  445. package/src/tui/workbench.js +1 -0
  446. package/src/ui-spec.js +1 -0
  447. package/src/uitree.js +1 -0
  448. package/src/uninstall.js +1 -1
  449. package/src/visual.js +1 -1
  450. package/src/voice-form.js +1 -0
  451. package/src/voice.js +1 -0
  452. package/src/watch.js +1 -1
  453. package/src/web-audit.js +1 -0
  454. package/src/web-cdp.js +1 -0
  455. package/src/web-inspect.js +1 -0
  456. package/src/websearch.js +1 -0
  457. package/src/weekly.js +1 -0
  458. package/src/workflow.js +1 -0
  459. package/src/worklog.js +1 -0
  460. package/src/xlsx-tools.js +1 -0
  461. package/ui/command-picker.js +79 -0
  462. package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
  463. package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
  464. package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
  465. package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
  466. package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
  467. package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
  468. package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
  469. package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
  470. package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
  471. package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
  472. package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
  473. package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
  474. package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
  475. package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
  476. package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
  477. package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
  478. package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
  479. package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
  480. package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
  481. package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
  482. package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
  483. package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
  484. package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
  485. package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -0
@@ -0,0 +1,602 @@
1
+ #!/usr/bin/env python3
2
+ # 财务测算引擎:假设(JSON)→ 投资估算表 / 项目投资现金流量表 / 财务评价指标(NPV·IRR·回收期·总投资收益率)/
3
+ # 情景分析 / 敏感性分析(含临界点)/ 结论句 / 可直接交给 render_chart 的图表 spec。只用标准库。
4
+ #
5
+ # ── 为什么要有它 ──
6
+ # 可研报告最常见的硬伤是「数字对不上、口径不对」,而这恰恰是模型心算最容易错的地方。实测(HuiCai 可研基线):
7
+ # · 静态回收期拿「收入」去除总投资——没扣经营成本,回收期被严重低估;
8
+ # · 「内部模型调用节约 60 万元/年」,而整个项目的模型调用预算才 22 万元——节约额比被节约的支出还大;
9
+ # · 模板要求的净现值、内部收益率、敏感性分析一个都没有。
10
+ # 这些不是文笔问题,是计算问题。计算交给引擎:模型只给假设和依据,数字由这里算、表由这里排,
11
+ # 全文引用的每个数都来自同一次计算,前后不可能矛盾。
12
+ #
13
+ # ── 口径(按《建设项目经济评价方法与参数》(第三版)融资前分析;notes 里原样写给评审)──
14
+ # · 计算期 = 建设期 m 年 + 运营期 n 年,年序 1..m+n;现金流按年末发生(年末习惯法),折现到第 1 年初;
15
+ # · 所得税前净现金流量 = 营业收入 + 成本节约 + 回收余值 − 建设投资 − 经营成本;
16
+ # · 调整所得税 = 息税前利润 × 税率,息税前利润 = 收入 + 节约 − 经营成本 − 折旧摊销(建设投资按直线法摊销),
17
+ # 亏损向后结转 5 年弥补;所得税后净现金流量 = 税前 − 调整所得税;
18
+ # · 成本节约 = 被节约的那项支出 × 节约比例——**节约额永远不会超过被节约的支出本身**;
19
+ # · 投资回收期含建设期:T − 1 + |第 T−1 年累计净现金流量| ÷ 第 T 年净现金流量;
20
+ # · 敏感性:各因素单独 ±10%/±20%(折现率 ±2 个百分点),临界点 = 使 NPV 恰为 0 的变动幅度(即 IRR = 基准收益率)。
21
+ #
22
+ # 用法:python fin_model.py <assumptions.json | JSON 文本> 最后一行输出 JSON。
23
+
24
+ import json
25
+ import math
26
+ import re
27
+ import sys
28
+
29
+
30
+ class ModelError(ValueError):
31
+ """假设写错了:信息要能直接照着改。"""
32
+
33
+
34
+ def _num(x, where):
35
+ try:
36
+ v = float(x)
37
+ except (TypeError, ValueError):
38
+ raise ModelError(f'{where} 不是数字:{x!r}')
39
+ if math.isnan(v) or math.isinf(v):
40
+ raise ModelError(f'{where} 不是有限数字:{x!r}')
41
+ return v
42
+
43
+
44
+ def fmt(v, d=2):
45
+ return '—' if v is None else f'{v:,.{d}f}'
46
+
47
+
48
+ def pct(v, d=1):
49
+ return '—' if v is None else f'{v * 100:.{d}f}%'
50
+
51
+
52
+ def yrs(v):
53
+ return '超出计算期' if v is None else f'{v:.2f} 年'
54
+
55
+
56
+ def md(headers, rows):
57
+ out = ['| ' + ' | '.join(headers) + ' |', '|' + '|'.join(['---'] * len(headers)) + '|']
58
+ out += ['| ' + ' | '.join(str(c) for c in r) + ' |' for r in rows]
59
+ return '\n'.join(out)
60
+
61
+
62
+ # ── 读假设 ──────────────────────────────────────────────────────────────────
63
+ def _len_hint(got, n, m):
64
+ """逐年数组长度不对时,猜最常见的原因:把建设期那几年也写进去了。"""
65
+ if got == n + m:
66
+ return f'——看起来把建设期 {m} 年也算进去了:逐年数组只写运营期,去掉开头建设期的 {m} 个数'
67
+ return ''
68
+
69
+
70
+ def series_of(item, n, where, is_cost=False, m=1):
71
+ """一行收入/成本 → 运营期 n 年的逐年数组。支持 values / price×volume / per_year(+growth)。"""
72
+ if 'values' in item:
73
+ vals = item['values']
74
+ if not isinstance(vals, list) or len(vals) != n:
75
+ got = len(vals) if isinstance(vals, list) else 0
76
+ raise ModelError(f'{where}.values 要有 {n} 个数(运营期 {n} 年逐年),实际 {got} 个' + _len_hint(got, n, m))
77
+ return [_num(v, f'{where}.values[{i}]') for i, v in enumerate(vals)]
78
+ if 'price' in item and 'volume' in item:
79
+ price = _num(item['price'], f'{where}.price')
80
+ vol = item['volume']
81
+ if not isinstance(vol, list) or len(vol) != n:
82
+ got = len(vol) if isinstance(vol, list) else 0
83
+ raise ModelError(f'{where}.volume 要有 {n} 个数(运营期逐年销量/客户数),实际 {got} 个' + _len_hint(got, n, m))
84
+ return [price * _num(v, f'{where}.volume[{i}]') for i, v in enumerate(vol)]
85
+ if 'per_year' in item:
86
+ base = _num(item['per_year'], f'{where}.per_year')
87
+ g = _num(item.get('growth', 0), f'{where}.growth')
88
+ return [base * (1 + g) ** i for i in range(n)]
89
+ raise ModelError(f'{where} 要给 values,或 price+volume,或 per_year(+growth)'
90
+ + (',或 pct_of_revenue' if is_cost else ''))
91
+
92
+
93
+ def load(spec):
94
+ if not isinstance(spec, dict):
95
+ raise ModelError('假设要是一个 JSON 对象')
96
+ n = int(_num(spec.get('years', 5), 'years'))
97
+ m = int(_num(spec.get('construction_years', 1), 'construction_years'))
98
+ if not 1 <= n <= 30:
99
+ raise ModelError('years(运营期年数)要在 1~30 之间')
100
+ if not 1 <= m <= 5:
101
+ raise ModelError('construction_years(建设期年数)要在 1~5 之间')
102
+ r = _num(spec.get('discount_rate', 0.08), 'discount_rate')
103
+ if not 0 <= r < 1:
104
+ raise ModelError('discount_rate 写成小数(8% 写 0.08)')
105
+ tax = _num(spec.get('tax_rate', 0), 'tax_rate')
106
+ if not 0 <= tax < 1:
107
+ raise ModelError('tax_rate 写成小数(高新技术企业 15% 写 0.15;0 = 只做所得税前分析)')
108
+ dep_years = int(_num(spec.get('depreciation_years', n), 'depreciation_years'))
109
+ if dep_years < 1:
110
+ raise ModelError('depreciation_years 至少 1 年')
111
+
112
+ inv = spec.get('investment') or []
113
+ if not inv:
114
+ raise ModelError('缺 investment(建设投资明细):[{"name":"研发人员人工","amount":480,"basis":"12人×12月×3.3万元"}]')
115
+ investment = []
116
+ for i, it in enumerate(inv):
117
+ amt = _num(it.get('amount'), f'investment[{i}].amount')
118
+ if amt < 0:
119
+ raise ModelError(f'investment[{i}].amount 不能为负')
120
+ sch = it.get('schedule')
121
+ if sch is None:
122
+ sch = [1.0 / m] * m # 没写分年计划:建设期内平均投入
123
+ if not isinstance(sch, list) or len(sch) != m:
124
+ raise ModelError(f'investment[{i}].schedule 要有 {m} 个比例(建设期逐年投入占比)')
125
+ sch = [_num(x, f'investment[{i}].schedule') for x in sch]
126
+ if abs(sum(sch) - 1) > 1e-6:
127
+ raise ModelError(f'investment[{i}].schedule 各年比例之和要等于 1(现在是 {sum(sch):g})')
128
+ investment.append({'name': str(it.get('name') or f'投资项{i + 1}'), 'amount': amt, 'schedule': sch,
129
+ 'basis': str(it.get('basis', '') or '')})
130
+
131
+ revenue = []
132
+ for i, it in enumerate(spec.get('revenue') or []):
133
+ revenue.append({'name': str(it.get('name') or f'收入{i + 1}'), 'values': series_of(it, n, f'revenue[{i}]', m=m),
134
+ 'basis': str(it.get('basis', '') or '')})
135
+ savings = []
136
+ for i, it in enumerate(spec.get('savings') or []):
137
+ base = it.get('base_values')
138
+ if not isinstance(base, list) or len(base) != n:
139
+ got = len(base) if isinstance(base, list) else 0
140
+ raise ModelError(f'savings[{i}] 要给 base_values(被节约的那项支出,运营期逐年 {n} 个数,实际 {got} 个)和 rate——'
141
+ '节约额 = 支出 × 比例,没有支出就谈不上节约' + _len_hint(got, n, m))
142
+ rate = _num(it.get('rate'), f'savings[{i}].rate')
143
+ if not 0 <= rate <= 1:
144
+ raise ModelError(f'savings[{i}].rate 要在 0~1 之间(节约 30% 写 0.3)')
145
+ bv = [_num(v, f'savings[{i}].base_values[{t}]') for t, v in enumerate(base)]
146
+ savings.append({'name': str(it.get('name') or f'成本节约{i + 1}'), 'base': bv, 'rate': rate,
147
+ 'values': [b * rate for b in bv], 'basis': str(it.get('basis', '') or '')})
148
+ if not revenue and not savings:
149
+ raise ModelError('revenue(营业收入)与 savings(成本节约)至少给一项。'
150
+ '既无收入也无可量化节约的公益/内部项目,应改做费用效果分析,不要硬编收入')
151
+ costs = []
152
+ for i, it in enumerate(spec.get('costs') or []):
153
+ p = it.get('pct_of_revenue')
154
+ if p is not None:
155
+ p = _num(p, f'costs[{i}].pct_of_revenue')
156
+ if not 0 <= p <= 1:
157
+ raise ModelError(f'costs[{i}].pct_of_revenue 要在 0~1 之间(收入的 20% 写 0.2)')
158
+ costs.append({'name': str(it.get('name') or f'成本{i + 1}'), 'pct': p,
159
+ 'values': series_of(it, n, f'costs[{i}]', is_cost=True, m=m) if p is None else None,
160
+ 'basis': str(it.get('basis', '') or '')})
161
+ scen = spec.get('scenarios') or {}
162
+ if not isinstance(scen, dict):
163
+ raise ModelError('scenarios 写成对象:{"乐观":{"revenue":1.2},"悲观":{"revenue":0.7,"costs":1.1}}')
164
+ for k, v in scen.items():
165
+ if not isinstance(v, dict) or not set(v) <= {'revenue', 'costs', 'investment', 'savings'}:
166
+ raise ModelError(f'情景「{k}」只能调 revenue/costs/investment/savings 四个乘数')
167
+ for kk, vv in v.items():
168
+ _num(vv, f'scenarios.{k}.{kk}')
169
+ start = spec.get('start_year')
170
+ return {
171
+ 'unit': str(spec.get('unit', '万元')), 'n': n, 'm': m, 'r': r, 'tax': tax, 'dep_years': dep_years,
172
+ 'start_year': int(_num(start, 'start_year')) if start is not None else None,
173
+ 'investment': investment, 'revenue': revenue, 'costs': costs, 'savings': savings,
174
+ 'residual': _num(spec.get('residual_value', 0), 'residual_value'), 'scenarios': scen,
175
+ 'decimals': max(0, min(4, int(_num(spec.get('decimals', 2), 'decimals')))),
176
+ }
177
+
178
+
179
+ # ── 计算 ────────────────────────────────────────────────────────────────────
180
+ def cashflows(M, k_rev=1.0, k_cost=1.0, k_inv=1.0, k_sav=1.0, rev_line=None):
181
+ """按乘数重算一遍现金流。基准、情景、敏感性、临界点全走这一个函数,口径不会分叉。"""
182
+ m, n = M['m'], M['n']
183
+ rev_lines = [[v * (k_rev if rev_line in (None, x['name']) else 1.0) for v in x['values']] for x in M['revenue']]
184
+ rev = [sum(l[j] for l in rev_lines) for j in range(n)]
185
+ cost_lines = [[(c['pct'] * rev[j] if c['pct'] is not None else c['values'][j]) * k_cost for j in range(n)]
186
+ for c in M['costs']]
187
+ cost = [sum(l[j] for l in cost_lines) for j in range(n)]
188
+ sav_lines = [[v * k_sav for v in s['values']] for s in M['savings']]
189
+ sav = [sum(l[j] for l in sav_lines) for j in range(n)]
190
+ inv_year = [sum(x['amount'] * x['schedule'][k] for x in M['investment']) * k_inv for k in range(m)]
191
+ inv = sum(inv_year)
192
+ dep = [inv / M['dep_years'] if j < M['dep_years'] else 0.0 for j in range(n)]
193
+ ebit = [rev[j] + sav[j] - cost[j] - dep[j] for j in range(n)]
194
+ tax, pool = [], [] # 亏损向后结转 5 年
195
+ for j, e in enumerate(ebit):
196
+ if e <= 0:
197
+ if e < 0:
198
+ pool.append([j, -e])
199
+ tax.append(0.0)
200
+ continue
201
+ taxable = e
202
+ for p in pool:
203
+ if j - p[0] > 5 or p[1] <= 0:
204
+ continue
205
+ use = min(p[1], taxable)
206
+ p[1] -= use
207
+ taxable -= use
208
+ if taxable <= 0:
209
+ break
210
+ tax.append(taxable * M['tax'])
211
+ resid = [M['residual'] if j == n - 1 else 0.0 for j in range(n)]
212
+ pre = [-v for v in inv_year] + [rev[j] + sav[j] + resid[j] - cost[j] for j in range(n)]
213
+ post = pre[:m] + [pre[m + j] - tax[j] for j in range(n)]
214
+ return {'rev': rev, 'rev_lines': rev_lines, 'cost': cost, 'cost_lines': cost_lines, 'sav': sav,
215
+ 'sav_lines': sav_lines, 'inv_year': inv_year, 'inv': inv, 'dep': dep, 'ebit': ebit, 'tax': tax,
216
+ 'resid': resid, 'pre': pre, 'post': post}
217
+
218
+
219
+ def npv(ncf, r):
220
+ """年末习惯法:第 t 年(t 从 1 起)的现金流折现 t 年。"""
221
+ return sum(v / (1 + r) ** (t + 1) for t, v in enumerate(ncf))
222
+
223
+
224
+ def irr(ncf):
225
+ """二分法求 NPV=0 的折现率;无根(如累计始终为负)返回 None——如实说「不存在」,不编数。"""
226
+ lo, hi = -0.99, 10.0
227
+ f_lo, f_hi = npv(ncf, lo), npv(ncf, hi)
228
+ if f_lo == 0:
229
+ return lo
230
+ if f_lo * f_hi > 0:
231
+ return None
232
+ for _ in range(300):
233
+ mid = (lo + hi) / 2
234
+ fm = npv(ncf, mid)
235
+ if abs(fm) < 1e-10 or hi - lo < 1e-12:
236
+ return mid
237
+ if (f_lo < 0) == (fm < 0):
238
+ lo, f_lo = mid, fm
239
+ else:
240
+ hi = mid
241
+ return (lo + hi) / 2
242
+
243
+
244
+ def payback(ncf, r=None):
245
+ """含建设期的投资回收期:T − 1 + |第 T−1 年累计| ÷ 第 T 年净现金流(T 为累计首次 ≥0 的年序)。收不回返回 None。"""
246
+ cum = 0.0
247
+ for t, v in enumerate(ncf):
248
+ dv = v if r is None else v / (1 + r) ** (t + 1)
249
+ prev, cum = cum, cum + dv
250
+ if t > 0 and prev < 0 <= cum:
251
+ return t + (-prev) / dv
252
+ return None
253
+
254
+
255
+ def indicators(ncf, r):
256
+ return {'npv': npv(ncf, r), 'irr': irr(ncf), 'payback': payback(ncf), 'payback_dynamic': payback(ncf, r)}
257
+
258
+
259
+ def main_ncf(M, cf):
260
+ return cf['post'] if M['tax'] > 0 else cf['pre']
261
+
262
+
263
+ def switching(M, key):
264
+ """临界点:使主口径 NPV 恰为 0 的因素变动幅度,在 [-99%, +300%] 内二分;找不到返回 None。"""
265
+ def f(k):
266
+ kw = {'k_rev': 1.0, 'k_cost': 1.0, 'k_inv': 1.0, 'k_sav': 1.0}
267
+ if key.startswith('rev:'):
268
+ kw['k_rev'] = 1 + k
269
+ return npv(main_ncf(M, cashflows(M, rev_line=key[4:], **kw)), M['r'])
270
+ kw[key] = 1 + k
271
+ return npv(main_ncf(M, cashflows(M, **kw)), M['r'])
272
+ if f(0.0) == 0:
273
+ return 0.0
274
+ for a, b in ((-0.99, 0.0), (0.0, 3.0)):
275
+ fa, fb = f(a), f(b)
276
+ if fa * fb < 0:
277
+ for _ in range(100):
278
+ mid = (a + b) / 2
279
+ fm = f(mid)
280
+ if (fm < 0) == (fa < 0):
281
+ a, fa = mid, fm
282
+ else:
283
+ b = mid
284
+ return (a + b) / 2
285
+ return None
286
+
287
+
288
+ def run(spec):
289
+ M = load(spec)
290
+ n, m, u, r, d = M['n'], M['m'], M['unit'], M['r'], M['decimals']
291
+ T = m + n
292
+ has_tax = M['tax'] > 0
293
+ basis = '所得税后' if has_tax else '所得税前'
294
+ base = cashflows(M)
295
+ ind_pre = indicators(base['pre'], r)
296
+ ind_post = indicators(base['post'], r) if has_tax else None
297
+ ind = ind_post or ind_pre
298
+ avg_ebit = sum(base['ebit']) / n
299
+ roi = avg_ebit / base['inv'] if base['inv'] else None
300
+
301
+ def f(v):
302
+ return fmt(v, d)
303
+
304
+ years = [str(M['start_year'] + t) if M['start_year'] else f'第{t + 1}年' for t in range(T)]
305
+
306
+ # ── 预警:不改数,只提醒哪里评审会追问 ──
307
+ warnings = []
308
+ if not M['costs']:
309
+ warnings.append('没有列经营成本:回收期与 NPV 会按「收入全是净收益」算,严重高估——至少列人员、云资源/算力、模型调用、运维与销售')
310
+ elif sum(base['rev']) > 0 and sum(base['cost']) < 0.15 * sum(base['rev']):
311
+ warnings.append(f'经营成本只占营业收入的 {pct(sum(base["cost"]) / sum(base["rev"]))},偏低——'
312
+ 'AI 类产品的模型调用、算力、运维人员、销售与服务费用要逐项列全')
313
+ if ind['npv'] < 0:
314
+ warnings.append(f'{basis} NPV 为 {f(ind["npv"])} {u} < 0:按当前假设财务上不可行——结论写「不可行」或'
315
+ '「有条件可行」并写清条件,不要为了结论去改假设')
316
+ if ind['irr'] is None:
317
+ warnings.append('内部收益率不存在(累计净现金流始终为负):如实写「IRR 不存在」,不要编一个数')
318
+ elif ind['irr'] > 1:
319
+ warnings.append(f'IRR 高达 {pct(ind["irr"])}:通常是投资漏列或收入假设过于乐观,评审一定会追问依据')
320
+ if ind['payback'] is None:
321
+ warnings.append(f'计算期 {T} 年内收不回投资:回收期写「超出计算期」')
322
+ for s in M['savings']:
323
+ if s['rate'] > 0.5:
324
+ warnings.append(f'「{s["name"]}」节约比例 {pct(s["rate"], 0)},超过一半——要有实测或试点数据支撑,否则评审不会采信')
325
+ for x in M['revenue']:
326
+ v = x['values']
327
+ for j in range(1, n):
328
+ if v[j - 1] > 0 and v[j] / v[j - 1] > 3:
329
+ warnings.append(f'收入「{x["name"]}」运营第 {j + 1} 年是上一年的 {v[j] / v[j - 1]:.1f} 倍:要写清增长依据(渠道、签约、试点转化)')
330
+ break
331
+ if sum(base['sav']) > sum(base['rev']) > 0:
332
+ warnings.append('成本节约合计大于营业收入合计:效益主要靠「省钱」,文中要写清节约的来源与测量方法')
333
+ # 情景方向要自洽。实测模型不知道「按收入比例的成本会自动随收入变」,为了让上游成本跟着收入降,
334
+ # 给悲观情景设了 costs ×0.8——得到一个「收入降 40%、成本降 20%」的悲观情景,写进报告就是笑话。
335
+ has_pct = any(c['pct'] is not None for c in M['costs'])
336
+ for name, mult in M['scenarios'].items():
337
+ kr, kc = float(mult.get('revenue', 1)), float(mult.get('costs', 1))
338
+ bad = re.search(r'悲观|保守|下行|不利|压力', name)
339
+ good = re.search(r'乐观|上行|有利', name)
340
+ if bad and (kc < 1 or kr > 1):
341
+ warnings.append(f'「{name}」情景的方向不对(收入×{kr:g}、经营成本×{kc:g}):悲观情景应是收入 ≤1、成本 ≥1。'
342
+ + ('按收入比例列的成本(pct_of_revenue)已经随收入自动下降,不用再把 costs 调低来「跟随」——' if has_pct and kc < 1 else '')
343
+ + '改完重算,别把方向相反的情景写进报告')
344
+ if good and (kc > 1 or kr < 1):
345
+ warnings.append(f'「{name}」情景的方向不对(收入×{kr:g}、经营成本×{kc:g}):乐观情景应是收入 ≥1、成本 ≤1'
346
+ + ('(按收入比例的成本会随收入自动上升,不必再调高 costs)' if has_pct and kc > 1 else '') + ',改完重算')
347
+
348
+ # ── 1) 投资估算表 ──
349
+ inv_total = base['inv']
350
+ head = ['序号', '工程或费用名称', f'金额({u})', '占比']
351
+ if m > 1:
352
+ head += [years[k] for k in range(m)]
353
+ head.append('估算依据')
354
+ rows = []
355
+ for i, x in enumerate(M['investment']):
356
+ row = [str(i + 1), x['name'], f(x['amount']), pct(x['amount'] / inv_total if inv_total else 0)]
357
+ if m > 1:
358
+ row += [f(x['amount'] * s) for s in x['schedule']]
359
+ rows.append(row + [x['basis'] or '—'])
360
+ tot = ['', '合计', f(inv_total), '100.0%']
361
+ if m > 1:
362
+ tot += [f(v) for v in base['inv_year']]
363
+ rows.append(tot + [''])
364
+ t_inv = md(head, rows)
365
+
366
+ # ── 2) 项目投资现金流量表(序号/项目/合计/逐年)──
367
+ def line(no, name, vals_full):
368
+ return [no, name, f(sum(vals_full))] + [f(v) if v != 0 else '—' for v in vals_full]
369
+
370
+ def ops(vals): # 运营期数组 → 计算期数组(建设期补 0)
371
+ return [0.0] * m + list(vals)
372
+
373
+ def cumsum(vals):
374
+ out, acc = [], 0.0
375
+ for v in vals:
376
+ acc += v
377
+ out.append(acc)
378
+ return out
379
+
380
+ rows = []
381
+ cin = [0.0] * m + [base['rev'][j] + base['sav'][j] + base['resid'][j] for j in range(n)]
382
+ rows.append(line('1', '现金流入', cin))
383
+ k = 1
384
+ for x, l in zip(M['revenue'], base['rev_lines']):
385
+ rows.append(line(f'1.{k}', f'营业收入:{x["name"]}', ops(l)))
386
+ k += 1
387
+ for s, l in zip(M['savings'], base['sav_lines']):
388
+ rows.append(line(f'1.{k}', f'成本节约:{s["name"]}', ops(l)))
389
+ k += 1
390
+ if M['residual']:
391
+ rows.append(line(f'1.{k}', '回收资产余值', ops(base['resid'])))
392
+ cout = [base['inv_year'][t] if t < m else base['cost'][t - m] for t in range(T)]
393
+ rows.append(line('2', '现金流出', cout))
394
+ rows.append(line('2.1', '建设投资', base['inv_year'] + [0.0] * n))
395
+ for i, (c, l) in enumerate(zip(M['costs'], base['cost_lines'])):
396
+ rows.append(line(f'2.{i + 2}', f'经营成本:{c["name"]}', ops(l)))
397
+ cum_pre = cumsum(base['pre'])
398
+ rows.append(line('3', '所得税前净现金流量(1−2)', base['pre']))
399
+ rows.append(['4', '累计所得税前净现金流量', '—'] + [f(v) for v in cum_pre])
400
+ cum_main = cum_pre
401
+ if has_tax:
402
+ cum_post = cumsum(base['post'])
403
+ rows.append(line('5', f'调整所得税({pct(M["tax"], 0)})', ops(base['tax'])))
404
+ rows.append(line('6', '所得税后净现金流量(3−5)', base['post']))
405
+ rows.append(['7', '累计所得税后净现金流量', '—'] + [f(v) for v in cum_post])
406
+ cum_main = cum_post
407
+ main = main_ncf(M, base)
408
+ disc = [v / (1 + r) ** (t + 1) for t, v in enumerate(main)]
409
+ cum_disc = cumsum(disc)
410
+ no = 8 if has_tax else 5
411
+ rows.append([str(no), f'折现系数(i={pct(r, 0)})', '—'] + [f'{1 / (1 + r) ** (t + 1):.4f}' for t in range(T)])
412
+ rows.append([str(no + 1), f'{basis}折现净现金流量', f(sum(disc))] + [f(v) for v in disc])
413
+ rows.append([str(no + 2), f'累计{basis}折现净现金流量', '—'] + [f(v) for v in cum_disc])
414
+ t_cf = md(['序号', '项目', '合计'] + years, rows)
415
+
416
+ # ── 3) 财务评价指标表 ──
417
+ def ind_rows(tag, ii):
418
+ return [
419
+ [f'项目投资财务净现值({tag},i={pct(r, 0)})', f'{f(ii["npv"])} {u}', 'FNPV ≥ 0 可行'],
420
+ [f'项目投资财务内部收益率({tag})', pct(ii['irr'], 2) if ii['irr'] is not None else '不存在',
421
+ f'FIRR ≥ 基准收益率 {pct(r, 0)} 可行'],
422
+ [f'静态投资回收期({tag},含建设期)', yrs(ii['payback']), f'计算期 {T} 年'],
423
+ [f'动态投资回收期({tag},含建设期)', yrs(ii['payback_dynamic']), '按折现后的累计净现金流量'],
424
+ ]
425
+ rows = ind_rows('所得税前', ind_pre)
426
+ if has_tax:
427
+ rows += ind_rows('所得税后', ind_post)
428
+ rows.append(['总投资收益率(运营期年均息税前利润 ÷ 总投资)', pct(roi), '与行业参考值比较'])
429
+ t_ind = md(['指标', '数值', '判据'], rows)
430
+
431
+ # ── 4) 情景分析 ──
432
+ scen_def = {'基准': {}}
433
+ scen_def.update(M['scenarios'])
434
+ rows, scen_out, scen_desc = [], {}, {}
435
+ labels = (('revenue', '收入'), ('costs', '经营成本'), ('investment', '投资'), ('savings', '节约'))
436
+ for name, mult in scen_def.items():
437
+ kw = {'k_rev': float(mult.get('revenue', 1)), 'k_cost': float(mult.get('costs', 1)),
438
+ 'k_inv': float(mult.get('investment', 1)), 'k_sav': float(mult.get('savings', 1))}
439
+ ii = indicators(main_ncf(M, cashflows(M, **kw)), r)
440
+ scen_out[name] = ii
441
+ desc = '、'.join(f'{lab}{(float(mult[key]) - 1) * 100:+.0f}%' for key, lab in labels
442
+ if key in mult and float(mult[key]) != 1) or '全部按基准假设'
443
+ scen_desc[name] = desc
444
+ rows.append([name, desc, f(ii['npv']), pct(ii['irr'], 2) if ii['irr'] is not None else '不存在',
445
+ yrs(ii['payback'])])
446
+ t_scen = md(['情景', '假设调整', f'NPV({basis},{u})', f'FIRR({basis})', '静态回收期'], rows)
447
+
448
+ # ── 5) 敏感性分析 ──
449
+ factors = []
450
+ if M['revenue']:
451
+ factors.append(('k_rev', '营业收入'))
452
+ if M['costs']:
453
+ factors.append(('k_cost', '经营成本'))
454
+ factors.append(('k_inv', '建设投资'))
455
+ if M['savings']:
456
+ factors.append(('k_sav', '成本节约'))
457
+ if len(M['revenue']) > 1:
458
+ factors += [(f'rev:{x["name"]}', f'其中:{x["name"]}') for x in M['revenue']]
459
+ steps = [-0.2, -0.1, 0.1, 0.2]
460
+ rows, tornado, sens = [], [], []
461
+ for key, lab in factors:
462
+ vals = []
463
+ for st in steps:
464
+ kw = {'k_rev': 1.0, 'k_cost': 1.0, 'k_inv': 1.0, 'k_sav': 1.0}
465
+ if key.startswith('rev:'):
466
+ kw['k_rev'] = 1 + st
467
+ cf = cashflows(M, rev_line=key[4:], **kw)
468
+ else:
469
+ kw[key] = 1 + st
470
+ cf = cashflows(M, **kw)
471
+ vals.append(npv(main_ncf(M, cf), r))
472
+ coef = ((vals[2] - vals[1]) / 0.2) / ind['npv'] if ind['npv'] else None
473
+ sw = switching(M, key)
474
+ sens.append({'factor': lab, 'coef': coef, 'switch': sw})
475
+ if sw is not None:
476
+ sw_txt = f'{sw * 100:+.1f}%'
477
+ elif (coef or 0) >= 0: # 正向因素:往下压到头 NPV 仍不变号
478
+ sw_txt = '减少 99% 仍不转负' if ind['npv'] >= 0 else '增加 300% 仍为负'
479
+ else:
480
+ sw_txt = '增加 300% 仍不转负' if ind['npv'] >= 0 else '减少 99% 仍为负'
481
+ rows.append([lab] + [f(v) for v in vals] + [f'{coef:.2f}' if coef is not None else '—', sw_txt])
482
+ if not key.startswith('rev:'): # 龙卷风只画总因素,分项留在表里
483
+ tornado.append({'name': lab, 'low': round(vals[0], d), 'high': round(vals[3], d),
484
+ 'low_label': '-20%', 'high_label': '+20%'})
485
+ lo_r, hi_r = max(r - 0.02, 0.0), r + 0.02
486
+ dr_lo, dr_hi = npv(main, lo_r), npv(main, hi_r)
487
+ tornado.append({'name': '基准收益率', 'low': round(dr_lo, d), 'high': round(dr_hi, d),
488
+ 'low_label': pct(lo_r, 0), 'high_label': pct(hi_r, 0)})
489
+ t_sens = md(['因素', '-20%', '-10%', '+10%', '+20%', '敏感度系数', '临界点(NPV=0)'], rows)
490
+ # 折现率不是「±10%」那种变动,单独一句,免得挤进同一列被误读
491
+ t_sens += (f'\n\n表中数值为{basis}财务净现值({u}),基准情景 {f(ind["npv"])} {u}。基准收益率取 {pct(lo_r, 0)} / {pct(hi_r, 0)} 时,'
492
+ f'NPV 分别为 {f(dr_lo)} / {f(dr_hi)} {u}'
493
+ + (f';折现率升至 {pct(ind["irr"], 2)}(即 FIRR)时 NPV 为 0。' if ind['irr'] is not None else '。'))
494
+
495
+ # ── 图表 spec(原样交给 render_chart)──
496
+ charts = {
497
+ 'investment_composition': {'type': 'pie', 'unit': u, 'categories': [x['name'] for x in M['investment']],
498
+ 'series': [{'name': '建设投资', 'values': [round(x['amount'], d) for x in M['investment']]}]},
499
+ 'investment_waterfall': {'type': 'waterfall', 'unit': u, 'total_label': '总投资',
500
+ 'items': [{'name': x['name'], 'value': round(x['amount'], d)} for x in M['investment']]},
501
+ 'cumulative_cashflow': {'type': 'line', 'unit': u, 'categories': years,
502
+ 'series': [{'name': f'累计{basis}净现金流量', 'values': [round(v, d) for v in cum_main]},
503
+ {'name': f'累计{basis}折现净现金流量', 'values': [round(v, d) for v in cum_disc]}]},
504
+ 'sensitivity_tornado': {'type': 'tornado', 'metric': f'{basis}NPV', 'unit': u, 'base': round(ind['npv'], d),
505
+ 'items': tornado},
506
+ }
507
+ if M['revenue']:
508
+ op_years = years[m:]
509
+ if len(M['revenue']) > 1:
510
+ charts['revenue_by_line'] = {'type': 'stacked', 'unit': u, 'categories': op_years,
511
+ 'series': [{'name': x['name'], 'values': [round(v, d) for v in l]}
512
+ for x, l in zip(M['revenue'], base['rev_lines'])]}
513
+ if M['costs']:
514
+ charts['revenue_vs_cost'] = {'type': 'bar', 'unit': u, 'categories': op_years,
515
+ 'series': [{'name': '营业收入', 'values': [round(v, d) for v in base['rev']]},
516
+ {'name': '经营成本', 'values': [round(v, d) for v in base['cost']]}]}
517
+
518
+ # ── 口径说明(原样写进报告,评审能照着复核)──
519
+ notes = [
520
+ f'计算期 {T} 年:建设期 {m} 年({years[0]}{"–" + years[m - 1] if m > 1 else ""}),运营期 {n} 年'
521
+ f'({years[m]}{"–" + years[-1] if n > 1 else ""});金额单位 {u};现金流按年末发生,折现到计算期第 1 年初;'
522
+ f'基准收益率 {pct(r, 1)}。',
523
+ '所得税前净现金流量 = 营业收入 + 成本节约 + 回收资产余值 − 建设投资 − 经营成本。',
524
+ ]
525
+ if has_tax:
526
+ notes.append(f'调整所得税 = 息税前利润 × {pct(M["tax"], 0)};息税前利润 = 营业收入 + 成本节约 − 经营成本 − 摊销,'
527
+ f'建设投资按 {M["dep_years"]} 年直线法摊销、残值为 0,亏损向后结转 5 年弥补。')
528
+ else:
529
+ notes.append('未计所得税(所得税前分析);如需所得税后指标,给出适用税率后重算。')
530
+ for x in M['revenue']:
531
+ if x['basis']:
532
+ notes.append(f'营业收入「{x["name"]}」:{x["basis"]}。')
533
+ for c in M['costs']:
534
+ if c['pct'] is not None:
535
+ notes.append(f'经营成本「{c["name"]}」按营业收入的 {pct(c["pct"], 0)} 计' + (f':{c["basis"]}' if c['basis'] else '') + '。')
536
+ elif c['basis']:
537
+ notes.append(f'经营成本「{c["name"]}」:{c["basis"]}。')
538
+ for s in M['savings']:
539
+ notes.append(f'成本节约「{s["name"]}」= 被节约的支出 × {pct(s["rate"], 0)}' + (f'({s["basis"]})' if s['basis'] else '')
540
+ + f';被节约支出逐年为 {"、".join(f(v) for v in s["base"])} {u},节约额不超过支出本身。')
541
+ if any(c['pct'] is not None for c in M['costs']):
542
+ notes.append('按营业收入比例计的经营成本,在情景分析与敏感性分析中随营业收入同步变动;'
543
+ '「经营成本」因素的调整乘在全部经营成本之上。')
544
+ notes.append('敏感性分析每次只变动一个因素、其余保持基准;临界点为使 NPV 恰为 0 的变动幅度。')
545
+
546
+ # ── 结论句(正文直接引用,保证与表一致)──
547
+ top = max(sens, key=lambda s: abs(s['coef'] or 0)) if sens else None
548
+ feasible = ind['npv'] >= 0 and ind['irr'] is not None and ind['irr'] >= r
549
+ concl = (f'按基准假设,项目总投资 {f(inv_total)} {u},{basis}财务净现值 {f(ind["npv"])} {u}'
550
+ f'(i={pct(r, 0)}),财务内部收益率 {pct(ind["irr"], 2) if ind["irr"] is not None else "不存在"},'
551
+ f'静态投资回收期 {yrs(ind["payback"])}(含建设期 {m} 年),动态投资回收期 {yrs(ind["payback_dynamic"])};')
552
+ concl += '财务上可行。' if feasible else '按当前假设财务上不可行。'
553
+ if top and top['switch'] is not None and top['coef']:
554
+ concl += (f'{top["factor"]}是最敏感的因素(敏感度系数 {top["coef"]:.2f}),'
555
+ f'{"下降" if top["switch"] < 0 else "上升"} {abs(top["switch"]) * 100:.1f}% 时 NPV 降为 0。')
556
+
557
+ bad = [(k, v) for k, v in scen_out.items() if k != '基准' and v['npv'] < 0]
558
+ if bad and feasible: # 结论「可行」不能盖住下行情景转负
559
+ concl += ';'.join(f'在「{k}」情景({scen_desc[k]})下 NPV 为 {f(v["npv"])} {u},转为不可行' for k, v in bad) \
560
+ + ',需在风险分析中给出应对措施。'
561
+
562
+ def rnd(ii):
563
+ return {kk: (round(v, 6) if isinstance(v, float) else v) for kk, v in ii.items()}
564
+
565
+ return {
566
+ 'ok': True, 'unit': u, 'basis': basis,
567
+ 'indicators': {'pre_tax': rnd(ind_pre), 'post_tax': rnd(ind_post) if ind_post else None,
568
+ 'roi': round(roi, 6) if roi is not None else None, 'total_investment': round(inv_total, 6)},
569
+ 'conclusion': concl,
570
+ 'scenarios': {kk: rnd(v) for kk, v in scen_out.items()},
571
+ 'sensitivity': [{'factor': s['factor'], 'coef': round(s['coef'], 4) if s['coef'] is not None else None,
572
+ 'switch': round(s['switch'], 4) if s['switch'] is not None else None} for s in sens],
573
+ 'tables': {'投资估算表': t_inv, '项目投资现金流量表': t_cf, '财务评价指标表': t_ind,
574
+ '情景分析表': t_scen, '敏感性分析表': t_sens},
575
+ 'notes': notes, 'charts': charts, 'warnings': warnings,
576
+ }
577
+
578
+
579
+ def main(argv):
580
+ if len(argv) < 2:
581
+ print(json.dumps({'ok': False, 'error': '用法:fin_model.py <assumptions.json>'}, ensure_ascii=False))
582
+ return 2
583
+ try:
584
+ src = argv[1]
585
+ if src.lstrip().startswith('{'): # 直接传 JSON(只读工具不往工作区写临时文件)
586
+ spec = json.loads(src)
587
+ else:
588
+ with open(src, encoding='utf-8') as fh:
589
+ spec = json.load(fh)
590
+ res = run(spec)
591
+ except ModelError as e:
592
+ res = {'ok': False, 'error': str(e)}
593
+ except json.JSONDecodeError as e:
594
+ res = {'ok': False, 'error': f'假设文件不是合法 JSON:{e}'}
595
+ except Exception as e: # 引擎自身出错:如实上报
596
+ res = {'ok': False, 'error': f'测算引擎出错:{type(e).__name__}: {e}'}
597
+ print(json.dumps(res, ensure_ascii=False))
598
+ return 0 if res.get('ok') else 1
599
+
600
+
601
+ if __name__ == '__main__':
602
+ sys.exit(main(sys.argv))