@anthusai/papyrus 1.3.0 → 1.4.0-next.10

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 (141) hide show
  1. package/amplify/data/resource.js +134 -3
  2. package/amplify/data/resource.ts +145 -3
  3. package/amplify/data/schema.js +131 -0
  4. package/amplify/data/schema.ts +142 -0
  5. package/amplify/functions/category-action/handler.ts +272 -20
  6. package/amplify/functions/console-chat-responder/src/main.rs +1 -1
  7. package/amplify/functions/content-actions/handler.py +23 -0
  8. package/amplify/functions/content-actions/requirements.txt +1 -1
  9. package/amplify/functions/content-actions/resource.js +1 -1
  10. package/amplify/functions/content-actions/resource.ts +1 -1
  11. package/amplify/functions/model-attachment-upload/handler.ts +1 -0
  12. package/amplify/functions/renditions/requirements.txt +1 -1
  13. package/amplify/functions/shared/curation-delegation-flow.ts +147 -0
  14. package/amplify/functions/shared/curation-delegation.ts +115 -0
  15. package/amplify/functions/shared/python-bundle.js +3 -3
  16. package/amplify/functions/shared/python-bundle.ts +3 -3
  17. package/amplify/functions/shared/reference-curation-metadata.ts +51 -0
  18. package/amplify/functions/shared/slack-events.ts +2 -2
  19. package/amplify/publication/renditions-function.js +1 -1
  20. package/amplify/publication/renditions-function.ts +1 -1
  21. package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +2 -1
  22. package/app/[year]/[month]/[day]/edition-route-page.tsx +2 -1
  23. package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +2 -1
  24. package/app/[year]/[month]/[day]/page.tsx +3 -0
  25. package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +2 -0
  26. package/app/api/archive/editions/route.ts +2 -0
  27. package/app/api/auth/prospectus/accounts/route.ts +9 -0
  28. package/app/api/local-preview/derive/route.ts +28 -0
  29. package/app/api/local-preview/media/route.ts +25 -0
  30. package/app/archive/page.tsx +3 -0
  31. package/app/globals.css +28 -28
  32. package/app/layout.tsx +8 -5
  33. package/app/newsroom/[section]/[...selection]/page.tsx +0 -1
  34. package/app/newsroom/[section]/page.tsx +8 -1
  35. package/app/newsroom/articles/page.tsx +10 -0
  36. package/app/newsroom/insights/[threadId]/page.tsx +5 -16
  37. package/app/newsroom/insights/page.tsx +4 -12
  38. package/app/newsroom/sections/[sectionId]/page.tsx +3 -0
  39. package/app/page.tsx +30 -1
  40. package/components/administration-delegations-panel.tsx +156 -0
  41. package/components/curation-delegation-client.ts +104 -0
  42. package/components/item-stream-view.tsx +63 -0
  43. package/components/markus-ir-preview.tsx +47 -2
  44. package/components/news-desk-client-provider.tsx +17 -2
  45. package/components/news-desk-page.tsx +0 -1
  46. package/components/news-desk-taxonomy-client.ts +154 -240
  47. package/components/newspaper.tsx +4 -4
  48. package/components/newsroom-article-editor.tsx +138 -40
  49. package/components/newsroom-assignment-detail.tsx +79 -0
  50. package/components/newsroom-assignments-view.tsx +1 -1
  51. package/components/newsroom-board-view.tsx +153 -43
  52. package/components/newsroom-client-shell.tsx +8 -1
  53. package/components/newsroom-newsprint-board.css +5 -46
  54. package/components/newsroom-ops-overview.tsx +27 -5
  55. package/components/newsroom-organizer-shell.tsx +36 -13
  56. package/components/newsroom-organizer.css +269 -13
  57. package/components/newsroom-references-view.tsx +118 -23
  58. package/components/newsroom-topics-view.tsx +1 -1
  59. package/components/priming-proof-page.tsx +17 -8
  60. package/components/prospectus-account-chooser.tsx +131 -0
  61. package/components/reference-cyclotron-priming.tsx +85 -0
  62. package/components/reference-cyclotron-status.tsx +5 -5
  63. package/components/reference-cyclotron-suggestion.tsx +143 -0
  64. package/components/reference-live-agreement.tsx +62 -0
  65. package/components/reference-section-suggestion.tsx +104 -0
  66. package/components/story-reporting-trail-client.ts +91 -0
  67. package/components/story-reporting-trail.tsx +115 -0
  68. package/components/topic-steering-workspace.tsx +280 -679
  69. package/components/ui/sheet.tsx +10 -4
  70. package/components/use-paged-content-item-versions.ts +151 -0
  71. package/components/wiki-concepts-view.tsx +153 -0
  72. package/components/wiki-index-client.ts +51 -0
  73. package/infra/amplify-app-shell.js +17 -3
  74. package/infra/amplify-app-shell.ts +18 -3
  75. package/infra/site-config.js +6 -1
  76. package/infra/site-config.ts +7 -1
  77. package/lib/approved-priming-proof-reports.ts +6 -3
  78. package/lib/approved-priming-proofs/papyrus.json +90 -0
  79. package/lib/article-flow.ts +95 -0
  80. package/lib/article-frame-solver.ts +519 -0
  81. package/lib/blog-feature-solver.ts +1 -109
  82. package/lib/category-repository.ts +1 -1
  83. package/lib/chrome-text.ts +504 -0
  84. package/lib/content-actions-client.ts +3 -0
  85. package/lib/content-actions-demo.ts +55 -0
  86. package/lib/content-item-lifecycle.ts +34 -5
  87. package/lib/content-source-context.ts +9 -1
  88. package/lib/curation-delegation-helpers.ts +69 -0
  89. package/lib/cyclotron-suggestions.ts +263 -0
  90. package/lib/front-matter-fields.ts +42 -0
  91. package/lib/front-mosaic.ts +1133 -0
  92. package/lib/furniture.ts +385 -0
  93. package/lib/graphql-content-repository.ts +42 -7
  94. package/lib/item-stream.ts +47 -0
  95. package/lib/labels.ts +37 -0
  96. package/lib/layout-config.ts +237 -0
  97. package/lib/layout-plan.ts +40 -11
  98. package/lib/layout-scenarios.ts +21 -11
  99. package/lib/layout-types.ts +457 -0
  100. package/lib/layout-warnings.ts +8 -0
  101. package/lib/local-preview-section-fixtures.ts +72 -0
  102. package/lib/local-preview-seed.ts +286 -0
  103. package/lib/local-preview-tenant.ts +27 -0
  104. package/lib/local-preview.ts +3 -0
  105. package/lib/newspaper-layout.ts +17 -3410
  106. package/lib/newsroom-articles.ts +2 -0
  107. package/lib/newsroom-assignments.ts +14 -1
  108. package/lib/newsroom-board.ts +99 -47
  109. package/lib/newsroom-demo-dashboard.ts +1 -1
  110. package/lib/newsroom-forum-routes.ts +2 -43
  111. package/lib/newsroom-index-filters.ts +3 -40
  112. package/lib/newsroom-media-backend.ts +5 -0
  113. package/lib/newsroom-nav.ts +1 -3
  114. package/lib/newsroom-organizer-nav.ts +9 -16
  115. package/lib/newsroom-section-routing.ts +1 -1
  116. package/lib/newsroom-sections.ts +12 -0
  117. package/lib/papyrus-web-locations.ts +3 -24
  118. package/lib/pretext-layout.ts +22 -0
  119. package/lib/priming-proof-report.ts +1 -0
  120. package/lib/prospectus-account-client.ts +90 -0
  121. package/lib/prospectus-sign-in-routes.ts +45 -0
  122. package/lib/prospectus-site-identity.ts +13 -1
  123. package/lib/prospectus-tenant.ts +22 -0
  124. package/lib/reader-static-params.ts +2 -1
  125. package/lib/relevance-decisions.ts +29 -0
  126. package/lib/relevance-status.ts +73 -0
  127. package/lib/reporting-story-budget.ts +2 -2
  128. package/lib/semantic-graph.ts +5 -5
  129. package/lib/site-brand.ts +28 -1
  130. package/lib/site-env.ts +5 -0
  131. package/lib/site-section-suggestions.ts +120 -0
  132. package/lib/site-stack.ts +1 -1
  133. package/lib/story-reporting-trail.ts +106 -0
  134. package/lib/tenant-keys.ts +32 -0
  135. package/lib/text-columns.ts +251 -0
  136. package/lib/wiki.ts +56 -0
  137. package/package.json +46 -3
  138. package/renderers/pretext/presentation-shell.tsx +1 -1
  139. package/renderers/pretext/rhythm-article-page.tsx +1 -18
  140. package/routes.manifest.json +34 -15
  141. package/app/newsroom/work/page.tsx +0 -10
