@unisonlabs/sixminute-admin 0.1.1232-next → 0.1.1234-next

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.
@@ -21,7 +21,7 @@ import { homedir } from "node:os";
21
21
  import { fileURLToPath } from "node:url";
22
22
 
23
23
  // tools/admin/src/build-info.ts
24
- var BUILT = { version: "0.1.1232-next", commit: "2bf7a3249e979dfadad0ba2f985d1dcf875dd3aa", surface: "4326872fec11ff43" };
24
+ var BUILT = { version: "0.1.1234-next", commit: "87eb8615276920192e16568312a53f1302621d8e", surface: "daa5005ac9ba21da" };
25
25
 
26
26
  // tools/admin/src/run.ts
27
27
  import { rmSync as rmSync3 } from "node:fs";
@@ -4356,6 +4356,39 @@ var ActivityTypeSchema = exports_external.object({
4356
4356
  });
4357
4357
  var JudgmentPipelineSchema = exports_external.enum(["standard", "jev"]);
4358
4358
 
4359
+ // packages/contracts/src/grid.ts
4360
+ var UNIT_MINUTES = [3, 5, 6, 10, 12, 15];
4361
+ var UNIT_MIN = UNIT_MINUTES[0];
4362
+ var UNIT_MAX = UNIT_MINUTES[UNIT_MINUTES.length - 1];
4363
+ var UnitMinutesSchema = exports_external.number().int().min(UNIT_MIN).max(UNIT_MAX).refine((minutes) => !Number.isInteger(minutes) || minutes < UNIT_MIN || minutes > UNIT_MAX || UNIT_MINUTES.includes(minutes), { message: `an interval is one of ${UNIT_MINUTES.join(", ")} minutes, a divisor of the hour` });
4364
+ var WALL_CLOCKS = new Map;
4365
+ function wallClockFormat(zone) {
4366
+ const kept = WALL_CLOCKS.get(zone);
4367
+ if (kept !== undefined)
4368
+ return kept;
4369
+ const format = new Intl.DateTimeFormat("en-US", {
4370
+ timeZone: zone,
4371
+ hourCycle: "h23",
4372
+ year: "numeric",
4373
+ month: "2-digit",
4374
+ day: "2-digit",
4375
+ hour: "2-digit",
4376
+ minute: "2-digit",
4377
+ second: "2-digit"
4378
+ });
4379
+ WALL_CLOCKS.set(zone, format);
4380
+ return format;
4381
+ }
4382
+ function wallClock(ms, zone) {
4383
+ const parts = wallClockFormat(zone).formatToParts(new Date(ms));
4384
+ const part = (type) => Number(parts.find((p) => p.type === type).value);
4385
+ const wholeSeconds = Date.UTC(part("year"), part("month") - 1, part("day"), part("hour"), part("minute"), part("second"));
4386
+ return wholeSeconds + (ms - Math.floor(ms / 1000) * 1000);
4387
+ }
4388
+ function dayOf(iso, zone) {
4389
+ return new Date(wallClock(Date.parse(iso), zone)).toISOString().slice(0, 10);
4390
+ }
4391
+
4359
4392
  // packages/contracts/src/entry.ts
4360
4393
  var EntryStatusSchema = exports_external.enum(["draft", "released"]);
4361
4394
  var AssignmentSchema = exports_external.enum(["model", "user", "none"]);
@@ -4370,6 +4403,12 @@ var NarrativeSchema = exports_external.string().transform((s) => s.trim()).super
4370
4403
  });
4371
4404
  }
4372
4405
  });
