@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,14 +1,19 @@
1
- import { parseCandidate, parseRequest } from './model.js';
1
+ /**
2
+ * 可行性引擎 TS 版(与 py/gotry_feasibility/engine.py 对齐;Python 是对照实现/oracle)。
3
+ * DEPRECATED(D-1 清偿):统一模型(unified.ts)是唯一求解入口;保留为兼容层与差分 oracle。
4
+ *
5
+ * Z3 走 npm z3-solver(WASM):
6
+ * - API 为驼峰(addAndTrack/unsatCore/getLower),check/getLower 返回 Promise;
7
+ * - exactly-one 用 Sum(If(sel,1,0)) == 1 表达(JS 绑定的 PbEq 有上下文强转问题);
8
+ * - 有效时长约束整体放大 200 倍避免整除(与 Python 的 /200、/10 数学等价);
9
+ * - WASM init ~60-140ms 一次性,单次求解 ~6ms(进程内,无子进程桥)。
10
+ */
2
11
  import { evaluateChoice, minToHhmm, requiredUsableHours, trueCostToDict, LATEST_ARRIVE_STAY_MIN } from './model.js';
3
12
  import { withZ3 } from './z3-shared.js';
4
13
  import { t } from './i18n.js';
5
14
  const CONSTRAINT_LABEL_KEYS = [
6
- 'wake_floor',
7
- 'energy_floor',
8
- 'usable_hours',
9
- 'budget',
10
- 'duration',
11
- 'arrival_before_evening'
15
+ 'wake_floor', 'energy_floor', 'usable_hours', 'budget',
16
+ 'duration', 'arrival_before_evening',
12
17
  ];
