@company-semantics/contracts 83.2.0 → 85.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +54 -3
- package/src/decisions/README.md +23 -0
- package/src/decisions/__tests__/README.md +10 -1
- package/src/decisions/__tests__/decision-schemas-golden.test.ts +6 -0
- package/src/decisions/__tests__/fixtures/decision-schemas.golden.json +2 -6
- package/src/decisions/__tests__/question-evidence-optional.test.ts +101 -0
- package/src/decisions/schemas.ts +2 -2
- package/src/decisions/types.ts +13 -5
- package/src/source-sync/README.md +59 -1
- package/src/source-sync/__tests__/approval-schemas.test.ts +2 -2
- package/src/source-sync/__tests__/barrel.test.ts +14 -0
- package/src/source-sync/__tests__/candidate-totals.test.ts +119 -0
- package/src/source-sync/__tests__/schemas.test.ts +236 -15
- package/src/source-sync/__tests__/vocabulary.test.ts +135 -1
- package/src/source-sync/candidate-totals.ts +20 -2
- package/src/source-sync/index.ts +40 -1
- package/src/source-sync/relevance.ts +216 -0
- package/src/source-sync/schemas.ts +106 -59
|
@@ -11,11 +11,19 @@
|
|
|
11
11
|
* - The wire carries a COUNT of people a source is shared with, never addresses.
|
|
12
12
|
* - Money is integer micro-USD strings; a decimal or a JSON number is refused.
|
|
13
13
|
* - A candidate set carries presented questions, never decision effects.
|
|
14
|
+
* - Relevance and treatment are two axes (control ADR source-discovery-relevance-funnel):
|
|
15
|
+
* a hidden line is never included; an unclassified line has no recommendation and
|
|
16
|
+
* no class; `selectionSource` is present exactly when `use` is.
|
|
17
|
+
* - A relevance reason's code belongs to its kind's closed set.
|
|
18
|
+
* - A candidate patch changes exactly one thing: bringing a line into review never
|
|
19
|
+
* also commits a treatment, and a patch can never hide a line.
|
|
20
|
+
* - The set response's discovery block is required and null before any completed run.
|
|
14
21
|
*/
|
|
15
22
|
|
|
16
23
|
import { describe, expect, it } from "vitest";
|
|
17
24
|
import {
|
|
18
|
-
|
|
25
|
+
RelevanceReasonSchema,
|
|
26
|
+
SourceSyncCandidateItemPatchSchema,
|
|
19
27
|
SourceSyncCandidateItemSchema,
|
|
20
28
|
SourceSyncCandidateSetResponseSchema,
|
|
21
29
|
SourceSyncDiscoveryRunResponseSchema,
|
|
@@ -41,6 +49,8 @@ function candidateItem(overrides: Record<string, unknown> = {}) {
|
|
|
41
49
|
scopeKey: "company",
|
|
42
50
|
targetDocId: TARGET_DOC_ID,
|
|
43
51
|
targetDocTitle: "Company overview",
|
|
52
|
+
visibility: "shown",
|
|
53
|
+
classificationState: "classified",
|
|
44
54
|
sourceClass: "board_deck",
|
|
45
55
|
periodLabel: "Q3 2026",
|
|
46
56
|
seriesRole: "current",
|
|
@@ -51,6 +61,12 @@ function candidateItem(overrides: Record<string, unknown> = {}) {
|
|
|
51
61
|
audienceFit: "fits",
|
|
52
62
|
recommendedUse: "direction_only",
|
|
53
63
|
use: null,
|
|
64
|
+
selectionSource: null,
|
|
65
|
+
personHoldReasons: [],
|
|
66
|
+
machineState: "fresh",
|
|
67
|
+
relevanceReasons: [],
|
|
68
|
+
targetAudience: "org_wide",
|
|
69
|
+
attention: { t1: null, t2: null },
|
|
54
70
|
included: true,
|
|
55
71
|
treatmentUnresolved: false,
|
|
56
72
|
matchReason: "A board deck for the current quarter.",
|
|
@@ -98,13 +114,29 @@ function candidateSet(overrides: Record<string, unknown> = {}) {
|
|
|
98
114
|
items: [candidateItem()],
|
|
99
115
|
questions: [presentedQuestion()],
|
|
100
116
|
withheldQuestionCount: 0,
|
|
101
|
-
totals: {
|
|
117
|
+
totals: {
|
|
118
|
+
files: 1,
|
|
119
|
+
filesIncluded: 1,
|
|
120
|
+
filesNeedingChoice: 0,
|
|
121
|
+
filesHidden: 0,
|
|
122
|
+
filesNotRediscovered: 0,
|
|
123
|
+
filesPendingClassification: 0,
|
|
124
|
+
},
|
|
102
125
|
scopes: [],
|
|
103
126
|
estimate: {
|
|
104
127
|
lowMicroUsd: "250000",
|
|
105
128
|
highMicroUsd: "1250000",
|
|
106
129
|
capMicroUsd: "2000000",
|
|
107
130
|
},
|
|
131
|
+
discovery: {
|
|
132
|
+
foundThisRun: 12,
|
|
133
|
+
retainedDecisions: 3,
|
|
134
|
+
notRediscovered: 1,
|
|
135
|
+
pendingClassification: 2,
|
|
136
|
+
filesDropped: 240,
|
|
137
|
+
coveragePercent: 87,
|
|
138
|
+
scanOutcome: "complete",
|
|
139
|
+
},
|
|
108
140
|
...overrides,
|
|
109
141
|
};
|
|
110
142
|
}
|
|
@@ -260,6 +292,208 @@ describe("SourceSyncCandidateSetResponseSchema", () => {
|
|
|
260
292
|
});
|
|
261
293
|
});
|
|
262
294
|
|
|
295
|
+
describe("candidate relevance", () => {
|
|
296
|
+
it("a hidden unclassified item has no recommendation, no class and is not included", () => {
|
|
297
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
298
|
+
candidateItem({
|
|
299
|
+
visibility: "hidden",
|
|
300
|
+
classificationState: "unclassified",
|
|
301
|
+
recommendedUse: null,
|
|
302
|
+
sourceClass: null,
|
|
303
|
+
included: false,
|
|
304
|
+
targetAudience: null,
|
|
305
|
+
machineState: "pending_classification",
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
expect(parsed.success).toBe(true);
|
|
309
|
+
expect(parsed.data?.recommendedUse).toBeNull();
|
|
310
|
+
expect(parsed.data?.sourceClass).toBeNull();
|
|
311
|
+
for (const claim of [
|
|
312
|
+
{ recommendedUse: "attached" },
|
|
313
|
+
{ sourceClass: "board_deck" },
|
|
314
|
+
]) {
|
|
315
|
+
expect(
|
|
316
|
+
SourceSyncCandidateItemSchema.safeParse(
|
|
317
|
+
candidateItem({
|
|
318
|
+
visibility: "hidden",
|
|
319
|
+
classificationState: "unclassified",
|
|
320
|
+
recommendedUse: null,
|
|
321
|
+
sourceClass: null,
|
|
322
|
+
included: false,
|
|
323
|
+
...claim,
|
|
324
|
+
}),
|
|
325
|
+
).success,
|
|
326
|
+
).toBe(false);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("a classified item has a recommendation and a class", () => {
|
|
331
|
+
for (const missing of [{ recommendedUse: null }, { sourceClass: null }]) {
|
|
332
|
+
expect(
|
|
333
|
+
SourceSyncCandidateItemSchema.safeParse(candidateItem(missing)).success,
|
|
334
|
+
).toBe(false);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("a shown item carries a reason of every kind", () => {
|
|
339
|
+
const relevanceReasons = [
|
|
340
|
+
{ kind: "lexicon", code: "title_term", term: "board", count: 2 },
|
|
341
|
+
{ kind: "signal", code: "version_churn", value: 41 },
|
|
342
|
+
{ kind: "signal", code: "owner_executive", value: null },
|
|
343
|
+
{ kind: "evaluation", code: "not_evaluated_t2" },
|
|
344
|
+
{ kind: "model", code: "title_path_score", score: 0.81 },
|
|
345
|
+
{ kind: "model", code: "peek_confirms", score: null },
|
|
346
|
+
];
|
|
347
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
348
|
+
candidateItem({
|
|
349
|
+
relevanceReasons,
|
|
350
|
+
attention: { t1: "exploit", t2: "explore" },
|
|
351
|
+
personHoldReasons: ["relevance_override"],
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
expect(parsed.success).toBe(true);
|
|
355
|
+
expect(parsed.data?.relevanceReasons.map((reason) => reason.kind)).toEqual([
|
|
356
|
+
"lexicon",
|
|
357
|
+
"signal",
|
|
358
|
+
"signal",
|
|
359
|
+
"evaluation",
|
|
360
|
+
"model",
|
|
361
|
+
"model",
|
|
362
|
+
]);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("refuses more than eight reasons", () => {
|
|
366
|
+
const reason = { kind: "evaluation", code: "explored_t1" };
|
|
367
|
+
expect(
|
|
368
|
+
SourceSyncCandidateItemSchema.safeParse(
|
|
369
|
+
candidateItem({ relevanceReasons: Array(9).fill(reason) }),
|
|
370
|
+
).success,
|
|
371
|
+
).toBe(false);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("a reason whose code belongs to another kind is refused", () => {
|
|
375
|
+
for (const reason of [
|
|
376
|
+
{ kind: "lexicon", code: "version_churn", term: "board", count: 1 },
|
|
377
|
+
{ kind: "signal", code: "title_term", value: 1 },
|
|
378
|
+
{ kind: "evaluation", code: "peek_confirms" },
|
|
379
|
+
{ kind: "model", code: "folder_unresolved", score: 0.5 },
|
|
380
|
+
]) {
|
|
381
|
+
expect(RelevanceReasonSchema.safeParse(reason).success).toBe(false);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("refuses a lexicon reason without a term or a count, and a model score outside 0..1", () => {
|
|
386
|
+
for (const reason of [
|
|
387
|
+
{ kind: "lexicon", code: "title_term", count: 1 },
|
|
388
|
+
{ kind: "lexicon", code: "title_term", term: "", count: 1 },
|
|
389
|
+
{ kind: "lexicon", code: "title_term", term: "board", count: 0 },
|
|
390
|
+
{ kind: "model", code: "title_path_score", score: 1.2 },
|
|
391
|
+
]) {
|
|
392
|
+
expect(RelevanceReasonSchema.safeParse(reason).success).toBe(false);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it("a hidden item that claims inclusion is refused", () => {
|
|
397
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
398
|
+
candidateItem({ visibility: "hidden", included: true }),
|
|
399
|
+
);
|
|
400
|
+
expect(parsed.success).toBe(false);
|
|
401
|
+
expect(parsed.error?.issues[0]?.message).toBe(
|
|
402
|
+
"a hidden line is never included",
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("refuses the dropped tier on the wire item", () => {
|
|
407
|
+
expect(
|
|
408
|
+
SourceSyncCandidateItemSchema.safeParse(
|
|
409
|
+
candidateItem({ visibility: "dropped", included: false }),
|
|
410
|
+
).success,
|
|
411
|
+
).toBe(false);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("carries a selection source exactly when a treatment is committed", () => {
|
|
415
|
+
const committed = { use: "attached", treatmentUnresolved: false };
|
|
416
|
+
expect(
|
|
417
|
+
SourceSyncCandidateItemSchema.safeParse(
|
|
418
|
+
candidateItem({ ...committed, selectionSource: "person" }),
|
|
419
|
+
).success,
|
|
420
|
+
).toBe(true);
|
|
421
|
+
expect(
|
|
422
|
+
SourceSyncCandidateItemSchema.safeParse(
|
|
423
|
+
candidateItem({ ...committed, selectionSource: "approval_default" }),
|
|
424
|
+
).success,
|
|
425
|
+
).toBe(true);
|
|
426
|
+
expect(
|
|
427
|
+
SourceSyncCandidateItemSchema.safeParse(
|
|
428
|
+
candidateItem({ ...committed, selectionSource: null }),
|
|
429
|
+
).success,
|
|
430
|
+
).toBe(false);
|
|
431
|
+
expect(
|
|
432
|
+
SourceSyncCandidateItemSchema.safeParse(
|
|
433
|
+
candidateItem({ use: null, selectionSource: "person" }),
|
|
434
|
+
).success,
|
|
435
|
+
).toBe(false);
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
describe("SourceSyncCandidateItemPatchSchema", () => {
|
|
440
|
+
it("bring into review is a patch of visibility alone", () => {
|
|
441
|
+
expect(
|
|
442
|
+
SourceSyncCandidateItemPatchSchema.safeParse({ visibility: "shown" })
|
|
443
|
+
.success,
|
|
444
|
+
).toBe(true);
|
|
445
|
+
expect(
|
|
446
|
+
SourceSyncCandidateItemPatchSchema.safeParse({ visibility: "hidden" })
|
|
447
|
+
.success,
|
|
448
|
+
).toBe(false);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("a patch may not both promote and choose a treatment", () => {
|
|
452
|
+
for (const patch of [
|
|
453
|
+
{ visibility: "shown", use: "attached" },
|
|
454
|
+
{ visibility: "shown", targetDocId: TARGET_DOC_ID },
|
|
455
|
+
{ use: "attached", sourceClass: "board_deck" },
|
|
456
|
+
{},
|
|
457
|
+
]) {
|
|
458
|
+
expect(SourceSyncCandidateItemPatchSchema.safeParse(patch).success).toBe(
|
|
459
|
+
false,
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
describe("SourceSyncCandidateSetResponseSchema discovery", () => {
|
|
466
|
+
it("a set response before any completed run has a null discovery block", () => {
|
|
467
|
+
expect(
|
|
468
|
+
SourceSyncCandidateSetResponseSchema.safeParse(
|
|
469
|
+
candidateSet({ discovery: null }),
|
|
470
|
+
).success,
|
|
471
|
+
).toBe(true);
|
|
472
|
+
const { discovery: _omitted, ...withoutKey } = candidateSet();
|
|
473
|
+
expect(
|
|
474
|
+
SourceSyncCandidateSetResponseSchema.safeParse(withoutKey).success,
|
|
475
|
+
).toBe(false);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it("accepts a discovery block and refuses a coverage above 100 or an unknown outcome", () => {
|
|
479
|
+
const set = candidateSet();
|
|
480
|
+
expect(SourceSyncCandidateSetResponseSchema.safeParse(set).success).toBe(
|
|
481
|
+
true,
|
|
482
|
+
);
|
|
483
|
+
for (const change of [
|
|
484
|
+
{ coveragePercent: 101 },
|
|
485
|
+
{ scanOutcome: "timed_out" },
|
|
486
|
+
{ filesDropped: -1 },
|
|
487
|
+
]) {
|
|
488
|
+
expect(
|
|
489
|
+
SourceSyncCandidateSetResponseSchema.safeParse(
|
|
490
|
+
candidateSet({ discovery: { ...set.discovery, ...change } }),
|
|
491
|
+
).success,
|
|
492
|
+
).toBe(false);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
263
497
|
describe("SourceSyncDiscoveryRunResponseSchema", () => {
|
|
264
498
|
it("refuses a workflow type other than source-sync/discover", () => {
|
|
265
499
|
expect(
|
|
@@ -281,16 +515,3 @@ describe("SourceSyncDiscoveryRunResponseSchema", () => {
|
|
|
281
515
|
).toBe(true);
|
|
282
516
|
});
|
|
283
517
|
});
|
|
284
|
-
|
|
285
|
-
describe("candidateTotalsOf", () => {
|
|
286
|
-
it("counts distinct files, never lines", () => {
|
|
287
|
-
expect(
|
|
288
|
-
candidateTotalsOf([
|
|
289
|
-
{ sourceId: "a", included: true, treatmentUnresolved: true },
|
|
290
|
-
{ sourceId: "a", included: true, treatmentUnresolved: false },
|
|
291
|
-
{ sourceId: "b", included: false, treatmentUnresolved: true },
|
|
292
|
-
{ sourceId: "c", included: true, treatmentUnresolved: false },
|
|
293
|
-
]),
|
|
294
|
-
).toEqual({ files: 3, filesIncluded: 2, filesNeedingChoice: 1 });
|
|
295
|
-
});
|
|
296
|
-
});
|
|
@@ -6,13 +6,31 @@
|
|
|
6
6
|
* order.
|
|
7
7
|
*
|
|
8
8
|
* INVARIANTS TESTED:
|
|
9
|
-
* - Each of the
|
|
9
|
+
* - Each of the twenty-three closed sets (ten Source Sync, thirteen discovery
|
|
10
|
+
* relevance) has exactly the members this release fixes
|
|
10
11
|
* - `decoy` is a source class; `unknown` is a sharing breadth
|
|
11
12
|
* - No member string appears in two sets
|
|
13
|
+
* - The four relevance reason code sets are pairwise disjoint
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
import { describe, it, expect } from "vitest";
|
|
15
17
|
|
|
18
|
+
import {
|
|
19
|
+
ATTENTION_LANES,
|
|
20
|
+
CANDIDATE_MACHINE_STATES,
|
|
21
|
+
CLASSIFICATION_STATES,
|
|
22
|
+
EVALUATION_REASON_CODES,
|
|
23
|
+
LEXICON_REASON_CODES,
|
|
24
|
+
MODEL_REASON_CODES,
|
|
25
|
+
PERSON_HOLD_REASONS,
|
|
26
|
+
RELEVANCE_REASON_KINDS,
|
|
27
|
+
SCAN_OUTCOMES,
|
|
28
|
+
SELECTION_SOURCES,
|
|
29
|
+
SIGNAL_REASON_CODES,
|
|
30
|
+
SOURCE_RELEVANCE_TIERS,
|
|
31
|
+
TARGET_AUDIENCES,
|
|
32
|
+
} from "../relevance";
|
|
33
|
+
|
|
16
34
|
import {
|
|
17
35
|
AUDIENCE_FITS,
|
|
18
36
|
SHARING_BREADTHS,
|
|
@@ -104,6 +122,108 @@ const VOCABULARIES = [
|
|
|
104
122
|
tuple: SOURCE_SYNC_SCOPE_APPROVER_KINDS,
|
|
105
123
|
members: ["org_admin", "unit_head"],
|
|
106
124
|
},
|
|
125
|
+
{
|
|
126
|
+
name: "SOURCE_RELEVANCE_TIERS",
|
|
127
|
+
tuple: SOURCE_RELEVANCE_TIERS,
|
|
128
|
+
members: ["dropped", "hidden", "shown"],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "CLASSIFICATION_STATES",
|
|
132
|
+
tuple: CLASSIFICATION_STATES,
|
|
133
|
+
members: ["unclassified", "classified"],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "RELEVANCE_REASON_KINDS",
|
|
137
|
+
tuple: RELEVANCE_REASON_KINDS,
|
|
138
|
+
members: ["lexicon", "signal", "evaluation", "model"],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "LEXICON_REASON_CODES",
|
|
142
|
+
tuple: LEXICON_REASON_CODES,
|
|
143
|
+
members: [
|
|
144
|
+
"title_term",
|
|
145
|
+
"folder_term",
|
|
146
|
+
"negative_title_term",
|
|
147
|
+
"negative_folder_term",
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "SIGNAL_REASON_CODES",
|
|
152
|
+
tuple: SIGNAL_REASON_CODES,
|
|
153
|
+
members: [
|
|
154
|
+
"folder_probe_hit",
|
|
155
|
+
"title_probe_hit",
|
|
156
|
+
"version_churn",
|
|
157
|
+
"mime_class",
|
|
158
|
+
"size_band",
|
|
159
|
+
"dated_series",
|
|
160
|
+
"recent_edit",
|
|
161
|
+
"owner_unit_head",
|
|
162
|
+
"owner_executive",
|
|
163
|
+
"sharing_breadth",
|
|
164
|
+
"duplicate_title",
|
|
165
|
+
"personal_mime",
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "EVALUATION_REASON_CODES",
|
|
170
|
+
tuple: EVALUATION_REASON_CODES,
|
|
171
|
+
members: [
|
|
172
|
+
"not_evaluated_t1",
|
|
173
|
+
"not_evaluated_t2",
|
|
174
|
+
"folder_unresolved",
|
|
175
|
+
"folder_truncated",
|
|
176
|
+
"explored_t1",
|
|
177
|
+
"explored_t2",
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "MODEL_REASON_CODES",
|
|
182
|
+
tuple: MODEL_REASON_CODES,
|
|
183
|
+
members: ["title_path_score", "peek_confirms", "peek_contradicts"],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "TARGET_AUDIENCES",
|
|
187
|
+
tuple: TARGET_AUDIENCES,
|
|
188
|
+
members: ["restricted", "unit", "org_wide"],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "ATTENTION_LANES",
|
|
192
|
+
tuple: ATTENTION_LANES,
|
|
193
|
+
members: ["exploit", "explore"],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "CANDIDATE_MACHINE_STATES",
|
|
197
|
+
tuple: CANDIDATE_MACHINE_STATES,
|
|
198
|
+
members: [
|
|
199
|
+
"fresh",
|
|
200
|
+
"refreshed",
|
|
201
|
+
"filtered_out",
|
|
202
|
+
"not_rediscovered",
|
|
203
|
+
"pending_classification",
|
|
204
|
+
"retired",
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "SELECTION_SOURCES",
|
|
209
|
+
tuple: SELECTION_SOURCES,
|
|
210
|
+
members: ["person", "approval_default"],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "PERSON_HOLD_REASONS",
|
|
214
|
+
tuple: PERSON_HOLD_REASONS,
|
|
215
|
+
members: [
|
|
216
|
+
"relevance_override",
|
|
217
|
+
"treatment_selection",
|
|
218
|
+
"target_override",
|
|
219
|
+
"manual_add",
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: "SCAN_OUTCOMES",
|
|
224
|
+
tuple: SCAN_OUTCOMES,
|
|
225
|
+
members: ["complete", "budget_exhausted", "file_ceiling", "stopped"],
|
|
226
|
+
},
|
|
107
227
|
] as const satisfies readonly {
|
|
108
228
|
name: string;
|
|
109
229
|
tuple: readonly string[];
|
|
@@ -142,4 +262,18 @@ describe("Source Sync closed sets", () => {
|
|
|
142
262
|
const all = VOCABULARIES.flatMap(({ tuple }) => [...tuple]);
|
|
143
263
|
expect(new Set(all).size).toBe(all.length);
|
|
144
264
|
});
|
|
265
|
+
|
|
266
|
+
it("the four reason code sets are pairwise disjoint", () => {
|
|
267
|
+
const codeSets: readonly (readonly string[])[] = [
|
|
268
|
+
LEXICON_REASON_CODES,
|
|
269
|
+
SIGNAL_REASON_CODES,
|
|
270
|
+
EVALUATION_REASON_CODES,
|
|
271
|
+
MODEL_REASON_CODES,
|
|
272
|
+
];
|
|
273
|
+
codeSets.forEach((a, i) => {
|
|
274
|
+
codeSets.slice(i + 1).forEach((b) => {
|
|
275
|
+
expect(a.filter((code) => b.includes(code))).toEqual([]);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
});
|
|
145
279
|
});
|
|
@@ -12,15 +12,19 @@ import type {
|
|
|
12
12
|
SourceSyncCandidateTotals,
|
|
13
13
|
} from "./schemas";
|
|
14
14
|
|
|
15
|
+
/** The relevance facts the count reads (control ADR source-discovery-relevance-funnel). */
|
|
16
|
+
type RelevanceFacts = "visibility" | "machineState";
|
|
17
|
+
|
|
15
18
|
/** The item facts the count reads. */
|
|
16
19
|
export type CandidateTotalsInput = Pick<
|
|
17
20
|
SourceSyncCandidateItem,
|
|
18
|
-
"sourceId" | "included" | "treatmentUnresolved"
|
|
21
|
+
"sourceId" | "included" | "treatmentUnresolved" | RelevanceFacts
|
|
19
22
|
>;
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* Included when any of the file's lines is; in need of a choice when any
|
|
23
|
-
* included line's treatment is unresolved.
|
|
26
|
+
* included line's treatment is unresolved. Hidden only when EVERY line of the
|
|
27
|
+
* file is hidden; not rediscovered / pending classification when ANY line is.
|
|
24
28
|
*/
|
|
25
29
|
export function candidateTotalsOf(
|
|
26
30
|
items: readonly CandidateTotalsInput[],
|
|
@@ -28,15 +32,29 @@ export function candidateTotalsOf(
|
|
|
28
32
|
const files = new Set<string>();
|
|
29
33
|
const included = new Set<string>();
|
|
30
34
|
const needingChoice = new Set<string>();
|
|
35
|
+
const anyShown = new Set<string>();
|
|
36
|
+
const notRediscovered = new Set<string>();
|
|
37
|
+
const pendingClassification = new Set<string>();
|
|
31
38
|
for (const item of items) {
|
|
32
39
|
files.add(item.sourceId);
|
|
40
|
+
if (item.visibility === "shown") anyShown.add(item.sourceId);
|
|
41
|
+
if (item.machineState === "not_rediscovered") {
|
|
42
|
+
notRediscovered.add(item.sourceId);
|
|
43
|
+
}
|
|
44
|
+
if (item.machineState === "pending_classification") {
|
|
45
|
+
pendingClassification.add(item.sourceId);
|
|
46
|
+
}
|
|
33
47
|
if (!item.included) continue;
|
|
34
48
|
included.add(item.sourceId);
|
|
35
49
|
if (item.treatmentUnresolved) needingChoice.add(item.sourceId);
|
|
36
50
|
}
|
|
51
|
+
const hidden = [...files].filter((id) => !anyShown.has(id)).length;
|
|
37
52
|
return {
|
|
38
53
|
files: files.size,
|
|
39
54
|
filesIncluded: included.size,
|
|
40
55
|
filesNeedingChoice: needingChoice.size,
|
|
56
|
+
filesHidden: hidden,
|
|
57
|
+
filesNotRediscovered: notRediscovered.size,
|
|
58
|
+
filesPendingClassification: pendingClassification.size,
|
|
41
59
|
};
|
|
42
60
|
}
|
package/src/source-sync/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Re-exports the closed Source Sync vocabularies (the sets the backend persists
|
|
5
5
|
* and mirrors as database CHECK constraints, and the app handles exhaustively)
|
|
6
|
-
* the discovery and candidate-set response schemas, the candidate-edit and
|
|
6
|
+
* including the discovery relevance vocabulary, the discovery and candidate-set response schemas, the candidate-edit and
|
|
7
7
|
* per-scope approval request and response schemas, the per-document change
|
|
8
8
|
* plan and apply schemas, the source-resolution rule schemas, the staleness
|
|
9
9
|
* finding and standing-order schemas, and the Drive-reach status and upgrade
|
|
@@ -44,6 +44,45 @@ export {
|
|
|
44
44
|
SOURCE_SYNC_SCOPE_APPROVER_KINDS,
|
|
45
45
|
} from "./vocabulary";
|
|
46
46
|
|
|
47
|
+
export type {
|
|
48
|
+
SourceRelevanceTier,
|
|
49
|
+
ClassificationState,
|
|
50
|
+
RelevanceReasonKind,
|
|
51
|
+
LexiconReasonCode,
|
|
52
|
+
SignalReasonCode,
|
|
53
|
+
EvaluationReasonCode,
|
|
54
|
+
ModelReasonCode,
|
|
55
|
+
TargetAudience,
|
|
56
|
+
AttentionLane,
|
|
57
|
+
CandidateMachineState,
|
|
58
|
+
SelectionSource,
|
|
59
|
+
PersonHoldReason,
|
|
60
|
+
ScanOutcome,
|
|
61
|
+
RelevanceReason,
|
|
62
|
+
CandidateAttention,
|
|
63
|
+
CandidateDiscoverySummary,
|
|
64
|
+
} from "./relevance";
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
SOURCE_RELEVANCE_TIERS,
|
|
68
|
+
CLASSIFICATION_STATES,
|
|
69
|
+
RELEVANCE_REASON_KINDS,
|
|
70
|
+
LEXICON_REASON_CODES,
|
|
71
|
+
SIGNAL_REASON_CODES,
|
|
72
|
+
EVALUATION_REASON_CODES,
|
|
73
|
+
MODEL_REASON_CODES,
|
|
74
|
+
TARGET_AUDIENCES,
|
|
75
|
+
ATTENTION_LANES,
|
|
76
|
+
CANDIDATE_MACHINE_STATES,
|
|
77
|
+
SELECTION_SOURCES,
|
|
78
|
+
PERSON_HOLD_REASONS,
|
|
79
|
+
SCAN_OUTCOMES,
|
|
80
|
+
RelevanceReasonSchema,
|
|
81
|
+
CandidateAttentionSchema,
|
|
82
|
+
CandidateDiscoverySummarySchema,
|
|
83
|
+
refineCandidateRelevance,
|
|
84
|
+
} from "./relevance";
|
|
85
|
+
|
|
47
86
|
export type {
|
|
48
87
|
SourceSyncDiscoveryRunResponse,
|
|
49
88
|
SourceSyncCandidateItem,
|