@happyvertical/smrt-timesheets 0.53.1 → 0.53.2

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 (46) hide show
  1. package/AGENTS.md +32 -21
  2. package/README.md +124 -58
  3. package/agents/period-rollup.md +130 -0
  4. package/dist/attendance/models.d.ts +59 -0
  5. package/dist/attendance/models.d.ts.map +1 -0
  6. package/dist/attendance/service.d.ts +64 -0
  7. package/dist/attendance/service.d.ts.map +1 -0
  8. package/dist/attendance.d.ts +5 -0
  9. package/dist/attendance.d.ts.map +1 -0
  10. package/dist/attendance.js +323 -0
  11. package/dist/attendance.js.map +1 -0
  12. package/dist/chunks/attendance-Bk8o96dt.js +60 -0
  13. package/dist/chunks/attendance-Bk8o96dt.js.map +1 -0
  14. package/dist/chunks/models-D9n8HKXH.js +166 -0
  15. package/dist/chunks/models-D9n8HKXH.js.map +1 -0
  16. package/dist/chunks/rollup-yE0TFLJe.js +425 -0
  17. package/dist/chunks/rollup-yE0TFLJe.js.map +1 -0
  18. package/dist/chunks/service-time-entry-B2gHrJYI.js +190 -0
  19. package/dist/chunks/service-time-entry-B2gHrJYI.js.map +1 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +25 -205
  23. package/dist/index.js.map +1 -1
  24. package/dist/manifest.json +2553 -652
  25. package/dist/rollup/attendance.d.ts +8 -0
  26. package/dist/rollup/attendance.d.ts.map +1 -0
  27. package/dist/rollup/calculation.d.ts +7 -0
  28. package/dist/rollup/calculation.d.ts.map +1 -0
  29. package/dist/rollup/models.d.ts +47 -0
  30. package/dist/rollup/models.d.ts.map +1 -0
  31. package/dist/rollup/service.d.ts +37 -0
  32. package/dist/rollup/service.d.ts.map +1 -0
  33. package/dist/rollup/types.d.ts +38 -0
  34. package/dist/rollup/types.d.ts.map +1 -0
  35. package/dist/rollup/write.d.ts +4 -0
  36. package/dist/rollup/write.d.ts.map +1 -0
  37. package/dist/rollup.d.ts +5 -0
  38. package/dist/rollup.d.ts.map +1 -0
  39. package/dist/rollup.js +2 -0
  40. package/dist/smrt-knowledge.json +1319 -433
  41. package/package.json +19 -10
  42. package/dist/svelte/components/__tests__/DurationDisplay.test.js +0 -57
  43. package/dist/svelte/components/__tests__/TimeEntryApprovalQueue.test.js +0 -114
  44. package/dist/svelte/components/__tests__/TimeEntryCard.test.js +0 -45
  45. package/dist/svelte/components/__tests__/TimeEntryList.test.js +0 -50
  46. package/dist/svelte/components/__tests__/TimeSummary.test.js +0 -39
