@huicai/cli 2026.804.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.
- package/LICENSE +391 -32
- package/README.md +176 -10
- package/bin/huicai.js +166 -73
- package/package.json +27 -8
- package/skills/ab-testing/trigger-cases.json +30 -0
- package/skills/academic-writing/trigger-cases.json +30 -0
- package/skills/accounting-ops/trigger-cases.json +30 -0
- package/skills/ai-agent/trigger-cases.json +30 -0
- package/skills/ai-aiops/trigger-cases.json +30 -0
- package/skills/ai-coding/trigger-cases.json +30 -0
- package/skills/ai-compliance/trigger-cases.json +30 -0
- package/skills/ai-eval/trigger-cases.json +30 -0
- package/skills/ai-llm-app/trigger-cases.json +30 -0
- package/skills/ai-mlops/trigger-cases.json +30 -0
- package/skills/ai-multimodal/trigger-cases.json +30 -0
- package/skills/ai-prompt/trigger-cases.json +30 -0
- package/skills/ai-rag/trigger-cases.json +30 -0
- package/skills/anomaly-detection/trigger-cases.json +30 -0
- package/skills/api-design/trigger-cases.json +31 -0
- package/skills/api-integration/trigger-cases.json +31 -0
- package/skills/arch-design/trigger-cases.json +12 -0
- package/skills/arch-diagram/SKILL.md +118 -0
- package/skills/arch-diagram/trigger-cases.json +30 -0
- package/skills/arch-distributed/trigger-cases.json +30 -0
- package/skills/arch-multitenant/trigger-cases.json +30 -0
- package/skills/audio-brief/SKILL.md +65 -0
- package/skills/audio-brief/trigger-cases.json +30 -0
- package/skills/audit-assist/trigger-cases.json +30 -0
- package/skills/audit-chain/trigger-cases.json +31 -0
- package/skills/banking-credit/trigger-cases.json +30 -0
- package/skills/brand-design/trigger-cases.json +30 -0
- package/skills/business-plan/trigger-cases.json +30 -0
- package/skills/capital-ops/trigger-cases.json +30 -0
- package/skills/cicd-pipeline/trigger-cases.json +31 -0
- package/skills/code-archaeology/SKILL.md +55 -0
- package/skills/code-archaeology/trigger-cases.json +9 -0
- package/skills/code-quality/SKILL.md +138 -0
- package/skills/code-quality/references/languages.md +107 -0
- package/skills/code-quality/scripts/__pycache__/code_battery.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_check.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_check.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_fix.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_fix.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_java.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_java.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_lens.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_lens.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_python.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_python.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_testplan.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/code_battery.py +1072 -0
- package/skills/code-quality/scripts/code_check.py +1269 -0
- package/skills/code-quality/scripts/code_csharp.py +371 -0
- package/skills/code-quality/scripts/code_fix.py +201 -0
- package/skills/code-quality/scripts/code_java.py +695 -0
- package/skills/code-quality/scripts/code_jsts.py +313 -0
- package/skills/code-quality/scripts/code_lens.py +1079 -0
- package/skills/code-quality/scripts/code_python.py +536 -0
- package/skills/code-quality/scripts/code_testplan.py +239 -0
- package/skills/code-quality/trigger-cases.json +45 -0
- package/skills/code-review/SKILL.md +24 -0
- package/skills/code-review/trigger-cases.json +35 -0
- package/skills/commit-and-pr/trigger-cases.json +31 -0
- package/skills/content-creation/trigger-cases.json +30 -0
- package/skills/corporate-governance/trigger-cases.json +30 -0
- package/skills/crawl-chain/trigger-cases.json +31 -0
- package/skills/customer-service/trigger-cases.json +30 -0
- package/skills/data-analysis/trigger-cases.json +30 -0
- package/skills/data-chain/SKILL.md +1 -1
- package/skills/data-chain/trigger-cases.json +31 -0
- package/skills/data-governance/trigger-cases.json +30 -0
- package/skills/data-report-pro/trigger-cases.json +30 -0
- package/skills/data-streaming/trigger-cases.json +30 -0
- package/skills/data-wrangling/SKILL.md +1 -1
- package/skills/data-wrangling/trigger-cases.json +30 -0
- package/skills/db-es/trigger-cases.json +30 -0
- package/skills/db-migration/trigger-cases.json +30 -0
- package/skills/db-mysql/trigger-cases.json +30 -0
- package/skills/db-postgres/trigger-cases.json +30 -0
- package/skills/db-redis/trigger-cases.json +30 -0
- package/skills/debug-root-cause/trigger-cases.json +30 -0
- package/skills/dependency-audit/SKILL.md +56 -0
- package/skills/dependency-audit/trigger-cases.json +7 -0
- package/skills/dependency-upgrade/trigger-cases.json +31 -0
- package/skills/dev-cpp/trigger-cases.json +30 -0
- package/skills/dev-cross-platform/trigger-cases.json +30 -0
- package/skills/dev-csharp/SKILL.md +23 -0
- package/skills/dev-csharp/trigger-cases.json +30 -0
- package/skills/dev-frontend/SKILL.md +19 -0
- package/skills/dev-frontend/trigger-cases.json +30 -0
- package/skills/dev-go/trigger-cases.json +30 -0
- package/skills/dev-harmony/trigger-cases.json +30 -0
- package/skills/dev-java/SKILL.md +20 -0
- package/skills/dev-java/trigger-cases.json +30 -0
- package/skills/dev-mobile/trigger-cases.json +30 -0
- package/skills/dev-node/trigger-cases.json +30 -0
- package/skills/dev-php/trigger-cases.json +30 -0
- package/skills/dev-python/SKILL.md +21 -0
- package/skills/dev-python/trigger-cases.json +30 -0
- package/skills/dev-rust/trigger-cases.json +30 -0
- package/skills/dev-toolchain/trigger-cases.json +31 -0
- package/skills/doc-chain/trigger-cases.json +31 -0
- package/skills/doc-compare/trigger-cases.json +30 -0
- package/skills/docx-author/SKILL.md +127 -25
- package/skills/docx-author/scripts/__pycache__/docx_battery.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/docx_build.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/docx_check.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/docx_layout.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/zh_prose.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/docx_battery.py +586 -0
- package/skills/docx-author/scripts/docx_build.py +995 -0
- package/skills/docx-author/scripts/docx_check.py +1306 -0
- package/skills/docx-author/scripts/docx_doc.py +14 -154
- package/skills/docx-author/scripts/docx_layout.py +490 -0
- package/skills/docx-author/scripts/zh_prose.py +639 -0
- package/skills/docx-author/trigger-cases.json +58 -0
- package/skills/echo-brief/SKILL.md +84 -0
- package/skills/echo-brief/references/echo-format.md +68 -0
- package/skills/echo-brief/trigger-cases.json +40 -0
- package/skills/ecommerce-ops/trigger-cases.json +30 -0
- package/skills/education-training/trigger-cases.json +30 -0
- package/skills/ehs-safety/trigger-cases.json +30 -0
- package/skills/email-writing/trigger-cases.json +30 -0
- package/skills/eng-ai/trigger-cases.json +30 -0
- package/skills/eng-architecture/trigger-cases.json +31 -0
- package/skills/eng-automation/trigger-cases.json +31 -0
- package/skills/eng-collaboration/trigger-cases.json +30 -0
- package/skills/eng-complex-env/trigger-cases.json +30 -0
- package/skills/eng-data-platform/trigger-cases.json +31 -0
- package/skills/eng-database/trigger-cases.json +31 -0
- package/skills/eng-develop/trigger-cases.json +30 -0
- package/skills/eng-docs/SKILL.md +24 -0
- package/skills/eng-docs/trigger-cases.json +30 -0
- package/skills/eng-efficiency/trigger-cases.json +30 -0
- package/skills/eng-infra/trigger-cases.json +30 -0
- package/skills/eng-integration/trigger-cases.json +31 -0
- package/skills/eng-legacy/trigger-cases.json +31 -0
- package/skills/eng-observability/trigger-cases.json +31 -0
- package/skills/eng-ops/trigger-cases.json +31 -0
- package/skills/eng-performance/trigger-cases.json +31 -0
- package/skills/eng-planning/trigger-cases.json +31 -0
- package/skills/eng-quality/SKILL.md +8 -0
- package/skills/eng-quality/trigger-cases.json +31 -0
- package/skills/eng-release/trigger-cases.json +31 -0
- package/skills/eng-reliability/trigger-cases.json +31 -0
- package/skills/eng-requirements/trigger-cases.json +12 -0
- package/skills/eng-research/trigger-cases.json +30 -0
- package/skills/eng-security/trigger-cases.json +31 -0
- package/skills/eng-ux/trigger-cases.json +30 -0
- package/skills/engineering-chain/trigger-cases.json +30 -0
- package/skills/env-offline/trigger-cases.json +31 -0
- package/skills/env-private-deploy/trigger-cases.json +31 -0
- package/skills/env-xinchuang/trigger-cases.json +31 -0
- package/skills/equipment-tpm/trigger-cases.json +30 -0
- package/skills/esg-report/trigger-cases.json +30 -0
- package/skills/event-management/trigger-cases.json +30 -0
- package/skills/excel-analysis-chain/SKILL.md +17 -4
- package/skills/excel-analysis-chain/trigger-cases.json +31 -0
- package/skills/explain-codebase/trigger-cases.json +11 -0
- package/skills/file-ingest/trigger-cases.json +30 -0
- package/skills/finance-analysis/trigger-cases.json +31 -0
- package/skills/finance-chain/trigger-cases.json +31 -0
- package/skills/financial-modeling/trigger-cases.json +31 -0
- package/skills/find-simplifications/SKILL.md +43 -0
- package/skills/find-simplifications/trigger-cases.json +23 -0
- package/skills/foreign-trade/trigger-cases.json +30 -0
- package/skills/funnel-cohort/trigger-cases.json +30 -0
- package/skills/gov-document/trigger-cases.json +30 -0
- package/skills/gui-change-evidence/SKILL.md +47 -0
- package/skills/gui-change-evidence/trigger-cases.json +23 -0
- package/skills/hr-chain/trigger-cases.json +31 -0
- package/skills/hr-operations/trigger-cases.json +30 -0
- package/skills/info-disclosure/trigger-cases.json +31 -0
- package/skills/infra-gateway/trigger-cases.json +31 -0
- package/skills/infra-k8s/trigger-cases.json +31 -0
- package/skills/infra-mq/trigger-cases.json +30 -0
- package/skills/insurance-advisory/trigger-cases.json +30 -0
- package/skills/internal-audit/trigger-cases.json +30 -0
- package/skills/ip-management/trigger-cases.json +31 -0
- package/skills/ipo-listing/trigger-cases.json +31 -0
- package/skills/it-audit/trigger-cases.json +31 -0
- package/skills/itsm-ops/trigger-cases.json +31 -0
- package/skills/knowledge-sop/trigger-cases.json +31 -0
- package/skills/legal-chain/trigger-cases.json +31 -0
- package/skills/legal-contract/trigger-cases.json +30 -0
- package/skills/litigation-support/trigger-cases.json +31 -0
- package/skills/logistics-warehouse/trigger-cases.json +30 -0
- package/skills/long-doc-analysis/trigger-cases.json +31 -0
- package/skills/management-accounting/trigger-cases.json +30 -0
- package/skills/manufacturing-chain/trigger-cases.json +31 -0
- package/skills/manufacturing-quality/trigger-cases.json +30 -0
- package/skills/marketing-analytics/trigger-cases.json +31 -0
- package/skills/marketing-chain/trigger-cases.json +31 -0
- package/skills/meeting-minutes/trigger-cases.json +30 -0
- package/skills/metrics-system/trigger-cases.json +31 -0
- package/skills/negotiation-prep/trigger-cases.json +30 -0
- package/skills/obs-prometheus/trigger-cases.json +31 -0
- package/skills/office-automation/trigger-cases.json +31 -0
- package/skills/okr-review/trigger-cases.json +30 -0
- package/skills/ops-fleet/trigger-cases.json +31 -0
- package/skills/ops-linux/trigger-cases.json +31 -0
- package/skills/optimization-solver/trigger-cases.json +30 -0
- package/skills/patent-disclosure/SKILL.md +105 -0
- package/skills/patent-disclosure/references/figures.md +110 -0
- package/skills/patent-disclosure/references/outline.md +162 -0
- package/skills/patent-disclosure/scripts/patent_docx.py +230 -0
- package/skills/patent-disclosure/trigger-cases.json +31 -0
- package/skills/patent-drafting/SKILL.md +108 -0
- package/skills/patent-drafting/trigger-cases.json +31 -0
- package/skills/patent-portfolio/SKILL.md +81 -0
- package/skills/patent-portfolio/trigger-cases.json +31 -0
- package/skills/pdf-toolkit/trigger-cases.json +31 -0
- package/skills/perf-optimize/trigger-cases.json +31 -0
- package/skills/pivot-crosstab/trigger-cases.json +30 -0
- package/skills/policy-analysis/trigger-cases.json +31 -0
- package/skills/portfolio-analysis/trigger-cases.json +31 -0
- package/skills/ppt-chain/SKILL.md +14 -6
- package/skills/ppt-chain/trigger-cases.json +31 -0
- package/skills/pptx-author/SKILL.md +86 -33
- package/skills/pptx-author/scripts/__pycache__/pptx_check.cpython-312.pyc +0 -0
- package/skills/pptx-author/scripts/__pycache__/pptx_deck.cpython-312.pyc +0 -0
- package/skills/pptx-author/scripts/__pycache__/pptx_layout.cpython-312.pyc +0 -0
- package/skills/pptx-author/scripts/pptx_battery.py +178 -0
- package/skills/pptx-author/scripts/pptx_build.py +119 -0
- package/skills/pptx-author/scripts/pptx_check.py +1182 -0
- package/skills/pptx-author/scripts/pptx_deck.py +736 -145
- package/skills/pptx-author/scripts/pptx_layout.py +384 -0
- package/skills/pptx-author/trigger-cases.json +15 -0
- package/skills/pr-comms/trigger-cases.json +30 -0
- package/skills/procurement-supply/trigger-cases.json +31 -0
- package/skills/product-design/trigger-cases.json +31 -0
- package/skills/production-planning/trigger-cases.json +31 -0
- package/skills/project-management/trigger-cases.json +31 -0
- package/skills/project-proposal/trigger-cases.json +31 -0
- package/skills/prose-standard/SKILL.md +53 -0
- package/skills/prose-standard/trigger-cases.json +23 -0
- package/skills/qa-api-automation/trigger-cases.json +31 -0
- package/skills/qa-mobile-automation/trigger-cases.json +31 -0
- package/skills/qa-ui-automation/trigger-cases.json +31 -0
- package/skills/quant-analysis/trigger-cases.json +31 -0
- package/skills/rag-triage/SKILL.md +64 -0
- package/skills/rag-triage/evals.json +57 -0
- package/skills/rag-triage/references/triage-playbook.md +75 -0
- package/skills/rag-triage/trigger-cases.json +31 -0
- package/skills/real-estate-property/trigger-cases.json +30 -0
- package/skills/refactor-safely/SKILL.md +1 -1
- package/skills/refactor-safely/trigger-cases.json +30 -0
- package/skills/regression-forecast/trigger-cases.json +31 -0
- package/skills/release-checklist/trigger-cases.json +31 -0
- package/skills/report-craft/SKILL.md +76 -0
- package/skills/report-craft/references/feasibility.md +118 -0
- package/skills/report-craft/references/review-checklist.md +51 -0
- package/skills/report-craft/references/visuals.md +114 -0
- package/skills/report-craft/references/writing.md +80 -0
- package/skills/report-craft/scripts/__pycache__/chart_render.cpython-312.pyc +0 -0
- package/skills/report-craft/scripts/__pycache__/fin_model.cpython-312.pyc +0 -0
- package/skills/report-craft/scripts/__pycache__/project_facts.cpython-312.pyc +0 -0
- package/skills/report-craft/scripts/chart_render.py +1222 -0
- package/skills/report-craft/scripts/fin_model.py +602 -0
- package/skills/report-craft/scripts/project_facts.py +580 -0
- package/skills/report-craft/scripts/report_battery.py +349 -0
- package/skills/report-craft/trigger-cases.json +15 -0
- package/skills/retail-store-ops/trigger-cases.json +30 -0
- package/skills/risk-compliance/trigger-cases.json +31 -0
- package/skills/scope-creep/SKILL.md +45 -0
- package/skills/scope-creep/trigger-cases.json +7 -0
- package/skills/sec-auth/trigger-cases.json +31 -0
- package/skills/sec-compliance/trigger-cases.json +31 -0
- package/skills/securities-chain/trigger-cases.json +31 -0
- package/skills/securities-research/trigger-cases.json +31 -0
- package/skills/security-audit/trigger-cases.json +31 -0
- package/skills/slides-html/SKILL.md +25 -10
- package/skills/slides-html/trigger-cases.json +11 -0
- package/skills/sql-analysis/SKILL.md +110 -0
- package/skills/sql-analysis/trigger-cases.json +34 -0
- package/skills/sql-tuning/trigger-cases.json +31 -0
- package/skills/statistical-testing/trigger-cases.json +31 -0
- package/skills/strategy-planning/trigger-cases.json +30 -0
- package/skills/supply-chain/trigger-cases.json +31 -0
- package/skills/tax-compliance/trigger-cases.json +30 -0
- package/skills/tech-doc/SKILL.md +262 -0
- package/skills/tech-doc/scripts/__pycache__/archive_battery.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/archive_build.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/archive_check.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/archive_spec.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/doc_templates.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/archive_battery.py +860 -0
- package/skills/tech-doc/scripts/archive_build.py +599 -0
- package/skills/tech-doc/scripts/archive_check.py +1307 -0
- package/skills/tech-doc/scripts/archive_spec.py +499 -0
- package/skills/tech-doc/scripts/doc_templates.py +624 -0
- package/skills/tech-doc/trigger-cases.json +57 -0
- package/skills/tech-research/trigger-cases.json +31 -0
- package/skills/tender-bidding/trigger-cases.json +31 -0
- package/skills/translation-pro/trigger-cases.json +30 -0
- package/skills/treasury-mgmt/trigger-cases.json +31 -0
- package/skills/trim-cot-leakage/SKILL.md +58 -0
- package/skills/trim-cot-leakage/trigger-cases.json +23 -0
- package/skills/web-craft/SKILL.md +103 -0
- package/skills/web-craft/references/craft-checklist.md +75 -0
- package/skills/web-craft/references/design-direction.md +122 -0
- package/skills/web-craft/references/h5-mobile.md +100 -0
- package/skills/web-craft/references/motion.md +136 -0
- package/skills/web-craft/references/recipes.md +74 -0
- package/skills/web-craft/templates/wedding-invitation.html +1180 -0
- package/skills/web-craft/trigger-cases.json +14 -0
- package/skills/web-scraping/trigger-cases.json +34 -0
- package/skills/web-search/SKILL.md +11 -10
- package/skills/web-search/trigger-cases.json +31 -0
- package/skills/weekly-report/SKILL.md +134 -0
- package/skills/weekly-report/trigger-cases.json +11 -0
- package/skills/write-tests/trigger-cases.json +31 -0
- package/skills/xlsx-analysis/SKILL.md +143 -77
- package/skills/xlsx-analysis/scripts/__pycache__/data_profile.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_build.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_check.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_layout.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_toolkit.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/data_profile.py +391 -0
- package/skills/xlsx-analysis/scripts/xlsx_battery.py +389 -0
- package/skills/xlsx-analysis/scripts/xlsx_build.py +522 -0
- package/skills/xlsx-analysis/scripts/xlsx_check.py +924 -0
- package/skills/xlsx-analysis/scripts/xlsx_layout.py +235 -0
- package/skills/xlsx-analysis/scripts/xlsx_style.py +28 -66
- package/skills/xlsx-analysis/scripts/xlsx_toolkit.py +61 -5
- package/skills/xlsx-analysis/trigger-cases.json +59 -0
- package/squads/hc-defense.md +47 -0
- package/squads/hc-rdarchive.md +110 -0
- package/squads/hc-redteam.md +50 -0
- package/squads/hc-weekly.md +125 -0
- package/src/account.js +1 -0
- package/src/acp.js +1 -0
- package/src/advisor.js +1 -1
- package/src/agent.js +1 -1
- package/src/api.js +1 -1
- package/src/archaeology.js +1 -0
- package/src/ask-ui.js +1 -0
- package/src/assets-import.js +1 -1
- package/src/autofix.js +1 -1
- package/src/automation-tools.js +1 -0
- package/src/automation.js +1 -1
- package/src/autorun.js +1 -1
- package/src/banner.js +1 -1
- package/src/capability-probe.js +1 -0
- package/src/checkpoints.js +1 -1
- package/src/code-tools.js +1 -0
- package/src/codeindex.js +1 -1
- package/src/codemode.js +1 -0
- package/src/command-bridge.js +1 -0
- package/src/command-ui.js +1 -0
- package/src/commands.js +1 -1
- package/src/compaction.js +1 -1
- package/src/computer.js +1 -0
- package/src/config.js +1 -1
- package/src/consult.js +1 -1
- package/src/context-report.js +1 -1
- package/src/context-window.js +1 -0
- package/src/context.js +1 -1
- package/src/crew.js +1 -0
- package/src/crush.js +1 -0
- package/src/daemon.js +1 -1
- package/src/decisions.js +1 -1
- package/src/depaudit.js +1 -0
- package/src/device.js +1 -1
- package/src/diagram-draw.js +1 -0
- package/src/diagram-layouts.js +1 -0
- package/src/diagram-raster.js +1 -0
- package/src/diagram.js +1 -0
- package/src/dictation.js +1 -0
- package/src/diffview.js +1 -1
- package/src/doctor.js +1 -1
- package/src/docx-tools.js +1 -0
- package/src/errkind.js +1 -0
- package/src/evidence.js +1 -0
- package/src/executors.js +1 -1
- package/src/expert-catalog.js +1 -1
- package/src/experts-store.js +1 -1
- package/src/experts.js +1 -1
- package/src/external-agents.js +1 -0
- package/src/forks.js +1 -1
- package/src/fs-observe.js +1 -0
- package/src/gitflow.js +1 -1
- package/src/goalround.js +1 -0
- package/src/hooks.js +1 -1
- package/src/input.js +1 -1
- package/src/insights.js +1 -1
- package/src/jobs.js +1 -0
- package/src/kbsync.js +1 -1
- package/src/lsp.js +1 -0
- package/src/mcp-apps.js +1 -0
- package/src/mcp.js +1 -1
- package/src/memory.js +1 -1
- package/src/notify.js +1 -0
- package/src/permissions.js +1 -1
- package/src/plan-limits.js +1 -0
- package/src/policy.js +1 -0
- package/src/pptx-tools.js +1 -0
- package/src/presence.js +1 -0
- package/src/privatedir.js +1 -0
- package/src/pyruntime.js +1 -1
- package/src/radar.js +1 -0
- package/src/render.js +1 -1
- package/src/repl.js +1 -1
- package/src/report-tools.js +1 -0
- package/src/routing.js +1 -0
- package/src/sandbox-probe.js +1 -0
- package/src/sandbox-win-launcher.mjs +156 -0
- package/src/sandbox.js +1 -0
- package/src/scan.js +1 -1
- package/src/schedule.js +1 -1
- package/src/scopecreep.js +1 -0
- package/src/sdk.js +1 -0
- package/src/selection.js +1 -0
- package/src/session-events.js +1 -0
- package/src/session.js +1 -1
- package/src/sessionindex.js +1 -0
- package/src/sessionlog.js +1 -0
- package/src/skill-catalog.js +1 -0
- package/src/skill-lint.js +1 -0
- package/src/skill-optimize.js +1 -0
- package/src/skill-scan.js +1 -0
- package/src/skills.js +1 -1
- package/src/spill.js +1 -0
- package/src/squad.js +1 -1
- package/src/subagent.js +1 -1
- package/src/surface-state.js +1 -0
- package/src/tapd.js +1 -0
- package/src/terminal-select.js +1 -0
- package/src/terminal.js +1 -0
- package/src/test-runner.js +1 -0
- package/src/todo.js +1 -1
- package/src/toolpipe.js +1 -0
- package/src/tools.js +1 -1
- package/src/toon.js +1 -0
- package/src/transcript.js +1 -1
- package/src/tui/app.js +1 -1
- package/src/tui/command-menu.js +1 -0
- package/src/tui/progress.js +1 -0
- package/src/tui/scroll.js +1 -0
- package/src/tui/welcome.js +1 -0
- package/src/tui/workbench.js +1 -0
- package/src/ui-spec.js +1 -0
- package/src/uitree.js +1 -0
- package/src/uninstall.js +1 -1
- package/src/vision.js +1 -0
- package/src/visual.js +1 -0
- package/src/voice-form.js +1 -0
- package/src/voice.js +1 -0
- package/src/watch.js +1 -1
- package/src/web-audit.js +1 -0
- package/src/web-cdp.js +1 -0
- package/src/web-inspect.js +1 -0
- package/src/websearch.js +1 -0
- package/src/weekly.js +1 -0
- package/src/workflow.js +1 -0
- package/src/worklog.js +1 -0
- package/src/xlsx-tools.js +1 -0
- package/ui/command-picker.js +79 -0
- package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
- package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
- package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
- package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
- package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
- package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
- package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
- package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
- package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
- package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
- package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
- package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
- package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
- package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
- package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
- package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
- package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
- package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
- package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
- package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
- package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
- package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
- package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
- package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# 表格引擎压力测试台:正向跑批 + **反向造缺陷** + 反误报 + 自动校正闭环。
|
|
2
|
+
#
|
|
3
|
+
# 为什么必须有反向:一个只会说「未发现问题」的体检器和没有体检器是一回事,
|
|
4
|
+
# 而且更糟——它给了人一个虚假的「已检查」印象。Excel 尤其如此:它的事故不改变外观,
|
|
5
|
+
# 只改变数字,肉眼根本复核不了,全靠这一层证明检查真的会响。
|
|
6
|
+
#
|
|
7
|
+
# 反误报同等重要,方向相反:分组小计用 SUBTOTAL、标题横幅在表头之上、说明页没有筛选,
|
|
8
|
+
# 这些都是**正确做法**,把它们判成毛病等于逼人把规范写法改坏。
|
|
9
|
+
#
|
|
10
|
+
# 用法:python xlsx_battery.py [份数] [--keep]
|
|
11
|
+
# 改了 xlsx_layout / xlsx_build / xlsx_check 任何一个,**必须重跑**。
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import random
|
|
15
|
+
import shutil
|
|
16
|
+
import sys
|
|
17
|
+
import tempfile
|
|
18
|
+
from collections import Counter
|
|
19
|
+
|
|
20
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
21
|
+
from openpyxl import load_workbook # noqa: E402
|
|
22
|
+
from openpyxl.styles import Font # noqa: E402
|
|
23
|
+
from openpyxl.utils import get_column_letter # noqa: E402
|
|
24
|
+
|
|
25
|
+
import xlsx_build as B # noqa: E402
|
|
26
|
+
import xlsx_check as C # noqa: E402
|
|
27
|
+
import xlsx_layout as L # noqa: E402
|
|
28
|
+
|
|
29
|
+
REGIONS = ['华东', '华南', '华北', '西南', '东北', '西北']
|
|
30
|
+
PROVS = ['上海', '江苏', '广东', '福建', '北京', '四川', '辽宁', '陕西', '浙江', '山东']
|
|
31
|
+
METRICS = [('销售额', 'amount'), ('成本', 'amount'), ('毛利', 'amount'),
|
|
32
|
+
('数量', 'int'), ('完成率', 'percent'), ('客户数', 'int')]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def random_spec(rng):
|
|
36
|
+
prof = rng.choice(['report', 'plain', 'finance'])
|
|
37
|
+
n_sheet = rng.randint(1, 3)
|
|
38
|
+
sheets = []
|
|
39
|
+
for k in range(n_sheet):
|
|
40
|
+
mets = rng.sample(METRICS, rng.randint(1, 4))
|
|
41
|
+
cols = [{'key': 'region', 'header': '大区'}, {'key': 'prov', 'header': '省份'}]
|
|
42
|
+
cols += [{'key': f'm{i}', 'header': h, 'format': f} for i, (h, f) in enumerate(mets)]
|
|
43
|
+
rows = []
|
|
44
|
+
for _ in range(rng.randint(2, 40)):
|
|
45
|
+
r = [rng.choice(REGIONS), rng.choice(PROVS)]
|
|
46
|
+
for _h, f in mets:
|
|
47
|
+
r.append(round(rng.uniform(0, 1), 4) if f == 'percent'
|
|
48
|
+
else (rng.randint(1, 9999) if f == 'int' else round(rng.uniform(-5e4, 5e6), 2)))
|
|
49
|
+
rows.append(r)
|
|
50
|
+
s = {'name': f'表{k+1}_{rng.choice(["明细","汇总","台账"])}', 'columns': cols, 'rows': rows}
|
|
51
|
+
if rng.random() < 0.5:
|
|
52
|
+
s['title'] = f'{rng.choice(REGIONS)}区域{rng.choice(["销售","成本","经营"])}明细'
|
|
53
|
+
if rng.random() < 0.4:
|
|
54
|
+
s['note'] = '口径:含税;单位万元'
|
|
55
|
+
if rng.random() < 0.5:
|
|
56
|
+
s['total'] = {'label': '合计'}
|
|
57
|
+
if rng.random() < 0.35:
|
|
58
|
+
s['group_by'] = 'region'
|
|
59
|
+
if rng.random() < 0.3 and mets:
|
|
60
|
+
s['chart'] = {'type': rng.choice(['bar', 'line']), 'cat': 'prov', 'values': ['m0']}
|
|
61
|
+
if rng.random() < 0.3 and mets:
|
|
62
|
+
s['conditional'] = [{'col': 'm0', 'type': rng.choice(['data_bar', 'color_scale'])}]
|
|
63
|
+
sheets.append(s)
|
|
64
|
+
spec = {'profile': prof, 'sheets': sheets}
|
|
65
|
+
if rng.random() < 0.4:
|
|
66
|
+
spec['cover'] = {'title': '压测报表', 'fields': [['编制单位', '压测'], ['口径', '含税']]}
|
|
67
|
+
return spec
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def forward(n, workdir, verbose=True):
|
|
71
|
+
rng = random.Random(20260901)
|
|
72
|
+
bad, worst = 0, []
|
|
73
|
+
for i in range(n):
|
|
74
|
+
spec = random_spec(rng)
|
|
75
|
+
out = os.path.join(workdir, f'f{i}.xlsx')
|
|
76
|
+
try:
|
|
77
|
+
_p, res, _w = B.build(spec, out)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
bad += 1
|
|
80
|
+
worst.append((f'f{i}', f'生成异常 {type(e).__name__}: {e}'))
|
|
81
|
+
continue
|
|
82
|
+
errs = [x for x in res['issues'] if x['level'] == 'error']
|
|
83
|
+
if errs:
|
|
84
|
+
bad += 1
|
|
85
|
+
worst.append((f"f{i}({spec['profile']})",
|
|
86
|
+
'; '.join(sorted({x['kind'] + ':' + x['msg'][:44] for x in errs}))))
|
|
87
|
+
os.remove(out)
|
|
88
|
+
if verbose and (i + 1) % 40 == 0:
|
|
89
|
+
print(f' 正向 {i+1}/{n} …')
|
|
90
|
+
return bad, worst
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ── 反向:每条规则配一个「故意做坏」的工作簿 ───────────────────────────────
|
|
94
|
+
def _base(workdir, name, rows=None, **kw):
|
|
95
|
+
spec = {'profile': 'report', 'inspect': False, 'sheets': [{
|
|
96
|
+
'name': '明细',
|
|
97
|
+
'columns': [{'key': 'prov', 'header': '省份'},
|
|
98
|
+
{'key': 'amt', 'header': '销售额', 'format': 'amount'},
|
|
99
|
+
{'key': 'qty', 'header': '数量', 'format': 'int'}],
|
|
100
|
+
'rows': rows or [[PROVS[i % len(PROVS)], round(1000 + i * 137.5, 2), i + 1] for i in range(24)],
|
|
101
|
+
**kw}]}
|
|
102
|
+
out = os.path.join(workdir, name)
|
|
103
|
+
B.build(spec, out)
|
|
104
|
+
return out
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _kinds(path):
|
|
108
|
+
return {i['kind'] for i in C.inspect(path)['issues']}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def reverse(workdir):
|
|
112
|
+
cases = []
|
|
113
|
+
|
|
114
|
+
def case(name, expect, mutate, **kw):
|
|
115
|
+
"""造一份缺陷件并断言体检会响。
|
|
116
|
+
|
|
117
|
+
**先验证用例真的改到了文件**:openpyxl 有 ws.cell(r,c,None) 这种「看着在写、其实是
|
|
118
|
+
空操作」的陷阱,用例没造出缺陷却被报成「规则是瞎的」——两件事性质完全不同,
|
|
119
|
+
混在一起会让人去改本来没问题的检查规则。"""
|
|
120
|
+
p = _base(workdir, name + '.xlsx', **kw)
|
|
121
|
+
before = open(p, 'rb').read()
|
|
122
|
+
wb = load_workbook(p)
|
|
123
|
+
mutate(wb)
|
|
124
|
+
wb.save(p)
|
|
125
|
+
if open(p, 'rb').read() == before:
|
|
126
|
+
cases.append((name, expect, False, {'<用例没改到文件>'}))
|
|
127
|
+
return p
|
|
128
|
+
got = _kinds(p)
|
|
129
|
+
cases.append((name, expect, expect in got, got))
|
|
130
|
+
return p
|
|
131
|
+
|
|
132
|
+
def col(wb, letter, first=2, last=25):
|
|
133
|
+
return [wb['明细'][f'{letter}{r}'] for r in range(first, last + 1)]
|
|
134
|
+
|
|
135
|
+
# 数据类
|
|
136
|
+
case('数字存成文本', 'num-as-text',
|
|
137
|
+
lambda wb: [setattr(c, 'value', f'{c.value:,.2f}') for c in col(wb, 'B') if c.value is not None])
|
|
138
|
+
case('数字文本混杂', 'mixed-type',
|
|
139
|
+
lambda wb: [setattr(c, 'value', f'{c.value:,.2f}') for c in col(wb, 'B', 2, 12) if c.value is not None])
|
|
140
|
+
case('日期存成文本', 'date-as-text',
|
|
141
|
+
lambda wb: [setattr(c, 'value', f'2026-{(i % 12) + 1:02d}-01')
|
|
142
|
+
for i, c in enumerate(col(wb, 'A'))])
|
|
143
|
+
case('首尾空格', 'whitespace',
|
|
144
|
+
lambda wb: [setattr(c, 'value', str(c.value) + ' ') for c in col(wb, 'A', 2, 8)])
|
|
145
|
+
case('完全重复行', 'dup-row',
|
|
146
|
+
lambda wb: [wb['明细'].cell(r, j, wb['明细'].cell(2, j).value)
|
|
147
|
+
for r in (20, 21) for j in (1, 2, 3)])
|
|
148
|
+
case('编号列存成数字', 'id-as-number',
|
|
149
|
+
lambda wb: [wb['明细'].cell(1, 1, '客户编号')]
|
|
150
|
+
+ [setattr(c, 'value', 100000 + i) for i, c in enumerate(col(wb, 'A'))])
|
|
151
|
+
|
|
152
|
+
# 结构类
|
|
153
|
+
def merge_body(wb):
|
|
154
|
+
wb['明细'].merge_cells('A5:A8')
|
|
155
|
+
case('数据区合并单元格', 'merged-cells', merge_body)
|
|
156
|
+
case('隐藏行', 'hidden-row',
|
|
157
|
+
lambda wb: [wb['明细'].row_dimensions[r].__setattr__('hidden', True) for r in (5, 6, 7)])
|
|
158
|
+
case('隐藏列', 'hidden-col',
|
|
159
|
+
lambda wb: wb['明细'].column_dimensions['C'].__setattr__('hidden', True))
|
|
160
|
+
case('重名列', 'dup-header', lambda wb: wb['明细'].cell(1, 3, '销售额'))
|
|
161
|
+
# 注意:ws.cell(r,c,None) 是空操作(openpyxl 把 None 当「没传值」),必须显式赋 .value
|
|
162
|
+
case('空列名', 'blank-header', lambda wb: setattr(wb['明细'].cell(1, 3), 'value', None))
|
|
163
|
+
case('隐藏工作表', 'hidden-sheet',
|
|
164
|
+
lambda wb: (wb.create_sheet('偷偷藏的'), setattr(wb['偷偷藏的'], 'sheet_state', 'hidden')))
|
|
165
|
+
case('默认工作表名', 'default-name', lambda wb: wb.create_sheet('Sheet2'))
|
|
166
|
+
case('常量小计混在明细', 'subtotal-inline',
|
|
167
|
+
lambda wb: (wb['明细'].insert_rows(10),
|
|
168
|
+
wb['明细'].cell(10, 1, '小计'), wb['明细'].cell(10, 2, 12345.0)))
|
|
169
|
+
|
|
170
|
+
# 公式类
|
|
171
|
+
case('错误值', 'error-value',
|
|
172
|
+
lambda wb: [wb['明细'].cell(r, 2, '#DIV/0!') for r in (4, 5)])
|
|
173
|
+
case('列内公式不一致', 'mixed-formula',
|
|
174
|
+
lambda wb: [wb['明细'].cell(r, 3, f'=B{r}/100') for r in range(2, 14)])
|
|
175
|
+
case('求和范围没覆盖末行', 'sum-short',
|
|
176
|
+
lambda wb: wb['明细'].cell(27, 2, '=SUM(B2:B10)'))
|
|
177
|
+
case('引用不存在的工作表', 'bad-sheet-ref',
|
|
178
|
+
lambda wb: wb['明细'].cell(27, 2, "='不存在的表'!B2"))
|
|
179
|
+
case('外部工作簿链接', 'external-link',
|
|
180
|
+
lambda wb: wb['明细'].cell(27, 2, "=[预算.xlsx]Sheet1!B2"))
|
|
181
|
+
|
|
182
|
+
# 勾稽类
|
|
183
|
+
def hardcode_total(wb):
|
|
184
|
+
ws = wb['明细']
|
|
185
|
+
ws.cell(26, 1, '合计')
|
|
186
|
+
ws.cell(26, 2, 99999.0) # 写死且与明细对不上
|
|
187
|
+
case('合计写死成常量', 'hardcoded-total', hardcode_total)
|
|
188
|
+
case('合计对不上明细', 'total-mismatch', hardcode_total)
|
|
189
|
+
case('占比合计不等于100%', 'pct-sum',
|
|
190
|
+
lambda wb: [wb['明细'].cell(1, 3, '占比')]
|
|
191
|
+
+ [(setattr(wb['明细'].cell(r, 3), 'value', 0.04),
|
|
192
|
+
setattr(wb['明细'].cell(r, 3), 'number_format', '0.0%'))
|
|
193
|
+
for r in range(2, 26)])
|
|
194
|
+
|
|
195
|
+
# 单列清单**不是**表单:早先 max_col<=2 一并算表单,任何单列工作表都被整组跳过检查。
|
|
196
|
+
case('单列表里的超长单元格', 'long-cell',
|
|
197
|
+
lambda wb: (wb.create_sheet('清单'),
|
|
198
|
+
wb['清单'].append(['说明']),
|
|
199
|
+
[wb['清单'].append(['长' * 900]) for _ in range(3)]))
|
|
200
|
+
|
|
201
|
+
# 两列的数据表**不是**说明页——早先按列数判会把整组检查跳过,坏表反而查不出问题
|
|
202
|
+
def two_col_bad(wb):
|
|
203
|
+
wb.create_sheet('两列表')
|
|
204
|
+
ws = wb['两列表']
|
|
205
|
+
ws.append(['省份', '销售额'])
|
|
206
|
+
for i in range(20):
|
|
207
|
+
ws.append([f'省{i}', f'{1000 + i * 137.5:,.2f}'])
|
|
208
|
+
ws.append(['合计', 99999.0])
|
|
209
|
+
case('两列数据表也要查', 'hardcoded-total', two_col_bad)
|
|
210
|
+
|
|
211
|
+
# 呈现类
|
|
212
|
+
case('列宽不足显示井号', 'too-narrow',
|
|
213
|
+
lambda wb: wb['明细'].column_dimensions['B'].__setattr__('width', 5))
|
|
214
|
+
case('同列格式不统一', 'format-mix',
|
|
215
|
+
lambda wb: [setattr(c, 'number_format', '0') for c in col(wb, 'B', 2, 10)])
|
|
216
|
+
case('金额列无千分位', 'no-thousands',
|
|
217
|
+
lambda wb: [setattr(c, 'number_format', 'General') for c in col(wb, 'B')])
|
|
218
|
+
case('没有冻结窗格', 'no-freeze', lambda wb: setattr(wb['明细'], 'freeze_panes', None))
|
|
219
|
+
|
|
220
|
+
def wide_print(wb):
|
|
221
|
+
ws = wb['明细']
|
|
222
|
+
ws.sheet_properties.pageSetUpPr = None
|
|
223
|
+
ws.page_setup.fitToWidth = None
|
|
224
|
+
for j in range(1, 4):
|
|
225
|
+
ws.column_dimensions[get_column_letter(j)].width = 60
|
|
226
|
+
case('打印会横跨多页', 'print-wide', wide_print)
|
|
227
|
+
return cases
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# ── 反误报:正确做法**绝不能**被判成问题 ───────────────────────────────────
|
|
231
|
+
def negative(workdir):
|
|
232
|
+
cases = []
|
|
233
|
+
|
|
234
|
+
def nocase(name, forbidden, spec):
|
|
235
|
+
out = os.path.join(workdir, 'neg-' + name + '.xlsx')
|
|
236
|
+
B.build(spec, out)
|
|
237
|
+
got = _kinds(out)
|
|
238
|
+
bad = [k for k in forbidden if k in got]
|
|
239
|
+
cases.append((name, not bad, bad, got))
|
|
240
|
+
|
|
241
|
+
# ── 旧版 .xls:读进来只有值,公式/列宽/合并全丢了 ──────────────────────
|
|
242
|
+
# 这些层必须**停掉**而不是照查:照查会凭空报出「合计是写死的常量」「缺冻结窗格」,
|
|
243
|
+
# 而报告看起来跟真的一样。真实归档里 .xls 占一半,这条错了会污染半数结论。
|
|
244
|
+
def legacy_guard():
|
|
245
|
+
out = os.path.join(workdir, 'neg-legacy.xlsx')
|
|
246
|
+
B.build({'profile': 'report', 'sheets': [{
|
|
247
|
+
'name': '明细',
|
|
248
|
+
'columns': [{'key': 'p', 'header': '省份'},
|
|
249
|
+
{'key': 'a', 'header': '销售额', 'format': 'amount'}],
|
|
250
|
+
'rows': [[PROVS[i % len(PROVS)], round(100 + i * 7.5, 2)] for i in range(30)],
|
|
251
|
+
'total': {'label': '总计'}}]}, out)
|
|
252
|
+
real = C.Book(out)
|
|
253
|
+
real.legacy = True # 只翻这一个开关,其余完全相同
|
|
254
|
+
got = {i['kind'] for i in
|
|
255
|
+
(C.check_data(real) + C.check_structure(real) + C.check_reconcile(real))}
|
|
256
|
+
banned = {'hardcoded-total', 'no-freeze', 'too-narrow', 'no-autofilter'}
|
|
257
|
+
bad = sorted(got & banned)
|
|
258
|
+
cases.append(('旧格式不得凭空报公式/呈现类', not bad, bad, got))
|
|
259
|
+
legacy_guard()
|
|
260
|
+
|
|
261
|
+
rows = [[REGIONS[i // 4], PROVS[i % len(PROVS)], round(1000 + i * 97.3, 2), i + 1]
|
|
262
|
+
for i in range(24)]
|
|
263
|
+
cols = [{'key': 'region', 'header': '大区'}, {'key': 'prov', 'header': '省份'},
|
|
264
|
+
{'key': 'amt', 'header': '销售额', 'format': 'amount'},
|
|
265
|
+
{'key': 'qty', 'header': '数量', 'format': 'int'}]
|
|
266
|
+
|
|
267
|
+
nocase('分组小计用SUBTOTAL', ['subtotal-inline', 'mixed-formula', 'hardcoded-total', 'total-mismatch'],
|
|
268
|
+
{'profile': 'report', 'sheets': [{'name': '明细', 'columns': cols, 'rows': rows,
|
|
269
|
+
'group_by': 'region', 'total': {'label': '总计'}}]})
|
|
270
|
+
nocase('标题横幅在表头之上', ['header-not-first', 'merged-cells'],
|
|
271
|
+
{'profile': 'report', 'sheets': [{'name': '明细', 'title': '2026 年 8 月销售明细',
|
|
272
|
+
'note': '口径:含税,单位万元',
|
|
273
|
+
'columns': cols, 'rows': rows}]})
|
|
274
|
+
nocase('说明页两列表单', ['no-freeze', 'no-filter', 'print-wide', 'header-not-first', 'merged-cells'],
|
|
275
|
+
{'profile': 'report', 'cover': {'title': '报表说明', 'fields': [
|
|
276
|
+
['编制单位', '数据一部'], ['口径', '含税'], ['来源', 'ERP']]},
|
|
277
|
+
'sheets': [{'name': '明细', 'columns': cols, 'rows': rows}]})
|
|
278
|
+
nocase('编号列锁成文本', ['id-as-number', 'num-as-text', 'mixed-type'],
|
|
279
|
+
{'profile': 'report', 'sheets': [{'name': '明细', 'columns': [
|
|
280
|
+
{'key': 'no', 'header': '订单编号', 'format': 'text'},
|
|
281
|
+
{'key': 'amt', 'header': '销售额', 'format': 'amount'}],
|
|
282
|
+
'rows': [[f'{i:08d}', round(1000 + i * 13.5, 2)] for i in range(24)]}]})
|
|
283
|
+
# 建表引擎按列格式写类型(date 列写真日期、amount 列的「1,234.56」写成数字)。
|
|
284
|
+
# 写错方向会静默毁数据:把订单号转成数字,前导零当场消失。这两条守着那个转换。
|
|
285
|
+
nocase('编号列写文本不被转数字', ['num-as-text', 'id-as-number', 'mixed-type'],
|
|
286
|
+
{'profile': 'report', 'sheets': [{'name': '明细', 'columns': [
|
|
287
|
+
{'key': 'no', 'header': '订单编号', 'format': 'text'},
|
|
288
|
+
{'key': 'amt', 'header': '销售额', 'format': 'amount'}],
|
|
289
|
+
'rows': [[f'{i:05d}', f'{1000 + i * 11.5:,.2f}'] for i in range(1, 25)]}]})
|
|
290
|
+
nocase('日期列写成真日期', ['date-as-text', 'mixed-type'],
|
|
291
|
+
{'profile': 'report', 'sheets': [{'name': '明细', 'columns': [
|
|
292
|
+
{'key': 'd', 'header': '记账日期', 'format': 'date'},
|
|
293
|
+
{'key': 'amt', 'header': '金额', 'format': 'amount'}],
|
|
294
|
+
'rows': [[f'2026-{i % 12 + 1:02d}-0{i % 9 + 1}', 1000.0 + i] for i in range(1, 25)]}]})
|
|
295
|
+
nocase('短表不必冻结', ['no-freeze', 'no-filter'],
|
|
296
|
+
{'profile': 'report', 'sheets': [{'name': '小表', 'columns': cols, 'rows': rows[:6]}]})
|
|
297
|
+
nocase('大额金额列宽度够', ['too-narrow'],
|
|
298
|
+
{'profile': 'report', 'sheets': [{'name': '明细', 'columns': [
|
|
299
|
+
{'key': 'p', 'header': '省份'}, {'key': 'a', 'header': '销售额', 'format': 'amount'}],
|
|
300
|
+
'rows': [[PROVS[i % 10], round(1234567890.12 + i, 2)] for i in range(24)]}]})
|
|
301
|
+
return cases
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# ── 校正闭环 ───────────────────────────────────────────────────────────────
|
|
305
|
+
FIXABLE = ['数字存成文本', '首尾空格', '数据区合并单元格', '求和范围没覆盖末行',
|
|
306
|
+
'合计写死成常量', '合计对不上明细', '列宽不足显示井号', '同列格式不统一',
|
|
307
|
+
'金额列无千分位', '没有冻结窗格', '打印会横跨多页', '编号列存成数字']
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def fix_round(workdir, cases):
|
|
311
|
+
out = []
|
|
312
|
+
for name, expect, hit, _got in cases:
|
|
313
|
+
if name not in FIXABLE or not hit:
|
|
314
|
+
continue
|
|
315
|
+
src = os.path.join(workdir, name + '.xlsx')
|
|
316
|
+
try:
|
|
317
|
+
dst, done = C.fix(src, os.path.join(workdir, name + '-fixed.xlsx'))
|
|
318
|
+
except Exception as e:
|
|
319
|
+
out.append((name, False, f'修复异常 {type(e).__name__}: {e}'))
|
|
320
|
+
continue
|
|
321
|
+
after, before = C.inspect(dst), C.inspect(src)
|
|
322
|
+
kinds = {i['kind'] for i in after['issues']}
|
|
323
|
+
new_err = ({i['kind'] for i in after['issues'] if i['level'] == 'error'}
|
|
324
|
+
- {i['kind'] for i in before['issues'] if i['level'] == 'error'})
|
|
325
|
+
ok = expect not in kinds and not new_err
|
|
326
|
+
why = (f'修完仍命中 {expect}' if expect in kinds else
|
|
327
|
+
(f'修出新必修 {sorted(new_err)}' if new_err else
|
|
328
|
+
'已修(' + '、'.join(f'{k}{v}' for k, v in done.items()) + ')'))
|
|
329
|
+
out.append((name, ok, why))
|
|
330
|
+
return out
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def main(argv):
|
|
334
|
+
n = 120
|
|
335
|
+
for a in argv:
|
|
336
|
+
if a.isdigit():
|
|
337
|
+
n = int(a)
|
|
338
|
+
keep = '--keep' in argv
|
|
339
|
+
workdir = os.path.join(os.getcwd(), '_xbattery') if keep else tempfile.mkdtemp(prefix='xlsxbat-')
|
|
340
|
+
os.makedirs(workdir, exist_ok=True)
|
|
341
|
+
print(f'压测目录:{workdir}')
|
|
342
|
+
fails = 0
|
|
343
|
+
|
|
344
|
+
print(f'\n[1/4] 正向:随机内容 × 三档规范 × {n} 份,断言合规报表零必修')
|
|
345
|
+
bad, worst = forward(n, workdir)
|
|
346
|
+
if bad:
|
|
347
|
+
fails += 1
|
|
348
|
+
print(f' ✗ {bad}/{n} 份出现必修项:')
|
|
349
|
+
for name, why in worst[:8]:
|
|
350
|
+
print(f' {name} → {why}')
|
|
351
|
+
else:
|
|
352
|
+
print(f' ✓ {n} 份全部零必修')
|
|
353
|
+
|
|
354
|
+
print('\n[2/4] 反向:每条规则配一个故意做坏的工作簿,断言体检真的会响')
|
|
355
|
+
cases = reverse(workdir)
|
|
356
|
+
miss = [c for c in cases if not c[2]]
|
|
357
|
+
for name, expect, hit, got in cases:
|
|
358
|
+
print(f" {'✓' if hit else '✗'} {name:<18} 期望 {expect}"
|
|
359
|
+
+ ('' if hit else f" 实际命中:{sorted(got)[:7]}"))
|
|
360
|
+
if miss:
|
|
361
|
+
fails += 1
|
|
362
|
+
print(f' ✗ {len(miss)}/{len(cases)} 条规则是瞎的')
|
|
363
|
+
else:
|
|
364
|
+
print(f' ✓ {len(cases)}/{len(cases)} 条规则全部命中')
|
|
365
|
+
|
|
366
|
+
print('\n[3/4] 反误报:正确做法不得被判成问题')
|
|
367
|
+
negs = negative(workdir)
|
|
368
|
+
for name, ok, bad2, _got in negs:
|
|
369
|
+
print(f" {'✓' if ok else '✗'} {name:<18}" + ('' if ok else f' 被误报:{bad2}'))
|
|
370
|
+
if any(not ok for _n, ok, _b, _g in negs):
|
|
371
|
+
fails += 1
|
|
372
|
+
else:
|
|
373
|
+
print(f' ✓ {len(negs)}/{len(negs)} 项正确做法零误报')
|
|
374
|
+
|
|
375
|
+
print('\n[4/4] 校正闭环:fix 后必须真干净,且不得修出新必修')
|
|
376
|
+
fr = fix_round(workdir, cases)
|
|
377
|
+
for name, ok, why in fr:
|
|
378
|
+
print(f" {'✓' if ok else '✗'} {name:<18} {why}")
|
|
379
|
+
if any(not ok for _n, ok, _w in fr):
|
|
380
|
+
fails += 1
|
|
381
|
+
|
|
382
|
+
if not keep:
|
|
383
|
+
shutil.rmtree(workdir, ignore_errors=True)
|
|
384
|
+
print('\n' + ('全部通过。' if not fails else f'有 {fails} 个环节未通过。'))
|
|
385
|
+
return 1 if fails else 0
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
if __name__ == '__main__':
|
|
389
|
+
sys.exit(main(sys.argv[1:]))
|