@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24
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 +102 -194
- package/README.zh-CN.md +115 -210
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-bootstrap.js +466 -35
- package/bin/gotry-inner.js +255 -36
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +2 -2
- package/client/client.js +174 -0
- package/cordis.gotry-patch.yml +70 -93
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +63 -148
- package/dist/capabilities/anything.js +89 -75
- package/dist/capabilities/artifacts.js +123 -104
- package/dist/capabilities/channel-health.js +65 -45
- package/dist/capabilities/channel-registry.js +108 -87
- package/dist/capabilities/doctor.js +357 -235
- package/dist/capabilities/effect.js +223 -309
- package/dist/capabilities/fact-log.js +28 -14
- package/dist/capabilities/flyai.js +245 -156
- package/dist/capabilities/ground-transfer.js +509 -0
- package/dist/capabilities/hbcli.js +144 -148
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/model-override.js +30 -10
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/resilience.js +50 -32
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
- package/dist/capabilities/session/adapters/dida-portal.js +206 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +169 -112
- package/dist/capabilities/session/benchmark.js +85 -149
- package/dist/capabilities/session/extension-bridge.js +320 -259
- package/dist/capabilities/session/extension-channel.js +57 -76
- package/dist/capabilities/session/extension-distribution.js +115 -119
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +55 -33
- package/dist/capabilities/session/health-watch.js +146 -154
- package/dist/capabilities/session/read-guard.js +55 -47
- package/dist/capabilities/session/static-flight-golden.js +49 -36
- package/dist/capabilities/session/transport.js +54 -61
- package/dist/capabilities/session/wizard.js +63 -93
- package/dist/capabilities/session-consent.js +143 -47
- package/dist/capabilities/session-login.js +95 -126
- package/dist/capabilities/session-search.js +391 -253
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +87 -73
- package/dist/capabilities/weather.js +163 -235
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
- package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
- package/dist/scripts/anything-tests.js +58 -62
- package/dist/scripts/artifact-client-contract-tests.js +69 -0
- package/dist/scripts/artifacts-capability-tests.js +235 -0
- package/dist/scripts/async-collect.js +26 -15
- package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
- package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
- package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
- package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
- package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
- package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
- package/dist/scripts/booking-executor-tests.js +80 -0
- package/dist/scripts/booking-recovery-chain-tests.js +182 -0
- package/dist/scripts/booking-saga-tests.js +61 -123
- package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
- package/dist/scripts/bootstrap-tests.js +1383 -128
- package/dist/scripts/build-changelog.js +89 -130
- package/dist/scripts/build-metrics-report.js +135 -143
- package/dist/scripts/changelog-tests.js +35 -82
- package/dist/scripts/channel-probe-tests.js +44 -116
- package/dist/scripts/channel-probe.js +84 -167
- package/dist/scripts/channel-registry-tests.js +97 -185
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +428 -120
- package/dist/scripts/dsh-artifact-e2e.js +150 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
- package/dist/scripts/dsh-runtime-closure.js +75 -63
- package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +165 -552
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +69 -242
- package/dist/scripts/evaluation-contract-tests.js +193 -499
- package/dist/scripts/extension-distribution-cli.js +19 -15
- package/dist/scripts/extension-distribution-tests.js +98 -211
- package/dist/scripts/extension-tests.js +366 -389
- package/dist/scripts/fact-gate-tests.js +725 -263
- package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
- package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
- package/dist/scripts/flight-malformed-tests.js +159 -0
- package/dist/scripts/flyai-tests.js +325 -68
- package/dist/scripts/g5-guard-tests.js +131 -0
- package/dist/scripts/g5-guard.js +189 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +71 -90
- package/dist/scripts/hbcli-live-optin-tests.js +96 -0
- package/dist/scripts/hbcli-release-contract-tests.js +122 -0
- package/dist/scripts/hbcli-tests.js +41 -115
- package/dist/scripts/health-watch-cli.js +42 -28
- package/dist/scripts/hotel-date-gate-tests.js +496 -0
- package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
- package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
- package/dist/scripts/i18n-tests.js +16 -9
- package/dist/scripts/incident-tests.js +281 -87
- package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
- package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
- package/dist/scripts/issue-271-liveness-tests.js +547 -0
- package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
- package/dist/scripts/issue-289-model-retry-tests.js +441 -0
- package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
- package/dist/scripts/issue-338-home-city-e2e.js +577 -0
- package/dist/scripts/issue343-real-entry-e2e.js +160 -0
- package/dist/scripts/journey-tests.js +22 -27
- package/dist/scripts/kernel-manifest-evidence.js +109 -0
- package/dist/scripts/kernel-manifest-gate.js +126 -0
- package/dist/scripts/kernel-manifest-tests.js +212 -0
- package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
- package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
- package/dist/scripts/kernel-manifest.js +369 -0
- package/dist/scripts/ledger-repair-apply-tests.js +316 -0
- package/dist/scripts/ledger-repair-plan-tests.js +375 -0
- package/dist/scripts/ledger-tests.js +325 -340
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +18 -52
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +24 -12
- package/dist/scripts/memory-value-report.js +474 -222
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +62 -199
- package/dist/scripts/model-override-e2e.js +66 -105
- package/dist/scripts/nightly-evidence-tests.js +64 -89
- package/dist/scripts/nightly-evidence.js +108 -111
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/onboarding-tests.js +63 -88
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/persona-surface-guard-tests.js +37 -12
- package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +126 -227
- package/dist/scripts/price-drift-watch.js +209 -253
- package/dist/scripts/probe-poi-tests.js +52 -45
- package/dist/scripts/product-metrics.js +187 -273
- package/dist/scripts/publish-preverify.js +47 -41
- package/dist/scripts/realtime-pricing-tests.js +37 -76
- package/dist/scripts/replay-async.js +20 -18
- package/dist/scripts/replay-real.js +16 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +16 -12
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +66 -147
- package/dist/scripts/session-dida-live-e2e.js +151 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-login.js +14 -13
- package/dist/scripts/session-tests.js +545 -583
- package/dist/scripts/sf-live-benchmark.js +92 -110
- package/dist/scripts/sf-live-cli-tests.js +2 -9
- package/dist/scripts/sf-soft-score-tests.js +19 -60
- package/dist/scripts/sf-summary-tests.js +149 -0
- package/dist/scripts/sf-summary.js +466 -84
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +11 -10
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +12 -20
- package/dist/scripts/smoke.js +541 -422
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +549 -212
- package/dist/scripts/static-golden-tests.js +46 -180
- package/dist/scripts/time-eval-tests.js +210 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +46 -93
- package/dist/scripts/turn-handoff-collect.js +71 -99
- package/dist/scripts/turn-policy-tests.js +28 -29
- package/dist/scripts/typed-contract-canary.js +77 -162
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +23 -19
- package/dist/scripts/visa-policy-tests.js +31 -44
- package/dist/scripts/weather-tests.js +121 -543
- package/dist/scripts/wish-channel-gate-tests.js +29 -86
- package/dist/scripts/write-gate-tests.js +522 -0
- package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
- package/dist/scripts/z3-lifecycle-tests.js +170 -0
- package/dist/scripts/z3-race-repeat-tests.js +39 -0
- package/dist/scripts/z3-race-tests.js +18 -12
- package/dist/src/artifact-gate.js +652 -221
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +55 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +194 -0
- package/dist/src/benchmark-agent-conformance.js +296 -262
- package/dist/src/benchmark-environment-bridge.js +298 -413
- package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
- package/dist/src/benchmark-tool-isolation.js +94 -67
- package/dist/src/bookable-facts.js +299 -174
- package/dist/src/booking-saga.js +64 -120
- package/dist/src/booking-surface/availability-policy.js +443 -576
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/contracts.js +24 -80
- package/dist/src/booking-surface/dsh-planner.js +510 -281
- package/dist/src/booking-surface/dsh-plugin.js +17 -1
- package/dist/src/booking-surface/error-codes.js +82 -10
- package/dist/src/booking-surface/index.js +0 -3
- package/dist/src/booking-surface/profile.js +10 -54
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +765 -891
- package/dist/src/booking-surface/server.js +200 -251
- package/dist/src/booking-surface/startup.js +58 -54
- package/dist/src/booking-surface/validation.js +150 -218
- package/dist/src/bridge.js +20 -13
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +82 -120
- package/dist/src/engine.js +253 -239
- package/dist/src/evaluation-cadence.js +72 -209
- package/dist/src/evaluation-contracts.js +220 -843
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/gotry-backend.js +70 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +18 -7
- package/dist/src/index.js +1425 -1629
- package/dist/src/journey.js +132 -81
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +257 -166
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +30 -14
- package/dist/src/memory-lifecycle.js +1310 -0
- package/dist/src/memory-utility.js +27 -40
- package/dist/src/mock-llm.js +36 -61
- package/dist/src/model.js +81 -36
- package/dist/src/realtime-pricing.js +63 -72
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +310 -471
- package/dist/src/subagent-job-id-guard.js +58 -0
- package/dist/src/time-anchor.js +231 -33
- package/dist/src/tool-packet.js +20 -8
- package/dist/src/travel-slots.js +58 -35
- package/dist/src/travel-timeline.js +44 -63
- package/dist/src/turn-deadline.js +121 -127
- package/dist/src/turn-policy.js +61 -89
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +504 -381
- package/dist/src/wish-pool.js +39 -14
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +273 -7
- package/extension/README.md +4 -3
- package/extension/background.js +125 -14
- package/extension/content-bridge.js +23 -1
- package/extension/content-main.js +15 -4
- package/extension/manifest.json +16 -8
- package/package.json +251 -220
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +1 -1
- package/ts/capabilities/artifacts.ts +46 -9
- package/ts/capabilities/flyai.ts +156 -37
- package/ts/capabilities/hbcli.ts +1 -1
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
- package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
- package/ts/capabilities/session/extension-bridge.ts +226 -101
- package/ts/capabilities/session/extension-channel.ts +22 -9
- package/ts/capabilities/session/wizard.ts +2 -2
- package/ts/capabilities/session-consent.ts +98 -14
- package/ts/capabilities/session-login.ts +1 -0
- package/ts/capabilities/session-search.ts +315 -31
- package/ts/dsh-runtime/vendor/README.md +8 -4
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
- package/ts/package.json +236 -16
- package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
- package/ts/scripts/state-cli.ts +418 -51
- package/ts/src/artifact-gate.ts +561 -15
- package/ts/src/benchmark-environment-bridge.ts +37 -20
- package/ts/src/bookable-facts.ts +183 -7
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/dsh-planner.ts +240 -32
- package/ts/src/booking-surface/dsh-plugin.js +17 -1
- package/ts/src/booking-surface/error-codes.ts +75 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/server.ts +84 -51
- package/ts/src/booking-surface/validation.ts +2 -0
- package/ts/src/contracts.ts +12 -2
- package/ts/src/dsh-llm.ts +4 -8
- package/ts/src/index.ts +609 -90
- package/ts/src/loop.ts +116 -7
- package/ts/src/memory-capture.ts +227 -3
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/state-ledger.ts +47 -29
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +217 -13
- package/ts/src/unified.ts +316 -54
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 12306 火车站点适配器(2026-09-03,继 ctrip-hotel 后会话面第三通道):
|
|
3
|
+
* - entry:https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=<城市名,电报码>&ts=<...>&date=YYYY-MM-DD&flag=N,N,Y
|
|
4
|
+
* (电报码表官方站表全量校准 2026-09-03,129 城;曾纠出南宁 NIZ→NNZ 错码)
|
|
5
|
+
* - networkHints:kyfw leftTicket/query(负载均衡变体 queryG/queryZ/queryA 等全部命中)
|
|
6
|
+
* - 解析:leftTicket/query 返回 data.result[] 管道行(12306 经典 | 拼接格式)。
|
|
7
|
+
* **第一方校准(2026-09-03)**:字段索引与站名映射(data.map[电报码])逐一
|
|
8
|
+
* 取自官方前端 queryLeftTicket_end_js.js 的 cN(result,map) 转换函数——旧版
|
|
9
|
+
* 公开常识索引与官方现行映射不符(站名/座位桶全错),真会话前即纠;行级
|
|
10
|
+
* 签名校验(车次码/时刻形态)继续兜住未来漂移:失配整行跳过,fail-visible
|
|
11
|
+
*
|
|
12
|
+
* 与机/酒通道的两点刻意差异:
|
|
13
|
+
* - 无登录闸:12306 余票查询是公开面(登录只关系下单),无账号数据过手;
|
|
14
|
+
* 扩展照样只读被动嗅探(零写行为),证据链标注「公开查询面」
|
|
15
|
+
* - 列表接口不含票价:如实只呈现 车次/时刻/历时/余票,票价以 12306 落地页为准
|
|
16
|
+
*/
|
|
17
|
+
/** 城市电报码表(**官方口径校准 2026-09-03**:全部条目逐一核对自
|
|
18
|
+
* kyfw.12306.cn/otn/resources/js/framework/station_name.js 官方全量站表
|
|
19
|
+
* ——曾借此抓出南宁 NIZ→NNZ 错码;代表站规则=城市同名站,fs/ts 用城市码
|
|
20
|
+
* 服务端扩到市内全部站。词表外 unresolved 逐字保留,调用方可显式传电报码
|
|
21
|
+
* 覆盖——kyfw 查询页 URL 里的 fs=城市,XXX 三位码;核对快照见
|
|
22
|
+
* data/stations-12306-verify.json) */
|
|
1
23
|
export const STATION_TELECODES = {
|
|
2
24
|
"三亚": 'SEQ',
|
|
3
25
|
"上海": 'SHH',
|
|
@@ -127,125 +149,138 @@ export const STATION_TELECODES = {
|
|
|
127
149
|
"香格里拉": 'EUM',
|
|
128
150
|
"香港": 'XJA',
|
|
129
151
|
"齐齐哈尔": 'QHX',
|
|
130
|
-
"龙岩": 'LYS'
|
|
152
|
+
"龙岩": 'LYS',
|
|
131
153
|
};
|
|
132
|
-
export function
|
|
154
|
+
export function resolveTrainQueryTelecodes(q) {
|
|
133
155
|
const unresolved = [];
|
|
134
156
|
const fromTc = (q.fromStationTelecode ?? '').trim().toUpperCase() || STATION_TELECODES[q.from.trim()];
|
|
135
157
|
const toTc = (q.toStationTelecode ?? '').trim().toUpperCase() || STATION_TELECODES[q.to.trim()];
|
|
136
|
-
if (!fromTc)
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
if (!fromTc)
|
|
159
|
+
unresolved.push(q.from);
|
|
160
|
+
if (!toTc)
|
|
161
|
+
unresolved.push(q.to);
|
|
162
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(q.date))
|
|
163
|
+
unresolved.push(q.date);
|
|
164
|
+
if (unresolved.length > 0 || !fromTc || !toTc)
|
|
165
|
+
return { ok: false, unresolved };
|
|
166
|
+
return { ok: true, telecodes: { fromStationTelecode: fromTc, toStationTelecode: toTc, date: q.date } };
|
|
167
|
+
}
|
|
168
|
+
/** Bind a parsed body to the exact 12306 query request that this invocation built. */
|
|
169
|
+
export function validateTrainQueryResponseUrl(observedUrl, expected) {
|
|
170
|
+
let parsed;
|
|
171
|
+
try {
|
|
172
|
+
parsed = new URL(observedUrl);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return { ok: false, reason: 'response URL malformed' };
|
|
176
|
+
}
|
|
177
|
+
if (parsed.protocol !== 'https:' || parsed.hostname.toLowerCase() !== TRAIN_SITE_HOST || parsed.port) {
|
|
178
|
+
return { ok: false, reason: 'response URL host/protocol is not the 12306 HTTPS host' };
|
|
179
|
+
}
|
|
180
|
+
if (!/^\/otn\/leftTicket\/query[A-Za-z0-9_-]*$/i.test(parsed.pathname)
|
|
181
|
+
|| !TRAIN_NETWORK_HINTS.some((hint) => hint.test(parsed.pathname))) {
|
|
182
|
+
return { ok: false, reason: 'response URL is not an allowed leftTicket query endpoint' };
|
|
145
183
|
}
|
|
184
|
+
const exactParam = (name) => {
|
|
185
|
+
const values = parsed.searchParams.getAll(name);
|
|
186
|
+
return values.length === 1 ? values[0] : undefined;
|
|
187
|
+
};
|
|
188
|
+
if (exactParam('leftTicketDTO.train_date') !== expected.date
|
|
189
|
+
|| exactParam('leftTicketDTO.from_station') !== expected.fromStationTelecode
|
|
190
|
+
|| exactParam('leftTicketDTO.to_station') !== expected.toStationTelecode) {
|
|
191
|
+
return { ok: false, reason: 'response URL query route/date does not match the invocation' };
|
|
192
|
+
}
|
|
193
|
+
return { ok: true, binding: { url: observedUrl, ...expected } };
|
|
194
|
+
}
|
|
195
|
+
export function buildTrainEntryUrl(q) {
|
|
196
|
+
const resolved = resolveTrainQueryTelecodes(q);
|
|
197
|
+
if (!resolved.ok)
|
|
198
|
+
return { ok: false, unresolved: resolved.unresolved };
|
|
199
|
+
const { fromStationTelecode: fromTc, toStationTelecode: toTc } = resolved.telecodes;
|
|
146
200
|
const fs = encodeURIComponent(`${q.from.trim()},${fromTc}`);
|
|
147
201
|
const ts = encodeURIComponent(`${q.to.trim()},${toTc}`);
|
|
148
|
-
return {
|
|
149
|
-
ok: true,
|
|
150
|
-
url: `https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=${fs}&ts=${ts}&date=${q.date}&flag=N,N,Y`
|
|
151
|
-
};
|
|
202
|
+
return { ok: true, url: `https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=${fs}&ts=${ts}&date=${q.date}&flag=N,N,Y` };
|
|
152
203
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
204
|
+
/** networkHints:余票查询 XHR(queryG/Z/A/U 等负载均衡变体全命中) */
|
|
205
|
+
export const TRAIN_NETWORK_HINTS = [/leftTicket\/query/i];
|
|
206
|
+
/** 12306 页域(content_scripts 注入面与 background 白名单的对账源) */
|
|
156
207
|
export const TRAIN_SITE_HOST = 'kyfw.12306.cn';
|
|
208
|
+
/** 座位桶索引表(**第一方校准 2026-09-03**:逐字段取自 12306 官方前端
|
|
209
|
+
* queryLeftTicket_end_js.js 的 cN(result,map) 转换函数——旧版公开常识索引
|
|
210
|
+
* (19-28 段)与官方现行映射不符,首查前即纠);label=页面席别口径,未知席别
|
|
211
|
+
* 保留上游字段 id 原样 */
|
|
157
212
|
const SEAT_BUCKETS = [
|
|
158
|
-
{
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
index: 23,
|
|
175
|
-
field: 'rw_num',
|
|
176
|
-
label: '软卧(一等卧)'
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
index: 24,
|
|
180
|
-
field: 'rz_num',
|
|
181
|
-
label: '软座'
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
index: 25,
|
|
185
|
-
field: 'tz_num',
|
|
186
|
-
label: '特等座'
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
index: 26,
|
|
190
|
-
field: 'wz_num',
|
|
191
|
-
label: '无座'
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
index: 27,
|
|
195
|
-
field: 'yb_num',
|
|
196
|
-
label: 'yp_b(上游席别)'
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
index: 28,
|
|
200
|
-
field: 'yw_num',
|
|
201
|
-
label: '硬卧(二等卧)'
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
index: 29,
|
|
205
|
-
field: 'yz_num',
|
|
206
|
-
label: '硬座'
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
index: 30,
|
|
210
|
-
field: 'ze_num',
|
|
211
|
-
label: '二等座'
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
index: 31,
|
|
215
|
-
field: 'zy_num',
|
|
216
|
-
label: '一等座'
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
index: 32,
|
|
220
|
-
field: 'swz_num',
|
|
221
|
-
label: '商务座'
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
index: 33,
|
|
225
|
-
field: 'srrb_num',
|
|
226
|
-
label: '动卧'
|
|
227
|
-
}
|
|
213
|
+
{ index: 20, field: 'gg_num', label: '其他(通勤)' },
|
|
214
|
+
{ index: 21, field: 'gr_num', label: '高级软卧' },
|
|
215
|
+
{ index: 22, field: 'qt_num', label: '其他' },
|
|
216
|
+
{ index: 23, field: 'rw_num', label: '软卧(一等卧)' },
|
|
217
|
+
{ index: 24, field: 'rz_num', label: '软座' },
|
|
218
|
+
{ index: 25, field: 'tz_num', label: '特等座' },
|
|
219
|
+
{ index: 26, field: 'wz_num', label: '无座' },
|
|
220
|
+
{ index: 27, field: 'yb_num', label: 'yp_b(上游席别)' },
|
|
221
|
+
{ index: 28, field: 'yw_num', label: '硬卧(二等卧)' },
|
|
222
|
+
{ index: 29, field: 'yz_num', label: '硬座' },
|
|
223
|
+
{ index: 30, field: 'ze_num', label: '二等座' },
|
|
224
|
+
{ index: 31, field: 'zy_num', label: '一等座' },
|
|
225
|
+
{ index: 32, field: 'swz_num', label: '商务座' },
|
|
226
|
+
{ index: 33, field: 'srrb_num', label: '动卧' },
|
|
228
227
|
];
|
|
229
228
|
const TRAIN_CODE_RE = /^[GDCZTKYLSF]\d{1,5}[A-Z]?$/i;
|
|
230
229
|
const HHMM_RE = /^\d{2}:\d{2}$/;
|
|
230
|
+
export const TRAIN_QUERY_PARSE_SCHEMA = 'gotry_session_train_parse.v1';
|
|
231
|
+
function isRecord(value) {
|
|
232
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
233
|
+
}
|
|
234
|
+
/** 历时 "07:52" → 分钟 */
|
|
231
235
|
function lishiToMin(s) {
|
|
232
236
|
const m = /^(\d{1,3}):(\d{2})$/.exec((s ?? '').trim());
|
|
233
|
-
if (!m)
|
|
237
|
+
if (!m)
|
|
238
|
+
return 0;
|
|
239
|
+
if (Number(m[2]) > 59)
|
|
240
|
+
return 0;
|
|
234
241
|
return Number(m[1]) * 60 + Number(m[2]);
|
|
235
242
|
}
|
|
243
|
+
function normalizeStartTrainDate(value) {
|
|
244
|
+
if (!/^\d{8}$/.test(value))
|
|
245
|
+
return undefined;
|
|
246
|
+
const normalized = `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`;
|
|
247
|
+
const parsed = new Date(`${normalized}T00:00:00.000Z`);
|
|
248
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== normalized)
|
|
249
|
+
return undefined;
|
|
250
|
+
return normalized;
|
|
251
|
+
}
|
|
252
|
+
/** 行级签名:车次码形态 + 时刻形态不符即整行跳过(索引漂移 fail-visible,不造数) */
|
|
236
253
|
function rowToOption(row, stationMap, entryUrl) {
|
|
237
254
|
const f = row.split('|');
|
|
238
|
-
if (f.length < 34)
|
|
255
|
+
if (f.length < 34)
|
|
256
|
+
return null;
|
|
239
257
|
const trainCode = (f[3] ?? '').trim();
|
|
240
258
|
const depTime = (f[8] ?? '').trim();
|
|
241
259
|
const arrTime = (f[9] ?? '').trim();
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
const
|
|
260
|
+
const durationMin = lishiToMin(f[10]);
|
|
261
|
+
const startTrainDateRaw = (f[13] ?? '').trim();
|
|
262
|
+
const startTrainDate = normalizeStartTrainDate(startTrainDateRaw);
|
|
263
|
+
const validHm = (value) => HHMM_RE.test(value)
|
|
264
|
+
&& Number(value.slice(0, 2)) <= 23 && Number(value.slice(3, 5)) <= 59;
|
|
265
|
+
if (!TRAIN_CODE_RE.test(trainCode) || !validHm(depTime) || !validHm(arrTime)
|
|
266
|
+
|| durationMin <= 0 || !startTrainDate || !(f[6] ?? '').trim() || !(f[7] ?? '').trim())
|
|
267
|
+
return null;
|
|
268
|
+
// 官方 cN 口径:站名 = data.map[电报码](map 缺失/缺键时退电报码原样,不猜名)
|
|
269
|
+
const stationName = (code) => {
|
|
270
|
+
if (!stationMap || !Object.prototype.hasOwnProperty.call(stationMap, code))
|
|
271
|
+
return code;
|
|
272
|
+
const mapped = stationMap[code];
|
|
273
|
+
return typeof mapped === 'string' && mapped.trim() ? mapped.trim() : null;
|
|
274
|
+
};
|
|
275
|
+
const fromStation = stationName((f[6] ?? '').trim());
|
|
276
|
+
const toStation = stationName((f[7] ?? '').trim());
|
|
277
|
+
if (!fromStation || !toStation)
|
|
278
|
+
return null;
|
|
245
279
|
const seats = {};
|
|
246
|
-
for (const b of SEAT_BUCKETS){
|
|
280
|
+
for (const b of SEAT_BUCKETS) {
|
|
247
281
|
const v = (f[b.index] ?? '').trim();
|
|
248
|
-
if (v && v !== '--')
|
|
282
|
+
if (v && v !== '--')
|
|
283
|
+
seats[b.label] = v;
|
|
249
284
|
}
|
|
250
285
|
return {
|
|
251
286
|
trainCode,
|
|
@@ -253,33 +288,55 @@ function rowToOption(row, stationMap, entryUrl) {
|
|
|
253
288
|
toStation,
|
|
254
289
|
depTime,
|
|
255
290
|
arrTime,
|
|
256
|
-
durationMin
|
|
291
|
+
durationMin,
|
|
292
|
+
startTrainDate,
|
|
257
293
|
canWebBuy: (f[11] ?? '').trim(),
|
|
258
294
|
seats,
|
|
259
|
-
jumpUrl: entryUrl
|
|
295
|
+
jumpUrl: entryUrl,
|
|
260
296
|
};
|
|
261
297
|
}
|
|
262
|
-
|
|
298
|
+
/** 解析 leftTicket/query 响应的 typed outcome。
|
|
299
|
+
* recognized-empty 只代表明确存在的 data.result=[];
|
|
300
|
+
* recognized-nonempty 要求批次每一行都通过签名校验,任一畸形行即整个批次
|
|
301
|
+
* malformed,不暴露部分候选。transport/runtime failure 不在此纯解析函数伪造。
|
|
302
|
+
*/
|
|
303
|
+
export function parseLeftTicketQueryResult(body, entryUrl, opts = {}) {
|
|
263
304
|
let raw;
|
|
264
305
|
try {
|
|
265
306
|
raw = JSON.parse(body);
|
|
266
|
-
} catch {
|
|
267
|
-
return [];
|
|
268
307
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
308
|
+
catch {
|
|
309
|
+
return { kind: 'malformed', reason: 'response body is not valid JSON' };
|
|
310
|
+
}
|
|
311
|
+
if (!isRecord(raw) || !isRecord(raw.data))
|
|
312
|
+
return { kind: 'malformed', reason: 'response root/data is not an object' };
|
|
313
|
+
const rows = raw.data.result;
|
|
314
|
+
if (!Array.isArray(rows))
|
|
315
|
+
return { kind: 'malformed', reason: 'data.result is not an array' };
|
|
316
|
+
if (raw.data.map !== undefined && !isRecord(raw.data.map)) {
|
|
317
|
+
return { kind: 'malformed', reason: 'data.map is not an object' };
|
|
318
|
+
}
|
|
319
|
+
if (rows.length === 0)
|
|
320
|
+
return { kind: 'recognized-empty', trains: [] };
|
|
321
|
+
const map = raw.data.map;
|
|
272
322
|
const out = [];
|
|
273
|
-
for (const r of rows){
|
|
274
|
-
if (typeof r !== 'string')
|
|
323
|
+
for (const r of rows) {
|
|
324
|
+
if (typeof r !== 'string')
|
|
325
|
+
return { kind: 'malformed', reason: 'data.result contains a non-string row' };
|
|
275
326
|
const opt = rowToOption(r, map, entryUrl);
|
|
276
|
-
if (opt)
|
|
327
|
+
if (!opt)
|
|
328
|
+
return { kind: 'malformed', reason: 'data.result contains a malformed or unrecognized row' };
|
|
329
|
+
if (out.length < (opts.maxItems ?? 30))
|
|
277
330
|
out.push(opt);
|
|
278
|
-
if (out.length >= (opts.maxItems ?? 30)) break;
|
|
279
|
-
}
|
|
280
331
|
}
|
|
281
|
-
return out;
|
|
332
|
+
return { kind: 'recognized-nonempty', trains: out };
|
|
333
|
+
}
|
|
334
|
+
/** 兼容旧调用方:typed outcome 之外仍保留旧的数组投影,错误与 genuine empty 均为 []。 */
|
|
335
|
+
export function parseLeftTicketQuery(body, entryUrl, opts = {}) {
|
|
336
|
+
const outcome = parseLeftTicketQueryResult(body, entryUrl, opts);
|
|
337
|
+
return outcome.kind === 'recognized-nonempty' ? outcome.trains : [];
|
|
338
|
+
}
|
|
339
|
+
/** Positive availability is deliberately a small closed set, not canWebBuy alone. */
|
|
340
|
+
export function hasRecognizedAvailableSeat(option) {
|
|
341
|
+
return Object.values(option.seats).some(value => value === '有' || /^[1-9]\d*$/.test(value));
|
|
282
342
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
//# sourceURL=ts/capabilities/session/adapters/rail-12306.ts
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session 双源字段合同与 fixture 评分器。
|
|
3
|
+
*
|
|
4
|
+
* 只处理已经脱敏、结构化的证据;不打开浏览器、不读登录态,也不执行任何写操作。
|
|
5
|
+
*/
|
|
1
6
|
export const SESSION_BENCHMARK_SCHEMA_VERSION = 'session-double-source.v1';
|
|
2
7
|
export const SESSION_FIELD_ACCURACY_THRESHOLD = 0.9;
|
|
3
8
|
export const REQUIRED_COMPARABLE_FIELDS = [
|
|
@@ -11,108 +16,64 @@ export const REQUIRED_COMPARABLE_FIELDS = [
|
|
|
11
16
|
'price',
|
|
12
17
|
'source',
|
|
13
18
|
'fetched_at',
|
|
14
|
-
'verdict'
|
|
19
|
+
'verdict',
|
|
15
20
|
];
|
|
16
21
|
function isMissing(value) {
|
|
17
|
-
return value === undefined || value === null || typeof value === 'string' && value.trim() === '';
|
|
22
|
+
return value === undefined || value === null || (typeof value === 'string' && value.trim() === '');
|
|
18
23
|
}
|
|
19
24
|
function sameValue(left, right) {
|
|
20
|
-
if (typeof left !== typeof right)
|
|
21
|
-
|
|
25
|
+
if (typeof left !== typeof right)
|
|
26
|
+
return false;
|
|
27
|
+
return typeof left === 'number' && typeof right === 'number'
|
|
28
|
+
? Object.is(left, right)
|
|
29
|
+
: left === right;
|
|
22
30
|
}
|
|
23
31
|
function sameComparison(row) {
|
|
24
|
-
if (/\.(?:departure_at|arrival_at)$/.test(row.path)
|
|
32
|
+
if (/\.(?:departure_at|arrival_at)$/.test(row.path)
|
|
33
|
+
&& typeof row.expected === 'string'
|
|
34
|
+
&& typeof row.actual === 'string') {
|
|
25
35
|
const expectedAt = Date.parse(row.expected);
|
|
26
36
|
const actualAt = Date.parse(row.actual);
|
|
27
|
-
if (Number.isFinite(expectedAt) && Number.isFinite(actualAt))
|
|
37
|
+
if (Number.isFinite(expectedAt) && Number.isFinite(actualAt))
|
|
38
|
+
return expectedAt === actualAt;
|
|
28
39
|
}
|
|
29
40
|
return sameValue(row.expected, row.actual);
|
|
30
41
|
}
|
|
31
42
|
function fixtureComparisons(expected, actual) {
|
|
32
43
|
const rows = [
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
actual: actual.route_segments?.length
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
path: 'journey_type',
|
|
45
|
-
expected: expected.journey_type,
|
|
46
|
-
actual: actual.journey_type
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
path: 'currency',
|
|
50
|
-
expected: expected.currency,
|
|
51
|
-
actual: actual.currency
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
path: 'price',
|
|
55
|
-
expected: expected.price,
|
|
56
|
-
actual: actual.price
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
path: 'source',
|
|
60
|
-
expected: expected.source,
|
|
61
|
-
actual: actual.source
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
path: 'fetched_at',
|
|
65
|
-
expected: expected.fetched_at,
|
|
66
|
-
actual: actual.fetched_at
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
path: 'verdict',
|
|
70
|
-
expected: expected.verdict,
|
|
71
|
-
actual: actual.verdict
|
|
72
|
-
}
|
|
44
|
+
{ path: 'query_id', expected: expected.query_id, actual: actual.query_id },
|
|
45
|
+
{ path: 'route_segments.length', expected: expected.route_segments.length, actual: actual.route_segments?.length },
|
|
46
|
+
{ path: 'journey_type', expected: expected.journey_type, actual: actual.journey_type },
|
|
47
|
+
{ path: 'currency', expected: expected.currency, actual: actual.currency },
|
|
48
|
+
{ path: 'price', expected: expected.price, actual: actual.price },
|
|
49
|
+
{ path: 'source', expected: expected.source, actual: actual.source },
|
|
50
|
+
{ path: 'fetched_at', expected: expected.fetched_at, actual: actual.fetched_at },
|
|
51
|
+
{ path: 'verdict', expected: expected.verdict, actual: actual.verdict },
|
|
73
52
|
];
|
|
74
|
-
for(let index = 0; index < expected.route_segments.length; index += 1){
|
|
53
|
+
for (let index = 0; index < expected.route_segments.length; index += 1) {
|
|
75
54
|
const exp = expected.route_segments[index];
|
|
76
55
|
const act = actual.route_segments?.[index];
|
|
77
|
-
rows.push({
|
|
78
|
-
path: `route_segments[${index}].from`,
|
|
79
|
-
expected: exp.from,
|
|
80
|
-
actual: act?.from
|
|
81
|
-
}, {
|
|
82
|
-
path: `route_segments[${index}].to`,
|
|
83
|
-
expected: exp.to,
|
|
84
|
-
actual: act?.to
|
|
85
|
-
}, {
|
|
86
|
-
path: `route_segments[${index}].departure_at`,
|
|
87
|
-
expected: exp.departure_at,
|
|
88
|
-
actual: act?.departure_at
|
|
89
|
-
}, {
|
|
90
|
-
path: `route_segments[${index}].arrival_at`,
|
|
91
|
-
expected: exp.arrival_at,
|
|
92
|
-
actual: act?.arrival_at
|
|
93
|
-
}, {
|
|
94
|
-
path: `route_segments[${index}].transport_number`,
|
|
95
|
-
expected: exp.transport_number,
|
|
96
|
-
actual: act?.transport_number
|
|
97
|
-
});
|
|
56
|
+
rows.push({ path: `route_segments[${index}].from`, expected: exp.from, actual: act?.from }, { path: `route_segments[${index}].to`, expected: exp.to, actual: act?.to }, { path: `route_segments[${index}].departure_at`, expected: exp.departure_at, actual: act?.departure_at }, { path: `route_segments[${index}].arrival_at`, expected: exp.arrival_at, actual: act?.arrival_at }, { path: `route_segments[${index}].transport_number`, expected: exp.transport_number, actual: act?.transport_number });
|
|
98
57
|
}
|
|
99
58
|
return rows;
|
|
100
59
|
}
|
|
101
60
|
export function scoreSessionFixture(expected, actual, threshold = SESSION_FIELD_ACCURACY_THRESHOLD) {
|
|
102
61
|
const rows = fixtureComparisons(expected, actual);
|
|
103
62
|
const fixtureErrors = [
|
|
104
|
-
...rows.filter((row)=>isMissing(row.expected)).map((row)=>row.path),
|
|
105
|
-
...requiredMissing(expected)
|
|
63
|
+
...rows.filter((row) => isMissing(row.expected)).map((row) => row.path),
|
|
64
|
+
...requiredMissing(expected),
|
|
106
65
|
];
|
|
107
66
|
const missing = [];
|
|
108
67
|
const incorrect = [];
|
|
109
68
|
let correct = 0;
|
|
110
|
-
for (const row of rows){
|
|
69
|
+
for (const row of rows) {
|
|
111
70
|
if (isMissing(row.actual)) {
|
|
112
71
|
missing.push(row.path);
|
|
113
|
-
}
|
|
72
|
+
}
|
|
73
|
+
else if (!sameValue(row.expected, row.actual)) {
|
|
114
74
|
incorrect.push(row.path);
|
|
115
|
-
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
116
77
|
correct += 1;
|
|
117
78
|
}
|
|
118
79
|
}
|
|
@@ -126,9 +87,7 @@ export function scoreSessionFixture(expected, actual, threshold = SESSION_FIELD_
|
|
|
126
87
|
accuracy,
|
|
127
88
|
missing,
|
|
128
89
|
incorrect,
|
|
129
|
-
fixture_errors: [
|
|
130
|
-
...new Set(fixtureErrors)
|
|
131
|
-
]
|
|
90
|
+
fixture_errors: [...new Set(fixtureErrors)],
|
|
132
91
|
};
|
|
133
92
|
}
|
|
134
93
|
function isIsoTimestamp(value) {
|
|
@@ -136,21 +95,34 @@ function isIsoTimestamp(value) {
|
|
|
136
95
|
}
|
|
137
96
|
function requiredMissing(record) {
|
|
138
97
|
const missing = [];
|
|
139
|
-
if (!record.query_id.trim())
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
if (!record.
|
|
144
|
-
|
|
145
|
-
if (record.
|
|
146
|
-
|
|
147
|
-
|
|
98
|
+
if (!record.query_id.trim())
|
|
99
|
+
missing.push('query_id');
|
|
100
|
+
if (record.route_segments.length === 0)
|
|
101
|
+
missing.push('route_segments');
|
|
102
|
+
if (!record.currency.trim())
|
|
103
|
+
missing.push('currency');
|
|
104
|
+
if (!(record.price > 0))
|
|
105
|
+
missing.push('price');
|
|
106
|
+
if (!record.source.trim())
|
|
107
|
+
missing.push('source');
|
|
108
|
+
if (!isIsoTimestamp(record.fetched_at))
|
|
109
|
+
missing.push('fetched_at');
|
|
110
|
+
if (record.journey_type === 'direct' && record.route_segments.length !== 1)
|
|
111
|
+
missing.push('journey_type/route_segments');
|
|
112
|
+
if (record.journey_type === 'transfer' && record.route_segments.length < 2)
|
|
113
|
+
missing.push('journey_type/route_segments');
|
|
114
|
+
for (let index = 0; index < record.route_segments.length; index += 1) {
|
|
148
115
|
const segment = record.route_segments[index];
|
|
149
|
-
if (!segment.from.trim())
|
|
150
|
-
|
|
151
|
-
if (!
|
|
152
|
-
|
|
153
|
-
if (!segment.
|
|
116
|
+
if (!segment.from.trim())
|
|
117
|
+
missing.push(`route_segments[${index}].from`);
|
|
118
|
+
if (!segment.to.trim())
|
|
119
|
+
missing.push(`route_segments[${index}].to`);
|
|
120
|
+
if (!isIsoTimestamp(segment.departure_at))
|
|
121
|
+
missing.push(`route_segments[${index}].departure_at`);
|
|
122
|
+
if (!isIsoTimestamp(segment.arrival_at))
|
|
123
|
+
missing.push(`route_segments[${index}].arrival_at`);
|
|
124
|
+
if (!segment.transport_number.trim())
|
|
125
|
+
missing.push(`route_segments[${index}].transport_number`);
|
|
154
126
|
}
|
|
155
127
|
return missing;
|
|
156
128
|
}
|
|
@@ -161,59 +133,21 @@ function baseEvaluation(state, quotaDisposition, extra = {}) {
|
|
|
161
133
|
quota_disposition: quotaDisposition,
|
|
162
134
|
mismatches: extra.mismatches ?? [],
|
|
163
135
|
missing: extra.missing ?? [],
|
|
164
|
-
...extra.price_delta === undefined ? {} : {
|
|
165
|
-
price_delta: extra.price_delta
|
|
166
|
-
}
|
|
136
|
+
...(extra.price_delta === undefined ? {} : { price_delta: extra.price_delta }),
|
|
167
137
|
};
|
|
168
138
|
}
|
|
169
139
|
function alignmentComparisons(official, session) {
|
|
170
140
|
const rows = [
|
|
171
|
-
{
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
path: 'route_segments.length',
|
|
178
|
-
expected: official.route_segments.length,
|
|
179
|
-
actual: session.route_segments.length
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
path: 'journey_type',
|
|
183
|
-
expected: official.journey_type,
|
|
184
|
-
actual: session.journey_type
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
path: 'currency',
|
|
188
|
-
expected: official.currency,
|
|
189
|
-
actual: session.currency
|
|
190
|
-
}
|
|
141
|
+
{ path: 'query_id', expected: official.query_id, actual: session.query_id },
|
|
142
|
+
{ path: 'route_segments.length', expected: official.route_segments.length, actual: session.route_segments.length },
|
|
143
|
+
{ path: 'journey_type', expected: official.journey_type, actual: session.journey_type },
|
|
144
|
+
{ path: 'currency', expected: official.currency, actual: session.currency },
|
|
191
145
|
];
|
|
192
146
|
const comparableSegments = Math.min(official.route_segments.length, session.route_segments.length);
|
|
193
|
-
for(let index = 0; index < comparableSegments; index += 1){
|
|
147
|
+
for (let index = 0; index < comparableSegments; index += 1) {
|
|
194
148
|
const exp = official.route_segments[index];
|
|
195
149
|
const act = session.route_segments[index];
|
|
196
|
-
rows.push({
|
|
197
|
-
path: `route_segments[${index}].from`,
|
|
198
|
-
expected: exp.from,
|
|
199
|
-
actual: act.from
|
|
200
|
-
}, {
|
|
201
|
-
path: `route_segments[${index}].to`,
|
|
202
|
-
expected: exp.to,
|
|
203
|
-
actual: act.to
|
|
204
|
-
}, {
|
|
205
|
-
path: `route_segments[${index}].departure_at`,
|
|
206
|
-
expected: exp.departure_at,
|
|
207
|
-
actual: act.departure_at
|
|
208
|
-
}, {
|
|
209
|
-
path: `route_segments[${index}].arrival_at`,
|
|
210
|
-
expected: exp.arrival_at,
|
|
211
|
-
actual: act.arrival_at
|
|
212
|
-
}, {
|
|
213
|
-
path: `route_segments[${index}].transport_number`,
|
|
214
|
-
expected: exp.transport_number,
|
|
215
|
-
actual: act.transport_number
|
|
216
|
-
});
|
|
150
|
+
rows.push({ path: `route_segments[${index}].from`, expected: exp.from, actual: act.from }, { path: `route_segments[${index}].to`, expected: exp.to, actual: act.to }, { path: `route_segments[${index}].departure_at`, expected: exp.departure_at, actual: act.departure_at }, { path: `route_segments[${index}].arrival_at`, expected: exp.arrival_at, actual: act.arrival_at }, { path: `route_segments[${index}].transport_number`, expected: exp.transport_number, actual: act.transport_number });
|
|
217
151
|
}
|
|
218
152
|
return rows;
|
|
219
153
|
}
|
|
@@ -226,28 +160,30 @@ export function evaluateDoubleSource(input) {
|
|
|
226
160
|
if ((session?.read_guard_blocked ?? 0) !== 0 || (official?.read_guard_blocked ?? 0) !== 0) {
|
|
227
161
|
return baseEvaluation('guard_violation', 'no_spend_stop');
|
|
228
162
|
}
|
|
229
|
-
if (!session)
|
|
230
|
-
|
|
231
|
-
if (session.verdict === 'needs-
|
|
232
|
-
|
|
163
|
+
if (!session)
|
|
164
|
+
return baseEvaluation('source_unavailable', 'no_spend_stop');
|
|
165
|
+
if (session.verdict === 'needs-attach')
|
|
166
|
+
return baseEvaluation('waiting_attach', 'no_spend_waiting_user');
|
|
167
|
+
// 扩展车道(2026-08-29 PRIMARY 传输)未就绪:与 waiting-* 同族 no-spend,等待用户一次性安装
|
|
168
|
+
if (session.verdict === 'needs-extension')
|
|
169
|
+
return baseEvaluation('waiting_extension', 'no_spend_waiting_user');
|
|
170
|
+
if (session.verdict === 'needs-login')
|
|
171
|
+
return baseEvaluation('waiting_login', 'no_spend_waiting_user');
|
|
233
172
|
if (!official || official.verdict !== 'hit' || session.verdict !== 'hit') {
|
|
234
173
|
return baseEvaluation('source_unavailable', 'no_spend_stop');
|
|
235
174
|
}
|
|
236
175
|
const missing = [
|
|
237
|
-
...requiredMissing(official).map((path)
|
|
238
|
-
...requiredMissing(session).map((path)
|
|
176
|
+
...requiredMissing(official).map((path) => `official.${path}`),
|
|
177
|
+
...requiredMissing(session).map((path) => `session.${path}`),
|
|
239
178
|
];
|
|
240
179
|
if (missing.length > 0) {
|
|
241
|
-
return baseEvaluation('invalid_contract', 'no_spend_stop', {
|
|
242
|
-
missing
|
|
243
|
-
});
|
|
180
|
+
return baseEvaluation('invalid_contract', 'no_spend_stop', { missing });
|
|
244
181
|
}
|
|
245
|
-
const mismatches = alignmentComparisons(official, session)
|
|
182
|
+
const mismatches = alignmentComparisons(official, session)
|
|
183
|
+
.filter((row) => !sameComparison(row))
|
|
184
|
+
.map((row) => row.path);
|
|
246
185
|
return baseEvaluation(mismatches.length === 0 ? 'comparable' : 'divergent', 'evidence_ready', {
|
|
247
186
|
mismatches,
|
|
248
|
-
price_delta: session.price - official.price
|
|
187
|
+
price_delta: session.price - official.price,
|
|
249
188
|
});
|
|
250
189
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
//# sourceURL=ts/capabilities/session/benchmark.ts
|