@danceiny/gotry 0.0.1-rc.15 → 0.0.1-rc.17

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 (238) hide show
  1. package/README.md +51 -13
  2. package/README.zh-CN.md +26 -12
  3. package/bin/gotry-booking-copilot.js +53 -0
  4. package/bin/gotry-bootstrap.js +357 -8
  5. package/bin/gotry-inner.js +442 -60
  6. package/bin/gotry-runtime-resolution.d.ts +27 -0
  7. package/bin/gotry-runtime-resolution.js +50 -0
  8. package/bin/gotry.js +1 -1
  9. package/cordis.gotry-patch.yml +15 -1
  10. package/data/airline-airports.json +39 -0
  11. package/dist/capabilities/agent-reach-deep.js +1 -1
  12. package/dist/capabilities/agent-reach.js +1 -1
  13. package/dist/capabilities/anything.js +1 -1
  14. package/dist/capabilities/artifacts.js +1 -1
  15. package/dist/capabilities/effect.js +293 -0
  16. package/dist/capabilities/fact-log.js +41 -0
  17. package/dist/capabilities/flyai.js +20 -6
  18. package/dist/capabilities/hbcli.js +43 -1
  19. package/dist/capabilities/incident-log.js +1 -1
  20. package/dist/capabilities/model-override.js +18 -0
  21. package/dist/capabilities/opensky.js +1 -1
  22. package/dist/capabilities/resilience.js +90 -0
  23. package/dist/capabilities/session/action-cache.js +1 -1
  24. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  25. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  26. package/dist/capabilities/session/benchmark.js +2 -1
  27. package/dist/capabilities/session/extension-bridge.js +358 -0
  28. package/dist/capabilities/session/extension-channel.js +93 -0
  29. package/dist/capabilities/session/extension-distribution.js +234 -0
  30. package/dist/capabilities/session/extract.js +1 -1
  31. package/dist/capabilities/session/golden-score.js +92 -0
  32. package/dist/capabilities/session/health-watch.js +216 -0
  33. package/dist/capabilities/session/read-guard.js +1 -1
  34. package/dist/capabilities/session/static-flight-golden.js +137 -0
  35. package/dist/capabilities/session/transport.js +1 -1
  36. package/dist/capabilities/session/wizard.js +147 -0
  37. package/dist/capabilities/session-consent.js +1 -1
  38. package/dist/capabilities/session-login.js +83 -1
  39. package/dist/capabilities/session-search.js +111 -2
  40. package/dist/capabilities/weather.js +168 -46
  41. package/dist/data/session-golden-20.json +25 -0
  42. package/dist/data/sf-golden-manifest.json +102 -0
  43. package/dist/data/sf-static-routes.json +91 -0
  44. package/dist/scripts/action-cache-tests.js +1 -1
  45. package/dist/scripts/agent-planning-budget-e2e.js +227 -0
  46. package/dist/scripts/agent-planning-budget-tests.js +173 -0
  47. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  48. package/dist/scripts/agent-reach-tests.js +1 -1
  49. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  50. package/dist/scripts/anything-tests.js +1 -1
  51. package/dist/scripts/async-collect.js +1 -1
  52. package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
  53. package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
  54. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
  55. package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
  56. package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
  57. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
  58. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
  59. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
  60. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
  61. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
  62. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
  63. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
  64. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
  65. package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
  66. package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
  67. package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
  68. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
  69. package/dist/scripts/booking-saga-tests.js +1 -1
  70. package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
  71. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
  72. package/dist/scripts/bootstrap-tests.js +59 -3
  73. package/dist/scripts/build-changelog.js +279 -0
  74. package/dist/scripts/changelog-tests.js +165 -0
  75. package/dist/scripts/companion-tests.js +1 -1
  76. package/dist/scripts/diff-test.js +1 -1
  77. package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
  78. package/dist/scripts/dsh-runtime-closure.js +150 -0
  79. package/dist/scripts/effect-tests.js +474 -0
  80. package/dist/scripts/engine-run.js +1 -1
  81. package/dist/scripts/engine-tests.js +1 -1
  82. package/dist/scripts/evaluation-cadence-tests.js +347 -0
  83. package/dist/scripts/evaluation-contract-tests.js +574 -0
  84. package/dist/scripts/extension-distribution-cli.js +35 -0
  85. package/dist/scripts/extension-distribution-tests.js +341 -0
  86. package/dist/scripts/extension-tests.js +573 -0
  87. package/dist/scripts/fact-gate-tests.js +318 -0
  88. package/dist/scripts/flyai-tests.js +1 -1
  89. package/dist/scripts/hbcli-e2e-tests.js +180 -0
  90. package/dist/scripts/hbcli-tests.js +1 -1
  91. package/dist/scripts/health-watch-cli.js +51 -0
  92. package/dist/scripts/i18n-tests.js +1 -1
  93. package/dist/scripts/incident-tests.js +1 -1
  94. package/dist/scripts/journey-tests.js +1 -1
  95. package/dist/scripts/ledger-tests.js +1 -1
  96. package/dist/scripts/ledger-workflow-crash.js +1 -1
  97. package/dist/scripts/memory-capture-tests.js +1 -1
  98. package/dist/scripts/memory-decay-tests.js +1 -1
  99. package/dist/scripts/memory-metrics.js +1 -1
  100. package/dist/scripts/memory-value-report.js +1 -1
  101. package/dist/scripts/model-override-e2e.js +176 -0
  102. package/dist/scripts/nightly-evidence-tests.js +67 -4
  103. package/dist/scripts/nightly-evidence.js +59 -3
  104. package/dist/scripts/nudge-digest.js +1 -1
  105. package/dist/scripts/onboarding-tests.js +186 -0
  106. package/dist/scripts/opensky-check.js +1 -1
  107. package/dist/scripts/opensky-tests.js +1 -1
  108. package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
  109. package/dist/scripts/price-drift-tests.js +441 -0
  110. package/dist/scripts/price-drift-watch.js +491 -0
  111. package/dist/scripts/probe-poi-tests.js +1 -1
  112. package/dist/scripts/product-metrics.js +1 -1
  113. package/dist/scripts/publish-preverify.js +40 -4
  114. package/dist/scripts/realtime-pricing-tests.js +1 -1
  115. package/dist/scripts/replay-async.js +1 -1
  116. package/dist/scripts/replay-real.js +1 -1
  117. package/dist/scripts/replay.js +1 -1
  118. package/dist/scripts/session-attach-diagnose.js +1 -1
  119. package/dist/scripts/session-attach-poc.js +1 -1
  120. package/dist/scripts/session-benchmark.js +1 -1
  121. package/dist/scripts/session-extract-tests.js +1 -1
  122. package/dist/scripts/session-login.js +1 -1
  123. package/dist/scripts/session-tests.js +70 -20
  124. package/dist/scripts/sf-live-benchmark.js +338 -0
  125. package/dist/scripts/sf-live-cli-tests.js +21 -0
  126. package/dist/scripts/sf-soft-score-tests.js +108 -0
  127. package/dist/scripts/sf-summary.js +93 -0
  128. package/dist/scripts/skeleton-check.js +1 -1
  129. package/dist/scripts/skeleton-integration-test.js +1 -1
  130. package/dist/scripts/skills-contract-tests.js +1 -1
  131. package/dist/scripts/smoke-session-gate-tests.js +29 -0
  132. package/dist/scripts/smoke.js +154 -33
  133. package/dist/scripts/state-cli-tests.js +1 -1
  134. package/dist/scripts/state-cli.js +1 -1
  135. package/dist/scripts/static-golden-tests.js +299 -0
  136. package/dist/scripts/time-eval-tests.js +1 -1
  137. package/dist/scripts/travel-timeline-tests.js +1 -1
  138. package/dist/scripts/unified-tests.js +1 -1
  139. package/dist/scripts/weather-tests.js +694 -44
  140. package/dist/scripts/z3-race-tests.js +1 -1
  141. package/dist/src/artifact-gate.js +310 -0
  142. package/dist/src/benchmark-agent-conformance.js +370 -0
  143. package/dist/src/benchmark-environment-bridge.js +384 -0
  144. package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
  145. package/dist/src/benchmark-tool-isolation.js +124 -0
  146. package/dist/src/bookable-facts.js +349 -0
  147. package/dist/src/booking-saga.js +1 -1
  148. package/dist/src/booking-surface/availability-policy-v2.js +830 -0
  149. package/dist/src/booking-surface/canonical-schema.js +113 -0
  150. package/dist/src/booking-surface/contracts-v2.js +89 -0
  151. package/dist/src/booking-surface/contracts.js +46 -0
  152. package/dist/src/booking-surface/dsh-planner.js +453 -0
  153. package/dist/src/booking-surface/dsh-plugin.js +93 -0
  154. package/dist/src/booking-surface/error-codes.js +94 -0
  155. package/dist/src/booking-surface/index.js +15 -0
  156. package/dist/src/booking-surface/profile.js +68 -0
  157. package/dist/src/booking-surface/runtime-v2.js +1771 -0
  158. package/dist/src/booking-surface/runtime.js +351 -0
  159. package/dist/src/booking-surface/server-v2.js +334 -0
  160. package/dist/src/booking-surface/server.js +302 -0
  161. package/dist/src/booking-surface/startup.js +159 -0
  162. package/dist/src/booking-surface/validation-v2.js +319 -0
  163. package/dist/src/booking-surface/validation.js +809 -0
  164. package/dist/src/bridge.js +1 -1
  165. package/dist/src/companions.js +1 -1
  166. package/dist/src/contracts.js +1 -1
  167. package/dist/src/dsh-llm.js +1 -1
  168. package/dist/src/engine.js +1 -1
  169. package/dist/src/evaluation-cadence.js +234 -0
  170. package/dist/src/evaluation-contracts.js +906 -0
  171. package/dist/src/i18n.js +1 -1
  172. package/dist/src/index.js +234 -60
  173. package/dist/src/journey.js +1 -1
  174. package/dist/src/loop.js +1 -1
  175. package/dist/src/memory-capture.js +1 -1
  176. package/dist/src/memory-decay.js +1 -1
  177. package/dist/src/memory-utility.js +1 -1
  178. package/dist/src/mock-llm.js +1 -1
  179. package/dist/src/model.js +1 -1
  180. package/dist/src/realtime-pricing.js +14 -7
  181. package/dist/src/slot-spec.js +1 -1
  182. package/dist/src/state-ledger.js +2 -1
  183. package/dist/src/time-anchor.js +1 -1
  184. package/dist/src/tool-budget.js +136 -0
  185. package/dist/src/tool-packet.js +1 -1
  186. package/dist/src/travel-slots.js +1 -1
  187. package/dist/src/travel-timeline.js +1 -1
  188. package/dist/src/unified.js +1 -1
  189. package/dist/src/wish-pool.js +1 -1
  190. package/dist/src/z3-shared.js +1 -1
  191. package/extension/README.md +50 -0
  192. package/extension/background.js +160 -0
  193. package/extension/content-bridge.js +41 -0
  194. package/extension/content-main.js +62 -0
  195. package/extension/manifest.json +32 -0
  196. package/package.json +291 -12
  197. package/schemas/booking.surface.v1.schema.json +927 -0
  198. package/schemas/booking.surface.v2.schema.json +61 -0
  199. package/ts/capabilities/fact-log.ts +54 -0
  200. package/ts/capabilities/flyai.ts +16 -3
  201. package/ts/capabilities/hbcli.ts +46 -0
  202. package/ts/capabilities/session/benchmark.ts +4 -1
  203. package/ts/capabilities/session/extension-bridge.ts +377 -0
  204. package/ts/capabilities/session/extension-channel.ts +95 -0
  205. package/ts/capabilities/session/extension-distribution.ts +264 -0
  206. package/ts/capabilities/session/golden-score.ts +139 -0
  207. package/ts/capabilities/session/health-watch.ts +257 -0
  208. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  209. package/ts/capabilities/session/wizard.ts +140 -0
  210. package/ts/capabilities/session-login.ts +63 -2
  211. package/ts/capabilities/session-search.ts +105 -3
  212. package/ts/capabilities/weather.ts +141 -52
  213. package/ts/package.json +3 -3
  214. package/ts/src/artifact-gate.ts +365 -0
  215. package/ts/src/benchmark-agent-conformance.ts +448 -0
  216. package/ts/src/benchmark-environment-bridge.ts +348 -0
  217. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  218. package/ts/src/benchmark-tool-isolation.ts +166 -0
  219. package/ts/src/bookable-facts.ts +557 -0
  220. package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
  221. package/ts/src/booking-surface/canonical-schema.js +113 -0
  222. package/ts/src/booking-surface/contracts-v2.ts +118 -0
  223. package/ts/src/booking-surface/contracts.ts +380 -0
  224. package/ts/src/booking-surface/dsh-planner.ts +452 -0
  225. package/ts/src/booking-surface/dsh-plugin.js +93 -0
  226. package/ts/src/booking-surface/error-codes.ts +101 -0
  227. package/ts/src/booking-surface/index.ts +12 -0
  228. package/ts/src/booking-surface/profile.ts +42 -0
  229. package/ts/src/booking-surface/runtime-v2.ts +1466 -0
  230. package/ts/src/booking-surface/runtime.ts +483 -0
  231. package/ts/src/booking-surface/server-v2.ts +247 -0
  232. package/ts/src/booking-surface/server.ts +324 -0
  233. package/ts/src/booking-surface/startup.ts +196 -0
  234. package/ts/src/booking-surface/validation-v2.ts +205 -0
  235. package/ts/src/booking-surface/validation.ts +453 -0
  236. package/ts/src/index.ts +161 -14
  237. package/ts/src/state-ledger.ts +1 -0
  238. package/ts/src/tool-budget.ts +165 -0
