@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -194
- package/README.zh-CN.md +115 -210
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-bootstrap.js +466 -35
- package/bin/gotry-inner.js +255 -36
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +2 -2
- package/client/client.js +174 -0
- package/cordis.gotry-patch.yml +70 -93
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +63 -148
- package/dist/capabilities/anything.js +89 -75
- package/dist/capabilities/artifacts.js +123 -104
- package/dist/capabilities/channel-health.js +65 -45
- package/dist/capabilities/channel-registry.js +108 -87
- package/dist/capabilities/doctor.js +357 -235
- package/dist/capabilities/effect.js +223 -309
- package/dist/capabilities/fact-log.js +28 -14
- package/dist/capabilities/flyai.js +245 -156
- package/dist/capabilities/ground-transfer.js +509 -0
- package/dist/capabilities/hbcli.js +144 -148
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/model-override.js +30 -10
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/resilience.js +50 -32
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
- package/dist/capabilities/session/adapters/dida-portal.js +206 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +169 -112
- package/dist/capabilities/session/benchmark.js +85 -149
- package/dist/capabilities/session/extension-bridge.js +320 -259
- package/dist/capabilities/session/extension-channel.js +57 -76
- package/dist/capabilities/session/extension-distribution.js +115 -119
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +55 -33
- package/dist/capabilities/session/health-watch.js +146 -154
- package/dist/capabilities/session/read-guard.js +55 -47
- package/dist/capabilities/session/static-flight-golden.js +49 -36
- package/dist/capabilities/session/transport.js +54 -61
- package/dist/capabilities/session/wizard.js +63 -93
- package/dist/capabilities/session-consent.js +143 -47
- package/dist/capabilities/session-login.js +95 -126
- package/dist/capabilities/session-search.js +391 -253
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +87 -73
- package/dist/capabilities/weather.js +163 -235
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
- package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
- package/dist/scripts/anything-tests.js +58 -62
- package/dist/scripts/artifact-client-contract-tests.js +69 -0
- package/dist/scripts/artifacts-capability-tests.js +235 -0
- package/dist/scripts/async-collect.js +26 -15
- package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
- package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
- package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
- package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
- package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
- package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
- package/dist/scripts/booking-executor-tests.js +80 -0
- package/dist/scripts/booking-recovery-chain-tests.js +182 -0
- package/dist/scripts/booking-saga-tests.js +61 -123
- package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
- package/dist/scripts/bootstrap-tests.js +1383 -128
- package/dist/scripts/build-changelog.js +89 -130
- package/dist/scripts/build-metrics-report.js +135 -143
- package/dist/scripts/changelog-tests.js +35 -82
- package/dist/scripts/channel-probe-tests.js +44 -116
- package/dist/scripts/channel-probe.js +84 -167
- package/dist/scripts/channel-registry-tests.js +97 -185
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +428 -120
- package/dist/scripts/dsh-artifact-e2e.js +150 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
- package/dist/scripts/dsh-runtime-closure.js +75 -63
- package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +165 -552
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +69 -242
- package/dist/scripts/evaluation-contract-tests.js +193 -499
- package/dist/scripts/extension-distribution-cli.js +19 -15
- package/dist/scripts/extension-distribution-tests.js +98 -211
- package/dist/scripts/extension-tests.js +366 -389
- package/dist/scripts/fact-gate-tests.js +725 -263
- package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
- package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
- package/dist/scripts/flight-malformed-tests.js +159 -0
- package/dist/scripts/flyai-tests.js +325 -68
- package/dist/scripts/g5-guard-tests.js +131 -0
- package/dist/scripts/g5-guard.js +189 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +71 -90
- package/dist/scripts/hbcli-live-optin-tests.js +96 -0
- package/dist/scripts/hbcli-release-contract-tests.js +122 -0
- package/dist/scripts/hbcli-tests.js +41 -115
- package/dist/scripts/health-watch-cli.js +42 -28
- package/dist/scripts/hotel-date-gate-tests.js +496 -0
- package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
- package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
- package/dist/scripts/i18n-tests.js +16 -9
- package/dist/scripts/incident-tests.js +281 -87
- package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
- package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
- package/dist/scripts/issue-271-liveness-tests.js +547 -0
- package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
- package/dist/scripts/issue-289-model-retry-tests.js +441 -0
- package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
- package/dist/scripts/issue-338-home-city-e2e.js +577 -0
- package/dist/scripts/issue343-real-entry-e2e.js +160 -0
- package/dist/scripts/journey-tests.js +22 -27
- package/dist/scripts/kernel-manifest-evidence.js +109 -0
- package/dist/scripts/kernel-manifest-gate.js +126 -0
- package/dist/scripts/kernel-manifest-tests.js +212 -0
- package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
- package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
- package/dist/scripts/kernel-manifest.js +369 -0
- package/dist/scripts/ledger-repair-apply-tests.js +316 -0
- package/dist/scripts/ledger-repair-plan-tests.js +375 -0
- package/dist/scripts/ledger-tests.js +325 -340
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +18 -52
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +24 -12
- package/dist/scripts/memory-value-report.js +474 -222
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +62 -199
- package/dist/scripts/model-override-e2e.js +66 -105
- package/dist/scripts/nightly-evidence-tests.js +64 -89
- package/dist/scripts/nightly-evidence.js +108 -111
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/onboarding-tests.js +63 -88
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/persona-surface-guard-tests.js +37 -12
- package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +126 -227
- package/dist/scripts/price-drift-watch.js +209 -253
- package/dist/scripts/probe-poi-tests.js +52 -45
- package/dist/scripts/product-metrics.js +187 -273
- package/dist/scripts/publish-preverify.js +47 -41
- package/dist/scripts/realtime-pricing-tests.js +37 -76
- package/dist/scripts/replay-async.js +20 -18
- package/dist/scripts/replay-real.js +16 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +16 -12
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +66 -147
- package/dist/scripts/session-dida-live-e2e.js +151 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-login.js +14 -13
- package/dist/scripts/session-tests.js +545 -583
- package/dist/scripts/sf-live-benchmark.js +92 -110
- package/dist/scripts/sf-live-cli-tests.js +2 -9
- package/dist/scripts/sf-soft-score-tests.js +19 -60
- package/dist/scripts/sf-summary-tests.js +149 -0
- package/dist/scripts/sf-summary.js +466 -84
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +11 -10
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +12 -20
- package/dist/scripts/smoke.js +541 -422
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +549 -212
- package/dist/scripts/static-golden-tests.js +46 -180
- package/dist/scripts/time-eval-tests.js +210 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +46 -93
- package/dist/scripts/turn-handoff-collect.js +71 -99
- package/dist/scripts/turn-policy-tests.js +28 -29
- package/dist/scripts/typed-contract-canary.js +77 -162
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +23 -19
- package/dist/scripts/visa-policy-tests.js +31 -44
- package/dist/scripts/weather-tests.js +121 -543
- package/dist/scripts/wish-channel-gate-tests.js +29 -86
- package/dist/scripts/write-gate-tests.js +522 -0
- package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
- package/dist/scripts/z3-lifecycle-tests.js +170 -0
- package/dist/scripts/z3-race-repeat-tests.js +39 -0
- package/dist/scripts/z3-race-tests.js +18 -12
- package/dist/src/artifact-gate.js +652 -221
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +55 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +194 -0
- package/dist/src/benchmark-agent-conformance.js +296 -262
- package/dist/src/benchmark-environment-bridge.js +298 -413
- package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
- package/dist/src/benchmark-tool-isolation.js +94 -67
- package/dist/src/bookable-facts.js +299 -174
- package/dist/src/booking-saga.js +64 -120
- package/dist/src/booking-surface/availability-policy.js +443 -576
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/contracts.js +24 -80
- package/dist/src/booking-surface/dsh-planner.js +510 -281
- package/dist/src/booking-surface/dsh-plugin.js +17 -1
- package/dist/src/booking-surface/error-codes.js +82 -10
- package/dist/src/booking-surface/index.js +0 -3
- package/dist/src/booking-surface/profile.js +10 -54
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +765 -891
- package/dist/src/booking-surface/server.js +200 -251
- package/dist/src/booking-surface/startup.js +58 -54
- package/dist/src/booking-surface/validation.js +150 -218
- package/dist/src/bridge.js +20 -13
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +82 -120
- package/dist/src/engine.js +253 -239
- package/dist/src/evaluation-cadence.js +72 -209
- package/dist/src/evaluation-contracts.js +220 -843
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/gotry-backend.js +70 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +18 -7
- package/dist/src/index.js +1425 -1629
- package/dist/src/journey.js +132 -81
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +257 -166
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +30 -14
- package/dist/src/memory-lifecycle.js +1310 -0
- package/dist/src/memory-utility.js +27 -40
- package/dist/src/mock-llm.js +36 -61
- package/dist/src/model.js +81 -36
- package/dist/src/realtime-pricing.js +63 -72
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +310 -471
- package/dist/src/subagent-job-id-guard.js +58 -0
- package/dist/src/time-anchor.js +231 -33
- package/dist/src/tool-packet.js +20 -8
- package/dist/src/travel-slots.js +58 -35
- package/dist/src/travel-timeline.js +44 -63
- package/dist/src/turn-deadline.js +121 -127
- package/dist/src/turn-policy.js +61 -89
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +504 -381
- package/dist/src/wish-pool.js +39 -14
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +273 -7
- package/extension/README.md +4 -3
- package/extension/background.js +125 -14
- package/extension/content-bridge.js +23 -1
- package/extension/content-main.js +15 -4
- package/extension/manifest.json +16 -8
- package/package.json +251 -220
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +1 -1
- package/ts/capabilities/artifacts.ts +46 -9
- package/ts/capabilities/flyai.ts +156 -37
- package/ts/capabilities/hbcli.ts +1 -1
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
- package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
- package/ts/capabilities/session/extension-bridge.ts +226 -101
- package/ts/capabilities/session/extension-channel.ts +22 -9
- package/ts/capabilities/session/wizard.ts +2 -2
- package/ts/capabilities/session-consent.ts +98 -14
- package/ts/capabilities/session-login.ts +1 -0
- package/ts/capabilities/session-search.ts +315 -31
- package/ts/dsh-runtime/vendor/README.md +8 -4
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
- package/ts/package.json +236 -16
- package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
- package/ts/scripts/state-cli.ts +418 -51
- package/ts/src/artifact-gate.ts +561 -15
- package/ts/src/benchmark-environment-bridge.ts +37 -20
- package/ts/src/bookable-facts.ts +183 -7
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/dsh-planner.ts +240 -32
- package/ts/src/booking-surface/dsh-plugin.js +17 -1
- package/ts/src/booking-surface/error-codes.ts +75 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/server.ts +84 -51
- package/ts/src/booking-surface/validation.ts +2 -0
- package/ts/src/contracts.ts +12 -2
- package/ts/src/dsh-llm.ts +4 -8
- package/ts/src/index.ts +609 -90
- package/ts/src/loop.ts +116 -7
- package/ts/src/memory-capture.ts +227 -3
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/state-ledger.ts +47 -29
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +217 -13
- package/ts/src/unified.ts +316 -54
|
@@ -1,14 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 时间感评测(时间锚点层 + travel_slot_extraction.v1 槽位抽取):
|
|
3
|
+
*
|
|
4
|
+
* 确定性部分(进 CI,run-all-tests.sh 挂载):
|
|
5
|
+
* 1. 锚点卡:固定 now=2026-08-27(周四)断言关键换算(下周一/大后天/下下周四/下个月中旬/季度/节日)
|
|
6
|
+
* 2. 过期校验 flagExpiredSlots:绝对月日早于锚点今天判过期;相对表达永不判;幂等
|
|
7
|
+
* 3. 评分器 scoreExtraction 自测(通过/语言/域/槽位值/missing_slots/多键 warning)
|
|
8
|
+
* 4. mock 回放管道:25 题 golden 经 mock 回吐,评分管道应 25/25(管道自测,与模型质量解耦)
|
|
9
|
+
* 7. 春节锚点生成面(issue #274):到期即红守卫(最晚锚点年份 ≥ 当前年+3)+ 双 oracle
|
|
10
|
+
* 交叉验证(旧表 2026-2031 六条 + 港天文台 2032-2040 九条)+ 耗尽显式告警
|
|
11
|
+
*
|
|
12
|
+
* 真模型巡检(只读报告,不进 CI 红线;ADR-11 质量层定位):
|
|
13
|
+
* cd ts && npx tsx scripts/time-eval-tests.ts --real
|
|
14
|
+
* 需 LLM_API_KEY(或 DEEPSEEK_API_KEY;仓根 .env 亦可)。锚点固定 anchor_date=2026-08-27
|
|
15
|
+
* (golden 的过期判定依赖它,见数据文件 note)。只读:不碰 gotry-state(巡检状态纪律)。
|
|
16
|
+
*/
|
|
1
17
|
import assert from 'node:assert/strict';
|
|
2
18
|
import { readFileSync } from 'node:fs';
|
|
3
19
|
import { join } from 'node:path';
|
|
4
|
-
import { buildTimeAnchor, parseAbsoluteDate } from '../src/time-anchor.js';
|
|
20
|
+
import { SPRING_FESTIVAL, SPRING_FESTIVAL_HORIZON_MIN_YEARS, buildTimeAnchor, parseAbsoluteDate, springFestivalHorizonOk, springFestivalHorizonYears, } from '../src/time-anchor.js';
|
|
5
21
|
import { detectLanguage, flagExpiredSlots, scoreExtraction } from '../src/travel-slots.js';
|
|
6
22
|
import { resolveSlotDate, resolveSlots, specDateMismatches } from '../src/slot-spec.js';
|
|
7
23
|
import { createMockLlm } from '../src/mock-llm.js';
|
|
8
24
|
import { createOpenAICompatLlm } from '../src/dsh-llm.js';
|
|
25
|
+
import { newState, runTurn } from '../src/loop.js';
|
|
26
|
+
import { parseRequest } from '../src/model.js';
|
|
27
|
+
import { solveChoiceSegment } from '../src/unified.js';
|
|
28
|
+
import { parsePlanningWindow } from '../src/time-anchor.js';
|
|
9
29
|
const dataset = JSON.parse(readFileSync(join('..', 'data', 'time-slot-eval.json'), 'utf-8'));
|
|
10
30
|
const [ay, am, ad] = dataset.anchor_date.split('-').map(Number);
|
|
11
|
-
const ANCHOR_NOW = new Date(ay, am - 1, ad, 12);
|
|
31
|
+
const ANCHOR_NOW = new Date(ay, am - 1, ad, 12); // 锚点日中午,避免午夜边界
|
|
32
|
+
// ---- 1. 锚点卡(固定 now 断言) ------------------------------------------------
|
|
12
33
|
{
|
|
13
34
|
const a = buildTimeAnchor(ANCHOR_NOW);
|
|
14
35
|
assert.equal(a.today, '2026-08-27', 'today');
|
|
@@ -26,124 +47,58 @@ const ANCHOR_NOW = new Date(ay, am - 1, ad, 12);
|
|
|
26
47
|
assert.ok(c.includes('元旦 2027-01-01'), '元旦取下一次发生');
|
|
27
48
|
assert.ok(c.includes('春节 2027-02-06'), '春节锚点(2026 春节已过,取 2027)');
|
|
28
49
|
console.log('1. 锚点卡断言 OK(固定 2026-08-27:下周一/大后天/下下周四/月分段/季度/节日)');
|
|
50
|
+
// D-9 扩表回归:2030 年中锚点 → 春节 2031-01-23(表外年份静默缺失的回归闸)
|
|
29
51
|
const a2030 = buildTimeAnchor(new Date(2030, 5, 1, 12));
|
|
30
52
|
assert.ok(a2030.card.includes('春节 2031-01-23'), 'D-9 扩表:2030 年视视角春节取 2031-01-23');
|
|
31
53
|
console.log('1b. D-9 扩表断言 OK(2030 锚点 → 春节 2031-01-23,表覆盖至 2031)');
|
|
32
|
-
}
|
|
54
|
+
}
|
|
55
|
+
// ---- 2. 过期校验(确定性,对齐 golden 约定) ------------------------------------
|
|
56
|
+
{
|
|
33
57
|
const anchor = buildTimeAnchor(ANCHOR_NOW);
|
|
34
58
|
const base = {
|
|
35
|
-
schema_version: 'travel_slot_extraction.v1',
|
|
36
|
-
|
|
37
|
-
domains: [
|
|
38
|
-
'requisition'
|
|
39
|
-
],
|
|
40
|
-
slots: {
|
|
41
|
-
requisition: {
|
|
42
|
-
start_date: '8.1',
|
|
43
|
-
end_date: '8.10'
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
missing_slots: []
|
|
59
|
+
schema_version: 'travel_slot_extraction.v1', language: 'zh',
|
|
60
|
+
domains: ['requisition'], slots: { requisition: { start_date: '8.1', end_date: '8.10' } }, missing_slots: [],
|
|
47
61
|
};
|
|
48
62
|
const flagged = flagExpiredSlots(base, anchor);
|
|
49
|
-
assert.deepEqual(flagged.missing_slots, [
|
|
50
|
-
'requisition.start_date (date is expired)'
|
|
51
|
-
], '8.1 在 08-27 锚点下过期');
|
|
63
|
+
assert.deepEqual(flagged.missing_slots, ['requisition.start_date (date is expired)'], '8.1 在 08-27 锚点下过期');
|
|
52
64
|
assert.equal(flagged.slots.requisition.start_date, '8.1', '槽位原值逐字保留');
|
|
53
65
|
assert.equal(flagged.missing_slots.length, 1, 'end_date 不重复判(只判主日期字段,对齐 golden)');
|
|
54
66
|
const again = flagExpiredSlots(flagged, anchor);
|
|
55
67
|
assert.equal(again.missing_slots.length, 1, '幂等不重复加');
|
|
56
|
-
const future = flagExpiredSlots({
|
|
57
|
-
...base,
|
|
58
|
-
slots: {
|
|
59
|
-
requisition: {
|
|
60
|
-
start_date: '9.1'
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}, anchor);
|
|
68
|
+
const future = flagExpiredSlots({ ...base, slots: { requisition: { start_date: '9.1' } } }, anchor);
|
|
64
69
|
assert.equal(future.missing_slots.length, 0, '9.1 不过期');
|
|
65
|
-
const relative = flagExpiredSlots({
|
|
66
|
-
...base,
|
|
67
|
-
slots: {
|
|
68
|
-
requisition: {
|
|
69
|
-
start_date: '本周三'
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}, anchor);
|
|
70
|
+
const relative = flagExpiredSlots({ ...base, slots: { requisition: { start_date: '本周三' } } }, anchor);
|
|
73
71
|
assert.equal(relative.missing_slots.length, 0, '相对表达(本周三)永不判过期');
|
|
74
|
-
const iso = flagExpiredSlots({
|
|
75
|
-
...base,
|
|
76
|
-
slots: {
|
|
77
|
-
requisition: {
|
|
78
|
-
start_date: '2026-08-01'
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}, anchor);
|
|
72
|
+
const iso = flagExpiredSlots({ ...base, slots: { requisition: { start_date: '2026-08-01' } } }, anchor);
|
|
82
73
|
assert.equal(iso.missing_slots.length, 1, 'ISO 绝对日期判过期');
|
|
83
|
-
const zhDate = flagExpiredSlots({
|
|
84
|
-
...base,
|
|
85
|
-
slots: {
|
|
86
|
-
requisition: {
|
|
87
|
-
start_date: '8月5日'
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}, anchor);
|
|
74
|
+
const zhDate = flagExpiredSlots({ ...base, slots: { requisition: { start_date: '8月5日' } } }, anchor);
|
|
91
75
|
assert.equal(zhDate.missing_slots.length, 1, '中文月日判过期');
|
|
92
|
-
const enDate = flagExpiredSlots({
|
|
93
|
-
...base,
|
|
94
|
-
slots: {
|
|
95
|
-
requisition: {
|
|
96
|
-
start_date: 'Aug-5'
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}, anchor);
|
|
76
|
+
const enDate = flagExpiredSlots({ ...base, slots: { requisition: { start_date: 'Aug-5' } } }, anchor);
|
|
100
77
|
assert.equal(enDate.missing_slots.length, 0, '英文月名不判过期(逐字传递,对齐 golden time_english_date_range)');
|
|
101
|
-
const flight = flagExpiredSlots({
|
|
102
|
-
|
|
103
|
-
domains: [
|
|
104
|
-
'flight'
|
|
105
|
-
],
|
|
106
|
-
slots: {
|
|
107
|
-
flight: {
|
|
108
|
-
departure_date: '8.1'
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}, anchor);
|
|
112
|
-
assert.deepEqual(flight.missing_slots, [
|
|
113
|
-
'flight.departure_date (date is expired)'
|
|
114
|
-
], 'flight 主日期字段');
|
|
78
|
+
const flight = flagExpiredSlots({ ...base, domains: ['flight'], slots: { flight: { departure_date: '8.1' } } }, anchor);
|
|
79
|
+
assert.deepEqual(flight.missing_slots, ['flight.departure_date (date is expired)'], 'flight 主日期字段');
|
|
115
80
|
assert.equal(parseAbsoluteDate('下周一', 2026), null, 'parseAbsoluteDate 不碰相对表达');
|
|
116
|
-
|
|
81
|
+
// language 判定归代码层(detectLanguage):全中文 zh / 全英文 en / 混排 mixed
|
|
82
|
+
for (const c of dataset.cases) {
|
|
117
83
|
assert.equal(detectLanguage(c.utterance), c.expected.language, `detectLanguage ${c.id}`);
|
|
118
84
|
}
|
|
119
85
|
console.log('2. 过期校验断言 OK(8.1 过期/9.1 不/本周三不/ISO+中文月日判/英文不判/幂等)+ detectLanguage 全题对齐');
|
|
120
|
-
}
|
|
86
|
+
}
|
|
87
|
+
// ---- 3. 评分器自测 ------------------------------------------------------------
|
|
88
|
+
{
|
|
121
89
|
const exp = dataset.cases[0].expected;
|
|
122
90
|
const identical = structuredClone(exp);
|
|
123
91
|
assert.equal(scoreExtraction(exp, identical).pass, true, '完全一致应 pass');
|
|
124
|
-
const wrongLang = {
|
|
125
|
-
...structuredClone(exp),
|
|
126
|
-
language: 'en'
|
|
127
|
-
};
|
|
92
|
+
const wrongLang = { ...structuredClone(exp), language: 'en' };
|
|
128
93
|
assert.equal(scoreExtraction(exp, wrongLang).pass, false, 'language 不一致应 fail');
|
|
129
|
-
const wrongDomain = {
|
|
130
|
-
...structuredClone(exp),
|
|
131
|
-
domains: [
|
|
132
|
-
'flight'
|
|
133
|
-
]
|
|
134
|
-
};
|
|
94
|
+
const wrongDomain = { ...structuredClone(exp), domains: ['flight'] };
|
|
135
95
|
assert.equal(scoreExtraction(exp, wrongDomain).pass, false, 'domains 不一致应 fail');
|
|
136
96
|
const wrongSlot = structuredClone(exp);
|
|
137
97
|
wrongSlot.slots.requisition.destination = '北京';
|
|
138
98
|
const r = scoreExtraction(exp, wrongSlot);
|
|
139
99
|
assert.equal(r.pass, false, '槽位值不一致应 fail');
|
|
140
|
-
assert.ok(r.diffs.some(
|
|
141
|
-
const wrongMissing = {
|
|
142
|
-
...structuredClone(exp),
|
|
143
|
-
missing_slots: [
|
|
144
|
-
'requisition.start_date (date is expired)'
|
|
145
|
-
]
|
|
146
|
-
};
|
|
100
|
+
assert.ok(r.diffs.some(d => d.includes('slots.requisition.destination')), 'diff 带路径');
|
|
101
|
+
const wrongMissing = { ...structuredClone(exp), missing_slots: ['requisition.start_date (date is expired)'] };
|
|
147
102
|
assert.equal(scoreExtraction(exp, wrongMissing).pass, false, 'missing_slots 不一致应 fail');
|
|
148
103
|
const extraKey = structuredClone(exp);
|
|
149
104
|
extraKey.slots.requisition['adults'] = 2;
|
|
@@ -151,149 +106,212 @@ const ANCHOR_NOW = new Date(ay, am - 1, ad, 12);
|
|
|
151
106
|
assert.equal(rk.pass, true, '多出键不 fail(非致命)');
|
|
152
107
|
assert.equal(rk.warnings.length, 1, '多出键记 warning');
|
|
153
108
|
console.log('3. 评分器自测 OK(pass/language/domains/槽位值/missing_slots/extra-key warning)');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}));
|
|
109
|
+
}
|
|
110
|
+
// ---- 4. mock 回放管道(25/25 管道自测) ------------------------------------------
|
|
111
|
+
{
|
|
112
|
+
const script = dataset.cases.map(c => ({ when: c.utterance, extraction: c.expected }));
|
|
159
113
|
const llm = createMockLlm(undefined, script);
|
|
160
114
|
let pass = 0;
|
|
161
|
-
for (const c of dataset.cases){
|
|
162
|
-
const ext = await llm.extractSlots([
|
|
163
|
-
{
|
|
164
|
-
role: 'user',
|
|
165
|
-
text: c.utterance
|
|
166
|
-
}
|
|
167
|
-
]);
|
|
115
|
+
for (const c of dataset.cases) {
|
|
116
|
+
const ext = await llm.extractSlots([{ role: 'user', text: c.utterance }]);
|
|
168
117
|
assert.ok(ext, `mock 应命中 ${c.id}`);
|
|
169
118
|
const r = scoreExtraction(c.expected, ext);
|
|
170
119
|
assert.ok(r.pass, `mock 回放 ${c.id} 应 pass:${r.diffs.join(';')}`);
|
|
171
120
|
pass++;
|
|
172
121
|
}
|
|
173
122
|
console.log(`4. mock 回放管道 OK(${pass}/${dataset.cases.length}——评分管道自身正确)`);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
123
|
+
}
|
|
124
|
+
// ---- 5. 槽位→日期解析层(D-10 切片 A,slot-spec.ts) ------------------------------
|
|
125
|
+
{
|
|
126
|
+
const anchor = buildTimeAnchor(ANCHOR_NOW); // 2026-08-27 周四
|
|
127
|
+
const r = (expr) => resolveSlotDate(expr, anchor);
|
|
128
|
+
// 绝对表达:ISO/点分/中文月日(kind=absolute)
|
|
177
129
|
assert.equal(r('2026-09-05').date, '2026-09-05', 'ISO');
|
|
178
130
|
assert.equal(r('8.20').date, '2026-08-20', '点分');
|
|
179
131
|
assert.equal(r('9月5日').date, '2026-09-05', '中文月日');
|
|
132
|
+
// 锚点卡词表:相对周/近邻日/月分段(kind=card)
|
|
180
133
|
assert.equal(r('下周一').date, '2026-08-31', '下周一');
|
|
181
134
|
assert.equal(r('本周六').date, '2026-08-29', '本周六');
|
|
182
135
|
assert.equal(r('下下周四').date, '2026-09-10', '下下周四');
|
|
183
136
|
assert.equal(r('明天').date, '2026-08-28', '明天');
|
|
184
137
|
assert.equal(r('下个月中旬').date, '2026-09-11', '下个月中旬取首日');
|
|
138
|
+
// 「+N」后缀(槽位规则 5/6 拼接约定)
|
|
185
139
|
assert.equal(r('下周五+3').date, '2026-09-07', '下周五+3');
|
|
186
140
|
assert.equal(r('8.20+2天').date, '2026-08-22', '8.20+2天');
|
|
141
|
+
// 词表外:unresolved 逐字保留(不做开放式解析,ADR-12 边界)
|
|
187
142
|
assert.equal(r('近期').kind, 'unresolved', '模糊词');
|
|
188
143
|
assert.equal(r('这阵子').date, null, '模糊词不猜');
|
|
189
144
|
assert.equal(r('next Monday').kind, 'unresolved', '英文相对不解析');
|
|
190
145
|
assert.equal(r('next Monday').raw, 'next Monday', 'unresolved 保留原话');
|
|
146
|
+
// 整张抽取解析:hotel+flight 双域 + unresolved 清单
|
|
191
147
|
const ext = {
|
|
192
148
|
schema_version: 'travel_slot_extraction.v1',
|
|
193
149
|
language: 'zh',
|
|
194
|
-
domains: [
|
|
195
|
-
'hotel',
|
|
196
|
-
'flight'
|
|
197
|
-
],
|
|
150
|
+
domains: ['hotel', 'flight'],
|
|
198
151
|
slots: {
|
|
199
|
-
hotel: {
|
|
200
|
-
|
|
201
|
-
city: '上海',
|
|
202
|
-
check_in_date: '下周五',
|
|
203
|
-
check_out_date: '下周五+3'
|
|
204
|
-
},
|
|
205
|
-
flight: {
|
|
206
|
-
action: 'search',
|
|
207
|
-
origin: '上海',
|
|
208
|
-
destination: '杭州',
|
|
209
|
-
departure_date: '这阵子',
|
|
210
|
-
trip_type: 'one_way'
|
|
211
|
-
}
|
|
152
|
+
hotel: { action: 'search', city: '上海', check_in_date: '下周五', check_out_date: '下周五+3' },
|
|
153
|
+
flight: { action: 'search', origin: '上海', destination: '杭州', departure_date: '这阵子', trip_type: 'one_way' },
|
|
212
154
|
},
|
|
213
|
-
missing_slots: []
|
|
155
|
+
missing_slots: [],
|
|
214
156
|
};
|
|
215
157
|
const resolved = resolveSlots(ext, anchor);
|
|
216
158
|
assert.equal(resolved.hotel?.check_in_date?.date, '2026-09-04', 'hotel 入住=下周五');
|
|
217
159
|
assert.equal(resolved.hotel?.check_out_date?.date, '2026-09-07', 'hotel 退房=下周五+3');
|
|
218
|
-
assert.deepEqual(resolved.unresolved, [
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
], 'unresolved 清单');
|
|
224
|
-
assert.equal(specDateMismatches({
|
|
225
|
-
segments: [
|
|
226
|
-
{
|
|
227
|
-
date: '2026-09-04'
|
|
228
|
-
}
|
|
229
|
-
]
|
|
230
|
-
}, resolved).length, 0, '一致则零 mismatch');
|
|
231
|
-
const mm = specDateMismatches({
|
|
232
|
-
segments: [
|
|
233
|
-
{
|
|
234
|
-
date: '2026-09-05'
|
|
235
|
-
}
|
|
236
|
-
]
|
|
237
|
-
}, resolved);
|
|
160
|
+
assert.deepEqual(resolved.unresolved, [{ field: 'flight.departure_date', raw: '这阵子' }], 'unresolved 清单');
|
|
161
|
+
// spec 一致性闸:单日期段分歧必须暴露;多段/无日期/unresolved 不判(不造假阳性)
|
|
162
|
+
assert.equal(specDateMismatches({ segments: [{ date: '2026-09-04' }] }, resolved).length, 0, '一致则零 mismatch');
|
|
163
|
+
const mm = specDateMismatches({ segments: [{ date: '2026-09-05' }] }, resolved);
|
|
238
164
|
assert.equal(mm.length, 1, '单日期段分歧暴露');
|
|
239
165
|
assert.equal(mm[0]?.specDate, '2026-09-05');
|
|
240
166
|
assert.equal(mm[0]?.slotDate, '2026-09-04');
|
|
241
|
-
assert.equal(specDateMismatches({
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
date: '2026-09-05'
|
|
248
|
-
}
|
|
249
|
-
]
|
|
250
|
-
}, resolved).length, 0, '多段无逐段真值不判(巡检修正)');
|
|
251
|
-
const unresolvedOnly = {
|
|
252
|
-
unresolved: [
|
|
253
|
-
{
|
|
254
|
-
field: 'flight.departure_date',
|
|
255
|
-
raw: '近期'
|
|
256
|
-
}
|
|
257
|
-
]
|
|
258
|
-
};
|
|
259
|
-
assert.equal(specDateMismatches({
|
|
260
|
-
segments: [
|
|
261
|
-
{
|
|
262
|
-
date: '2026-09-05'
|
|
263
|
-
}
|
|
264
|
-
]
|
|
265
|
-
}, unresolvedOnly).length, 0, '全 unresolved 不参与比对');
|
|
266
|
-
assert.equal(specDateMismatches({
|
|
267
|
-
segments: []
|
|
268
|
-
}, resolved).length, 0, 'spec 无日期不比对');
|
|
167
|
+
assert.equal(specDateMismatches({ segments: [{ date: '2026-09-04' }, { date: '2026-09-05' }] }, resolved).length, 0, '多段无逐段真值不判(巡检修正)');
|
|
168
|
+
const unresolvedOnly = { unresolved: [{ field: 'flight.departure_date', raw: '近期' }] };
|
|
169
|
+
assert.equal(specDateMismatches({ segments: [{ date: '2026-09-05' }] }, unresolvedOnly).length, 0, '全 unresolved 不参与比对');
|
|
170
|
+
assert.equal(specDateMismatches({ segments: [] }, resolved).length, 0, 'spec 无日期不比对');
|
|
269
171
|
console.log('5. 槽位→日期解析 OK(绝对/词表/+N/unresolved 边界/整张解析/spec 一致性闸)');
|
|
270
|
-
}
|
|
172
|
+
}
|
|
173
|
+
// ---- 6. Issue #2 未来年度窗口(真实 runTurn→validateSpec→solve 路径) -----------
|
|
174
|
+
{
|
|
175
|
+
const request = parseRequest({
|
|
176
|
+
note: 'issue-2-fixture',
|
|
177
|
+
home: { hubs: { H: { to_hub_min: 0 } } },
|
|
178
|
+
motivation: { weights: {}, hard: { wake_not_before: '06:00', min_arrival_energy_pct: 0 } },
|
|
179
|
+
window_days: 2,
|
|
180
|
+
budget_cny: 1000,
|
|
181
|
+
});
|
|
182
|
+
const option = (id, date, score) => ({
|
|
183
|
+
id, label: id, date, score, minDays: 1,
|
|
184
|
+
move: {
|
|
185
|
+
hub: 'H',
|
|
186
|
+
services: [{ id: `out-${id}`, depMin: 600, arrMin: 700, priceCny: 10 }],
|
|
187
|
+
retServices: [{ id: `ret-${id}`, depMin: 1000, arrMin: 1100, priceCny: 10 }],
|
|
188
|
+
transfers: [{ mode: 'walk', minutes: 10, priceCny: 0 }],
|
|
189
|
+
bufferMin: 30, bufferRetMin: 30, originTransferMin: 0, destTransferMin: 0,
|
|
190
|
+
},
|
|
191
|
+
stay: { nights: 1, stayCnyPerNight: 10, localDailyCny: 10 },
|
|
192
|
+
});
|
|
193
|
+
const spec = () => ({
|
|
194
|
+
segments: [{ id: 'dest', role: 'choice', options: [
|
|
195
|
+
option('past', '2026-09-01', 1), option('valid', '2026-10-01', 0.5), option('next-year', '2027-01-01', 0.9),
|
|
196
|
+
] }],
|
|
197
|
+
});
|
|
198
|
+
const llmFor = (planned) => ({
|
|
199
|
+
extractFacts: async () => ({
|
|
200
|
+
assumptions: [],
|
|
201
|
+
profile: { workWindow: { homeTzOffsetMin: 0, startMin: 600, endMin: 1080, workdays: [0, 1, 2, 3, 4], evidence: 'fixture' }, bookedResources: [] },
|
|
202
|
+
}),
|
|
203
|
+
extractSpec: async () => structuredClone(planned),
|
|
204
|
+
extractSlots: async () => null,
|
|
205
|
+
polishQuestion: async (q) => q.text,
|
|
206
|
+
});
|
|
207
|
+
const run = (message, now) => runTurn(newState(2026), message, llmFor(spec()), [], async (candidate) => solveChoiceSegment(candidate, request), now);
|
|
208
|
+
const anchor = buildTimeAnchor(new Date(2026, 8, 10, 12));
|
|
209
|
+
const parsed = parsePlanningWindow('计划在2026年内完成一次旅行', anchor);
|
|
210
|
+
assert.deepEqual(parsed, { referenceDate: '2026-09-10', requestedYear: 2026, intent: 'future' }, 'named-year future context');
|
|
211
|
+
assert.equal(parsePlanningWindow('不要再推荐过去的时段,计划2026年内完成旅行', anchor)?.intent, 'future', 'negated past recommendation is not historical intent');
|
|
212
|
+
assert.equal(parsePlanningWindow('2025年没去成,计划2026年内完成旅行', anchor)?.requestedYear, 2026, 'historical context does not override requested future year');
|
|
213
|
+
assert.equal(parsePlanningWindow('计划在2025年或2026年内完成旅行', anchor), null, 'conflicting future years remain ambiguous');
|
|
214
|
+
assert.equal(parsePlanningWindow('历史回测2025年,计划2026年内完成旅行', anchor), null, 'historical and future years remain ambiguous');
|
|
215
|
+
// 相对年份(今年/明年/后年/年内)按本轮锚点换算为命名年窗口——未来时态不落过去(issue #2)
|
|
216
|
+
assert.deepEqual(parsePlanningWindow('今年年内完成一次旅行', anchor), { referenceDate: '2026-09-10', requestedYear: 2026, intent: 'future' }, 'relative this-year future context resolves against the anchor');
|
|
217
|
+
assert.equal(parsePlanningWindow('计划明年出行一次', anchor)?.requestedYear, 2027, 'relative next-year future context');
|
|
218
|
+
assert.equal(parsePlanningWindow('计划在明年或后年完成旅行', anchor), null, 'conflicting relative years remain ambiguous');
|
|
219
|
+
assert.equal(parsePlanningWindow('今年的计划很多', anchor), null, 'non-planning mention of this year is not a window');
|
|
220
|
+
assert.equal(parsePlanningWindow('历史回测今年内的方案', anchor)?.intent, 'historical', 'relative-year historical lookup stays historical');
|
|
221
|
+
assert.equal(parsePlanningWindow('2025年没去成,今年年内完成旅行', anchor)?.requestedYear, 2026, 'past failure does not override relative future year');
|
|
222
|
+
const future = await run('计划在2026年内完成一次旅行', new Date(2026, 8, 10, 12));
|
|
223
|
+
assert.deepEqual(future.state.spec?.segments[0]?.options.map(o => o.date), ['2026-10-01'], 'past option removed before solve');
|
|
224
|
+
assert.equal(future.state.solve?.recommended, 'valid', 'solver accepts only same-year future option');
|
|
225
|
+
assert.match(future.reply, /已排除规划窗口外的候选/, 'reply records deterministic rejection');
|
|
226
|
+
const expired = await run('计划在2025年内完成一次旅行', new Date(2026, 8, 10, 12));
|
|
227
|
+
assert.equal(expired.state.solve, undefined, 'expired requested year does not solve');
|
|
228
|
+
assert.match(expired.reply, /2025 年已结束/, 'expired requested year is explicit');
|
|
229
|
+
assert.doesNotMatch(expired.reply, /2027/, 'expired year is not silently rolled over');
|
|
230
|
+
const historical = await run('历史回测2026年内的方案', new Date(2026, 8, 10, 12));
|
|
231
|
+
assert.deepEqual(historical.state.spec?.segments[0]?.options.map(o => o.date), ['2026-09-01', '2026-10-01', '2027-01-01'], 'historical lookup bypass preserved');
|
|
232
|
+
assert.equal(historical.state.solve?.recommended, 'past', 'historical solver may select historical option');
|
|
233
|
+
// 相对年份未来意图同样不得把过去候选送进求解(issue #2 剩余面,固定时钟反例)
|
|
234
|
+
const relativeFuture = await run('今年年内完成一次旅行', new Date(2026, 8, 10, 12));
|
|
235
|
+
assert.deepEqual(relativeFuture.state.spec?.segments[0]?.options.map(o => o.date), ['2026-10-01'], 'relative this-year intent removes past option before solve');
|
|
236
|
+
assert.equal(relativeFuture.state.solve?.recommended, 'valid', 'relative this-year intent does not recommend a past slot');
|
|
237
|
+
assert.match(relativeFuture.reply, /已排除规划窗口外的候选/, 'relative this-year intent records deterministic rejection');
|
|
238
|
+
const relativeNextYear = await run('计划明年出行一次', new Date(2026, 8, 10, 12));
|
|
239
|
+
assert.deepEqual(relativeNextYear.state.spec?.segments[0]?.options.map(o => o.date), ['2027-01-01'], 'relative next-year intent keeps only in-window options');
|
|
240
|
+
assert.equal(relativeNextYear.state.solve?.recommended, 'next-year', 'relative next-year intent solves within the requested year');
|
|
241
|
+
const relativeHistorical = await run('历史回测今年内的方案', new Date(2026, 8, 10, 12));
|
|
242
|
+
assert.deepEqual(relativeHistorical.state.spec?.segments[0]?.options.map(o => o.date), ['2026-09-01', '2026-10-01', '2027-01-01'], 'explicit relative-year historical lookup keeps past options');
|
|
243
|
+
assert.equal(relativeHistorical.state.solve?.recommended, 'past', 'relative-year historical lookup may select past option');
|
|
244
|
+
const refreshedState = newState(2026);
|
|
245
|
+
await runTurn(refreshedState, '计划在2026年内完成一次旅行', llmFor(spec()), [], async (candidate) => solveChoiceSegment(candidate, request), new Date(2026, 8, 10, 12));
|
|
246
|
+
const refreshed = await runTurn(refreshedState, '计划在2026年内完成一次旅行', llmFor(spec()), [], async (candidate) => solveChoiceSegment(candidate, request), new Date(2026, 9, 2, 12));
|
|
247
|
+
assert.equal(refreshed.state.solve, undefined, 'later eligible turn refreshes reference date');
|
|
248
|
+
assert.match(refreshed.reply, /2026-10-02 至 2026-12-31/, 'later reference date is used');
|
|
249
|
+
assert.equal('planningWindow' in refreshed.state, false, 'planning reference is not persisted in TripState');
|
|
250
|
+
console.log('6. Issue #2 未来年度窗口 OK(runTurn→validateSpec→solve:排除过去候选/拒绝过期年/保留历史回测/刷新参考日)');
|
|
251
|
+
}
|
|
252
|
+
// ---- 7. Issue #274 春节锚点生成面:到期即红守卫 + 双 oracle 交叉验证 + 耗尽显式告警 ----
|
|
253
|
+
{
|
|
254
|
+
// 守卫(真实时钟):最晚锚点年份 ≥ 当前年+3——现在过,临近耗尽自动红(2099 表则 2097 年起红)
|
|
255
|
+
assert.ok(springFestivalHorizonOk(), `春节锚点地平线不足 ${SPRING_FESTIVAL_HORIZON_MIN_YEARS} 年(到期即红守卫)`);
|
|
256
|
+
// 牙齿证明:被否决的旧 2031 手抄表,进入预警窗(2029-01-01 时钟)守卫必红——而非 2031 静默耗尽
|
|
257
|
+
const LEGACY_HAND_2031 = {
|
|
258
|
+
2026: '2026-02-17', 2027: '2027-02-06', 2028: '2028-01-26',
|
|
259
|
+
2029: '2029-02-13', 2030: '2030-02-03', 2031: '2031-01-23',
|
|
260
|
+
};
|
|
261
|
+
assert.ok(springFestivalHorizonYears(LEGACY_HAND_2031, new Date(2029, 0, 1)) < SPRING_FESTIVAL_HORIZON_MIN_YEARS, '旧 2031 手抄表在 2029 时钟下守卫必须红(牙齿证明)');
|
|
262
|
+
// 守卫行程自证(对当前表,表扩展后无需改测试):耗尽前 3 年绿、前 2 年红
|
|
263
|
+
const latestYear = Math.max(...Object.keys(SPRING_FESTIVAL).map(Number));
|
|
264
|
+
assert.equal(springFestivalHorizonOk(new Date(latestYear - 3, 0, 1)), true, '耗尽前 3 年守卫绿');
|
|
265
|
+
assert.equal(springFestivalHorizonOk(new Date(latestYear - 2, 0, 1)), false, '耗尽前 2 年守卫红');
|
|
266
|
+
// oracle A:仓库旧表 2026-2031 逐条比对——库生成不许漂移现状行为
|
|
267
|
+
for (const [year, date] of Object.entries(LEGACY_HAND_2031)) {
|
|
268
|
+
assert.equal(SPRING_FESTIVAL[Number(year)], date, `oracle A 旧表 ${year}`);
|
|
269
|
+
}
|
|
270
|
+
// oracle B:#384 港天文台核实 2032-2040(gts/time/calendar T2032e–T2040e 正月初一行,2026-09-11 取)
|
|
271
|
+
const HKO_2032_2040 = {
|
|
272
|
+
2032: '2032-02-11', 2033: '2033-01-31', 2034: '2034-02-19',
|
|
273
|
+
2035: '2035-02-08', 2036: '2036-01-28', 2037: '2037-02-15',
|
|
274
|
+
2038: '2038-02-04', 2039: '2039-01-24', 2040: '2040-02-12',
|
|
275
|
+
};
|
|
276
|
+
for (const [year, date] of Object.entries(HKO_2032_2040)) {
|
|
277
|
+
assert.equal(SPRING_FESTIVAL[Number(year)], date, `oracle B 港天文台 ${year}`);
|
|
278
|
+
}
|
|
279
|
+
// 生成面贯通:旧表外年份(2038)经 buildTimeAnchor 真实出现在锚点卡
|
|
280
|
+
const a2038 = buildTimeAnchor(new Date(2038, 0, 10, 12));
|
|
281
|
+
assert.ok(a2038.card.includes('春节 2038-02-04'), '2038 视角春节取 2038-02-04(生成表贯通锚点卡)');
|
|
282
|
+
// 耗尽显式告警:表耗尽后锚点卡显式标注,不再静默省略;未耗尽则不告警
|
|
283
|
+
const exhausted = buildTimeAnchor(new Date(latestYear + 1, 5, 1, 12));
|
|
284
|
+
assert.ok(exhausted.card.includes('春节(锚点表已到期'), '耗尽后锚点卡显式告警(非静默省略)');
|
|
285
|
+
assert.ok(!buildTimeAnchor(ANCHOR_NOW).card.includes('锚点表已到期'), '未耗尽不告警');
|
|
286
|
+
console.log('7. 春节锚点生成面 OK(到期守卫/旧 2031 表牙齿/行程自证/双 oracle 15 条/生成贯通/耗尽告警)');
|
|
287
|
+
}
|
|
288
|
+
console.log('\nTIME-EVAL TESTS: 7/7 OK(确定性部分,CI 口径)');
|
|
289
|
+
// ---- 真模型巡检(--real,只读报告,不进 CI 红线) ----------------------------------
|
|
271
290
|
if (process.argv.includes('--real')) {
|
|
272
291
|
try {
|
|
273
|
-
for (const line of readFileSync(join('..', '.env'), 'utf-8').split('\n')){
|
|
292
|
+
for (const line of readFileSync(join('..', '.env'), 'utf-8').split('\n')) {
|
|
274
293
|
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
275
|
-
if (m && !process.env[m[1]])
|
|
294
|
+
if (m && !process.env[m[1]])
|
|
295
|
+
process.env[m[1]] = m[2].trim();
|
|
276
296
|
}
|
|
277
|
-
}
|
|
297
|
+
}
|
|
298
|
+
catch { /* .env 可选 */ }
|
|
278
299
|
const hasKey = Boolean(process.env['LLM_API_KEY'] ?? process.env['DEEPSEEK_API_KEY']);
|
|
279
300
|
if (!hasKey) {
|
|
280
301
|
console.log('\n--real:无 LLM_API_KEY,跳过真模型巡检(ADR-8 回退原则)');
|
|
281
|
-
}
|
|
282
|
-
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
const clock = () => ANCHOR_NOW; // 锚点固定:golden 过期判定依赖 anchor_date
|
|
283
305
|
const llm = createOpenAICompatLlm(undefined, clock);
|
|
284
306
|
console.log(`\n=== 真模型巡检(${process.env['LLM_MODEL'] ?? 'MiniMax-M2'},锚点 ${dataset.anchor_date})===`);
|
|
285
307
|
let pass = 0;
|
|
286
308
|
let p0Pass = 0;
|
|
287
309
|
let p0Total = 0;
|
|
288
|
-
for (const c of dataset.cases){
|
|
289
|
-
if (c.priority === 'P0')
|
|
310
|
+
for (const c of dataset.cases) {
|
|
311
|
+
if (c.priority === 'P0')
|
|
312
|
+
p0Total++;
|
|
290
313
|
try {
|
|
291
|
-
const ext = await llm.extractSlots([
|
|
292
|
-
{
|
|
293
|
-
role: 'user',
|
|
294
|
-
text: c.utterance
|
|
295
|
-
}
|
|
296
|
-
]);
|
|
314
|
+
const ext = await llm.extractSlots([{ role: 'user', text: c.utterance }]);
|
|
297
315
|
if (!ext) {
|
|
298
316
|
console.log(`FAIL ${c.id}(${c.priority}):模型未产出合法 v1 JSON`);
|
|
299
317
|
continue;
|
|
@@ -301,18 +319,18 @@ if (process.argv.includes('--real')) {
|
|
|
301
319
|
const r = scoreExtraction(c.expected, ext);
|
|
302
320
|
if (r.pass) {
|
|
303
321
|
pass++;
|
|
304
|
-
if (c.priority === 'P0')
|
|
322
|
+
if (c.priority === 'P0')
|
|
323
|
+
p0Pass++;
|
|
305
324
|
console.log(`PASS ${c.id}${r.warnings.length ? `(warning: ${r.warnings.join(';')})` : ''}`);
|
|
306
|
-
}
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
307
327
|
console.log(`FAIL ${c.id}(${c.priority}):\n ${r.diffs.join('\n ')}`);
|
|
308
328
|
}
|
|
309
|
-
}
|
|
329
|
+
}
|
|
330
|
+
catch (e) {
|
|
310
331
|
console.log(`ERROR ${c.id}(${c.priority}):${e.message.slice(0, 120)}`);
|
|
311
332
|
}
|
|
312
333
|
}
|
|
313
334
|
console.log(`\nREAL ACCURACY: ${pass}/${dataset.cases.length}(P0: ${p0Pass}/${p0Total})——巡检报告口径,不设红线`);
|
|
314
335
|
}
|
|
315
336
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
//# sourceURL=ts/scripts/time-eval-tests.ts
|