4406
+ var EntryPartSchema = exports_external.object({
4407
+ t_start: TimestampSchema,
4408
+ t_end: TimestampSchema
4409
+ }).refine((part) => part.t_start < part.t_end, { path: ["t_end"], message: "a part ends after it starts" });
4410
+ var OVERRIDE_MAX_SECONDS = 86400;
4411
+ var OverrideSecondsSchema = exports_external.number().int().positive().max(OVERRIDE_MAX_SECONDS).refine((seconds) => seconds % 60 === 0, { message: "an override is a whole number of minutes" });
4373
4412
  var EntryContentFields = exports_external.object({
4374
4413
  id: EntryIdSchema,
4375
4414
  day: LocalDaySchema,
@@ -4377,6 +4416,7 @@ var EntryContentFields = exports_external.object({
4377
4416
  timekeeper_id: exports_external.string().min(1),
4378
4417
  t_start: TimestampSchema,
4379
4418
  t_end: TimestampSchema,
4419
+ parts: exports_external.array(EntryPartSchema).min(1).optional(),
4380
4420
  narrative: NarrativeSchema,
4381
4421
  matter_id: MatterIdSchema.nullable().default(null),
4382
4422
  attributes: EntryAttributesSchema.default({}),
@@ -4398,10 +4438,13 @@ var AsJudgedSchema = EntryContentFields.omit({
4398
4438
  });
4399
4439
  var AS_JUDGED_FIELDS = Object.keys(AsJudgedSchema.shape);
4400
4440
  var EntryFields = EntryContentFields.extend({
4401
- edited_at: TimestampSchema.nullable().default(null)
4441
+ edited_at: TimestampSchema.nullable().default(null),
4442
+ override_seconds: OverrideSecondsSchema.nullable().optional()
4402
4443
  });
4403
4444
  var JudgedEntryFields = EntryFields.extend({
4404
- as_judged: AsJudgedSchema.nullable().default(null)
4445
+ as_judged: AsJudgedSchema.superRefine((copy, ctx) => {
4446
+ refineParts(copy, ctx);
4447
+ }).nullable().default(null)
4405
4448
  });
4406
4449
  function refineEntry(entry, ctx) {
4407
4450
  if (entry.status !== "draft" && entry.released_at === null) {
@@ -4418,9 +4461,58 @@ function refineEntry(entry, ctx) {
4418
4461
  message: "the block ends after it starts"
4419
4462
  });
4420
4463
  }
4464
+ refineParts(entry, ctx);
4421
4465
  }
4422
- var EntrySchema = EntryFields.superRefine(refineEntry);
4423
- var JudgedEntrySchema = JudgedEntryFields.superRefine(refineEntry);
4466
+ function refineParts(entry, ctx) {
4467
+ const parts = entry.parts;
4468
+ if (parts === undefined || parts.length === 0)
4469
+ return;
4470
+ parts.forEach((part, index) => {
4471
+ const previous = parts[index - 1];
4472
+ if (previous !== undefined && part.t_start <= previous.t_end) {
4473
+ ctx.addIssue({
4474
+ code: exports_external.ZodIssueCode.custom,
4475
+ path: ["parts", index, "t_start"],
4476
+ message: part.t_start === previous.t_end ? "parts that touch are one part: join them" : "the parts are in time order and do not overlap: this one starts before the one before it ends"
4477
+ });
4478
+ }
4479
+ });
4480
+ if (parts.length < 2)
4481
+ return;
4482
+ if (parts[0].t_start !== entry.t_start) {
4483
+ ctx.addIssue({
4484
+ code: exports_external.ZodIssueCode.custom,
4485
+ path: ["t_start"],
4486
+ message: "t_start is the first part's start"
4487
+ });
4488
+ }
4489
+ if (parts[parts.length - 1].t_end !== entry.t_end) {
4490
+ ctx.addIssue({
4491
+ code: exports_external.ZodIssueCode.custom,
4492
+ path: ["t_end"],
4493
+ message: "t_end is the last part's end"
4494
+ });
4495
+ }
4496
+ parts.forEach((part, index) => {
4497
+ if (!partOnDay(part, entry.day, entry.time_zone)) {
4498
+ ctx.addIssue({
4499
+ code: exports_external.ZodIssueCode.custom,
4500
+ path: ["parts", index],
4501
+ message: `every part lies on the entry's day, ${entry.day} in ${entry.time_zone}, and never crosses midnight`
4502
+ });
4503
+ }
4504
+ });
4505
+ }
4506
+ function settleEntry(entry, ctx) {
4507
+ refineEntry(entry, ctx);
4508
+ return {
4509
+ ...entry,
4510
+ parts: entry.parts !== undefined && entry.parts.length > 1 ? entry.parts : [{ t_start: entry.t_start, t_end: entry.t_end }],
4511
+ override_seconds: entry.override_seconds ?? null
4512
+ };
4513
+ }
4514
+ var EntrySchema = EntryFields.transform(settleEntry);
4515
+ var JudgedEntrySchema = JudgedEntryFields.transform(settleEntry);
4424
4516
  var RecordingFields = {
4425
4517
  idle_ratio: exports_external.number().min(0).max(1).default(0),
4426
4518
  observation_ids: exports_external.array(ObservationIdSchema).default([]),
@@ -4429,8 +4521,23 @@ var RecordingFields = {
4429
4521
  assigned_at: TimestampSchema.nullable().default(null),
4430
4522
  recording_id: RecordingIdSchema.nullable().default(null)
4431
4523
  };
4432
- var RecordingEntrySchema = JudgedEntryFields.extend(RecordingFields).superRefine(refineEntry);
4524
+ var RecordingEntrySchema = JudgedEntryFields.extend(RecordingFields).transform(settleEntry);
4433
4525
  var RECORDING_ENTRY_FIELDS = Object.keys(RecordingFields);
4526
+ function partsOf(entry) {
4527
+ const parts = entry.parts;
4528
+ const block = [{ t_start: entry.t_start, t_end: entry.t_end }];
4529
+ if (parts === undefined || parts.length === 0)
4530
+ return block;
4531
+ const bounded = Date.parse(parts[0].t_start) === Date.parse(entry.t_start) && Date.parse(parts[parts.length - 1].t_end) === Date.parse(entry.t_end);
4532
+ return bounded ? parts : block;
4533
+ }
4534
+ function partOnDay(part, day, zone) {
4535
+ try {
4536
+ return dayOf(part.t_start, zone) === day && dayOf(new Date(Date.parse(part.t_end) - 1).toISOString(), zone) === day;
4537
+ } catch {
4538
+ return false;
4539
+ }
4540
+ }
4434
4541
 
4435
4542
  // packages/contracts/src/connector.ts
4436
4543
  var ConnectorKindSchema = exports_external.enum(["urios", "legalsense", "partner", "fake"]);
@@ -4863,13 +4970,6 @@ var IntervalTextSchema = exports_external.object({
4863
4970
  entries: exports_external.array(IntervalTextEntrySchema).optional()
4864
4971
  });
4865
4972
 
4866
- // packages/contracts/src/grid.ts
4867
- var UNIT_MINUTES = [3, 5, 6, 10, 12, 15];
4868
- var UNIT_MIN = UNIT_MINUTES[0];
4869
- var UNIT_MAX = UNIT_MINUTES[UNIT_MINUTES.length - 1];
4870
- var UnitMinutesSchema = exports_external.number().int().min(UNIT_MIN).max(UNIT_MAX).refine((minutes) => !Number.isInteger(minutes) || minutes < UNIT_MIN || minutes > UNIT_MAX || UNIT_MINUTES.includes(minutes), { message: `an interval is one of ${UNIT_MINUTES.join(", ")} minutes, a divisor of the hour` });
4871
- var WALL_CLOCKS = new Map;
4872
-
4873
4973
  // packages/contracts/src/api.ts
4874
4974
  var ProblemSchema = exports_external.object({
4875
4975
  type: exports_external.string().min(1),
@@ -5154,6 +5254,8 @@ var EntryPatchSchema = exports_external.object({
5154
5254
  matter_id: EntrySchema.innerType().shape.matter_id.optional(),
5155
5255
  t_start: TimestampSchema.optional(),
5156
5256
  t_end: TimestampSchema.optional(),
5257
+ parts: exports_external.array(EntryPartSchema).min(1).optional(),
5258
+ override_seconds: OverrideSecondsSchema.nullable().optional(),
5157
5259
  reason: exports_external.string().min(1).nullable().default(null)
5158
5260
  }).superRefine((patch, ctx) => {
5159
5261
  const hasStart = patch.t_start !== undefined;
@@ -5165,6 +5267,27 @@ var EntryPatchSchema = exports_external.object({
5165
5267
  message: "a block is a pair: send t_start and t_end together"
5166
5268
  });
5167
5269
  }
5270
+ if (patch.parts !== undefined && (hasStart || hasEnd)) {
5271
+ ctx.addIssue({
5272
+ code: exports_external.ZodIssueCode.custom,
5273
+ path: ["parts"],
5274
+ message: "parts replace the block: send parts or t_start and t_end, not both"
5275
+ });
5276
+ }
5277
+ if (patch.parts !== undefined && patch.override_seconds !== undefined) {
5278
+ ctx.addIssue({
5279
+ code: exports_external.ZodIssueCode.custom,
5280
+ path: ["override_seconds"],
5281
+ message: "a change to the parts clears the override: send parts or override_seconds, not both"
5282
+ });
5283
+ }
5284
+ if ((hasStart || hasEnd) && patch.override_seconds !== undefined) {
5285
+ ctx.addIssue({
5286
+ code: exports_external.ZodIssueCode.custom,
5287
+ path: ["override_seconds"],
5288
+ message: "a move of the block clears the override: send t_start and t_end or override_seconds, not both"
5289
+ });
5290
+ }
5168
5291
  });
5169
5292
  var ConfirmedNarrativeSchema = EntrySchema.innerType().shape.narrative.refine((narrative) => narrative.length > 0, { message: "a confirmed narrative is not empty: this is the line the person read and approved" });
5170
5293
  var EntryHalfSchema = exports_external.object({
@@ -5173,10 +5296,18 @@ var EntryHalfSchema = exports_external.object({
5173
5296
  attributes: EntryAttributesSchema
5174
5297
  });
5175
5298
  var EntrySplitRequestSchema = exports_external.object({
5176
- at: TimestampSchema,
5299
+ at: TimestampSchema.optional(),
5300
+ leaving: exports_external.array(EntryPartSchema).min(1).optional(),
5177
5301
  first: EntryHalfSchema.optional(),
5178
5302
  second: EntryHalfSchema.optional()
5179
5303
  }).superRefine((body, ctx) => {
5304
+ if (body.at === undefined === (body.leaving === undefined)) {
5305
+ ctx.addIssue({
5306
+ code: exports_external.ZodIssueCode.custom,
5307
+ path: ["leaving"],
5308
+ message: "a split names a line in `at` or the parts that leave in `leaving`, and exactly one of them"
5309
+ });
5310
+ }
5180
5311
  if (body.first === undefined !== (body.second === undefined)) {
5181
5312
  ctx.addIssue({
5182
5313
  code: exports_external.ZodIssueCode.custom,
@@ -5215,13 +5346,20 @@ var EntryBatchItemSchema = exports_external.object({
5215
5346
  entry: JudgedEntrySchema,
5216
5347
  intervals: exports_external.array(IntervalSchema).default([])
5217
5348
  }).superRefine((item, ctx) => {
5218
- const { t_start, t_end } = item.entry;
5349
+ if (item.entry.override_seconds != null) {
5350
+ ctx.addIssue({
5351
+ code: exports_external.ZodIssueCode.custom,
5352
+ path: ["entry", "override_seconds"],
5353
+ message: "a batch row carries no override: a person sets one on the entry's PATCH"
5354
+ });
5355
+ }
5356
+ const parts = partsOf(item.entry);
5219
5357
  item.intervals.forEach((interval, index) => {
5220
- if (interval.t_start < t_start || interval.t_end > t_end) {
5358
+ if (!parts.some((part) => part.t_start <= interval.t_start && interval.t_end <= part.t_end)) {
5221
5359
  ctx.addIssue({
5222
5360
  code: exports_external.ZodIssueCode.custom,
5223
5361
  path: ["intervals", index],
5224
- message: `an interval lies inside its entry's block: this one covers ${interval.t_start} to ${interval.t_end}, and the block ${t_start} to ${t_end}`
5362
+ message: `an interval lies inside one of its entry's parts: this one covers ${interval.t_start} to ${interval.t_end}, and the parts ${parts.map((part) => `${part.t_start} to ${part.t_end}`).join(", ")}`
5225
5363
  });
5226
5364
  return;
5227
5365
  }
@@ -5650,6 +5788,8 @@ var JudgedPairEntrySchema = exports_external.object({
5650
5788
  timekeeper_id: exports_external.string(),
5651
5789
  t_start: exports_external.string(),
5652
5790
  t_end: exports_external.string(),
5791
+ parts: exports_external.array(exports_external.object({ t_start: exports_external.string(), t_end: exports_external.string() })).nullable().default(null),
5792
+ override_seconds: exports_external.number().int().nullable().default(null),
5653
5793
  narrative: exports_external.string(),
5654
5794
  matter_id: exports_external.string().nullable(),
5655
5795
  attributes: exports_external.unknown(),
@@ -6264,9 +6404,29 @@ var LocalRecordingStopSchema = exports_external.object({
6264
6404
  seconds: exports_external.number().nonnegative()
6265
6405
  });
6266
6406
  var LocalAcceptedSchema = exports_external.object({ accepted: exports_external.literal(true) });
6267
- var LocalProposeRangeSchema = exports_external.object({ from: TimestampSchema, to: TimestampSchema, matter_id: MatterIdSchema.optional() }).refine((range) => Date.parse(range.from) < Date.parse(range.to), {
6268
- path: ["to"],
6269
- message: "a range ends after it starts"
6407
+ var LocalProposeRangeSchema = exports_external.object({
6408
+ from: TimestampSchema,
6409
+ to: TimestampSchema,
6410
+ matter_id: MatterIdSchema.optional(),
6411
+ parts: exports_external.array(exports_external.object({ from: TimestampSchema, to: TimestampSchema })).min(1).optional()
6412
+ }).superRefine((range, ctx) => {
6413
+ if (!(Date.parse(range.from) < Date.parse(range.to))) {
6414
+ ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["to"], message: "a range ends after it starts" });
6415
+ }
6416
+ range.parts?.forEach((part, index) => {
6417
+ const previous = range.parts[index - 1];
6418
+ if (!(Date.parse(part.from) < Date.parse(part.to))) {
6419
+ ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["parts", index, "to"], message: "a part ends after it starts" });
6420
+ } else if (Date.parse(part.from) < Date.parse(range.from) || Date.parse(part.to) > Date.parse(range.to)) {
6421
+ ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["parts", index], message: "a part lies inside its range" });
6422
+ } else if (previous !== undefined && Date.parse(part.from) <= Date.parse(previous.to)) {
6423
+ ctx.addIssue({
6424
+ code: exports_external.ZodIssueCode.custom,
6425
+ path: ["parts", index, "from"],
6426
+ message: "the parts are in time order and neither touch nor overlap"
6427
+ });
6428
+ }
6429
+ });
6270
6430
  });
6271
6431
  var LocalProposeRangesRequestSchema = exports_external.object({
6272
6432
  ranges: exports_external.array(LocalProposeRangeSchema).min(1).max(2)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unisonlabs/sixminute-admin",
3
- "version": "0.1.1232-next",
3
+ "version": "0.1.1234-next",
4
4
  "description": "Sixminute operator tool: the admin commands of the Sixminute API, run with your own operator sign-in.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {