@anthusai/papyrus 1.4.0-next.6 → 1.4.0-next.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
- import type { AssignmentRecord, ReferenceRecord } from "./category-repository";
1
+ import type { AssignmentRecord, MessageRecord, ReferenceRecord, SemanticRelationRecord } from "./category-repository";
2
+ import { SHADOW_AGREEMENT_SCHEMA, type ShadowAgreement } from "./cyclotron-suggestions";
2
3
  import type { EditableArticle } from "./newsroom-articles";
3
4
  import type { MediaAssetRecord } from "./newsroom-media";
4
5
  import { EMPTY_STORY_REPORTING_TRAIL, type StoryReportingTrail, type TrailVersion } from "./story-reporting-trail";
@@ -166,3 +167,120 @@ export function createLocalPreviewReportingTrail(lineageId: string): StoryReport
166
167
  export function createLocalPreviewMediaAssets(_itemId: string): MediaAssetRecord[] {
167
168
  return [];
168
169
  }
170
+
171
+ type PreviewSuggestionPlan = {
172
+ label: "include" | "exclude";
173
+ confidence: number;
174
+ curation: "none" | "human" | "service" | "delegated";
175
+ provenance: boolean;
176
+ };
177
+
178
+ const PREVIEW_SUGGESTION_PLANS: readonly PreviewSuggestionPlan[] = [
179
+ { label: "include", confidence: 0.91, curation: "none", provenance: true },
180
+ { label: "include", confidence: 0.86, curation: "human", provenance: true },
181
+ { label: "exclude", confidence: 0.72, curation: "service", provenance: true },
182
+ { label: "exclude", confidence: 0.88, curation: "delegated", provenance: true },
183
+ { label: "exclude", confidence: 0.64, curation: "none", provenance: false },
184
+ ];
185
+
186
+ const PREVIEW_CYCLOTRON_ID = "papyrus-relevance";
187
+
188
+ /**
189
+ * Fixture data for reviewing the cyclotron suggestion UI without a backend:
190
+ * suggestions with provenance on the first references, a mix of human and
191
+ * non-human curation messages, and a live-agreement payload.
192
+ */
193
+ export function createLocalPreviewCyclotronFixtures(references: ReferenceRecord[]): {
194
+ relations: SemanticRelationRecord[];
195
+ messages: MessageRecord[];
196
+ shadowAgreement: ShadowAgreement;
197
+ } {
198
+ const relations: SemanticRelationRecord[] = [];
199
+ const messages: MessageRecord[] = [];
200
+ references.slice(0, PREVIEW_SUGGESTION_PLANS.length).forEach((reference, index) => {
201
+ const plan = PREVIEW_SUGGESTION_PLANS[index];
202
+ const lineageId = reference.lineageId ?? reference.id;
203
+ const decidedAt = isoTimestampHoursBeforeBase(40 - index);
204
+ relations.push({
205
+ id: `preview-relevance-relation-${index}`,
206
+ relationState: "current",
207
+ predicate: "relevance_decision_is",
208
+ subjectKind: "reference",
209
+ subjectId: reference.id,
210
+ subjectLineageId: lineageId,
211
+ objectKind: "semanticNode",
212
+ objectId: `semantic-node-relevance-${plan.label}-v1`,
213
+ objectLineageId: `semantic-node-relevance-${plan.label}`,
214
+ subjectStateKey: `reference#${lineageId}#current`,
215
+ objectStateKey: `semanticNode#semantic-node-relevance-${plan.label}#current`,
216
+ objectSubjectStateKey: `semanticNode#semantic-node-relevance-${plan.label}#current#reference`,
217
+ predicateObjectStateKey: `relevance_decision_is#semanticNode#semantic-node-relevance-${plan.label}#current`,
218
+ subjectVersionKey: `reference#${reference.id}`,
219
+ objectVersionKey: `semanticNode#semantic-node-relevance-${plan.label}-v1`,
220
+ confidence: plan.confidence,
221
+ modelVersion: "4",
222
+ reviewRecommended: false,
223
+ metadata: JSON.stringify({
224
+ decisionId: `preview-decision-${index}`,
225
+ label: plan.label,
226
+ probabilities: { include: plan.label === "include" ? plan.confidence : 1 - plan.confidence, exclude: plan.label === "exclude" ? plan.confidence : 1 - plan.confidence },
227
+ version: 4,
228
+ decidedAt,
229
+ labels: ["include", "exclude"],
230
+ positiveLabel: "include",
231
+ question: "Should this source be used as evidence for the publication?",
232
+ cyclotronId: PREVIEW_CYCLOTRON_ID,
233
+ ...(plan.provenance ? {
234
+ provenance: {
235
+ labelSet: ["include", "exclude"],
236
+ doctrineFingerprint: "3f9c2a71d80b46e5",
237
+ teachingFingerprint: "b71e04c9aa5d2f13",
238
+ teachingLabels: 37,
239
+ primingExamples: 6,
240
+ },
241
+ } : {}),
242
+ }),
243
+ } as SemanticRelationRecord);
244
+ if (plan.curation !== "none") {
245
+ const createdAt = isoTimestampHoursBeforeBase(30 - index);
246
+ messages.push({
247
+ id: `preview-curation-message-${index}`,
248
+ messageKind: "reference_curation",
249
+ messageDomain: "commentary",
250
+ status: "active",
251
+ summary: `${reference.title ?? lineageId}: curated`,
252
+ authorLabel: plan.curation === "delegated" ? "Maya Okafor" : null,
253
+ source: "newsroom",
254
+ createdAt,
255
+ updatedAt: createdAt,
256
+ metadata: JSON.stringify({
257
+ action: reference.curationStatus === "rejected" ? "reject" : "accept",
258
+ curationStatus: reference.curationStatus,
259
+ referenceLineageId: lineageId,
260
+ actorKind: plan.curation === "service" ? "service" : "human",
261
+ ...(plan.curation === "delegated" ? {
262
+ attestation: "delegated",
263
+ relayedBy: "newsroom-reporter",
264
+ delegationId: "curation-delegation-9b1f6c24-5d0e-4a7b-8c31-2e6f0a1d7b45",
265
+ instruction: "Reject that one, it duplicates the filing from Tuesday.",
266
+ } : {}),
267
+ }),
268
+ });
269
+ }
270
+ });
271
+ return {
272
+ relations,
273
+ messages,
274
+ shadowAgreement: {
275
+ schema: SHADOW_AGREEMENT_SCHEMA,
276
+ comparisons: 23,
277
+ agreement: 0.87,
278
+ precision: 0.9,
279
+ recall: 0.82,
280
+ confusion: { truePositive: 9, falsePositive: 1, falseNegative: 2, trueNegative: 11 },
281
+ computedAt: isoTimestampHoursBeforeBase(3),
282
+ positiveLabel: "include",
283
+ loop: { enabled: true, stoppedBecause: "daily decision cap", lastSweepAt: isoTimestampHoursBeforeBase(3), maxDecisionsPerDay: 50 },
284
+ },
285
+ };
286
+ }
@@ -2,6 +2,15 @@ import type { SemanticRelationRecord } from "./category-repository";
2
2
 