@@ -0,0 +1,457 @@
1
+ import { type LayoutCursor, type TextLine } from "./pretext-layout";
2
+ import { type ArticleImageThemeVariants } from "./articles";
3
+ import {
4
+ type ArticleFrameBlockSpec,
5
+ type EditorialPriorityId,
6
+ type HeadlineScaleId,
7
+ type LayoutBlockSpec,
8
+ type LayoutPageSpec,
9
+ type LayoutRegionSpec,
10
+ type ResponsivePlacementSpec,
11
+ } from "./layout-plan";
12
+ import { type ArticlePublicationItem, type PublicationItem } from "./publication-items";
13
+ import { type VerticalRhythm } from "./blog-rhythm";
14
+
15
+ export type SolvedTextLine = TextLine;
16
+
17
+ export type SolvedFurniture = SolvedImageFurniture | SolvedPullQuoteFurniture | SolvedMediaClusterFurniture | SolvedAdFurniture;
18
+
19
+ export type SolvedImageFurniture = {
20
+ kind: "image";
21
+ id: string;
22
+ assetId: string;
23
+ src: string;
24
+ themeVariants?: ArticleImageThemeVariants;
25
+ alt: string;
26
+ caption: string;
27
+ credit: string;
28
+ templateId: string;
29
+ columnStart: number;
30
+ columnSpan: number;
31
+ x: number;
32
+ y: number;
33
+ width: number;
34
+ height: number;
35
+ imageHeight: number;
36
+ captionHeight: number;
37
+ captionFontSize: number;
38
+ captionLineHeight: number;
39
+ aspectRatio: number;
40
+ objectFit: "contain" | "cover";
41
+ objectPosition: string;
42
+ wrapsText: boolean;
43
+ preferredHeight: number;
44
+ };
45
+
46
+ export type SolvedPullQuoteFurniture = {
47
+ kind: "pullQuote";
48
+ id: string;
49
+ text: string;
50
+ templateId: string;
51
+ columnStart: number;
52
+ columnSpan: number;
53
+ x: number;
54
+ y: number;
55
+ width: number;
56
+ height: number;
57
+ fontSize: number;
58
+ lineHeight: number;
59
+ wrapsText: boolean;
60
+ };
61
+
62
+ export type SolvedMediaClusterFurniture = {
63
+ kind: "mediaCluster";
64
+ id: string;
65
+ templateId: string;
66
+ images: SolvedImageFurniture[];
67
+ caption: string;
68
+ x: number;
69
+ y: number;
70
+ width: number;
71
+ height: number;
72
+ wrapsText: false;
73
+ };
74
+
75
+ export type SolvedAdFurniture = {
76
+ kind: "ad";
77
+ id: string;
78
+ src?: string;
79
+ label: string;
80
+ x: number;
81
+ y: number;
82
+ width: number;
83
+ height: number;
84
+ wrapsText: false;
85
+ };
86
+
87
+ export type SolvedChromeBox = {
88
+ id: string;
89
+ slot: "label" | "headline" | "deck" | "byline";
90
+ text: string;
91
+ columnStart: number;
92
+ columnSpan: number;
93
+ x: number;
94
+ y: number;
95
+ width: number;
96
+ height: number;
97
+ fontSize: number;
98
+ lineHeight: number;
99
+ paintBuffer: number;
100
+ paddingTop?: number;
101
+ paddingBottom?: number;
102
+ ruleTopHeight?: number;
103
+ ruleBottomHeight?: number;
104
+ fontFamily: "serif" | "sans";
105
+ fontWeight?: number | string;
106
+ fontStyle?: "normal" | "italic";
107
+ textTransform?: "uppercase";
108
+ };
109
+
110
+ export type SolvedBlock = {
111
+ id: string;
112
+ type: LayoutBlockSpec["type"];
113
+ presetId?: string;
114
+ headlineScale?: HeadlineScaleId;
115
+ editorialPriority?: EditorialPriorityId;
116
+ item?: PublicationItem;
117
+ items?: PublicationItem[];
118
+ article?: ArticlePublicationItem;
119
+ pageNumber: number;
120
+ jumpTargetPage?: number;
121
+ jumpLabel?: string;
122
+ label?: string;
123
+ title?: string;
124
+ deck?: string;
125
+ byline?: string;
126
+ dateline?: string;
127
+ section?: string;
128
+ x: number;
129
+ y: number;
130
+ width: number;
131
+ height: number;
132
+ span: number;
133
+ columnCount: number;
134
+ columns: TextLine[][];
135
+ chromeBoxes?: SolvedChromeBox[];
136
+ furniture: SolvedFurniture[];
137
+ textRange?: PlacedTextRange;
138
+ hasMore?: boolean;
139
+ front?: SolvedFrontStoryMetrics;
140
+ titleChrome?: ContinuationTitleMetrics;
141
+ titleHeight?: number;
142
+ bodyHeight?: number;
143
+ furnitureSufficiency?: FurnitureSufficiencyReport;
144
+ };
145
+
146
+ export type SolvedFrontStoryMetrics = {
147
+ rowHeight: number;
148
+ bodySlotHeight: number;
149
+ chromeHeight: number;
150
+ jumpReserveHeight: number;
151
+ chrome: StoryChromeMetrics;
152
+ gridPlacement?: SolvedFrontGridPlacement;
153
+ composition?: {
154
+ mode: FrontCompositionFlowMode;
155
+ bodyTop: number;
156
+ bodyHeight: number;
157
+ columnCount: number;
158
+ copyBandTop: number;
159
+ copyBandTops: number[];
160
+ };
161
+ };
162
+
163
+ export type SolvedFrontGridPlacement = {
164
+ columnStart: number;
165
+ columnSpan: number;
166
+ rowStart: number;
167
+ rowSpan: number;
168
+ };
169
+
170
+ export type SolvedRegion = {
171
+ id: string;
172
+ type: LayoutRegionSpec["type"];
173
+ role?: string;
174
+ x: number;
175
+ y: number;
176
+ width: number;
177
+ height: number;
178
+ rowHeights?: number[];
179
+ columnCount: number;
180
+ blocks: SolvedBlock[];
181
+ };
182
+
183
+ export type SolvedFrontFooterEntry = {
184
+ section: string;
185
+ articleSlug: string;
186
+ articleTitle: string;
187
+ blockId: string;
188
+ pageNumber: number;
189
+ };
190
+
191
+ export type SolvedFrontFooterUtilityEntry =
192
+ | {
193
+ id: "archive";
194
+ label: string;
195
+ href: string;
196
+ disabled: false;
197
+ }
198
+ | {
199
+ id: "newsDesk";
200
+ label: string;
201
+ href: string;
202
+ disabled: false;
203
+ }
204
+ | {
205
+ id: "settings";
206
+ label: string;
207
+ href: string;
208
+ disabled: false;
209
+ }
210
+ | {
211
+ id: "login";
212
+ label: string;
213
+ disabled: true;
214
+ };
215
+
216
+ export type SolvedFrontFooter = {
217
+ rowHeight: number;
218
+ marginTop: number;
219
+ height: number;
220
+ sectionRows: number;
221
+ sectionColumns: number;
222
+ entries: SolvedFrontFooterEntry[];
223
+ utilityEntries: SolvedFrontFooterUtilityEntry[];
224
+ };
225
+
226
+ export type SolvedPage = {
227
+ id: string;
228
+ pageNumber: number;
229
+ presetId: LayoutPageSpec["presetId"];
230
+ kind: string;
231
+ height: number;
232
+ columnCount: number;
233
+ regions: SolvedRegion[];
234
+ frontFooter?: SolvedFrontFooter;
235
+ };
236
+
237
+ export type NewspaperLayout = {
238
+ columnCount: number;
239
+ contentWidth: number;
240
+ gap: number;
241
+ rowGap: number;
242
+ rhythm: VerticalRhythm;
243
+ pageHeight: number;
244
+ frontPageHeight: number;
245
+ pageHeights: Record<number, number>;
246
+ pageChrome: PageChromeMetrics;
247
+ pages: SolvedPage[];
248
+ textRanges: PlacedTextRange[];
249
+ };
250
+
251
+ export type PageChromeMetrics = {
252
+ pagePaddingTop: number;
253
+ pagePaddingX: number;
254
+ pagePaddingBottom: number;
255
+ mastheadHeight: number;
256
+ mastheadKickerLineHeight: number;
257
+ mastheadTitleFontSize: number;
258
+ mastheadTitleLineHeight: number;
259
+ mastheadTitleMarginTop: number;
260
+ mastheadTitleMarginBottom: number;
261
+ mastheadTitleOpticalShift: number;
262
+ mastheadMetaLineHeight: number;
263
+ mastheadMetaGap: number;
264
+ mastheadMetaPaddingTop: number;
265
+ frontGridMarginTop: number;
266
+ insideHeaderHeight: number;
267
+ continuedTitleHeight: number;
268
+ continuedTitleChromeHeight: number;
269
+ continuedTitleFontSize: number;
270
+ continuedTitleLineHeight: number;
271
+ continuationSectionSeparatorHeight: number;
272
+ };
273
+
274
+ export type ChromeTextBoxMetrics = {
275
+ fontSize: number;
276
+ lineHeight: number;
277
+ paintHeight: number;
278
+ paintBuffer: number;
279
+ contentHeight: number;
280
+ paddingTop: number;
281
+ paddingBottom: number;
282
+ ruleTopHeight: number;
283
+ ruleBottomHeight: number;
284
+ height: number;
285
+ lineCount: number;
286
+ marginBefore: number;
287
+ marginAfter: number;
288
+ totalHeight: number;
289
+ };
290
+
291
+ export type StoryChromeMetrics = {
292
+ borderTopHeight: number;
293
+ paddingTop: number;
294
+ mediaPreludeHeight: number;
295
+ mediaPreludeMarginBottom: number;
296
+ labelFontSize: number;
297
+ labelLineHeight: number;
298
+ labelPaintHeight: number;
299
+ labelPaintBuffer: number;
300
+ labelHeight: number;
301
+ headlineFontSize: number;
302
+ headlineLineHeight: number;
303
+ headlinePaintHeight: number;
304
+ headlinePaintBuffer: number;
305
+ headlineHeight: number;
306
+ headlineLineCount: number;
307
+ headlineMarginTop: number;
308
+ headlineMarginBottom: number;
309
+ deckFontSize: number;
310
+ deckLineHeight: number;
311
+ deckPaintHeight: number;
312
+ deckPaintBuffer: number;
313
+ deckPaddingTop: number;
314
+ deckPaddingBottom: number;
315
+ deckRuleTopHeight: number;
316
+ deckRuleBottomHeight: number;
317
+ deckHeight: number;
318
+ deckLineCount: number;
319
+ deckMarginBottom: number;
320
+ bylineFontSize: number;
321
+ bylineLineHeight: number;
322
+ bylinePaintHeight: number;
323
+ bylinePaintBuffer: number;
324
+ bylinePaddingTop: number;
325
+ bylinePaddingBottom: number;
326
+ bylineHeight: number;
327
+ bylineLineCount: number;
328
+ bylineMarginBottom: number;
329
+ measureChromeHeight: number;
330
+ jumpFontSize: number;
331
+ jumpLineHeight: number;
332
+ jumpPaintHeight: number;
333
+ jumpPaintBuffer: number;
334
+ jumpHeight: number;
335
+ jumpPaddingTop: number;
336
+ jumpPaddingBottom: number;
337
+ jumpBorderTopHeight: number;
338
+ };
339
+
340
+ export type ContinuationTitleMetrics = {
341
+ label: ChromeTextBoxMetrics;
342
+ heading: ChromeTextBoxMetrics;
343
+ chromeHeight: number;
344
+ totalHeight: number;
345
+ };
346
+
347
+ export type FrontStoryRole = "feature" | "rail" | "standard";
348
+
349
+ export type ChromeTextStyle = {
350
+ fontSize: number;
351
+ fontFamily: string;
352
+ fontWeight?: number | string;
353
+ fontStyle?: "normal" | "italic";
354
+ lineHeightEm: number;
355
+ paintHeightEm: number;
356
+ marginBeforeEm?: number;
357
+ marginAfterEm?: number;
358
+ minHeightEm?: number;
359
+ };
360
+
361
+ export type ChromeBoxTreatment = {
362
+ paddingTop: number;
363
+ paddingBottom: number;
364
+ ruleTopHeight?: number;
365
+ ruleBottomHeight?: number;
366
+ };
367
+
368
+ export type PlacedTextRange = {
369
+ articleId: string;
370
+ pageId: string;
371
+ blockId: string;
372
+ startCursor: LayoutCursor;
373
+ endCursor: LayoutCursor;
374
+ exhausted: boolean;
375
+ };
376
+
377
+ export type ArticleFlow = {
378
+ article: ArticlePublicationItem;
379
+ currentCursor: LayoutCursor;
380
+ placedRanges: PlacedTextRange[];
381
+ usedImageAssetIds: Set<string>;
382
+ };
383
+
384
+ export type LayoutConfig = {
385
+ columnCount: number;
386
+ contentWidth: number;
387
+ gap: number;
388
+ rowGap: number;
389
+ rhythm: VerticalRhythm;
390
+ pageChrome: PageChromeMetrics;
391
+ lineHeight: number;
392
+ linePaintHeight: number;
393
+ bodyFontSize: number;
394
+ bodyFont: string;
395
+ frontRows: FrontRow[];
396
+ continuationHeight: number;
397
+ };
398
+
399
+ export type FrontRow = {
400
+ startIndex: number;
401
+ endIndex: number;
402
+ height: number;
403
+ };
404
+
405
+ export type FrontArticlePlacement = {
406
+ block: ArticleFrameBlockSpec;
407
+ originalIndex: number;
408
+ solveIndex: number;
409
+ gridPlacement?: SolvedFrontGridPlacement;
410
+ rowHeight?: number;
411
+ };
412
+
413
+ export type FrontArticleSolveOptions = {
414
+ enforcePlacementHeight?: boolean;
415
+ };
416
+
417
+ export type FrontCompositionFlowMode = "offsetBody" | "integrated" | "titleStackedMedia" | "stackedMedia";
418
+
419
+ export type ArticleFrameCandidate = {
420
+ block: SolvedBlock;
421
+ range: PlacedTextRange;
422
+ score: number;
423
+ whitespace: number;
424
+ furnitureSufficiency?: FurnitureSufficiencyReport;
425
+ };
426
+
427
+ export type FurnitureSufficiencyReport = {
428
+ accepted: boolean;
429
+ reason?: string;
430
+ visibleRows: number;
431
+ furnitureRows: number;
432
+ };
433
+
434
+ export type MediaVariant = {
435
+ id: string;
436
+ placement: ResponsivePlacementSpec | null;
437
+ assetRole?: string;
438
+ columnStart: number;
439
+ columnSpan: number;
440
+ fallbackPenalty: number;
441
+ };
442
+
443
+ export type PlacementVariant = {
444
+ id: string;
445
+ placement: ResponsivePlacementSpec;
446
+ columnStart: number;
447
+ columnSpan: number;
448
+ fallbackPenalty: number;
449
+ };
450
+
451
+ export type PullQuoteVariant = {
452
+ id: string;
453
+ placement: ResponsivePlacementSpec | null;
454
+ columnStart: number;
455
+ columnSpan: number;
456
+ fallbackPenalty: number;
457
+ };
@@ -0,0 +1,8 @@
1
+ const missingMediaWarningsAlreadyLogged = new Set<string>();
2
+
3
+ /** A story without its lead image must still publish; the layout renders without the slot. */
4
+ export function warnAboutMissingMedia(message: string): void {
5
+ if (missingMediaWarningsAlreadyLogged.has(message)) return;
6
+ missingMediaWarningsAlreadyLogged.add(message);
7
+ console.warn(`[layout-plan] ${message}`);
8
+ }
@@ -0,0 +1,72 @@
1
+ import type { ReferenceRecord, SemanticRelationRecord } from "./category-repository";
2
+ import { SHADOW_AGREEMENT_SCHEMA } from "./cyclotron-suggestions";
3
+ import { SECTION_FIT_CLASSIFIER_PREFIX, SECTION_SELECTOR_CLASSIFIER_PREFIX, parseSiteSectionAgreement, type SiteSectionAgreement } from "./site-section-suggestions";
4
+
5
+ const PREVIEW_SITE_CYCLOTRON_ID = "preview-site-cyclotron";
6
+
7
+ /**
8
+ * Local preview only: a suggested Section on the first pending references and
9
+ * a measured agreement for each Section, so the References screen can be
10
+ * reviewed without a sweep.
11
+ */
12
+ export function createLocalPreviewSectionFixtures(
13
+ references: ReferenceRecord[],
14
+ sectionIds: string[],
15
+ ): { relations: SemanticRelationRecord[]; agreement: SiteSectionAgreement | null } {
16
+ if (sectionIds.length < 2) return { relations: [], agreement: null };
17
+ const relations: SemanticRelationRecord[] = references.slice(0, 6).map((reference, index) => {
18
+ const lineageId = reference.lineageId ?? reference.id;
19
+ const top = sectionIds[index % sectionIds.length];
20
+ const runnerUp = sectionIds[(index + 1) % sectionIds.length];
21
+ const confidence = 0.88 - index * 0.07;
22
+ const probabilities: Record<string, number> = { [top]: confidence, [runnerUp]: (1 - confidence) * 0.7 };
23
+ const rest = sectionIds.filter((candidate) => candidate !== top && candidate !== runnerUp);
24
+ rest.forEach((candidate) => { probabilities[candidate] = (1 - confidence) * 0.3 / rest.length; });
25
+ return {
26
+ id: `preview-section-relation-${index}`,
27
+ relationState: "current",
28
+ predicate: "site_decision_is",
29
+ subjectKind: "reference",
30
+ subjectId: reference.id,
31
+ subjectLineageId: lineageId,
32
+ objectKind: "semanticNode",
33
+ objectId: `semantic-node-site-${top}-v1`,
34
+ objectLineageId: `semantic-node-site-${top}`,
35
+ subjectStateKey: `reference#${lineageId}#current`,
36
+ objectStateKey: `semanticNode#semantic-node-site-${top}#current`,
37
+ objectSubjectStateKey: `semanticNode#semantic-node-site-${top}#current#reference`,
38
+ predicateObjectStateKey: `site_decision_is#semanticNode#semantic-node-site-${top}#current`,
39
+ subjectVersionKey: `reference#${reference.id}`,
40
+ objectVersionKey: `semanticNode#semantic-node-site-${top}-v1`,
41
+ confidence,
42
+ classifierId: `${PREVIEW_SITE_CYCLOTRON_ID}/${SECTION_SELECTOR_CLASSIFIER_PREFIX}preview`,
43
+ modelVersion: "2",
44
+ reviewRecommended: false,
45
+ metadata: JSON.stringify({
46
+ decisionId: `preview-site-decision-${index}`,
47
+ cyclotronId: PREVIEW_SITE_CYCLOTRON_ID,
48
+ classifier: `${SECTION_SELECTOR_CLASSIFIER_PREFIX}preview`,
49
+ label: top,
50
+ probabilities,
51
+ version: 2,
52
+ decidedAt: new Date(Date.UTC(2026, 9, 18, 6 - index)).toISOString(),
53
+ }),
54
+ } as SemanticRelationRecord;
55
+ });
56
+ const classifiers: Record<string, unknown> = { [`${SECTION_SELECTOR_CLASSIFIER_PREFIX}preview`]: { comparisons: 14, correct: 10, agreement: 10 / 14 } };
57
+ sectionIds.forEach((sectionId, index) => {
58
+ classifiers[`${SECTION_FIT_CLASSIFIER_PREFIX}${sectionId}`] = index === sectionIds.length - 1
59
+ ? { comparisons: 4, agreement: 0.75, precision: 0.5, recall: 1 }
60
+ : { comparisons: 12 + index, agreement: 0.9 - index * 0.08, precision: 0.85 - index * 0.06, recall: 0.8 };
61
+ });
62
+ return {
63
+ relations,
64
+ agreement: parseSiteSectionAgreement({
65
+ schema: SHADOW_AGREEMENT_SCHEMA,
66
+ computedAt: "2026-10-18T09:00:00.000Z",
67
+ comparisons: 14,
68
+ selectorAccuracy: 10 / 14,
69
+ classifiers,
70
+ }),
71
+ };
72
+ }