@huicai/cli 2026.714.0 → 2026.721.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/README.md +2 -1
- package/bin/huicai.js +83 -0
- package/package.json +2 -2
- package/skills/ab-testing/SKILL.md +58 -0
- package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
- package/skills/ab-testing/scripts/ab_test.py +71 -0
- package/skills/academic-writing/SKILL.md +54 -0
- package/skills/accounting-ops/SKILL.md +68 -0
- package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
- package/skills/accounting-ops/scripts/acct_tools.py +89 -0
- package/skills/ai-agent/SKILL.md +85 -0
- package/skills/ai-agent/references/tool-design-checklist.md +51 -0
- package/skills/ai-aiops/SKILL.md +73 -0
- package/skills/ai-aiops/references/aiops-rollout-checklist.md +36 -0
- package/skills/ai-coding/SKILL.md +89 -0
- package/skills/ai-coding/references/task-brief-template.md +48 -0
- package/skills/ai-compliance/SKILL.md +74 -0
- package/skills/ai-compliance/references/genai-compliance-checklist.md +40 -0
- package/skills/ai-eval/SKILL.md +80 -0
- package/skills/ai-eval/scripts/eval-runner.py +150 -0
- package/skills/ai-llm-app/SKILL.md +90 -0
- package/skills/ai-llm-app/references/llm-integration-checklist.md +39 -0
- package/skills/ai-mlops/SKILL.md +72 -0
- package/skills/ai-mlops/scripts/token-cost-calc.py +108 -0
- package/skills/ai-multimodal/SKILL.md +86 -0
- package/skills/ai-multimodal/scripts/field-validate.py +182 -0
- package/skills/ai-prompt/SKILL.md +75 -0
- package/skills/ai-prompt/references/prompt-template.md +46 -0
- package/skills/ai-rag/SKILL.md +76 -0
- package/skills/ai-rag/scripts/chunk-inspect.py +129 -0
- package/skills/anomaly-detection/SKILL.md +54 -0
- package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
- package/skills/anomaly-detection/scripts/anomaly.py +99 -0
- package/skills/api-integration/SKILL.md +74 -0
- package/skills/api-integration/references/signing-recipes.md +141 -0
- package/skills/arch-design/SKILL.md +57 -0
- package/skills/arch-distributed/SKILL.md +81 -0
- package/skills/arch-distributed/references/idempotency-review.md +43 -0
- package/skills/arch-multitenant/SKILL.md +73 -0
- package/skills/arch-multitenant/references/tenant-isolation-checklist.md +34 -0
- package/skills/audit-assist/SKILL.md +63 -0
- package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
- package/skills/audit-assist/scripts/audit_sampling.py +93 -0
- package/skills/audit-chain/SKILL.md +60 -0
- package/skills/banking-credit/SKILL.md +59 -0
- package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
- package/skills/banking-credit/scripts/credit_metrics.py +53 -0
- package/skills/brand-design/SKILL.md +56 -0
- package/skills/business-plan/SKILL.md +56 -0
- package/skills/capital-ops/SKILL.md +52 -0
- package/skills/cicd-pipeline/SKILL.md +85 -0
- package/skills/cicd-pipeline/references/pipeline-review-checklist.md +39 -0
- package/skills/content-creation/SKILL.md +56 -0
- package/skills/corporate-governance/SKILL.md +56 -0
- package/skills/crawl-chain/SKILL.md +61 -0
- package/skills/customer-service/SKILL.md +51 -0
- package/skills/data-analysis/SKILL.md +46 -0
- package/skills/data-analysis/scripts/cjk_font.py +56 -0
- package/skills/data-chain/SKILL.md +54 -0
- package/skills/data-governance/SKILL.md +54 -0
- package/skills/data-report-pro/SKILL.md +65 -0
- package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
- package/skills/data-report-pro/scripts/bigdata.py +79 -0
- package/skills/data-streaming/SKILL.md +87 -0
- package/skills/data-streaming/references/job-checklist.md +36 -0
- package/skills/data-wrangling/SKILL.md +52 -0
- package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
- package/skills/data-wrangling/scripts/join_tools.py +99 -0
- package/skills/db-es/SKILL.md +68 -0
- package/skills/db-es/scripts/es-health.sh +72 -0
- package/skills/db-migration/SKILL.md +72 -0
- package/skills/db-migration/scripts/checksum-mysql.sql +46 -0
- package/skills/db-migration/scripts/checksum-postgres.sql +39 -0
- package/skills/db-migration/scripts/compare-reports.py +98 -0
- package/skills/db-mysql/SKILL.md +78 -0
- package/skills/db-mysql/scripts/mysql-health.sql +80 -0
- package/skills/db-postgres/SKILL.md +86 -0
- package/skills/db-postgres/scripts/pg-health.sql +67 -0
- package/skills/db-redis/SKILL.md +83 -0
- package/skills/db-redis/scripts/redis-health.sh +92 -0
- package/skills/dev-cpp/SKILL.md +94 -0
- package/skills/dev-cpp/references/cmake-sanitizer-snippets.md +56 -0
- package/skills/dev-cross-platform/SKILL.md +73 -0
- package/skills/dev-cross-platform/references/capability-matrix-template.md +39 -0
- package/skills/dev-csharp/SKILL.md +96 -0
- package/skills/dev-csharp/references/review-checklist.md +37 -0
- package/skills/dev-frontend/SKILL.md +91 -0
- package/skills/dev-frontend/scripts/bundle-budget.py +106 -0
- package/skills/dev-go/SKILL.md +88 -0
- package/skills/dev-go/scripts/go-triage.sh +54 -0
- package/skills/dev-harmony/SKILL.md +64 -0
- package/skills/dev-harmony/references/harmony-checklist.md +40 -0
- package/skills/dev-java/SKILL.md +77 -0
- package/skills/dev-java/scripts/jvm-triage.sh +84 -0
- package/skills/dev-mobile/SKILL.md +82 -0
- package/skills/dev-mobile/references/store-compliance-checklist.md +33 -0
- package/skills/dev-node/SKILL.md +82 -0
- package/skills/dev-node/scripts/node-triage.sh +78 -0
- package/skills/dev-php/SKILL.md +86 -0
- package/skills/dev-php/scripts/fpm-calc.py +48 -0
- package/skills/dev-python/SKILL.md +81 -0
- package/skills/dev-python/scripts/venv-doctor.sh +75 -0
- package/skills/dev-rust/SKILL.md +84 -0
- package/skills/dev-rust/references/cargo-ci-snippets.md +50 -0
- package/skills/dev-toolchain/SKILL.md +78 -0
- package/skills/dev-toolchain/scripts/secret-scan.sh +83 -0
- package/skills/doc-chain/SKILL.md +58 -0
- package/skills/doc-compare/SKILL.md +44 -0
- package/skills/doc-compare/scripts/compare.py +93 -0
- package/skills/docx-author/SKILL.md +39 -0
- package/skills/docx-author/scripts/docx_doc.py +161 -0
- package/skills/ecommerce-ops/SKILL.md +54 -0
- package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
- package/skills/ecommerce-ops/scripts/gmv_decompose.py +61 -0
- package/skills/education-training/SKILL.md +53 -0
- package/skills/ehs-safety/SKILL.md +63 -0
- package/skills/email-writing/SKILL.md +52 -0
- package/skills/eng-ai/SKILL.md +74 -0
- package/skills/eng-ai/references/ai-feature-checklist.md +36 -0
- package/skills/eng-architecture/SKILL.md +85 -0
- package/skills/eng-architecture/scripts/capacity-calc.py +100 -0
- package/skills/eng-automation/SKILL.md +84 -0
- package/skills/eng-automation/references/automation-inventory.md +38 -0
- package/skills/eng-automation/references/routine-presets.md +73 -0
- package/skills/eng-collaboration/SKILL.md +76 -0
- package/skills/eng-collaboration/references/collab-templates.md +43 -0
- package/skills/eng-complex-env/SKILL.md +84 -0
- package/skills/eng-complex-env/references/env-dossier-template.md +52 -0
- package/skills/eng-complex-env/scripts/env-recon.sh +152 -0
- package/skills/eng-data-platform/SKILL.md +81 -0
- package/skills/eng-data-platform/references/table-spec-template.md +39 -0
- package/skills/eng-database/SKILL.md +79 -0
- package/skills/eng-database/references/schema-review-checklist.md +37 -0
- package/skills/eng-develop/SKILL.md +76 -0
- package/skills/eng-develop/references/dod-checklist.md +43 -0
- package/skills/eng-docs/SKILL.md +71 -0
- package/skills/eng-docs/references/runbook-template.md +57 -0
- package/skills/eng-efficiency/SKILL.md +75 -0
- package/skills/eng-efficiency/scripts/dora-calc.py +104 -0
- package/skills/eng-infra/SKILL.md +81 -0
- package/skills/eng-infra/references/production-readiness.md +46 -0
- package/skills/eng-integration/SKILL.md +82 -0
- package/skills/eng-integration/references/integration-dossier-template.md +38 -0
- package/skills/eng-legacy/SKILL.md +81 -0
- package/skills/eng-legacy/references/modernization-workbook.md +46 -0
- package/skills/eng-observability/SKILL.md +78 -0
- package/skills/eng-observability/scripts/slo-calc.py +82 -0
- package/skills/eng-ops/SKILL.md +74 -0
- package/skills/eng-ops/references/postmortem-template.md +67 -0
- package/skills/eng-performance/SKILL.md +73 -0
- package/skills/eng-performance/scripts/perf-baseline.py +110 -0
- package/skills/eng-planning/SKILL.md +79 -0
- package/skills/eng-planning/scripts/schedule-calc.py +101 -0
- package/skills/eng-quality/SKILL.md +74 -0
- package/skills/eng-quality/references/test-plan-templates.md +46 -0
- package/skills/eng-release/SKILL.md +78 -0
- package/skills/eng-release/references/release-plan-template.md +36 -0
- package/skills/eng-reliability/SKILL.md +72 -0
- package/skills/eng-reliability/references/resilience-review.md +43 -0
- package/skills/eng-requirements/SKILL.md +86 -0
- package/skills/eng-requirements/references/story-templates.md +47 -0
- package/skills/eng-research/SKILL.md +73 -0
- package/skills/eng-research/references/research-templates.md +42 -0
- package/skills/eng-security/SKILL.md +73 -0
- package/skills/eng-security/references/security-review-checklist.md +33 -0
- package/skills/eng-ux/SKILL.md +85 -0
- package/skills/eng-ux/references/ux-review-checklist.md +36 -0
- package/skills/engineering-chain/SKILL.md +101 -0
- package/skills/engineering-chain/references/eng-health-review.md +37 -0
- package/skills/env-offline/SKILL.md +81 -0
- package/skills/env-offline/scripts/bundle-manifest.sh +94 -0
- package/skills/env-private-deploy/SKILL.md +72 -0
- package/skills/env-private-deploy/references/delivery-templates.md +35 -0
- package/skills/env-xinchuang/SKILL.md +73 -0
- package/skills/env-xinchuang/references/adaptation-matrix-template.md +41 -0
- package/skills/equipment-tpm/SKILL.md +56 -0
- package/skills/equipment-tpm/scripts/oee.py +58 -0
- package/skills/esg-report/SKILL.md +58 -0
- package/skills/esg-report/scripts/carbon_calc.py +61 -0
- package/skills/event-management/SKILL.md +51 -0
- package/skills/excel-analysis-chain/SKILL.md +73 -0
- package/skills/file-ingest/SKILL.md +51 -0
- package/skills/file-ingest/scripts/__pycache__/file_toolkit.cpython-312.pyc +0 -0
- package/skills/file-ingest/scripts/file_toolkit.py +404 -0
- package/skills/finance-analysis/SKILL.md +60 -0
- package/skills/finance-analysis/references/ratios.md +54 -0
- package/skills/finance-analysis/scripts/fin_ratios.py +132 -0
- package/skills/finance-chain/SKILL.md +59 -0
- package/skills/financial-modeling/SKILL.md +58 -0
- package/skills/financial-modeling/scripts/fin_model.py +123 -0
- package/skills/foreign-trade/SKILL.md +54 -0
- package/skills/funnel-cohort/SKILL.md +53 -0
- package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
- package/skills/funnel-cohort/scripts/funnel.py +105 -0
- package/skills/gov-document/SKILL.md +57 -0
- package/skills/hr-chain/SKILL.md +51 -0
- package/skills/hr-operations/SKILL.md +53 -0
- package/skills/info-disclosure/SKILL.md +56 -0
- package/skills/infra-gateway/SKILL.md +85 -0
- package/skills/infra-gateway/references/golive-checklist.md +36 -0
- package/skills/infra-k8s/SKILL.md +78 -0
- package/skills/infra-k8s/scripts/k8s-triage.sh +84 -0
- package/skills/infra-mq/SKILL.md +87 -0
- package/skills/infra-mq/references/mq-review-checklist.md +36 -0
- package/skills/insurance-advisory/SKILL.md +57 -0
- package/skills/internal-audit/SKILL.md +66 -0
- package/skills/ip-management/SKILL.md +54 -0
- package/skills/ipo-listing/SKILL.md +60 -0
- package/skills/it-audit/SKILL.md +56 -0
- package/skills/itsm-ops/SKILL.md +55 -0
- package/skills/knowledge-sop/SKILL.md +51 -0
- package/skills/legal-chain/SKILL.md +55 -0
- package/skills/legal-contract/SKILL.md +58 -0
- package/skills/legal-contract/references/contract-checklists.md +59 -0
- package/skills/litigation-support/SKILL.md +53 -0
- package/skills/logistics-warehouse/SKILL.md +53 -0
- package/skills/logistics-warehouse/scripts/freight_check.py +71 -0
- package/skills/long-doc-analysis/SKILL.md +52 -0
- package/skills/management-accounting/SKILL.md +57 -0
- package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
- package/skills/management-accounting/scripts/costing.py +90 -0
- package/skills/manufacturing-chain/SKILL.md +57 -0
- package/skills/manufacturing-quality/SKILL.md +57 -0
- package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
- package/skills/manufacturing-quality/scripts/spc.py +86 -0
- package/skills/marketing-analytics/SKILL.md +59 -0
- package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
- package/skills/marketing-analytics/scripts/rfm.py +60 -0
- package/skills/marketing-chain/SKILL.md +57 -0
- package/skills/meeting-minutes/SKILL.md +55 -0
- package/skills/metrics-system/SKILL.md +55 -0
- package/skills/negotiation-prep/SKILL.md +56 -0
- package/skills/obs-prometheus/SKILL.md +74 -0
- package/skills/obs-prometheus/references/promql-cheatsheet.md +78 -0
- package/skills/office-automation/SKILL.md +54 -0
- package/skills/okr-review/SKILL.md +44 -0
- package/skills/ops-fleet/SKILL.md +69 -0
- package/skills/ops-fleet/scripts/batch-exec-guard.sh +119 -0
- package/skills/ops-linux/SKILL.md +69 -0
- package/skills/ops-linux/scripts/quick-triage.sh +138 -0
- package/skills/optimization-solver/SKILL.md +53 -0
- package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
- package/skills/optimization-solver/scripts/solver.py +107 -0
- package/skills/pdf-toolkit/SKILL.md +43 -0
- package/skills/pdf-toolkit/scripts/pdf_ops.py +137 -0
- package/skills/pivot-crosstab/SKILL.md +44 -0
- package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
- package/skills/pivot-crosstab/scripts/pivot_tools.py +90 -0
- package/skills/policy-analysis/SKILL.md +47 -0
- package/skills/portfolio-analysis/SKILL.md +56 -0
- package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
- package/skills/portfolio-analysis/scripts/portfolio.py +78 -0
- package/skills/ppt-chain/SKILL.md +61 -0
- package/skills/pptx-author/SKILL.md +48 -0
- package/skills/pptx-author/scripts/pptx_deck.py +193 -0
- package/skills/pr-comms/SKILL.md +58 -0
- package/skills/pr-comms/references/pr-templates.md +54 -0
- package/skills/procurement-supply/SKILL.md +47 -0
- package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
- package/skills/procurement-supply/scripts/supply.py +71 -0
- package/skills/product-design/SKILL.md +66 -0
- package/skills/product-design/references/prd-framework.md +60 -0
- package/skills/production-planning/SKILL.md +56 -0
- package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
- package/skills/production-planning/scripts/mrp_capacity.py +70 -0
- package/skills/project-management/SKILL.md +53 -0
- package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
- package/skills/project-management/scripts/cpm.py +84 -0
- package/skills/project-proposal/SKILL.md +54 -0
- package/skills/project-proposal/scripts/budget_sheet.py +90 -0
- package/skills/qa-api-automation/SKILL.md +67 -0
- package/skills/qa-api-automation/references/three-layer-skeleton.md +61 -0
- package/skills/qa-mobile-automation/SKILL.md +70 -0
- package/skills/qa-mobile-automation/references/device-matrix-template.md +34 -0
- package/skills/qa-ui-automation/SKILL.md +80 -0
- package/skills/qa-ui-automation/references/stability-conventions.md +41 -0
- package/skills/quant-analysis/SKILL.md +60 -0
- package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
- package/skills/quant-analysis/scripts/quant.py +102 -0
- package/skills/real-estate-property/SKILL.md +51 -0
- package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
- package/skills/real-estate-property/scripts/property_calc.py +106 -0
- package/skills/regression-forecast/SKILL.md +52 -0
- package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
- package/skills/regression-forecast/scripts/regress.py +106 -0
- package/skills/retail-store-ops/SKILL.md +57 -0
- package/skills/retail-store-ops/scripts/store_ops.py +57 -0
- package/skills/risk-compliance/SKILL.md +51 -0
- package/skills/sec-auth/SKILL.md +89 -0
- package/skills/sec-auth/references/auth-review-checklist.md +38 -0
- package/skills/sec-compliance/SKILL.md +74 -0
- package/skills/sec-compliance/references/dengbao-checklist.md +43 -0
- package/skills/securities-chain/SKILL.md +69 -0
- package/skills/securities-research/SKILL.md +58 -0
- package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
- package/skills/securities-research/scripts/valuation.py +93 -0
- package/skills/slides-html/SKILL.md +50 -0
- package/skills/slides-html/scripts/template.html +117 -0
- package/skills/statistical-testing/SKILL.md +51 -0
- package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
- package/skills/statistical-testing/scripts/stat_tests.py +131 -0
- package/skills/strategy-planning/SKILL.md +53 -0
- package/skills/supply-chain/SKILL.md +53 -0
- package/skills/tax-compliance/SKILL.md +57 -0
- package/skills/tax-compliance/scripts/tax_calc.py +90 -0
- package/skills/tech-research/SKILL.md +55 -0
- package/skills/tech-research/references/research-framework.md +50 -0
- package/skills/tender-bidding/SKILL.md +65 -0
- package/skills/tender-bidding/references/bid-checklist.md +44 -0
- package/skills/translation-pro/SKILL.md +44 -0
- package/skills/treasury-mgmt/SKILL.md +56 -0
- package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -0
- package/skills/treasury-mgmt/scripts/cashflow_forecast.py +51 -0
- package/skills/web-scraping/SKILL.md +81 -0
- package/skills/web-search/SKILL.md +67 -0
- package/skills/xlsx-analysis/SKILL.md +96 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_toolkit.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/xlsx_style.py +71 -0
- package/skills/xlsx-analysis/scripts/xlsx_toolkit.py +335 -0
- package/src/agent.js +1 -1
- package/src/api.js +1 -1
- package/src/codeindex.js +1 -1
- package/src/commands.js +1 -1
- package/src/config.js +1 -1
- package/src/context.js +1 -1
- package/src/doccache.js +1 -0
- package/src/doctor.js +1 -1
- package/src/executors.js +1 -1
- package/src/input.js +1 -1
- package/src/permissions.js +1 -1
- package/src/pyruntime.js +1 -0
- package/src/repl.js +1 -1
- package/src/scan.js +1 -1
- package/src/tools.js +1 -1
- package/src/tui/app.js +1 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Linux 一键体检脚本(ops-linux 四棵决策树的采集与初判,只读零依赖,工具缺失自动降级)。
|
|
3
|
+
# 用法: sh quick-triage.sh [-o 目录] # -o 时全量原始输出落盘该目录并打 tar.gz(事件现场保全)
|
|
4
|
+
# 输出: 屏幕=红黄灯初判+关键数据;落盘=完整命令输出(eng-ops 证据保全先于操作)。
|
|
5
|
+
# 安全: 全部只读命令(eng-automation 只读诊断级);不含 strace/tcpdump 等有代价工具(按需人工上,见链内红线)。
|
|
6
|
+
|
|
7
|
+
OUT=""
|
|
8
|
+
[ "$1" = "-o" ] && OUT="$2"
|
|
9
|
+
TS=$(date '+%Y%m%d_%H%M%S')
|
|
10
|
+
if [ -n "$OUT" ]; then D="$OUT/triage_$TS"; mkdir -p "$D" || { echo "无法创建 $D"; exit 2; }; fi
|
|
11
|
+
|
|
12
|
+
have() { command -v "$1" >/dev/null 2>&1; }
|
|
13
|
+
grab() { # grab <文件名> <命令...> : 落盘完整输出,返回输出内容供解析
|
|
14
|
+
f="$1"; shift
|
|
15
|
+
o=$("$@" 2>&1)
|
|
16
|
+
[ -n "$OUT" ] && echo "$o" > "$D/$f"
|
|
17
|
+
echo "$o"
|
|
18
|
+
}
|
|
19
|
+
FLAG_R=""; FLAG_Y=""
|
|
20
|
+
red() { FLAG_R="$FLAG_R
|
|
21
|
+
[红] $1"; }
|
|
22
|
+
yel() { FLAG_Y="$FLAG_Y
|
|
23
|
+
[黄] $1"; }
|
|
24
|
+
|
|
25
|
+
echo "==================== 一键体检 $TS ===================="
|
|
26
|
+
echo "主机: $(hostname 2>/dev/null) | 内核: $(uname -r 2>/dev/null)"
|
|
27
|
+
|
|
28
|
+
# ---- 全局: load 与核数 ----
|
|
29
|
+
# 实战回写 2026-07:macOS 是 "load averages: a b c"(无逗号)、无 nproc——解析与核数双兜底
|
|
30
|
+
CORES=$( (nproc 2>/dev/null) || (sysctl -n hw.ncpu 2>/dev/null) || grep -c ^processor /proc/cpuinfo 2>/dev/null || echo 1)
|
|
31
|
+
UP=$(grab uptime.txt uptime)
|
|
32
|
+
echo "$UP"
|
|
33
|
+
L1=$(echo "$UP" | sed 's/.*load average[s]*: *//' | awk '{print $1}' | tr -d ',')
|
|
34
|
+
L1I=${L1%%.*}
|
|
35
|
+
[ -n "$L1I" ] && [ "$L1I" -ge "$CORES" ] 2>/dev/null && yel "load($L1)≥核数($CORES) —— 若 CPU 使用率不高,查 D 状态进程(决策树:CPU)"
|
|
36
|
+
|
|
37
|
+
# ---- CPU ----
|
|
38
|
+
echo ""
|
|
39
|
+
echo "---- CPU ----"
|
|
40
|
+
if have vmstat; then
|
|
41
|
+
VM=$(grab vmstat.txt vmstat 1 3)
|
|
42
|
+
echo "$VM" | tail -4
|
|
43
|
+
CS=$(echo "$VM" | tail -1 | awk '{print $12}')
|
|
44
|
+
WA=$(echo "$VM" | tail -1 | awk '{print $16}')
|
|
45
|
+
SY=$(echo "$VM" | tail -1 | awk '{print $14}')
|
|
46
|
+
case "$CS" in [0-9]*) [ "$CS" -gt 100000 ] && yel "上下文切换 cs=$CS —— 线程过多或锁颠簸(strace -c/各语言链画像)";; esac
|
|
47
|
+
case "$WA" in [0-9]*) [ "$WA" -gt 20 ] && red "IO 等待 wa=$WA% —— 转 IO 决策树,加 CPU 无用";; esac
|
|
48
|
+
case "$SY" in [0-9]*) [ "$SY" -gt 30 ] && yel "内核态 sy=$SY% —— 系统调用风暴嫌疑";; esac
|
|
49
|
+
else echo "(无 vmstat,跳过)"; fi
|
|
50
|
+
echo "TOP CPU 进程:"
|
|
51
|
+
grab ps_cpu.txt ps aux | sort -rk3 | head -6 | awk '{printf " %-10s %5s%%CPU %5s%%MEM %s\n",$1,$3,$4,$11}'
|
|
52
|
+
DCNT=$(ps -eo stat 2>/dev/null | grep -c '^D')
|
|
53
|
+
[ "$DCNT" -gt 0 ] 2>/dev/null && { red "D 状态(不可中断)进程 $DCNT 个 —— 等 IO/NFS/内核锁,load 假高元凶"; ps -eo pid,stat,wchan:20,comm 2>/dev/null | awk '$2~/^D/' | head -5 | sed 's/^/ /'; }
|
|
54
|
+
|
|
55
|
+
# ---- 内存 ----
|
|
56
|
+
echo ""
|
|
57
|
+
echo "---- 内存 ----"
|
|
58
|
+
if [ -r /proc/meminfo ]; then
|
|
59
|
+
grab meminfo.txt cat /proc/meminfo >/dev/null
|
|
60
|
+
AV=$(awk '/MemAvailable/{print int($2/1024)}' /proc/meminfo)
|
|
61
|
+
TO=$(awk '/MemTotal/{print int($2/1024)}' /proc/meminfo)
|
|
62
|
+
echo "总 ${TO}M / 可用 ${AV}M (看 available)"
|
|
63
|
+
[ -n "$AV" ] && [ -n "$TO" ] && [ "$TO" -gt 0 ] && [ $(( AV * 100 / TO )) -lt 10 ] && red "可用内存 <10% —— 查 TOP RSS 与泄漏(语言链堆分析)"
|
|
64
|
+
fi
|
|
65
|
+
echo "TOP 内存进程:"
|
|
66
|
+
ps aux 2>/dev/null | sort -rk4 | head -4 | awk '{printf " %-10s %5s%%MEM rss=%sK %s\n",$1,$4,$6,$11}'
|
|
67
|
+
OOM=$(dmesg -T 2>/dev/null | grep -iE 'out of memory|oom-kill' | tail -3)
|
|
68
|
+
if [ -n "$OOM" ]; then red "dmesg 有 OOM 记录(被杀的未必是元凶):"; echo "$OOM" | sed 's/^/ /'; [ -n "$OUT" ] && dmesg -T 2>/dev/null | tail -200 > "$D/dmesg_tail.txt"; fi
|
|
69
|
+
# cgroup(容器)校正
|
|
70
|
+
if [ -f /.dockerenv ] || grep -qsE 'kubepods|docker' /proc/1/cgroup 2>/dev/null; then
|
|
71
|
+
for f in /sys/fs/cgroup/memory.current /sys/fs/cgroup/memory.max; do [ -r "$f" ] && echo " cgroup $(basename "$f"): $(cat "$f")"; done
|
|
72
|
+
echo " (容器内 free/top 显示宿主内存,以 cgroup 为准 —— 决策树:内存假象)"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# ---- 磁盘与 IO ----
|
|
76
|
+
echo ""
|
|
77
|
+
echo "---- 磁盘/IO ----"
|
|
78
|
+
grab df.txt df -h >/dev/null
|
|
79
|
+
# 只读/镜像卷恒 100% 是常态噪声(Linux 的 /dev/loop|squashfs=snap 卷;macOS 的封存系统卷)——
|
|
80
|
+
# 实战回写 2026-07:排除后再判红,别把 snap 卷当磁盘满(ops-linux 磁盘满四查注记)
|
|
81
|
+
DF_NOISE='/dev/loop|squashfs|iso9660|cdrom|/System/Volumes|devfs|map '
|
|
82
|
+
df -h 2>/dev/null | grep -vE "$DF_NOISE" | awk 'NR>1 && $5+0>85 {print " "$0" <== >85%"}' | while read -r l; do echo "$l"; done
|
|
83
|
+
DFULL=$(df -h 2>/dev/null | grep -vE "$DF_NOISE" | awk '$5+0>92 && $1 ~ /^\//' | head -1)
|
|
84
|
+
[ -n "$DFULL" ] && red "磁盘 >92%: $(echo "$DFULL" | awk '{print $6" "$5}') —— 磁盘满四查(du/df 差=已删未释放 lsof +L1;inode;日志;挂载遮蔽)"
|
|
85
|
+
INO=$(df -i / 2>/dev/null | awk 'NR==2{print $5+0}')
|
|
86
|
+
[ -n "$INO" ] && [ "$INO" -gt 85 ] 2>/dev/null && red "根分区 inode ${INO}% —— 海量小文件"
|
|
87
|
+
if have lsof; then
|
|
88
|
+
DEL=$(lsof +L1 2>/dev/null | awk 'NR>1{s+=$7} END{print int(s/1048576)}')
|
|
89
|
+
[ -n "$DEL" ] && [ "$DEL" -gt 500 ] 2>/dev/null && yel "已删除未释放文件占 ${DEL}M —— 找持有进程重启/truncate"
|
|
90
|
+
fi
|
|
91
|
+
if have iostat; then
|
|
92
|
+
IO=$(grab iostat.txt iostat -x 1 2)
|
|
93
|
+
echo "$IO" | awk '/Device|^[a-z]/ && !/loop/' | tail -6 | sed 's/^/ /'
|
|
94
|
+
echo "$IO" | tail -6 | awk '$1!~/loop|Device/ && $NF+0>90 {print " <== "$1" util>90%"}'
|
|
95
|
+
else echo " (无 iostat,IO 细节跳过)"; fi
|
|
96
|
+
|
|
97
|
+
# ---- 网络 ----
|
|
98
|
+
echo ""
|
|
99
|
+
echo "---- 网络 ----"
|
|
100
|
+
if have ss; then
|
|
101
|
+
grab ss_summary.txt ss -s >/dev/null
|
|
102
|
+
EST=$(ss -tan state established 2>/dev/null | wc -l)
|
|
103
|
+
TW=$(ss -tan state time-wait 2>/dev/null | wc -l)
|
|
104
|
+
CW=$(ss -tan state close-wait 2>/dev/null | wc -l)
|
|
105
|
+
echo "连接: ESTAB≈$((EST-1)) TIME_WAIT≈$((TW-1)) CLOSE_WAIT≈$((CW-1))"
|
|
106
|
+
[ "$CW" -gt 500 ] 2>/dev/null && red "CLOSE_WAIT $((CW-1)) —— 代码没关连接(查连接池/HTTP body 未关,dev-* 链)"
|
|
107
|
+
[ "$TW" -gt 20000 ] 2>/dev/null && yel "TIME_WAIT $((TW-1)) —— 主动关闭方常态,过高考虑连接复用/长连接化"
|
|
108
|
+
[ -n "$OUT" ] && ss -tlnp > "$D/listen.txt" 2>&1
|
|
109
|
+
elif have netstat; then netstat -an 2>/dev/null | awk '/tcp/{s[$NF]++} END{for(k in s) printf " %s=%d",k,s[k]; print ""}'
|
|
110
|
+
else echo " (无 ss/netstat)"; fi
|
|
111
|
+
RETR=$(netstat -s 2>/dev/null | grep -iE 'retransmit' | head -2)
|
|
112
|
+
[ -n "$RETR" ] && echo "$RETR" | sed 's/^/ 重传: /'
|
|
113
|
+
if [ -r /proc/sys/net/netfilter/nf_conntrack_count ] && [ -r /proc/sys/net/netfilter/nf_conntrack_max ]; then
|
|
114
|
+
CC=$(cat /proc/sys/net/netfilter/nf_conntrack_count); CM=$(cat /proc/sys/net/netfilter/nf_conntrack_max)
|
|
115
|
+
[ "$CM" -gt 0 ] && [ $(( CC * 100 / CM )) -gt 80 ] && red "conntrack ${CC}/${CM} >80% —— 表满会静默丢连接"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# ---- 句柄与时钟 ----
|
|
119
|
+
echo ""
|
|
120
|
+
echo "---- 句柄/时钟 ----"
|
|
121
|
+
[ -r /proc/sys/fs/file-nr ] && { FN=$(cat /proc/sys/fs/file-nr); echo "系统句柄(file-nr): $FN"; }
|
|
122
|
+
echo "本进程 ulimit -n: $(ulimit -n 2>/dev/null)"
|
|
123
|
+
if have chronyc; then OFF=$(chronyc tracking 2>/dev/null | awk -F': *' '/System time/{print $2}'); echo "时钟偏移: ${OFF:-未知}"
|
|
124
|
+
elif have timedatectl; then echo "对时: $(timedatectl 2>/dev/null | grep -i synchronized | tr -d ' ')"
|
|
125
|
+
else yel "无法确认对时状态 —— 证书/分布式凭证问题先想到时钟"; fi
|
|
126
|
+
|
|
127
|
+
# ---- 汇总 ----
|
|
128
|
+
echo ""
|
|
129
|
+
echo "==================== 初判汇总 ===================="
|
|
130
|
+
[ -n "$FLAG_R" ] && { echo "红灯:$FLAG_R"; }
|
|
131
|
+
[ -n "$FLAG_Y" ] && { echo "黄灯:$FLAG_Y"; }
|
|
132
|
+
[ -z "$FLAG_R$FLAG_Y" ] && echo "未见系统层明显异常 —— 问题可能在应用层(语言链画像)或依赖侧(RED 先于 USE)"
|
|
133
|
+
echo "下一步: 对照红黄灯进 ops-linux 对应决策树;应用层转 dev-* 语言链画像。"
|
|
134
|
+
if [ -n "$OUT" ]; then
|
|
135
|
+
( cd "$OUT" && tar czf "triage_$TS.tar.gz" "triage_$TS" 2>/dev/null )
|
|
136
|
+
echo "现场包: $OUT/triage_$TS.tar.gz (eng-ops 证据保全,先归档再操作)"
|
|
137
|
+
fi
|
|
138
|
+
exit 0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: optimization-solver
|
|
3
|
+
description: 运筹优化与决策求解(线性规划、指派/运输、目标规划、What-if 情景与规划求解、约束优化)。当用户说"最优方案/怎么分配/排产优化/成本最低/利润最大/规划求解/资源怎么配"时使用。Excel 分析链之决策环。
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 运筹优化与决策求解(Excel 分析链 · 决策环;全景见 excel-analysis-chain)
|
|
8
|
+
|
|
9
|
+
对标 Excel"规划求解(Solver)"但无变量数限制,可复算、可版本化。**建模比求解难**——
|
|
10
|
+
本技能重点在把业务问题翻译成"目标+决策变量+约束"三件套。
|
|
11
|
+
|
|
12
|
+
## 建模三件套(求解前必须写清)
|
|
13
|
+
|
|
14
|
+
1. **决策变量**:我们能控制什么(生产多少/怎么分配/选不选)——连续还是整数(0-1 选址、
|
|
15
|
+
整数排产);
|
|
16
|
+
2. **目标函数**:最大化什么/最小化什么(利润/成本/工期)——**单目标先行**,多目标转目标规划;
|
|
17
|
+
3. **约束**:资源上限(产能/预算/工时)、需求下限、平衡关系、逻辑约束——
|
|
18
|
+
**约束漏一条,最优解就是错的**("最优但不可行"是建模事故)。
|
|
19
|
+
|
|
20
|
+
## 求解(`read_skill_file` 读 `scripts/solver.py`,需 scipy:packages=['scipy'])
|
|
21
|
+
|
|
22
|
+
- `linprog_solve()`:线性规划(生产组合/配料/投资分配)——目标系数+约束矩阵,
|
|
23
|
+
输出最优解+目标值+**影子价格**(约束松紧:哪个资源是瓶颈,多一单位值多少钱——
|
|
24
|
+
这是运筹给管理层最有价值的洞察);
|
|
25
|
+
- `assignment()`:指派问题(人-任务/机-单最优匹配,匈牙利算法);
|
|
26
|
+
- `transport()`:运输问题(多产地多销地最小运费,supply-chain 衔接);
|
|
27
|
+
- **整数约束**(0-1 选址/整数产量)用分支定界(scipy milp 或提示装 pulp)——
|
|
28
|
+
连续解取整≠整数最优,别偷懒。
|
|
29
|
+
|
|
30
|
+
## What-if 与敏感性(决策的稳健性)
|
|
31
|
+
|
|
32
|
+
- 情景对比:关键参数(需求/价格/产能)几档下的最优解如何变——**最优解对哪个假设敏感**;
|
|
33
|
+
- 影子价格解读:瓶颈资源加产能的边际收益 → 扩产决策依据;
|
|
34
|
+
- 参数区间:目标系数在多大范围内最优解不变(解的稳定性);
|
|
35
|
+
- **诚实边界**:模型是现实的简化,给出"未建模因素"清单(质量/关系/风险偏好),
|
|
36
|
+
最优解是决策输入不是决策本身。
|
|
37
|
+
|
|
38
|
+
## 典型问题映射
|
|
39
|
+
|
|
40
|
+
生产计划(利润最大化 s.t. 产能物料)→ 与 production-planning 产能负荷衔接;
|
|
41
|
+
投资组合(收益最大 s.t. 预算风险);排班(覆盖需求 min 人力);配料/下料(成本最小 s.t. 规格);
|
|
42
|
+
选址(总成本最小,0-1 变量)。
|
|
43
|
+
|
|
44
|
+
## 验收自查
|
|
45
|
+
|
|
46
|
+
- [ ] 三件套写清,约束无遗漏(解可行)
|
|
47
|
+
- [ ] 整数问题用整数求解,未连续取整
|
|
48
|
+
- [ ] 给了影子价格/瓶颈解读
|
|
49
|
+
- [ ] 做了关键参数敏感性;标注未建模因素
|
|
50
|
+
|
|
51
|
+
## 附件
|
|
52
|
+
|
|
53
|
+
- `scripts/solver.py` — linprog_solve(影子价格)/assignment/transport(scipy);缺 scipy 时给安装提示。可执行自测(有 scipy 才跑求解)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# 运筹求解件(经 run_python 执行,需 scipy:run_python 传 packages=['scipy']):
|
|
2
|
+
# 线性规划(含影子价格)/ 指派 / 运输。缺 scipy 时给清晰安装提示,不崩。
|
|
3
|
+
# 直接执行本文件 = 自测(有 scipy 才执行求解断言)。
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from scipy.optimize import linprog, linear_sum_assignment
|
|
8
|
+
HAS_SCIPY = True
|
|
9
|
+
except ImportError:
|
|
10
|
+
HAS_SCIPY = False
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _need_scipy():
|
|
14
|
+
if not HAS_SCIPY:
|
|
15
|
+
raise ImportError('运筹求解需要 scipy:run_python 调用时传 packages=["scipy"]')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def linprog_solve(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=None, maximize=True, names=None):
|
|
19
|
+
"""线性规划。默认最大化(内部转最小化)。返回最优解/目标值/影子价格(约束对偶值)。"""
|
|
20
|
+
_need_scipy()
|
|
21
|
+
cc = [-x for x in c] if maximize else list(c)
|
|
22
|
+
res = linprog(cc, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=bounds, method='highs')
|
|
23
|
+
if not res.success:
|
|
24
|
+
print(f'[LP] 无可行解/求解失败:{res.message}——检查约束是否矛盾')
|
|
25
|
+
return {'success': False, 'message': res.message}
|
|
26
|
+
obj = -res.fun if maximize else res.fun
|
|
27
|
+
names = names or [f'x{i+1}' for i in range(len(c))]
|
|
28
|
+
sol = dict(zip(names, np.round(res.x, 4)))
|
|
29
|
+
# 影子价格 = 上界约束的对偶值(marginals,highs 提供)
|
|
30
|
+
shadow = None
|
|
31
|
+
try:
|
|
32
|
+
shadow = np.round(res.ineqlin.marginals, 4).tolist() if A_ub is not None else None
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
print(f'[LP] {"最大" if maximize else "最小"}化目标值 = {obj:.4f}')
|
|
36
|
+
print(f' 最优解:{sol}')
|
|
37
|
+
if shadow:
|
|
38
|
+
binding = [i for i, m in enumerate(shadow) if abs(m) > 1e-9]
|
|
39
|
+
print(f' 影子价格(≠0=瓶颈约束):{shadow};瓶颈约束索引 {binding}')
|
|
40
|
+
return {'success': True, 'objective': round(obj, 4), 'solution': sol, 'shadow_prices': shadow}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def assignment(cost_matrix, maximize=False):
|
|
44
|
+
"""指派问题(匈牙利算法):行=人/机,列=任务。返回匹配对与总成本。"""
|
|
45
|
+
_need_scipy()
|
|
46
|
+
cost = np.asarray(cost_matrix, float)
|
|
47
|
+
r, c = linear_sum_assignment(cost, maximize=maximize)
|
|
48
|
+
total = cost[r, c].sum()
|
|
49
|
+
pairs = list(zip(r.tolist(), c.tolist()))
|
|
50
|
+
print(f'[指派] {"最大" if maximize else "最小"}化总额 = {total:.2f} · 匹配 行→列 {pairs}')
|
|
51
|
+
return {'pairs': pairs, 'total': round(float(total), 2)}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def transport(supply, demand, cost):
|
|
55
|
+
"""运输问题(转为 LP):最小化总运费。供需不平衡时自动加零成本虚拟产地/销地配平(标准做法,
|
|
56
|
+
虚拟产地的调运=未满足需求、虚拟销地的调运=过剩供应)——生产中不平衡才是常态。"""
|
|
57
|
+
_need_scipy()
|
|
58
|
+
supply = [float(x) for x in supply]
|
|
59
|
+
demand = [float(x) for x in demand]
|
|
60
|
+
cost = [[float(x) for x in row] for row in cost]
|
|
61
|
+
ts, td = sum(supply), sum(demand)
|
|
62
|
+
note = ''
|
|
63
|
+
if ts + 1e-9 < td: # 供不应求:加虚拟产地(一行零成本),其调运即未满足需求
|
|
64
|
+
cost = cost + [[0.0] * len(demand)]
|
|
65
|
+
supply = supply + [td - ts]
|
|
66
|
+
note = f'(供<需,加虚拟产地补 {td - ts:.2f}=未满足需求)'
|
|
67
|
+
elif td + 1e-9 < ts: # 供过于求:加虚拟销地(一列零成本),其调运即过剩供应
|
|
68
|
+
cost = [row + [0.0] for row in cost]
|
|
69
|
+
demand = demand + [ts - td]
|
|
70
|
+
note = f'(供>需,加虚拟销地吸 {ts - td:.2f}=过剩供应)'
|
|
71
|
+
supply, demand = np.asarray(supply, float), np.asarray(demand, float)
|
|
72
|
+
cost = np.asarray(cost, float)
|
|
73
|
+
m, n = cost.shape
|
|
74
|
+
c = cost.flatten()
|
|
75
|
+
# 供应约束(≤)与需求约束(≥转≤)
|
|
76
|
+
A_ub, b_ub = [], []
|
|
77
|
+
for i in range(m):
|
|
78
|
+
row = np.zeros(m * n)
|
|
79
|
+
row[i * n:(i + 1) * n] = 1
|
|
80
|
+
A_ub.append(row)
|
|
81
|
+
b_ub.append(supply[i])
|
|
82
|
+
for j in range(n):
|
|
83
|
+
row = np.zeros(m * n)
|
|
84
|
+
row[j::n] = -1
|
|
85
|
+
A_ub.append(row)
|
|
86
|
+
b_ub.append(-demand[j])
|
|
87
|
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=[(0, None)] * (m * n), method='highs')
|
|
88
|
+
plan = np.round(res.x.reshape(m, n), 2)
|
|
89
|
+
print(f'[运输] 最小总运费 = {res.fun:.2f}{note}\n 调运方案:\n{plan}')
|
|
90
|
+
return {'total_cost': round(float(res.fun), 2), 'plan': plan}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == '__main__':
|
|
94
|
+
if not HAS_SCIPY:
|
|
95
|
+
print('SKIP solver.py 自测:未装 scipy(生产环境 run_python 传 packages=["scipy"])')
|
|
96
|
+
else:
|
|
97
|
+
# 生产组合:max 3A+5B s.t. A≤4, 2B≤12, 3A+2B≤18
|
|
98
|
+
r = linprog_solve(c=[3, 5], A_ub=[[1, 0], [0, 2], [3, 2]], b_ub=[4, 12, 18],
|
|
99
|
+
bounds=[(0, None), (0, None)], names=['A', 'B'])
|
|
100
|
+
assert r['success'] and abs(r['objective'] - 36.0) < 1e-3 # 经典解 A=2,B=6 → 36
|
|
101
|
+
asg = assignment([[9, 2, 7], [6, 4, 3], [5, 8, 1]])
|
|
102
|
+
assert asg['total'] <= 9 # 最优匹配
|
|
103
|
+
tr = transport([20, 30], [10, 25, 15], [[8, 6, 10], [9, 12, 13]])
|
|
104
|
+
assert tr['total_cost'] > 0 and tr['plan'].sum() == 50
|
|
105
|
+
tr2 = transport([50, 30], [10, 25, 15], [[8, 6, 10], [9, 12, 13]]) # 供>需 80>50:自动配平不崩
|
|
106
|
+
assert tr2['plan'].sum() == 80 # 含虚拟销地吸收的 30 过剩供应
|
|
107
|
+
print('OK solver.py')
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pdf-toolkit
|
|
3
|
+
description: PDF 处理:拆分/合并/抽页/旋转/水印/加密解密/页信息。当用户说"PDF 拆分/合并/加水印/提取某几页/去密码"时使用。
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# PDF 处理(经 run_python 执行,pypdf 已随托管运行时就绪)
|
|
8
|
+
|
|
9
|
+
## 先分清两类任务
|
|
10
|
+
|
|
11
|
+
- **读内容**(总结/翻译/问答):优先 `read_document`(平台解析,支持数百页分段通读,扫描件也能 OCR);pypdf 的 `extract_text()` 对扫描件返回空。
|
|
12
|
+
- **操作文件**(拆/合/水印/加密):用本技能,`read_skill_file` 读 `scripts/pdf_ops.py` 连同任务代码执行。
|
|
13
|
+
|
|
14
|
+
## 代码模式
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from pdf_ops import info, split, merge, rotate, watermark_text, decrypt
|
|
18
|
+
|
|
19
|
+
info('合同.pdf') # 页数/元数据/是否加密(动手前先看)
|
|
20
|
+
split('合同.pdf', '1-3,7', '合同_节选.pdf') # 抽页(页码从 1 起,支持区间与散页)
|
|
21
|
+
merge(['a.pdf', 'b.pdf'], '合并.pdf')
|
|
22
|
+
rotate('扫描.pdf', 90, '转正.pdf') # 顺时针
|
|
23
|
+
watermark_text('报告.pdf', '内部资料 勿外传', '报告_水印.pdf') # 需 reportlab:run_python 传 packages=['reportlab']
|
|
24
|
+
decrypt('加密.pdf', '密码', '解密.pdf') # 仅限用户提供了合法密码的自有文件
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 注意
|
|
28
|
+
|
|
29
|
+
- **水印**依赖 reportlab(生成水印层再逐页叠加):调用 run_python 时带 `packages=['reportlab']` 按需补装;中文水印 helper 已注册 CID 字体(STSong-Light),无需字体文件。
|
|
30
|
+
- 加密 PDF:任何操作前先 `decrypt`;用户没给密码就直说,**不尝试破解**。
|
|
31
|
+
- 页码口径:对用户一律 1 起;pypdf 内部 0 起(helper 已转换)。
|
|
32
|
+
- 大 PDF(几百 MB)逐页流式处理没问题,但别 extract_text 全量打印——输出有 30K 截断。
|
|
33
|
+
|
|
34
|
+
## 验收自查
|
|
35
|
+
|
|
36
|
+
- [ ] 动手前 `info()` 的输出在结果里(页数对得上用户预期)
|
|
37
|
+
- [ ] 每个产物都重开 `info()` 校验页数并 print
|
|
38
|
+
- [ ] 水印/旋转抽查了首页效果描述(页数、方向)
|
|
39
|
+
- [ ] 没有对加密文件做未授权尝试
|
|
40
|
+
|
|
41
|
+
## 附件
|
|
42
|
+
|
|
43
|
+
- `scripts/pdf_ops.py` — info/split/merge/rotate/watermark_text/decrypt 六件套(页码 1 起、产物自动 info 校验);可直接执行自测(自建空白页 PDF 演练)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# PDF 操作六件套(经 run_python 执行)。页码对外一律 1 起;每个产物写完自动 info 校验。
|
|
2
|
+
# 水印需 reportlab(run_python 传 packages=['reportlab'] 按需装),其余仅 pypdf。
|
|
3
|
+
# 直接执行本文件 = 自测:自建空白页 PDF 演练 拆/合/转(水印在装了 reportlab 时才验)。
|
|
4
|
+
from pypdf import PdfReader, PdfWriter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def info(path, quiet=False):
|
|
8
|
+
"""页数/元数据/加密状态。动手前先看,产物写完再看。"""
|
|
9
|
+
r = PdfReader(path)
|
|
10
|
+
enc = r.is_encrypted
|
|
11
|
+
meta = {} if enc else {k.strip('/'): str(v) for k, v in (r.metadata or {}).items()}
|
|
12
|
+
n = 0 if enc else len(r.pages)
|
|
13
|
+
if not quiet:
|
|
14
|
+
print(f'[info] {path} · {"已加密" if enc else f"{n} 页"}'
|
|
15
|
+
+ (f' · {meta}' if meta else ''))
|
|
16
|
+
return {'pages': n, 'encrypted': enc, 'meta': meta}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _parse_ranges(spec, total):
|
|
20
|
+
"""'1-3,7,10-12' → 0 起页索引列表(越界报错)。"""
|
|
21
|
+
out = []
|
|
22
|
+
for part in str(spec).replace(',', ',').split(','):
|
|
23
|
+
part = part.strip()
|
|
24
|
+
if not part:
|
|
25
|
+
continue
|
|
26
|
+
if '-' in part:
|
|
27
|
+
a, b = part.split('-', 1)
|
|
28
|
+
lo, hi = int(a), int(b)
|
|
29
|
+
else:
|
|
30
|
+
lo = hi = int(part)
|
|
31
|
+
if not (1 <= lo <= hi <= total):
|
|
32
|
+
raise ValueError(f'页码越界:{part}(共 {total} 页,页码从 1 起)')
|
|
33
|
+
out.extend(range(lo - 1, hi))
|
|
34
|
+
return out
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def split(path, ranges, out):
|
|
38
|
+
"""抽页:split('a.pdf', '1-3,7', 'out.pdf')。"""
|
|
39
|
+
r = PdfReader(path)
|
|
40
|
+
w = PdfWriter()
|
|
41
|
+
for i in _parse_ranges(ranges, len(r.pages)):
|
|
42
|
+
w.add_page(r.pages[i])
|
|
43
|
+
with open(out, 'wb') as f:
|
|
44
|
+
w.write(f)
|
|
45
|
+
info(out)
|
|
46
|
+
return out
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def merge(paths, out):
|
|
50
|
+
w = PdfWriter()
|
|
51
|
+
for p in paths:
|
|
52
|
+
w.append(p)
|
|
53
|
+
with open(out, 'wb') as f:
|
|
54
|
+
w.write(f)
|
|
55
|
+
info(out)
|
|
56
|
+
return out
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def rotate(path, degrees, out, pages=None):
|
|
60
|
+
"""顺时针旋转(pages 缺省=全部,或 '2-5' 式页码串)。"""
|
|
61
|
+
r = PdfReader(path)
|
|
62
|
+
idx = set(_parse_ranges(pages, len(r.pages))) if pages else set(range(len(r.pages)))
|
|
63
|
+
w = PdfWriter()
|
|
64
|
+
for i, page in enumerate(r.pages):
|
|
65
|
+
w.add_page(page.rotate(degrees) if i in idx else page)
|
|
66
|
+
with open(out, 'wb') as f:
|
|
67
|
+
w.write(f)
|
|
68
|
+
info(out)
|
|
69
|
+
return out
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def watermark_text(path, text, out, opacity=0.15, size=36, angle=30):
|
|
73
|
+
"""全页文字水印。依赖 reportlab(packages=['reportlab']);中文用内置 CID 字体,无需字体文件。"""
|
|
74
|
+
import io
|
|
75
|
+
try:
|
|
76
|
+
from reportlab.lib.pagesizes import A4
|
|
77
|
+
from reportlab.pdfbase import pdfmetrics
|
|
78
|
+
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
|
|
79
|
+
from reportlab.pdfgen import canvas
|
|
80
|
+
except ImportError:
|
|
81
|
+
raise ImportError('水印需要 reportlab:run_python 调用时传 packages=["reportlab"]')
|
|
82
|
+
pdfmetrics.registerFont(UnicodeCIDFont('STSong-Light'))
|
|
83
|
+
r = PdfReader(path)
|
|
84
|
+
w = PdfWriter()
|
|
85
|
+
for page in r.pages:
|
|
86
|
+
pw, ph = float(page.mediabox.width), float(page.mediabox.height)
|
|
87
|
+
buf = io.BytesIO()
|
|
88
|
+
cv = canvas.Canvas(buf, pagesize=(pw, ph))
|
|
89
|
+
cv.setFont('STSong-Light', size)
|
|
90
|
+
cv.setFillGray(0.5, opacity)
|
|
91
|
+
cv.saveState()
|
|
92
|
+
cv.translate(pw / 2, ph / 2)
|
|
93
|
+
cv.rotate(angle)
|
|
94
|
+
cv.drawCentredString(0, 0, text)
|
|
95
|
+
cv.restoreState()
|
|
96
|
+
cv.save()
|
|
97
|
+
buf.seek(0)
|
|
98
|
+
page.merge_page(PdfReader(buf).pages[0])
|
|
99
|
+
w.add_page(page)
|
|
100
|
+
with open(out, 'wb') as f:
|
|
101
|
+
w.write(f)
|
|
102
|
+
info(out)
|
|
103
|
+
return out
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def decrypt(path, password, out):
|
|
107
|
+
"""解密(仅限用户提供合法密码的自有文件;不做破解)。"""
|
|
108
|
+
r = PdfReader(path)
|
|
109
|
+
if r.is_encrypted and not r.decrypt(password):
|
|
110
|
+
raise ValueError('密码不正确')
|
|
111
|
+
w = PdfWriter()
|
|
112
|
+
for page in r.pages:
|
|
113
|
+
w.add_page(page)
|
|
114
|
+
with open(out, 'wb') as f:
|
|
115
|
+
w.write(f)
|
|
116
|
+
info(out)
|
|
117
|
+
return out
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == '__main__':
|
|
121
|
+
w = PdfWriter()
|
|
122
|
+
for _ in range(5):
|
|
123
|
+
w.add_blank_page(width=595, height=842) # A4 空白页 ×5
|
|
124
|
+
with open('pdf_ops_src.pdf', 'wb') as f:
|
|
125
|
+
w.write(f)
|
|
126
|
+
info('pdf_ops_src.pdf')
|
|
127
|
+
split('pdf_ops_src.pdf', '1-2,5', 'pdf_ops_split.pdf')
|
|
128
|
+
merge(['pdf_ops_split.pdf', 'pdf_ops_src.pdf'], 'pdf_ops_merge.pdf')
|
|
129
|
+
rotate('pdf_ops_split.pdf', 90, 'pdf_ops_rot.pdf')
|
|
130
|
+
assert info('pdf_ops_split.pdf', quiet=True)['pages'] == 3
|
|
131
|
+
assert info('pdf_ops_merge.pdf', quiet=True)['pages'] == 8
|
|
132
|
+
try:
|
|
133
|
+
watermark_text('pdf_ops_split.pdf', '内部资料', 'pdf_ops_wm.pdf')
|
|
134
|
+
print('watermark: OK')
|
|
135
|
+
except ImportError as e:
|
|
136
|
+
print('watermark: 跳过(' + str(e).split(':')[0] + ')')
|
|
137
|
+
print('OK')
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pivot-crosstab
|
|
3
|
+
description: 维度透视与交叉分析(数据透视表、交叉表、占比与贡献、趋势环比同比、多维钻取)。当用户说"做个透视/交叉分析/占比/环比同比/按维度拆/分组汇总"时使用。Excel 分析链之透视环。
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 维度透视与交叉分析(Excel 分析链 · 透视环;全景见 excel-analysis-chain)
|
|
8
|
+
|
|
9
|
+
pandas 的 pivot 比 Excel 透视表强在**可复算、可组合、无行数上限**。会 Excel 透视表的用户
|
|
10
|
+
把"拖字段"翻译成参数即可。
|
|
11
|
+
|
|
12
|
+
## 核心操作(`read_skill_file` 读 `scripts/pivot_tools.py`)
|
|
13
|
+
|
|
14
|
+
- `pivot()`:多维透视——index/columns/values/aggfunc(sum/mean/count/median…),
|
|
15
|
+
margins 出总计行列;对齐 Excel"行/列/值/筛选"四区心智;
|
|
16
|
+
- `crosstab_pct()`:交叉表 + 三种占比(占总体/占行/占列——**"占比"要问清占谁的**,
|
|
17
|
+
这是交叉分析最常见的口径错);卡方独立性检验挂钩(转 statistical-testing 判断维度相关性);
|
|
18
|
+
- `share_contribution()`:占比与**贡献度**(谁贡献了增长的大头,联动帕累托);
|
|
19
|
+
- `trend()`:环比(较上期)/同比(较去年同期)/YTD 累计——**日期维表驱动**,不手拼字符串;
|
|
20
|
+
移动平均平滑;增长率的分母陷阱(基期为 0/负)如实标注。
|
|
21
|
+
|
|
22
|
+
## 分析纪律
|
|
23
|
+
|
|
24
|
+
- 先摸结构(data-report-pro 口径:dtypes/空值/量级)再透视;
|
|
25
|
+
- 多维钻取按"总-分-明细"层层下探,每层带结论("华东增长主要来自新客,占增量 68%")而非只出表;
|
|
26
|
+
- **聚合掩盖结构**:均值看分布(配 median 与分位)、总量看构成(配占比)——
|
|
27
|
+
单一聚合数会骗人;
|
|
28
|
+
- 大数据量(百万行)透视用 observed=True + category 类型(data-report-pro 的 dtype 压缩)。
|
|
29
|
+
|
|
30
|
+
## 产出
|
|
31
|
+
|
|
32
|
+
透视结果簿(xlsx-analysis 样式回写:冻结/条件格式/数据条)+ 关键图(data-analysis:
|
|
33
|
+
堆叠柱看构成/折线看趋势/热力图看交叉密度)+ 结论段。可做成月度自动透视例程(office-automation)。
|
|
34
|
+
|
|
35
|
+
## 验收自查
|
|
36
|
+
|
|
37
|
+
- [ ] 占比口径明确(占总/行/列)
|
|
38
|
+
- [ ] 趋势用日期维驱动,基期异常已标注
|
|
39
|
+
- [ ] 聚合配了分布/构成佐证
|
|
40
|
+
- [ ] 钻取每层有结论
|
|
41
|
+
|
|
42
|
+
## 附件
|
|
43
|
+
|
|
44
|
+
- `scripts/pivot_tools.py` — pivot/crosstab_pct/share_contribution/trend(环比同比 YTD);可直接执行自测
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# 透视与交叉分析件(经 run_python 执行):多维透视 / 交叉表三占比 / 占比贡献 / 环比同比 YTD。
|
|
2
|
+
# 直接执行本文件 = 自测。
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def pivot(df, index, values, columns=None, aggfunc='sum', margins=True):
|
|
8
|
+
"""多维透视(对齐 Excel 行/列/值)。index/columns 可为列表(多层)。"""
|
|
9
|
+
pt = pd.pivot_table(df, index=index, columns=columns, values=values,
|
|
10
|
+
aggfunc=aggfunc, margins=margins, margins_name='合计', observed=True)
|
|
11
|
+
print(f'[透视] index={index} columns={columns} values={values} agg={aggfunc}')
|
|
12
|
+
print(pt.round(2).to_markdown())
|
|
13
|
+
return pt
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def crosstab_pct(df, row, col, how='all'):
|
|
17
|
+
"""交叉表 + 占比。how: 'all'占总体 / 'row'占行 / 'col'占列。"""
|
|
18
|
+
ct = pd.crosstab(df[row], df[col], margins=True, margins_name='合计')
|
|
19
|
+
if how == 'row':
|
|
20
|
+
pct = pd.crosstab(df[row], df[col], normalize='index')
|
|
21
|
+
elif how == 'col':
|
|
22
|
+
pct = pd.crosstab(df[row], df[col], normalize='columns')
|
|
23
|
+
else:
|
|
24
|
+
pct = pd.crosstab(df[row], df[col], normalize='all')
|
|
25
|
+
label = {'all': '占总体', 'row': '占行', 'col': '占列'}[how]
|
|
26
|
+
print(f'[交叉] {row} × {col} · {label}')
|
|
27
|
+
print((pct * 100).round(1).to_markdown())
|
|
28
|
+
return ct, pct
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def share_contribution(df, dim, value, base_df=None):
|
|
32
|
+
"""占比与贡献度。给 base_df(上期同结构)则算增量贡献(谁贡献了增长)。"""
|
|
33
|
+
cur = df.groupby(dim, observed=True)[value].sum()
|
|
34
|
+
out = pd.DataFrame({'当期': cur})
|
|
35
|
+
out['占比%'] = (cur / cur.sum() * 100).round(1)
|
|
36
|
+
if base_df is not None:
|
|
37
|
+
base = base_df.groupby(dim, observed=True)[value].sum().reindex(cur.index).fillna(0)
|
|
38
|
+
delta_total = cur.sum() - base.sum()
|
|
39
|
+
out['上期'] = base
|
|
40
|
+
out['增量'] = (cur - base).round(2)
|
|
41
|
+
out['增量贡献%'] = (out['增量'] / delta_total * 100).round(1) if delta_total else np.nan
|
|
42
|
+
out = out.sort_values('当期', ascending=False)
|
|
43
|
+
print(f'[占比贡献] {dim} on {value}' + ('(含增量贡献)' if base_df is not None else ''))
|
|
44
|
+
print(out.to_markdown())
|
|
45
|
+
return out
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def trend(df, date_col, value, freq='M', agg='sum'):
|
|
49
|
+
"""时间趋势:环比(MoM)/同比(YoY,freq=M 时按 12 期)/YTD 累计 + 3 期移动平均。"""
|
|
50
|
+
d = df.copy()
|
|
51
|
+
# 稳健解析:脏日期列(越界年份/混入"汇总"等)用 coerce 归 NaT,再剔除,避免 OutOfBounds/ParseError 打断趋势
|
|
52
|
+
d[date_col] = pd.to_datetime(d[date_col], errors='coerce')
|
|
53
|
+
if not pd.api.types.is_datetime64_any_dtype(d[date_col]):
|
|
54
|
+
print(f'[趋势] {value}:{date_col} 不是可解析的日期列'); return pd.DataFrame()
|
|
55
|
+
d = d[d[date_col].dt.year.between(1900, 2200)]
|
|
56
|
+
if d.empty:
|
|
57
|
+
print(f'[趋势] {value}:无可解析的有效日期'); return pd.DataFrame()
|
|
58
|
+
freq_norm = {'M': 'ME', 'Q': 'QE', 'Y': 'YE'}.get(freq, freq) # pandas 3.0:M→ME
|
|
59
|
+
s = d.set_index(date_col)[value].resample(freq_norm).agg(agg)
|
|
60
|
+
out = pd.DataFrame({'值': s})
|
|
61
|
+
out['环比%'] = (s.pct_change() * 100).round(1)
|
|
62
|
+
yoy_lag = {'M': 12, 'ME': 12, 'Q': 4, 'QE': 4, 'W': 52, 'D': 365}.get(freq, 12)
|
|
63
|
+
out['同比%'] = (s.pct_change(yoy_lag) * 100).round(1)
|
|
64
|
+
out['YTD'] = s.groupby(s.index.year).cumsum()
|
|
65
|
+
out['MA3'] = s.rolling(3).mean().round(2)
|
|
66
|
+
print(f'[趋势] {value} · {freq} · 环比/同比/YTD')
|
|
67
|
+
print(out.round(2).tail(15).to_markdown())
|
|
68
|
+
return out
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == '__main__':
|
|
72
|
+
rng = np.random.default_rng(4)
|
|
73
|
+
n = 2000
|
|
74
|
+
df = pd.DataFrame({
|
|
75
|
+
'地区': rng.choice(['华东', '华南', '华北'], n),
|
|
76
|
+
'渠道': rng.choice(['线上', '线下'], n),
|
|
77
|
+
'日期': pd.Timestamp('2025-01-01') + pd.to_timedelta(rng.integers(0, 540, n), unit='D'),
|
|
78
|
+
'销售额': rng.uniform(100, 1000, n).round(2),
|
|
79
|
+
})
|
|
80
|
+
pt = pivot(df, index='地区', columns='渠道', values='销售额', aggfunc='sum')
|
|
81
|
+
assert '合计' in pt.index
|
|
82
|
+
ct, pct = crosstab_pct(df, '地区', '渠道', how='row')
|
|
83
|
+
assert abs(pct.sum(axis=1).iloc[0] - 1.0) < 1e-9 # 占行:每行和为 1
|
|
84
|
+
cur = df[df['日期'] >= '2025-07-01']
|
|
85
|
+
base = df[df['日期'] < '2025-07-01']
|
|
86
|
+
sc = share_contribution(cur, '地区', '销售额', base_df=base)
|
|
87
|
+
assert '增量贡献%' in sc.columns
|
|
88
|
+
tr = trend(df, '日期', '销售额', freq='M')
|
|
89
|
+
assert '环比%' in tr.columns and '同比%' in tr.columns and tr['YTD'].notna().any()
|
|
90
|
+
print('OK pivot_tools.py')
|