3
3
  export const RELEVANCE_RELATION = "relevance_decision_is";
4
4
 
5
+ /** What produced a suggestion beyond the model version, as the sweep recorded it. */
6
+ export type RelevanceProvenance = {
7
+ labelSet: string[];
8
+ doctrineFingerprint: string | null;
9
+ teachingFingerprint: string | null;
10
+ teachingLabels: number;
11
+ primingExamples: number;
12
+ };
13
+
5
14
  /** The relevance cyclotron's current decision about one reference, as the References tab shows it. */
6
15
  export type RelevanceDecisionView = {
7
16
  decisionRelationId: string;
@@ -19,6 +28,10 @@ export type RelevanceDecisionView = {
19
28
  /** Thumbs for a two-class decision with a positive label; per-class buttons otherwise. */
20
29
  reviewControl: "thumbs" | "labels";
21
30
  cyclotronId: string | null;
31
+ /** When the cyclotron made the suggestion, if the decision recorded it. */
32
+ decidedAt: string | null;
33
+ /** Null for decisions made before provenance was recorded. */
34
+ provenance: RelevanceProvenance | null;
22
35
  };
23
36
 
24
37
  /** What an editor submits from the review control. */
@@ -49,6 +62,20 @@ function parseMetadata(value: unknown): Record<string, unknown> {
49
62
  }
50
63
  }
51
64
 