@@ -0,0 +1,425 @@
1
+ import { r as ServiceTimeEntryCollection } from "./service-time-entry-B2gHrJYI.js";
2
+ import { SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, isEmbeddedDatabase, isPostgresDatabase, smrt, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
3
+ import { TenantScoped, getTenantId, tenantId, withTenant } from "@happyvertical/smrt-tenancy";
4
+ import { createHash } from "node:crypto";
5
+ //#region src/rollup/write.ts
6
+ var permitted = /* @__PURE__ */ new WeakSet();
7
+ function assertRollupWrite(object) {
8
+ if (!permitted.has(object)) throw new Error("Timecards and adjustments are writable only through PeriodRollupService.");
9
+ }
10
+ async function persistRollup(object) {
11
+ permitted.add(object);
12
+ try {
13
+ await object.save();
14
+ return object;
15
+ } finally {
16
+ permitted.delete(object);
17
+ }
18
+ }
19
+ //#endregion
20
+ //#region src/rollup/models.ts
21
+ var __defProp = Object.defineProperty;
22
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
23
+ var __decorateClass = (decorators, target, key, kind) => {
24
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
25
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
26
+ if (kind && result) __defProp(target, key, result);
27
+ return result;
28
+ };
29
+ var PeriodTimecard = class extends SmrtObject {
30
+ tenantId = "";
31
+ profileId = "";
32
+ startsAt = /* @__PURE__ */ new Date(0);
33
+ endsAt = /* @__PURE__ */ new Date(0);
34
+ timezone = "";
35
+ rulesVersion = "";
36
+ rulesSnapshot = "{}";
37
+ sourceSnapshot = "[]";
38
+ regularSeconds = 0;
39
+ overtimeSeconds = 0;
40
+ totalSeconds = 0;
41
+ status = "open";
42
+ confirmedAt = null;
43
+ confirmedByProfileId = null;
44
+ /** Return a detached source snapshot; corrupt persisted JSON fails closed. */
45
+ getSources() {
46
+ const result = JSON.parse(this.sourceSnapshot);
47
+ if (!Array.isArray(result)) throw new Error("Invalid timecard source snapshot.");
48
+ return result;
49
+ }
50
+ /** Return the consumer policy frozen with this rollup. */
51
+ getRules() {
52
+ const result = JSON.parse(this.rulesSnapshot);
53
+ if (!result || typeof result !== "object" || Array.isArray(result)) throw new Error("Invalid timecard rules snapshot.");
54
+ return result;
55
+ }
56
+ async save() {
57
+ assertRollupWrite(this);
58
+ const prior = this.id ? await this.db.get(this.tableName, { id: this.id }) : null;
59
+ if (prior?.status === "confirmed") throw new Error("Confirmed timecards are immutable; append an adjustment.");
60
+ if (prior && (prior.tenant_id !== this.tenantId || prior.profile_id !== this.profileId || new Date(prior.starts_at).getTime() !== this.startsAt.getTime())) throw new Error("Timecard identity is immutable.");
61
+ return await super.save();
62
+ }
63
+ async delete() {
64
+ throw new Error("Timecard history cannot be deleted.");
65
+ }
66
+ };
67
+ __decorateClass([tenantId()], PeriodTimecard.prototype, "tenantId", 2);
68
+ __decorateClass([crossPackageRef("@happyvertical/smrt-profiles:Profile", { onDelete: "RESTRICT" })], PeriodTimecard.prototype, "profileId", 2);
69
+ __decorateClass([field({ type: "text" })], PeriodTimecard.prototype, "rulesSnapshot", 2);
70
+ __decorateClass([field({ type: "text" })], PeriodTimecard.prototype, "sourceSnapshot", 2);
71
+ __decorateClass([field({ type: "text" })], PeriodTimecard.prototype, "status", 2);
72
+ __decorateClass([crossPackageRef("@happyvertical/smrt-profiles:Profile", {
73
+ nullable: true,
74
+ onDelete: "RESTRICT"
75
+ })], PeriodTimecard.prototype, "confirmedByProfileId", 2);
76
+ PeriodTimecard = __decorateClass([TenantScoped({ mode: "required" }), smrt({
77
+ tableName: "period_timecards",
78
+ sensitive: true,
79
+ api: { include: [] },
80
+ cli: { include: [] },
81
+ mcp: { include: [] }
82
+ })], PeriodTimecard);
83
+ var TimecardAdjustment = class extends SmrtObject {
84
+ tenantId = "";
85
+ timecardId = "";
86
+ profileId = "";
87
+ actorProfileId = "";
88
+ operationId = "";
89
+ regularSeconds = 0;
90
+ overtimeSeconds = 0;
91
+ reason = "";
92
+ async save() {
93
+ assertRollupWrite(this);
94
+ if (this.id && await this.db.get(this.tableName, { id: this.id })) throw new Error("Timecard adjustments are append-only.");
95
+ this.requireInsertOnSave();
96
+ return await super.save();
97
+ }
98
+ async delete() {
99
+ throw new Error("Timecard adjustments cannot be deleted.");
100
+ }
101
+ };
102
+ __decorateClass([tenantId()], TimecardAdjustment.prototype, "tenantId", 2);
103
+ __decorateClass([foreignKey(PeriodTimecard, { onDelete: "RESTRICT" })], TimecardAdjustment.prototype, "timecardId", 2);
104
+ __decorateClass([crossPackageRef("@happyvertical/smrt-profiles:Profile", { onDelete: "RESTRICT" })], TimecardAdjustment.prototype, "profileId", 2);
105
+ __decorateClass([crossPackageRef("@happyvertical/smrt-profiles:Profile", { onDelete: "RESTRICT" })], TimecardAdjustment.prototype, "actorProfileId", 2);
106
+ TimecardAdjustment = __decorateClass([TenantScoped({ mode: "required" }), smrt({
107
+ tableName: "timecard_adjustments",
108
+ sensitive: true,
109
+ api: { include: [] },
110
+ cli: { include: [] },
111
+ mcp: { include: [] }
112
+ })], TimecardAdjustment);
113
+ var PeriodTimecardCollection = class extends SmrtCollection {
114
+ static _itemClass = PeriodTimecard;
115
+ };
116
+ var TimecardAdjustmentCollection = class extends SmrtCollection {
117
+ static _itemClass = TimecardAdjustment;
118
+ };
119
+ //#endregion
120
+ //#region src/rollup/calculation.ts
121
+ function integer(value, signed = false) {
122
+ if (!Number.isSafeInteger(value) || !signed && value < 0) throw new Error("Timecard seconds must be safe integers and nonnegative.");
123
+ return value;
124
+ }
125
+ function instant(value) {
126
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) throw new Error("Timecard timestamps must be valid dates.");
127
+ return value.getTime();
128
+ }
129
+ function validatePeriod(value, at) {
130
+ if (!value || instant(value.startsAt) > instant(at) || instant(value.endsAt) <= instant(at) || instant(value.startsAt) >= instant(value.endsAt)) throw new Error("Resolver period must contain the requested instant.");
131
+ if (!value.timezone || !value.version?.trim()) throw new Error("Resolver must supply a timezone and policy version.");
132
+ new Intl.DateTimeFormat("en", { timeZone: value.timezone });
133
+ if (!value.rules || typeof value.rules !== "object" || Array.isArray(value.rules)) throw new Error("Resolver rules must be a JSON object.");
134
+ const json = JSON.stringify(value.rules, (_key, item) => {
135
+ if (typeof item === "function" || typeof item === "undefined" || typeof item === "symbol" || typeof item === "number" && !Number.isFinite(item)) throw new Error("Resolver rules must be JSON serializable.");
136
+ return item;
137
+ });
138
+ const rules = JSON.parse(json);
139
+ if (!rules || typeof rules !== "object" || Array.isArray(rules)) throw new Error("Resolver rules must serialize to a JSON object.");
140
+ return {
141
+ startsAt: new Date(value.startsAt),
142
+ endsAt: new Date(value.endsAt),
143
+ timezone: value.timezone,
144
+ version: value.version,
145
+ rules
146
+ };
147
+ }
148
+ function clipSource(source, period) {
149
+ const start = instant(new Date(source.startsAt));
150
+ const end = instant(new Date(source.endsAt));
151
+ integer(source.seconds);
152
+ if (end < start) throw new Error("Invalid source interval.");
153
+ const left = Math.max(start, period.startsAt.getTime());
154
+ const right = Math.min(end, period.endsAt.getTime());
155
+ if (start === end) return start >= period.startsAt.getTime() && start < period.endsAt.getTime() ? { ...source } : null;
156
+ if (left >= right) return null;
157
+ const span = integer(end - start);
158
+ const prefix = (elapsed) => Number(BigInt(source.seconds) * BigInt(integer(elapsed)) / BigInt(span));
159
+ const seconds = prefix(right - start) - prefix(left - start);
160
+ return {
161
+ ...source,
162
+ startsAt: new Date(left).toISOString(),
163
+ endsAt: new Date(right).toISOString(),
164
+ seconds: integer(seconds)
165
+ };
166
+ }
167
+ //#endregion
168
+ //#region src/rollup/service.ts
169
+ function identity(parts) {
170
+ const bytes = createHash("sha256").update(JSON.stringify(parts)).digest();
171
+ bytes[6] = bytes[6] & 15 | 80;
172
+ bytes[8] = bytes[8] & 63 | 128;
173
+ const hex = bytes.subarray(0, 16).toString("hex");
174
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
175
+ }
176
+ function adjustedSeconds(base, rows, field) {
177
+ const total = rows.reduce((sum, row) => sum + BigInt(integer(row[field], true)), BigInt(integer(base)));
178
+ return integer(Number(total));
179
+ }
180
+ var PeriodRollupService = class {
181
+ constructor(db, actor, resolver, options = {}) {
182
+ this.db = db;
183
+ this.actor = actor;
184
+ this.resolver = resolver;
185
+ this.options = options;
186
+ if (!actor.tenantId?.trim() || !actor.profileId?.trim()) throw new Error("Timecards require a trusted tenant/profile actor.");
187
+ this.actor = Object.freeze({
188
+ tenantId: this.identity(actor.tenantId),
189
+ profileId: this.identity(actor.profileId)
190
+ });
191
+ if (options.actorProfileId !== void 0 && !options.actorProfileId.trim()) throw new Error("Actor attribution must be a nonempty profile id.");
192
+ this.options = {
193
+ attendance: options.attendance,
194
+ actorProfileId: this.identity(options.actorProfileId ?? this.actor.profileId)
195
+ };
196
+ }
197
+ db;
198
+ actor;
199
+ resolver;
200
+ options;
201
+ identity(value) {
202
+ if (!isPostgresDatabase(this.db)) return value;
203
+ const hex = value.replace(/[{}-]/g, "").toLowerCase();
204
+ if (!/^[0-9a-f]{32}$/.test(hex)) throw new Error("PostgreSQL timecard identities must be UUIDs.");
205
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
206
+ }
207
+ scope(run) {
208
+ const active = getTenantId();
209
+ if (active && this.identity(active) !== this.actor.tenantId) throw new Error("Timecard actor differs from active tenant.");
210
+ return withTenant({ tenantId: this.actor.tenantId }, run);
211
+ }
212
+ transact(run) {
213
+ return this.scope(() => withEmbeddedWriteTransaction(this.db, isEmbeddedDatabase(this.db), async (db) => {
214
+ if (isPostgresDatabase(db)) await db.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", JSON.stringify([
215
+ "smrt.rollup",
216
+ this.actor.tenantId,
217
+ this.actor.profileId
218
+ ]));
219
+ if (this.options.attendance && isPostgresDatabase(db)) await db.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", JSON.stringify([
220
+ "smrt.attendance",
221
+ this.actor.tenantId,
222
+ this.actor.profileId
223
+ ]));
224
+ return run(db);
225
+ }, true));
226
+ }
227
+ async card(db, id) {
228
+ const [card] = await (await PeriodTimecardCollection.create({ db })).list({
229
+ where: {
230
+ ...this.actor,
231
+ id
232
+ },
233
+ limit: 1
234
+ });
235
+ if (!card) throw new Error("Timecard not found in actor scope.");
236
+ return card;
237
+ }
238
+ /** Scoped read on the root executor, including after transaction completion. */
239
+ async get(id) {
240
+ return this.scope(() => this.card(this.db, id));
241
+ }
242
+ /** Rebuild one open card from a consistent transaction-bound source snapshot. */
243
+ async rollup(at) {
244
+ at = new Date(instant(at));
245
+ const id = await this.transact(async (db) => {
246
+ const period = validatePeriod(await this.resolver.periodFor(new Date(at), this.actor), at);
247
+ const id2 = identity([
248
+ "smrt.period",
249
+ this.actor.tenantId,
250
+ this.actor.profileId,
251
+ period.startsAt.toISOString()
252
+ ]);
253
+ const collection = await PeriodTimecardCollection.create({ db });
254
+ const [prior] = await collection.list({
255
+ where: {
256
+ ...this.actor,
257
+ id: id2
258
+ },
259
+ limit: 1
260
+ });
261
+ if (prior?.status === "confirmed") throw new Error("Confirmed timecards are immutable; append an adjustment.");
262
+ for (const other of await collection.list({ where: { ...this.actor } })) if (other.id !== id2 && other.startsAt < period.endsAt && other.endsAt > period.startsAt) throw new Error("Resolver period overlaps an existing timecard.");
263
+ let sources = [];
264
+ let linkedEntryIds = /* @__PURE__ */ new Set();
265
+ if (this.options.attendance) {
266
+ const { attendanceSources } = await import("./attendance-Bk8o96dt.js");
267
+ ({sources, linkedEntryIds} = await attendanceSources(db, this.actor, period));
268
+ }
269
+ const evidence = await (await ServiceTimeEntryCollection.create({ db })).list({ where: {
270
+ tenantId: this.actor.tenantId,
271
+ participantProfileId: this.actor.profileId,
272
+ participantKind: "human"
273
+ } });
274
+ const byId = new Map(evidence.map((entry) => [String(entry.id), entry]));
275
+ const approvedRoots = /* @__PURE__ */ new Set();
276
+ for (const entry of evidence) {
277
+ if (entry.status !== "approved") continue;
278
+ let linked = linkedEntryIds.has(String(entry.id));
279
+ let ancestor = entry;
280
+ const seen = /* @__PURE__ */ new Set([String(entry.id)]);
281
+ while (ancestor.correctionOfId) {
282
+ const parentId = ancestor.correctionOfId;
283
+ if (seen.has(parentId)) throw new Error("Cyclic evidence correction chain.");
284
+ seen.add(parentId);
285
+ const parent = byId.get(parentId);
286
+ if (!parent || parent.status !== "corrected") throw new Error("Correction requires corrected evidence in the same actor scope.");
287
+ linked ||= linkedEntryIds.has(parentId);
288
+ ancestor = parent;
289
+ }
290
+ const rootId = String(ancestor.id);
291
+ if (approvedRoots.has(rootId)) throw new Error("Ambiguous approved evidence correction chain.");
292
+ approvedRoots.add(rootId);
293
+ if (linked) continue;
294
+ const start = entry.startedAt ?? entry.approvedAt;
295
+ if (!start) throw new Error("Approved evidence needs a start or approval timestamp.");
296
+ const end = entry.startedAt && entry.endedAt ? entry.endedAt : start;
297
+ const source = clipSource({
298
+ kind: "entry",
299
+ id: String(entry.id),
300
+ startsAt: new Date(instant(start)).toISOString(),
301
+ endsAt: new Date(instant(end)).toISOString(),
302
+ seconds: integer(entry.durationSeconds)
303
+ }, period);
304
+ if (source?.seconds) sources.push(source);
305
+ }
306
+ sources.sort((a, b) => a.startsAt.localeCompare(b.startsAt) || a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id));
307
+ const totalSeconds = sources.reduce((sum, source) => integer(sum + source.seconds), 0);
308
+ const classified = await this.resolver.classify(structuredClone(period), structuredClone(sources), this.actor);
309
+ const regularSeconds = integer(classified?.regularSeconds);
310
+ const overtimeSeconds = integer(classified?.overtimeSeconds);
311
+ if (integer(regularSeconds + overtimeSeconds) !== totalSeconds) throw new Error("Resolver classification must conserve total seconds.");
312
+ const card = prior ?? await new PeriodTimecard({
313
+ db,
314
+ id: id2,
315
+ _skipLoad: true
316
+ }).initialize();
317
+ if (!prior) card.requireInsertOnSave();
318
+ Object.assign(card, this.actor, {
319
+ startsAt: period.startsAt,
320
+ endsAt: period.endsAt,
321
+ timezone: period.timezone,
322
+ rulesVersion: period.version,
323
+ rulesSnapshot: JSON.stringify(period.rules),
324
+ sourceSnapshot: JSON.stringify(sources),
325
+ regularSeconds,
326
+ overtimeSeconds,
327
+ totalSeconds
328
+ });
329
+ await persistRollup(card);
330
+ return id2;
331
+ });
332
+ return this.get(id);
333
+ }
334
+ /** Confirm the current snapshot; repeated confirmation is idempotent. */
335
+ async confirm(id) {
336
+ await this.transact(async (db) => {
337
+ const card = await this.card(db, id);
338
+ if (card.status === "confirmed") return;
339
+ card.status = "confirmed";
340
+ card.confirmedAt = /* @__PURE__ */ new Date();
341
+ card.confirmedByProfileId = this.options.actorProfileId;
342
+ await persistRollup(card);
343
+ });
344
+ return this.get(id);
345
+ }
346
+ /** Append a correction. An operation key retry must have exactly the same payload. */
347
+ async adjust(id, input) {
348
+ id = this.identity(id);
349
+ input = {
350
+ operationId: input.operationId,
351
+ reason: input.reason,
352
+ regularSeconds: input.regularSeconds,
353
+ overtimeSeconds: input.overtimeSeconds
354
+ };
355
+ integer(input.regularSeconds, true);
356
+ integer(input.overtimeSeconds, true);
357
+ if (!input.operationId?.trim() || !input.reason?.trim()) throw new Error("Adjustment requires an operationId and reason.");
358
+ const adjustmentId = identity([
359
+ "smrt.adjustment",
360
+ this.actor.tenantId,
361
+ this.actor.profileId,
362
+ id,
363
+ input.operationId
364
+ ]);
365
+ await this.transact(async (db) => {
366
+ const card = await this.card(db, id);
367
+ if (card.status !== "confirmed") throw new Error("Adjustments require a confirmed timecard.");
368
+ const rows = await (await TimecardAdjustmentCollection.create({ db })).list({ where: {
369
+ ...this.actor,
370
+ timecardId: id
371
+ } });
372
+ const prior = rows.find((row) => row.id === adjustmentId);
373
+ if (prior) {
374
+ if (prior.regularSeconds !== input.regularSeconds || prior.overtimeSeconds !== input.overtimeSeconds || prior.reason !== input.reason || prior.actorProfileId !== this.options.actorProfileId) throw new Error("Adjustment operationId reused with a different payload.");
375
+ return;
376
+ }
377
+ const regular = adjustedSeconds(card.regularSeconds, rows, "regularSeconds");
378
+ const overtime = adjustedSeconds(card.overtimeSeconds, rows, "overtimeSeconds");
379
+ integer(integer(regular + input.regularSeconds) + integer(overtime + input.overtimeSeconds));
380
+ const adjustment = await new TimecardAdjustment({
381
+ db,
382
+ id: adjustmentId,
383
+ _skipLoad: true
384
+ }).initialize();
385
+ Object.assign(adjustment, this.actor, {
386
+ timecardId: id,
387
+ actorProfileId: this.options.actorProfileId,
388
+ ...input
389
+ });
390
+ await persistRollup(adjustment);
391
+ });
392
+ return this.scope(async () => {
393
+ const [row] = await (await TimecardAdjustmentCollection.create({ db: this.db })).list({
394
+ where: {
395
+ ...this.actor,
396
+ id: adjustmentId
397
+ },
398
+ limit: 1
399
+ });
400
+ if (!row) throw new Error("Committed adjustment disappeared.");
401
+ return row;
402
+ });
403
+ }
404
+ /** Effective totals combine the frozen base with append-only corrections. */
405
+ async totals(id) {
406
+ return this.transact(async (db) => {
407
+ const card = await this.card(db, id);
408
+ const rows = await (await TimecardAdjustmentCollection.create({ db })).list({ where: {
409
+ ...this.actor,
410
+ timecardId: id
411
+ } });
412
+ const regularSeconds = adjustedSeconds(card.regularSeconds, rows, "regularSeconds");
413
+ const overtimeSeconds = adjustedSeconds(card.overtimeSeconds, rows, "overtimeSeconds");
414
+ return {
415
+ regularSeconds,
416
+ overtimeSeconds,
417
+ totalSeconds: integer(regularSeconds + overtimeSeconds)
418
+ };
419
+ });
420
+ }
421
+ };
422
+ //#endregion
423
+ export { PeriodTimecard as a, TimecardAdjustmentCollection as c, integer as i, clipSource as n, PeriodTimecardCollection as o, instant as r, TimecardAdjustment as s, PeriodRollupService as t };
424
+
425
+ //# sourceMappingURL=rollup-yE0TFLJe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollup-yE0TFLJe.js","names":["id"],"sources":["../../src/rollup/write.ts","../../src/rollup/models.ts","../../src/rollup/calculation.ts","../../src/rollup/service.ts"],"sourcesContent":["import type { SmrtObject } from '@happyvertical/smrt-core';\n\n// Per-instance capability, deliberately absent from the package export surface.\nconst permitted = new WeakSet<SmrtObject>();\nexport function assertRollupWrite(object: SmrtObject): void {\n if (!permitted.has(object))\n throw new Error(\n 'Timecards and adjustments are writable only through PeriodRollupService.',\n );\n}\nexport async function persistRollup<T extends SmrtObject>(\n object: T,\n): Promise<T> {\n permitted.add(object);\n try {\n await object.save();\n return object;\n } finally {\n permitted.delete(object);\n }\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { TimecardSource } from './types.js';\nimport { assertRollupWrite } from './write.js';\n\n/** One person's immutable-on-confirmation period snapshot, in integer seconds. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'period_timecards',\n sensitive: true,\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class PeriodTimecard extends SmrtObject {\n @tenantId() tenantId = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n profileId = '';\n startsAt: Date = new Date(0);\n endsAt: Date = new Date(0);\n timezone = '';\n rulesVersion = '';\n @field({ type: 'text' }) rulesSnapshot = '{}';\n @field({ type: 'text' }) sourceSnapshot = '[]';\n regularSeconds = 0;\n overtimeSeconds = 0;\n totalSeconds = 0;\n @field({ type: 'text' }) status: 'open' | 'confirmed' = 'open';\n confirmedAt: Date | null = null;\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n nullable: true,\n onDelete: 'RESTRICT',\n })\n confirmedByProfileId: string | null = null;\n\n /** Return a detached source snapshot; corrupt persisted JSON fails closed. */\n getSources(): TimecardSource[] {\n const result: unknown = JSON.parse(this.sourceSnapshot);\n if (!Array.isArray(result))\n throw new Error('Invalid timecard source snapshot.');\n return result as TimecardSource[];\n }\n /** Return the consumer policy frozen with this rollup. */\n getRules(): Record<string, unknown> {\n const result: unknown = JSON.parse(this.rulesSnapshot);\n if (!result || typeof result !== 'object' || Array.isArray(result))\n throw new Error('Invalid timecard rules snapshot.');\n return result as Record<string, unknown>;\n }\n override async save(): Promise<this> {\n assertRollupWrite(this);\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (prior?.status === 'confirmed')\n throw new Error(\n 'Confirmed timecards are immutable; append an adjustment.',\n );\n if (\n prior &&\n (prior.tenant_id !== this.tenantId ||\n prior.profile_id !== this.profileId ||\n new Date(prior.starts_at as string).getTime() !==\n this.startsAt.getTime())\n )\n throw new Error('Timecard identity is immutable.');\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new Error('Timecard history cannot be deleted.');\n }\n}\n\n/** Attributed append-only signed seconds; the confirmed base card never changes. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'timecard_adjustments',\n sensitive: true,\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class TimecardAdjustment extends SmrtObject {\n @tenantId() tenantId = '';\n @foreignKey(PeriodTimecard, { onDelete: 'RESTRICT' }) timecardId = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n profileId = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n actorProfileId = '';\n operationId = '';\n regularSeconds = 0;\n overtimeSeconds = 0;\n reason = '';\n override async save(): Promise<this> {\n assertRollupWrite(this);\n if (this.id && (await this.db.get(this.tableName, { id: this.id })))\n throw new Error('Timecard adjustments are append-only.');\n this.requireInsertOnSave();\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new Error('Timecard adjustments cannot be deleted.');\n }\n}\n/** Scoped persistence collection; mutations must use PeriodRollupService. */\nexport class PeriodTimecardCollection extends SmrtCollection<PeriodTimecard> {\n static readonly _itemClass = PeriodTimecard;\n}\n/** Scoped append-only adjustment collection. */\nexport class TimecardAdjustmentCollection extends SmrtCollection<TimecardAdjustment> {\n static readonly _itemClass = TimecardAdjustment;\n}\n","import type { TimecardPeriod, TimecardSource } from './types.js';\n\nexport function integer(value: number, signed = false): number {\n if (!Number.isSafeInteger(value) || (!signed && value < 0))\n throw new Error('Timecard seconds must be safe integers and nonnegative.');\n return value;\n}\nexport function instant(value: Date): number {\n if (!(value instanceof Date) || !Number.isFinite(value.getTime()))\n throw new Error('Timecard timestamps must be valid dates.');\n return value.getTime();\n}\nexport function validatePeriod(\n value: TimecardPeriod,\n at: Date,\n): TimecardPeriod {\n if (\n !value ||\n instant(value.startsAt) > instant(at) ||\n instant(value.endsAt) <= instant(at) ||\n instant(value.startsAt) >= instant(value.endsAt)\n )\n throw new Error('Resolver period must contain the requested instant.');\n if (!value.timezone || !value.version?.trim())\n throw new Error('Resolver must supply a timezone and policy version.');\n new Intl.DateTimeFormat('en', { timeZone: value.timezone });\n if (\n !value.rules ||\n typeof value.rules !== 'object' ||\n Array.isArray(value.rules)\n )\n throw new Error('Resolver rules must be a JSON object.');\n // JSON round-trip detaches consumer-owned state; fail rather than silently lose non-JSON values.\n const json = JSON.stringify(value.rules, (_key, item) => {\n if (\n typeof item === 'function' ||\n typeof item === 'undefined' ||\n typeof item === 'symbol' ||\n (typeof item === 'number' && !Number.isFinite(item))\n )\n throw new Error('Resolver rules must be JSON serializable.');\n return item;\n });\n const rules: unknown = JSON.parse(json);\n if (!rules || typeof rules !== 'object' || Array.isArray(rules))\n throw new Error('Resolver rules must serialize to a JSON object.');\n return {\n startsAt: new Date(value.startsAt),\n endsAt: new Date(value.endsAt),\n timezone: value.timezone,\n version: value.version,\n rules: rules as Record<string, unknown>,\n };\n}\n/** Prefix allocation preserves integer seconds across adjacent half-open periods. */\nexport function clipSource(\n source: TimecardSource,\n period: TimecardPeriod,\n): TimecardSource | null {\n const start = instant(new Date(source.startsAt));\n const end = instant(new Date(source.endsAt));\n integer(source.seconds);\n if (end < start) throw new Error('Invalid source interval.');\n const left = Math.max(start, period.startsAt.getTime());\n const right = Math.min(end, period.endsAt.getTime());\n if (start === end)\n return start >= period.startsAt.getTime() && start < period.endsAt.getTime()\n ? { ...source }\n : null;\n if (left >= right) return null;\n const span = integer(end - start);\n const prefix = (elapsed: number) =>\n Number((BigInt(source.seconds) * BigInt(integer(elapsed))) / BigInt(span));\n const seconds = prefix(right - start) - prefix(left - start);\n return {\n ...source,\n startsAt: new Date(left).toISOString(),\n endsAt: new Date(right).toISOString(),\n seconds: integer(seconds),\n };\n}\n","import { createHash } from 'node:crypto';\nimport {\n isEmbeddedDatabase,\n isPostgresDatabase,\n withEmbeddedWriteTransaction,\n} from '@happyvertical/smrt-core';\nimport { getTenantId, withTenant } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { ServiceTimeEntryCollection } from '../models/service-time-entry.js';\nimport { clipSource, instant, integer, validatePeriod } from './calculation.js';\nimport {\n PeriodTimecard,\n PeriodTimecardCollection,\n TimecardAdjustment,\n TimecardAdjustmentCollection,\n} from './models.js';\nimport type {\n PeriodRulesResolver,\n TimecardActor,\n TimecardAdjustmentInput,\n TimecardSource,\n} from './types.js';\nimport { persistRollup } from './write.js';\n\nfunction identity(parts: string[]): string {\n const bytes = createHash('sha256').update(JSON.stringify(parts)).digest();\n bytes[6] = (bytes[6] & 15) | 80;\n bytes[8] = (bytes[8] & 63) | 128;\n const hex = bytes.subarray(0, 16).toString('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\nfunction adjustedSeconds(\n base: number,\n rows: TimecardAdjustment[],\n field: 'regularSeconds' | 'overtimeSeconds',\n): number {\n const total = rows.reduce(\n (sum, row) => sum + BigInt(integer(row[field], true)),\n BigInt(integer(base)),\n );\n return integer(Number(total));\n}\n\n/**\n * Server-side, trusted tenant/person capability. Consumers authorize this scope\n * before construction. Reads and writes reject conflicting ambient tenancy.\n * SQLite and PostgreSQL are supported; policy and payroll remain consumer-owned.\n */\nexport class PeriodRollupService {\n constructor(\n private readonly db: DatabaseInterface,\n private readonly actor: TimecardActor,\n private readonly resolver: PeriodRulesResolver,\n private readonly options: {\n attendance?: boolean;\n actorProfileId?: string;\n } = {},\n ) {\n if (!actor.tenantId?.trim() || !actor.profileId?.trim())\n throw new Error('Timecards require a trusted tenant/profile actor.');\n this.actor = Object.freeze({\n tenantId: this.identity(actor.tenantId),\n profileId: this.identity(actor.profileId),\n });\n if (options.actorProfileId !== undefined && !options.actorProfileId.trim())\n throw new Error('Actor attribution must be a nonempty profile id.');\n this.options = {\n attendance: options.attendance,\n actorProfileId: this.identity(\n options.actorProfileId ?? this.actor.profileId,\n ),\n };\n }\n\n private identity(value: string): string {\n if (!isPostgresDatabase(this.db)) return value;\n const hex = value.replace(/[{}-]/g, '').toLowerCase();\n if (!/^[0-9a-f]{32}$/.test(hex))\n throw new Error('PostgreSQL timecard identities must be UUIDs.');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n }\n private scope<T>(run: () => Promise<T>): Promise<T> {\n const active = getTenantId();\n if (active && this.identity(active) !== this.actor.tenantId)\n throw new Error('Timecard actor differs from active tenant.');\n return withTenant({ tenantId: this.actor.tenantId }, run);\n }\n private transact<T>(run: (db: DatabaseInterface) => Promise<T>): Promise<T> {\n return this.scope(() =>\n withEmbeddedWriteTransaction(\n this.db,\n isEmbeddedDatabase(this.db),\n async (db) => {\n if (isPostgresDatabase(db))\n await db.query(\n 'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',\n JSON.stringify([\n 'smrt.rollup',\n this.actor.tenantId,\n this.actor.profileId,\n ]),\n );\n if (this.options.attendance && isPostgresDatabase(db))\n await db.query(\n 'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',\n JSON.stringify([\n 'smrt.attendance',\n this.actor.tenantId,\n this.actor.profileId,\n ]),\n );\n return run(db);\n },\n true,\n ),\n );\n }\n private async card(\n db: DatabaseInterface,\n id: string,\n ): Promise<PeriodTimecard> {\n const collection = await PeriodTimecardCollection.create({ db });\n const [card] = await collection.list({\n where: { ...this.actor, id },\n limit: 1,\n });\n if (!card) throw new Error('Timecard not found in actor scope.');\n return card;\n }\n /** Scoped read on the root executor, including after transaction completion. */\n async get(id: string): Promise<PeriodTimecard> {\n return this.scope(() => this.card(this.db, id));\n }\n /** Rebuild one open card from a consistent transaction-bound source snapshot. */\n async rollup(at: Date): Promise<PeriodTimecard> {\n at = new Date(instant(at));\n const id = await this.transact(async (db) => {\n const period = validatePeriod(\n await this.resolver.periodFor(new Date(at), this.actor),\n at,\n );\n const id = identity([\n 'smrt.period',\n this.actor.tenantId,\n this.actor.profileId,\n period.startsAt.toISOString(),\n ]);\n const collection = await PeriodTimecardCollection.create({ db });\n const [prior] = await collection.list({\n where: { ...this.actor, id },\n limit: 1,\n });\n if (prior?.status === 'confirmed')\n throw new Error(\n 'Confirmed timecards are immutable; append an adjustment.',\n );\n // A policy change must not create overlapping cards for the same person.\n for (const other of await collection.list({ where: { ...this.actor } })) {\n if (\n other.id !== id &&\n other.startsAt < period.endsAt &&\n other.endsAt > period.startsAt\n )\n throw new Error('Resolver period overlaps an existing timecard.');\n }\n let sources: TimecardSource[] = [];\n let linkedEntryIds = new Set<string>();\n if (this.options.attendance) {\n const { attendanceSources } = await import('./attendance.js');\n ({ sources, linkedEntryIds } = await attendanceSources(\n db,\n this.actor,\n period,\n ));\n }\n const entries = await ServiceTimeEntryCollection.create({ db });\n const evidence = await entries.list({\n where: {\n tenantId: this.actor.tenantId,\n participantProfileId: this.actor.profileId,\n participantKind: 'human',\n },\n });\n const byId = new Map(evidence.map((entry) => [String(entry.id), entry]));\n const approvedRoots = new Set<string>();\n for (const entry of evidence) {\n if (entry.status !== 'approved') continue;\n let linked = linkedEntryIds.has(String(entry.id));\n let ancestor = entry;\n const seen = new Set([String(entry.id)]);\n while (ancestor.correctionOfId) {\n const parentId = ancestor.correctionOfId;\n if (seen.has(parentId))\n throw new Error('Cyclic evidence correction chain.');\n seen.add(parentId);\n const parent = byId.get(parentId);\n if (!parent || parent.status !== 'corrected')\n throw new Error(\n 'Correction requires corrected evidence in the same actor scope.',\n );\n linked ||= linkedEntryIds.has(parentId);\n ancestor = parent;\n }\n const rootId = String(ancestor.id);\n if (approvedRoots.has(rootId))\n throw new Error('Ambiguous approved evidence correction chain.');\n approvedRoots.add(rootId);\n if (linked) continue;\n // Duration-only manual evidence belongs to its approval instant. A\n // bounded interval allocates its actual duration by elapsed fraction.\n const start = entry.startedAt ?? entry.approvedAt;\n if (!start)\n throw new Error(\n 'Approved evidence needs a start or approval timestamp.',\n );\n const end = entry.startedAt && entry.endedAt ? entry.endedAt : start;\n const source = clipSource(\n {\n kind: 'entry',\n id: String(entry.id),\n startsAt: new Date(instant(start)).toISOString(),\n endsAt: new Date(instant(end)).toISOString(),\n seconds: integer(entry.durationSeconds),\n },\n period,\n );\n if (source?.seconds) sources.push(source);\n }\n sources.sort(\n (a, b) =>\n a.startsAt.localeCompare(b.startsAt) ||\n a.kind.localeCompare(b.kind) ||\n a.id.localeCompare(b.id),\n );\n const totalSeconds = sources.reduce(\n (sum, source) => integer(sum + source.seconds),\n 0,\n );\n // Resolver receives detached snapshots so it cannot alter persisted evidence.\n const classified = await this.resolver.classify(\n structuredClone(period),\n structuredClone(sources),\n this.actor,\n );\n const regularSeconds = integer(classified?.regularSeconds);\n const overtimeSeconds = integer(classified?.overtimeSeconds);\n if (integer(regularSeconds + overtimeSeconds) !== totalSeconds)\n throw new Error('Resolver classification must conserve total seconds.');\n const card =\n prior ??\n (await new PeriodTimecard({ db, id, _skipLoad: true }).initialize());\n if (!prior) card.requireInsertOnSave();\n Object.assign(card, this.actor, {\n startsAt: period.startsAt,\n endsAt: period.endsAt,\n timezone: period.timezone,\n rulesVersion: period.version,\n rulesSnapshot: JSON.stringify(period.rules),\n sourceSnapshot: JSON.stringify(sources),\n regularSeconds,\n overtimeSeconds,\n totalSeconds,\n });\n await persistRollup(card);\n return id;\n });\n return this.get(id);\n }\n /** Confirm the current snapshot; repeated confirmation is idempotent. */\n async confirm(id: string): Promise<PeriodTimecard> {\n await this.transact(async (db) => {\n const card = await this.card(db, id);\n if (card.status === 'confirmed') return;\n card.status = 'confirmed';\n card.confirmedAt = new Date();\n card.confirmedByProfileId = this.options.actorProfileId!;\n await persistRollup(card);\n });\n return this.get(id);\n }\n /** Append a correction. An operation key retry must have exactly the same payload. */\n async adjust(\n id: string,\n input: TimecardAdjustmentInput,\n ): Promise<TimecardAdjustment> {\n id = this.identity(id);\n input = {\n operationId: input.operationId,\n reason: input.reason,\n regularSeconds: input.regularSeconds,\n overtimeSeconds: input.overtimeSeconds,\n };\n integer(input.regularSeconds, true);\n integer(input.overtimeSeconds, true);\n if (!input.operationId?.trim() || !input.reason?.trim())\n throw new Error('Adjustment requires an operationId and reason.');\n const adjustmentId = identity([\n 'smrt.adjustment',\n this.actor.tenantId,\n this.actor.profileId,\n id,\n input.operationId,\n ]);\n await this.transact(async (db) => {\n const card = await this.card(db, id);\n if (card.status !== 'confirmed')\n throw new Error('Adjustments require a confirmed timecard.');\n const collection = await TimecardAdjustmentCollection.create({ db });\n const rows = await collection.list({\n where: { ...this.actor, timecardId: id },\n });\n const prior = rows.find((row) => row.id === adjustmentId);\n if (prior) {\n if (\n prior.regularSeconds !== input.regularSeconds ||\n prior.overtimeSeconds !== input.overtimeSeconds ||\n prior.reason !== input.reason ||\n prior.actorProfileId !== this.options.actorProfileId\n )\n throw new Error(\n 'Adjustment operationId reused with a different payload.',\n );\n return;\n }\n const regular = adjustedSeconds(\n card.regularSeconds,\n rows,\n 'regularSeconds',\n );\n const overtime = adjustedSeconds(\n card.overtimeSeconds,\n rows,\n 'overtimeSeconds',\n );\n integer(\n integer(regular + input.regularSeconds) +\n integer(overtime + input.overtimeSeconds),\n );\n const adjustment = await new TimecardAdjustment({\n db,\n id: adjustmentId,\n _skipLoad: true,\n }).initialize();\n Object.assign(adjustment, this.actor, {\n timecardId: id,\n actorProfileId: this.options.actorProfileId!,\n ...input,\n });\n await persistRollup(adjustment);\n });\n return this.scope(async () => {\n const collection = await TimecardAdjustmentCollection.create({\n db: this.db,\n });\n const [row] = await collection.list({\n where: { ...this.actor, id: adjustmentId },\n limit: 1,\n });\n if (!row) throw new Error('Committed adjustment disappeared.');\n return row;\n });\n }\n /** Effective totals combine the frozen base with append-only corrections. */\n async totals(id: string): Promise<{\n regularSeconds: number;\n overtimeSeconds: number;\n totalSeconds: number;\n }> {\n return this.transact(async (db) => {\n const card = await this.card(db, id);\n const collection = await TimecardAdjustmentCollection.create({ db });\n const rows = await collection.list({\n where: { ...this.actor, timecardId: id },\n });\n const regularSeconds = adjustedSeconds(\n card.regularSeconds,\n rows,\n 'regularSeconds',\n );\n const overtimeSeconds = adjustedSeconds(\n card.overtimeSeconds,\n rows,\n 'overtimeSeconds',\n );\n return {\n regularSeconds,\n overtimeSeconds,\n totalSeconds: integer(regularSeconds + overtimeSeconds),\n };\n });\n }\n}\n"],"mappings":";;;;;AAGA,IAAM,4BAAY,IAAI,QAAoB;AACnC,SAAS,kBAAkB,QAA0B;CAC1D,IAAI,CAAC,UAAU,IAAI,MAAM,GACvB,MAAM,IAAI,MACR,0EACF;AACJ;AACA,eAAsB,cACpB,QACY;CACZ,UAAU,IAAI,MAAM;CACpB,IAAI;EACF,MAAM,OAAO,KAAK;EAClB,OAAO;CACT,UAAE;EACA,UAAU,OAAO,MAAM;CACzB;AACF;;;;;;;;;;;ACCO,IAAM,iBAAN,cAA6B,WAAW;CACjC,WAAW;CAIvB,YAAY;CACZ,2BAAiB,IAAI,KAAK,CAAC;CAC3B,yBAAe,IAAI,KAAK,CAAC;CACzB,WAAW;CACX,eAAe;CACU,gBAAgB;CAChB,iBAAiB;CAC1C,iBAAiB;CACjB,kBAAkB;CAClB,eAAe;CACU,SAA+B;CACxD,cAA2B;CAK3B,uBAAsC;;CAGtC,aAA+B;EAC7B,MAAM,SAAkB,KAAK,MAAM,KAAK,cAAc;EACtD,IAAI,CAAC,MAAM,QAAQ,MAAM,GACvB,MAAM,IAAI,MAAM,mCAAmC;EACrD,OAAO;CACT;;CAEA,WAAoC;EAClC,MAAM,SAAkB,KAAK,MAAM,KAAK,aAAa;EACrD,IAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAC/D,MAAM,IAAI,MAAM,kCAAkC;EACpD,OAAO;CACT;CACA,MAAe,OAAsB;EACnC,kBAAkB,IAAI;EACtB,MAAM,QAAQ,KAAK,KACf,MAAM,KAAK,GAAG,IAAI,KAAK,WAAW,EAAE,IAAI,KAAK,GAAG,CAAC,IACjD;EACJ,IAAI,OAAO,WAAW,aACpB,MAAM,IAAI,MACR,0DACF;EACF,IACE,UACC,MAAM,cAAc,KAAK,YACxB,MAAM,eAAe,KAAK,aAC1B,IAAI,KAAK,MAAM,SAAmB,CAAA,CAAE,QAAQ,MAC1C,KAAK,SAAS,QAAQ,IAE1B,MAAM,IAAI,MAAM,iCAAiC;EACnD,OAAQ,MAAM,MAAM,KAAK;CAC3B;CACA,MAAe,SAAwB;EACrC,MAAM,IAAI,MAAM,qCAAqC;CACvD;AACF;AA1Dc,gBAAA,CAAX,SAAS,CAAA,GADC,eACC,WAAA,YAAA,CAAA;AAIZ,gBAAA,CAHC,gBAAgB,wCAAwC,EACvD,UAAU,WACZ,CAAC,CAAA,GAJU,eAKX,WAAA,aAAA,CAAA;AAKyB,gBAAA,CAAxB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAA,GAVZ,eAUc,WAAA,iBAAA,CAAA;AACA,gBAAA,CAAxB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAA,GAXZ,eAWc,WAAA,kBAAA,CAAA;AAIA,gBAAA,CAAxB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAA,GAfZ,eAec,WAAA,UAAA,CAAA;AAMzB,gBAAA,CAJC,gBAAgB,wCAAwC;CACvD,UAAU;CACV,UAAU;AACZ,CAAC,CAAA,GApBU,eAqBX,WAAA,wBAAA,CAAA;AArBW,iBAAN,gBAAA,CARN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,WAAW;CACX,WAAW;CACX,KAAK,EAAE,SAAS,CAAC,EAAE;CACnB,KAAK,EAAE,SAAS,CAAC,EAAE;CACnB,KAAK,EAAE,SAAS,CAAC,EAAE;AACrB,CAAC,CAAA,GACY,cAAA;AAsEN,IAAM,qBAAN,cAAiC,WAAW;CACrC,WAAW;CAC+B,aAAa;CAInE,YAAY;CAIZ,iBAAiB;CACjB,cAAc;CACd,iBAAiB;CACjB,kBAAkB;CAClB,SAAS;CACT,MAAe,OAAsB;EACnC,kBAAkB,IAAI;EACtB,IAAI,KAAK,MAAO,MAAM,KAAK,GAAG,IAAI,KAAK,WAAW,EAAE,IAAI,KAAK,GAAG,CAAC,GAC/D,MAAM,IAAI,MAAM,uCAAuC;EACzD,KAAK,oBAAoB;EACzB,OAAQ,MAAM,MAAM,KAAK;CAC3B;CACA,MAAe,SAAwB;EACrC,MAAM,IAAI,MAAM,yCAAyC;CAC3D;AACF;AAxBc,gBAAA,CAAX,SAAS,CAAA,GADC,mBACC,WAAA,YAAA,CAAA;AAC0C,gBAAA,CAArD,WAAW,gBAAgB,EAAE,UAAU,WAAW,CAAC,CAAA,GAFzC,mBAE2C,WAAA,cAAA,CAAA;AAItD,gBAAA,CAHC,gBAAgB,wCAAwC,EACvD,UAAU,WACZ,CAAC,CAAA,GALU,mBAMX,WAAA,aAAA,CAAA;AAIA,gBAAA,CAHC,gBAAgB,wCAAwC,EACvD,UAAU,WACZ,CAAC,CAAA,GATU,mBAUX,WAAA,kBAAA,CAAA;AAVW,qBAAN,gBAAA,CARN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,WAAW;CACX,WAAW;CACX,KAAK,EAAE,SAAS,CAAC,EAAE;CACnB,KAAK,EAAE,SAAS,CAAC,EAAE;CACnB,KAAK,EAAE,SAAS,CAAC,EAAE;AACrB,CAAC,CAAA,GACY,kBAAA;AA2BN,IAAM,2BAAN,cAAuC,eAA+B;CAC3E,OAAgB,aAAa;AAC/B;AAEO,IAAM,+BAAN,cAA2C,eAAmC;CACnF,OAAgB,aAAa;AAC/B;;;AC1HO,SAAS,QAAQ,OAAe,SAAS,OAAe;CAC7D,IAAI,CAAC,OAAO,cAAc,KAAK,KAAM,CAAC,UAAU,QAAQ,GACtD,MAAM,IAAI,MAAM,yDAAyD;CAC3E,OAAO;AACT;AACO,SAAS,QAAQ,OAAqB;CAC3C,IAAI,EAAE,iBAAiB,SAAS,CAAC,OAAO,SAAS,MAAM,QAAQ,CAAC,GAC9D,MAAM,IAAI,MAAM,0CAA0C;CAC5D,OAAO,MAAM,QAAQ;AACvB;AACO,SAAS,eACd,OACA,IACgB;CAChB,IACE,CAAC,SACD,QAAQ,MAAM,QAAQ,IAAI,QAAQ,EAAE,KACpC,QAAQ,MAAM,MAAM,KAAK,QAAQ,EAAE,KACnC,QAAQ,MAAM,QAAQ,KAAK,QAAQ,MAAM,MAAM,GAE/C,MAAM,IAAI,MAAM,qDAAqD;CACvE,IAAI,CAAC,MAAM,YAAY,CAAC,MAAM,SAAS,KAAK,GAC1C,MAAM,IAAI,MAAM,qDAAqD;CACvE,IAAI,KAAK,eAAe,MAAM,EAAE,UAAU,MAAM,SAAS,CAAC;CAC1D,IACE,CAAC,MAAM,SACP,OAAO,MAAM,UAAU,YACvB,MAAM,QAAQ,MAAM,KAAK,GAEzB,MAAM,IAAI,MAAM,uCAAuC;CAEzD,MAAM,OAAO,KAAK,UAAU,MAAM,QAAQ,MAAM,SAAS;EACvD,IACE,OAAO,SAAS,cAChB,OAAO,SAAS,eAChB,OAAO,SAAS,YACf,OAAO,SAAS,YAAY,CAAC,OAAO,SAAS,IAAI,GAElD,MAAM,IAAI,MAAM,2CAA2C;EAC7D,OAAO;CACT,CAAC;CACD,MAAM,QAAiB,KAAK,MAAM,IAAI;CACtC,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAC5D,MAAM,IAAI,MAAM,iDAAiD;CACnE,OAAO;EACL,UAAU,IAAI,KAAK,MAAM,QAAQ;EACjC,QAAQ,IAAI,KAAK,MAAM,MAAM;EAC7B,UAAU,MAAM;EAChB,SAAS,MAAM;EACf;CACF;AACF;AAEO,SAAS,WACd,QACA,QACuB;CACvB,MAAM,QAAQ,QAAQ,IAAI,KAAK,OAAO,QAAQ,CAAC;CAC/C,MAAM,MAAM,QAAQ,IAAI,KAAK,OAAO,MAAM,CAAC;CAC3C,QAAQ,OAAO,OAAO;CACtB,IAAI,MAAM,OAAO,MAAM,IAAI,MAAM,0BAA0B;CAC3D,MAAM,OAAO,KAAK,IAAI,OAAO,OAAO,SAAS,QAAQ,CAAC;CACtD,MAAM,QAAQ,KAAK,IAAI,KAAK,OAAO,OAAO,QAAQ,CAAC;CACnD,IAAI,UAAU,KACZ,OAAO,SAAS,OAAO,SAAS,QAAQ,KAAK,QAAQ,OAAO,OAAO,QAAQ,IACvE,EAAE,GAAG,OAAO,IACZ;CACN,IAAI,QAAQ,OAAO,OAAO;CAC1B,MAAM,OAAO,QAAQ,MAAM,KAAK;CAChC,MAAM,UAAU,YACd,OAAQ,OAAO,OAAO,OAAO,IAAI,OAAO,QAAQ,OAAO,CAAC,IAAK,OAAO,IAAI,CAAC;CAC3E,MAAM,UAAU,OAAO,QAAQ,KAAK,IAAI,OAAO,OAAO,KAAK;CAC3D,OAAO;EACL,GAAG;EACH,UAAU,IAAI,KAAK,IAAI,CAAA,CAAE,YAAY;EACrC,QAAQ,IAAI,KAAK,KAAK,CAAA,CAAE,YAAY;EACpC,SAAS,QAAQ,OAAO;CAC1B;AACF;;;ACxDA,SAAS,SAAS,OAAyB;CACzC,MAAM,QAAQ,WAAW,QAAQ,CAAA,CAAE,OAAO,KAAK,UAAU,KAAK,CAAC,CAAA,CAAE,OAAO;CACxE,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,MAAM,MAAM,SAAS,GAAG,EAAE,CAAA,CAAE,SAAS,KAAK;CAChD,OAAO,GAAG,IAAI,MAAM,GAAG,CAAC,EAAC,GAAI,IAAI,MAAM,GAAG,EAAE,EAAC,GAAI,IAAI,MAAM,IAAI,EAAE,EAAC,GAAI,IAAI,MAAM,IAAI,EAAE,EAAC,GAAI,IAAI,MAAM,EAAE;AACzG;AAEA,SAAS,gBACP,MACA,MACA,OACQ;CACR,MAAM,QAAQ,KAAK,QAChB,KAAK,QAAQ,MAAM,OAAO,QAAQ,IAAI,QAAQ,IAAI,CAAC,GACpD,OAAO,QAAQ,IAAI,CAAC,CACtB;CACA,OAAO,QAAQ,OAAO,KAAK,CAAC;AAC9B;AAOO,IAAM,sBAAN,MAA0B;CAC/B,YACmB,IACA,OACA,UACA,UAGb,CAAC,GACL;EAPiB,KAAA,KAAA;EACA,KAAA,QAAA;EACA,KAAA,WAAA;EACA,KAAA,UAAA;EAKjB,IAAI,CAAC,MAAM,UAAU,KAAK,KAAK,CAAC,MAAM,WAAW,KAAK,GACpD,MAAM,IAAI,MAAM,mDAAmD;EACrE,KAAK,QAAQ,OAAO,OAAO;GACzB,UAAU,KAAK,SAAS,MAAM,QAAQ;GACtC,WAAW,KAAK,SAAS,MAAM,SAAS;EAC1C,CAAC;EACD,IAAI,QAAQ,mBAAmB,KAAA,KAAa,CAAC,QAAQ,eAAe,KAAK,GACvE,MAAM,IAAI,MAAM,kDAAkD;EACpE,KAAK,UAAU;GACb,YAAY,QAAQ;GACpB,gBAAgB,KAAK,SACnB,QAAQ,kBAAkB,KAAK,MAAM,SACvC;EACF;CACF;CAtBmB;CACA;CACA;CACA;CAqBX,SAAS,OAAuB;EACtC,IAAI,CAAC,mBAAmB,KAAK,EAAE,GAAG,OAAO;EACzC,MAAM,MAAM,MAAM,QAAQ,UAAU,EAAE,CAAA,CAAE,YAAY;EACpD,IAAI,CAAC,iBAAiB,KAAK,GAAG,GAC5B,MAAM,IAAI,MAAM,+CAA+C;EACjE,OAAO,GAAG,IAAI,MAAM,GAAG,CAAC,EAAC,GAAI,IAAI,MAAM,GAAG,EAAE,EAAC,GAAI,IAAI,MAAM,IAAI,EAAE,EAAC,GAAI,IAAI,MAAM,IAAI,EAAE,EAAC,GAAI,IAAI,MAAM,EAAE;CACzG;CACQ,MAAS,KAAmC;EAClD,MAAM,SAAS,YAAY;EAC3B,IAAI,UAAU,KAAK,SAAS,MAAM,MAAM,KAAK,MAAM,UACjD,MAAM,IAAI,MAAM,4CAA4C;EAC9D,OAAO,WAAW,EAAE,UAAU,KAAK,MAAM,SAAS,GAAG,GAAG;CAC1D;CACQ,SAAY,KAAwD;EAC1E,OAAO,KAAK,YACV,6BACE,KAAK,IACL,mBAAmB,KAAK,EAAE,GAC1B,OAAO,OAAO;GACZ,IAAI,mBAAmB,EAAE,GACvB,MAAM,GAAG,MACP,yDACA,KAAK,UAAU;IACb;IACA,KAAK,MAAM;IACX,KAAK,MAAM;GACb,CAAC,CACH;GACF,IAAI,KAAK,QAAQ,cAAc,mBAAmB,EAAE,GAClD,MAAM,GAAG,MACP,yDACA,KAAK,UAAU;IACb;IACA,KAAK,MAAM;IACX,KAAK,MAAM;GACb,CAAC,CACH;GACF,OAAO,IAAI,EAAE;EACf,GACA,IACF,CACF;CACF;CACA,MAAc,KACZ,IACA,IACyB;EAEzB,MAAM,CAAC,QAAQ,OAAM,MADI,yBAAyB,OAAO,EAAE,GAAG,CAAC,EAAA,CAC/B,KAAK;GACnC,OAAO;IAAE,GAAG,KAAK;IAAO;GAAG;GAC3B,OAAO;EACT,CAAC;EACD,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,oCAAoC;EAC/D,OAAO;CACT;;CAEA,MAAM,IAAI,IAAqC;EAC7C,OAAO,KAAK,YAAY,KAAK,KAAK,KAAK,IAAI,EAAE,CAAC;CAChD;;CAEA,MAAM,OAAO,IAAmC;EAC9C,KAAK,IAAI,KAAK,QAAQ,EAAE,CAAC;EACzB,MAAM,KAAK,MAAM,KAAK,SAAS,OAAO,OAAO;GAC3C,MAAM,SAAS,eACb,MAAM,KAAK,SAAS,UAAU,IAAI,KAAK,EAAE,GAAG,KAAK,KAAK,GACtD,EACF;GACA,MAAMA,MAAK,SAAS;IAClB;IACA,KAAK,MAAM;IACX,KAAK,MAAM;IACX,OAAO,SAAS,YAAY;GAC9B,CAAC;GACD,MAAM,aAAa,MAAM,yBAAyB,OAAO,EAAE,GAAG,CAAC;GAC/D,MAAM,CAAC,SAAS,MAAM,WAAW,KAAK;IACpC,OAAO;KAAE,GAAG,KAAK;KAAO,IAAAA;IAAG;IAC3B,OAAO;GACT,CAAC;GACD,IAAI,OAAO,WAAW,aACpB,MAAM,IAAI,MACR,0DACF;GAEF,KAAA,MAAW,SAAS,MAAM,WAAW,KAAK,EAAE,OAAO,EAAE,GAAG,KAAK,MAAM,EAAE,CAAC,GACpE,IACE,MAAM,OAAOA,OACb,MAAM,WAAW,OAAO,UACxB,MAAM,SAAS,OAAO,UAEtB,MAAM,IAAI,MAAM,gDAAgD;GAEpE,IAAI,UAA4B,CAAC;GACjC,IAAI,iCAAiB,IAAI,IAAY;GACrC,IAAI,KAAK,QAAQ,YAAY;IAC3B,MAAM,EAAE,sBAAsB,MAAM,OAAO;IAC3C,CAAC,CAAE,SAAS,kBAAmB,MAAM,kBACnC,IACA,KAAK,OACL,MACF;GACF;GAEA,MAAM,WAAW,OAAM,MADD,2BAA2B,OAAO,EAAE,GAAG,CAAC,EAAA,CAC/B,KAAK,EAClC,OAAO;IACL,UAAU,KAAK,MAAM;IACrB,sBAAsB,KAAK,MAAM;IACjC,iBAAiB;GACnB,EACF,CAAC;GACD,MAAM,OAAO,IAAI,IAAI,SAAS,KAAK,UAAU,CAAC,OAAO,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC;GACvE,MAAM,gCAAgB,IAAI,IAAY;GACtC,KAAA,MAAW,SAAS,UAAU;IAC5B,IAAI,MAAM,WAAW,YAAY;IACjC,IAAI,SAAS,eAAe,IAAI,OAAO,MAAM,EAAE,CAAC;IAChD,IAAI,WAAW;IACf,MAAM,uBAAO,IAAI,IAAI,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC;IACvC,OAAO,SAAS,gBAAgB;KAC9B,MAAM,WAAW,SAAS;KAC1B,IAAI,KAAK,IAAI,QAAQ,GACnB,MAAM,IAAI,MAAM,mCAAmC;KACrD,KAAK,IAAI,QAAQ;KACjB,MAAM,SAAS,KAAK,IAAI,QAAQ;KAChC,IAAI,CAAC,UAAU,OAAO,WAAW,aAC/B,MAAM,IAAI,MACR,iEACF;KACF,WAAW,eAAe,IAAI,QAAQ;KACtC,WAAW;IACb;IACA,MAAM,SAAS,OAAO,SAAS,EAAE;IACjC,IAAI,cAAc,IAAI,MAAM,GAC1B,MAAM,IAAI,MAAM,+CAA+C;IACjE,cAAc,IAAI,MAAM;IACxB,IAAI,QAAQ;IAGZ,MAAM,QAAQ,MAAM,aAAa,MAAM;IACvC,IAAI,CAAC,OACH,MAAM,IAAI,MACR,wDACF;IACF,MAAM,MAAM,MAAM,aAAa,MAAM,UAAU,MAAM,UAAU;IAC/D,MAAM,SAAS,WACb;KACE,MAAM;KACN,IAAI,OAAO,MAAM,EAAE;KACnB,UAAU,IAAI,KAAK,QAAQ,KAAK,CAAC,CAAA,CAAE,YAAY;KAC/C,QAAQ,IAAI,KAAK,QAAQ,GAAG,CAAC,CAAA,CAAE,YAAY;KAC3C,SAAS,QAAQ,MAAM,eAAe;IACxC,GACA,MACF;IACA,IAAI,QAAQ,SAAS,QAAQ,KAAK,MAAM;GAC1C;GACA,QAAQ,MACL,GAAG,MACF,EAAE,SAAS,cAAc,EAAE,QAAQ,KACnC,EAAE,KAAK,cAAc,EAAE,IAAI,KAC3B,EAAE,GAAG,cAAc,EAAE,EAAE,CAC3B;GACA,MAAM,eAAe,QAAQ,QAC1B,KAAK,WAAW,QAAQ,MAAM,OAAO,OAAO,GAC7C,CACF;GAEA,MAAM,aAAa,MAAM,KAAK,SAAS,SACrC,gBAAgB,MAAM,GACtB,gBAAgB,OAAO,GACvB,KAAK,KACP;GACA,MAAM,iBAAiB,QAAQ,YAAY,cAAc;GACzD,MAAM,kBAAkB,QAAQ,YAAY,eAAe;GAC3D,IAAI,QAAQ,iBAAiB,eAAe,MAAM,cAChD,MAAM,IAAI,MAAM,sDAAsD;GACxE,MAAM,OACJ,SACC,MAAM,IAAI,eAAe;IAAE;IAAI,IAAAA;IAAI,WAAW;GAAK,CAAC,CAAA,CAAE,WAAW;GACpE,IAAI,CAAC,OAAO,KAAK,oBAAoB;GACrC,OAAO,OAAO,MAAM,KAAK,OAAO;IAC9B,UAAU,OAAO;IACjB,QAAQ,OAAO;IACf,UAAU,OAAO;IACjB,cAAc,OAAO;IACrB,eAAe,KAAK,UAAU,OAAO,KAAK;IAC1C,gBAAgB,KAAK,UAAU,OAAO;IACtC;IACA;IACA;GACF,CAAC;GACD,MAAM,cAAc,IAAI;GACxB,OAAOA;EACT,CAAC;EACD,OAAO,KAAK,IAAI,EAAE;CACpB;;CAEA,MAAM,QAAQ,IAAqC;EACjD,MAAM,KAAK,SAAS,OAAO,OAAO;GAChC,MAAM,OAAO,MAAM,KAAK,KAAK,IAAI,EAAE;GACnC,IAAI,KAAK,WAAW,aAAa;GACjC,KAAK,SAAS;GACd,KAAK,8BAAc,IAAI,KAAK;GAC5B,KAAK,uBAAuB,KAAK,QAAQ;GACzC,MAAM,cAAc,IAAI;EAC1B,CAAC;EACD,OAAO,KAAK,IAAI,EAAE;CACpB;;CAEA,MAAM,OACJ,IACA,OAC6B;EAC7B,KAAK,KAAK,SAAS,EAAE;EACrB,QAAQ;GACN,aAAa,MAAM;GACnB,QAAQ,MAAM;GACd,gBAAgB,MAAM;GACtB,iBAAiB,MAAM;EACzB;EACA,QAAQ,MAAM,gBAAgB,IAAI;EAClC,QAAQ,MAAM,iBAAiB,IAAI;EACnC,IAAI,CAAC,MAAM,aAAa,KAAK,KAAK,CAAC,MAAM,QAAQ,KAAK,GACpD,MAAM,IAAI,MAAM,gDAAgD;EAClE,MAAM,eAAe,SAAS;GAC5B;GACA,KAAK,MAAM;GACX,KAAK,MAAM;GACX;GACA,MAAM;EACR,CAAC;EACD,MAAM,KAAK,SAAS,OAAO,OAAO;GAChC,MAAM,OAAO,MAAM,KAAK,KAAK,IAAI,EAAE;GACnC,IAAI,KAAK,WAAW,aAClB,MAAM,IAAI,MAAM,2CAA2C;GAE7D,MAAM,OAAO,OAAM,MADM,6BAA6B,OAAO,EAAE,GAAG,CAAC,EAAA,CACrC,KAAK,EACjC,OAAO;IAAE,GAAG,KAAK;IAAO,YAAY;GAAG,EACzC,CAAC;GACD,MAAM,QAAQ,KAAK,MAAM,QAAQ,IAAI,OAAO,YAAY;GACxD,IAAI,OAAO;IACT,IACE,MAAM,mBAAmB,MAAM,kBAC/B,MAAM,oBAAoB,MAAM,mBAChC,MAAM,WAAW,MAAM,UACvB,MAAM,mBAAmB,KAAK,QAAQ,gBAEtC,MAAM,IAAI,MACR,yDACF;IACF;GACF;GACA,MAAM,UAAU,gBACd,KAAK,gBACL,MACA,gBACF;GACA,MAAM,WAAW,gBACf,KAAK,iBACL,MACA,iBACF;GACA,QACE,QAAQ,UAAU,MAAM,cAAc,IACpC,QAAQ,WAAW,MAAM,eAAe,CAC5C;GACA,MAAM,aAAa,MAAM,IAAI,mBAAmB;IAC9C;IACA,IAAI;IACJ,WAAW;GACb,CAAC,CAAA,CAAE,WAAW;GACd,OAAO,OAAO,YAAY,KAAK,OAAO;IACpC,YAAY;IACZ,gBAAgB,KAAK,QAAQ;IAC7B,GAAG;GACL,CAAC;GACD,MAAM,cAAc,UAAU;EAChC,CAAC;EACD,OAAO,KAAK,MAAM,YAAY;GAI5B,MAAM,CAAC,OAAO,OAAM,MAHK,6BAA6B,OAAO,EAC3D,IAAI,KAAK,GACX,CAAC,EAAA,CAC8B,KAAK;IAClC,OAAO;KAAE,GAAG,KAAK;KAAO,IAAI;IAAa;IACzC,OAAO;GACT,CAAC;GACD,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,mCAAmC;GAC7D,OAAO;EACT,CAAC;CACH;;CAEA,MAAM,OAAO,IAIV;EACD,OAAO,KAAK,SAAS,OAAO,OAAO;GACjC,MAAM,OAAO,MAAM,KAAK,KAAK,IAAI,EAAE;GAEnC,MAAM,OAAO,OAAM,MADM,6BAA6B,OAAO,EAAE,GAAG,CAAC,EAAA,CACrC,KAAK,EACjC,OAAO;IAAE,GAAG,KAAK;IAAO,YAAY;GAAG,EACzC,CAAC;GACD,MAAM,iBAAiB,gBACrB,KAAK,gBACL,MACA,gBACF;GACA,MAAM,kBAAkB,gBACtB,KAAK,iBACL,MACA,iBACF;GACA,OAAO;IACL;IACA;IACA,cAAc,QAAQ,iBAAiB,eAAe;GACxD;EACF,CAAC;CACH;AACF"}