@kontourai/survey 0.4.4 → 0.4.6
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/README.md
CHANGED
|
@@ -871,11 +871,18 @@ const learning = buildSurveyLearningProjections(surveyInput);
|
|
|
871
871
|
const trustInput = buildSurveyTrustInput(surveyInput);
|
|
872
872
|
```
|
|
873
873
|
|
|
874
|
-
Learning projections are product-neutral `learning.*` records. Survey
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
874
|
+
Learning projections are product-neutral `learning.*` records. Survey emits
|
|
875
|
+
`learning.rejected-candidate` from structured candidate rejection data such as
|
|
876
|
+
non-empty `Candidate.rejectionReason` values or a candidate-specific
|
|
877
|
+
`ReviewOutcome.status === "rejected"` outcome with rationale. When both exist,
|
|
878
|
+
Survey emits one rejected-candidate projection enriched with candidate and
|
|
879
|
+
review outcome context.
|
|
880
|
+
Ordinary rejected candidates do not emit `learning.comfort-zone`.
|
|
881
|
+
|
|
882
|
+
Survey also emits `learning.comfort-zone` from structured
|
|
883
|
+
`ReviewOutcome.withinComfortZone === false` data and `learning.escalation` from
|
|
884
|
+
unresolved `EscalationRecord`s, including unattached records that producer
|
|
885
|
+
tooling can route but Surface cannot attach to a claim event.
|
|
879
886
|
|
|
880
887
|
These projections are producer/review workflow and evaluation signals. They are
|
|
881
888
|
not claims about truth or veracity, not Surface claim status, not evidence, and
|
|
@@ -129,6 +129,144 @@ export declare const publicDirectoryReviewItemFixture: {
|
|
|
129
129
|
reviewDecisionName: string;
|
|
130
130
|
};
|
|
131
131
|
};
|
|
132
|
+
export declare const regulatedRuleConflictReviewItemFixture: {
|
|
133
|
+
apiVersion: "survey.kontourai.io/v1alpha1";
|
|
134
|
+
kind: "ReviewItem";
|
|
135
|
+
metadata: {
|
|
136
|
+
name: string;
|
|
137
|
+
labels: {
|
|
138
|
+
domain: string;
|
|
139
|
+
};
|
|
140
|
+
producer: {
|
|
141
|
+
displayName: string;
|
|
142
|
+
jurisdiction: string;
|
|
143
|
+
reportingPeriod: string;
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
spec: {
|
|
147
|
+
target: string;
|
|
148
|
+
selectedCandidateId: string;
|
|
149
|
+
candidateSetStatus: "conflict";
|
|
150
|
+
rationale: string;
|
|
151
|
+
projection: {
|
|
152
|
+
candidateSetId: string;
|
|
153
|
+
};
|
|
154
|
+
producerPolicy: {
|
|
155
|
+
decisionMode: string;
|
|
156
|
+
sourceAuthorityProjection: string;
|
|
157
|
+
feedbackTags: string[];
|
|
158
|
+
};
|
|
159
|
+
candidates: ({
|
|
160
|
+
id: string;
|
|
161
|
+
role: "current";
|
|
162
|
+
value: number;
|
|
163
|
+
confidence: number;
|
|
164
|
+
source: {
|
|
165
|
+
sourceId: string;
|
|
166
|
+
sourceRef: string;
|
|
167
|
+
kind: "manual-entry";
|
|
168
|
+
observedAt: string;
|
|
169
|
+
locatorScheme: "structured-field";
|
|
170
|
+
};
|
|
171
|
+
locator: {
|
|
172
|
+
scheme: "structured-field";
|
|
173
|
+
locator: string;
|
|
174
|
+
excerpt: string;
|
|
175
|
+
};
|
|
176
|
+
extraction: {
|
|
177
|
+
extractionId: string;
|
|
178
|
+
target: string;
|
|
179
|
+
confidence: number;
|
|
180
|
+
extractor: string;
|
|
181
|
+
extractedAt: string;
|
|
182
|
+
};
|
|
183
|
+
claimTarget: {
|
|
184
|
+
claimId: string;
|
|
185
|
+
subjectType: string;
|
|
186
|
+
subjectId: string;
|
|
187
|
+
surface: string;
|
|
188
|
+
claimType: string;
|
|
189
|
+
fieldOrBehavior: string;
|
|
190
|
+
impactLevel: "high";
|
|
191
|
+
evidenceType: "human_attestation";
|
|
192
|
+
evidenceMethod: "attestation";
|
|
193
|
+
collectedBy: string;
|
|
194
|
+
};
|
|
195
|
+
projection: {
|
|
196
|
+
rawSourceId: string;
|
|
197
|
+
extractionId: string;
|
|
198
|
+
candidateSetId: string;
|
|
199
|
+
candidateId: string;
|
|
200
|
+
claimId: string;
|
|
201
|
+
};
|
|
202
|
+
producer: {
|
|
203
|
+
status: string;
|
|
204
|
+
sourceSection?: undefined;
|
|
205
|
+
sourcePage?: undefined;
|
|
206
|
+
sourceAuthority?: undefined;
|
|
207
|
+
};
|
|
208
|
+
sourceRank?: undefined;
|
|
209
|
+
} | {
|
|
210
|
+
id: string;
|
|
211
|
+
role: "proposed";
|
|
212
|
+
value: number;
|
|
213
|
+
confidence: number;
|
|
214
|
+
sourceRank: number;
|
|
215
|
+
source: {
|
|
216
|
+
sourceId: string;
|
|
217
|
+
sourceRef: string;
|
|
218
|
+
kind: "uploaded-document";
|
|
219
|
+
observedAt: string;
|
|
220
|
+
locatorScheme: "pdf";
|
|
221
|
+
};
|
|
222
|
+
locator: {
|
|
223
|
+
scheme: "pdf";
|
|
224
|
+
locator: string;
|
|
225
|
+
excerpt: string;
|
|
226
|
+
};
|
|
227
|
+
extraction: {
|
|
228
|
+
extractionId: string;
|
|
229
|
+
target: string;
|
|
230
|
+
confidence: number;
|
|
231
|
+
extractor: string;
|
|
232
|
+
extractedAt: string;
|
|
233
|
+
};
|
|
234
|
+
claimTarget: {
|
|
235
|
+
claimId: string;
|
|
236
|
+
subjectType: string;
|
|
237
|
+
subjectId: string;
|
|
238
|
+
surface: string;
|
|
239
|
+
claimType: string;
|
|
240
|
+
fieldOrBehavior: string;
|
|
241
|
+
impactLevel: "high";
|
|
242
|
+
evidenceType: "policy_rule";
|
|
243
|
+
evidenceMethod: "extraction";
|
|
244
|
+
collectedBy: string;
|
|
245
|
+
};
|
|
246
|
+
projection: {
|
|
247
|
+
rawSourceId: string;
|
|
248
|
+
extractionId: string;
|
|
249
|
+
candidateSetId: string;
|
|
250
|
+
candidateId: string;
|
|
251
|
+
claimId: string;
|
|
252
|
+
};
|
|
253
|
+
producer: {
|
|
254
|
+
sourceSection: string;
|
|
255
|
+
sourcePage: number;
|
|
256
|
+
sourceAuthority: {
|
|
257
|
+
authorityClass: string;
|
|
258
|
+
declaredBy: string;
|
|
259
|
+
scope: string;
|
|
260
|
+
};
|
|
261
|
+
status?: undefined;
|
|
262
|
+
};
|
|
263
|
+
})[];
|
|
264
|
+
};
|
|
265
|
+
status: {
|
|
266
|
+
observedCandidateCount: number;
|
|
267
|
+
selectedCandidateId: string;
|
|
268
|
+
};
|
|
269
|
+
};
|
|
132
270
|
export declare const reviewWorkbenchQueueFixtures: (ReviewItem | {
|
|
133
271
|
apiVersion: "survey.kontourai.io/v1alpha1";
|
|
134
272
|
kind: "ReviewItem";
|
|
@@ -258,4 +396,141 @@ export declare const reviewWorkbenchQueueFixtures: (ReviewItem | {
|
|
|
258
396
|
selectedCandidateId: string;
|
|
259
397
|
reviewDecisionName: string;
|
|
260
398
|
};
|
|
399
|
+
} | {
|
|
400
|
+
apiVersion: "survey.kontourai.io/v1alpha1";
|
|
401
|
+
kind: "ReviewItem";
|
|
402
|
+
metadata: {
|
|
403
|
+
name: string;
|
|
404
|
+
labels: {
|
|
405
|
+
domain: string;
|
|
406
|
+
};
|
|
407
|
+
producer: {
|
|
408
|
+
displayName: string;
|
|
409
|
+
jurisdiction: string;
|
|
410
|
+
reportingPeriod: string;
|
|
411
|
+
};
|
|
412
|
+
};
|
|
413
|
+
spec: {
|
|
414
|
+
target: string;
|
|
415
|
+
selectedCandidateId: string;
|
|
416
|
+
candidateSetStatus: "conflict";
|
|
417
|
+
rationale: string;
|
|
418
|
+
projection: {
|
|
419
|
+
candidateSetId: string;
|
|
420
|
+
};
|
|
421
|
+
producerPolicy: {
|
|
422
|
+
decisionMode: string;
|
|
423
|
+
sourceAuthorityProjection: string;
|
|
424
|
+
feedbackTags: string[];
|
|
425
|
+
};
|
|
426
|
+
candidates: ({
|
|
427
|
+
id: string;
|
|
428
|
+
role: "current";
|
|
429
|
+
value: number;
|
|
430
|
+
confidence: number;
|
|
431
|
+
source: {
|
|
432
|
+
sourceId: string;
|
|
433
|
+
sourceRef: string;
|
|
434
|
+
kind: "manual-entry";
|
|
435
|
+
observedAt: string;
|
|
436
|
+
locatorScheme: "structured-field";
|
|
437
|
+
};
|
|
438
|
+
locator: {
|
|
439
|
+
scheme: "structured-field";
|
|
440
|
+
locator: string;
|
|
441
|
+
excerpt: string;
|
|
442
|
+
};
|
|
443
|
+
extraction: {
|
|
444
|
+
extractionId: string;
|
|
445
|
+
target: string;
|
|
446
|
+
confidence: number;
|
|
447
|
+
extractor: string;
|
|
448
|
+
extractedAt: string;
|
|
449
|
+
};
|
|
450
|
+
claimTarget: {
|
|
451
|
+
claimId: string;
|
|
452
|
+
subjectType: string;
|
|
453
|
+
subjectId: string;
|
|
454
|
+
surface: string;
|
|
455
|
+
claimType: string;
|
|
456
|
+
fieldOrBehavior: string;
|
|
457
|
+
impactLevel: "high";
|
|
458
|
+
evidenceType: "human_attestation";
|
|
459
|
+
evidenceMethod: "attestation";
|
|
460
|
+
collectedBy: string;
|
|
461
|
+
};
|
|
462
|
+
projection: {
|
|
463
|
+
rawSourceId: string;
|
|
464
|
+
extractionId: string;
|
|
465
|
+
candidateSetId: string;
|
|
466
|
+
candidateId: string;
|
|
467
|
+
claimId: string;
|
|
468
|
+
};
|
|
469
|
+
producer: {
|
|
470
|
+
status: string;
|
|
471
|
+
sourceSection?: undefined;
|
|
472
|
+
sourcePage?: undefined;
|
|
473
|
+
sourceAuthority?: undefined;
|
|
474
|
+
};
|
|
475
|
+
sourceRank?: undefined;
|
|
476
|
+
} | {
|
|
477
|
+
id: string;
|
|
478
|
+
role: "proposed";
|
|
479
|
+
value: number;
|
|
480
|
+
confidence: number;
|
|
481
|
+
sourceRank: number;
|
|
482
|
+
source: {
|
|
483
|
+
sourceId: string;
|
|
484
|
+
sourceRef: string;
|
|
485
|
+
kind: "uploaded-document";
|
|
486
|
+
observedAt: string;
|
|
487
|
+
locatorScheme: "pdf";
|
|
488
|
+
};
|
|
489
|
+
locator: {
|
|
490
|
+
scheme: "pdf";
|
|
491
|
+
locator: string;
|
|
492
|
+
excerpt: string;
|
|
493
|
+
};
|
|
494
|
+
extraction: {
|
|
495
|
+
extractionId: string;
|
|
496
|
+
target: string;
|
|
497
|
+
confidence: number;
|
|
498
|
+
extractor: string;
|
|
499
|
+
extractedAt: string;
|
|
500
|
+
};
|
|
501
|
+
claimTarget: {
|
|
502
|
+
claimId: string;
|
|
503
|
+
subjectType: string;
|
|
504
|
+
subjectId: string;
|
|
505
|
+
surface: string;
|
|
506
|
+
claimType: string;
|
|
507
|
+
fieldOrBehavior: string;
|
|
508
|
+
impactLevel: "high";
|
|
509
|
+
evidenceType: "policy_rule";
|
|
510
|
+
evidenceMethod: "extraction";
|
|
511
|
+
collectedBy: string;
|
|
512
|
+
};
|
|
513
|
+
projection: {
|
|
514
|
+
rawSourceId: string;
|
|
515
|
+
extractionId: string;
|
|
516
|
+
candidateSetId: string;
|
|
517
|
+
candidateId: string;
|
|
518
|
+
claimId: string;
|
|
519
|
+
};
|
|
520
|
+
producer: {
|
|
521
|
+
sourceSection: string;
|
|
522
|
+
sourcePage: number;
|
|
523
|
+
sourceAuthority: {
|
|
524
|
+
authorityClass: string;
|
|
525
|
+
declaredBy: string;
|
|
526
|
+
scope: string;
|
|
527
|
+
};
|
|
528
|
+
status?: undefined;
|
|
529
|
+
};
|
|
530
|
+
})[];
|
|
531
|
+
};
|
|
532
|
+
status: {
|
|
533
|
+
observedCandidateCount: number;
|
|
534
|
+
selectedCandidateId: string;
|
|
535
|
+
};
|
|
261
536
|
})[];
|
|
@@ -131,6 +131,142 @@ export const publicDirectoryReviewItemFixture = {
|
|
|
131
131
|
reviewDecisionName: "public-directory-availability-operator-decision",
|
|
132
132
|
},
|
|
133
133
|
};
|
|
134
|
+
export const regulatedRuleConflictReviewItemFixture = {
|
|
135
|
+
apiVersion: reviewResourceApiVersion,
|
|
136
|
+
kind: "ReviewItem",
|
|
137
|
+
metadata: {
|
|
138
|
+
name: "regulated-rule-conflict-standard-threshold",
|
|
139
|
+
labels: {
|
|
140
|
+
domain: "regulated-rule-source",
|
|
141
|
+
},
|
|
142
|
+
producer: {
|
|
143
|
+
displayName: "Regulated Rule Review",
|
|
144
|
+
jurisdiction: "example-jurisdiction",
|
|
145
|
+
reportingPeriod: "2026",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
spec: {
|
|
149
|
+
target: "standardThreshold",
|
|
150
|
+
selectedCandidateId: "regulated-rule-conflict-standard-threshold:candidate:current",
|
|
151
|
+
candidateSetStatus: "conflict",
|
|
152
|
+
rationale: "A newly extracted source candidate conflicts with the currently managed rule value.",
|
|
153
|
+
projection: {
|
|
154
|
+
candidateSetId: "regulated-rule-conflict-standard-threshold:candidate-set",
|
|
155
|
+
},
|
|
156
|
+
producerPolicy: {
|
|
157
|
+
decisionMode: "keep-current",
|
|
158
|
+
sourceAuthorityProjection: "only-for-selected-source-backed-value",
|
|
159
|
+
feedbackTags: ["rule-conflict", "source-extraction-review"],
|
|
160
|
+
},
|
|
161
|
+
candidates: [
|
|
162
|
+
{
|
|
163
|
+
id: "regulated-rule-conflict-standard-threshold:candidate:current",
|
|
164
|
+
role: "current",
|
|
165
|
+
value: 15000,
|
|
166
|
+
confidence: 1,
|
|
167
|
+
source: {
|
|
168
|
+
sourceId: "regulated-rule-conflict-standard-threshold:source:current",
|
|
169
|
+
sourceRef: "survey-example://rules/example-jurisdiction/2026/standardThreshold",
|
|
170
|
+
kind: "manual-entry",
|
|
171
|
+
observedAt: "2026-06-03T00:00:00.000Z",
|
|
172
|
+
locatorScheme: "structured-field",
|
|
173
|
+
},
|
|
174
|
+
locator: {
|
|
175
|
+
scheme: "structured-field",
|
|
176
|
+
locator: "managed-rules:path=standardThreshold",
|
|
177
|
+
excerpt: "Current managed rule value.",
|
|
178
|
+
},
|
|
179
|
+
extraction: {
|
|
180
|
+
extractionId: "regulated-rule-conflict-standard-threshold:extraction:current",
|
|
181
|
+
target: "standardThreshold",
|
|
182
|
+
confidence: 1,
|
|
183
|
+
extractor: "example-rule-manager",
|
|
184
|
+
extractedAt: "2026-06-03T00:00:00.000Z",
|
|
185
|
+
},
|
|
186
|
+
claimTarget: {
|
|
187
|
+
claimId: "regulated-rule.example-jurisdiction.2026.standard-threshold.current",
|
|
188
|
+
subjectType: "regulated-rule-source",
|
|
189
|
+
subjectId: "example-jurisdiction:2026:standardThreshold",
|
|
190
|
+
surface: "regulated.rules",
|
|
191
|
+
claimType: "regulated.rule-source-value",
|
|
192
|
+
fieldOrBehavior: "standardThreshold",
|
|
193
|
+
impactLevel: "high",
|
|
194
|
+
evidenceType: "human_attestation",
|
|
195
|
+
evidenceMethod: "attestation",
|
|
196
|
+
collectedBy: "example-rule-manager",
|
|
197
|
+
},
|
|
198
|
+
projection: {
|
|
199
|
+
rawSourceId: "regulated-rule-conflict-standard-threshold:source:current",
|
|
200
|
+
extractionId: "regulated-rule-conflict-standard-threshold:extraction:current",
|
|
201
|
+
candidateSetId: "regulated-rule-conflict-standard-threshold:candidate-set",
|
|
202
|
+
candidateId: "regulated-rule-conflict-standard-threshold:projection:current",
|
|
203
|
+
claimId: "regulated-rule.example-jurisdiction.2026.standard-threshold.current",
|
|
204
|
+
},
|
|
205
|
+
producer: {
|
|
206
|
+
status: "current-managed-value",
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: "regulated-rule-conflict-standard-threshold:candidate:proposed",
|
|
211
|
+
role: "proposed",
|
|
212
|
+
value: 16000,
|
|
213
|
+
confidence: 0.95,
|
|
214
|
+
sourceRank: 1,
|
|
215
|
+
source: {
|
|
216
|
+
sourceId: "regulated-rule-conflict-standard-threshold:source:proposed",
|
|
217
|
+
sourceRef: "https://example.test/regulatory-bulletins/2026-thresholds.pdf",
|
|
218
|
+
kind: "uploaded-document",
|
|
219
|
+
observedAt: "2026-06-03T00:30:00.000Z",
|
|
220
|
+
locatorScheme: "pdf",
|
|
221
|
+
},
|
|
222
|
+
locator: {
|
|
223
|
+
scheme: "pdf",
|
|
224
|
+
locator: "pdf:page=12;section=Standard%20Threshold;path=standardThreshold",
|
|
225
|
+
excerpt: "Example Individual Standard Threshold $16,000",
|
|
226
|
+
},
|
|
227
|
+
extraction: {
|
|
228
|
+
extractionId: "regulated-rule-conflict-standard-threshold:extraction:proposed",
|
|
229
|
+
target: "standardThreshold",
|
|
230
|
+
confidence: 0.95,
|
|
231
|
+
extractor: "example-rule-source-parser",
|
|
232
|
+
extractedAt: "2026-06-03T00:30:00.000Z",
|
|
233
|
+
},
|
|
234
|
+
claimTarget: {
|
|
235
|
+
claimId: "regulated-rule.example-jurisdiction.2026.standard-threshold.proposed",
|
|
236
|
+
subjectType: "regulated-rule-source",
|
|
237
|
+
subjectId: "example-jurisdiction:2026:standardThreshold",
|
|
238
|
+
surface: "regulated.rules",
|
|
239
|
+
claimType: "regulated.rule-source-value",
|
|
240
|
+
fieldOrBehavior: "standardThreshold",
|
|
241
|
+
impactLevel: "high",
|
|
242
|
+
evidenceType: "policy_rule",
|
|
243
|
+
evidenceMethod: "extraction",
|
|
244
|
+
collectedBy: "example-rule-source-parser",
|
|
245
|
+
},
|
|
246
|
+
projection: {
|
|
247
|
+
rawSourceId: "regulated-rule-conflict-standard-threshold:source:proposed",
|
|
248
|
+
extractionId: "regulated-rule-conflict-standard-threshold:extraction:proposed",
|
|
249
|
+
candidateSetId: "regulated-rule-conflict-standard-threshold:candidate-set",
|
|
250
|
+
candidateId: "regulated-rule-conflict-standard-threshold:projection:proposed",
|
|
251
|
+
claimId: "regulated-rule.example-jurisdiction.2026.standard-threshold.proposed",
|
|
252
|
+
},
|
|
253
|
+
producer: {
|
|
254
|
+
sourceSection: "Standard Threshold",
|
|
255
|
+
sourcePage: 12,
|
|
256
|
+
sourceAuthority: {
|
|
257
|
+
authorityClass: "official_publication",
|
|
258
|
+
declaredBy: "Example regulatory source registry",
|
|
259
|
+
scope: "standardThreshold rule value for example-jurisdiction 2026",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
status: {
|
|
266
|
+
observedCandidateCount: 2,
|
|
267
|
+
selectedCandidateId: "regulated-rule-conflict-standard-threshold:candidate:current",
|
|
268
|
+
},
|
|
269
|
+
};
|
|
134
270
|
function queueFixture(name, target, currentValue, proposedValue, candidateSetStatus, feedbackTags) {
|
|
135
271
|
return {
|
|
136
272
|
...publicDirectoryReviewItemFixture,
|
|
@@ -196,4 +332,5 @@ export const reviewWorkbenchQueueFixtures = [
|
|
|
196
332
|
publicDirectoryReviewItemFixture,
|
|
197
333
|
queueFixture("public-directory-address", "streetAddress", "100 Main Street", "102 Main Street", "needs-review", ["address-change", "producer-escalation-candidate"]),
|
|
198
334
|
queueFixture("public-directory-license", "licenseStatus", "ACTIVE", "EXPIRED", "escalated", ["licensing", "manual-review-required"]),
|
|
335
|
+
regulatedRuleConflictReviewItemFixture,
|
|
199
336
|
];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SurveyInput } from "./types.js";
|
|
2
|
-
export type LearningProjectionKind = "learning.comfort-zone" | "learning.escalation";
|
|
3
|
-
export type LearningProjectionSignal = "comfort-zone.outside" | "escalation.unresolved";
|
|
2
|
+
export type LearningProjectionKind = "learning.comfort-zone" | "learning.escalation" | "learning.rejected-candidate";
|
|
3
|
+
export type LearningProjectionSignal = "comfort-zone.outside" | "escalation.unresolved" | "rejected-candidate.reason";
|
|
4
4
|
export type LearningProjectionSeverity = "info" | "attention";
|
|
5
5
|
export interface LearningProjection {
|
|
6
6
|
id: string;
|
|
@@ -1,7 +1,44 @@
|
|
|
1
1
|
export function buildSurveyLearningProjections(input) {
|
|
2
2
|
const claimsByCandidateSet = groupBy(input.claims, (claim) => claim.candidateSetId);
|
|
3
3
|
const candidateSetTargets = new Map(input.candidateSets.map((candidateSet) => [candidateSet.id, candidateSet.target]));
|
|
4
|
+
const extractionsById = new Map(input.extractions.map((extraction) => [extraction.id, extraction]));
|
|
5
|
+
const rejectedReviewsByCandidate = groupBy(input.reviewOutcomes.filter((reviewOutcome) => reviewOutcome.status === "rejected" && reviewOutcome.candidateId), (reviewOutcome) => `${reviewOutcome.candidateSetId}:${reviewOutcome.candidateId}`);
|
|
4
6
|
const projections = [];
|
|
7
|
+
for (const candidateSet of input.candidateSets) {
|
|
8
|
+
for (const candidate of candidateSet.candidates) {
|
|
9
|
+
const rejectedReviews = rejectedReviewsByCandidate.get(`${candidateSet.id}:${candidate.id}`) ?? [];
|
|
10
|
+
const candidateReason = normalizeText(candidate.rejectionReason);
|
|
11
|
+
const reviewOutcome = rejectedReviews[0];
|
|
12
|
+
const reviewRationale = normalizeText(reviewOutcome?.rationale);
|
|
13
|
+
const rejectionReason = candidateReason ?? reviewRationale;
|
|
14
|
+
if (!rejectionReason)
|
|
15
|
+
continue;
|
|
16
|
+
const claim = claimsByCandidateSet.get(candidateSet.id)?.find((candidateSetClaim) => candidateSetClaim.candidateId === candidate.id);
|
|
17
|
+
const extraction = extractionsById.get(candidate.extractionId);
|
|
18
|
+
projections.push({
|
|
19
|
+
id: `${candidateSet.id}.${candidate.id}.learning.rejected-candidate`,
|
|
20
|
+
kind: "learning.rejected-candidate",
|
|
21
|
+
source: input.source,
|
|
22
|
+
createdAt: reviewOutcome?.reviewedAt ?? extraction?.extractedAt ?? input.generatedAt,
|
|
23
|
+
target: candidateSet.target,
|
|
24
|
+
claimId: claim?.id,
|
|
25
|
+
reviewOutcomeId: reviewOutcome?.id,
|
|
26
|
+
signal: "rejected-candidate.reason",
|
|
27
|
+
severity: "info",
|
|
28
|
+
summary: `Rejected candidate reason: ${rejectionReason}`,
|
|
29
|
+
metadata: {
|
|
30
|
+
rejectedCandidate: rejectedCandidateMetadata({
|
|
31
|
+
candidateSet,
|
|
32
|
+
candidate,
|
|
33
|
+
extraction,
|
|
34
|
+
reviewOutcome,
|
|
35
|
+
claim,
|
|
36
|
+
rejectionReason,
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
5
42
|
for (const reviewOutcome of input.reviewOutcomes) {
|
|
6
43
|
if (reviewOutcome.withinComfortZone !== false)
|
|
7
44
|
continue;
|
|
@@ -48,6 +85,20 @@ export function buildSurveyLearningProjections(input) {
|
|
|
48
85
|
}
|
|
49
86
|
return projections;
|
|
50
87
|
}
|
|
88
|
+
function rejectedCandidateMetadata(input) {
|
|
89
|
+
return {
|
|
90
|
+
candidateId: input.candidate.id,
|
|
91
|
+
candidateSetId: input.candidateSet.id,
|
|
92
|
+
target: input.candidateSet.target,
|
|
93
|
+
rejectionReason: input.rejectionReason,
|
|
94
|
+
...(input.reviewOutcome ? { reviewOutcomeId: input.reviewOutcome.id, reviewStatus: input.reviewOutcome.status } : {}),
|
|
95
|
+
...(input.claim ? { claimId: input.claim.id } : {}),
|
|
96
|
+
...(input.extraction ? { extractionId: input.extraction.id } : {}),
|
|
97
|
+
...(input.candidateSet.selectedCandidateId ? { selectedCandidateId: input.candidateSet.selectedCandidateId } : {}),
|
|
98
|
+
...(input.candidate.rejectionReason ? { candidateRejectionReason: input.candidate.rejectionReason } : {}),
|
|
99
|
+
...(input.reviewOutcome?.rationale ? { reviewRationale: input.reviewOutcome.rationale } : {}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
51
102
|
function findClaimForReview(claims, reviewOutcome) {
|
|
52
103
|
if (reviewOutcome.candidateId) {
|
|
53
104
|
return claims.find((claim) => claim.candidateId === reviewOutcome.candidateId);
|
|
@@ -75,3 +126,7 @@ function groupBy(items, getKey) {
|
|
|
75
126
|
}
|
|
76
127
|
return map;
|
|
77
128
|
}
|
|
129
|
+
function normalizeText(value) {
|
|
130
|
+
const trimmed = value?.trim();
|
|
131
|
+
return trimmed ? trimmed : undefined;
|
|
132
|
+
}
|
package/package.json
CHANGED