65
+ function parseProvenance(value: unknown): RelevanceProvenance | null {
66
+ const record = parseMetadata(value);
67
+ if (!Object.keys(record).length) return null;
68
+ const count = (entry: unknown) => (Number.isFinite(Number(entry)) ? Number(entry) : 0);
69
+ const text = (entry: unknown) => (typeof entry === "string" && entry ? entry : null);
70
+ return {
71
+ labelSet: Array.isArray(record.labelSet) ? record.labelSet.map(String) : [],
72
+ doctrineFingerprint: text(record.doctrineFingerprint),
73
+ teachingFingerprint: text(record.teachingFingerprint),
74
+ teachingLabels: count(record.teachingLabels),
75
+ primingExamples: count(record.primingExamples),
76
+ };
77
+ }
78
+
52
79
  /**
53
80
  * Current decisions keyed by reference lineage, from relations the Newsroom
54
81
  * already loads: no extra request per row.
@@ -77,6 +104,8 @@ export function currentRelevanceDecisions(relations: SemanticRelationRecord[]):
77
104
  question: typeof metadata.question === "string" ? metadata.question : null,
78
105
  reviewControl: metadata.reviewControl === "thumbs" ? "thumbs" : "labels",
79
106
  cyclotronId: typeof metadata.cyclotronId === "string" ? metadata.cyclotronId : null,
107
+ decidedAt: typeof metadata.decidedAt === "string" ? metadata.decidedAt : null,
108
+ provenance: parseProvenance(metadata.provenance),
80
109
  });
81
110
  }
82
111
  return decisions;
package/package.json CHANGED
@@ -64,6 +64,7 @@
64
64
  "types": "./infra/amplify-app-shell.ts",
65
65
  "default": "./infra/index.js"
66
66
  },
67
+ "./components/administration-delegations-panel": "./components/administration-delegations-panel.tsx",
67
68
  "./components/ai-elements/conversation": "./components/ai-elements/conversation.tsx",
68
69
  "./components/ai-elements/model-selector": "./components/ai-elements/model-selector.tsx",
69
70
  "./components/ai-elements/prompt-input": "./components/ai-elements/prompt-input.tsx",
@@ -76,6 +77,7 @@
76
77
  "./components/article-video": "./components/article-video.tsx",
77
78
  "./components/blog-page-background": "./components/blog-page-background.tsx",
78
79
  "./components/board-card-excerpts": "./components/board-card-excerpts.tsx",
80
+ "./components/curation-delegation-client": "./components/curation-delegation-client.ts",
79
81
  "./components/dev-sandbox-editor-sign-in": "./components/dev-sandbox-editor-sign-in.tsx",
80
82
  "./components/markus-ir-preview": "./components/markus-ir-preview.tsx",
81
83
  "./components/news-desk-client-provider": "./components/news-desk-client-provider.tsx",
@@ -110,6 +112,8 @@
110
112
  "./components/reference-curation-controls": "./components/reference-curation-controls.tsx",
111
113
  "./components/reference-cyclotron-priming": "./components/reference-cyclotron-priming.tsx",
112
114
  "./components/reference-cyclotron-status": "./components/reference-cyclotron-status.tsx",
115
+ "./components/reference-cyclotron-suggestion": "./components/reference-cyclotron-suggestion.tsx",
116
+ "./components/reference-live-agreement": "./components/reference-live-agreement.tsx",
113
117
  "./components/reference-priming": "./components/reference-priming.tsx",
114
118
  "./components/reference-relevance-review": "./components/reference-relevance-review.tsx",
115
119
  "./components/reference-source-preview": "./components/reference-source-preview.tsx",
@@ -172,6 +176,8 @@
172
176
  "./lib/content-source-context": "./lib/content-source-context.ts",
173
177
  "./lib/content-types": "./lib/content-types.ts",
174
178
  "./lib/curation-client": "./lib/curation-client.ts",
179
+ "./lib/curation-delegation-helpers": "./lib/curation-delegation-helpers.ts",
180
+ "./lib/cyclotron-suggestions": "./lib/cyclotron-suggestions.ts",
175
181
  "./lib/define-site": "./lib/define-site.ts",
176
182
  "./lib/demo-amplify-outputs": "./lib/demo-amplify-outputs.ts",
177
183
  "./lib/dev-sandbox-editor-auth": "./lib/dev-sandbox-editor-auth.ts",
@@ -368,7 +374,7 @@
368
374
  "optional": true
369
375
  }
370
376
  },
371
- "version": "1.4.0-next.6",
377
+ "version": "1.4.0-next.7",
372
378
  "dependencies": {
373
379
  "@aws-amplify/adapter-nextjs": "^1.7.3",
374
380
  "@aws-crypto/sha256-js": "5.2.0",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.4.0-next.6",
2
+ "version": "1.4.0-next.7",
3
3
  "routes": [
4
4
  {
5
5
  "file": "app/%5F_preview/[[...path]]/route.ts",