@danceiny/gotry 0.0.1-rc.10
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/LICENSE +21 -0
- package/README.md +267 -0
- package/bin/gotry-inner.js +214 -0
- package/bin/gotry-stdio-ask.js +59 -0
- package/bin/gotry.js +28 -0
- package/cordis.gotry-patch.yml +86 -0
- package/data/flights_2026.json +218 -0
- package/data/golden_erhai.json +92 -0
- package/data/golden_trip_2026.json +75 -0
- package/data/hotels_2026.json +151 -0
- package/data/openflights-skeleton.json +895 -0
- package/data/time-slot-eval.json +384 -0
- package/data/yunnan-pack.json +202 -0
- package/dist/capabilities/agent-reach-bridge.py +90 -0
- package/dist/capabilities/agent-reach-deep.js +172 -0
- package/dist/capabilities/agent-reach.js +204 -0
- package/dist/capabilities/anything.js +139 -0
- package/dist/capabilities/flyai.js +131 -0
- package/dist/capabilities/hbcli.js +143 -0
- package/dist/capabilities/incident-log.js +100 -0
- package/dist/capabilities/opensky.js +74 -0
- package/dist/capabilities/session/action-cache.js +120 -0
- package/dist/capabilities/session/adapters/ctrip-flight.js +83 -0
- package/dist/capabilities/session/adapters/meituan-local.js +73 -0
- package/dist/capabilities/session/extract.js +40 -0
- package/dist/capabilities/session/read-guard.js +99 -0
- package/dist/capabilities/session/transport.js +101 -0
- package/dist/capabilities/session-search.js +93 -0
- package/dist/capabilities/weather.js +180 -0
- package/dist/scripts/action-cache-tests.js +107 -0
- package/dist/scripts/agent-reach-deep-tests.js +54 -0
- package/dist/scripts/agent-reach-tests.js +38 -0
- package/dist/scripts/agent-reach-wrapper-tests.js +79 -0
- package/dist/scripts/anything-tests.js +95 -0
- package/dist/scripts/async-collect.js +37 -0
- package/dist/scripts/companion-tests.js +112 -0
- package/dist/scripts/diff-test.js +39 -0
- package/dist/scripts/engine-run.js +14 -0
- package/dist/scripts/engine-tests.js +45 -0
- package/dist/scripts/hbcli-tests.js +101 -0
- package/dist/scripts/incident-tests.js +96 -0
- package/dist/scripts/journey-tests.js +45 -0
- package/dist/scripts/ledger-tests.js +365 -0
- package/dist/scripts/ledger-workflow-crash.js +42 -0
- package/dist/scripts/memory-capture-tests.js +77 -0
- package/dist/scripts/memory-decay-tests.js +83 -0
- package/dist/scripts/memory-metrics.js +24 -0
- package/dist/scripts/nudge-digest.js +83 -0
- package/dist/scripts/opensky-check.js +20 -0
- package/dist/scripts/opensky-tests.js +52 -0
- package/dist/scripts/probe-poi-tests.js +88 -0
- package/dist/scripts/publish-preverify.js +68 -0
- package/dist/scripts/replay-async.js +44 -0
- package/dist/scripts/replay-real.js +38 -0
- package/dist/scripts/replay.js +116 -0
- package/dist/scripts/session-attach-diagnose.js +27 -0
- package/dist/scripts/session-attach-poc.js +65 -0
- package/dist/scripts/session-extract-tests.js +81 -0
- package/dist/scripts/session-tests.js +162 -0
- package/dist/scripts/skeleton-check.js +37 -0
- package/dist/scripts/skeleton-integration-test.js +22 -0
- package/dist/scripts/skills-contract-tests.js +85 -0
- package/dist/scripts/smoke.js +328 -0
- package/dist/scripts/state-cli-tests.js +136 -0
- package/dist/scripts/state-cli.js +248 -0
- package/dist/scripts/time-eval-tests.js +318 -0
- package/dist/scripts/travel-timeline-tests.js +123 -0
- package/dist/scripts/unified-tests.js +49 -0
- package/dist/scripts/weather-tests.js +48 -0
- package/dist/src/bridge.js +34 -0
- package/dist/src/companions.js +112 -0
- package/dist/src/contracts.js +64 -0
- package/dist/src/dsh-llm.js +174 -0
- package/dist/src/engine.js +331 -0
- package/dist/src/index.js +1239 -0
- package/dist/src/journey.js +147 -0
- package/dist/src/loop.js +360 -0
- package/dist/src/memory-capture.js +40 -0
- package/dist/src/memory-decay.js +31 -0
- package/dist/src/memory-utility.js +55 -0
- package/dist/src/mock-llm.js +103 -0
- package/dist/src/model.js +134 -0
- package/dist/src/slot-spec.js +165 -0
- package/dist/src/state-ledger.js +901 -0
- package/dist/src/time-anchor.js +100 -0
- package/dist/src/tool-packet.js +12 -0
- package/dist/src/travel-slots.js +144 -0
- package/dist/src/travel-timeline.js +78 -0
- package/dist/src/unified.js +538 -0
- package/dist/src/wish-pool.js +27 -0
- package/package.json +84 -0
- package/ts/capabilities/agent-reach-deep.ts +114 -0
- package/ts/capabilities/agent-reach.ts +158 -0
- package/ts/capabilities/anything.ts +201 -0
- package/ts/capabilities/flyai.ts +151 -0
- package/ts/capabilities/hbcli.ts +152 -0
- package/ts/capabilities/incident-log.ts +148 -0
- package/ts/capabilities/opensky.ts +121 -0
- package/ts/capabilities/session/action-cache.ts +148 -0
- package/ts/capabilities/session/adapters/ctrip-flight.ts +107 -0
- package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
- package/ts/capabilities/session/extract.ts +54 -0
- package/ts/capabilities/session/read-guard.ts +141 -0
- package/ts/capabilities/session/transport.ts +114 -0
- package/ts/capabilities/session-search.ts +129 -0
- package/ts/capabilities/weather.ts +176 -0
- package/ts/package.json +27 -0
- package/ts/scripts/async-collect.ts +50 -0
- package/ts/scripts/skeleton-check.ts +49 -0
- package/ts/scripts/skeleton-integration-test.ts +23 -0
- package/ts/scripts/state-cli.ts +201 -0
- package/ts/src/bridge.ts +44 -0
- package/ts/src/companions.ts +113 -0
- package/ts/src/contracts.ts +165 -0
- package/ts/src/dsh-llm.ts +155 -0
- package/ts/src/index.ts +968 -0
- package/ts/src/loop.ts +411 -0
- package/ts/src/memory-capture.ts +59 -0
- package/ts/src/memory-decay.ts +55 -0
- package/ts/src/memory-utility.ts +70 -0
- package/ts/src/mock-llm.ts +100 -0
- package/ts/src/model.ts +220 -0
- package/ts/src/slot-spec.ts +172 -0
- package/ts/src/state-ledger.ts +849 -0
- package/ts/src/time-anchor.ts +134 -0
- package/ts/src/tool-packet.ts +32 -0
- package/ts/src/travel-slots.ts +219 -0
- package/ts/src/travel-timeline.ts +83 -0
- package/ts/src/unified.ts +582 -0
- package/ts/src/wish-pool.ts +66 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { parseFlightPackToSpec } from './unified.js';
|
|
2
|
+
const SCRIPT = [
|
|
3
|
+
{
|
|
4
|
+
when: '请给我做机票和酒店的行程规划和推荐',
|
|
5
|
+
calendar: {
|
|
6
|
+
year: 2026,
|
|
7
|
+
assertedWeekdays: {
|
|
8
|
+
'2026-07-17': 'fri',
|
|
9
|
+
'2026-07-18': 'sat',
|
|
10
|
+
'2026-07-31': 'fri',
|
|
11
|
+
'2026-08-01': 'sat',
|
|
12
|
+
'2026-08-09': 'sun',
|
|
13
|
+
'2026-08-10': 'mon'
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
profile: {
|
|
17
|
+
companions: [
|
|
18
|
+
'girlfriend(先在普吉)'
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
when: '工作时间',
|
|
24
|
+
profile: {
|
|
25
|
+
workWindow: {
|
|
26
|
+
homeTzOffsetMin: 240,
|
|
27
|
+
startMin: 600,
|
|
28
|
+
endMin: 1140,
|
|
29
|
+
workdays: [
|
|
30
|
+
0,
|
|
31
|
+
1,
|
|
32
|
+
2,
|
|
33
|
+
3,
|
|
34
|
+
4
|
|
35
|
+
],
|
|
36
|
+
evidence: '用户原话:我的工作时间是UTC+4的早上10点到下午7点'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
when: '订了酒店',
|
|
42
|
+
profile: {
|
|
43
|
+
bookedResources: [
|
|
44
|
+
{
|
|
45
|
+
kind: 'hotel',
|
|
46
|
+
ref: 'The Title East Wing Rawai(7.18-23, 5 晚)',
|
|
47
|
+
window: '2026-07-18~07-23'
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
export function createMockLlm(flightPackPath, slotScript = []) {
|
|
54
|
+
let stepIdx = 0;
|
|
55
|
+
return {
|
|
56
|
+
async extractFacts (history, _state) {
|
|
57
|
+
const last = history[history.length - 1]?.text ?? '';
|
|
58
|
+
const step = SCRIPT[stepIdx];
|
|
59
|
+
if (step && last.includes(step.when)) {
|
|
60
|
+
stepIdx++;
|
|
61
|
+
return {
|
|
62
|
+
calendar: step.calendar,
|
|
63
|
+
profile: step.profile,
|
|
64
|
+
assumptions: Object.keys({
|
|
65
|
+
...step.profile
|
|
66
|
+
}).map((f)=>({
|
|
67
|
+
field: f,
|
|
68
|
+
source: 'user-verbatim'
|
|
69
|
+
}))
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
assumptions: []
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
async extractSpec (history, state) {
|
|
77
|
+
if (!state.profile.workWindow || !state.profile.bookedResources) return null;
|
|
78
|
+
if (!flightPackPath) return null;
|
|
79
|
+
const { readFile } = await import('node:fs/promises');
|
|
80
|
+
const pack = JSON.parse(await readFile(flightPackPath, 'utf-8'));
|
|
81
|
+
const spec = parseFlightPackToSpec(pack);
|
|
82
|
+
spec.workWindow = {
|
|
83
|
+
homeTzOffsetMin: state.profile.workWindow.homeTzOffsetMin,
|
|
84
|
+
startMin: state.profile.workWindow.startMin,
|
|
85
|
+
endMin: state.profile.workWindow.endMin,
|
|
86
|
+
workdays: state.profile.workWindow.workdays
|
|
87
|
+
};
|
|
88
|
+
spec.budgetCny = 9000;
|
|
89
|
+
return spec;
|
|
90
|
+
},
|
|
91
|
+
async extractSlots (history) {
|
|
92
|
+
const last = history[history.length - 1]?.text ?? '';
|
|
93
|
+
const hit = slotScript.find((s)=>last.includes(s.when));
|
|
94
|
+
return hit ? structuredClone(hit.extraction) : null;
|
|
95
|
+
},
|
|
96
|
+
async polishQuestion (q) {
|
|
97
|
+
return `【${q.key}】${q.text}\n(为什么问:${q.why})`;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/src/mock-llm.ts
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export function hhmmToMin(s) {
|
|
2
|
+
const [h, m] = s.split(':');
|
|
3
|
+
return Number(h) * 60 + Number(m);
|
|
4
|
+
}
|
|
5
|
+
export function minToHhmm(m) {
|
|
6
|
+
return `${String(Math.floor(m / 60)).padStart(2, '0')}:${String(m % 60).padStart(2, '0')}`;
|
|
7
|
+
}
|
|
8
|
+
export function requiredUsableHours(mot) {
|
|
9
|
+
return mot.baseUsableHours + mot.escapeHoursPerWeight * (mot.weights['escape_rest'] ?? 0);
|
|
10
|
+
}
|
|
11
|
+
export const WAKE_PENALTY_BEFORE_5 = 30;
|
|
12
|
+
export const WAKE_PENALTY_BEFORE_6 = 25;
|
|
13
|
+
export const WAKE_PENALTY_BEFORE_630 = 15;
|
|
14
|
+
export const TRANSFER_PENALTY = 8;
|
|
15
|
+
export const LATE_ARRIVAL_PENALTY = 10;
|
|
16
|
+
export const LONG_D2D_PENALTY = 10;
|
|
17
|
+
export const DAY_END_MIN = 21 * 60;
|
|
18
|
+
export const DAY2_START_MIN = 9 * 60;
|
|
19
|
+
export const DAY2_QUALITY = 0.9;
|
|
20
|
+
export const LATEST_ARRIVE_STAY_MIN = 18 * 60;
|
|
21
|
+
export function parseMotivation(d) {
|
|
22
|
+
const hard = d['hard'] ?? {};
|
|
23
|
+
const rawWeights = d['weights'] ?? d;
|
|
24
|
+
const weights = {};
|
|
25
|
+
for (const [k, v] of Object.entries(rawWeights)){
|
|
26
|
+
if (typeof v === 'number') weights[k] = v;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
weights,
|
|
30
|
+
wakeFloorMin: hhmmToMin(String(hard['wake_not_before'] ?? '06:30')),
|
|
31
|
+
minArrivalEnergyPct: Number(hard['min_arrival_energy_pct'] ?? 40),
|
|
32
|
+
baseUsableHours: 4.0,
|
|
33
|
+
escapeHoursPerWeight: 2.0
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function parseRequest(d) {
|
|
37
|
+
const home = (d['home'] ?? {})['hubs'] ?? {};
|
|
38
|
+
const homeHubAccess = {};
|
|
39
|
+
for (const [hub, acc] of Object.entries(home)){
|
|
40
|
+
homeHubAccess[hub] = {
|
|
41
|
+
hub,
|
|
42
|
+
toHubMin: Number(acc['to_hub_min'])
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
note: String(d['note'] ?? ''),
|
|
47
|
+
motivation: parseMotivation(d['motivation']),
|
|
48
|
+
windowDays: Number(d['window_days']),
|
|
49
|
+
budgetCny: Number(d['budget_cny']),
|
|
50
|
+
homeHubAccess
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function parseService(d) {
|
|
54
|
+
return {
|
|
55
|
+
id: String(d['id']),
|
|
56
|
+
depMin: hhmmToMin(String(d['dep'])),
|
|
57
|
+
arrMin: hhmmToMin(String(d['arr'])),
|
|
58
|
+
priceCny: Number(d['price_cny'])
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function parseTransfer(d) {
|
|
62
|
+
return {
|
|
63
|
+
mode: String(d['mode']),
|
|
64
|
+
minutes: Number(d['min']),
|
|
65
|
+
priceCny: Number(d['price_cny'])
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function parseCandidate(d) {
|
|
69
|
+
return {
|
|
70
|
+
id: String(d['id']),
|
|
71
|
+
name: String(d['name']),
|
|
72
|
+
hub: String(d['hub']),
|
|
73
|
+
bufferOutMin: Number(d['buffer_out_min'] ?? 60),
|
|
74
|
+
bufferRetMin: Number(d['buffer_ret_min'] ?? 60),
|
|
75
|
+
servicesOut: d['services_out'].map(parseService),
|
|
76
|
+
servicesRet: d['services_ret'].map(parseService),
|
|
77
|
+
destTransfers: d['dest_transfers'].map(parseTransfer),
|
|
78
|
+
stayCnyPerNight: Number(d['stay_cny_per_night']),
|
|
79
|
+
localDailyCny: Number(d['local_daily_cny']),
|
|
80
|
+
minDaysForPurpose: Number(d['min_days_for_purpose']),
|
|
81
|
+
imageryMatch: Number(d['imagery_match']),
|
|
82
|
+
bestMonths: (d['best_months'] ?? []).map(Number)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function evaluateChoice(cand, req, ch) {
|
|
86
|
+
const access = req.homeHubAccess[cand.hub];
|
|
87
|
+
const wake = ch.outService.depMin - cand.bufferOutMin - access.toHubMin;
|
|
88
|
+
const arriveStay = ch.outService.arrMin + ch.outTransfer.minutes;
|
|
89
|
+
const d2dOut = arriveStay - wake;
|
|
90
|
+
let energy = 100;
|
|
91
|
+
if (wake < 5 * 60) energy -= WAKE_PENALTY_BEFORE_5;
|
|
92
|
+
else if (wake < 6 * 60) energy -= WAKE_PENALTY_BEFORE_6;
|
|
93
|
+
else if (wake < hhmmToMin('06:30')) energy -= WAKE_PENALTY_BEFORE_630;
|
|
94
|
+
energy -= 2 * TRANSFER_PENALTY;
|
|
95
|
+
if (arriveStay > 21 * 60) energy -= LATE_ARRIVAL_PENALTY;
|
|
96
|
+
if (d2dOut > 6 * 60) energy -= LONG_D2D_PENALTY;
|
|
97
|
+
energy = Math.max(0, energy);
|
|
98
|
+
const day1Raw = Math.max(0, DAY_END_MIN - arriveStay) / 60;
|
|
99
|
+
const day1 = day1Raw * (0.5 + energy / 200);
|
|
100
|
+
const leaveStayRet = ch.retService.depMin - cand.bufferRetMin - ch.retTransfer.minutes;
|
|
101
|
+
const day2 = Math.max(0, leaveStayRet - DAY2_START_MIN) / 60 * DAY2_QUALITY;
|
|
102
|
+
const midDays = Math.max(0, ch.days - 2);
|
|
103
|
+
const usable = day1 + day2 + midDays * 8.0 * DAY2_QUALITY;
|
|
104
|
+
const money = ch.outService.priceCny + ch.retService.priceCny + ch.outTransfer.priceCny + ch.retTransfer.priceCny + cand.stayCnyPerNight * (ch.days - 1) + cand.localDailyCny * ch.days;
|
|
105
|
+
return {
|
|
106
|
+
moneyCny: money,
|
|
107
|
+
wakeMin: wake,
|
|
108
|
+
arriveStayMin: arriveStay,
|
|
109
|
+
doorToDoorOutMin: d2dOut,
|
|
110
|
+
energyArrivalPct: energy,
|
|
111
|
+
usableHours: usable,
|
|
112
|
+
usableDay1Hours: day1,
|
|
113
|
+
usableDay2Hours: day2,
|
|
114
|
+
departHomeRetMin: leaveStayRet,
|
|
115
|
+
arriveHomeRetMin: ch.retService.arrMin + access.toHubMin
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export function trueCostToDict(t) {
|
|
119
|
+
return {
|
|
120
|
+
money_cny: t.moneyCny,
|
|
121
|
+
wake: minToHhmm(t.wakeMin),
|
|
122
|
+
arrive_stay: minToHhmm(t.arriveStayMin),
|
|
123
|
+
door_to_door_out: `${Math.floor(t.doorToDoorOutMin / 60)}h${String(t.doorToDoorOutMin % 60).padStart(2, '0')}m`,
|
|
124
|
+
energy_arrival_pct: t.energyArrivalPct,
|
|
125
|
+
usable_hours: Math.round(t.usableHours * 10) / 10,
|
|
126
|
+
usable_day1_hours: Math.round(t.usableDay1Hours * 10) / 10,
|
|
127
|
+
usable_day2_hours: Math.round(t.usableDay2Hours * 10) / 10,
|
|
128
|
+
leave_stay_return: minToHhmm(t.departHomeRetMin),
|
|
129
|
+
arrive_home_return: minToHhmm(Math.min(1440, t.arriveHomeRetMin))
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/src/model.ts
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { parseAbsoluteDate, ymd } from './time-anchor.js';
|
|
2
|
+
const WEEKDAY_ZH = {
|
|
3
|
+
日: 0,
|
|
4
|
+
一: 1,
|
|
5
|
+
二: 2,
|
|
6
|
+
三: 3,
|
|
7
|
+
四: 4,
|
|
8
|
+
五: 5,
|
|
9
|
+
六: 6
|
|
10
|
+
};
|
|
11
|
+
function addDaysYmd(today, n) {
|
|
12
|
+
const [y, m, d] = today.split('-').map(Number);
|
|
13
|
+
return ymd(new Date(y, m - 1, d + n));
|
|
14
|
+
}
|
|
15
|
+
function splitPlusSuffix(expr) {
|
|
16
|
+
const m = expr.match(/^(.*?)\+(\d+)天?$/);
|
|
17
|
+
if (m && m[1]) return {
|
|
18
|
+
base: m[1],
|
|
19
|
+
plusDays: Number(m[2])
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
base: expr,
|
|
23
|
+
plusDays: 0
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function neighborDayOffset(expr) {
|
|
27
|
+
if (expr === '今天') return 0;
|
|
28
|
+
if (expr === '明天') return 1;
|
|
29
|
+
if (expr === '后天') return 2;
|
|
30
|
+
if (expr === '大后天') return 3;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function weekdayOffsetDays(expr, today) {
|
|
34
|
+
const m = expr.match(/^(本|下|下下)周([日一二三四五六])$/);
|
|
35
|
+
if (!m || !m[2]) return null;
|
|
36
|
+
const target = WEEKDAY_ZH[m[2]];
|
|
37
|
+
if (target === undefined) return null;
|
|
38
|
+
const [y, mm, d] = today.split('-').map(Number);
|
|
39
|
+
const todayMonOffset = (new Date(y, mm - 1, d).getDay() + 6) % 7;
|
|
40
|
+
const targetMonOffset = (target + 6) % 7;
|
|
41
|
+
const weekShift = m[1] === '本' ? 0 : m[1] === '下' ? 7 : 14;
|
|
42
|
+
return weekShift + targetMonOffset - todayMonOffset;
|
|
43
|
+
}
|
|
44
|
+
function monthFragmentDate(expr, today) {
|
|
45
|
+
if (![
|
|
46
|
+
'下个月初',
|
|
47
|
+
'下个月中旬',
|
|
48
|
+
'下个月下旬'
|
|
49
|
+
].includes(expr)) return null;
|
|
50
|
+
const [y, m] = today.split('-').map(Number);
|
|
51
|
+
const day = expr === '下个月初' ? 1 : expr === '下个月中旬' ? 11 : 21;
|
|
52
|
+
return ymd(new Date(y, m, day));
|
|
53
|
+
}
|
|
54
|
+
export function resolveSlotDate(expr, anchor) {
|
|
55
|
+
const raw = expr.trim();
|
|
56
|
+
const year = Number(anchor.today.slice(0, 4));
|
|
57
|
+
const absolute = parseAbsoluteDate(raw, year);
|
|
58
|
+
if (absolute) return {
|
|
59
|
+
raw,
|
|
60
|
+
date: absolute,
|
|
61
|
+
kind: 'absolute'
|
|
62
|
+
};
|
|
63
|
+
const { base, plusDays } = splitPlusSuffix(raw);
|
|
64
|
+
const baseAbs = parseAbsoluteDate(base, year);
|
|
65
|
+
if (baseAbs) return {
|
|
66
|
+
raw,
|
|
67
|
+
date: addDaysYmd(baseAbs, plusDays),
|
|
68
|
+
kind: 'absolute'
|
|
69
|
+
};
|
|
70
|
+
const month = monthFragmentDate(base, anchor.today);
|
|
71
|
+
if (month) return {
|
|
72
|
+
raw,
|
|
73
|
+
date: addDaysYmd(month, plusDays),
|
|
74
|
+
kind: 'card'
|
|
75
|
+
};
|
|
76
|
+
const neighbor = neighborDayOffset(base);
|
|
77
|
+
if (neighbor !== null) return {
|
|
78
|
+
raw,
|
|
79
|
+
date: addDaysYmd(anchor.today, neighbor + plusDays),
|
|
80
|
+
kind: 'card'
|
|
81
|
+
};
|
|
82
|
+
const offset = weekdayOffsetDays(base, anchor.today);
|
|
83
|
+
if (offset !== null) return {
|
|
84
|
+
raw,
|
|
85
|
+
date: addDaysYmd(anchor.today, offset + plusDays),
|
|
86
|
+
kind: 'card'
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
raw,
|
|
90
|
+
date: null,
|
|
91
|
+
kind: 'unresolved'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const DATE_FIELDS = {
|
|
95
|
+
requisition: [
|
|
96
|
+
'start_date',
|
|
97
|
+
'end_date'
|
|
98
|
+
],
|
|
99
|
+
flight: [
|
|
100
|
+
'departure_date',
|
|
101
|
+
'return_date'
|
|
102
|
+
],
|
|
103
|
+
hotel: [
|
|
104
|
+
'check_in_date',
|
|
105
|
+
'check_out_date'
|
|
106
|
+
]
|
|
107
|
+
};
|
|
108
|
+
export function resolveSlots(ext, anchor) {
|
|
109
|
+
const out = {
|
|
110
|
+
unresolved: []
|
|
111
|
+
};
|
|
112
|
+
for (const domain of ext.domains){
|
|
113
|
+
const slots = ext.slots[domain];
|
|
114
|
+
if (!slots) continue;
|
|
115
|
+
for (const field of DATE_FIELDS[domain] ?? []){
|
|
116
|
+
const value = slots[field];
|
|
117
|
+
if (typeof value !== 'string') continue;
|
|
118
|
+
const res = resolveSlotDate(value, anchor);
|
|
119
|
+
const bucket = out[domain] ??= {};
|
|
120
|
+
bucket[field] = res;
|
|
121
|
+
if (res.kind === 'unresolved') out.unresolved.push({
|
|
122
|
+
field: `${domain}.${field}`,
|
|
123
|
+
raw: res.raw
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
export function specDateMismatches(spec, resolved) {
|
|
130
|
+
const mismatches = [];
|
|
131
|
+
const primary = [
|
|
132
|
+
{
|
|
133
|
+
field: 'requisition.start_date',
|
|
134
|
+
res: resolved.requisition?.start_date
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
field: 'flight.departure_date',
|
|
138
|
+
res: resolved.flight?.departure_date
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
field: 'hotel.check_in_date',
|
|
142
|
+
res: resolved.hotel?.check_in_date
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
const dated = primary.filter((p)=>p.res?.date != null);
|
|
146
|
+
if (dated.length === 0) return mismatches;
|
|
147
|
+
const datedSegs = (spec.segments ?? []).filter((s)=>typeof s.date === 'string' && s.date);
|
|
148
|
+
if (datedSegs.length !== 1) return mismatches;
|
|
149
|
+
const seg = datedSegs[0];
|
|
150
|
+
const slotDates = new Set(dated.map((s)=>s.res.date));
|
|
151
|
+
if (!slotDates.has(seg.date)) {
|
|
152
|
+
mismatches.push({
|
|
153
|
+
specAt: `segments[${(spec.segments ?? []).indexOf(seg)}].date`,
|
|
154
|
+
field: dated.map((s)=>s.field).join('|'),
|
|
155
|
+
specDate: seg.date,
|
|
156
|
+
slotDate: [
|
|
157
|
+
...slotDates
|
|
158
|
+
].join('|')
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return mismatches;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/src/slot-spec.ts
|