@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.
Files changed (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -1,30 +1,49 @@
1
+ /**
2
+ * 真实 sf-01..08 双源 e2e 跑批(issue #21 验收清单,goal 2 sf-live-benchmark)
3
+ * ——pluggable golden 版:
4
+ *
5
+ * 之前版本绑 flyaiSearch 失败(Trial limit reached);本版改 pluggable:
6
+ * - 默认 comparator = 手工 golden(data/sf-golden-manifest.json 时间/价格带,零网络零 vendor)
7
+ * - 可选 --golden=flyai / --golden=static 显式切换;未知 vendor 在网络调用前 fail-closed
8
+ * - static = OpenFlights 固定 route/carrier 快照 + manual 时间/价格带;估算字段与回退原因显式落证据
9
+ * - 字段评分改软命中:硬字段(query_id/from/to/currency/source/verdict)必中,软字段
10
+ * (transport_number/departure_at/arrival_at/price)落在窗口内算 correct
11
+ *
12
+ * 与 ts/scripts/session-benchmark.ts 的关系:session-benchmark.ts 是离线 fixture 自测
13
+ * (13/18 字段黄金 + 双源合同纯函数断言);sf-live-benchmark.ts 是真网络真扩展端到端。
14
+ *
15
+ * evidence 落盘:~/.gotry/evidence/session/sf-XX/<ISO ts>.json(issue #21/#67 私有证据)
16
+ *
17
+ * 退出码:0 = 跑完(即便部分 query miss);人类评审 evidence 决定 issue 是否可关
18
+ */
1
19
  import { mkdirSync, writeFileSync, readFileSync } from 'node:fs';
2
20
  import { dirname, join } from 'node:path';
3
21
  import { homedir } from 'node:os';
4
- import { evaluateDoubleSource, SESSION_FIELD_ACCURACY_THRESHOLD } from '../capabilities/session/benchmark.js';
22
+ import { evaluateDoubleSource, SESSION_FIELD_ACCURACY_THRESHOLD, } from '../capabilities/session/benchmark.js';
5
23
  import { sessionFlightSearch } from '../capabilities/session-search.js';
6
24
  import { flyaiSearch } from '../capabilities/flyai.js';
7
- import { loadStaticFlightSnapshot, parseGoldenSource, resolveStaticGolden } from '../capabilities/session/static-flight-golden.js';
8
- import { scoreSessionAgainstGoldenBand } from '../capabilities/session/golden-score.js';
25
+ import { loadStaticFlightSnapshot, parseGoldenSource, resolveStaticGolden, } from '../capabilities/session/static-flight-golden.js';
26
+ import { scoreSessionAgainstGoldenBand, } from '../capabilities/session/golden-score.js';
27
+ /** 从 session-golden-20.json 拿 8 条飞行 query */
9
28
  function loadFlightQueries() {
10
29
  const golden = JSON.parse(readFileSync(join(import.meta.dirname, '..', 'data', 'session-golden-20.json'), 'utf8'));
11
- return golden.queries.filter((q)=>q.id.startsWith('sf-')).slice(0, 8);
30
+ return golden.queries.filter((q) => q.id.startsWith('sf-')).slice(0, 8);
12
31
  }
32
+ /** 从 sf-golden-manifest.json 拿手工 golden(默认 official 来源) */
13
33
  function loadManualGolden() {
14
34
  return JSON.parse(readFileSync(join(import.meta.dirname, '..', 'data', 'sf-golden-manifest.json'), 'utf8'));
15
35
  }
36
+ /** 手工 golden 的单条 → SessionComparableRecord */
16
37
  function manualToRecord(m, sample) {
17
38
  return {
18
39
  query_id: m.query_id,
19
- route_segments: [
20
- {
40
+ route_segments: [{
21
41
  from: m.from,
22
42
  to: m.to,
23
43
  departure_at: `${m.date}T${sample.depTime}:00+08:00`,
24
44
  arrival_at: `${m.date}T${sample.arrTime}:00+08:00`,
25
- transport_number: sample.flightNo
26
- }
27
- ],
45
+ transport_number: sample.flightNo,
46
+ }],
28
47
  journey_type: 'direct',
29
48
  currency: 'CNY',
30
49
  price: sample.price,
@@ -32,34 +51,27 @@ function manualToRecord(m, sample) {
32
51
  fetched_at: new Date().toISOString(),
33
52
  verdict: 'hit',
34
53
  latency_ms: 0,
35
- read_guard_blocked: 0
54
+ read_guard_blocked: 0,
36
55
  };
37
56
  }
57
+ /** FlyAI vendor: 真打飞猪官方(可能 trial limit,落 verdict=error) */
38
58
  async function flyaiOfficial(q) {
39
59
  const started = Date.now();
40
60
  try {
41
- const r = await flyaiSearch({
42
- kind: 'flight',
43
- origin: q.from,
44
- destination: q.to,
45
- depDate: q.date,
46
- timeoutMs: 30_000
47
- });
61
+ const r = await flyaiSearch({ kind: 'flight', origin: q.from, destination: q.to, depDate: q.date, timeoutMs: 30_000 });
48
62
  const latencyMs = Date.now() - started;
49
63
  if (r.ok && r.options && r.options.length > 0) {
50
- const sorted = r.options.slice().sort((a, b)=>Date.parse(a.depDateTime) - Date.parse(b.depDateTime));
64
+ const sorted = r.options.slice().sort((a, b) => Date.parse(a.depDateTime) - Date.parse(b.depDateTime));
51
65
  const opt = sorted[0];
52
66
  const rec = {
53
67
  query_id: q.id,
54
- route_segments: [
55
- {
68
+ route_segments: [{
56
69
  from: q.from,
57
70
  to: q.to,
58
71
  departure_at: opt.depDateTime,
59
72
  arrival_at: opt.arrDateTime,
60
- transport_number: opt.no
61
- }
62
- ],
73
+ transport_number: opt.no,
74
+ }],
63
75
  journey_type: 'direct',
64
76
  currency: 'CNY',
65
77
  price: opt.price,
@@ -67,48 +79,29 @@ async function flyaiOfficial(q) {
67
79
  fetched_at: new Date().toISOString(),
68
80
  verdict: 'hit',
69
81
  latency_ms: latencyMs,
70
- read_guard_blocked: 0
71
- };
72
- return {
73
- record: rec,
74
- latencyMs
82
+ read_guard_blocked: 0,
75
83
  };
84
+ return { record: rec, latencyMs };
76
85
  }
77
- return {
78
- record: {
79
- ...emptyOfficialRecord(q, 'flyai'),
80
- latency_ms: latencyMs,
81
- verdict: 'error'
82
- },
83
- latencyMs,
84
- error: r.error ?? 'no options'
85
- };
86
- } catch (e) {
87
- return {
88
- record: emptyOfficialRecord(q, 'flyai'),
89
- latencyMs: Date.now() - started,
90
- error: e instanceof Error ? e.message : String(e)
91
- };
86
+ return { record: { ...emptyOfficialRecord(q, 'flyai'), latency_ms: latencyMs, verdict: 'error' }, latencyMs, error: r.error ?? 'no options' };
87
+ }
88
+ catch (e) {
89
+ return { record: emptyOfficialRecord(q, 'flyai'), latencyMs: Date.now() - started, error: e instanceof Error ? e.message : String(e) };
92
90
  }
93
91
  }
92
+ /** 手工 golden vendor: 不打网络,从 manifest 取首个 known_flight 的时刻窗口算中位时间 + 价格带中位 */
94
93
  function manualOfficial(q, manifest) {
95
94
  const started = Date.now();
96
- const m = manifest.matches.find((x)=>x.query_id === q.id);
97
- if (!m) return {
98
- record: emptyOfficialRecord(q, 'manual-golden'),
99
- latencyMs: Date.now() - started
100
- };
95
+ const m = manifest.matches.find((x) => x.query_id === q.id);
96
+ if (!m)
97
+ return { record: emptyOfficialRecord(q, 'manual-golden'), latencyMs: Date.now() - started };
98
+ // 中位时间( + 晚 ÷ 2);中位价格(带中位)
101
99
  const depMedian = hhmmMid(m.window_dep_local.earliest, m.window_dep_local.latest);
102
100
  const arrMedian = hhmmMid(m.window_arr_local.earliest, m.window_arr_local.latest);
103
101
  const priceMedian = Math.round((m.price_cny.min + m.price_cny.max) / 2);
104
102
  return {
105
- record: manualToRecord(m, {
106
- depTime: depMedian,
107
- arrTime: arrMedian,
108
- flightNo: m.known_flights[0] ?? '',
109
- price: priceMedian
110
- }),
111
- latencyMs: Date.now() - started
103
+ record: manualToRecord(m, { depTime: depMedian, arrTime: arrMedian, flightNo: m.known_flights[0] ?? '', price: priceMedian }),
104
+ latencyMs: Date.now() - started,
112
105
  };
113
106
  }
114
107
  function hhmmMid(a, b) {
@@ -128,7 +121,7 @@ function emptyOfficialRecord(q, source) {
128
121
  fetched_at: new Date().toISOString(),
129
122
  verdict: 'error',
130
123
  latency_ms: 0,
131
- read_guard_blocked: 0
124
+ read_guard_blocked: 0,
132
125
  };
133
126
  }
134
127
  async function runOne(q, requestedSource, manifest, staticContext) {
@@ -143,11 +136,13 @@ async function runOne(q, requestedSource, manifest, staticContext) {
143
136
  const m = manualOfficial(q, manifest);
144
137
  official = m.record;
145
138
  officialLatencyMs = m.latencyMs;
146
- } else if (requestedSource === 'flyai') {
139
+ }
140
+ else if (requestedSource === 'flyai') {
147
141
  const r = await flyaiOfficial(q);
148
142
  official = r.record;
149
143
  officialLatencyMs = r.latencyMs;
150
- } else {
144
+ }
145
+ else {
151
146
  const started = Date.now();
152
147
  const manual = manualOfficial(q, manifest);
153
148
  const resolution = resolveStaticGolden({
@@ -155,7 +150,7 @@ async function runOne(q, requestedSource, manifest, staticContext) {
155
150
  manualRecord: manual.record ?? emptyOfficialRecord(q, 'manual-golden'),
156
151
  snapshot: staticContext.snapshot,
157
152
  snapshotError: staticContext.error,
158
- warn: (message)=>console.error(message)
153
+ warn: (message) => console.error(message),
159
154
  });
160
155
  official = resolution.record;
161
156
  officialLatencyMs = Date.now() - started;
@@ -164,31 +159,25 @@ async function runOne(q, requestedSource, manifest, staticContext) {
164
159
  estimatedFields = resolution.estimated_fields;
165
160
  officialProvenance = resolution.provenance;
166
161
  }
162
+ // session = sessionFlightSearch(节律闸 ≥30s 由 caller sleep 35s 处理)
167
163
  const sessStarted = Date.now();
168
- const sessRes = await sessionFlightSearch({
169
- from: q.from,
170
- to: q.to,
171
- date: q.date,
172
- timeoutMs: 25_000
173
- });
164
+ const sessRes = await sessionFlightSearch({ from: q.from, to: q.to, date: q.date, timeoutMs: 25_000 });
174
165
  const sessionLatencyMs = Date.now() - sessStarted;
175
166
  const sessionVerdict = sessRes.verdict;
176
167
  const sessionError = sessRes.error;
177
168
  let session = null;
178
169
  if (sessRes.verdict === 'hit' && sessRes.options && sessRes.options.length > 0) {
179
- const sorted = sessRes.options.slice().sort((a, b)=>Date.parse(a.depDateTime) - Date.parse(b.depDateTime));
170
+ const sorted = sessRes.options.slice().sort((a, b) => Date.parse(a.depDateTime) - Date.parse(b.depDateTime));
180
171
  const opt = sorted[0];
181
172
  session = {
182
173
  query_id: q.id,
183
- route_segments: [
184
- {
174
+ route_segments: [{
185
175
  from: q.from,
186
176
  to: q.to,
187
177
  departure_at: opt.depDateTime,
188
178
  arrival_at: opt.arrDateTime,
189
- transport_number: opt.flightNo
190
- }
191
- ],
179
+ transport_number: opt.flightNo,
180
+ }],
192
181
  journey_type: 'direct',
193
182
  currency: 'CNY',
194
183
  price: opt.price,
@@ -196,9 +185,10 @@ async function runOne(q, requestedSource, manifest, staticContext) {
196
185
  fetched_at: new Date().toISOString(),
197
186
  verdict: 'hit',
198
187
  latency_ms: sessionLatencyMs,
199
- read_guard_blocked: 0
188
+ read_guard_blocked: 0,
200
189
  };
201
- } else {
190
+ }
191
+ else {
202
192
  session = {
203
193
  query_id: q.id,
204
194
  route_segments: [],
@@ -209,26 +199,19 @@ async function runOne(q, requestedSource, manifest, staticContext) {
209
199
  fetched_at: new Date().toISOString(),
210
200
  verdict: sessRes.verdict === 'hit' || sessRes.verdict === 'miss' ? sessRes.verdict : 'error',
211
201
  latency_ms: sessionLatencyMs,
212
- read_guard_blocked: 0
202
+ read_guard_blocked: 0,
213
203
  };
214
204
  }
215
- const doubleSource = evaluateDoubleSource({
216
- official: official ?? undefined,
217
- session: session ?? undefined
218
- });
205
+ const doubleSource = evaluateDoubleSource({ official: official ?? undefined, session: session ?? undefined });
206
+ // 字段准确率始终对照同一 manual band;provider 只改变 official 来源与 provenance。
219
207
  let softResult = null;
220
- const m = manifest.matches.find((x)=>x.query_id === q.id);
208
+ const m = manifest.matches.find((x) => x.query_id === q.id);
221
209
  if (session?.verdict === 'hit' && m) {
222
210
  softResult = scoreSessionAgainstGoldenBand(session, m, effectiveSource);
223
211
  }
224
212
  return {
225
213
  query_id: q.id,
226
- expected: {
227
- from: q.from,
228
- to: q.to,
229
- date: q.date,
230
- kind: q.kind
231
- },
214
+ expected: { from: q.from, to: q.to, date: q.date, kind: q.kind },
232
215
  requested_source: requestedSource,
233
216
  effective_source: effectiveSource,
234
217
  fallback_reason: fallbackReason,
@@ -243,7 +226,7 @@ async function runOne(q, requestedSource, manifest, staticContext) {
243
226
  sessionLatencyMs,
244
227
  sessionVerdict: String(sessionVerdict),
245
228
  sessionError,
246
- started_at: startedAt
229
+ started_at: startedAt,
247
230
  };
248
231
  }
249
232
  async function main() {
@@ -259,25 +242,29 @@ async function main() {
259
242
  if (requestedSource === 'static') {
260
243
  try {
261
244
  staticContext.snapshot = loadStaticFlightSnapshot();
262
- } catch (error) {
245
+ }
246
+ catch (error) {
263
247
  staticContext.error = error instanceof Error ? error.message : String(error);
264
248
  }
265
249
  }
266
- const sourceDescription = requestedSource === 'manual' ? '手工 golden (sf-golden-manifest.json,零网络零 vendor)' : requestedSource === 'flyai' ? 'flyai (FlyAI 官方)' : 'static (OpenFlights route/carrier + manual time/price band,估算字段显式标注)';
250
+ const sourceDescription = requestedSource === 'manual'
251
+ ? '手工 golden (sf-golden-manifest.json,零网络零 vendor)'
252
+ : requestedSource === 'flyai'
253
+ ? 'flyai (FlyAI 官方)'
254
+ : 'static (OpenFlights route/carrier + manual time/price band,估算字段显式标注)';
267
255
  console.log(`[sf-live-benchmark] requested official source = ${sourceDescription}`);
268
256
  console.log(`[sf-live-benchmark] 阈值:字段准确率 ≥${SESSION_FIELD_ACCURACY_THRESHOLD * 100}%(软命中)`);
269
257
  const runStartedAt = new Date().toISOString();
270
258
  const runStartedMs = Date.now();
271
259
  const records = [];
272
- for (const q of queries){
273
- if (records.length > 0) await new Promise((r)=>setTimeout(r, 35_000));
260
+ for (const q of queries) {
261
+ if (records.length > 0)
262
+ await new Promise((r) => setTimeout(r, 35_000));
274
263
  console.log(`\n[sf-live-benchmark] ${q.id} ${q.from}→${q.to} ${q.date}`);
275
264
  const rec = await runOne(q, requestedSource, manifest, staticContext);
276
265
  records.push(rec);
277
266
  const evPath = join(homedir(), '.gotry', 'evidence', 'session', q.id, `${runStartedAt.replace(/[:.]/g, '-')}.json`);
278
- mkdirSync(dirname(evPath), {
279
- recursive: true
280
- });
267
+ mkdirSync(dirname(evPath), { recursive: true });
281
268
  writeFileSync(evPath, JSON.stringify(rec, null, 2));
282
269
  console.log(` official(requested=${rec.requested_source},effective=${rec.effective_source}): verdict=${rec.official?.verdict ?? '-'} latency=${rec.officialLatencyMs ?? 0}ms${rec.fallback_reason ? ` fallback=${rec.fallback_reason}` : ''}`);
283
270
  console.log(` session(ctrip): verdict=${rec.sessionVerdict} latency=${rec.sessionLatencyMs ?? 0}ms${rec.sessionError ? ` err=${rec.sessionError.slice(0, 80)}` : ''}`);
@@ -286,14 +273,14 @@ async function main() {
286
273
  console.log(` soft score: ${(rec.softScore.accuracy * 100).toFixed(1)}% (${rec.softScore.correct}/${rec.softScore.total}) ${rec.softScore.pass ? '✅' : '❌'} missing=${JSON.stringify(rec.softScore.missing)} incorrect=${JSON.stringify(rec.softScore.incorrect)}`);
287
274
  }
288
275
  }
289
- const accuracy_pass = records.filter((r)=>r.softScore?.pass === true).length;
290
- const comparable = records.filter((r)=>r.doubleSource.state === 'comparable').length;
291
- const hit = records.filter((r)=>r.sessionVerdict === 'hit').length;
292
- const challenge = records.filter((r)=>r.sessionVerdict === 'challenged' || r.doubleSource.state === 'challenge_stop' || r.doubleSource.state === 'guard_violation').length;
293
- const no_data = records.filter((r)=>r.official?.verdict !== 'hit' || r.sessionVerdict !== 'hit' && r.sessionVerdict !== 'challenged').length;
294
- const live_under_15s = records.filter((r)=>r.sessionLatencyMs !== null && r.sessionVerdict === 'hit' && r.sessionLatencyMs < 15_000).length;
295
- const effectiveSources = Array.from(new Set(records.map((record)=>record.effective_source)));
296
- const fallbackCount = records.filter((record)=>record.fallback_reason !== null).length;
276
+ const accuracy_pass = records.filter((r) => r.softScore?.pass === true).length;
277
+ const comparable = records.filter((r) => r.doubleSource.state === 'comparable').length;
278
+ const hit = records.filter((r) => r.sessionVerdict === 'hit').length;
279
+ const challenge = records.filter((r) => r.sessionVerdict === 'challenged' || r.doubleSource.state === 'challenge_stop' || r.doubleSource.state === 'guard_violation').length;
280
+ const no_data = records.filter((r) => r.official?.verdict !== 'hit' || (r.sessionVerdict !== 'hit' && r.sessionVerdict !== 'challenged')).length;
281
+ const live_under_15s = records.filter((r) => (r.sessionLatencyMs !== null && r.sessionVerdict === 'hit' && r.sessionLatencyMs < 15_000)).length;
282
+ const effectiveSources = Array.from(new Set(records.map((record) => record.effective_source)));
283
+ const fallbackCount = records.filter((record) => record.fallback_reason !== null).length;
297
284
  const summary = {
298
285
  started_at: runStartedAt,
299
286
  duration_ms: Date.now() - runStartedMs,
@@ -309,12 +296,10 @@ async function main() {
309
296
  effective_sources: effectiveSources,
310
297
  fallback_count: fallbackCount,
311
298
  golden_source: requestedSource === 'manual' ? 'manual-golden' : requestedSource,
312
- records
299
+ records,
313
300
  };
314
301
  const summaryPath = join(homedir(), '.gotry', 'evidence', 'session', 'sf-summary', `${runStartedAt.replace(/[:.]/g, '-')}.json`);
315
- mkdirSync(dirname(summaryPath), {
316
- recursive: true
317
- });
302
+ mkdirSync(dirname(summaryPath), { recursive: true });
318
303
  writeFileSync(summaryPath, JSON.stringify(summary, null, 2));
319
304
  console.log(`\n──── sf-live-benchmark 汇总 ────`);
320
305
  console.log(`golden requested = ${summary.requested_source}`);
@@ -322,17 +307,14 @@ async function main() {
322
307
  console.log(`跑批 query 数: ${records.length}`);
323
308
  console.log(`verdict=hit: ${hit}/${records.length}`);
324
309
  console.log(`双源合同=comparable: ${comparable}/${records.length}`);
325
- console.log(`字段准确率 ≥${SESSION_FIELD_ACCURACY_THRESHOLD * 100}% (软命中): ${accuracy_pass}/${records.filter((r)=>r.softScore !== null).length}`);
310
+ console.log(`字段准确率 ≥${SESSION_FIELD_ACCURACY_THRESHOLD * 100}% (软命中): ${accuracy_pass}/${records.filter((r) => r.softScore !== null).length}`);
326
311
  console.log(`live <15s (hit): ${live_under_15s}/${hit || 0}`);
327
312
  console.log(`challenge/guard 触发: ${challenge}`);
328
313
  console.log(`总耗时: ${summary.duration_ms}ms`);
329
314
  process.exit(0);
330
315
  }
331
- main().catch((e)=>{
316
+ main().catch((e) => {
332
317
  console.error(`[sf-live-benchmark] 异常: ${e instanceof Error ? e.message : String(e)}`);
333
318
  console.error(e instanceof Error ? e.stack : '');
334
319
  process.exit(1);
335
320
  });
336
-
337
-
338
- //# sourceURL=ts/scripts/sf-live-benchmark.ts
@@ -3,19 +3,12 @@ import { spawnSync } from 'node:child_process';
3
3
  import { join } from 'node:path';
4
4
  const tsxCli = join(import.meta.dirname, '..', 'node_modules', 'tsx', 'dist', 'cli.mjs');
5
5
  const runner = join(import.meta.dirname, 'sf-live-benchmark.ts');
6
- const unknown = spawnSync(process.execPath, [
7
- tsxCli,
8
- runner,
9
- '--golden=ctrip-open'
10
- ], {
6
+ const unknown = spawnSync(process.execPath, [tsxCli, runner, '--golden=ctrip-open'], {
11
7
  cwd: join(import.meta.dirname, '..'),
12
8
  encoding: 'utf8',
13
- timeout: 5_000
9
+ timeout: 5_000,
14
10
  });
15
11
  assert.equal(unknown.status, 1, `未知 vendor 应在网络调用前 exit 1,实际 status=${unknown.status} error=${unknown.error?.message ?? '-'}`);
16
12
  assert.match(unknown.stderr, /不支持的 golden vendor: ctrip-open/);
17
13
  assert.equal(unknown.stdout.includes('sf-01 上海→丽江'), false, 'fail-closed 前不得启动任何 query');
18
14
  console.log('SF LIVE CLI TESTS: unknown vendor fail-closed before network OK');
19
-
20
-
21
- //# sourceURL=ts/scripts/sf-live-cli-tests.ts
@@ -1,46 +1,28 @@
1
1
  import assert from 'node:assert/strict';
2
2
  const modulePath = '../capabilities/session/golden-score.js';
3
- const scorer = await import(modulePath).catch(()=>({}));
3
+ const scorer = await import(modulePath).catch(() => ({}));
4
4
  assert.equal(typeof scorer.scoreSessionAgainstGoldenBand, 'function', 'soft score 必须从 live runner 抽出,static provider 才能走同一离线评分合同');
5
5
  const band = {
6
6
  query_id: 'sf-01',
7
7
  from: '上海',
8
8
  to: '丽江',
9
9
  date: '2026-10-01',
10
- window_dep_local: {
11
- earliest: '06:00',
12
- latest: '22:00'
13
- },
14
- window_arr_local: {
15
- earliest: '10:00',
16
- latest: '23:59'
17
- },
18
- duration_min: {
19
- min: 180,
20
- max: 720
21
- },
22
- price_cny: {
23
- min: 800,
24
- max: 4000
25
- },
10
+ window_dep_local: { earliest: '06:00', latest: '22:00' },
11
+ window_arr_local: { earliest: '10:00', latest: '23:59' },
12
+ duration_min: { min: 180, max: 720 },
13
+ price_cny: { min: 800, max: 4000 },
26
14
  transport_hint: '公开班期承运提示',
27
- known_flights: [
28
- 'HO5577',
29
- 'MU6145',
30
- '9C8779'
31
- ]
15
+ known_flights: ['HO5577', 'MU6145', '9C8779'],
32
16
  };
33
17
  const session = {
34
18
  query_id: 'sf-01',
35
- route_segments: [
36
- {
19
+ route_segments: [{
37
20
  from: '上海',
38
21
  to: '丽江',
39
22
  departure_at: '2026-10-01T06:45:00+08:00',
40
23
  arrival_at: '2026-10-01T10:15:00+08:00',
41
- transport_number: 'MU6145'
42
- }
43
- ],
24
+ transport_number: 'MU6145',
25
+ }],
44
26
  journey_type: 'direct',
45
27
  currency: 'CNY',
46
28
  price: 1200,
@@ -48,7 +30,7 @@ const session = {
48
30
  fetched_at: '2026-08-30T12:00:00.000Z',
49
31
  verdict: 'hit',
50
32
  latency_ms: 1200,
51
- read_guard_blocked: 0
33
+ read_guard_blocked: 0,
52
34
  };
53
35
  const score = scorer.scoreSessionAgainstGoldenBand(session, band, 'static-openflights+manual-band');
54
36
  assert.deepEqual(score, {
@@ -58,51 +40,28 @@ assert.deepEqual(score, {
58
40
  accuracy: 1,
59
41
  missing: [],
60
42
  incorrect: [],
61
- golden_source: 'static-openflights+manual-band'
43
+ golden_source: 'static-openflights+manual-band',
62
44
  });
63
- const wrongOrigin = scorer.scoreSessionAgainstGoldenBand({
64
- ...session,
65
- route_segments: [
66
- {
67
- ...session.route_segments[0],
68
- from: '广州'
69
- }
70
- ]
71
- }, band, 'static-openflights+manual-band');
45
+ const wrongOrigin = scorer.scoreSessionAgainstGoldenBand({ ...session, route_segments: [{ ...session.route_segments[0], from: '广州' }] }, band, 'static-openflights+manual-band');
72
46
  assert.equal(wrongOrigin.accuracy, 12 / 13);
73
47
  assert.equal(wrongOrigin.pass, false, 'from/to 是硬字段;高于 90% 也不能放过错误 OD');
74
- assert.deepEqual(wrongOrigin.incorrect, [
75
- 'route_segments[0].from'
76
- ]);
77
- const missingPrice = scorer.scoreSessionAgainstGoldenBand({
78
- ...session,
79
- price: 0
80
- }, band, 'static-openflights+manual-band');
48
+ assert.deepEqual(wrongOrigin.incorrect, ['route_segments[0].from']);
49
+ const missingPrice = scorer.scoreSessionAgainstGoldenBand({ ...session, price: 0 }, band, 'static-openflights+manual-band');
81
50
  assert.equal(missingPrice.total, 13, '价格缺失不能靠缩小分母抬高准确率');
82
51
  assert.equal(missingPrice.pass, false);
83
- assert.deepEqual(missingPrice.missing, [
84
- 'price'
85
- ]);
52
+ assert.deepEqual(missingPrice.missing, ['price']);
86
53
  const wrongDate = scorer.scoreSessionAgainstGoldenBand({
87
54
  ...session,
88
- route_segments: [
89
- {
55
+ route_segments: [{
90
56
  ...session.route_segments[0],
91
57
  departure_at: '2026-10-02T06:45:00+08:00',
92
- arrival_at: '2026-10-02T10:15:00+08:00'
93
- }
94
- ]
58
+ arrival_at: '2026-10-02T10:15:00+08:00',
59
+ }],
95
60
  }, band, 'static-openflights+manual-band');
96
61
  assert.equal(wrongDate.pass, false, '仅 HH:mm 落窗但服务日期错误时必须 fail-closed');
97
62
  assert.ok(wrongDate.incorrect.includes('route_segments[0].departure_at.date'));
98
63
  assert.ok(wrongDate.incorrect.includes('route_segments[0].arrival_at.date'));
99
- const wrongSessionSource = scorer.scoreSessionAgainstGoldenBand({
100
- ...session,
101
- source: 'manual-golden'
102
- }, band, 'static-openflights+manual-band');
64
+ const wrongSessionSource = scorer.scoreSessionAgainstGoldenBand({ ...session, source: 'manual-golden' }, band, 'static-openflights+manual-band');
103
65
  assert.equal(wrongSessionSource.pass, false, 'golden 自身不能冒充待核验的 Ctrip session 来源');
104
66
  assert.ok(wrongSessionSource.incorrect.includes('source'));
105
67
  console.log('SF SOFT SCORE TESTS: fixed 13-field score + hard OD/date/session-source fail-closed OK');
106
-
107
-
108
- //# sourceURL=ts/scripts/sf-soft-score-tests.ts