@mrtdown/core 2.0.0-alpha.5 → 2.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/dist/index.d.ts +0 -7
  2. package/dist/index.js +0 -7
  3. package/dist/index.js.map +1 -1
  4. package/dist/schema/Landmark.js.map +1 -1
  5. package/dist/schema/Line.d.ts +6 -0
  6. package/dist/schema/Line.js +2 -1
  7. package/dist/schema/Line.js.map +1 -1
  8. package/dist/schema/Operator.js.map +1 -1
  9. package/dist/schema/Service.js.map +1 -1
  10. package/dist/schema/Station.js.map +1 -1
  11. package/dist/schema/Town.js.map +1 -1
  12. package/dist/schema/common.js.map +1 -1
  13. package/dist/schema/issue/bundle.js.map +1 -1
  14. package/dist/schema/issue/cause.js.map +1 -1
  15. package/dist/schema/issue/claim.js.map +1 -1
  16. package/dist/schema/issue/entity.js.map +1 -1
  17. package/dist/schema/issue/evidence.js.map +1 -1
  18. package/dist/schema/issue/facilityEffect.js.map +1 -1
  19. package/dist/schema/issue/id.js.map +1 -1
  20. package/dist/schema/issue/impactEvent.js.map +1 -1
  21. package/dist/schema/issue/issue.js.map +1 -1
  22. package/dist/schema/issue/issueType.js.map +1 -1
  23. package/dist/schema/issue/period.js.map +1 -1
  24. package/dist/schema/issue/serviceEffect.js.map +1 -1
  25. package/dist/schema/issue/serviceScope.js.map +1 -1
  26. package/package.json +19 -44
  27. package/README.md +0 -107
  28. package/dist/cli/commands/create.d.ts +0 -30
  29. package/dist/cli/commands/create.js +0 -189
  30. package/dist/cli/commands/create.js.map +0 -1
  31. package/dist/cli/commands/list.d.ts +0 -6
  32. package/dist/cli/commands/list.js +0 -106
  33. package/dist/cli/commands/list.js.map +0 -1
  34. package/dist/cli/commands/show.d.ts +0 -6
  35. package/dist/cli/commands/show.js +0 -156
  36. package/dist/cli/commands/show.js.map +0 -1
  37. package/dist/cli/commands/validate.d.ts +0 -6
  38. package/dist/cli/commands/validate.js +0 -19
  39. package/dist/cli/commands/validate.js.map +0 -1
  40. package/dist/cli/index.d.ts +0 -2
  41. package/dist/cli/index.js +0 -162
  42. package/dist/cli/index.js.map +0 -1
  43. package/dist/constants.d.ts +0 -10
  44. package/dist/constants.js +0 -11
  45. package/dist/constants.js.map +0 -1
  46. package/dist/helpers/calculateDurationWithinServiceHours.d.ts +0 -2
  47. package/dist/helpers/calculateDurationWithinServiceHours.js +0 -13
  48. package/dist/helpers/calculateDurationWithinServiceHours.js.map +0 -1
  49. package/dist/helpers/calculateDurationWithinServiceHours.test.d.ts +0 -1
  50. package/dist/helpers/calculateDurationWithinServiceHours.test.js +0 -83
  51. package/dist/helpers/calculateDurationWithinServiceHours.test.js.map +0 -1
  52. package/dist/helpers/computeImpactFromEvidenceClaims.d.ts +0 -21
  53. package/dist/helpers/computeImpactFromEvidenceClaims.js +0 -293
  54. package/dist/helpers/computeImpactFromEvidenceClaims.js.map +0 -1
  55. package/dist/helpers/computeImpactFromEvidenceClaims.test.d.ts +0 -1
  56. package/dist/helpers/computeImpactFromEvidenceClaims.test.js +0 -544
  57. package/dist/helpers/computeImpactFromEvidenceClaims.test.js.map +0 -1
  58. package/dist/helpers/computeStartOfDaysWithinInterval.d.ts +0 -2
  59. package/dist/helpers/computeStartOfDaysWithinInterval.js +0 -15
  60. package/dist/helpers/computeStartOfDaysWithinInterval.js.map +0 -1
  61. package/dist/helpers/computeStartOfDaysWithinInterval.test.d.ts +0 -1
  62. package/dist/helpers/computeStartOfDaysWithinInterval.test.js +0 -126
  63. package/dist/helpers/computeStartOfDaysWithinInterval.test.js.map +0 -1
  64. package/dist/helpers/estimateOpenAICost.d.ts +0 -40
  65. package/dist/helpers/estimateOpenAICost.js +0 -55
  66. package/dist/helpers/estimateOpenAICost.js.map +0 -1
  67. package/dist/helpers/keyForAffectedEntity.d.ts +0 -7
  68. package/dist/helpers/keyForAffectedEntity.js +0 -14
  69. package/dist/helpers/keyForAffectedEntity.js.map +0 -1
  70. package/dist/helpers/normalizeRecurringPeriod.d.ts +0 -7
  71. package/dist/helpers/normalizeRecurringPeriod.js +0 -118
  72. package/dist/helpers/normalizeRecurringPeriod.js.map +0 -1
  73. package/dist/helpers/normalizeRecurringPeriod.test.d.ts +0 -1
  74. package/dist/helpers/normalizeRecurringPeriod.test.js +0 -93
  75. package/dist/helpers/normalizeRecurringPeriod.test.js.map +0 -1
  76. package/dist/helpers/resolvePeriods.d.ts +0 -224
  77. package/dist/helpers/resolvePeriods.js +0 -207
  78. package/dist/helpers/resolvePeriods.js.map +0 -1
  79. package/dist/helpers/resolvePeriods.test.d.ts +0 -1
  80. package/dist/helpers/resolvePeriods.test.js +0 -239
  81. package/dist/helpers/resolvePeriods.test.js.map +0 -1
  82. package/dist/helpers/splitIntervalByServiceHours.d.ts +0 -2
  83. package/dist/helpers/splitIntervalByServiceHours.js +0 -30
  84. package/dist/helpers/splitIntervalByServiceHours.js.map +0 -1
  85. package/dist/helpers/splitIntervalByServiceHours.test.d.ts +0 -1
  86. package/dist/helpers/splitIntervalByServiceHours.test.js +0 -152
  87. package/dist/helpers/splitIntervalByServiceHours.test.js.map +0 -1
  88. package/dist/helpers/sumIntervalDuration.d.ts +0 -2
  89. package/dist/helpers/sumIntervalDuration.js +0 -9
  90. package/dist/helpers/sumIntervalDuration.js.map +0 -1
  91. package/dist/llm/client.d.ts +0 -2
  92. package/dist/llm/client.js +0 -5
  93. package/dist/llm/client.js.map +0 -1
  94. package/dist/llm/common/MemoryStore.d.ts +0 -21
  95. package/dist/llm/common/MemoryStore.js +0 -100
  96. package/dist/llm/common/MemoryStore.js.map +0 -1
  97. package/dist/llm/common/MemoryStore.test.d.ts +0 -1
  98. package/dist/llm/common/MemoryStore.test.js +0 -225
  99. package/dist/llm/common/MemoryStore.test.js.map +0 -1
  100. package/dist/llm/common/formatCurrentState.d.ts +0 -10
  101. package/dist/llm/common/formatCurrentState.js +0 -342
  102. package/dist/llm/common/formatCurrentState.js.map +0 -1
  103. package/dist/llm/common/tool.d.ts +0 -32
  104. package/dist/llm/common/tool.js +0 -6
  105. package/dist/llm/common/tool.js.map +0 -1
  106. package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.d.ts +0 -1
  107. package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.js +0 -433
  108. package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.js.map +0 -1
  109. package/dist/llm/functions/extractClaimsFromNewEvidence/index.d.ts +0 -18
  110. package/dist/llm/functions/extractClaimsFromNewEvidence/index.js +0 -153
  111. package/dist/llm/functions/extractClaimsFromNewEvidence/index.js.map +0 -1
  112. package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.d.ts +0 -1
  113. package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.js +0 -168
  114. package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.js.map +0 -1
  115. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.d.ts +0 -19
  116. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.js +0 -65
  117. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.js.map +0 -1
  118. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.d.ts +0 -21
  119. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.js +0 -115
  120. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.js.map +0 -1
  121. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.d.ts +0 -24
  122. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.js +0 -110
  123. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.js.map +0 -1
  124. package/dist/llm/functions/generateIssueTitleAndSlug/index.d.ts +0 -14
  125. package/dist/llm/functions/generateIssueTitleAndSlug/index.js +0 -38
  126. package/dist/llm/functions/generateIssueTitleAndSlug/index.js.map +0 -1
  127. package/dist/llm/functions/generateIssueTitleAndSlug/prompt.d.ts +0 -1
  128. package/dist/llm/functions/generateIssueTitleAndSlug/prompt.js +0 -23
  129. package/dist/llm/functions/generateIssueTitleAndSlug/prompt.js.map +0 -1
  130. package/dist/llm/functions/translate/index.d.ts +0 -1
  131. package/dist/llm/functions/translate/index.js +0 -59
  132. package/dist/llm/functions/translate/index.js.map +0 -1
  133. package/dist/llm/functions/triageNewEvidence/eval.test.d.ts +0 -1
  134. package/dist/llm/functions/triageNewEvidence/eval.test.js +0 -139
  135. package/dist/llm/functions/triageNewEvidence/eval.test.js.map +0 -1
  136. package/dist/llm/functions/triageNewEvidence/index.d.ts +0 -37
  137. package/dist/llm/functions/triageNewEvidence/index.js +0 -121
  138. package/dist/llm/functions/triageNewEvidence/index.js.map +0 -1
  139. package/dist/llm/functions/triageNewEvidence/prompt.d.ts +0 -1
  140. package/dist/llm/functions/triageNewEvidence/prompt.js +0 -60
  141. package/dist/llm/functions/triageNewEvidence/prompt.js.map +0 -1
  142. package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.d.ts +0 -19
  143. package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.js +0 -65
  144. package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.js.map +0 -1
  145. package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.d.ts +0 -19
  146. package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.js +0 -37
  147. package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.js.map +0 -1
  148. package/dist/repo/MRTDownRepository.d.ts +0 -23
  149. package/dist/repo/MRTDownRepository.js +0 -28
  150. package/dist/repo/MRTDownRepository.js.map +0 -1
  151. package/dist/repo/common/FileStore.d.ts +0 -12
  152. package/dist/repo/common/FileStore.js +0 -27
  153. package/dist/repo/common/FileStore.js.map +0 -1
  154. package/dist/repo/common/StandardRepository.d.ts +0 -32
  155. package/dist/repo/common/StandardRepository.js +0 -58
  156. package/dist/repo/common/StandardRepository.js.map +0 -1
  157. package/dist/repo/common/store.d.ts +0 -29
  158. package/dist/repo/common/store.js +0 -2
  159. package/dist/repo/common/store.js.map +0 -1
  160. package/dist/repo/issue/IssueRepository.d.ts +0 -36
  161. package/dist/repo/issue/IssueRepository.js +0 -177
  162. package/dist/repo/issue/IssueRepository.js.map +0 -1
  163. package/dist/repo/issue/helpers/deriveCurrentState.d.ts +0 -51
  164. package/dist/repo/issue/helpers/deriveCurrentState.js +0 -113
  165. package/dist/repo/issue/helpers/deriveCurrentState.js.map +0 -1
  166. package/dist/repo/issue/helpers/deriveCurrentState.test.d.ts +0 -1
  167. package/dist/repo/issue/helpers/deriveCurrentState.test.js +0 -477
  168. package/dist/repo/issue/helpers/deriveCurrentState.test.js.map +0 -1
  169. package/dist/repo/landmark/LandmarkRepository.d.ts +0 -7
  170. package/dist/repo/landmark/LandmarkRepository.js +0 -12
  171. package/dist/repo/landmark/LandmarkRepository.js.map +0 -1
  172. package/dist/repo/line/LineRepository.d.ts +0 -13
  173. package/dist/repo/line/LineRepository.js +0 -32
  174. package/dist/repo/line/LineRepository.js.map +0 -1
  175. package/dist/repo/operator/OperatorRepository.d.ts +0 -7
  176. package/dist/repo/operator/OperatorRepository.js +0 -12
  177. package/dist/repo/operator/OperatorRepository.js.map +0 -1
  178. package/dist/repo/service/ServiceRepository.d.ts +0 -19
  179. package/dist/repo/service/ServiceRepository.js +0 -39
  180. package/dist/repo/service/ServiceRepository.js.map +0 -1
  181. package/dist/repo/station/StationRepository.d.ts +0 -13
  182. package/dist/repo/station/StationRepository.js +0 -30
  183. package/dist/repo/station/StationRepository.js.map +0 -1
  184. package/dist/repo/town/TownRepository.d.ts +0 -7
  185. package/dist/repo/town/TownRepository.js +0 -12
  186. package/dist/repo/town/TownRepository.js.map +0 -1
  187. package/dist/scripts/ingestViaWebhook.d.ts +0 -1
  188. package/dist/scripts/ingestViaWebhook.js +0 -9
  189. package/dist/scripts/ingestViaWebhook.js.map +0 -1
  190. package/dist/util/assert.d.ts +0 -1
  191. package/dist/util/assert.js +0 -6
  192. package/dist/util/assert.js.map +0 -1
  193. package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.d.ts +0 -7
  194. package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.js +0 -24
  195. package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.js.map +0 -1
  196. package/dist/util/ingestContent/index.d.ts +0 -12
  197. package/dist/util/ingestContent/index.js +0 -171
  198. package/dist/util/ingestContent/index.js.map +0 -1
  199. package/dist/util/ingestContent/types.d.ts +0 -32
  200. package/dist/util/ingestContent/types.js +0 -2
  201. package/dist/util/ingestContent/types.js.map +0 -1
  202. package/dist/validators/buildContext.d.ts +0 -7
  203. package/dist/validators/buildContext.js +0 -164
  204. package/dist/validators/buildContext.js.map +0 -1
  205. package/dist/validators/index.d.ts +0 -17
  206. package/dist/validators/index.js +0 -58
  207. package/dist/validators/index.js.map +0 -1
  208. package/dist/validators/issue.d.ts +0 -13
  209. package/dist/validators/issue.js +0 -220
  210. package/dist/validators/issue.js.map +0 -1
  211. package/dist/validators/landmark.d.ts +0 -7
  212. package/dist/validators/landmark.js +0 -43
  213. package/dist/validators/landmark.js.map +0 -1
  214. package/dist/validators/line.d.ts +0 -8
  215. package/dist/validators/line.js +0 -87
  216. package/dist/validators/line.js.map +0 -1
  217. package/dist/validators/operator.d.ts +0 -7
  218. package/dist/validators/operator.js +0 -43
  219. package/dist/validators/operator.js.map +0 -1
  220. package/dist/validators/service.d.ts +0 -8
  221. package/dist/validators/service.js +0 -87
  222. package/dist/validators/service.js.map +0 -1
  223. package/dist/validators/station.d.ts +0 -8
  224. package/dist/validators/station.js +0 -93
  225. package/dist/validators/station.js.map +0 -1
  226. package/dist/validators/town.d.ts +0 -7
  227. package/dist/validators/town.js +0 -43
  228. package/dist/validators/town.js.map +0 -1
  229. package/dist/validators/types.d.ts +0 -19
  230. package/dist/validators/types.js +0 -2
  231. package/dist/validators/types.js.map +0 -1
  232. package/dist/validators/utils.d.ts +0 -2
  233. package/dist/validators/utils.js +0 -9
  234. package/dist/validators/utils.js.map +0 -1
  235. package/dist/write/MRTDownWriter.d.ts +0 -27
  236. package/dist/write/MRTDownWriter.js +0 -27
  237. package/dist/write/MRTDownWriter.js.map +0 -1
  238. package/dist/write/common/FileWriteStore.d.ts +0 -13
  239. package/dist/write/common/FileWriteStore.js +0 -31
  240. package/dist/write/common/FileWriteStore.js.map +0 -1
  241. package/dist/write/common/StandardWriter.d.ts +0 -14
  242. package/dist/write/common/StandardWriter.js +0 -17
  243. package/dist/write/common/StandardWriter.js.map +0 -1
  244. package/dist/write/common/store.d.ts +0 -32
  245. package/dist/write/common/store.js +0 -2
  246. package/dist/write/common/store.js.map +0 -1
  247. package/dist/write/id/IdGenerator.d.ts +0 -18
  248. package/dist/write/id/IdGenerator.js +0 -23
  249. package/dist/write/id/IdGenerator.js.map +0 -1
  250. package/dist/write/issue/IssueWriter.d.ts +0 -12
  251. package/dist/write/issue/IssueWriter.js +0 -33
  252. package/dist/write/issue/IssueWriter.js.map +0 -1