@@ -0,0 +1,349 @@
1
+ import { createHash } from 'node:crypto';
2
+ export const BOOKABLE_FACT_SCHEMA = 'gotry_bookable_fact.v1';
3
+ function sha16(text) {
4
+ return createHash('sha256').update(text).digest('hex').slice(0, 16);
5
+ }
6
+ export function makeQueryId(source, kind, origin, destination, date) {
7
+ return `${source}:${kind}:${origin}-${destination}:${date}`;
8
+ }
9
+ export function makeFactId(parts) {
10
+ return sha16(parts.map((p)=>p ?? '').join('|'));
11
+ }
12
+ function iataOf(alias, city) {
13
+ return alias?.[city] ?? city;
14
+ }
15
+ const YMD = /^\d{4}-\d{2}-\d{2}$/;
16
+ function hmOf(dateTime) {
17
+ const m = dateTime?.match(/T?(\d{2}:\d{2})/);
18
+ return m?.[1];
19
+ }
20
+ function dateOf(dateTime, fallback) {
21
+ const m = dateTime?.match(/^(\d{4}-\d{2}-\d{2})/);
22
+ return m?.[1] ?? fallback;
23
+ }
24
+ export function negativeFact(queryId, kind, origin, destination, date, source, fetchedAt, alias) {
25
+ return {
26
+ schema: BOOKABLE_FACT_SCHEMA,
27
+ fact_id: makeFactId([
28
+ 'neg',
29
+ kind,
30
+ iataOf(alias, origin),
31
+ iataOf(alias, destination),
32
+ date,
33
+ queryId
34
+ ]),
35
+ kind,
36
+ route: {
37
+ origin: iataOf(alias, origin),
38
+ destination: iataOf(alias, destination)
39
+ },
40
+ date,
41
+ flight_no: '',
42
+ tier: 'live_inventory',
43
+ bookability: 'unavailable_exact_date',
44
+ source,
45
+ query_id: queryId,
46
+ fetched_at: fetchedAt,
47
+ as_of: fetchedAt.slice(0, 10)
48
+ };
49
+ }
50
+ export function factsFromFlyai(q, r, fetchedAt, alias) {
51
+ const source = 'flyai';
52
+ const queryId = makeQueryId(source, q.kind, q.origin, q.destination, q.date);
53
+ const origin = iataOf(alias, q.origin);
54
+ const destination = iataOf(alias, q.destination);
55
+ if (r.verdict === 'error') return [];
56
+ if (r.verdict === 'miss') return [
57
+ negativeFact(queryId, q.kind, q.origin, q.destination, q.date, source, fetchedAt, alias)
58
+ ];
59
+ const facts = [];
60
+ for (const o of r.options ?? []){
61
+ if (!o.no || !o.depDateTime) continue;
62
+ const numeric = typeof o.price === 'number' && Number.isFinite(o.price) && o.price > 0 ? o.price : undefined;
63
+ facts.push({
64
+ schema: BOOKABLE_FACT_SCHEMA,
65
+ fact_id: makeFactId([
66
+ q.kind,
67
+ origin,
68
+ destination,
69
+ q.date,
70
+ o.no,
71
+ queryId
72
+ ]),
73
+ kind: q.kind,
74
+ route: {
75
+ origin,
76
+ destination,
77
+ origin_airport: o.depStation,
78
+ dest_airport: o.arrStation
79
+ },
80
+ date: dateOf(o.depDateTime, q.date),
81
+ flight_no: o.no,
82
+ marketing_carrier: o.name || undefined,
83
+ operating_carrier: undefined,
84
+ dep_local: hmOf(o.depDateTime),
85
+ arr_local: hmOf(o.arrDateTime),
86
+ nonstop: o.nonstop !== false,
87
+ price: numeric,
88
+ currency: numeric !== undefined ? 'CNY' : undefined,
89
+ tier: 'live_inventory',
90
+ bookability: 'bookable_exact_date',
91
+ source,
92
+ query_id: queryId,
93
+ fetched_at: fetchedAt,
94
+ as_of: fetchedAt.slice(0, 10)
95
+ });
96
+ }
97
+ return facts.length > 0 ? facts : [
98
+ negativeFact(queryId, q.kind, q.origin, q.destination, q.date, source, fetchedAt, alias)
99
+ ];
100
+ }
101
+ export function factsFromSession(q, r, fetchedAt, alias) {
102
+ const source = 'session:ctrip-flight';
103
+ const queryId = makeQueryId(source, 'flight', q.origin, q.destination, q.date);
104
+ const origin = iataOf(alias, q.origin);
105
+ const destination = iataOf(alias, q.destination);
106
+ if (r.verdict !== 'hit') {
107
+ return r.verdict === 'miss' ? [
108
+ negativeFact(queryId, 'flight', q.origin, q.destination, q.date, source, fetchedAt, alias)
109
+ ] : [];
110
+ }
111
+ const facts = [];
112
+ for (const o of r.options ?? []){
113
+ if (!o.flightNo || !o.depDateTime) continue;
114
+ const numeric = typeof o.price === 'number' && Number.isFinite(o.price) && o.price > 0 ? o.price : undefined;
115
+ facts.push({
116
+ schema: BOOKABLE_FACT_SCHEMA,
117
+ fact_id: makeFactId([
118
+ 'flight',
119
+ origin,
120
+ destination,
121
+ q.date,
122
+ o.flightNo,
123
+ queryId
124
+ ]),
125
+ kind: 'flight',
126
+ route: {
127
+ origin,
128
+ destination,
129
+ origin_airport: o.depAirport,
130
+ dest_airport: o.arrAirport
131
+ },
132
+ date: dateOf(o.depDateTime, q.date),
133
+ flight_no: o.flightNo,
134
+ marketing_carrier: o.airline || undefined,
135
+ operating_carrier: undefined,
136
+ dep_local: hmOf(o.depDateTime),
137
+ arr_local: hmOf(o.arrDateTime),
138
+ nonstop: true,
139
+ price: numeric,
140
+ currency: numeric !== undefined ? 'CNY' : undefined,
141
+ tier: 'live_inventory',
142
+ bookability: 'bookable_exact_date',
143
+ source,
144
+ query_id: queryId,
145
+ fetched_at: fetchedAt,
146
+ as_of: fetchedAt.slice(0, 10)
147
+ });
148
+ }
149
+ return facts;
150
+ }
151
+ export function dedupeFacts(facts) {
152
+ const byId = new Map();
153
+ for (const f of facts){
154
+ const prev = byId.get(f.fact_id);
155
+ if (!prev || prev.fetched_at <= f.fetched_at) byId.set(f.fact_id, f);
156
+ }
157
+ return [
158
+ ...byId.values()
159
+ ].sort((a, b)=>a.fetched_at.localeCompare(b.fetched_at) || a.fact_id.localeCompare(b.fact_id));
160
+ }
161
+ export function latestFactsForRouteDate(facts, origin, destination, date) {
162
+ const hits = facts.filter((f)=>f.kind !== 'policy' && f.route.origin === origin && f.route.destination === destination && f.date === date);
163
+ const latestQueryTs = new Map();
164
+ for (const f of hits){
165
+ const prev = latestQueryTs.get(f.query_id);
166
+ if (!prev || prev < f.fetched_at) latestQueryTs.set(f.query_id, f.fetched_at);
167
+ }
168
+ const latestTs = [
169
+ ...latestQueryTs.values()
170
+ ].sort().pop();
171
+ if (!latestTs) return [];
172
+ return hits.filter((f)=>f.fetched_at === latestTs);
173
+ }
174
+ export function flightClaimVerdict(facts, claim) {
175
+ const no = claim.flight_no.toUpperCase().replace(/\s+/g, '');
176
+ const sameNo = facts.filter((f)=>f.kind !== 'policy' && f.flight_no.toUpperCase() === no && f.bookability === 'bookable_exact_date');
177
+ const routeMatched = sameNo.filter((f)=>(!claim.origin || f.route.origin === claim.origin || f.route.origin_airport === claim.origin) && (!claim.destination || f.route.destination === claim.destination || f.route.dest_airport === claim.destination) && (!claim.date || f.date === claim.date));
178
+ if (routeMatched.length > 0) return {
179
+ verdict: 'traceable',
180
+ fact: routeMatched[0],
181
+ reason: `回溯 ${routeMatched[0].query_id}`
182
+ };
183
+ if (claim.origin && claim.destination && claim.date) {
184
+ const scoped = latestFactsForRouteDate(facts, claim.origin, claim.destination, claim.date);
185
+ if (scoped.length > 0) {
186
+ return {
187
+ verdict: 'not_in_source',
188
+ reason: scoped.some((f)=>f.bookability === 'unavailable_exact_date') ? `${claim.origin}→${claim.destination} ${claim.date} exact-date 源返回 0 条(${scoped[0].query_id})——不得用历史班期/相邻日期填充` : `${claim.origin}→${claim.destination} ${claim.date} exact-date 源在架 ${scoped.filter((f)=>f.bookability === 'bookable_exact_date').map((f)=>f.flight_no).join('/')}——无 ${no}`
189
+ };
190
+ }
191
+ return {
192
+ verdict: 'route_unqueried',
193
+ reason: `${claim.origin}→${claim.destination} ${claim.date} 从未经 exact-date 源核验`
194
+ };
195
+ }
196
+ return {
197
+ verdict: 'route_unqueried',
198
+ reason: `航班 ${no} 无可回溯的 exact-date 事实(缺 route/date 上下文)`
199
+ };
200
+ }
201
+ function hmToMin(hm) {
202
+ const [h, m] = hm.split(':').map(Number);
203
+ return (h ?? 0) * 60 + (m ?? 0);
204
+ }
205
+ function minToHm(min) {
206
+ return `${String(Math.floor(min / 60)).padStart(2, '0')}:${String(min % 60).padStart(2, '0')}`;
207
+ }
208
+ export function checkSameDayDeparture(facts, q, c) {
209
+ const earliest = hmToMin(c.errand_end_local) + c.airport_transit_min + c.checkin_deadline_min;
210
+ const scoped = latestFactsForRouteDate(facts, q.origin, q.destination, q.date);
211
+ const nonstopBookable = scoped.filter((f)=>f.bookability === 'bookable_exact_date' && f.nonstop !== false);
212
+ const feasible = [];
213
+ const excluded = [];
214
+ for (const f of nonstopBookable){
215
+ const dep = f.dep_local ? hmToMin(f.dep_local) : null;
216
+ if (dep === null) {
217
+ excluded.push({
218
+ flight_no: f.flight_no,
219
+ reason: '缺起飞时刻,不参与衔接判定'
220
+ });
221
+ } else if (dep >= earliest) {
222
+ feasible.push(f);
223
+ } else {
224
+ excluded.push({
225
+ flight_no: f.flight_no,
226
+ reason: `${f.dep_local} 起飞早于最早可接时刻 ${minToHm(earliest)}(${c.errand_end_local} 结束事务 + ${c.airport_transit_min}m 到机场 + ${c.checkin_deadline_min}m 值机)`
227
+ });
228
+ }
229
+ }
230
+ const ok = feasible.length > 0;
231
+ const suggestions = [];
232
+ if (!ok) {
233
+ const inStock = nonstopBookable.map((f)=>`${f.flight_no} ${f.dep_local ?? '?'}`).join('、');
234
+ suggestions.push(`增加 1 个${q.origin}住宿夜:改乘次日早班${inStock ? `(当日在架:${inStock})` : ''}`);
235
+ suggestions.push('或提前一天完成地面事务/提前一天出发,把同日衔接改为隔夜衔接');
236
+ }
237
+ return {
238
+ feasible: ok,
239
+ earliest_departure_local: minToHm(earliest),
240
+ feasible_flights: feasible,
241
+ excluded,
242
+ suggestions
243
+ };
244
+ }
245
+ function daysBetween(a, b) {
246
+ return Math.round((Date.parse(`${b}T00:00:00Z`) - Date.parse(`${a}T00:00:00Z`)) / 86_400_000);
247
+ }
248
+ export function itineraryInvariants(it) {
249
+ const violations = [];
250
+ const totalNights = daysBetween(it.trip_start, it.trip_end);
251
+ let hotelNights = 0;
252
+ for (const s of it.stays){
253
+ const n = daysBetween(s.check_in, s.check_out);
254
+ if (n <= 0) violations.push({
255
+ kind: 'date_order',
256
+ detail: `${s.place} 住宿 ${s.check_in}→${s.check_out} 晚数 ≤0`
257
+ });
258
+ else hotelNights += n;
259
+ }
260
+ if (hotelNights + it.onboard_nights !== totalNights) {
261
+ violations.push({
262
+ kind: 'nights_inconsistent',
263
+ detail: `酒店夜 ${hotelNights} + 机上夜 ${it.onboard_nights} = ${hotelNights + it.onboard_nights} ≠ 行程总夜数 ${totalNights}(${it.trip_start}→${it.trip_end})——夜数口径必须由结构化住宿反算,不许自然语言估算`
264
+ });
265
+ }
266
+ for (const seg of it.od_segments){
267
+ if (seg.mode === 'flight' && seg.legs < 1) {
268
+ violations.push({
269
+ kind: 'legs_inconsistent',
270
+ detail: `${seg.from}→${seg.to}(${seg.date})flight O&D 段 legs=${seg.legs}——每段至少 1 leg`
271
+ });
272
+ }
273
+ if (seg.date < it.trip_start || seg.date > it.trip_end) {
274
+ violations.push({
275
+ kind: 'date_order',
276
+ detail: `${seg.from}→${seg.to} 日期 ${seg.date} 落在行程窗 ${it.trip_start}→${it.trip_end} 之外`
277
+ });
278
+ }
279
+ }
280
+ const minSum = it.budget_items.reduce((acc, b)=>acc + b.min_cny, 0);
281
+ if (it.claimed_floor_cny !== undefined && it.claimed_floor_cny < minSum) {
282
+ violations.push({
283
+ kind: 'budget_floor_inconsistent',
284
+ detail: `预算分项最低合计 ¥${minSum.toLocaleString()} > 产物声称可压到 ¥${it.claimed_floor_cny.toLocaleString()}——下限话术不得低于分项最低合计`
285
+ });
286
+ }
287
+ return violations;
288
+ }
289
+ export function detectBacktrack(stops, nonstopEdges) {
290
+ const findings = [];
291
+ for(let i = 0; i + 2 < stops.length; i++){
292
+ const a = stops[i], b = stops[i + 1], c = stops[i + 2];
293
+ if (a !== c) continue;
294
+ const d = stops[i + 3];
295
+ const suggestion = d && nonstopEdges.has(`${b}-${d}`) ? `${b}→${d} 有直飞:改为 ${a}→${b} 后直接 ${b}→${d},消除 ${a}→${b}→${a} 回头路` : undefined;
296
+ findings.push({
297
+ kind: 'backtrack',
298
+ path: [
299
+ a,
300
+ b,
301
+ c
302
+ ],
303
+ suggestion
304
+ });
305
+ }
306
+ return findings;
307
+ }
308
+ export function defaultReviewBy(tripDate) {
309
+ const d = new Date(Date.parse(`${tripDate}T00:00:00Z`) - 30 * 86_400_000);
310
+ return d.toISOString().slice(0, 10);
311
+ }
312
+ export function renderFlightFact(f) {
313
+ const route = `${f.route.origin}→${f.route.destination}`;
314
+ const evidence = `[${f.source}@${f.fetched_at} #${f.query_id}]`;
315
+ if (f.bookability === 'unavailable_exact_date') {
316
+ const review = f.review_by ?? defaultReviewBy(f.date);
317
+ return `- ${route} ${f.date}:**未确认/当前不可售**——exact-date 源 ${f.source} 于 ${f.fetched_at.slice(0, 10)} 返回 0 条;到 ${review} 复核,不得用历史班期/相邻日期填充 ${evidence}`;
318
+ }
319
+ if (f.bookability !== 'bookable_exact_date') {
320
+ return `- ${route} ${f.date} ${f.flight_no || ''}:**未核验**(${f.bookability})——仅作参考,不得作为可下单方案呈现 ${evidence}`;
321
+ }
322
+ const carrier = f.operating_carrier && f.operating_carrier !== f.marketing_carrier ? `营销 ${f.marketing_carrier ?? '?'} / 实际承运 ${f.operating_carrier}` : f.marketing_carrier ?? '';
323
+ const times = f.dep_local && f.arr_local ? ` ${f.dep_local}→${f.arr_local}` : '';
324
+ const nonstop = f.nonstop === false ? '(中转)' : ' 直飞';
325
+ const airportNote = f.route.dest_airport && f.route.dest_airport !== f.route.destination ? `(落 ${f.route.dest_airport})` : '';
326
+ const price = f.price !== undefined ? ` ¥${f.price}` : ' 价待询';
327
+ const baggage = f.baggage_included === false ? ',不含托运行李' : '';
328
+ return `- ${route} ${f.date} ${f.flight_no}${carrier ? `(${carrier})` : ''}${times}${nonstop}${airportNote}${price}${baggage} ${evidence}`;
329
+ }
330
+ export function renderConnection(legA, legB) {
331
+ const through = legA.protected_connection === true && legB.protected_connection === true;
332
+ const head = `${legA.route.origin}→${legA.route.destination} ${legA.flight_no} + ${legB.route.origin}→${legB.route.destination} ${legB.flight_no}`;
333
+ return through ? `- ${head}:联程(同一票号/行李直挂/误机保护)` : `- ${head}:⚠️ **分票/自助转机**——两段接得上 ≠ 联程;无同一票号、行李需自取重挂、误机无保护,缓冲与风险自担`;
334
+ }
335
+ export function renderPolicyFact(p, tripStart) {
336
+ const review = p.review_by ?? (tripStart ? defaultReviewBy(tripStart) : undefined);
337
+ const reviewNote = review ? `;远期政策须复核——到 ${review} 再核验一次` : '';
338
+ return `- ${p.subject}:截至 ${p.as_of} 的现行政策——${p.statement}${reviewNote} [${p.source}@${p.fetched_at} #${p.query_id}]`;
339
+ }
340
+ export function renderNightsLine(it) {
341
+ const totalNights = daysBetween(it.trip_start, it.trip_end);
342
+ const hotelNights = it.stays.reduce((acc, s)=>acc + Math.max(0, daysBetween(s.check_in, s.check_out)), 0);
343
+ const flightOd = it.od_segments.filter((s)=>s.mode === 'flight').length;
344
+ const flightLegs = it.od_segments.filter((s)=>s.mode === 'flight').reduce((acc, s)=>acc + s.legs, 0);
345
+ return `共 ${daysBetween(it.trip_start, it.trip_end) + 1} 天 ${totalNights} 晚 = ${hotelNights} 个酒店夜 + ${it.onboard_nights} 个机上夜;航班按 O&D ${flightOd} 段(共 ${flightLegs} 个 flight legs)`;
346
+ }
347
+
348
+
349
+ //# sourceURL=ts/src/bookable-facts.ts
@@ -150,4 +150,4 @@ export function sagaLegalPaths() {
150
150
  }
151
151
 
152
152
 
153
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/booking-saga.ts
153
+ //# sourceURL=ts/src/booking-saga.ts