@whenis/booking 0.1.1 → 0.3.0

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/dist/index.cjs CHANGED
@@ -39,12 +39,13 @@ var windowWithinNRule = {
39
39
  priority: 75,
40
40
  pattern: [
41
41
  { kind: "tag", tag: "Grabber", predicate: (t) => t.tags.some((x) => x.kind === "Grabber" && x.modifier === "within") },
42
+ { kind: "tag", tag: "Grabber", predicate: (t) => t.tags.some((x) => x.kind === "Grabber" && x.modifier === "next"), optional: true },
42
43
  { kind: "tag", tag: "Numeral" },
43
44
  { kind: "tag", tag: "TimeUnit" }
44
45
  ],
45
46
  produce: (matched) => {
46
- const n = findTag(matched[1], "Numeral");
47
- const u = findTag(matched[2], "TimeUnit");
47
+ const n = findTag(matched[2], "Numeral");
48
+ const u = findTag(matched[3], "TimeUnit");
48
49
  if (!n || n.kind !== "Numeral" || !u || u.kind !== "TimeUnit") return null;
49
50
  if (u.unit !== "day" && u.unit !== "week") return null;
50
51
  return makeWindow(n.value, u.unit);
@@ -56,12 +57,13 @@ var windowWithinNWithPrefixRule = {
56
57
  pattern: [
57
58
  { kind: "tag", tag: "Connector", predicate: (t) => /^[ув]$/i.test(t.text) },
58
59
  { kind: "tag", tag: "Grabber", predicate: (t) => t.tags.some((x) => x.kind === "Grabber" && x.modifier === "within") },
60
+ { kind: "tag", tag: "Grabber", predicate: (t) => t.tags.some((x) => x.kind === "Grabber" && x.modifier === "next"), optional: true },
59
61
  { kind: "tag", tag: "Numeral" },
60
62
  { kind: "tag", tag: "TimeUnit" }
61
63
  ],
62
64
  produce: (matched) => {
63
- const n = findTag(matched[2], "Numeral");
64
- const u = findTag(matched[3], "TimeUnit");
65
+ const n = findTag(matched[3], "Numeral");
66
+ const u = findTag(matched[4], "TimeUnit");
65
67
  if (!n || n.kind !== "Numeral" || !u || u.kind !== "TimeUnit") return null;
66
68
  if (u.unit !== "day" && u.unit !== "week") return null;
67
69
  return makeWindow(n.value, u.unit);
@@ -119,6 +121,27 @@ var weekendThisRule = {
119
121
  convention: "checkout"
120
122
  })
121
123
  };
124
+ var weekendLastOfMonthRule = {
125
+ name: "booking-weekend-last-of-month",
126
+ priority: 82,
127
+ pattern: [
128
+ { kind: "tag", tag: "Grabber", predicate: (t) => t.tags.some((x) => x.kind === "Grabber" && x.modifier === "last") },
129
+ { kind: "tag", tag: "Literal", predicate: (t) => /вихідн/i.test(t.text) },
130
+ { kind: "tag", tag: "MonthName", optional: true }
131
+ ],
132
+ produce: (matched) => {
133
+ const monthTok = matched[2];
134
+ const mTag = monthTok?.tags.find((t) => t.kind === "MonthName");
135
+ const month = mTag && mTag.kind === "MonthName" ? mTag.month : void 0;
136
+ const start = month !== void 0 ? { type: "last_weekday_in_month", weekday: 6, month } : { type: "last_weekday_in_month", weekday: 6 };
137
+ return {
138
+ type: "range",
139
+ start,
140
+ end: { type: "offset_from", base: start, days: 1 },
141
+ convention: "checkout"
142
+ };
143
+ }
144
+ };
122
145
  var holidayPislyaRule = {
123
146
  name: "booking-holiday-pislya",
124
147
  priority: 90,
@@ -147,12 +170,36 @@ var holidayNaRule = {
147
170
  reason: "holiday_ref"
148
171
  })
149
172
  };
173
+ var bookingDateWithNightsRule = {
174
+ name: "booking-date-with-nights",
175
+ priority: 80,
176
+ pattern: [
177
+ { kind: "tag", tag: "Connector", predicate: (t) => t.tags.some((x) => x.kind === "Connector" && x.conn === "from"), optional: true },
178
+ { kind: "node", node: "absolute" },
179
+ { kind: "node", node: "duration" }
180
+ ],
181
+ produce: (matched) => {
182
+ const a = matched[1];
183
+ const d = matched[2];
184
+ if (a.type !== "absolute" || d.type !== "duration") return null;
185
+ const nights = d.nights;
186
+ if (nights === void 0) return null;
187
+ return {
188
+ type: "range",
189
+ start: a,
190
+ end: { type: "offset_from", base: a, days: nights },
191
+ convention: "checkout"
192
+ };
193
+ }
194
+ };
150
195
  var bookingRules = [
151
196
  windowWithinNRule,
152
197
  windowWithinNWithPrefixRule,
198
+ bookingDateWithNightsRule,
153
199
  stayDurationRule,
154
200
  weekendNextRule,
155
201
  weekendThisRule,
202
+ weekendLastOfMonthRule,
156
203
  holidayPislyaRule,
157
204
  holidayNaRule
158
205
  ];
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/rules.ts","../src/enrichers.ts"],"sourcesContent":["import type { Plugin } from '@whenis/core';\nimport { bookingRules, bookingTags } from './rules';\nimport { mostlyPastEnricher } from './enrichers';\n\nexport const booking: Plugin = {\n name: '@whenis/booking',\n tags: bookingTags,\n rules: bookingRules,\n enrichers: [mostlyPastEnricher],\n};\n","import type { Rule, Token, Tag, IRNode } from '@whenis/core';\n\nconst findTag = (t: Token, kind: string) => t.tags.find(x => x.kind === kind);\n\nfunction makeWindow(n: number, unit: 'day' | 'week'): IRNode {\n const days = unit === 'week' ? n * 7 : n;\n return {\n type: 'window',\n from: { type: 'relative', offset: { days: 0 }, direction: 'this' },\n to: { type: 'relative', offset: { days: days - 1 }, direction: 'future' },\n };\n}\n\n// \"впродовж 7 днів\" — 3-token form\nexport const windowWithinNRule: Rule = {\n name: 'booking-window-within',\n priority: 75,\n pattern: [\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'within') },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[1] as Token, 'Numeral');\n const u = findTag(matched[2] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n if (u.unit !== 'day' && u.unit !== 'week') return null;\n return makeWindow(n.value, u.unit);\n },\n};\n\n// \"у найближчі 5 днів\" — 4-token form with leading «у/в» Connector\nexport const windowWithinNWithPrefixRule: Rule = {\n name: 'booking-window-within-prefixed',\n priority: 76,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^[ув]$/i.test(t.text) },\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'within') },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[2] as Token, 'Numeral');\n const u = findTag(matched[3] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n if (u.unit !== 'day' && u.unit !== 'week') return null;\n return makeWindow(n.value, u.unit);\n },\n};\n\nexport const stayDurationRule: Rule = {\n name: 'booking-stay-duration',\n priority: 75,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^на$/i.test(t.text) },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[1] as Token, 'Numeral');\n const u = findTag(matched[2] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n switch (u.unit) {\n case 'night': return { type: 'duration', nights: n.value };\n case 'day': return { type: 'duration', nights: n.value };\n case 'week': return { type: 'duration', nights: n.value * 7 };\n default: return null;\n }\n },\n};\n\nexport const weekendNextRule: Rule = {\n name: 'booking-weekend-next',\n priority: 80,\n pattern: [\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'next') },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /вихідн/i.test(t.text) },\n ],\n produce: () => ({\n type: 'range',\n start: { type: 'weekday', weekday: 6, modifier: 'next' },\n end: { type: 'weekday', weekday: 7, modifier: 'next' },\n convention: 'checkout',\n }),\n};\n\nexport const weekendThisRule: Rule = {\n name: 'booking-weekend-this',\n priority: 80,\n pattern: [\n { kind: 'tag', tag: 'Pointer', predicate: (t) => t.tags.some(x => x.kind === 'Pointer' && x.direction === 'this') },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /вихідн/i.test(t.text) },\n ],\n produce: () => ({\n type: 'range',\n start: { type: 'weekday', weekday: 6, modifier: 'this' },\n end: { type: 'weekday', weekday: 7, modifier: 'this' },\n convention: 'checkout',\n }),\n};\n\n// \"після свят\" — neither word has a tag in default UA lexicon, both arrive as Literal.\nexport const holidayPislyaRule: Rule = {\n name: 'booking-holiday-pislya',\n priority: 90,\n pattern: [\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^після$/i.test(t.text) },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^свят/i.test(t.text) },\n ],\n produce: () => ({\n type: 'fuzzy',\n granularity: 'month',\n ref: { type: 'absolute' },\n reason: 'holiday_ref',\n }),\n};\n\n// \"на свята\" — «на» is tagged by booking as Connector; «свят*» arrives as Literal.\nexport const holidayNaRule: Rule = {\n name: 'booking-holiday-na',\n priority: 90,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^на$/i.test(t.text) },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^свят/i.test(t.text) },\n ],\n produce: () => ({\n type: 'fuzzy',\n granularity: 'month',\n ref: { type: 'absolute' },\n reason: 'holiday_ref',\n }),\n};\n\nexport const bookingRules: Rule[] = [\n windowWithinNRule,\n windowWithinNWithPrefixRule,\n stayDurationRule,\n weekendNextRule,\n weekendThisRule,\n holidayPislyaRule,\n holidayNaRule,\n];\n\n// Tags the booking plugin contributes to ANY locale that uses it.\nexport const bookingTags = new Map<string, Tag[]>([\n ['впродовж', [{ kind: 'Grabber', modifier: 'within' }]],\n ['найближчі', [{ kind: 'Grabber', modifier: 'within' }]],\n ['найближчих', [{ kind: 'Grabber', modifier: 'within' }]],\n ['у', [{ kind: 'Connector', conn: 'from' }]],\n ['в', [{ kind: 'Connector', conn: 'from' }]],\n ['на', [{ kind: 'Connector', conn: 'from' }]],\n]);\n","import { DateTime } from 'luxon';\nimport type { Enricher, ResolverCtx, ResolvedDate } from '@whenis/core';\n\nconst DEFAULT_THRESHOLD = 0.75;\n\nexport const mostlyPastEnricher: Enricher = {\n apply(candidate: ResolvedDate, ctx: ResolverCtx): ResolvedDate {\n if (candidate.type !== 'fuzzy') return candidate;\n if (candidate.granularity !== 'month') return candidate;\n const ref = DateTime.fromJSDate(ctx.reference, { zone: ctx.timezone });\n const elapsed = ref.day / (ref.daysInMonth ?? 30);\n if (elapsed < DEFAULT_THRESHOLD) return candidate;\n return {\n ...candidate,\n metadata: { ...(candidate.metadata ?? {}), suggest_next_month: true },\n };\n },\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/rules.ts","../src/enrichers.ts"],"sourcesContent":["import type { Plugin } from '@whenis/core';\nimport { bookingRules, bookingTags } from './rules';\nimport { mostlyPastEnricher } from './enrichers';\n\nexport const booking: Plugin = {\n name: '@whenis/booking',\n tags: bookingTags,\n rules: bookingRules,\n enrichers: [mostlyPastEnricher],\n};\n","import type { Rule, Token, Tag, IRNode } from '@whenis/core';\n\nconst findTag = (t: Token, kind: string) => t.tags.find(x => x.kind === kind);\n\nfunction makeWindow(n: number, unit: 'day' | 'week'): IRNode {\n const days = unit === 'week' ? n * 7 : n;\n return {\n type: 'window',\n from: { type: 'relative', offset: { days: 0 }, direction: 'this' },\n to: { type: 'relative', offset: { days: days - 1 }, direction: 'future' },\n };\n}\n\n// \"впродовж 7 днів\" — also matches \"впродовж наступних 7 днів\" via optional next-filler\nexport const windowWithinNRule: Rule = {\n name: 'booking-window-within',\n priority: 75,\n pattern: [\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'within') },\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'next'), optional: true },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[2] as Token, 'Numeral');\n const u = findTag(matched[3] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n if (u.unit !== 'day' && u.unit !== 'week') return null;\n return makeWindow(n.value, u.unit);\n },\n};\n\n// \"у найближчі 5 днів\" — also matches \"у наступні найближчі 5 днів\" via optional next-filler\nexport const windowWithinNWithPrefixRule: Rule = {\n name: 'booking-window-within-prefixed',\n priority: 76,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^[ув]$/i.test(t.text) },\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'within') },\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'next'), optional: true },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[3] as Token, 'Numeral');\n const u = findTag(matched[4] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n if (u.unit !== 'day' && u.unit !== 'week') return null;\n return makeWindow(n.value, u.unit);\n },\n};\n\nexport const stayDurationRule: Rule = {\n name: 'booking-stay-duration',\n priority: 75,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^на$/i.test(t.text) },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[1] as Token, 'Numeral');\n const u = findTag(matched[2] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n switch (u.unit) {\n case 'night': return { type: 'duration', nights: n.value };\n case 'day': return { type: 'duration', nights: n.value };\n case 'week': return { type: 'duration', nights: n.value * 7 };\n default: return null;\n }\n },\n};\n\nexport const weekendNextRule: Rule = {\n name: 'booking-weekend-next',\n priority: 80,\n pattern: [\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'next') },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /вихідн/i.test(t.text) },\n ],\n produce: () => ({\n type: 'range',\n start: { type: 'weekday', weekday: 6, modifier: 'next' },\n end: { type: 'weekday', weekday: 7, modifier: 'next' },\n convention: 'checkout',\n }),\n};\n\nexport const weekendThisRule: Rule = {\n name: 'booking-weekend-this',\n priority: 80,\n pattern: [\n { kind: 'tag', tag: 'Pointer', predicate: (t) => t.tags.some(x => x.kind === 'Pointer' && x.direction === 'this') },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /вихідн/i.test(t.text) },\n ],\n produce: () => ({\n type: 'range',\n start: { type: 'weekday', weekday: 6, modifier: 'this' },\n end: { type: 'weekday', weekday: 7, modifier: 'this' },\n convention: 'checkout',\n }),\n};\n\n// \"останні вихідні [травня]\" — last Saturday-Sunday pair of the named (or current) month.\nexport const weekendLastOfMonthRule: Rule = {\n name: 'booking-weekend-last-of-month',\n priority: 82,\n pattern: [\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'last') },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /вихідн/i.test(t.text) },\n { kind: 'tag', tag: 'MonthName', optional: true },\n ],\n produce: (matched) => {\n const monthTok = matched[2] as Token | null;\n const mTag = monthTok?.tags.find(t => t.kind === 'MonthName');\n const month = mTag && mTag.kind === 'MonthName' ? mTag.month : undefined;\n const start: IRNode = month !== undefined\n ? { type: 'last_weekday_in_month', weekday: 6, month }\n : { type: 'last_weekday_in_month', weekday: 6 };\n return {\n type: 'range',\n start,\n end: { type: 'offset_from', base: start, days: 1 },\n convention: 'checkout',\n };\n },\n};\n\n// \"після свят\" — neither word has a tag in default UA lexicon, both arrive as Literal.\nexport const holidayPislyaRule: Rule = {\n name: 'booking-holiday-pislya',\n priority: 90,\n pattern: [\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^після$/i.test(t.text) },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^свят/i.test(t.text) },\n ],\n produce: () => ({\n type: 'fuzzy',\n granularity: 'month',\n ref: { type: 'absolute' },\n reason: 'holiday_ref',\n }),\n};\n\n// \"на свята\" — «на» is tagged by booking as Connector; «свят*» arrives as Literal.\nexport const holidayNaRule: Rule = {\n name: 'booking-holiday-na',\n priority: 90,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^на$/i.test(t.text) },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^свят/i.test(t.text) },\n ],\n produce: () => ({\n type: 'fuzzy',\n granularity: 'month',\n ref: { type: 'absolute' },\n reason: 'holiday_ref',\n }),\n};\n\n// \"[з] 5 червня на 3 ночі\" — combine a previously-emitted absolute date with a\n// duration into a single range. Both sub-IRs are produced by their own rules\n// at lower priority (60 / 75); this rule fires next, gluing them together.\nexport const bookingDateWithNightsRule: Rule = {\n name: 'booking-date-with-nights',\n priority: 80,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => t.tags.some(x => x.kind === 'Connector' && x.conn === 'from'), optional: true },\n { kind: 'node', node: 'absolute' },\n { kind: 'node', node: 'duration' },\n ],\n produce: (matched) => {\n const a = matched[1] as IRNode;\n const d = matched[2] as IRNode;\n if (a.type !== 'absolute' || d.type !== 'duration') return null;\n const nights = d.nights;\n if (nights === undefined) return null;\n return {\n type: 'range',\n start: a,\n end: { type: 'offset_from', base: a, days: nights },\n convention: 'checkout',\n };\n },\n};\n\nexport const bookingRules: Rule[] = [\n windowWithinNRule,\n windowWithinNWithPrefixRule,\n bookingDateWithNightsRule,\n stayDurationRule,\n weekendNextRule,\n weekendThisRule,\n weekendLastOfMonthRule,\n holidayPislyaRule,\n holidayNaRule,\n];\n\n// Tags the booking plugin contributes to ANY locale that uses it.\nexport const bookingTags = new Map<string, Tag[]>([\n ['впродовж', [{ kind: 'Grabber', modifier: 'within' }]],\n ['найближчі', [{ kind: 'Grabber', modifier: 'within' }]],\n ['найближчих', [{ kind: 'Grabber', modifier: 'within' }]],\n ['у', [{ kind: 'Connector', conn: 'from' }]],\n ['в', [{ kind: 'Connector', conn: 'from' }]],\n ['на', [{ kind: 'Connector', conn: 'from' }]],\n]);\n","import { DateTime } from 'luxon';\nimport type { Enricher, ResolverCtx, ResolvedDate } from '@whenis/core';\n\nconst DEFAULT_THRESHOLD = 0.75;\n\nexport const mostlyPastEnricher: Enricher = {\n apply(candidate: ResolvedDate, ctx: ResolverCtx): ResolvedDate {\n if (candidate.type !== 'fuzzy') return candidate;\n if (candidate.granularity !== 'month') return candidate;\n const ref = DateTime.fromJSDate(ctx.reference, { zone: ctx.timezone });\n const elapsed = ref.day / (ref.daysInMonth ?? 30);\n if (elapsed < DEFAULT_THRESHOLD) return candidate;\n return {\n ...candidate,\n metadata: { ...(candidate.metadata ?? {}), suggest_next_month: true },\n };\n },\n};\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -13,12 +13,13 @@ var windowWithinNRule = {
13
13
  priority: 75,
14
14
  pattern: [
15
15
  { kind: "tag", tag: "Grabber", predicate: (t) => t.tags.some((x) => x.kind === "Grabber" && x.modifier === "within") },
16
+ { kind: "tag", tag: "Grabber", predicate: (t) => t.tags.some((x) => x.kind === "Grabber" && x.modifier === "next"), optional: true },
16
17
  { kind: "tag", tag: "Numeral" },
17
18
  { kind: "tag", tag: "TimeUnit" }
18
19
  ],
19
20
  produce: (matched) => {
20
- const n = findTag(matched[1], "Numeral");
21
- const u = findTag(matched[2], "TimeUnit");
21
+ const n = findTag(matched[2], "Numeral");
22
+ const u = findTag(matched[3], "TimeUnit");
22
23
  if (!n || n.kind !== "Numeral" || !u || u.kind !== "TimeUnit") return null;
23
24
  if (u.unit !== "day" && u.unit !== "week") return null;
24
25
  return makeWindow(n.value, u.unit);
@@ -30,12 +31,13 @@ var windowWithinNWithPrefixRule = {
30
31
  pattern: [
31
32
  { kind: "tag", tag: "Connector", predicate: (t) => /^[ув]$/i.test(t.text) },
32
33
  { kind: "tag", tag: "Grabber", predicate: (t) => t.tags.some((x) => x.kind === "Grabber" && x.modifier === "within") },
34
+ { kind: "tag", tag: "Grabber", predicate: (t) => t.tags.some((x) => x.kind === "Grabber" && x.modifier === "next"), optional: true },
33
35
  { kind: "tag", tag: "Numeral" },
34
36
  { kind: "tag", tag: "TimeUnit" }
35
37
  ],
36
38
  produce: (matched) => {
37
- const n = findTag(matched[2], "Numeral");
38
- const u = findTag(matched[3], "TimeUnit");
39
+ const n = findTag(matched[3], "Numeral");
40
+ const u = findTag(matched[4], "TimeUnit");
39
41
  if (!n || n.kind !== "Numeral" || !u || u.kind !== "TimeUnit") return null;
40
42
  if (u.unit !== "day" && u.unit !== "week") return null;
41
43
  return makeWindow(n.value, u.unit);
@@ -93,6 +95,27 @@ var weekendThisRule = {
93
95
  convention: "checkout"
94
96
  })
95
97
  };
98
+ var weekendLastOfMonthRule = {
99
+ name: "booking-weekend-last-of-month",
100
+ priority: 82,
101
+ pattern: [
102
+ { kind: "tag", tag: "Grabber", predicate: (t) => t.tags.some((x) => x.kind === "Grabber" && x.modifier === "last") },
103
+ { kind: "tag", tag: "Literal", predicate: (t) => /вихідн/i.test(t.text) },
104
+ { kind: "tag", tag: "MonthName", optional: true }
105
+ ],
106
+ produce: (matched) => {
107
+ const monthTok = matched[2];
108
+ const mTag = monthTok?.tags.find((t) => t.kind === "MonthName");
109
+ const month = mTag && mTag.kind === "MonthName" ? mTag.month : void 0;
110
+ const start = month !== void 0 ? { type: "last_weekday_in_month", weekday: 6, month } : { type: "last_weekday_in_month", weekday: 6 };
111
+ return {
112
+ type: "range",
113
+ start,
114
+ end: { type: "offset_from", base: start, days: 1 },
115
+ convention: "checkout"
116
+ };
117
+ }
118
+ };
96
119
  var holidayPislyaRule = {
97
120
  name: "booking-holiday-pislya",
98
121
  priority: 90,
@@ -121,12 +144,36 @@ var holidayNaRule = {
121
144
  reason: "holiday_ref"
122
145
  })
123
146
  };
147
+ var bookingDateWithNightsRule = {
148
+ name: "booking-date-with-nights",
149
+ priority: 80,
150
+ pattern: [
151
+ { kind: "tag", tag: "Connector", predicate: (t) => t.tags.some((x) => x.kind === "Connector" && x.conn === "from"), optional: true },
152
+ { kind: "node", node: "absolute" },
153
+ { kind: "node", node: "duration" }
154
+ ],
155
+ produce: (matched) => {
156
+ const a = matched[1];
157
+ const d = matched[2];
158
+ if (a.type !== "absolute" || d.type !== "duration") return null;
159
+ const nights = d.nights;
160
+ if (nights === void 0) return null;
161
+ return {
162
+ type: "range",
163
+ start: a,
164
+ end: { type: "offset_from", base: a, days: nights },
165
+ convention: "checkout"
166
+ };
167
+ }
168
+ };
124
169
  var bookingRules = [
125
170
  windowWithinNRule,
126
171
  windowWithinNWithPrefixRule,
172
+ bookingDateWithNightsRule,
127
173
  stayDurationRule,
128
174
  weekendNextRule,
129
175
  weekendThisRule,
176
+ weekendLastOfMonthRule,
130
177
  holidayPislyaRule,
131
178
  holidayNaRule
132
179
  ];
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/rules.ts","../src/enrichers.ts","../src/index.ts"],"sourcesContent":["import type { Rule, Token, Tag, IRNode } from '@whenis/core';\n\nconst findTag = (t: Token, kind: string) => t.tags.find(x => x.kind === kind);\n\nfunction makeWindow(n: number, unit: 'day' | 'week'): IRNode {\n const days = unit === 'week' ? n * 7 : n;\n return {\n type: 'window',\n from: { type: 'relative', offset: { days: 0 }, direction: 'this' },\n to: { type: 'relative', offset: { days: days - 1 }, direction: 'future' },\n };\n}\n\n// \"впродовж 7 днів\" — 3-token form\nexport const windowWithinNRule: Rule = {\n name: 'booking-window-within',\n priority: 75,\n pattern: [\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'within') },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[1] as Token, 'Numeral');\n const u = findTag(matched[2] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n if (u.unit !== 'day' && u.unit !== 'week') return null;\n return makeWindow(n.value, u.unit);\n },\n};\n\n// \"у найближчі 5 днів\" — 4-token form with leading «у/в» Connector\nexport const windowWithinNWithPrefixRule: Rule = {\n name: 'booking-window-within-prefixed',\n priority: 76,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^[ув]$/i.test(t.text) },\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'within') },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[2] as Token, 'Numeral');\n const u = findTag(matched[3] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n if (u.unit !== 'day' && u.unit !== 'week') return null;\n return makeWindow(n.value, u.unit);\n },\n};\n\nexport const stayDurationRule: Rule = {\n name: 'booking-stay-duration',\n priority: 75,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^на$/i.test(t.text) },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[1] as Token, 'Numeral');\n const u = findTag(matched[2] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n switch (u.unit) {\n case 'night': return { type: 'duration', nights: n.value };\n case 'day': return { type: 'duration', nights: n.value };\n case 'week': return { type: 'duration', nights: n.value * 7 };\n default: return null;\n }\n },\n};\n\nexport const weekendNextRule: Rule = {\n name: 'booking-weekend-next',\n priority: 80,\n pattern: [\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'next') },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /вихідн/i.test(t.text) },\n ],\n produce: () => ({\n type: 'range',\n start: { type: 'weekday', weekday: 6, modifier: 'next' },\n end: { type: 'weekday', weekday: 7, modifier: 'next' },\n convention: 'checkout',\n }),\n};\n\nexport const weekendThisRule: Rule = {\n name: 'booking-weekend-this',\n priority: 80,\n pattern: [\n { kind: 'tag', tag: 'Pointer', predicate: (t) => t.tags.some(x => x.kind === 'Pointer' && x.direction === 'this') },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /вихідн/i.test(t.text) },\n ],\n produce: () => ({\n type: 'range',\n start: { type: 'weekday', weekday: 6, modifier: 'this' },\n end: { type: 'weekday', weekday: 7, modifier: 'this' },\n convention: 'checkout',\n }),\n};\n\n// \"після свят\" — neither word has a tag in default UA lexicon, both arrive as Literal.\nexport const holidayPislyaRule: Rule = {\n name: 'booking-holiday-pislya',\n priority: 90,\n pattern: [\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^після$/i.test(t.text) },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^свят/i.test(t.text) },\n ],\n produce: () => ({\n type: 'fuzzy',\n granularity: 'month',\n ref: { type: 'absolute' },\n reason: 'holiday_ref',\n }),\n};\n\n// \"на свята\" — «на» is tagged by booking as Connector; «свят*» arrives as Literal.\nexport const holidayNaRule: Rule = {\n name: 'booking-holiday-na',\n priority: 90,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^на$/i.test(t.text) },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^свят/i.test(t.text) },\n ],\n produce: () => ({\n type: 'fuzzy',\n granularity: 'month',\n ref: { type: 'absolute' },\n reason: 'holiday_ref',\n }),\n};\n\nexport const bookingRules: Rule[] = [\n windowWithinNRule,\n windowWithinNWithPrefixRule,\n stayDurationRule,\n weekendNextRule,\n weekendThisRule,\n holidayPislyaRule,\n holidayNaRule,\n];\n\n// Tags the booking plugin contributes to ANY locale that uses it.\nexport const bookingTags = new Map<string, Tag[]>([\n ['впродовж', [{ kind: 'Grabber', modifier: 'within' }]],\n ['найближчі', [{ kind: 'Grabber', modifier: 'within' }]],\n ['найближчих', [{ kind: 'Grabber', modifier: 'within' }]],\n ['у', [{ kind: 'Connector', conn: 'from' }]],\n ['в', [{ kind: 'Connector', conn: 'from' }]],\n ['на', [{ kind: 'Connector', conn: 'from' }]],\n]);\n","import { DateTime } from 'luxon';\nimport type { Enricher, ResolverCtx, ResolvedDate } from '@whenis/core';\n\nconst DEFAULT_THRESHOLD = 0.75;\n\nexport const mostlyPastEnricher: Enricher = {\n apply(candidate: ResolvedDate, ctx: ResolverCtx): ResolvedDate {\n if (candidate.type !== 'fuzzy') return candidate;\n if (candidate.granularity !== 'month') return candidate;\n const ref = DateTime.fromJSDate(ctx.reference, { zone: ctx.timezone });\n const elapsed = ref.day / (ref.daysInMonth ?? 30);\n if (elapsed < DEFAULT_THRESHOLD) return candidate;\n return {\n ...candidate,\n metadata: { ...(candidate.metadata ?? {}), suggest_next_month: true },\n };\n },\n};\n","import type { Plugin } from '@whenis/core';\nimport { bookingRules, bookingTags } from './rules';\nimport { mostlyPastEnricher } from './enrichers';\n\nexport const booking: Plugin = {\n name: '@whenis/booking',\n tags: bookingTags,\n rules: bookingRules,\n enrichers: [mostlyPastEnricher],\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/rules.ts","../src/enrichers.ts","../src/index.ts"],"sourcesContent":["import type { Rule, Token, Tag, IRNode } from '@whenis/core';\n\nconst findTag = (t: Token, kind: string) => t.tags.find(x => x.kind === kind);\n\nfunction makeWindow(n: number, unit: 'day' | 'week'): IRNode {\n const days = unit === 'week' ? n * 7 : n;\n return {\n type: 'window',\n from: { type: 'relative', offset: { days: 0 }, direction: 'this' },\n to: { type: 'relative', offset: { days: days - 1 }, direction: 'future' },\n };\n}\n\n// \"впродовж 7 днів\" — also matches \"впродовж наступних 7 днів\" via optional next-filler\nexport const windowWithinNRule: Rule = {\n name: 'booking-window-within',\n priority: 75,\n pattern: [\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'within') },\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'next'), optional: true },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[2] as Token, 'Numeral');\n const u = findTag(matched[3] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n if (u.unit !== 'day' && u.unit !== 'week') return null;\n return makeWindow(n.value, u.unit);\n },\n};\n\n// \"у найближчі 5 днів\" — also matches \"у наступні найближчі 5 днів\" via optional next-filler\nexport const windowWithinNWithPrefixRule: Rule = {\n name: 'booking-window-within-prefixed',\n priority: 76,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^[ув]$/i.test(t.text) },\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'within') },\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'next'), optional: true },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[3] as Token, 'Numeral');\n const u = findTag(matched[4] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n if (u.unit !== 'day' && u.unit !== 'week') return null;\n return makeWindow(n.value, u.unit);\n },\n};\n\nexport const stayDurationRule: Rule = {\n name: 'booking-stay-duration',\n priority: 75,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^на$/i.test(t.text) },\n { kind: 'tag', tag: 'Numeral' },\n { kind: 'tag', tag: 'TimeUnit' },\n ],\n produce: (matched) => {\n const n = findTag(matched[1] as Token, 'Numeral');\n const u = findTag(matched[2] as Token, 'TimeUnit');\n if (!n || n.kind !== 'Numeral' || !u || u.kind !== 'TimeUnit') return null;\n switch (u.unit) {\n case 'night': return { type: 'duration', nights: n.value };\n case 'day': return { type: 'duration', nights: n.value };\n case 'week': return { type: 'duration', nights: n.value * 7 };\n default: return null;\n }\n },\n};\n\nexport const weekendNextRule: Rule = {\n name: 'booking-weekend-next',\n priority: 80,\n pattern: [\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'next') },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /вихідн/i.test(t.text) },\n ],\n produce: () => ({\n type: 'range',\n start: { type: 'weekday', weekday: 6, modifier: 'next' },\n end: { type: 'weekday', weekday: 7, modifier: 'next' },\n convention: 'checkout',\n }),\n};\n\nexport const weekendThisRule: Rule = {\n name: 'booking-weekend-this',\n priority: 80,\n pattern: [\n { kind: 'tag', tag: 'Pointer', predicate: (t) => t.tags.some(x => x.kind === 'Pointer' && x.direction === 'this') },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /вихідн/i.test(t.text) },\n ],\n produce: () => ({\n type: 'range',\n start: { type: 'weekday', weekday: 6, modifier: 'this' },\n end: { type: 'weekday', weekday: 7, modifier: 'this' },\n convention: 'checkout',\n }),\n};\n\n// \"останні вихідні [травня]\" — last Saturday-Sunday pair of the named (or current) month.\nexport const weekendLastOfMonthRule: Rule = {\n name: 'booking-weekend-last-of-month',\n priority: 82,\n pattern: [\n { kind: 'tag', tag: 'Grabber', predicate: (t) => t.tags.some(x => x.kind === 'Grabber' && x.modifier === 'last') },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /вихідн/i.test(t.text) },\n { kind: 'tag', tag: 'MonthName', optional: true },\n ],\n produce: (matched) => {\n const monthTok = matched[2] as Token | null;\n const mTag = monthTok?.tags.find(t => t.kind === 'MonthName');\n const month = mTag && mTag.kind === 'MonthName' ? mTag.month : undefined;\n const start: IRNode = month !== undefined\n ? { type: 'last_weekday_in_month', weekday: 6, month }\n : { type: 'last_weekday_in_month', weekday: 6 };\n return {\n type: 'range',\n start,\n end: { type: 'offset_from', base: start, days: 1 },\n convention: 'checkout',\n };\n },\n};\n\n// \"після свят\" — neither word has a tag in default UA lexicon, both arrive as Literal.\nexport const holidayPislyaRule: Rule = {\n name: 'booking-holiday-pislya',\n priority: 90,\n pattern: [\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^після$/i.test(t.text) },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^свят/i.test(t.text) },\n ],\n produce: () => ({\n type: 'fuzzy',\n granularity: 'month',\n ref: { type: 'absolute' },\n reason: 'holiday_ref',\n }),\n};\n\n// \"на свята\" — «на» is tagged by booking as Connector; «свят*» arrives as Literal.\nexport const holidayNaRule: Rule = {\n name: 'booking-holiday-na',\n priority: 90,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => /^на$/i.test(t.text) },\n { kind: 'tag', tag: 'Literal', predicate: (t) => /^свят/i.test(t.text) },\n ],\n produce: () => ({\n type: 'fuzzy',\n granularity: 'month',\n ref: { type: 'absolute' },\n reason: 'holiday_ref',\n }),\n};\n\n// \"[з] 5 червня на 3 ночі\" — combine a previously-emitted absolute date with a\n// duration into a single range. Both sub-IRs are produced by their own rules\n// at lower priority (60 / 75); this rule fires next, gluing them together.\nexport const bookingDateWithNightsRule: Rule = {\n name: 'booking-date-with-nights',\n priority: 80,\n pattern: [\n { kind: 'tag', tag: 'Connector', predicate: (t) => t.tags.some(x => x.kind === 'Connector' && x.conn === 'from'), optional: true },\n { kind: 'node', node: 'absolute' },\n { kind: 'node', node: 'duration' },\n ],\n produce: (matched) => {\n const a = matched[1] as IRNode;\n const d = matched[2] as IRNode;\n if (a.type !== 'absolute' || d.type !== 'duration') return null;\n const nights = d.nights;\n if (nights === undefined) return null;\n return {\n type: 'range',\n start: a,\n end: { type: 'offset_from', base: a, days: nights },\n convention: 'checkout',\n };\n },\n};\n\nexport const bookingRules: Rule[] = [\n windowWithinNRule,\n windowWithinNWithPrefixRule,\n bookingDateWithNightsRule,\n stayDurationRule,\n weekendNextRule,\n weekendThisRule,\n weekendLastOfMonthRule,\n holidayPislyaRule,\n holidayNaRule,\n];\n\n// Tags the booking plugin contributes to ANY locale that uses it.\nexport const bookingTags = new Map<string, Tag[]>([\n ['впродовж', [{ kind: 'Grabber', modifier: 'within' }]],\n ['найближчі', [{ kind: 'Grabber', modifier: 'within' }]],\n ['найближчих', [{ kind: 'Grabber', modifier: 'within' }]],\n ['у', [{ kind: 'Connector', conn: 'from' }]],\n ['в', [{ kind: 'Connector', conn: 'from' }]],\n ['на', [{ kind: 'Connector', conn: 'from' }]],\n]);\n","import { DateTime } from 'luxon';\nimport type { Enricher, ResolverCtx, ResolvedDate } from '@whenis/core';\n\nconst DEFAULT_THRESHOLD = 0.75;\n\nexport const mostlyPastEnricher: Enricher = {\n apply(candidate: ResolvedDate, ctx: ResolverCtx): ResolvedDate {\n if (candidate.type !== 'fuzzy') return candidate;\n if (candidate.granularity !== 'month') return candidate;\n const ref = DateTime.fromJSDate(ctx.reference, { zone: ctx.timezone });\n const elapsed = ref.day / (ref.daysInMonth ?? 30);\n if (elapsed < DEFAULT_THRESHOLD) return candidate;\n return {\n ...candidate,\n metadata: { ...(candidate.metadata ?? {}), suggest_next_month: true },\n };\n },\n};\n","import type { Plugin } from '@whenis/core';\nimport { bookingRules, bookingTags } from './rules';\nimport { mostlyPastEnricher } from './enrichers';\n\nexport const booking: Plugin = {\n name: '@whenis/booking',\n tags: bookingTags,\n rules: bookingRules,\n enrichers: [mostlyPastEnricher],\n};\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whenis/booking",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Booking-domain plugin for whenis — search windows, stay durations, weekend semantics, holiday references, mostly-past month enricher.",
5
5
  "keywords": [
6
6
  "date",
@@ -53,14 +53,14 @@
53
53
  "node": ">=18"
54
54
  },
55
55
  "peerDependencies": {
56
- "@whenis/core": "^0.1.1"
56
+ "@whenis/core": ">=0.1.1 <1.0.0"
57
57
  },
58
58
  "dependencies": {
59
59
  "luxon": "^3.5.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/luxon": "^3.4.0",
63
- "@whenis/core": "0.1.1"
63
+ "@whenis/core": "0.3.0"
64
64
  },
65
65
  "publishConfig": {
66
66
  "access": "public"