13
18
  class Encoding {
14
19
  days;
@@ -22,41 +27,50 @@ class Encoding {
22
27
  assertions;
23
28
  moneyExpr;
24
29
  z3;
25
- constructor(z3, cand, req, days){
30
+ constructor(z3, cand, req, days) {
26
31
  this.z3 = z3;
27
32
  this.cand = cand;
28
33
  this.req = req;
29
34
  this.days = days ?? req.windowDays;
30
35
  const { Bool, Int, If, Sum, And } = this.z3;
31
36
  const mot = req.motivation;
32
- this.selO = cand.servicesOut.map((_, i)=>Bool.const(`o${i}`));
33
- this.selT = cand.destTransfers.map((_, j)=>Bool.const(`t${j}`));
34
- this.selR = cand.servicesRet.map((_, k)=>Bool.const(`r${k}`));
35
- this.selTr = cand.destTransfers.map((_, m)=>Bool.const(`tr${m}`));
37
+ this.selO = cand.servicesOut.map((_, i) => Bool.const(`o${i}`));
38
+ this.selT = cand.destTransfers.map((_, j) => Bool.const(`t${j}`));
39
+ this.selR = cand.servicesRet.map((_, k) => Bool.const(`r${k}`));
40
+ this.selTr = cand.destTransfers.map((_, m) => Bool.const(`tr${m}`));
36
41
  const access = req.homeHubAccess[cand.hub];
37
- const pick = (sels, values)=>Sum(...sels.map((s, i)=>If(s, Int.val(values[i]), Int.val(0))));
38
- const depOut = pick(this.selO, cand.servicesOut.map((s)=>s.depMin));
39
- const arrOut = pick(this.selO, cand.servicesOut.map((s)=>s.arrMin));
40
- const priceOut = pick(this.selO, cand.servicesOut.map((s)=>s.priceCny));
41
- const transferOut = pick(this.selT, cand.destTransfers.map((t)=>t.minutes));
42
- const transferOutPrice = pick(this.selT, cand.destTransfers.map((t)=>t.priceCny));
43
- const depRet = pick(this.selR, cand.servicesRet.map((s)=>s.depMin));
44
- const priceRet = pick(this.selR, cand.servicesRet.map((s)=>s.priceCny));
45
- const transferRet = pick(this.selTr, cand.destTransfers.map((t)=>t.minutes));
46
- const transferRetPrice = pick(this.selTr, cand.destTransfers.map((t)=>t.priceCny));
42
+ const pick = (sels, values) => Sum(...sels.map((s, i) => If(s, Int.val(values[i]), Int.val(0))));
43
+ const depOut = pick(this.selO, cand.servicesOut.map(s => s.depMin));
44
+ const arrOut = pick(this.selO, cand.servicesOut.map(s => s.arrMin));
45
+ const priceOut = pick(this.selO, cand.servicesOut.map(s => s.priceCny));
46
+ const transferOut = pick(this.selT, cand.destTransfers.map(t => t.minutes));
47
+ const transferOutPrice = pick(this.selT, cand.destTransfers.map(t => t.priceCny));
48
+ const depRet = pick(this.selR, cand.servicesRet.map(s => s.depMin));
49
+ const priceRet = pick(this.selR, cand.servicesRet.map(s => s.priceCny));
50
+ const transferRet = pick(this.selTr, cand.destTransfers.map(t => t.minutes));
51
+ const transferRetPrice = pick(this.selTr, cand.destTransfers.map(t => t.priceCny));
47
52
  const wake = depOut.sub(Int.val(cand.bufferOutMin + access.toHubMin));
48
53
  const arriveStay = arrOut.add(transferOut);
49
54
  const d2dOut = arriveStay.sub(wake);
50
55
  const wakePen = If(wake.lt(Int.val(300)), Int.val(30), If(wake.lt(Int.val(360)), Int.val(25), If(wake.lt(Int.val(390)), Int.val(15), Int.val(0))));
51
- const energy = Int.val(100).sub(wakePen).sub(Int.val(2 * 8)).sub(If(arriveStay.gt(Int.val(21 * 60)), Int.val(10), Int.val(0))).sub(If(d2dOut.gt(Int.val(6 * 60)), Int.val(10), Int.val(0)));
56
+ const energy = Int.val(100)
57
+ .sub(wakePen)
58
+ .sub(Int.val(2 * 8))
59
+ .sub(If(arriveStay.gt(Int.val(21 * 60)), Int.val(10), Int.val(0)))
60
+ .sub(If(d2dOut.gt(Int.val(6 * 60)), Int.val(10), Int.val(0)));
61
+ // 有效时长整体放大 200 倍(免整除,与 Python /200、/10 等价):
62
+ // 200*usable = day1Raw*(100+energy) + 180*day2Raw + 86400*midDays
52
63
  const dayEnd = Int.val(LATEST_ARRIVE_STAY_MIN + 3 * 60);
53
64
  const day1Raw = If(arriveStay.lt(dayEnd), dayEnd.sub(arriveStay), Int.val(0));
54
65
  const leaveStayRet = depRet.sub(Int.val(cand.bufferRetMin)).sub(transferRet);
55
66
  const day2Raw = If(leaveStayRet.gt(Int.val(9 * 60)), leaveStayRet.sub(Int.val(9 * 60)), Int.val(0));
56
67
  const midDays = Math.max(0, this.days - 2);
57
- const usable200 = day1Raw.mul(energy.add(Int.val(100))).add(day2Raw.mul(Int.val(180))).add(Int.val(midDays * 86400));
58
- const money = priceOut.add(priceRet).add(transferOutPrice).add(transferRetPrice).add(Int.val(cand.stayCnyPerNight * (this.days - 1) + cand.localDailyCny * this.days));
59
- const exactlyOne = (sels)=>Sum(...sels.map((s)=>If(s, Int.val(1), Int.val(0)))).eq(Int.val(1));
68
+ const usable200 = day1Raw.mul(energy.add(Int.val(100)))
69
+ .add(day2Raw.mul(Int.val(180)))
70
+ .add(Int.val(midDays * 86400));
71
+ const money = priceOut.add(priceRet).add(transferOutPrice).add(transferRetPrice)
72
+ .add(Int.val(cand.stayCnyPerNight * (this.days - 1) + cand.localDailyCny * this.days));
73
+ const exactlyOne = (sels) => Sum(...sels.map(s => If(s, Int.val(1), Int.val(0)))).eq(Int.val(1));
60
74
  const requiredMin = Math.round(requiredUsableHours(mot) * 60);
61
75
  this.assertions = {
62
76
  wake_floor: wake.ge(Int.val(mot.wakeFloorMin)),
@@ -64,24 +78,32 @@ class Encoding {
64
78
  usable_hours: usable200.ge(Int.val(requiredMin * 200)),
65
79
  budget: money.le(Int.val(req.budgetCny)),
66
80
  duration: Bool.val(this.days >= cand.minDaysForPurpose),
67
- arrival_before_evening: arriveStay.le(Int.val(LATEST_ARRIVE_STAY_MIN))
81
+ arrival_before_evening: arriveStay.le(Int.val(LATEST_ARRIVE_STAY_MIN)),
68
82
  };
69
83
  this.structure = And(exactlyOne(this.selO), exactlyOne(this.selT), exactlyOne(this.selR), exactlyOne(this.selTr));
70
84
  this.moneyExpr = money;
71
85
  }
72
86
  async solverWith(skip = new Set()) {
73
87
  const s = new this.z3.Solver();
74
- s.add(this.structure);
75
- for (const name of CONSTRAINT_LABEL_KEYS){
76
- if (!skip.has(name)) s.addAndTrack(this.assertions[name], name);
88
+ try {
89
+ s.add(this.structure);
90
+ for (const name of CONSTRAINT_LABEL_KEYS) {
91
+ if (!skip.has(name))
92
+ s.addAndTrack(this.assertions[name], name);
93
+ }
94
+ return s;
95
+ }
96
+ catch (error) {
97
+ releaseZ3(s);
98
+ throw error;
77
99
  }
78
- return s;
79
100
  }
80
101
  async extract(model) {
81
- const chosen = async (sels, items)=>{
82
- for(let i = 0; i < sels.length; i++){
102
+ const chosen = async (sels, items) => {
103
+ for (let i = 0; i < sels.length; i++) {
83
104
  const v = await maybeAwait(model.eval(sels[i], true));
84
- if (String(v) === 'true' || String(String(v)) === 'true') return items[i];
105
+ if (String(v) === 'true' || String(String(v)) === 'true')
106
+ return items[i];
85
107
  }
86
108
  throw new Error('no selection in model');
87
109
  };
@@ -90,10 +112,14 @@ class Encoding {
90
112
  outTransfer: await chosen(this.selT, this.cand.destTransfers),
91
113
  retService: await chosen(this.selR, this.cand.servicesRet),
92
114
  retTransfer: await chosen(this.selTr, this.cand.destTransfers),
93
- days: this.days
115
+ days: this.days,
94
116
  };
95
117
  }
96
118
  }
119
+ function releaseZ3(resource) {
120
+ if (typeof resource?.release === 'function')
121
+ resource.release();
122
+ }
97
123
  async function maybeAwait(v) {
98
124
  return v instanceof Promise ? await v : v;
99
125
  }
@@ -103,174 +129,202 @@ async function isUnsat(s) {
103
129
  async function unsatCoreOf(s) {
104
130
  const v = s.unsatCore();
105
131
  const out = [];
106
- for(let i = 0; i < v.length(); i++)out.push(String(v.get(i)));
132
+ for (let i = 0; i < v.length(); i++)
133
+ out.push(String(v.get(i)));
107
134
  return out;
108
135
  }
109
136
  async function cheapestPlan(enc, skip, z3) {
137
+ /** 放宽 skip 约束下的最省钱方案:getLower 取最优值,等值回代提模(与 Python _cheapest_plan 同)。 */
110
138
  const opt = new z3.Optimize();
111
- opt.add(enc.structure);
112
- for (const name of CONSTRAINT_LABEL_KEYS){
113
- if (!skip.has(name)) opt.add(enc.assertions[name]);
114
- }
115
- const obj = opt.minimize(enc.moneyExpr);
116
- if (await isUnsat(opt)) return null;
117
- const best = Number(String(await maybeAwait(opt.getLower(obj))));
118
- const s = await enc.solverWith(skip);
119
- s.add(enc.moneyExpr.eq(z3.Int.val(best)));
120
- if (await isUnsat(s)) return null;
121
- const choice = await enc.extract(s.model());
122
- return {
123
- choice,
124
- cost: evaluateChoice(enc.cand, enc.req, choice)
139
+ let optOpen = true;
140
+ const closeOpt = () => {
141
+ if (optOpen) {
142
+ releaseZ3(opt);
143
+ optOpen = false;
144
+ }
125
145
  };
146
+ try {
147
+ opt.add(enc.structure);
148
+ for (const name of CONSTRAINT_LABEL_KEYS) {
149
+ if (!skip.has(name))
150
+ opt.add(enc.assertions[name]);
151
+ }
152
+ const obj = opt.minimize(enc.moneyExpr);
153
+ if (await isUnsat(opt))
154
+ return null;
155
+ const best = Number(String(await maybeAwait(opt.getLower(obj))));
156
+ closeOpt();
157
+ const s = await enc.solverWith(skip);
158
+ try {
159
+ s.add(enc.moneyExpr.eq(z3.Int.val(best)));
160
+ if (await isUnsat(s))
161
+ return null;
162
+ const model = s.model();
163
+ try {
164
+ const choice = await enc.extract(model);
165
+ return { choice, cost: evaluateChoice(enc.cand, enc.req, choice) };
166
+ }
167
+ finally {
168
+ releaseZ3(model);
169
+ }
170
+ }
171
+ finally {
172
+ releaseZ3(s);
173
+ }
174
+ }
175
+ finally {
176
+ closeOpt();
177
+ }
126
178
  }
127
179
  function suggestText(relax, cand, req, choice, cost) {
128
180
  const mot = req.motivation;
129
181
  const parts = [];
130
- if (relax.includes('duration')) parts.push(t('sg.duration', {
131
- days: choice.days,
132
- name: cand.name
133
- }));
134
- if (relax.includes('budget')) parts.push(t('sg.budget', {
135
- cost: cost.moneyCny,
136
- budget: req.budgetCny
137
- }));
138
- if (relax.includes('wake_floor')) parts.push(t('sg.wake_floor', {
139
- wake: minToHhmm(cost.wakeMin)
140
- }));
141
- if (relax.includes('energy_floor')) parts.push(t('sg.energy_floor', {
142
- pct: cost.energyArrivalPct,
143
- min: mot.minArrivalEnergyPct
144
- }));
145
- if (relax.includes('usable_hours')) parts.push(t('sg.usable_hours', {
146
- hours: cost.usableHours.toFixed(1),
147
- need: requiredUsableHours(mot).toFixed(1)
148
- }));
149
- if (relax.includes('arrival_before_evening')) parts.push(t('sg.arrival', {
150
- time: minToHhmm(cost.arriveStayMin)
151
- }));
182
+ if (relax.includes('duration'))
183
+ parts.push(t('sg.duration', { days: choice.days, name: cand.name }));
184
+ if (relax.includes('budget'))
185
+ parts.push(t('sg.budget', { cost: cost.moneyCny, budget: req.budgetCny }));
186
+ if (relax.includes('wake_floor'))
187
+ parts.push(t('sg.wake_floor', { wake: minToHhmm(cost.wakeMin) }));
188
+ if (relax.includes('energy_floor'))
189
+ parts.push(t('sg.energy_floor', { pct: cost.energyArrivalPct, min: mot.minArrivalEnergyPct }));
190
+ if (relax.includes('usable_hours'))
191
+ parts.push(t('sg.usable_hours', { hours: cost.usableHours.toFixed(1), need: requiredUsableHours(mot).toFixed(1) }));
192
+ if (relax.includes('arrival_before_evening'))
193
+ parts.push(t('sg.arrival', { time: minToHhmm(cost.arriveStayMin) }));
152
194
  const plan = t('sg.plan', {
153
- id: choice.outService.id,
154
- dep: minToHhmm(choice.outService.depMin),
155
- mode: choice.outTransfer.mode,
156
- money: cost.moneyCny,
157
- wake: minToHhmm(cost.wakeMin),
158
- arrive: minToHhmm(cost.arriveStayMin),
159
- percent: cost.energyArrivalPct
195
+ id: choice.outService.id, dep: minToHhmm(choice.outService.depMin), mode: choice.outTransfer.mode,
196
+ money: cost.moneyCny, wake: minToHhmm(cost.wakeMin), arrive: minToHhmm(cost.arriveStayMin),
197
+ percent: cost.energyArrivalPct,
160
198
  });
161
199
  return parts.join(' + ') + `;${plan}`;
162
200
  }
163
201
  export async function solveCandidate(cand, req) {
164
- return withZ3('engine.solveCandidate', async (z3)=>{
202
+ // 会话级互斥门:整个判定(编码 + 多轮求解)独占共享实例;门内绝不嵌套 withZ3。
203
+ return withZ3('engine.solveCandidate', async (z3) => {
165
204
  const enc = new Encoding(z3, cand, req);
166
205
  const verdict = {
167
- candidateId: cand.id,
168
- name: cand.name,
169
- feasible: false,
170
- imageryMatch: cand.imageryMatch,
171
- unsatCore: [],
172
- suggestions: []
206
+ candidateId: cand.id, name: cand.name, feasible: false,
207
+ imageryMatch: cand.imageryMatch, unsatCore: [], suggestions: [],
173
208
  };
174
209
  const s = await enc.solverWith();
175
- if (!await isUnsat(s)) {
176
- verdict.feasible = true;
177
- verdict.choice = await enc.extract(s.model());
178
- verdict.trueCost = evaluateChoice(cand, req, verdict.choice);
179
- return verdict;
180
- }
181
- const core = (await unsatCoreOf(s)).sort();
182
- verdict.unsatCore = core;
183
- for (const name of core){
184
- const skip = new Set([
185
- name
186
- ]);
187
- const enc2 = name === 'duration' ? new Encoding(z3, cand, req, cand.minDaysForPurpose) : enc;
188
- const s2 = await enc2.solverWith(skip);
189
- if (!await isUnsat(s2)) {
190
- const choice = await enc2.extract(s2.model());
191
- const cost = evaluateChoice(cand, req, choice);
192
- verdict.suggestions.push({
193
- relax: [
194
- name
195
- ],
196
- text: suggestText([
197
- name
198
- ], cand, req, choice, cost),
199
- resulting: {
200
- days: choice.days,
201
- ...trueCostToDict(cost)
202
- }
203
- });
210
+ let sOpen = true;
211
+ const closeS = () => {
212
+ if (sOpen) {
213
+ releaseZ3(s);
214
+ sOpen = false;
204
215
  }
205
- }
206
- if (core.includes('duration')) {
207
- const longEnc = new Encoding(z3, cand, req, cand.minDaysForPurpose);
208
- const s2 = await longEnc.solverWith(new Set([
209
- 'duration'
210
- ]));
211
- if (await isUnsat(s2)) {
212
- const core2 = await unsatCoreOf(s2);
213
- let joint = [
214
- 'duration',
215
- ...core2.filter((n)=>n !== 'duration')
216
- ];
217
- for (const name of joint.filter((n)=>n !== 'duration')){
218
- const trial = new Set(joint.filter((n)=>n !== name));
219
- if (!await isUnsat(await longEnc.solverWith(trial))) joint = [
220
- ...trial
221
- ];
216
+ };
217
+ try {
218
+ if (!(await isUnsat(s))) {
219
+ verdict.feasible = true;
220
+ const model = s.model();
221
+ try {
222
+ verdict.choice = await enc.extract(model);
223
+ }
224
+ finally {
225
+ releaseZ3(model);
226
+ }
227
+ verdict.trueCost = evaluateChoice(cand, req, verdict.choice);
228
+ return verdict;
229
+ }
230
+ const core = (await unsatCoreOf(s)).sort();
231
+ verdict.unsatCore = core;
232
+ closeS();
233
+ // 单条放宽(days 仍是数据:放宽 duration 时换长窗口口径重新编码)
234
+ for (const name of core) {
235
+ const skip = new Set([name]);
236
+ const enc2 = name === 'duration' ? new Encoding(z3, cand, req, cand.minDaysForPurpose) : enc;
237
+ const s2 = await enc2.solverWith(skip);
238
+ try {
239
+ if (!(await isUnsat(s2))) {
240
+ const model = s2.model();
241
+ try {
242
+ const choice = await enc2.extract(model);
243
+ const cost = evaluateChoice(cand, req, choice);
244
+ verdict.suggestions.push({ relax: [name], text: suggestText([name], cand, req, choice, cost), resulting: { days: choice.days, ...trueCostToDict(cost) } });
245
+ }
246
+ finally {
247
+ releaseZ3(model);
248
+ }
249
+ }
250
+ }
251
+ finally {
252
+ releaseZ3(s2);
222
253
  }
223
- const plan = await cheapestPlan(longEnc, new Set(joint), z3);
224
- if (plan) {
225
- verdict.suggestions.push({
226
- relax: joint,
227
- text: suggestText(joint, cand, req, plan.choice, plan.cost),
228
- resulting: {
229
- days: plan.choice.days,
230
- ...trueCostToDict(plan.cost)
254
+ }
255
+ // 组合 core:放宽 duration 换长口径 → 取新口径 core 叠加 → 收缩
256
+ if (core.includes('duration')) {
257
+ const longEnc = new Encoding(z3, cand, req, cand.minDaysForPurpose);
258
+ const s2 = await longEnc.solverWith(new Set(['duration']));
259
+ try {
260
+ if (await isUnsat(s2)) {
261
+ const core2 = await unsatCoreOf(s2);
262
+ let joint = ['duration', ...core2.filter(n => n !== 'duration')];
263
+ for (const name of joint.filter(n => n !== 'duration')) {
264
+ const trial = new Set(joint.filter(n => n !== name));
265
+ const trialSolver = await longEnc.solverWith(trial);
266
+ try {
267
+ if (!(await isUnsat(trialSolver)))
268
+ joint = [...trial];
269
+ }
270
+ finally {
271
+ releaseZ3(trialSolver);
272
+ }
231
273
  }
232
- });
274
+ const plan = await cheapestPlan(longEnc, new Set(joint), z3);
275
+ if (plan) {
276
+ verdict.suggestions.push({
277
+ relax: joint,
278
+ text: suggestText(joint, cand, req, plan.choice, plan.cost),
279
+ resulting: { days: plan.choice.days, ...trueCostToDict(plan.cost) },
280
+ });
281
+ }
282
+ }
283
+ }
284
+ finally {
285
+ releaseZ3(s2);
233
286
  }
234
287
  }
288
+ // 憧憬不被拒绝:放宽时长能救活 → wish pool + 成行条件(最省钱口径)
289
+ if (verdict.suggestions.some(sg => sg.relax.includes('duration'))) {
290
+ const longEnc = new Encoding(z3, cand, req, cand.minDaysForPurpose);
291
+ const plan = await cheapestPlan(longEnc, new Set(['duration', 'budget']), z3);
292
+ const conditions = { days: cand.minDaysForPurpose };
293
+ if (plan)
294
+ conditions['budget_cny'] = plan.cost.moneyCny;
295
+ if (cand.bestMonths.length)
296
+ conditions['best_months'] = cand.bestMonths;
297
+ verdict.wishPool = {
298
+ name: cand.name,
299
+ conditions,
300
+ reason: t('sg.wish_reason_engine', { weights: JSON.stringify(req.motivation.weights), days: req.windowDays }),
301
+ };
302
+ }
303
+ return verdict;
235
304
  }
236
- if (verdict.suggestions.some((sg)=>sg.relax.includes('duration'))) {
237
- const longEnc = new Encoding(z3, cand, req, cand.minDaysForPurpose);
238
- const plan = await cheapestPlan(longEnc, new Set([
239
- 'duration',
240
- 'budget'
241
- ]), z3);
242
- const conditions = {
243
- days: cand.minDaysForPurpose
244
- };
245
- if (plan) conditions['budget_cny'] = plan.cost.moneyCny;
246
- if (cand.bestMonths.length) conditions['best_months'] = cand.bestMonths;
247
- verdict.wishPool = {
248
- name: cand.name,
249
- conditions,
250
- reason: t('sg.wish_reason_engine', {
251
- weights: JSON.stringify(req.motivation.weights),
252
- days: req.windowDays
253
- })
254
- };
305
+ finally {
306
+ closeS();
255
307
  }
256
- return verdict;
257
308
  });
258
309
  }
259
310
  export async function solve(req, candidates) {
311
+ // 顺序求解:候选间共享单一 WASM 实例,不并发交错(此前 Promise.all 是
312
+ // 「memory access out of bounds」偶发的根因;求解 ~6ms/次,串行可忽略)。
260
313
  const verdicts = [];
261
- for (const c of candidates)verdicts.push(await solveCandidate(c, req));
262
- const feasible = verdicts.filter((v)=>v.feasible).sort((a, b)=>b.imageryMatch - a.imageryMatch);
314
+ for (const c of candidates)
315
+ verdicts.push(await solveCandidate(c, req));
316
+ const feasible = verdicts.filter(v => v.feasible).sort((a, b) => b.imageryMatch - a.imageryMatch);
263
317
  const recommended = feasible[0]?.candidateId ?? null;
264
318
  return {
265
319
  request: {
266
320
  note: req.note,
267
321
  window_days: req.windowDays,
268
322
  budget_cny: req.budgetCny,
269
- required_usable_hours: Math.round(requiredUsableHours(req.motivation) * 10) / 10
323
+ required_usable_hours: Math.round(requiredUsableHours(req.motivation) * 10) / 10,
270
324
  },
271
325
  verdicts: verdicts.map(verdictToDict),
272
326
  recommended,
273
- answer_md: renderMarkdown(req, verdicts, recommended)
327
+ answer_md: renderMarkdown(req, verdicts, recommended),
274
328
  };
275
329
  }
276
330
  function verdictToDict(v) {
@@ -280,11 +334,7 @@ function verdictToDict(v) {
280
334
  feasible: v.feasible,
281
335
  imagery_match: v.imageryMatch,
282
336
  unsat_core: v.unsatCore,
283
- suggestions: v.suggestions.map((s)=>({
284
- relax: s.relax,
285
- text: s.text,
286
- resulting: s.resulting
287
- }))
337
+ suggestions: v.suggestions.map(s => ({ relax: s.relax, text: s.text, resulting: s.resulting })),
288
338
  };
289
339
  if (v.choice) {
290
340
  d['chosen'] = {
@@ -292,109 +342,73 @@ function verdictToDict(v) {
292
342
  out_transfer: v.choice.outTransfer.mode,
293
343
  ret_service: v.choice.retService.id,
294
344
  ret_transfer: v.choice.retTransfer.mode,
295
- days: v.choice.days
345
+ days: v.choice.days,
296
346
  };
297
347
  }
298
- if (v.trueCost) d['true_cost'] = trueCostToDict(v.trueCost);
299
- if (v.wishPool) d['wish_pool'] = {
300
- name: v.wishPool.name,
301
- conditions: v.wishPool.conditions,
302
- reason: v.wishPool.reason
303
- };
348
+ if (v.trueCost)
349
+ d['true_cost'] = trueCostToDict(v.trueCost);
350
+ if (v.wishPool)
351
+ d['wish_pool'] = { name: v.wishPool.name, conditions: v.wishPool.conditions, reason: v.wishPool.reason };
304
352
  return d;
305
353
  }
306
354
  function renderMarkdown(req, verdicts, recommended) {
307
- const feasible = verdicts.filter((v)=>v.feasible).sort((a, b)=>b.imageryMatch - a.imageryMatch);
308
- const parked = verdicts.filter((v)=>!v.feasible);
355
+ const feasible = verdicts.filter(v => v.feasible).sort((a, b) => b.imageryMatch - a.imageryMatch);
356
+ const parked = verdicts.filter(v => !v.feasible);
309
357
  const lines = [];
310
- lines.push(t('md.header', {
311
- note: req.note
312
- }));
358
+ lines.push(t('md.header', { note: req.note }));
313
359
  lines.push(t('md.constraints', {
314
- days: req.windowDays,
315
- budget: req.budgetCny,
316
- hours: requiredUsableHours(req.motivation).toFixed(1)
360
+ days: req.windowDays, budget: req.budgetCny,
361
+ hours: requiredUsableHours(req.motivation).toFixed(1),
317
362
  }));
318
363
  lines.push('');
319
- for (const v of parked){
320
- lines.push(t('md.infeasible', {
321
- name: v.name,
322
- core: v.unsatCore.join('、')
323
- }));
324
- for (const sg of v.suggestions.slice(0, 1))lines.push(`- ${sg.text}`);
364
+ for (const v of parked) {
365
+ lines.push(t('md.infeasible', { name: v.name, core: v.unsatCore.join('、') }));
366
+ for (const sg of v.suggestions.slice(0, 1))
367
+ lines.push(`- ${sg.text}`);
325
368
  if (v.wishPool) {
326
369
  const conds = v.wishPool.conditions;
327
- const budgetNote = conds['budget_cny'] ? t('md.wish_budget', {
328
- cny: String(conds['budget_cny'])
329
- }) : '';
370
+ const budgetNote = conds['budget_cny'] ? t('md.wish_budget', { cny: String(conds['budget_cny']) }) : '';
330
371
  const months = conds['best_months'];
331
- const season = months ? t('md.wish_season', {
332
- months: months.join(',')
333
- }) : '';
334
- lines.push(t('md.wish_pool', {
335
- days: String(conds['days']),
336
- budgetNote,
337
- season
338
- }));
372
+ const season = months ? t('md.wish_season', { months: months.join(',') }) : '';
373
+ lines.push(t('md.wish_pool', { days: String(conds['days']), budgetNote, season }));
339
374
  }
340
375
  }
341
376
  lines.push('');
342
- for (const v of feasible){
377
+ for (const v of feasible) {
343
378
  const c = v.choice, cost = v.trueCost;
344
379
  lines.push(t('md.feasible_engine', {
345
- name: v.name,
346
- id: c.outService.id,
347
- dep: minToHhmm(c.outService.depMin),
348
- mode: c.outTransfer.mode,
349
- arrive: minToHhmm(cost.arriveStayMin),
350
- wake: minToHhmm(cost.wakeMin),
351
- energy: cost.energyArrivalPct,
380
+ name: v.name, id: c.outService.id, dep: minToHhmm(c.outService.depMin), mode: c.outTransfer.mode,
381
+ arrive: minToHhmm(cost.arriveStayMin), wake: minToHhmm(cost.wakeMin), energy: cost.energyArrivalPct,
352
382
  d2d: `${Math.floor(cost.doorToDoorOutMin / 60)}h${String(cost.doorToDoorOutMin % 60).padStart(2, '0')}m`,
353
- hours: cost.usableHours.toFixed(1),
354
- money: cost.moneyCny
383
+ hours: cost.usableHours.toFixed(1), money: cost.moneyCny,
355
384
  }));
356
385
  }
357
386
  if (feasible.length) {
358
387
  lines.push('');
359
388
  const best = feasible[0];
360
389
  const alt = feasible[1];
361
- lines.push(t('md.recommend', {
362
- best: best.name,
363
- match: (best.imageryMatch * 100).toFixed(0)
364
- }) + (alt ? t('md.alt', {
365
- name: alt.name,
366
- money: alt.trueCost.moneyCny,
367
- match: (alt.imageryMatch * 100).toFixed(0)
368
- }) : ''));
390
+ lines.push(t('md.recommend', { best: best.name, match: (best.imageryMatch * 100).toFixed(0) })
391
+ + (alt ? t('md.alt', { name: alt.name, money: alt.trueCost.moneyCny, match: (alt.imageryMatch * 100).toFixed(0) }) : ''));
369
392
  }
370
393
  lines.push('');
371
394
  lines.push(t('md.decide_two'));
372
395
  if (feasible.length >= 2) {
373
- lines.push(t('md.q_choice', {
374
- a: feasible[0].name,
375
- b: feasible[1].name
376
- }));
377
- } else if (feasible.length === 1) {
378
- lines.push(t('md.q_single', {
379
- name: feasible[0].name
380
- }));
381
- } else {
396
+ lines.push(t('md.q_choice', { a: feasible[0].name, b: feasible[1].name }));
397
+ }
398
+ else if (feasible.length === 1) {
399
+ lines.push(t('md.q_single', { name: feasible[0].name }));
400
+ }
401
+ else {
382
402
  lines.push(t('md.q_none'));
383
403
  }
384
404
  const p0 = parked[0];
385
405
  if (p0?.wishPool) {
386
- lines.push(t('md.q_wish', {
387
- name: p0.name,
388
- days: String(p0.wishPool.conditions['days'])
389
- }));
390
- } else if (feasible.length) {
406
+ lines.push(t('md.q_wish', { name: p0.name, days: String(p0.wishPool.conditions['days']) }));
407
+ }
408
+ else if (feasible.length) {
391
409
  lines.push(t('md.q_depart', {
392
- id: feasible[0].choice.outService.id,
393
- dep: minToHhmm(feasible[0].choice.outService.depMin)
410
+ id: feasible[0].choice.outService.id, dep: minToHhmm(feasible[0].choice.outService.depMin),
394
411
  }));
395
412
  }
396
413
  return lines.join('\n');
397
414
  }
398
-
399
-
400
- //# sourceURL=ts/src/engine.ts