@whyshu/argent-engine 0.0.0-stage → 0.6.1
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/MANIFEST.json +341 -0
- package/engine/VERSION +1 -0
- package/engine/agents/catalog.json +454 -0
- package/engine/configs/_aliases.proposed.json +651 -0
- package/engine/configs/_migration-plan.proposed.json +899 -0
- package/engine/configs/account.json +272 -0
- package/engine/configs/advanced.json +205 -0
- package/engine/configs/host.json +149 -0
- package/engine/configs/hr.json +27 -0
- package/engine/configs/notify.json +264 -0
- package/engine/core/cli/argent.mjs +277 -0
- package/engine/core/contracts/argent.contracts.json +1065 -0
- package/engine/core/contracts/verbs.schema.json +2539 -0
- package/engine/core/lib/argent-datadir.mjs +223 -0
- package/engine/core/lib/audit.mjs +187 -0
- package/engine/core/lib/claim.mjs +258 -0
- package/engine/core/lib/config-catalog.mjs +1172 -0
- package/engine/core/lib/doctor-customer.mjs +577 -0
- package/engine/core/lib/flow-list.mjs +138 -0
- package/engine/core/lib/flow-registry.mjs +48 -0
- package/engine/core/lib/init.mjs +247 -0
- package/engine/core/lib/ledger.mjs +124 -0
- package/engine/core/lib/license-activate.mjs +813 -0
- package/engine/core/lib/license-device.mjs +314 -0
- package/engine/core/lib/license-file.mjs +349 -0
- package/engine/core/lib/license-guard.mjs +123 -0
- package/engine/core/lib/license-ops.mjs +1645 -0
- package/engine/core/lib/license-verdict.mjs +133 -0
- package/engine/core/lib/license.mjs +577 -0
- package/engine/core/lib/notify/dedup.mjs +101 -0
- package/engine/core/lib/notify/feishu.mjs +38 -0
- package/engine/core/lib/notify/index.mjs +95 -0
- package/engine/core/lib/notify/verb.mjs +327 -0
- package/engine/core/lib/notify/webhook.mjs +135 -0
- package/engine/core/lib/notify/wecom.mjs +36 -0
- package/engine/core/lib/python.mjs +145 -0
- package/engine/core/lib/sop.mjs +116 -0
- package/engine/core/lib/versions.mjs +205 -0
- package/engine/core/mcp/client.mjs +67 -0
- package/engine/core/mcp/http.mjs +138 -0
- package/engine/core/mcp/server.mjs +702 -0
- package/engine/scripts/_engine_rules_probe.py +15 -0
- package/engine/scripts/analyze-full-day.py +568 -0
- package/engine/scripts/archive/feishu-bridge.js +246 -0
- package/engine/scripts/argent-agents.mjs +527 -0
- package/engine/scripts/argent-capabilities.mjs +776 -0
- package/engine/scripts/argent-catalog.mjs +186 -0
- package/engine/scripts/argent-datadir.py +62 -0
- package/engine/scripts/argent-doctor.mjs +624 -0
- package/engine/scripts/argent-env-write.mjs +302 -0
- package/engine/scripts/argent-schedule.mjs +620 -0
- package/engine/scripts/argent-setup-check.mjs +369 -0
- package/engine/scripts/argent-upgrade.mjs +1046 -0
- package/engine/scripts/base-bootstrap.sh +576 -0
- package/engine/scripts/billing-mode-daily.sh +13 -0
- package/engine/scripts/build-engine-package.mjs +126 -0
- package/engine/scripts/capability-packs.lock.json +484 -0
- package/engine/scripts/capability_pack.py +1242 -0
- package/engine/scripts/check-1.0-criteria.mjs +355 -0
- package/engine/scripts/check-agent-catalog.mjs +141 -0
- package/engine/scripts/check-billing-mode.sh +378 -0
- package/engine/scripts/check-child-sessions.mjs +83 -0
- package/engine/scripts/check-config-catalog.mjs +677 -0
- package/engine/scripts/check-docs-drift.mjs +522 -0
- package/engine/scripts/check-dsh-web-expose.sh +98 -0
- package/engine/scripts/check-judgment-drift.mjs +403 -0
- package/engine/scripts/check-license.mjs +199 -0
- package/engine/scripts/check-model-auth.py +443 -0
- package/engine/scripts/check-pack-closure.mjs +661 -0
- package/engine/scripts/check-portable-symbols.mjs +116 -0
- package/engine/scripts/check-preset-landing.mjs +124 -0
- package/engine/scripts/check-production-home.mjs +602 -0
- package/engine/scripts/check-role-mapping.mjs +461 -0
- package/engine/scripts/check-sample-redaction.mjs +320 -0
- package/engine/scripts/check-skill-classification.mjs +188 -0
- package/engine/scripts/check-sop-consistency.mjs +431 -0
- package/engine/scripts/check-test-env-safety.mjs +627 -0
- package/engine/scripts/check-version-consistency.mjs +452 -0
- package/engine/scripts/ci-run.sh +558 -0
- package/engine/scripts/collect-evidence.mjs +42 -0
- package/engine/scripts/config-explain.mjs +278 -0
- package/engine/scripts/configure-provider.rb +29 -0
- package/engine/scripts/customer-config-migrate.mjs +179 -0
- package/engine/scripts/customer-preflight.py +892 -0
- package/engine/scripts/customer-readiness.sh +135 -0
- package/engine/scripts/daily-report-inbox.py +242 -0
- package/engine/scripts/daily-report-reconcile.mjs +312 -0
- package/engine/scripts/daily-report.mjs +1310 -0
- package/engine/scripts/daily-report.sh +47 -0
- package/engine/scripts/data/lip-product-dict.json +306 -0
- package/engine/scripts/deliver-customer.mjs +932 -0
- package/engine/scripts/delivery-faces.txt +31 -0
- package/engine/scripts/desktop-trial.sh +146 -0
- package/engine/scripts/dev-window.sh +261 -0
- package/engine/scripts/drill-clean-machine.sh +315 -0
- package/engine/scripts/dsh-api-client.mjs +215 -0
- package/engine/scripts/dsh-backup-scope.sh +83 -0
- package/engine/scripts/dsh-offline-pack.mjs +77 -0
- package/engine/scripts/dsh-overlay-pin.sh +87 -0
- package/engine/scripts/dsh-plane.mjs +153 -0
- package/engine/scripts/dsh-portforward.py +223 -0
- package/engine/scripts/dsh-preflight.mjs +179 -0
- package/engine/scripts/dsh-release-watch-daily.sh +19 -0
- package/engine/scripts/dsh-release-watch.mjs +458 -0
- package/engine/scripts/dsh-remote-check.sh +99 -0
- package/engine/scripts/dsh-restart.sh +179 -0
- package/engine/scripts/dsh-restore.sh +111 -0
- package/engine/scripts/dsh-scope-rewire.mjs +803 -0
- package/engine/scripts/dsh-snapshot.mjs +116 -0
- package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
- package/engine/scripts/dsh-upgrade.mjs +612 -0
- package/engine/scripts/dsh-url.sh +86 -0
- package/engine/scripts/dsh-web-launch.mjs +155 -0
- package/engine/scripts/ensure-store-windows.py +509 -0
- package/engine/scripts/entitlements.py +458 -0
- package/engine/scripts/env_probe.py +411 -0
- package/engine/scripts/feishu-card.mjs +252 -0
- package/engine/scripts/final-check.mjs +43 -0
- package/engine/scripts/fix-skill-frontmatter.py +209 -0
- package/engine/scripts/flow-auto-retry.py +454 -0
- package/engine/scripts/flow-requirements.json +735 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +87 -0
- package/engine/scripts/flow-timers.sh +184 -0
- package/engine/scripts/flow-trigger-wake.mjs +284 -0
- package/engine/scripts/flow-trigger.sh +571 -0
- package/engine/scripts/flow_billing.py +354 -0
- package/engine/scripts/flow_capability_stats.py +314 -0
- package/engine/scripts/flow_case.py +194 -0
- package/engine/scripts/flow_claim.py +497 -0
- package/engine/scripts/flow_config.py +1025 -0
- package/engine/scripts/flow_failure.py +588 -0
- package/engine/scripts/flow_learn.py +105 -0
- package/engine/scripts/flow_ledger.py +235 -0
- package/engine/scripts/flow_policy.py +238 -0
- package/engine/scripts/flow_preflight.py +339 -0
- package/engine/scripts/flow_registry.py +341 -0
- package/engine/scripts/flow_retry.py +304 -0
- package/engine/scripts/flow_return_to_ops.py +191 -0
- package/engine/scripts/flow_source.py +315 -0
- package/engine/scripts/flow_sources/__init__.py +53 -0
- package/engine/scripts/flow_sources/cases_json.py +106 -0
- package/engine/scripts/flow_sources/common.py +428 -0
- package/engine/scripts/flow_sources/feishu_base.py +273 -0
- package/engine/scripts/flow_stale.py +185 -0
- package/engine/scripts/flow_status_reconcile.py +344 -0
- package/engine/scripts/flow_triage.py +849 -0
- package/engine/scripts/flow_write_verify.py +496 -0
- package/engine/scripts/fulfillment.js +586 -0
- package/engine/scripts/gen-agent-bundles.mjs +276 -0
- package/engine/scripts/gen-preset-rows.mjs +259 -0
- package/engine/scripts/init-ml.sh +2278 -0
- package/engine/scripts/install-internal-role.sh +190 -0
- package/engine/scripts/install-license.mjs +641 -0
- package/engine/scripts/install-one-shot.mjs +665 -0
- package/engine/scripts/integration_contract.py +220 -0
- package/engine/scripts/integrations/wenyao/adapter.py +98 -0
- package/engine/scripts/integrations/xzh/adapter.py +75 -0
- package/engine/scripts/invoice-mapping.example.json +40 -0
- package/engine/scripts/invoice-table.sh +195 -0
- package/engine/scripts/invoice_carrier_notice.py +279 -0
- package/engine/scripts/invoice_fail_closeout.py +297 -0
- package/engine/scripts/invoice_lip_runner.py +1508 -0
- package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
- package/engine/scripts/invoice_ready_audit.py +191 -0
- package/engine/scripts/invoice_runner.py +675 -0
- package/engine/scripts/invoice_stageb.py +516 -0
- package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
- package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
- package/engine/scripts/lease.py +377 -0
- package/engine/scripts/lib/agent-install.mjs +649 -0
- package/engine/scripts/lib/argent-datadir.mjs +16 -0
- package/engine/scripts/lib/argent-datadir.sh +18 -0
- package/engine/scripts/lib/argent_datadir.py +285 -0
- package/engine/scripts/lib/argent_filelock.py +507 -0
- package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
- package/engine/scripts/lib/cdp_ports.py +197 -0
- package/engine/scripts/lib/customer-env.mjs +102 -0
- package/engine/scripts/lib/daily-report-core.mjs +517 -0
- package/engine/scripts/lib/dep-declaration.mjs +81 -0
- package/engine/scripts/lib/deps-migrate.mjs +310 -0
- package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
- package/engine/scripts/lib/dsh-scope.mjs +500 -0
- package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
- package/engine/scripts/lib/dsh-versions.mjs +96 -0
- package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
- package/engine/scripts/lib/is-direct-run.mjs +61 -0
- package/engine/scripts/lib/patch-compose.mjs +241 -0
- package/engine/scripts/lib/patch-target.mjs +35 -0
- package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
- package/engine/scripts/lib/plugin-packages.mjs +332 -0
- package/engine/scripts/lib/preset-compat.mjs +127 -0
- package/engine/scripts/lib/preset-install.mjs +575 -0
- package/engine/scripts/lib/preset-mechanism.mjs +56 -0
- package/engine/scripts/lib/preset-sync.mjs +59 -0
- package/engine/scripts/lib/profile-retire.mjs +97 -0
- package/engine/scripts/lib/python-runtime.mjs +190 -0
- package/engine/scripts/lib/registry.mjs +72 -0
- package/engine/scripts/lib/repo-root.sh +56 -0
- package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
- package/engine/scripts/lib/schedule-probe.mjs +835 -0
- package/engine/scripts/lib/scope-pointer.mjs +164 -0
- package/engine/scripts/lib/settings-panels.mjs +816 -0
- package/engine/scripts/lib/stdio_utf8.py +86 -0
- package/engine/scripts/lib/wake-auth.mjs +392 -0
- package/engine/scripts/lib/webdriver_ipc.py +264 -0
- package/engine/scripts/lib/zstd-frames.mjs +104 -0
- package/engine/scripts/license-e2e-drill.mjs +426 -0
- package/engine/scripts/license-gate.mjs +245 -0
- package/engine/scripts/lip_backlog.py +171 -0
- package/engine/scripts/lip_table.py +185 -0
- package/engine/scripts/local-usage.mjs +214 -0
- package/engine/scripts/migrate-providers.mjs +127 -0
- package/engine/scripts/migrate-skills.sh +65 -0
- package/engine/scripts/ml-oauth-setup.mjs +550 -0
- package/engine/scripts/ml-store-ids.json +26 -0
- package/engine/scripts/ml_artifact_attribution.py +333 -0
- package/engine/scripts/ml_capacity_probe.py +1158 -0
- package/engine/scripts/ml_hub_status.py +1556 -0
- package/engine/scripts/ml_schema_check.py +115 -0
- package/engine/scripts/ml_store_ids.py +139 -0
- package/engine/scripts/npm-publish-argent.mjs +499 -0
- package/engine/scripts/npm-publish-argent.sh +20 -0
- package/engine/scripts/orchestrator.py +13811 -0
- package/engine/scripts/pack-plugins.mjs +140 -0
- package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
- package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
- package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
- package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
- package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
- package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
- package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
- package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
- package/engine/scripts/patches/verify-hardening.mjs +167 -0
- package/engine/scripts/patches/verify-session-migration.mjs +149 -0
- package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
- package/engine/scripts/plan-role-rename.mjs +125 -0
- package/engine/scripts/plugin-bootcheck.mjs +451 -0
- package/engine/scripts/plugin-composition.json +15 -0
- package/engine/scripts/poc-mcp-host.mjs +455 -0
- package/engine/scripts/poll-fulfillment.sh +295 -0
- package/engine/scripts/poll-invoice.sh +76 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +71 -0
- package/engine/scripts/provider-switch.sh +146 -0
- package/engine/scripts/provider-sync-target.check.mjs +64 -0
- package/engine/scripts/python-runtime.sh +78 -0
- package/engine/scripts/query_runner.py +310 -0
- package/engine/scripts/release-argent.sh +574 -0
- package/engine/scripts/repair-unresolved-tools.mjs +178 -0
- package/engine/scripts/retire-inventory.mjs +40 -0
- package/engine/scripts/run-tests.sh +257 -0
- package/engine/scripts/s1-audit.mjs +85 -0
- package/engine/scripts/self-sign-license.mjs +179 -0
- package/engine/scripts/session-health-scan.mjs +63 -0
- package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
- package/engine/scripts/setup.sh +22 -0
- package/engine/scripts/skill-install.mjs +629 -0
- package/engine/scripts/sku_translate.py +148 -0
- package/engine/scripts/sync-plugins.sh +90 -0
- package/engine/scripts/sync-presets.sh +27 -0
- package/engine/scripts/sync-scripts.sh +445 -0
- package/engine/scripts/telemetry.py +873 -0
- package/engine/scripts/ui-copy-count.mjs +220 -0
- package/engine/scripts/usage-anchor.sh +137 -0
- package/engine/scripts/usage_report.py +531 -0
- package/engine/scripts/validate-preset-schema.mjs +208 -0
- package/engine/scripts/vertical-init.mjs +323 -0
- package/engine/scripts/vertical-lint.mjs +482 -0
- package/engine/scripts/wenyao-browser.sh +74 -0
- package/engine/scripts/wenyao-fixture-export.py +116 -0
- package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
- package/engine/scripts/wenyao-login.sh +35 -0
- package/engine/scripts/wenyao-template-map.json +292 -0
- package/engine/scripts/wenyao_attach.py +131 -0
- package/engine/scripts/wenyao_download_template.py +144 -0
- package/engine/scripts/wenyao_fill_template.py +568 -0
- package/engine/scripts/wenyao_login.py +145 -0
- package/engine/scripts/wenyao_portal.py +633 -0
- package/engine/scripts/wenyao_recon.py +230 -0
- package/engine/scripts/wenyao_run.py +503 -0
- package/engine/scripts/wenyao_shadow_diff.py +160 -0
- package/engine/scripts/wenyao_skill_contract.py +156 -0
- package/engine/scripts/wenyao_template_fetch.py +175 -0
- package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
- package/engine/scripts/ziniao-auth-probe.sh +268 -0
- package/engine/scripts/ziniao-keepalive.sh +217 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +121 -0
- package/engine/scripts/ziniao-windows.sh +154 -0
- package/engine/skills/README.md +86 -0
- package/engine/skills/argent-dev/SKILL.md +41 -0
- package/engine/skills/internal.json +37 -0
- package/engine/skills/lktop-pm/SKILL.md +35 -0
- package/engine/skills/lktopapp-dev/SKILL.md +34 -0
- package/engine/skills/ml-config-guide/SKILL.md +50 -0
- package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
- package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
- package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
- package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
- package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
- package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
- package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
- package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
- package/engine/skills/ml-invoice/SKILL.md +506 -0
- package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
- package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
- package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
- package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
- package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
- package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
- package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
- package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
- package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
- package/engine/skills/pet-dev/SKILL.md +42 -0
- package/engine/skills/whyshu-pm/SKILL.md +35 -0
- package/engine/skills/whyshu-web-dev/SKILL.md +50 -0
- package/engine/version.mjs +1 -0
- package/engine/verticals/README.md +103 -0
- package/engine/verticals/_engine-words.baseline.txt +237 -0
- package/engine/verticals/_engine-words.json +19 -0
- package/engine/verticals/mercadolibre/README.md +43 -0
- package/engine/verticals/mercadolibre/config.json +709 -0
- package/engine/verticals/mercadolibre/flows.json +738 -0
- package/engine/verticals/mercadolibre/manifest.json +538 -0
- package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
- package/engine/verticals/mercadolibre/preflight.py +143 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
- package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
- package/engine/verticals/mercadolibre/prompts/full.md +91 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
- package/package.json +22 -4
- package/requirements.txt +5 -0
- package/README.md +0 -3
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""flow_billing —— 结果计费:从台账**派生**可计费的成功件 + 月度账单 + value receipt。
|
|
3
|
+
|
|
4
|
+
为什么是"派生"而不是"跑完时写一笔"(2026-09-21 批 C2/C3 的设计取舍):
|
|
5
|
+
· 台账(flow-ledger.jsonl)本来就是**不可变的事实流**(done/failed/blocked/phase…);
|
|
6
|
+
计费口径后来才定,派生脚本可以**追溯**已跑过的历史,且随时可复核;
|
|
7
|
+
· 写时计费会把"口径"烙进业务脚本 —— 改价、改单位、修正误判都要动主链路;
|
|
8
|
+
· 幂等由 `outcomes.jsonl` 里的 `outcomeId` 保证(同一票只落一次),重复跑不会重复计费。
|
|
9
|
+
|
|
10
|
+
三条铁规(来自用户口径 + 市场惯例):
|
|
11
|
+
① **只算成功**:只认终态 `done`(failed / blocked / 在飞 一律不计);
|
|
12
|
+
② **一票一结**:同一 caseId 只算一次(重跑、重建、复核留言都不再加倍);
|
|
13
|
+
③ **证据可核**:done 的 detail 必须命中注册表 `billing.units.<cap>.evidence` 里的标记,
|
|
14
|
+
否则进 `unverified`(列出来给人看,不计费)—— 例:角色发的"交接复核:无需动作"也是 done,
|
|
15
|
+
但它不是交付物。
|
|
16
|
+
|
|
17
|
+
用法:
|
|
18
|
+
python3 scripts/flow_billing.py --report --month 2026-09 # 打账单 + value receipt
|
|
19
|
+
python3 scripts/flow_billing.py --write # 落 outcomes.jsonl(幂等)
|
|
20
|
+
python3 scripts/flow_billing.py --json --month 2026-09 # 机器可读
|
|
21
|
+
python3 scripts/flow_billing.py --unverified # 只看证据不足的票(人工复核)
|
|
22
|
+
退出码:0 正常;2 参数/读取错误
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
import argparse, datetime as dt, hashlib, json, os, pathlib, re, sys, time
|
|
26
|
+
|
|
27
|
+
HERE = pathlib.Path(__file__).resolve().parent
|
|
28
|
+
REGISTRY = HERE / "flow-requirements.json"
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
from flow_registry import consumer_doc as _registry_doc
|
|
32
|
+
except Exception: # noqa: BLE001 —— 半坏机器上退回单文件(升级链路要能跑)
|
|
33
|
+
def _registry_doc(path=None):
|
|
34
|
+
return json.loads((pathlib.Path(path) if path else REGISTRY).read_text(encoding="utf-8"))
|
|
35
|
+
|
|
36
|
+
# 数据目录:**唯一判据**在 `scripts/lib/argent_datadir.py`(三级链 `ARGENT_DATA_DIR` →
|
|
37
|
+
# `$DSH_HOME/argent` → `~/.dsh/argent`,与 JS 内核 `core/lib/argent-datadir.mjs` 同语义)。
|
|
38
|
+
# 历史:先**硬编码** ~/.dsh/argent(隔离实例照样读**生产**台账,2026-10-02 修),
|
|
39
|
+
# 后改两级链(不看 DSH_HOME)—— 都不算数:判据只能有一份。
|
|
40
|
+
sys.path.insert(0, str(HERE / 'lib'))
|
|
41
|
+
from argent_datadir import resolve_data_dir # noqa: E402
|
|
42
|
+
_DATA_DIR = pathlib.Path(resolve_data_dir()['dir'])
|
|
43
|
+
DEFAULT_LEDGER = _DATA_DIR / "flow-ledger.jsonl"
|
|
44
|
+
DEFAULT_OUT = _DATA_DIR / "billing"
|
|
45
|
+
|
|
46
|
+
# `by` → 流程:历史台账里有一批 caseId **没带流程前缀**(早期只写 record_id),
|
|
47
|
+
# 只能靠记账者反推(`ml-fulfillment`/`full-executor` = FULL;两条发票角色各归各的)。
|
|
48
|
+
BY_FLOW = {
|
|
49
|
+
"ml-fulfillment": "full", "full-executor": "full", "orchestrator": "full",
|
|
50
|
+
"gw-ml-invoice-wy": "wenyao-invoice", "wenyao-invoice": "wenyao-invoice",
|
|
51
|
+
"gw-ml-invoice-xzh": "xzh-invoice", "xzh-invoice": "xzh-invoice",
|
|
52
|
+
}
|
|
53
|
+
FLOW_CAPABILITY: dict = {} # 运行时从注册表填:flow → capability
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def load_registry(path: pathlib.Path | None = None) -> dict:
|
|
57
|
+
return _registry_doc(str(path) if path else None)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _alias_of(flow: str) -> tuple:
|
|
61
|
+
"""指向该流程的所有旧名(P2a:台账里的前缀是**写台账时**的流程名)。"""
|
|
62
|
+
try:
|
|
63
|
+
import flow_registry
|
|
64
|
+
doc = flow_registry.load_doc()
|
|
65
|
+
return tuple(k for k, v in (doc.get('aliases') or {}).items() if v == flow)
|
|
66
|
+
except Exception: # noqa: BLE001
|
|
67
|
+
return ()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def flow_of(case_id: str, by: str, flows: set) -> str:
|
|
71
|
+
"""caseId 前缀 → 流程(**规范 id**);没前缀的用记账者反推;都不行 ⇒ 空串(进 unattributed)。
|
|
72
|
+
|
|
73
|
+
P2a:`flows` 里是规范 id(`<vertical>.<flow>`),而台账前缀可能是旧短名 ⇒ 两种都认,
|
|
74
|
+
返回的始终是规范 id(计费单位/能力归属都按规范 id 查)。
|
|
75
|
+
"""
|
|
76
|
+
for f in sorted(flows, key=len, reverse=True):
|
|
77
|
+
if case_id.startswith(f + "__") or any(case_id.startswith(a + "__") for a in _alias_of(f)):
|
|
78
|
+
return f
|
|
79
|
+
# 反推值历史上写的是短名 ⇒ 过一遍别名,统一返回规范 id(P2a)
|
|
80
|
+
guess = BY_FLOW.get(str(by or "").strip(), "")
|
|
81
|
+
if not guess:
|
|
82
|
+
return ""
|
|
83
|
+
try:
|
|
84
|
+
import flow_registry
|
|
85
|
+
return flow_registry.resolve_flow_id(guess)
|
|
86
|
+
except Exception: # noqa: BLE001
|
|
87
|
+
return guess
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def capability_of(flow: str, doc: dict) -> str:
|
|
91
|
+
return str((doc.get("flows", {}).get(flow) or {}).get("capability") or "")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def month_of(ts: float) -> str:
|
|
95
|
+
return dt.datetime.fromtimestamp(float(ts or 0)).strftime("%Y-%m")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def evidence_ok(detail: str, patterns: list) -> bool:
|
|
99
|
+
"""证据判据:**全部**标记都要命中(宁可少算,不可多算)。"""
|
|
100
|
+
text = str(detail or "")
|
|
101
|
+
if not patterns:
|
|
102
|
+
return True
|
|
103
|
+
return all(re.search(str(p), text) for p in patterns)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def derive_outcomes(ledger: pathlib.Path, doc: dict) -> dict:
|
|
107
|
+
"""台账 → {outcomes: [...], unverified: [...], unattributed: [...], stores: {...}}(纯函数)。"""
|
|
108
|
+
flow_caps = {fid: str((f or {}).get("capability") or "") for fid, f in (doc.get("flows") or {}).items()}
|
|
109
|
+
units = (doc.get("billing") or {}).get("units") or {}
|
|
110
|
+
billed: dict = {} # caseId → 已计费的成功件(**一票一结**:同一票只算一次)
|
|
111
|
+
pending: dict = {} # caseId → 证据不足的 done(等后续带明细的 done 把它升级)
|
|
112
|
+
unattributed: list = []
|
|
113
|
+
unattributed_seen: set = set()
|
|
114
|
+
stores: dict = {} # (capability, store) → 出现过的月份集合(日报按店包月要看店数)
|
|
115
|
+
if not ledger.exists():
|
|
116
|
+
return {"outcomes": [], "unverified": [], "unattributed": [], "stores": {}}
|
|
117
|
+
for line in ledger.read_text(encoding="utf-8").splitlines():
|
|
118
|
+
line = line.strip()
|
|
119
|
+
if not line:
|
|
120
|
+
continue
|
|
121
|
+
try:
|
|
122
|
+
e = json.loads(line)
|
|
123
|
+
except Exception:
|
|
124
|
+
continue
|
|
125
|
+
if str(e.get("event") or "") != "done":
|
|
126
|
+
continue
|
|
127
|
+
cid = str(e.get("caseId") or "")
|
|
128
|
+
if not cid or cid in billed:
|
|
129
|
+
continue # 已计费的票:后续 done(复核留言)不再看
|
|
130
|
+
flow = flow_of(cid, e.get("by"), set(flow_caps))
|
|
131
|
+
cap = flow_caps.get(flow) or flow_caps.get(cid.split("__")[0], "")
|
|
132
|
+
if not flow or not cap:
|
|
133
|
+
if cid not in unattributed_seen:
|
|
134
|
+
unattributed_seen.add(cid)
|
|
135
|
+
unattributed.append({"caseId": cid, "by": e.get("by"), "ts": e.get("ts")})
|
|
136
|
+
continue
|
|
137
|
+
spec = units.get(cap) or {}
|
|
138
|
+
detail = str(e.get("detail") or "")
|
|
139
|
+
rec = {"capability": cap, "flow": flow, "caseId": cid, "at": float(e.get("ts") or 0),
|
|
140
|
+
"month": month_of(e.get("ts")), "unit": spec.get("unit", ""),
|
|
141
|
+
"by": str(e.get("by") or ""), "evidence": detail[:300]}
|
|
142
|
+
rec["outcomeId"] = hashlib.sha256(f"{cap}|{cid}".encode("utf-8")).hexdigest()[:16]
|
|
143
|
+
if not evidence_ok(detail, spec.get("evidence") or []):
|
|
144
|
+
# 证据不足:**不立刻进复核清单** —— 实测有票先记一条不带明细的 done、之后才补记带产物的
|
|
145
|
+
# done(后补明细)。先记"待定",等后面的 done 有机会把它升级成计费件。
|
|
146
|
+
if cid not in billed and cid not in pending:
|
|
147
|
+
pending[cid] = {**rec, "reason": "done 的 detail 未命中证据标记(可能是复核/无需动作类留言)"}
|
|
148
|
+
continue
|
|
149
|
+
if cid in billed:
|
|
150
|
+
continue # 一票一结:已计费过就不再计
|
|
151
|
+
billed[cid] = rec
|
|
152
|
+
pending.pop(cid, None) # 升级:从"证据不足"里移除
|
|
153
|
+
unattributed_seen.discard(cid)
|
|
154
|
+
# 日报:从明细里取店铺(幂等标记带 store id;detail 里有「店铺 X」字样)
|
|
155
|
+
if str(spec.get("mode") or "") == "per_store_month":
|
|
156
|
+
m = re.search(r"store-([0-9A-Za-z]+)", detail) or re.search(r"店铺\s*([^\s;;,,]+)", detail)
|
|
157
|
+
store = m.group(1) if m else "(未知店)"
|
|
158
|
+
stores.setdefault((cap, store), set()).add(str(rec["month"]))
|
|
159
|
+
return {"outcomes": list(billed.values()), "unverified": list(pending.values()),
|
|
160
|
+
"unattributed": unattributed, "stores": {f"{k[0]}|{k[1]}": sorted(v) for k, v in stores.items()}}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def month_report(derived: dict, doc: dict, month: str) -> dict:
|
|
164
|
+
"""某月的账单 + value receipt(人类可读的数字都在这里算)。"""
|
|
165
|
+
b = doc.get("billing") or {}
|
|
166
|
+
units = b.get("units") or {}
|
|
167
|
+
wage = float(b.get("laborHourlyYuan") or 0)
|
|
168
|
+
rows, total_fee, total_minutes = [], 0.0, 0.0
|
|
169
|
+
caps = sorted({o["capability"] for o in derived["outcomes"]} | set(units))
|
|
170
|
+
for cap in caps:
|
|
171
|
+
spec = units.get(cap) or {}
|
|
172
|
+
mine = [o for o in derived["outcomes"] if o["capability"] == cap and o["month"] == month]
|
|
173
|
+
mode = str(spec.get("mode") or "")
|
|
174
|
+
if mode == "per_store_month":
|
|
175
|
+
# 日报:按"这个月出现过推送的店数"× 单价(不按天计件)
|
|
176
|
+
n = len([k for k, v in derived["stores"].items() if k.startswith(cap + "|") and month in v])
|
|
177
|
+
label = f"{n} 店×月"
|
|
178
|
+
elif mode == "per_seat_month":
|
|
179
|
+
n, label = 0, "按合同(无流程事件)"
|
|
180
|
+
else:
|
|
181
|
+
n, label = len(mine), f"{len(mine)} 件"
|
|
182
|
+
fee = round(n * float(spec.get("rateYuan") or 0), 2)
|
|
183
|
+
minutes = n * float(spec.get("humanMinutes") or 0)
|
|
184
|
+
total_fee += fee
|
|
185
|
+
total_minutes += minutes
|
|
186
|
+
rows.append({"capability": cap, "unit": spec.get("unit", ""), "count": n, "label": label,
|
|
187
|
+
"rateYuan": float(spec.get("rateYuan") or 0), "feeYuan": fee,
|
|
188
|
+
"humanMinutes": minutes,
|
|
189
|
+
"cases": [o["caseId"] for o in mine][:50]})
|
|
190
|
+
human_yuan = round(total_minutes / 60.0 * wage, 2)
|
|
191
|
+
return {
|
|
192
|
+
"month": month, "currency": str(b.get("currency") or "CNY"),
|
|
193
|
+
"rows": rows, "totalFeeYuan": round(total_fee, 2),
|
|
194
|
+
"humanHours": round(total_minutes / 60.0, 1), "humanValueYuan": human_yuan,
|
|
195
|
+
"laborHourlyYuan": wage,
|
|
196
|
+
"valueMultiple": (round(human_yuan / total_fee, 1) if total_fee else None),
|
|
197
|
+
# 两个计数**故意分开**:outcomesRaw = 台账里的成功件事实(日报两天就是两条),
|
|
198
|
+
# billable = 账单口径的计费单位(日报按店×月合并成 1)—— 混在一起会让人对不上账。
|
|
199
|
+
"counts": {"billable": sum(r["count"] for r in rows),
|
|
200
|
+
"outcomesRaw": len([o for o in derived["outcomes"] if o["month"] == month]),
|
|
201
|
+
"unverified": len([o for o in derived["unverified"] if o["month"] == month]),
|
|
202
|
+
"unattributed": len(derived["unattributed"])},
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def statement_markdown(report: dict, derived: dict, doc: dict, month: str, *, customer: str = "") -> str:
|
|
207
|
+
"""客户版账单(Markdown):完成件数 → 单价 → 金额;附证据清单与"未计费"说明。
|
|
208
|
+
|
|
209
|
+
为什么要有它:工作台那张卡是给我们看的;**能收钱**需要一份客户能复核、能归档、
|
|
210
|
+
能拿去做对公转账凭据的东西 —— 每一件都能追到台账里的那行证据(只算成功、一票一结)。
|
|
211
|
+
"""
|
|
212
|
+
b = doc.get("billing") or {}
|
|
213
|
+
lines = [
|
|
214
|
+
f"# Argent 服务账单({month})",
|
|
215
|
+
"",
|
|
216
|
+
f"- 客户:{customer or '(未填写)'}",
|
|
217
|
+
f"- 计费周期:{month}(自然月)",
|
|
218
|
+
f"- 计费口径:只计**成功交付**的业务对象;同一票只计一次;失败/交还运营不计费",
|
|
219
|
+
f"- 单价来源:注册表 `billing.units`(当前为**占位价**,以合同为准)",
|
|
220
|
+
"",
|
|
221
|
+
"| 能力 | 计费单位 | 数量 | 单价(元) | 金额(元) |",
|
|
222
|
+
"|---|---|---:|---:|---:|",
|
|
223
|
+
]
|
|
224
|
+
for r in report["rows"]:
|
|
225
|
+
if not r["count"] and "合同" not in str(r.get("label", "")):
|
|
226
|
+
continue
|
|
227
|
+
lines.append(f"| {r['capability']} | {r['label']} | {r['count']} | {r['rateYuan']:.2f} | {r['feeYuan']:.2f} |")
|
|
228
|
+
lines += [
|
|
229
|
+
f"| **合计** | | | | **{report['totalFeeYuan']:.2f}** |",
|
|
230
|
+
"",
|
|
231
|
+
f"**本期费用:{report['totalFeeYuan']:.2f} {report['currency']}**",
|
|
232
|
+
"",
|
|
233
|
+
"## 价值对账(value receipt)",
|
|
234
|
+
f"- 本期完成计费单位 **{report['counts']['billable']}**(台账成功件 {report['counts']['outcomesRaw']} 条事实)",
|
|
235
|
+
f"- 等价人工:**{report['humanHours']} 小时** × {report['laborHourlyYuan']:.0f} 元/小时 = **{report['humanValueYuan']:.2f} 元**",
|
|
236
|
+
(f"- 价值 / 费用 = **{report['valueMultiple']}×**" if report.get("valueMultiple") else ""),
|
|
237
|
+
"",
|
|
238
|
+
"## 证据清单(每件都能在台账里复查)",
|
|
239
|
+
]
|
|
240
|
+
month_cases = [o for o in derived["outcomes"] if o["month"] == month]
|
|
241
|
+
for o in sorted(month_cases, key=lambda x: x["at"]):
|
|
242
|
+
when = time.strftime("%m-%d %H:%M", time.localtime(float(o["at"] or 0)))
|
|
243
|
+
ev = str(o.get("evidence") or "").replace("\n", " ")[:110]
|
|
244
|
+
lines.append(f"- `{o['caseId']}`|{o['capability']}|{when}|{ev}")
|
|
245
|
+
if not month_cases:
|
|
246
|
+
lines.append("-(本期无成功件)")
|
|
247
|
+
un = [o for o in derived["unverified"] if o["month"] == month]
|
|
248
|
+
lines += ["", f"## 未计费项({len(un)} 条,**不计入本期费用**)"]
|
|
249
|
+
for o in un[:10]:
|
|
250
|
+
lines.append(f"- `{o['caseId']}`:{o.get('reason', '证据不足')}|{str(o.get('evidence') or '')[:90]}")
|
|
251
|
+
if not un:
|
|
252
|
+
lines.append("-(无)")
|
|
253
|
+
lines += ["", "---", f"_由 `scripts/flow_billing.py --statement` 生成({time.strftime('%Y-%m-%d %H:%M')});"
|
|
254
|
+
f"单价与人工时薪来自注册表,改注册表即改账单口径。_"]
|
|
255
|
+
return "\n".join(lines)
|
|
256
|
+
|
|
257
|
+
def render(report: dict, derived: dict, month: str) -> str:
|
|
258
|
+
L = [f" 账单 {report['month']}({report['currency']},单价与人时来自注册表 billing.units —— 目前是占位)",
|
|
259
|
+
" " + "-" * 96]
|
|
260
|
+
for r in report["rows"]:
|
|
261
|
+
L.append(f" {r['capability']:16} {r['label']:16} × {r['rateYuan']:>6.2f} = {r['feeYuan']:>8.2f}"
|
|
262
|
+
f" 人工等价 {r['humanMinutes']/60.0:>5.1f} 小时")
|
|
263
|
+
L.append(" " + "-" * 96)
|
|
264
|
+
L.append(f" 合计费用 {report['totalFeeYuan']:.2f} {report['currency']}"
|
|
265
|
+
f"|等价人工 {report['humanHours']} 小时 = {report['humanValueYuan']:.2f} {report['currency']}"
|
|
266
|
+
f"(时薪 {report['laborHourlyYuan']:.0f})"
|
|
267
|
+
+ (f"|价值/费用 = {report['valueMultiple']}×" if report["valueMultiple"] else ""))
|
|
268
|
+
L.append(f" 本月计费单位 {report['counts']['billable']}(台账成功件 {report['counts']['outcomesRaw']} 条事实)"
|
|
269
|
+
f"|证据不足(未计费,待复核){report['counts']['unverified']}"
|
|
270
|
+
f"|无法归属 {report['counts']['unattributed']}")
|
|
271
|
+
for o in [x for x in derived["unverified"] if x["month"] == month][:5]:
|
|
272
|
+
L.append(f" ⚠️ 未计费 {o['caseId']}:{o['reason']}|{o['evidence'][:80]}")
|
|
273
|
+
return "\n".join(L)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def main(argv=None) -> int:
|
|
277
|
+
ap = argparse.ArgumentParser(description="结果计费:台账 → 成功件 → 月结账单 + value receipt")
|
|
278
|
+
ap.add_argument("--ledger", default=str(DEFAULT_LEDGER))
|
|
279
|
+
ap.add_argument("--out-dir", default=str(DEFAULT_OUT))
|
|
280
|
+
ap.add_argument("--month", help="YYYY-MM(缺省=当前月)")
|
|
281
|
+
ap.add_argument("--report", action="store_true", help="打账单(人类可读)")
|
|
282
|
+
ap.add_argument("--write", action="store_true", help="把新出现的成功件落 outcomes.jsonl(幂等)")
|
|
283
|
+
ap.add_argument("--unverified", action="store_true", help="只列证据不足的票")
|
|
284
|
+
ap.add_argument("--statement", action="store_true",
|
|
285
|
+
help="输出**客户版账单**(Markdown:件数/单价/金额 + 价值对账 + 逐件证据)")
|
|
286
|
+
ap.add_argument("--customer", help="账单抬头(客户名)")
|
|
287
|
+
ap.add_argument("--out", help="--statement 的落盘路径(省略则打印到 stdout)")
|
|
288
|
+
ap.add_argument("--json", action="store_true")
|
|
289
|
+
ap.add_argument("--no-snapshot", action="store_true",
|
|
290
|
+
help="只读:不写 <月>.json 快照(工作台控制台轮询用;快照由 --report 或已排期的月结任务写)")
|
|
291
|
+
a = ap.parse_args(argv)
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
doc = load_registry()
|
|
295
|
+
except Exception as exc: # noqa: BLE001
|
|
296
|
+
print(f"❌ 读取注册表失败:{exc}", file=sys.stderr)
|
|
297
|
+
return 2
|
|
298
|
+
ledger = pathlib.Path(a.ledger).expanduser()
|
|
299
|
+
derived = derive_outcomes(ledger, doc)
|
|
300
|
+
month = a.month or dt.datetime.now().strftime("%Y-%m")
|
|
301
|
+
|
|
302
|
+
if a.unverified:
|
|
303
|
+
rows = [o for o in derived["unverified"] if o["month"] == month]
|
|
304
|
+
if a.json:
|
|
305
|
+
print(json.dumps(rows, ensure_ascii=False, indent=2))
|
|
306
|
+
else:
|
|
307
|
+
print(f" 证据不足(不计费){len(rows)} 条:")
|
|
308
|
+
for o in rows:
|
|
309
|
+
print(f" {o['caseId']:34} {o['capability']:14}|{o['evidence'][:100]}")
|
|
310
|
+
return 0
|
|
311
|
+
|
|
312
|
+
out_dir = pathlib.Path(a.out_dir).expanduser()
|
|
313
|
+
if a.write:
|
|
314
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
315
|
+
f = out_dir / "outcomes.jsonl"
|
|
316
|
+
known = set()
|
|
317
|
+
if f.exists():
|
|
318
|
+
for ln in f.read_text(encoding="utf-8").splitlines():
|
|
319
|
+
try:
|
|
320
|
+
known.add(json.loads(ln).get("outcomeId"))
|
|
321
|
+
except Exception:
|
|
322
|
+
continue
|
|
323
|
+
new = [o for o in derived["outcomes"] if o["outcomeId"] not in known]
|
|
324
|
+
with f.open("a", encoding="utf-8") as fh:
|
|
325
|
+
for o in sorted(new, key=lambda x: x["at"]):
|
|
326
|
+
fh.write(json.dumps({**o, "recordedAt": int(time.time())}, ensure_ascii=False) + "\n")
|
|
327
|
+
print(f" ✅ outcomes.jsonl:新增 {len(new)} 条(已存在 {len(known)} 条,重复跑不会重复计费)→ {f}")
|
|
328
|
+
|
|
329
|
+
report = month_report(derived, doc, month)
|
|
330
|
+
if a.statement:
|
|
331
|
+
md = statement_markdown(report, derived, doc, month, customer=a.customer or "")
|
|
332
|
+
if a.out:
|
|
333
|
+
pathlib.Path(a.out).expanduser().write_text(md + "\n", encoding="utf-8")
|
|
334
|
+
print(f" ✅ 客户版账单已写出:{a.out}")
|
|
335
|
+
else:
|
|
336
|
+
print(md)
|
|
337
|
+
return 0
|
|
338
|
+
snap = out_dir / f"{month}.json"
|
|
339
|
+
if not a.no_snapshot:
|
|
340
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
341
|
+
snap.write_text(json.dumps({**report, "generatedAt": int(time.time()),
|
|
342
|
+
"unverified": [o for o in derived["unverified"] if o["month"] == month],
|
|
343
|
+
"stores": derived["stores"]}, ensure_ascii=False, indent=2) + "\n",
|
|
344
|
+
encoding="utf-8")
|
|
345
|
+
if a.json:
|
|
346
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
347
|
+
else:
|
|
348
|
+
print(render(report, derived, month))
|
|
349
|
+
print(f" (快照已写:{snap})" if not a.no_snapshot else " (--no-snapshot:未写快照)")
|
|
350
|
+
return 0
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
if __name__ == "__main__":
|
|
354
|
+
raise SystemExit(main())
|