@@ -1,15 +0,0 @@
1
- import { Interval } from 'luxon';
2
- import { assert } from '../util/assert.js';
3
- export function computeStartOfDaysWithinInterval(start, end) {
4
- const interval = Interval.fromDateTimes(start.startOf('day'), end);
5
- const segments = interval.splitBy({ days: 1 });
6
- if (start.toMillis() !== start.startOf('day').toMillis()) {
7
- segments.splice(0, 1);
8
- }
9
- const result = segments.map((segment) => {
10
- assert(segment.start != null);
11
- return segment.start;
12
- });
13
- return result;
14
- }
15
- //# sourceMappingURL=computeStartOfDaysWithinInterval.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"computeStartOfDaysWithinInterval.js","sourceRoot":"/","sources":["helpers/computeStartOfDaysWithinInterval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,MAAM,UAAU,gCAAgC,CAC9C,KAAe,EACf,GAAa;IAEb,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;IACnE,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IAE/C,IAAI,KAAK,CAAC,QAAQ,EAAE,KAAK,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;QACzD,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACxB,CAAC;IAED,MAAM,MAAM,GAAe,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAClD,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;QAC9B,OAAO,OAAO,CAAC,KAAK,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { type DateTime, Interval } from 'luxon';\nimport { assert } from '../util/assert.js';\n\nexport function computeStartOfDaysWithinInterval(\n start: DateTime,\n end: DateTime,\n): DateTime[] {\n const interval = Interval.fromDateTimes(start.startOf('day'), end);\n const segments = interval.splitBy({ days: 1 });\n\n if (start.toMillis() !== start.startOf('day').toMillis()) {\n segments.splice(0, 1);\n }\n\n const result: DateTime[] = segments.map((segment) => {\n assert(segment.start != null);\n return segment.start;\n });\n\n return result;\n}\n"]}
@@ -1,126 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { computeStartOfDaysWithinInterval } from './computeStartOfDaysWithinInterval.js';
3
- import { DateTime } from 'luxon';
4
- describe('computeStartOfDaysWithinInterval', () => {
5
- test('interval that does not transcend days', () => {
6
- expect(computeStartOfDaysWithinInterval(DateTime.fromObject({
7
- day: 1,
8
- month: 4,
9
- year: 2025,
10
- hour: 12,
11
- minute: 30,
12
- }), DateTime.fromObject({
13
- day: 1,
14
- month: 4,
15
- year: 2025,
16
- hour: 15,
17
- minute: 30,
18
- }))).toEqual([]);
19
- });
20
- test('interval that starts from midnight', () => {
21
- expect(computeStartOfDaysWithinInterval(DateTime.fromObject({
22
- day: 1,
23
- month: 4,
24
- year: 2025,
25
- hour: 0,
26
- minute: 0,
27
- }), DateTime.fromObject({
28
- day: 1,
29
- month: 4,
30
- year: 2025,
31
- hour: 15,
32
- minute: 30,
33
- }))).toEqual([
34
- DateTime.fromObject({
35
- day: 1,
36
- month: 4,
37
- year: 2025,
38
- hour: 0,
39
- minute: 0,
40
- }),
41
- ]);
42
- });
43
- test('interval that ends at midnight', () => {
44
- expect(computeStartOfDaysWithinInterval(DateTime.fromObject({
45
- day: 1,
46
- month: 4,
47
- year: 2025,
48
- hour: 15,
49
- minute: 30,
50
- }), DateTime.fromObject({
51
- day: 2,
52
- month: 4,
53
- year: 2025,
54
- hour: 0,
55
- minute: 0,
56
- }))).toEqual([]);
57
- });
58
- test('interval that transcends 2 days', () => {
59
- expect(computeStartOfDaysWithinInterval(DateTime.fromObject({
60
- day: 1,
61
- month: 4,
62
- year: 2025,
63
- hour: 18,
64
- minute: 30,
65
- }), DateTime.fromObject({
66
- day: 2,
67
- month: 4,
68
- year: 2025,
69
- hour: 18,
70
- minute: 30,
71
- }))).toEqual([
72
- DateTime.fromObject({
73
- day: 2,
74
- month: 4,
75
- year: 2025,
76
- hour: 0,
77
- minute: 0,
78
- }),
79
- ]);
80
- });
81
- test('interval that transcends more than 2 days', () => {
82
- expect(computeStartOfDaysWithinInterval(DateTime.fromObject({
83
- day: 1,
84
- month: 4,
85
- year: 2025,
86
- hour: 18,
87
- minute: 30,
88
- }), DateTime.fromObject({
89
- day: 5,
90
- month: 4,
91
- year: 2025,
92
- hour: 4,
93
- minute: 30,
94
- }))).toEqual([
95
- DateTime.fromObject({
96
- day: 2,
97
- month: 4,
98
- year: 2025,
99
- hour: 0,
100
- minute: 0,
101
- }),
102
- DateTime.fromObject({
103
- day: 3,
104
- month: 4,
105
- year: 2025,
106
- hour: 0,
107
- minute: 0,
108
- }),
109
- DateTime.fromObject({
110
- day: 4,
111
- month: 4,
112
- year: 2025,
113
- hour: 0,
114
- minute: 0,
115
- }),
116
- DateTime.fromObject({
117
- day: 5,
118
- month: 4,
119
- year: 2025,
120
- hour: 0,
121
- minute: 0,
122
- }),
123
- ]);
124
- });
125
- });
126
- //# sourceMappingURL=computeStartOfDaysWithinInterval.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"computeStartOfDaysWithinInterval.test.js","sourceRoot":"/","sources":["helpers/computeStartOfDaysWithinInterval.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, test } from 'vitest';\n\nimport { computeStartOfDaysWithinInterval } from './computeStartOfDaysWithinInterval.js';\nimport { DateTime } from 'luxon';\n\ndescribe('computeStartOfDaysWithinInterval', () => {\n test('interval that does not transcend days', () => {\n expect(\n computeStartOfDaysWithinInterval(\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 12,\n minute: 30,\n }),\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 15,\n minute: 30,\n }),\n ),\n ).toEqual([] satisfies DateTime[]);\n });\n\n test('interval that starts from midnight', () => {\n expect(\n computeStartOfDaysWithinInterval(\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 0,\n minute: 0,\n }),\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 15,\n minute: 30,\n }),\n ),\n ).toEqual([\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 0,\n minute: 0,\n }),\n ] satisfies DateTime[]);\n });\n\n test('interval that ends at midnight', () => {\n expect(\n computeStartOfDaysWithinInterval(\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 15,\n minute: 30,\n }),\n DateTime.fromObject({\n day: 2,\n month: 4,\n year: 2025,\n hour: 0,\n minute: 0,\n }),\n ),\n ).toEqual([] satisfies DateTime[]);\n });\n\n test('interval that transcends 2 days', () => {\n expect(\n computeStartOfDaysWithinInterval(\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 18,\n minute: 30,\n }),\n DateTime.fromObject({\n day: 2,\n month: 4,\n year: 2025,\n hour: 18,\n minute: 30,\n }),\n ),\n ).toEqual([\n DateTime.fromObject({\n day: 2,\n month: 4,\n year: 2025,\n hour: 0,\n minute: 0,\n }),\n ] satisfies DateTime[]);\n });\n\n test('interval that transcends more than 2 days', () => {\n expect(\n computeStartOfDaysWithinInterval(\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 18,\n minute: 30,\n }),\n DateTime.fromObject({\n day: 5,\n month: 4,\n year: 2025,\n hour: 4,\n minute: 30,\n }),\n ),\n ).toEqual([\n DateTime.fromObject({\n day: 2,\n month: 4,\n year: 2025,\n hour: 0,\n minute: 0,\n }),\n DateTime.fromObject({\n day: 3,\n month: 4,\n year: 2025,\n hour: 0,\n minute: 0,\n }),\n DateTime.fromObject({\n day: 4,\n month: 4,\n year: 2025,\n hour: 0,\n minute: 0,\n }),\n DateTime.fromObject({\n day: 5,\n month: 4,\n year: 2025,\n hour: 0,\n minute: 0,\n }),\n ] satisfies DateTime[]);\n });\n});\n"]}
@@ -1,40 +0,0 @@
1
- type ResponsesUsageLike = {
2
- input_tokens: number;
3
- output_tokens: number;
4
- total_tokens: number;
5
- input_tokens_details?: {
6
- cached_tokens?: number;
7
- } | null;
8
- } | null | undefined;
9
- type ChatCompletionUsageLike = {
10
- prompt_tokens: number;
11
- completion_tokens: number;
12
- total_tokens: number;
13
- prompt_tokens_details?: {
14
- cached_tokens?: number;
15
- } | null;
16
- } | null | undefined;
17
- export type OpenAITokenUsage = {
18
- inputTokens: number;
19
- cachedInputTokens: number;
20
- outputTokens: number;
21
- totalTokens: number;
22
- };
23
- export type OpenAIModelPricing = {
24
- inputUsdPer1MTokens: number;
25
- cachedInputUsdPer1MTokens: number;
26
- outputUsdPer1MTokens: number;
27
- };
28
- export declare const OPENAI_MODEL_PRICING: Record<string, OpenAIModelPricing>;
29
- export declare function normalizeOpenAIResponsesUsage(usage: ResponsesUsageLike): OpenAITokenUsage | null;
30
- export declare function normalizeOpenAIChatCompletionUsage(usage: ChatCompletionUsageLike): OpenAITokenUsage | null;
31
- export declare function estimateOpenAICostFromUsage({ model, usage, pricingByModel, }: {
32
- model: string;
33
- usage: OpenAITokenUsage | null;
34
- pricingByModel?: Record<string, OpenAIModelPricing>;
35
- }): {
36
- estimatedCostUsd: number;
37
- usage: OpenAITokenUsage;
38
- pricing: OpenAIModelPricing;
39
- } | null;
40
- export {};
@@ -1,55 +0,0 @@
1
- export const OPENAI_MODEL_PRICING = {
2
- 'gpt-5-mini': {
3
- inputUsdPer1MTokens: 0.25,
4
- cachedInputUsdPer1MTokens: 0.025,
5
- outputUsdPer1MTokens: 2,
6
- },
7
- 'gpt-5-nano': {
8
- inputUsdPer1MTokens: 0.05,
9
- cachedInputUsdPer1MTokens: 0.005,
10
- outputUsdPer1MTokens: 0.4,
11
- },
12
- };
13
- export function normalizeOpenAIResponsesUsage(usage) {
14
- if (usage == null) {
15
- return null;
16
- }
17
- return {
18
- inputTokens: usage.input_tokens,
19
- cachedInputTokens: usage.input_tokens_details?.cached_tokens ?? 0,
20
- outputTokens: usage.output_tokens,
21
- totalTokens: usage.total_tokens,
22
- };
23
- }
24
- export function normalizeOpenAIChatCompletionUsage(usage) {
25
- if (usage == null) {
26
- return null;
27
- }
28
- return {
29
- inputTokens: usage.prompt_tokens,
30
- cachedInputTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,
31
- outputTokens: usage.completion_tokens,
32
- totalTokens: usage.total_tokens,
33
- };
34
- }
35
- export function estimateOpenAICostFromUsage({ model, usage, pricingByModel = OPENAI_MODEL_PRICING, }) {
36
- if (usage == null) {
37
- return null;
38
- }
39
- const pricing = pricingByModel[model];
40
- if (pricing == null) {
41
- return null;
42
- }
43
- const cachedInputTokens = Math.max(usage.cachedInputTokens, 0);
44
- const uncachedInputTokens = Math.max(usage.inputTokens - cachedInputTokens, 0);
45
- const outputTokens = Math.max(usage.outputTokens, 0);
46
- const estimatedCostUsd = (uncachedInputTokens / 1_000_000) * pricing.inputUsdPer1MTokens +
47
- (cachedInputTokens / 1_000_000) * pricing.cachedInputUsdPer1MTokens +
48
- (outputTokens / 1_000_000) * pricing.outputUsdPer1MTokens;
49
- return {
50
- estimatedCostUsd,
51
- usage,
52
- pricing,
53
- };
54
- }
55
- //# sourceMappingURL=estimateOpenAICost.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"estimateOpenAICost.js","sourceRoot":"/","sources":["helpers/estimateOpenAICost.ts"],"names":[],"mappings":"AA+BA,MAAM,CAAC,MAAM,oBAAoB,GAAuC;IACtE,YAAY,EAAE;QACZ,mBAAmB,EAAE,IAAI;QACzB,yBAAyB,EAAE,KAAK;QAChC,oBAAoB,EAAE,CAAC;KACxB;IACD,YAAY,EAAE;QACZ,mBAAmB,EAAE,IAAI;QACzB,yBAAyB,EAAE,KAAK;QAChC,oBAAoB,EAAE,GAAG;KAC1B;CACF,CAAC;AAEF,MAAM,UAAU,6BAA6B,CAC3C,KAAyB;IAEzB,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,WAAW,EAAE,KAAK,CAAC,YAAY;QAC/B,iBAAiB,EAAE,KAAK,CAAC,oBAAoB,EAAE,aAAa,IAAI,CAAC;QACjE,YAAY,EAAE,KAAK,CAAC,aAAa;QACjC,WAAW,EAAE,KAAK,CAAC,YAAY;KAChC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kCAAkC,CAChD,KAA8B;IAE9B,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,WAAW,EAAE,KAAK,CAAC,aAAa;QAChC,iBAAiB,EAAE,KAAK,CAAC,qBAAqB,EAAE,aAAa,IAAI,CAAC;QAClE,YAAY,EAAE,KAAK,CAAC,iBAAiB;QACrC,WAAW,EAAE,KAAK,CAAC,YAAY;KAChC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,EAC1C,KAAK,EACL,KAAK,EACL,cAAc,GAAG,oBAAoB,GAKtC;IACC,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC;IAC/D,MAAM,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,GAAG,iBAAiB,EAAE,CAAC,CAAC,CAAC;IAC/E,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IAErD,MAAM,gBAAgB,GACpB,CAAC,mBAAmB,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,mBAAmB;QAC/D,CAAC,iBAAiB,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,yBAAyB;QACnE,CAAC,YAAY,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAE5D,OAAO;QACL,gBAAgB;QAChB,KAAK;QACL,OAAO;KACR,CAAC;AACJ,CAAC","sourcesContent":["type ResponsesUsageLike = {\n input_tokens: number;\n output_tokens: number;\n total_tokens: number;\n input_tokens_details?: {\n cached_tokens?: number;\n } | null;\n} | null | undefined;\n\ntype ChatCompletionUsageLike = {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n prompt_tokens_details?: {\n cached_tokens?: number;\n } | null;\n} | null | undefined;\n\nexport type OpenAITokenUsage = {\n inputTokens: number;\n cachedInputTokens: number;\n outputTokens: number;\n totalTokens: number;\n};\n\nexport type OpenAIModelPricing = {\n inputUsdPer1MTokens: number;\n cachedInputUsdPer1MTokens: number;\n outputUsdPer1MTokens: number;\n};\n\nexport const OPENAI_MODEL_PRICING: Record<string, OpenAIModelPricing> = {\n 'gpt-5-mini': {\n inputUsdPer1MTokens: 0.25,\n cachedInputUsdPer1MTokens: 0.025,\n outputUsdPer1MTokens: 2,\n },\n 'gpt-5-nano': {\n inputUsdPer1MTokens: 0.05,\n cachedInputUsdPer1MTokens: 0.005,\n outputUsdPer1MTokens: 0.4,\n },\n};\n\nexport function normalizeOpenAIResponsesUsage(\n usage: ResponsesUsageLike,\n): OpenAITokenUsage | null {\n if (usage == null) {\n return null;\n }\n\n return {\n inputTokens: usage.input_tokens,\n cachedInputTokens: usage.input_tokens_details?.cached_tokens ?? 0,\n outputTokens: usage.output_tokens,\n totalTokens: usage.total_tokens,\n };\n}\n\nexport function normalizeOpenAIChatCompletionUsage(\n usage: ChatCompletionUsageLike,\n): OpenAITokenUsage | null {\n if (usage == null) {\n return null;\n }\n\n return {\n inputTokens: usage.prompt_tokens,\n cachedInputTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,\n outputTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n };\n}\n\nexport function estimateOpenAICostFromUsage({\n model,\n usage,\n pricingByModel = OPENAI_MODEL_PRICING,\n}: {\n model: string;\n usage: OpenAITokenUsage | null;\n pricingByModel?: Record<string, OpenAIModelPricing>;\n}) {\n if (usage == null) {\n return null;\n }\n\n const pricing = pricingByModel[model];\n if (pricing == null) {\n return null;\n }\n\n const cachedInputTokens = Math.max(usage.cachedInputTokens, 0);\n const uncachedInputTokens = Math.max(usage.inputTokens - cachedInputTokens, 0);\n const outputTokens = Math.max(usage.outputTokens, 0);\n\n const estimatedCostUsd =\n (uncachedInputTokens / 1_000_000) * pricing.inputUsdPer1MTokens +\n (cachedInputTokens / 1_000_000) * pricing.cachedInputUsdPer1MTokens +\n (outputTokens / 1_000_000) * pricing.outputUsdPer1MTokens;\n\n return {\n estimatedCostUsd,\n usage,\n pricing,\n };\n}\n"]}
@@ -1,7 +0,0 @@
1
- import type { AffectedEntity } from '../schema/issue/entity.js';
2
- /**
3
- * Generate a stable key for an affected entity.
4
- * @param target - The affected entity.
5
- * @returns The key.
6
- */
7
- export declare function keyForAffectedEntity(affectedEntity: AffectedEntity): string;
@@ -1,14 +0,0 @@
1
- /**
2
- * Generate a stable key for an affected entity.
3
- * @param target - The affected entity.
4
- * @returns The key.
5
- */
6
- export function keyForAffectedEntity(affectedEntity) {
7
- switch (affectedEntity.type) {
8
- case 'service':
9
- return `service:${affectedEntity.serviceId}`;
10
- case 'facility':
11
- return `facility:${affectedEntity.stationId}:${affectedEntity.kind}`;
12
- }
13
- }
14
- //# sourceMappingURL=keyForAffectedEntity.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"keyForAffectedEntity.js","sourceRoot":"/","sources":["helpers/keyForAffectedEntity.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,cAA8B;IACjE,QAAQ,cAAc,CAAC,IAAI,EAAE,CAAC;QAC5B,KAAK,SAAS;YACZ,OAAO,WAAW,cAAc,CAAC,SAAS,EAAE,CAAC;QAC/C,KAAK,UAAU;YACb,OAAO,YAAY,cAAc,CAAC,SAAS,IAAI,cAAc,CAAC,IAAI,EAAE,CAAC;IACzE,CAAC;AACH,CAAC","sourcesContent":["import type { AffectedEntity } from '../schema/issue/entity.js';\n\n/**\n * Generate a stable key for an affected entity.\n * @param target - The affected entity.\n * @returns The key.\n */\nexport function keyForAffectedEntity(affectedEntity: AffectedEntity): string {\n switch (affectedEntity.type) {\n case 'service':\n return `service:${affectedEntity.serviceId}`;\n case 'facility':\n return `facility:${affectedEntity.stationId}:${affectedEntity.kind}`;\n }\n}\n"]}
@@ -1,7 +0,0 @@
1
- import type { PeriodFixed, PeriodRecurring } from '../schema/issue/period.js';
2
- /**
3
- * Normalize a recurring period into a list of fixed periods.
4
- * @param period - The recurring period to normalize.
5
- * @returns The list of fixed periods.
6
- */
7
- export declare function normalizeRecurringPeriod(period: PeriodRecurring): PeriodFixed[];
@@ -1,118 +0,0 @@
1
- import { DateTime } from 'luxon';
2
- import { DateTime as DateTimeRust, Frequency, RRule, RRuleSet, Weekday, } from 'rrule-rust';
3
- import { assert } from '../util/assert.js';
4
- function toFrequency(frequency) {
5
- switch (frequency) {
6
- case 'daily':
7
- return Frequency.Daily;
8
- case 'weekly':
9
- return Frequency.Weekly;
10
- case 'monthly':
11
- return Frequency.Monthly;
12
- case 'yearly':
13
- return Frequency.Yearly;
14
- default:
15
- throw new Error(`Invalid frequency: ${frequency}`);
16
- }
17
- }
18
- function toDaysOfWeek(daysOfWeek) {
19
- const result = [];
20
- for (const day of daysOfWeek ?? []) {
21
- switch (day) {
22
- case 'MO':
23
- result.push(Weekday.Monday);
24
- break;
25
- case 'TU':
26
- result.push(Weekday.Tuesday);
27
- break;
28
- case 'WE':
29
- result.push(Weekday.Wednesday);
30
- break;
31
- case 'TH':
32
- result.push(Weekday.Thursday);
33
- break;
34
- case 'FR':
35
- result.push(Weekday.Friday);
36
- break;
37
- case 'SA':
38
- result.push(Weekday.Saturday);
39
- break;
40
- case 'SU':
41
- result.push(Weekday.Sunday);
42
- break;
43
- default:
44
- throw new Error(`Invalid day of week: ${day}`);
45
- }
46
- }
47
- return result;
48
- }
49
- function toByTimes(timeWindow) {
50
- const startAtTime = DateTime.fromISO(timeWindow.startAt);
51
- assert(startAtTime.isValid, `Invalid ISO datetime: ${timeWindow.startAt}`);
52
- return {
53
- byHour: [startAtTime.hour],
54
- byMinute: [startAtTime.minute],
55
- bySecond: [startAtTime.second],
56
- };
57
- }
58
- /**
59
- * Normalize a recurring period into a list of fixed periods.
60
- * @param period - The recurring period to normalize.
61
- * @returns The list of fixed periods.
62
- */
63
- export function normalizeRecurringPeriod(period) {
64
- const fixedPeriods = [];
65
- const startAt = DateTime.fromISO(period.startAt).setZone(period.timeZone, {
66
- keepLocalTime: true,
67
- });
68
- assert(startAt.isValid, `Invalid ISO datetime: ${period.startAt}`);
69
- const endAt = DateTime.fromISO(period.endAt).setZone(period.timeZone, {
70
- keepLocalTime: true,
71
- });
72
- assert(endAt.isValid, `Invalid ISO datetime: ${period.endAt}`);
73
- const byTimes = toByTimes(period.timeWindow);
74
- const rruleSet = new RRuleSet({
75
- dtstart: DateTimeRust.fromObject(startAt.toObject()),
76
- tzid: period.timeZone,
77
- rrules: [
78
- new RRule({
79
- until: DateTimeRust.fromObject(endAt.toObject()),
80
- frequency: toFrequency(period.frequency),
81
- interval: 1,
82
- byWeekday: toDaysOfWeek(period.daysOfWeek),
83
- byHour: byTimes.byHour,
84
- byMinute: byTimes.byMinute,
85
- bySecond: byTimes.bySecond,
86
- }),
87
- ],
88
- exdates: period.excludedDates?.map((date) => {
89
- const dateTime = DateTime.fromISO(date).setZone(period.timeZone, {
90
- keepLocalTime: true,
91
- });
92
- assert(dateTime.isValid, `Invalid ISO datetime: ${date}`);
93
- return DateTimeRust.fromObject(dateTime.toObject());
94
- }),
95
- });
96
- const timeWindowEndAt = DateTime.fromISO(period.timeWindow.endAt);
97
- assert(timeWindowEndAt.isValid, `Invalid ISO datetime: ${period.timeWindow.endAt}`);
98
- for (const dt of rruleSet.all()) {
99
- const dtStart = DateTime.fromObject(dt.toObject()).setZone(rruleSet.tzid, {
100
- keepLocalTime: true,
101
- });
102
- assert(dtStart.isValid);
103
- const dtEnd = DateTime.fromObject({
104
- ...dt.toObject(),
105
- hour: timeWindowEndAt.toObject().hour,
106
- minute: timeWindowEndAt.toObject().minute,
107
- second: timeWindowEndAt.toObject().second,
108
- });
109
- assert(dtEnd.isValid);
110
- fixedPeriods.push({
111
- kind: 'fixed',
112
- startAt: dtStart.toISO(),
113
- endAt: dtEnd.toISO(),
114
- });
115
- }
116
- return fixedPeriods;
117
- }
118
- //# sourceMappingURL=normalizeRecurringPeriod.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"normalizeRecurringPeriod.js","sourceRoot":"/","sources":["helpers/normalizeRecurringPeriod.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EACL,QAAQ,IAAI,YAAY,EACxB,SAAS,EAET,KAAK,EACL,QAAQ,EACR,OAAO,GACR,MAAM,YAAY,CAAC;AAMpB,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,SAAS,WAAW,CAAC,SAA0B;IAC7C,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC,KAAK,CAAC;QACzB,KAAK,QAAQ;YACX,OAAO,SAAS,CAAC,MAAM,CAAC;QAC1B,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC,OAAO,CAAC;QAC3B,KAAK,QAAQ;YACX,OAAO,SAAS,CAAC,MAAM,CAAC;QAC1B;YACE,MAAM,IAAI,KAAK,CAAC,sBAAsB,SAAS,EAAE,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CACnB,UAAyC;IAEzC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAE1C,KAAK,MAAM,GAAG,IAAI,UAAU,IAAI,EAAE,EAAE,CAAC;QACnC,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,IAAI;gBACP,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;gBAC5B,MAAM;YACR,KAAK,IAAI;gBACP,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBAC7B,MAAM;YACR,KAAK,IAAI;gBACP,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBAC/B,MAAM;YACR,KAAK,IAAI;gBACP,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAC9B,MAAM;YACR,KAAK,IAAI;gBACP,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;gBAC5B,MAAM;YACR,KAAK,IAAI;gBACP,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAC9B,MAAM;YACR,KAAK,IAAI;gBACP,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;gBAC5B,MAAM;YACR;gBACE,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,SAAS,CAAC,UAAyC;IAK1D,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IACzD,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,yBAAyB,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;IAE3E,OAAO;QACL,MAAM,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC;QAC1B,QAAQ,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC;QAC9B,QAAQ,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC;KAC/B,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAuB;IAEvB,MAAM,YAAY,GAAkB,EAAE,CAAC;IAEvC,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE;QACxE,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,yBAAyB,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE;QACpE,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,yBAAyB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IAE/D,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAE7C,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC;QAC5B,OAAO,EAAE,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACpD,IAAI,EAAE,MAAM,CAAC,QAAQ;QACrB,MAAM,EAAE;YACN,IAAI,KAAK,CAAC;gBACR,KAAK,EAAE,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAChD,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC;gBACxC,QAAQ,EAAE,CAAC;gBACX,SAAS,EAAE,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC;gBAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;aAC3B,CAAC;SACH;QACD,OAAO,EAAE,MAAM,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YAC1C,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE;gBAC/D,aAAa,EAAE,IAAI;aACpB,CAAC,CAAC;YACH,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,yBAAyB,IAAI,EAAE,CAAC,CAAC;YAC1D,OAAO,YAAY,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QACtD,CAAC,CAAC;KACH,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAClE,MAAM,CACJ,eAAe,CAAC,OAAO,EACvB,yBAAyB,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,CACnD,CAAC;IAEF,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE;YACxE,aAAa,EAAE,IAAI;SACpB,CAAC,CAAC;QACH,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACxB,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC;YAChC,GAAG,EAAE,CAAC,QAAQ,EAAE;YAChB,IAAI,EAAE,eAAe,CAAC,QAAQ,EAAE,CAAC,IAAI;YACrC,MAAM,EAAE,eAAe,CAAC,QAAQ,EAAE,CAAC,MAAM;YACzC,MAAM,EAAE,eAAe,CAAC,QAAQ,EAAE,CAAC,MAAM;SAC1C,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtB,YAAY,CAAC,IAAI,CAAC;YAChB,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE;YACxB,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE;SACrB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC","sourcesContent":["import { DateTime } from 'luxon';\nimport {\n DateTime as DateTimeRust,\n Frequency,\n type NWeekday,\n RRule,\n RRuleSet,\n Weekday,\n} from 'rrule-rust';\nimport type {\n PeriodFixed,\n PeriodFrequency,\n PeriodRecurring,\n} from '../schema/issue/period.js';\nimport { assert } from '../util/assert.js';\n\nfunction toFrequency(frequency: PeriodFrequency): Frequency {\n switch (frequency) {\n case 'daily':\n return Frequency.Daily;\n case 'weekly':\n return Frequency.Weekly;\n case 'monthly':\n return Frequency.Monthly;\n case 'yearly':\n return Frequency.Yearly;\n default:\n throw new Error(`Invalid frequency: ${frequency}`);\n }\n}\n\nfunction toDaysOfWeek(\n daysOfWeek: PeriodRecurring['daysOfWeek'],\n): readonly (NWeekday | Weekday)[] {\n const result: (NWeekday | Weekday)[] = [];\n\n for (const day of daysOfWeek ?? []) {\n switch (day) {\n case 'MO':\n result.push(Weekday.Monday);\n break;\n case 'TU':\n result.push(Weekday.Tuesday);\n break;\n case 'WE':\n result.push(Weekday.Wednesday);\n break;\n case 'TH':\n result.push(Weekday.Thursday);\n break;\n case 'FR':\n result.push(Weekday.Friday);\n break;\n case 'SA':\n result.push(Weekday.Saturday);\n break;\n case 'SU':\n result.push(Weekday.Sunday);\n break;\n default:\n throw new Error(`Invalid day of week: ${day}`);\n }\n }\n\n return result;\n}\n\nfunction toByTimes(timeWindow: PeriodRecurring['timeWindow']): {\n byHour: number[];\n byMinute: number[];\n bySecond: number[];\n} {\n const startAtTime = DateTime.fromISO(timeWindow.startAt);\n assert(startAtTime.isValid, `Invalid ISO datetime: ${timeWindow.startAt}`);\n\n return {\n byHour: [startAtTime.hour],\n byMinute: [startAtTime.minute],\n bySecond: [startAtTime.second],\n };\n}\n\n/**\n * Normalize a recurring period into a list of fixed periods.\n * @param period - The recurring period to normalize.\n * @returns The list of fixed periods.\n */\nexport function normalizeRecurringPeriod(\n period: PeriodRecurring,\n): PeriodFixed[] {\n const fixedPeriods: PeriodFixed[] = [];\n\n const startAt = DateTime.fromISO(period.startAt).setZone(period.timeZone, {\n keepLocalTime: true,\n });\n assert(startAt.isValid, `Invalid ISO datetime: ${period.startAt}`);\n const endAt = DateTime.fromISO(period.endAt).setZone(period.timeZone, {\n keepLocalTime: true,\n });\n assert(endAt.isValid, `Invalid ISO datetime: ${period.endAt}`);\n\n const byTimes = toByTimes(period.timeWindow);\n\n const rruleSet = new RRuleSet({\n dtstart: DateTimeRust.fromObject(startAt.toObject()),\n tzid: period.timeZone,\n rrules: [\n new RRule({\n until: DateTimeRust.fromObject(endAt.toObject()),\n frequency: toFrequency(period.frequency),\n interval: 1,\n byWeekday: toDaysOfWeek(period.daysOfWeek),\n byHour: byTimes.byHour,\n byMinute: byTimes.byMinute,\n bySecond: byTimes.bySecond,\n }),\n ],\n exdates: period.excludedDates?.map((date) => {\n const dateTime = DateTime.fromISO(date).setZone(period.timeZone, {\n keepLocalTime: true,\n });\n assert(dateTime.isValid, `Invalid ISO datetime: ${date}`);\n return DateTimeRust.fromObject(dateTime.toObject());\n }),\n });\n\n const timeWindowEndAt = DateTime.fromISO(period.timeWindow.endAt);\n assert(\n timeWindowEndAt.isValid,\n `Invalid ISO datetime: ${period.timeWindow.endAt}`,\n );\n\n for (const dt of rruleSet.all()) {\n const dtStart = DateTime.fromObject(dt.toObject()).setZone(rruleSet.tzid, {\n keepLocalTime: true,\n });\n assert(dtStart.isValid);\n const dtEnd = DateTime.fromObject({\n ...dt.toObject(),\n hour: timeWindowEndAt.toObject().hour,\n minute: timeWindowEndAt.toObject().minute,\n second: timeWindowEndAt.toObject().second,\n });\n assert(dtEnd.isValid);\n fixedPeriods.push({\n kind: 'fixed',\n startAt: dtStart.toISO(),\n endAt: dtEnd.toISO(),\n });\n }\n\n return fixedPeriods;\n}\n"]}
@@ -1 +0,0 @@
1
- export {};
@@ -1,93 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { normalizeRecurringPeriod } from './normalizeRecurringPeriod.js';
3
- function makeRecurringPeriod(overrides = {}) {
4
- return {
5
- kind: 'recurring',
6
- frequency: 'daily',
7
- startAt: '2025-01-01T00:00:00+08:00',
8
- endAt: '2025-01-03T23:59:59+08:00',
9
- daysOfWeek: null,
10
- timeWindow: {
11
- startAt: '08:00:00',
12
- endAt: '10:00:00',
13
- },
14
- timeZone: 'Asia/Singapore',
15
- excludedDates: null,
16
- ...overrides,
17
- };
18
- }
19
- describe('normalizeRecurringPeriod', () => {
20
- test('normalizes a bounded daily recurring period into fixed periods', () => {
21
- const period = makeRecurringPeriod();
22
- const fixed = normalizeRecurringPeriod(period);
23
- expect(fixed).toHaveLength(3);
24
- expect(fixed).toEqual([
25
- {
26
- kind: 'fixed',
27
- startAt: '2025-01-01T08:00:00.000+08:00',
28
- endAt: '2025-01-01T10:00:00.000+08:00',
29
- },
30
- {
31
- kind: 'fixed',
32
- startAt: '2025-01-02T08:00:00.000+08:00',
33
- endAt: '2025-01-02T10:00:00.000+08:00',
34
- },
35
- {
36
- kind: 'fixed',
37
- startAt: '2025-01-03T08:00:00.000+08:00',
38
- endAt: '2025-01-03T10:00:00.000+08:00',
39
- },
40
- ]);
41
- });
42
- test('filters recurrence by configured daysOfWeek', () => {
43
- const period = makeRecurringPeriod({
44
- startAt: '2025-01-01T00:00:00+08:00', // Wednesday
45
- endAt: '2025-01-10T23:59:59+08:00',
46
- frequency: 'weekly',
47
- daysOfWeek: ['MO', 'WE', 'FR'],
48
- });
49
- const fixed = normalizeRecurringPeriod(period);
50
- expect(fixed.map((item) => item.startAt)).toEqual([
51
- '2025-01-01T08:00:00.000+08:00',
52
- '2025-01-03T08:00:00.000+08:00',
53
- '2025-01-06T08:00:00.000+08:00',
54
- '2025-01-08T08:00:00.000+08:00',
55
- '2025-01-10T08:00:00.000+08:00',
56
- ]);
57
- });
58
- test('excludes specific dates from recurrence', () => {
59
- const period = makeRecurringPeriod({
60
- timeWindow: {
61
- startAt: '00:00:00',
62
- endAt: '01:00:00',
63
- },
64
- excludedDates: ['2025-01-02'],
65
- });
66
- const fixed = normalizeRecurringPeriod(period);
67
- expect(fixed).toEqual([
68
- {
69
- kind: 'fixed',
70
- startAt: '2025-01-01T00:00:00.000+08:00',
71
- endAt: '2025-01-01T01:00:00.000+08:00',
72
- },
73
- {
74
- kind: 'fixed',
75
- startAt: '2025-01-03T00:00:00.000+08:00',
76
- endAt: '2025-01-03T01:00:00.000+08:00',
77
- },
78
- ]);
79
- });
80
- test('returns start and end timestamps in Singapore timezone', () => {
81
- const period = makeRecurringPeriod({
82
- startAt: '2025-01-01T00:00:00Z',
83
- endAt: '2025-01-02T23:59:59Z',
84
- });
85
- const fixed = normalizeRecurringPeriod(period);
86
- expect(fixed).toHaveLength(2);
87
- for (const item of fixed) {
88
- expect(item.startAt.endsWith('+08:00')).toBe(true);
89
- expect(item.endAt?.endsWith('+08:00')).toBe(true);
90
- }
91
- });
92
- });
93
- //# sourceMappingURL=normalizeRecurringPeriod.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"normalizeRecurringPeriod.test.js","sourceRoot":"/","sources":["helpers/normalizeRecurringPeriod.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, test } from 'vitest';\nimport type { PeriodRecurring } from '../schema/issue/period.js';\nimport { normalizeRecurringPeriod } from './normalizeRecurringPeriod.js';\n\nfunction makeRecurringPeriod(\n overrides: Partial<PeriodRecurring> = {},\n): PeriodRecurring {\n return {\n kind: 'recurring',\n frequency: 'daily',\n startAt: '2025-01-01T00:00:00+08:00',\n endAt: '2025-01-03T23:59:59+08:00',\n daysOfWeek: null,\n timeWindow: {\n startAt: '08:00:00',\n endAt: '10:00:00',\n },\n timeZone: 'Asia/Singapore',\n excludedDates: null,\n ...overrides,\n };\n}\n\ndescribe('normalizeRecurringPeriod', () => {\n test('normalizes a bounded daily recurring period into fixed periods', () => {\n const period = makeRecurringPeriod();\n\n const fixed = normalizeRecurringPeriod(period);\n\n expect(fixed).toHaveLength(3);\n expect(fixed).toEqual([\n {\n kind: 'fixed',\n startAt: '2025-01-01T08:00:00.000+08:00',\n endAt: '2025-01-01T10:00:00.000+08:00',\n },\n {\n kind: 'fixed',\n startAt: '2025-01-02T08:00:00.000+08:00',\n endAt: '2025-01-02T10:00:00.000+08:00',\n },\n {\n kind: 'fixed',\n startAt: '2025-01-03T08:00:00.000+08:00',\n endAt: '2025-01-03T10:00:00.000+08:00',\n },\n ]);\n });\n\n test('filters recurrence by configured daysOfWeek', () => {\n const period = makeRecurringPeriod({\n startAt: '2025-01-01T00:00:00+08:00', // Wednesday\n endAt: '2025-01-10T23:59:59+08:00',\n frequency: 'weekly',\n daysOfWeek: ['MO', 'WE', 'FR'],\n });\n\n const fixed = normalizeRecurringPeriod(period);\n\n expect(fixed.map((item) => item.startAt)).toEqual([\n '2025-01-01T08:00:00.000+08:00',\n '2025-01-03T08:00:00.000+08:00',\n '2025-01-06T08:00:00.000+08:00',\n '2025-01-08T08:00:00.000+08:00',\n '2025-01-10T08:00:00.000+08:00',\n ]);\n });\n\n test('excludes specific dates from recurrence', () => {\n const period = makeRecurringPeriod({\n timeWindow: {\n startAt: '00:00:00',\n endAt: '01:00:00',\n },\n excludedDates: ['2025-01-02'],\n });\n\n const fixed = normalizeRecurringPeriod(period);\n\n expect(fixed).toEqual([\n {\n kind: 'fixed',\n startAt: '2025-01-01T00:00:00.000+08:00',\n endAt: '2025-01-01T01:00:00.000+08:00',\n },\n {\n kind: 'fixed',\n startAt: '2025-01-03T00:00:00.000+08:00',\n endAt: '2025-01-03T01:00:00.000+08:00',\n },\n ]);\n });\n\n test('returns start and end timestamps in Singapore timezone', () => {\n const period = makeRecurringPeriod({\n startAt: '2025-01-01T00:00:00Z',\n endAt: '2025-01-02T23:59:59Z',\n });\n\n const fixed = normalizeRecurringPeriod(period);\n\n expect(fixed).toHaveLength(2);\n for (const item of fixed) {\n expect(item.startAt.endsWith('+08:00')).toBe(true);\n expect(item.endAt?.endsWith('+08:00')).toBe(true);\n }\n });\n});\n"]}