@anthusai/papyrus 0.0.0 → 1.0.0-next.1
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/LICENSE +21 -0
- package/amplify/auth/resource.js +61 -0
- package/amplify/auth/resource.ts +65 -0
- package/amplify/data/resource.js +2037 -0
- package/amplify/data/resource.ts +2148 -0
- package/amplify/data/schema.js +1850 -0
- package/amplify/data/schema.ts +1953 -0
- package/amplify/functions/assignment-action/handler.ts +1416 -0
- package/amplify/functions/assignment-action/resource.js +8 -0
- package/amplify/functions/assignment-action/resource.ts +9 -0
- package/amplify/functions/category-action/handler.ts +2259 -0
- package/amplify/functions/category-action/resource.js +8 -0
- package/amplify/functions/category-action/resource.ts +9 -0
- package/amplify/functions/console-chat-responder/Cargo.lock +3456 -0
- package/amplify/functions/console-chat-responder/Cargo.toml +26 -0
- package/amplify/functions/console-chat-responder/Dockerfile +19 -0
- package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +116 -0
- package/amplify/functions/console-chat-responder/resource.js +153 -0
- package/amplify/functions/console-chat-responder/resource.ts +179 -0
- package/amplify/functions/console-chat-responder/src/main.rs +3242 -0
- package/amplify/functions/email-submission-processor/handler.py +46 -0
- package/amplify/functions/email-submission-processor/requirements.txt +2 -0
- package/amplify/functions/email-submission-processor/resource.js +56 -0
- package/amplify/functions/email-submission-processor/resource.ts +69 -0
- package/amplify/functions/graphql-jwt-authorizer/handler.ts +193 -0
- package/amplify/functions/graphql-jwt-authorizer/resource.js +14 -0
- package/amplify/functions/graphql-jwt-authorizer/resource.ts +15 -0
- package/amplify/functions/knowledge-query/handler.py +1 -0
- package/amplify/functions/knowledge-query/requirements.txt +3 -0
- package/amplify/functions/knowledge-query/resource.js +50 -0
- package/amplify/functions/knowledge-query/resource.ts +61 -0
- package/amplify/functions/manage-user-role/handler.ts +799 -0
- package/amplify/functions/manage-user-role/resource.js +8 -0
- package/amplify/functions/manage-user-role/resource.ts +9 -0
- package/amplify/functions/model-attachment-upload/handler.ts +387 -0
- package/amplify/functions/model-attachment-upload/resource.js +8 -0
- package/amplify/functions/model-attachment-upload/resource.ts +9 -0
- package/amplify/functions/newsroom-summary/handler.ts +321 -0
- package/amplify/functions/newsroom-summary/resource.js +8 -0
- package/amplify/functions/newsroom-summary/resource.ts +9 -0
- package/amplify/functions/procedure-action/handler.ts +621 -0
- package/amplify/functions/procedure-action/resource.js +8 -0
- package/amplify/functions/procedure-action/resource.ts +9 -0
- package/amplify/functions/reader-settings/handler.ts +322 -0
- package/amplify/functions/reader-settings/resource.js +8 -0
- package/amplify/functions/reader-settings/resource.ts +9 -0
- package/amplify/functions/ses-inbound-receive/handler.ts +493 -0
- package/amplify/functions/ses-inbound-receive/resource.js +8 -0
- package/amplify/functions/ses-inbound-receive/resource.ts +9 -0
- package/amplify/functions/shared/amplify-secrets.ts +65 -0
- package/amplify/functions/shared/console-chat-enqueue.ts +117 -0
- package/amplify/functions/shared/email-mime-intake.ts +196 -0
- package/amplify/functions/shared/email-submission.ts +415 -0
- package/amplify/functions/shared/inbound-email-intake.ts +37 -0
- package/amplify/functions/shared/lambda-data-client.ts +21 -0
- package/amplify/functions/shared/model-payloads.ts +186 -0
- package/amplify/functions/shared/python-bundle.js +115 -0
- package/amplify/functions/shared/python-bundle.ts +141 -0
- package/amplify/functions/shared/slack-events.ts +111 -0
- package/amplify/functions/slack-delivery/handler.py +112 -0
- package/amplify/functions/slack-delivery/requirements.txt +2 -0
- package/amplify/functions/slack-delivery/resource.js +38 -0
- package/amplify/functions/slack-delivery/resource.ts +51 -0
- package/amplify/functions/slack-events/handler.ts +155 -0
- package/amplify/functions/slack-events/resource.js +11 -0
- package/amplify/functions/slack-events/resource.ts +12 -0
- package/amplify/inbound-email/stack.js +46 -0
- package/amplify/inbound-email/stack.ts +61 -0
- package/amplify/package.json +3 -0
- package/amplify/site-backend-config.js +1 -0
- package/amplify/site-backend-config.ts +30 -0
- package/amplify/site-backend.js +587 -0
- package/amplify/site-backend.ts +704 -0
- package/amplify/storage/resource.js +18 -0
- package/amplify/storage/resource.ts +19 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +62 -0
- package/app/[year]/[month]/[day]/edition-route-page.tsx +53 -0
- package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +32 -0
- package/app/[year]/[month]/[day]/page.tsx +22 -0
- package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +32 -0
- package/app/api/archive/editions/route.ts +18 -0
- package/app/api/media/[...path]/route.ts +35 -0
- package/app/api/revalidate/route.ts +94 -0
- package/app/archive/page.tsx +24 -0
- package/app/articles/[slug]/page.tsx +35 -0
- package/app/globals.css +10329 -0
- package/app/layout.tsx +146 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +50 -0
- package/app/newsroom/[section]/page.tsx +47 -0
- package/app/newsroom/forum/[threadId]/page.tsx +35 -0
- package/app/newsroom/insights/[threadId]/page.tsx +22 -0
- package/app/newsroom/insights/page.tsx +14 -0
- package/app/newsroom/layout.tsx +12 -0
- package/app/newsroom/page.tsx +31 -0
- package/app/newsroom/sections/[sectionId]/page.tsx +17 -0
- package/app/page.tsx +272 -0
- package/app/settings/page.tsx +14 -0
- package/app/tailwind.css +65 -0
- package/bin/papyrus-app.mjs +10 -0
- package/bin/papyrus-infra.mjs +39 -0
- package/components/ai-elements/conversation.tsx +168 -0
- package/components/ai-elements/message.tsx +360 -0
- package/components/ai-elements/model-selector.tsx +30 -0
- package/components/ai-elements/prompt-input.tsx +1461 -0
- package/components/ai-elements/shimmer.tsx +76 -0
- package/components/ai-elements/suggestion.tsx +54 -0
- package/components/ai-elements/tool.tsx +173 -0
- package/components/amplify-client-provider.tsx +140 -0
- package/components/archive-grid.tsx +89 -0
- package/components/archive-shell.tsx +41 -0
- package/components/article-video.tsx +47 -0
- package/components/blog-page-background.tsx +20 -0
- package/components/dev-sandbox-editor-sign-in.tsx +60 -0
- package/components/news-desk-client-provider.tsx +211 -0
- package/components/news-desk-page.tsx +119 -0
- package/components/news-desk-taxonomy-client.ts +3684 -0
- package/components/newspaper.tsx +1929 -0
- package/components/newsroom-assignments-view.tsx +664 -0
- package/components/newsroom-ops-overview.tsx +226 -0
- package/components/newsroom-ops-shell.tsx +319 -0
- package/components/newsroom-references-view.tsx +598 -0
- package/components/newsroom-topics-view.tsx +425 -0
- package/components/papyrus-console-shell.tsx +1912 -0
- package/components/pictogram-figure.tsx +65 -0
- package/components/presentation-footer.tsx +146 -0
- package/components/reader-auth-control.tsx +162 -0
- package/components/reader-auth-state.ts +122 -0
- package/components/reader-settings.ts +332 -0
- package/components/reader-theme-control.tsx +85 -0
- package/components/reference-source-preview.tsx +163 -0
- package/components/settings-page.tsx +239 -0
- package/components/topic-steering-workspace.tsx +18063 -0
- package/components/ui/badge.tsx +30 -0
- package/components/ui/button-group.tsx +87 -0
- package/components/ui/button.tsx +58 -0
- package/components/ui/card.tsx +32 -0
- package/components/ui/command.tsx +194 -0
- package/components/ui/dialog.tsx +158 -0
- package/components/ui/dropdown-menu.tsx +266 -0
- package/components/ui/hover-card.tsx +51 -0
- package/components/ui/input-group.tsx +158 -0
- package/components/ui/input.tsx +20 -0
- package/components/ui/scroll-area.tsx +16 -0
- package/components/ui/select.tsx +199 -0
- package/components/ui/separator.tsx +23 -0
- package/components/ui/sheet.tsx +92 -0
- package/components/ui/spinner.tsx +10 -0
- package/components/ui/tabs.tsx +113 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/tooltip.tsx +66 -0
- package/components/use-resolved-papyrus-theme.ts +47 -0
- package/infra/amplify-app-shell.js +109 -0
- package/infra/amplify-app-shell.ts +155 -0
- package/infra/index.js +12 -0
- package/lib/amplify-outputs-guard.ts +24 -0
- package/lib/amplify-outputs-path.ts +16 -0
- package/lib/amplify-server-runtime.ts +63 -0
- package/lib/archive-data.ts +30 -0
- package/lib/archive-types.ts +15 -0
- package/lib/articles.ts +320 -0
- package/lib/cached-content-repository.ts +89 -0
- package/lib/category-dashboard.ts +37 -0
- package/lib/category-repository.ts +1907 -0
- package/lib/console-chat-client.ts +1316 -0
- package/lib/console-web-context.ts +118 -0
- package/lib/content-load-timing.ts +23 -0
- package/lib/content-repository.ts +40 -0
- package/lib/content-types.ts +104 -0
- package/lib/define-site.js +3 -0
- package/lib/define-site.ts +21 -0
- package/lib/demo-amplify-outputs.ts +15 -0
- package/lib/dev-sandbox-editor-auth.ts +18 -0
- package/lib/doctrine.ts +128 -0
- package/lib/edition-routes.ts +155 -0
- package/lib/edition-sections.ts +111 -0
- package/lib/empty-edition-layout-plan.ts +109 -0
- package/lib/excerpts.ts +8 -0
- package/lib/graphql-content-repository.ts +986 -0
- package/lib/image-url.ts +10 -0
- package/lib/layout-plan.ts +981 -0
- package/lib/layout-scenarios.ts +564 -0
- package/lib/markus-body.ts +168 -0
- package/lib/markus-ir.ts +235 -0
- package/lib/markus-projection.ts +243 -0
- package/lib/markus-to-article.ts +93 -0
- package/lib/news-desk-session.ts +160 -0
- package/lib/newspaper-layout.ts +3459 -0
- package/lib/newsroom-assignments.ts +173 -0
- package/lib/newsroom-base-path.ts +48 -0
- package/lib/newsroom-card-layout.ts +102 -0
- package/lib/newsroom-category-drilldown.ts +133 -0
- package/lib/newsroom-demo-dashboard.ts +267 -0
- package/lib/newsroom-demo-profile.ts +46 -0
- package/lib/newsroom-forum-routes.ts +153 -0
- package/lib/newsroom-index-filters.ts +202 -0
- package/lib/newsroom-knowledge-query-request.ts +51 -0
- package/lib/newsroom-list-selection.ts +42 -0
- package/lib/newsroom-nav.ts +42 -0
- package/lib/newsroom-references.ts +116 -0
- package/lib/newsroom-sections.ts +52 -0
- package/lib/newsroom-topics.ts +118 -0
- package/lib/papyrus-object-kinds.ts +24 -0
- package/lib/papyrus-web-locations.ts +480 -0
- package/lib/pilobol-sample.ts +167 -0
- package/lib/presentation-footer.ts +92 -0
- package/lib/pretext-layout.ts +179 -0
- package/lib/public-placeholder-config.ts +102 -0
- package/lib/publication-items.ts +181 -0
- package/lib/reader-route-config.ts +1 -0
- package/lib/reader-static-params.ts +56 -0
- package/lib/reader-storage-url.ts +63 -0
- package/lib/reference-display.ts +74 -0
- package/lib/reference-policy.ts +97 -0
- package/lib/reference-source-preview.ts +175 -0
- package/lib/renderer-config.ts +50 -0
- package/lib/renderer.ts +87 -0
- package/lib/reporting-story-budget.ts +638 -0
- package/lib/semantic-graph.ts +554 -0
- package/lib/site-brand-tenant.ts +41 -0
- package/lib/site-brand.ts +179 -0
- package/lib/site-renderer.ts +17 -0
- package/lib/site-stack.ts +195 -0
- package/lib/themed-image.ts +30 -0
- package/lib/utils.ts +6 -0
- package/middleware.ts +78 -0
- package/package.json +296 -1
- package/publications/anth_us/brand.ts +68 -0
- package/publications/anth_us/theme.css +66 -0
- package/publications/papyrus/theme.css +11 -0
- package/publications/pilobol_us/brand.ts +37 -0
- package/publications/pilobol_us/theme.css +140 -0
- package/publications/threat_intelligence/blog-defense/graph.ts +636 -0
- package/publications/threat_intelligence/blog-defense/layout.ts +934 -0
- package/publications/threat_intelligence/blog-defense/page-background.tsx +590 -0
- package/publications/threat_intelligence/brand.ts +40 -0
- package/publications/threat_intelligence/pictograms/art.tsx +608 -0
- package/publications/threat_intelligence/pictograms/figure.tsx +82 -0
- package/publications/threat_intelligence/pictograms/registry.ts +30 -0
- package/publications/threat_intelligence/pictograms/system.tsx +305 -0
- package/publications/threat_intelligence/seed/seed-edition-content.json +629 -0
- package/publications/threat_intelligence/theme.css +1440 -0
- package/renderers/markus/index.ts +25 -0
- package/renderers/markus/stubs.tsx +33 -0
- package/renderers/pretext/article-page.tsx +174 -0
- package/renderers/pretext/index.ts +30 -0
- package/renderers/pretext/layouts.ts +16 -0
- package/renderers/pretext/presentation-shell.tsx +477 -0
- package/routes.manifest.json +182 -0
- package/scripts/fixtures/pilobol-sample.ast.json +234 -0
- package/src/backend.js +1 -0
- package/src/backend.ts +3 -0
- package/src/sync.mjs +87 -0
- package/src/with-papyrus.d.mts +2 -0
- package/src/with-papyrus.mjs +47 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AssignmentRecord,
|
|
3
|
+
CategorySteeringCategory,
|
|
4
|
+
MessageRecord,
|
|
5
|
+
NewsroomSectionRecord,
|
|
6
|
+
ReferenceAttachmentRecord,
|
|
7
|
+
ReferenceRecord,
|
|
8
|
+
SemanticNodeRecord,
|
|
9
|
+
SemanticRelationRecord,
|
|
10
|
+
} from "./category-repository";
|
|
11
|
+
import { isEvidenceEligibleReference } from "./reference-policy";
|
|
12
|
+
|
|
13
|
+
export const SEMANTIC_OBJECT_KINDS = [
|
|
14
|
+
"reference",
|
|
15
|
+
"assignment",
|
|
16
|
+
"item",
|
|
17
|
+
"category",
|
|
18
|
+
"categorySet",
|
|
19
|
+
"semanticNode",
|
|
20
|
+
"semanticRelation",
|
|
21
|
+
"message",
|
|
22
|
+
"steeringProposal",
|
|
23
|
+
"steeringDecision",
|
|
24
|
+
"knowledgeArtifact",
|
|
25
|
+
"knowledgeImportRun",
|
|
26
|
+
"newsroomSection",
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
export type SemanticObjectKind = typeof SEMANTIC_OBJECT_KINDS[number];
|
|
30
|
+
|
|
31
|
+
export type SemanticPredicateId =
|
|
32
|
+
| "classified_as"
|
|
33
|
+
| "quality_rating_is"
|
|
34
|
+
| "reference_summary_100_tokens"
|
|
35
|
+
| "reference_summary_200_tokens"
|
|
36
|
+
| "reference_summary_500_tokens"
|
|
37
|
+
| "reference_summary_1000_tokens"
|
|
38
|
+
| "mentions"
|
|
39
|
+
| "has_editorial_form"
|
|
40
|
+
| "about"
|
|
41
|
+
| "comment"
|
|
42
|
+
| "insight_about"
|
|
43
|
+
| "ingestion_rationale"
|
|
44
|
+
| "uses_evidence"
|
|
45
|
+
| "uses_signal"
|
|
46
|
+
| "requests_work_on"
|
|
47
|
+
| "planned_for_edition"
|
|
48
|
+
| "targets_lane"
|
|
49
|
+
| "targets_section"
|
|
50
|
+
| "targets_topic"
|
|
51
|
+
| "scoped_to_topic"
|
|
52
|
+
| "produces"
|
|
53
|
+
| "blocked_by"
|
|
54
|
+
| "derived_from"
|
|
55
|
+
| "related_to"
|
|
56
|
+
| "broader_than"
|
|
57
|
+
| "narrower_than"
|
|
58
|
+
| "supports"
|
|
59
|
+
| "contradicts";
|
|
60
|
+
|
|
61
|
+
export type SemanticPredicateDefinition = {
|
|
62
|
+
id: SemanticPredicateId | string;
|
|
63
|
+
label: string;
|
|
64
|
+
group: "knowledge" | "editorial" | "workflow" | "evidence" | "ontology" | "commentary" | "publication" | "generic" | "classification" | "curation" | "summarization";
|
|
65
|
+
inverseLabel: string;
|
|
66
|
+
contextPackTags?: string[];
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const SEMANTIC_PREDICATES: SemanticPredicateDefinition[] = [
|
|
70
|
+
{ id: "classified_as", label: "classified as", group: "knowledge", inverseLabel: "classified references/items", contextPackTags: ["reference_graph", "research", "category_context"] },
|
|
71
|
+
{ id: "quality_rating_is", label: "quality rating is", group: "curation", inverseLabel: "quality rating for", contextPackTags: ["reference_curation", "research", "context_ranking"] },
|
|
72
|
+
{ id: "reference_summary_100_tokens", label: "100-token reference summary", group: "summarization", inverseLabel: "100-token summary for", contextPackTags: ["reference_curation", "research", "context_ranking"] },
|
|
73
|
+
{ id: "reference_summary_200_tokens", label: "200-token reference summary", group: "summarization", inverseLabel: "200-token summary for", contextPackTags: ["reference_curation", "research", "context_ranking"] },
|
|
74
|
+
{ id: "reference_summary_500_tokens", label: "500-token reference summary", group: "summarization", inverseLabel: "500-token summary for", contextPackTags: ["reference_curation", "research", "context_ranking"] },
|
|
75
|
+
{ id: "reference_summary_1000_tokens", label: "1000-token reference summary", group: "summarization", inverseLabel: "1000-token summary for", contextPackTags: ["reference_curation", "research", "context_ranking"] },
|
|
76
|
+
{ id: "mentions", label: "mentions", group: "ontology", inverseLabel: "mentioned by" },
|
|
77
|
+
{ id: "has_editorial_form", label: "has editorial form", group: "editorial", inverseLabel: "items by editorial form", contextPackTags: ["editing", "publication", "assignment_context"] },
|
|
78
|
+
{ id: "insight_about", label: "insight about", group: "knowledge", inverseLabel: "insights", contextPackTags: ["research", "reference_graph", "editing"] },
|
|
79
|
+
{ id: "about", label: "about", group: "commentary", inverseLabel: "commentary" },
|
|
80
|
+
{ id: "comment", label: "comments on", group: "commentary", inverseLabel: "commented on by", contextPackTags: ["reference_curation", "editing", "research", "assignment_context"] },
|
|
81
|
+
{ id: "ingestion_rationale", label: "ingestion rationale for", group: "commentary", inverseLabel: "ingestion rationale", contextPackTags: ["reference_curation", "editing", "research", "assignment_context"] },
|
|
82
|
+
{ id: "requests_work_on", label: "requests work on", group: "workflow", inverseLabel: "requested work", contextPackTags: ["assignment_context", "editing"] },
|
|
83
|
+
{ id: "uses_evidence", label: "uses evidence", group: "evidence", inverseLabel: "used as evidence by", contextPackTags: ["assignment_context", "research", "reference_graph"] },
|
|
84
|
+
{ id: "uses_signal", label: "uses signal", group: "evidence", inverseLabel: "signal for", contextPackTags: ["assignment_context", "research", "reference_graph"] },
|
|
85
|
+
{ id: "planned_for_edition", label: "planned for edition", group: "publication", inverseLabel: "planned assignments", contextPackTags: ["assignment_context", "editing", "publication"] },
|
|
86
|
+
{ id: "targets_lane", label: "targets lane", group: "editorial", inverseLabel: "lane targets", contextPackTags: ["assignment_context", "editing", "publication"] },
|
|
87
|
+
{ id: "targets_section", label: "targets section", group: "editorial", inverseLabel: "section assignments", contextPackTags: ["assignment_context", "editing", "publication"] },
|
|
88
|
+
{ id: "targets_topic", label: "targets topic", group: "editorial", inverseLabel: "topic assignments", contextPackTags: ["assignment_context", "editing", "publication", "research"] },
|
|
89
|
+
{ id: "scoped_to_topic", label: "scoped to topic", group: "ontology", inverseLabel: "semantic concepts scoped here", contextPackTags: ["assignment_context", "research", "reference_graph", "category_context"] },
|
|
90
|
+
{ id: "produces", label: "produces", group: "workflow", inverseLabel: "produced by" },
|
|
91
|
+
{ id: "blocked_by", label: "blocked by", group: "workflow", inverseLabel: "blocks" },
|
|
92
|
+
{ id: "derived_from", label: "derived from", group: "evidence", inverseLabel: "source for" },
|
|
93
|
+
{ id: "related_to", label: "related to", group: "generic", inverseLabel: "related from" },
|
|
94
|
+
{ id: "broader_than", label: "broader than", group: "ontology", inverseLabel: "narrower concept" },
|
|
95
|
+
{ id: "narrower_than", label: "narrower than", group: "ontology", inverseLabel: "broader concept" },
|
|
96
|
+
{ id: "supports", label: "supports", group: "evidence", inverseLabel: "supported by" },
|
|
97
|
+
{ id: "contradicts", label: "contradicts", group: "evidence", inverseLabel: "contradicted by" },
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
export type SemanticObjectRecord =
|
|
101
|
+
| ReferenceRecord
|
|
102
|
+
| CategorySteeringCategory
|
|
103
|
+
| SemanticNodeRecord
|
|
104
|
+
| MessageRecord
|
|
105
|
+
| NewsroomSectionRecord
|
|
106
|
+
| AssignmentRecord
|
|
107
|
+
| SemanticRelationRecord;
|
|
108
|
+
|
|
109
|
+
export type SemanticObjectSummary = {
|
|
110
|
+
kind: SemanticObjectKind;
|
|
111
|
+
id: string;
|
|
112
|
+
lineageId: string;
|
|
113
|
+
versionNumber?: number | null;
|
|
114
|
+
label: string;
|
|
115
|
+
subtitle?: string | null;
|
|
116
|
+
href: string;
|
|
117
|
+
record?: SemanticObjectRecord;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export type SemanticNeighborGroup = {
|
|
121
|
+
predicate: string;
|
|
122
|
+
label: string;
|
|
123
|
+
direction: "outgoing" | "incoming";
|
|
124
|
+
relations: SemanticRelationRecord[];
|
|
125
|
+
objects: SemanticObjectSummary[];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export type SemanticWalkResult = {
|
|
129
|
+
start: SemanticObjectSummary | null;
|
|
130
|
+
nodes: SemanticObjectSummary[];
|
|
131
|
+
edges: SemanticRelationRecord[];
|
|
132
|
+
warnings: string[];
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export type SemanticGraphSnapshotInput = {
|
|
136
|
+
references: ReferenceRecord[];
|
|
137
|
+
categories: CategorySteeringCategory[];
|
|
138
|
+
semanticNodes: SemanticNodeRecord[];
|
|
139
|
+
messages: MessageRecord[];
|
|
140
|
+
newsroomSections?: NewsroomSectionRecord[];
|
|
141
|
+
semanticRelations: SemanticRelationRecord[];
|
|
142
|
+
assignments?: AssignmentRecord[];
|
|
143
|
+
referenceAttachments?: ReferenceAttachmentRecord[];
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export function semanticStateKey(kind: string, lineageId: string, state = "current"): string {
|
|
147
|
+
return `${kind}#${lineageId}#${state}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function semanticVersionKey(kind: string, versionId: string): string {
|
|
151
|
+
return `${kind}#${versionId}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function semanticObjectSubjectStateKey(objectKind: string, objectLineageId: string, subjectKind: string, state = "current"): string {
|
|
155
|
+
return `${objectKind}#${objectLineageId}#${state}#${subjectKind}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function semanticPredicateObjectStateKey(predicate: string, objectKind: string, objectLineageId: string, state = "current"): string {
|
|
159
|
+
return `${predicate}#${objectKind}#${objectLineageId}#${state}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function relationTypeKey(relation: Pick<SemanticRelationRecord, "predicate" | "relationTypeKey">): string {
|
|
163
|
+
return relation.relationTypeKey ?? relation.predicate;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function relationDomain(relation: Pick<SemanticRelationRecord, "predicate" | "relationTypeKey" | "relationDomain">): string {
|
|
167
|
+
if (relation.relationDomain) return relation.relationDomain;
|
|
168
|
+
return SEMANTIC_PREDICATES.find((entry) => entry.id === relationTypeKey(relation))?.group ?? "generic";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function predicateLabel(predicate: string, direction: "outgoing" | "incoming" = "outgoing"): string {
|
|
172
|
+
const definition = SEMANTIC_PREDICATES.find((entry) => entry.id === predicate);
|
|
173
|
+
if (!definition) return predicate.replaceAll("_", " ");
|
|
174
|
+
return direction === "incoming" ? definition.inverseLabel : definition.label;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function newsDeskHrefForSemanticObject(kind: string, lineageId: string): string {
|
|
178
|
+
const encoded = encodeURIComponent(lineageId);
|
|
179
|
+
if (kind === "reference") return `/newsroom/references/${encoded}`;
|
|
180
|
+
if (kind === "category") return `/newsroom/topics?category=${encoded}`;
|
|
181
|
+
if (kind === "semanticNode") return `/newsroom/concepts?node=${encoded}`;
|
|
182
|
+
if (kind === "assignment") return `/newsroom/assignments?assignment=${encoded}`;
|
|
183
|
+
if (kind === "item") return `/newsroom?item=${encoded}`;
|
|
184
|
+
if (kind === "message") return `/newsroom/messages?message=${encoded}`;
|
|
185
|
+
if (kind === "newsroomSection") return `/newsroom/sections/${encoded}`;
|
|
186
|
+
return `/newsroom?object=${encodeURIComponent(kind)}:${encoded}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function createSemanticGraphSnapshot(input: SemanticGraphSnapshotInput) {
|
|
190
|
+
return new SemanticGraphSnapshot(input);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export class SemanticGraphSnapshot {
|
|
194
|
+
private referencesById = new Map<string, ReferenceRecord>();
|
|
195
|
+
private referencesByLineage = new Map<string, ReferenceRecord>();
|
|
196
|
+
private categoriesById = new Map<string, CategorySteeringCategory>();
|
|
197
|
+
private categoriesByLineage = new Map<string, CategorySteeringCategory>();
|
|
198
|
+
private semanticNodesById = new Map<string, SemanticNodeRecord>();
|
|
199
|
+
private semanticNodesByLineage = new Map<string, SemanticNodeRecord>();
|
|
200
|
+
private messagesById = new Map<string, MessageRecord>();
|
|
201
|
+
private assignmentsById = new Map<string, AssignmentRecord>();
|
|
202
|
+
private newsroomSectionsById = new Map<string, NewsroomSectionRecord>();
|
|
203
|
+
private relationsBySubjectState = new Map<string, SemanticRelationRecord[]>();
|
|
204
|
+
private relationsByObjectState = new Map<string, SemanticRelationRecord[]>();
|
|
205
|
+
|
|
206
|
+
readonly attachmentsByReferenceLineage = new Map<string, ReferenceAttachmentRecord[]>();
|
|
207
|
+
|
|
208
|
+
constructor(private input: SemanticGraphSnapshotInput) {
|
|
209
|
+
for (const reference of input.references) {
|
|
210
|
+
indexVersionedRecord(reference, this.referencesById, this.referencesByLineage);
|
|
211
|
+
}
|
|
212
|
+
for (const category of input.categories) {
|
|
213
|
+
indexVersionedRecord(category, this.categoriesById, this.categoriesByLineage);
|
|
214
|
+
}
|
|
215
|
+
for (const node of input.semanticNodes) {
|
|
216
|
+
indexVersionedRecord(node, this.semanticNodesById, this.semanticNodesByLineage);
|
|
217
|
+
}
|
|
218
|
+
for (const message of input.messages) {
|
|
219
|
+
this.messagesById.set(message.id, message);
|
|
220
|
+
}
|
|
221
|
+
for (const assignment of input.assignments ?? []) {
|
|
222
|
+
this.assignmentsById.set(assignment.id, assignment);
|
|
223
|
+
}
|
|
224
|
+
for (const section of input.newsroomSections ?? []) {
|
|
225
|
+
this.newsroomSectionsById.set(section.id, section);
|
|
226
|
+
}
|
|
227
|
+
for (const relation of input.semanticRelations.filter((entry) => entry.relationState === "current")) {
|
|
228
|
+
pushMap(this.relationsBySubjectState, relation.subjectStateKey, relation);
|
|
229
|
+
pushMap(this.relationsByObjectState, relation.objectStateKey, relation);
|
|
230
|
+
}
|
|
231
|
+
for (const attachment of input.referenceAttachments ?? []) {
|
|
232
|
+
pushMap(this.attachmentsByReferenceLineage, attachment.referenceLineageId, attachment);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
resolve(kind: string, idOrLineageId: string): SemanticObjectSummary | null {
|
|
237
|
+
if (!idOrLineageId) return null;
|
|
238
|
+
if (kind === "reference") return summarizeReference(this.referencesByLineage.get(idOrLineageId) ?? this.referencesById.get(idOrLineageId) ?? null);
|
|
239
|
+
if (kind === "category") return summarizeCategory(this.categoriesByLineage.get(idOrLineageId) ?? this.categoriesById.get(idOrLineageId) ?? null);
|
|
240
|
+
if (kind === "semanticNode") return summarizeSemanticNode(this.semanticNodesByLineage.get(idOrLineageId) ?? this.semanticNodesById.get(idOrLineageId) ?? null);
|
|
241
|
+
if (kind === "message") return summarizeMessage(this.messagesById.get(idOrLineageId) ?? null);
|
|
242
|
+
if (kind === "assignment") return summarizeAssignment(this.assignmentsById.get(idOrLineageId) ?? null);
|
|
243
|
+
if (kind === "newsroomSection") return summarizeNewsroomSection(this.newsroomSectionsById.get(idOrLineageId) ?? null);
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
resolveRelationObject(relation: SemanticRelationRecord, direction: "outgoing" | "incoming"): SemanticObjectSummary | null {
|
|
248
|
+
return direction === "outgoing"
|
|
249
|
+
? this.resolve(relation.objectKind, relation.objectLineageId) ?? fallbackRelationObject(relation, "object")
|
|
250
|
+
: this.resolve(relation.subjectKind, relation.subjectLineageId) ?? fallbackRelationObject(relation, "subject");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
outgoing(kind: string, lineageId: string): SemanticRelationRecord[] {
|
|
254
|
+
return this.relationsBySubjectState.get(semanticStateKey(kind, lineageId)) ?? [];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
incoming(kind: string, lineageId: string): SemanticRelationRecord[] {
|
|
258
|
+
return this.relationsByObjectState.get(semanticStateKey(kind, lineageId)) ?? [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
neighbors(kind: string, lineageId: string): SemanticNeighborGroup[] {
|
|
262
|
+
const groups: SemanticNeighborGroup[] = [];
|
|
263
|
+
groups.push(...groupRelations(this.outgoing(kind, lineageId), "outgoing", (relation) => this.resolveRelationObject(relation, "outgoing")));
|
|
264
|
+
groups.push(...groupRelations(this.incoming(kind, lineageId), "incoming", (relation) => this.resolveRelationObject(relation, "incoming")));
|
|
265
|
+
return groups;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
referencesForCategory(categoryLineageId: string): SemanticObjectSummary[] {
|
|
269
|
+
return this.subjectsForObject("category", categoryLineageId, "reference", "classified_as")
|
|
270
|
+
.filter(isEvidenceEligibleReferenceSummary);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
referencesForSemanticNode(nodeLineageId: string, predicate?: string): SemanticObjectSummary[] {
|
|
274
|
+
return this.subjectsForObject("semanticNode", nodeLineageId, "reference", predicate)
|
|
275
|
+
.filter(isEvidenceEligibleReferenceSummary);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
subjectsForObject(objectKind: string, objectLineageId: string, subjectKind: string, predicate?: string): SemanticObjectSummary[] {
|
|
279
|
+
const relations = this.incoming(objectKind, objectLineageId)
|
|
280
|
+
.filter((relation) => relation.subjectKind === subjectKind)
|
|
281
|
+
.filter((relation) => !predicate || relationTypeKey(relation) === predicate || relation.predicate === predicate);
|
|
282
|
+
return uniqueSemanticObjects(relations.map((relation) => this.resolve(relation.subjectKind, relation.subjectLineageId) ?? fallbackRelationObject(relation, "subject")));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
messagesFor(kind: string, lineageId: string): MessageRecord[] {
|
|
286
|
+
const incomingCommentMessages = this.incoming(kind, lineageId)
|
|
287
|
+
.filter((relation) => relation.subjectKind === "message")
|
|
288
|
+
.filter((relation) => ["comment", "ingestion_rationale"].includes(relationTypeKey(relation)) || ["comment", "ingestion_rationale"].includes(relation.predicate))
|
|
289
|
+
.map((relation) => this.messagesById.get(relation.subjectId))
|
|
290
|
+
.filter((message): message is MessageRecord => Boolean(message));
|
|
291
|
+
const producedMessages = this.outgoing(kind, lineageId)
|
|
292
|
+
.filter((relation) => relation.objectKind === "message")
|
|
293
|
+
.filter((relation) => relationTypeKey(relation) === "produces" || relation.predicate === "produces")
|
|
294
|
+
.map((relation) => this.messagesById.get(relation.objectId))
|
|
295
|
+
.filter((message): message is MessageRecord => Boolean(message));
|
|
296
|
+
return uniqueBy([...incomingCommentMessages, ...producedMessages], (message) => message.id)
|
|
297
|
+
.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
summariesFor(kind: string, lineageId: string): MessageRecord[] {
|
|
301
|
+
const ranked = this.incoming(kind, lineageId)
|
|
302
|
+
.filter((relation) => relation.subjectKind === "message")
|
|
303
|
+
.filter((relation) => isReferenceSummaryPredicate(relationTypeKey(relation)) || isReferenceSummaryPredicate(relation.predicate))
|
|
304
|
+
.map((relation) => ({
|
|
305
|
+
rank: summaryPredicateRank(relationTypeKey(relation)),
|
|
306
|
+
message: this.messagesById.get(relation.subjectId),
|
|
307
|
+
}))
|
|
308
|
+
.filter((entry): entry is { rank: number; message: MessageRecord } => Boolean(entry.message && entry.message.messageKind === "reference_summary"));
|
|
309
|
+
ranked.sort((left, right) => left.rank - right.rank || right.message.createdAt.localeCompare(left.message.createdAt));
|
|
310
|
+
return ranked.map((entry) => entry.message);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
insightsFor(kind: string, lineageId: string): MessageRecord[] {
|
|
314
|
+
return this.incoming(kind, lineageId)
|
|
315
|
+
.filter((relation) => relation.subjectKind === "message")
|
|
316
|
+
.filter((relation) => relationTypeKey(relation) === "insight_about" || relation.predicate === "insight_about")
|
|
317
|
+
.map((relation) => this.messagesById.get(relation.subjectId))
|
|
318
|
+
.filter((message): message is MessageRecord => Boolean(message))
|
|
319
|
+
.filter((message) => message.messageKind === "insight" && message.messageDomain === "knowledge")
|
|
320
|
+
.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
currentReferenceQualityRating(referenceLineageId: string): number | null {
|
|
324
|
+
const relation = this.outgoing("reference", referenceLineageId)
|
|
325
|
+
.find((entry) => relationTypeKey(entry) === "quality_rating_is" || entry.predicate === "quality_rating_is");
|
|
326
|
+
return relation ? qualityRatingFromRelation(relation) : null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
attachmentsForReference(referenceLineageId: string): ReferenceAttachmentRecord[] {
|
|
330
|
+
return (this.attachmentsByReferenceLineage.get(referenceLineageId) ?? []).sort((left, right) => left.sortKey.localeCompare(right.sortKey));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
walk(kind: string, lineageId: string, depth = 2): SemanticWalkResult {
|
|
334
|
+
const start = this.resolve(kind, lineageId);
|
|
335
|
+
if (!start) return { start: null, nodes: [], edges: [], warnings: [`Missing start object ${kind}#${lineageId}`] };
|
|
336
|
+
const nodes = new Map<string, SemanticObjectSummary>([[`${start.kind}#${start.lineageId}`, start]]);
|
|
337
|
+
const edges = new Map<string, SemanticRelationRecord>();
|
|
338
|
+
let frontier = [start];
|
|
339
|
+
for (let level = 0; level < Math.max(0, depth); level += 1) {
|
|
340
|
+
const nextFrontier: SemanticObjectSummary[] = [];
|
|
341
|
+
for (const node of frontier) {
|
|
342
|
+
for (const relation of [...this.outgoing(node.kind, node.lineageId), ...this.incoming(node.kind, node.lineageId)]) {
|
|
343
|
+
edges.set(relation.id, relation);
|
|
344
|
+
const neighbor = relation.subjectLineageId === node.lineageId && relation.subjectKind === node.kind
|
|
345
|
+
? this.resolveRelationObject(relation, "outgoing")
|
|
346
|
+
: this.resolveRelationObject(relation, "incoming");
|
|
347
|
+
if (!neighbor) continue;
|
|
348
|
+
const key = `${neighbor.kind}#${neighbor.lineageId}`;
|
|
349
|
+
if (!nodes.has(key)) {
|
|
350
|
+
nodes.set(key, neighbor);
|
|
351
|
+
nextFrontier.push(neighbor);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
frontier = nextFrontier;
|
|
356
|
+
if (!frontier.length) break;
|
|
357
|
+
}
|
|
358
|
+
return { start, nodes: Array.from(nodes.values()), edges: Array.from(edges.values()), warnings: [] };
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function indexVersionedRecord<T extends { id: string; lineageId?: string | null; versionState?: string | null; versionNumber?: number | null }>(
|
|
363
|
+
record: T,
|
|
364
|
+
byId: Map<string, T>,
|
|
365
|
+
byLineage: Map<string, T>,
|
|
366
|
+
) {
|
|
367
|
+
byId.set(record.id, record);
|
|
368
|
+
const lineageId = record.lineageId ?? record.id;
|
|
369
|
+
const current = byLineage.get(lineageId);
|
|
370
|
+
if (!current || record.versionState === "current" || Number(record.versionNumber ?? 0) > Number(current.versionNumber ?? 0)) {
|
|
371
|
+
byLineage.set(lineageId, record);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function groupRelations(
|
|
376
|
+
relations: SemanticRelationRecord[],
|
|
377
|
+
direction: "outgoing" | "incoming",
|
|
378
|
+
objectFor: (relation: SemanticRelationRecord) => SemanticObjectSummary | null,
|
|
379
|
+
): SemanticNeighborGroup[] {
|
|
380
|
+
const groups = new Map<string, SemanticRelationRecord[]>();
|
|
381
|
+
for (const relation of relations) pushMap(groups, relationTypeKey(relation), relation);
|
|
382
|
+
return Array.from(groups.entries()).map(([predicate, entries]) => ({
|
|
383
|
+
predicate,
|
|
384
|
+
label: predicateLabel(predicate, direction),
|
|
385
|
+
direction,
|
|
386
|
+
relations: entries.sort(compareRelations),
|
|
387
|
+
objects: uniqueSemanticObjects(entries.map(objectFor)),
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function compareRelations(left: SemanticRelationRecord, right: SemanticRelationRecord): number {
|
|
392
|
+
const rankDiff = (left.rank ?? 999999) - (right.rank ?? 999999);
|
|
393
|
+
if (rankDiff !== 0) return rankDiff;
|
|
394
|
+
return (right.score ?? 0) - (left.score ?? 0);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function uniqueSemanticObjects(objects: Array<SemanticObjectSummary | null>): SemanticObjectSummary[] {
|
|
398
|
+
const map = new Map<string, SemanticObjectSummary>();
|
|
399
|
+
for (const object of objects) {
|
|
400
|
+
if (!object) continue;
|
|
401
|
+
map.set(`${object.kind}#${object.lineageId}`, object);
|
|
402
|
+
}
|
|
403
|
+
return Array.from(map.values()).sort((left, right) => left.label.localeCompare(right.label));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function isEvidenceEligibleReferenceSummary(summary: SemanticObjectSummary): boolean {
|
|
407
|
+
return summary.kind === "reference" && isEvidenceEligibleReference(summary.record as ReferenceRecord | undefined);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function summarizeReference(record: ReferenceRecord | null): SemanticObjectSummary | null {
|
|
411
|
+
if (!record) return null;
|
|
412
|
+
const lineageId = record.lineageId ?? record.id;
|
|
413
|
+
return {
|
|
414
|
+
kind: "reference",
|
|
415
|
+
id: record.id,
|
|
416
|
+
lineageId,
|
|
417
|
+
versionNumber: record.versionNumber,
|
|
418
|
+
label: record.title ?? record.externalItemId,
|
|
419
|
+
subtitle: record.mediaType ?? record.storagePath ?? record.sourceUri,
|
|
420
|
+
href: newsDeskHrefForSemanticObject("reference", lineageId),
|
|
421
|
+
record,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function summarizeCategory(record: CategorySteeringCategory | null): SemanticObjectSummary | null {
|
|
426
|
+
if (!record) return null;
|
|
427
|
+
const lineageId = record.lineageId ?? record.id;
|
|
428
|
+
return {
|
|
429
|
+
kind: "category",
|
|
430
|
+
id: record.id,
|
|
431
|
+
lineageId,
|
|
432
|
+
versionNumber: record.versionNumber,
|
|
433
|
+
label: record.displayName,
|
|
434
|
+
subtitle: record.subtitle ?? record.categoryKey,
|
|
435
|
+
href: newsDeskHrefForSemanticObject("category", lineageId),
|
|
436
|
+
record,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function summarizeSemanticNode(record: SemanticNodeRecord | null): SemanticObjectSummary | null {
|
|
441
|
+
if (!record) return null;
|
|
442
|
+
const lineageId = record.lineageId ?? record.id;
|
|
443
|
+
return {
|
|
444
|
+
kind: "semanticNode",
|
|
445
|
+
id: record.id,
|
|
446
|
+
lineageId,
|
|
447
|
+
versionNumber: record.versionNumber,
|
|
448
|
+
label: record.displayName ?? record.nodeKey,
|
|
449
|
+
subtitle: record.nodeKind,
|
|
450
|
+
href: newsDeskHrefForSemanticObject("semanticNode", lineageId),
|
|
451
|
+
record,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function summarizeMessage(record: MessageRecord | null): SemanticObjectSummary | null {
|
|
456
|
+
if (!record) return null;
|
|
457
|
+
const lineageId = record.id;
|
|
458
|
+
return {
|
|
459
|
+
kind: "message",
|
|
460
|
+
id: record.id,
|
|
461
|
+
lineageId,
|
|
462
|
+
versionNumber: 1,
|
|
463
|
+
label: record.summary ?? record.messageKind,
|
|
464
|
+
subtitle: record.source ?? record.messageDomain,
|
|
465
|
+
href: newsDeskHrefForSemanticObject("message", lineageId),
|
|
466
|
+
record,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function summarizeAssignment(record: AssignmentRecord | null): SemanticObjectSummary | null {
|
|
471
|
+
if (!record) return null;
|
|
472
|
+
return {
|
|
473
|
+
kind: "assignment",
|
|
474
|
+
id: record.id,
|
|
475
|
+
lineageId: record.id,
|
|
476
|
+
label: record.title,
|
|
477
|
+
subtitle: `${record.assignmentTypeKey} / ${record.status}`,
|
|
478
|
+
href: newsDeskHrefForSemanticObject("assignment", record.id),
|
|
479
|
+
record,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function summarizeNewsroomSection(record: NewsroomSectionRecord | null): SemanticObjectSummary | null {
|
|
484
|
+
if (!record) return null;
|
|
485
|
+
return {
|
|
486
|
+
kind: "newsroomSection",
|
|
487
|
+
id: record.id,
|
|
488
|
+
lineageId: record.id,
|
|
489
|
+
label: record.title,
|
|
490
|
+
subtitle: record.type,
|
|
491
|
+
href: newsDeskHrefForSemanticObject("newsroomSection", record.id),
|
|
492
|
+
record,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function fallbackRelationObject(relation: SemanticRelationRecord, side: "subject" | "object"): SemanticObjectSummary {
|
|
497
|
+
const kind = (side === "subject" ? relation.subjectKind : relation.objectKind) as SemanticObjectKind;
|
|
498
|
+
const id = side === "subject" ? relation.subjectId : relation.objectId;
|
|
499
|
+
const lineageId = side === "subject" ? relation.subjectLineageId : relation.objectLineageId;
|
|
500
|
+
const versionNumber = side === "subject" ? relation.subjectVersionNumber : relation.objectVersionNumber;
|
|
501
|
+
return {
|
|
502
|
+
kind,
|
|
503
|
+
id,
|
|
504
|
+
lineageId,
|
|
505
|
+
versionNumber,
|
|
506
|
+
label: `${kind} ${lineageId}`,
|
|
507
|
+
href: newsDeskHrefForSemanticObject(kind, lineageId),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function qualityRatingFromRelation(relation: Pick<SemanticRelationRecord, "score" | "objectLineageId" | "objectId">): number | null {
|
|
512
|
+
const score = typeof relation.score === "number" ? relation.score : Number(relation.score);
|
|
513
|
+
if (Number.isFinite(score) && Number.isInteger(score) && score >= 1 && score <= 5) return score;
|
|
514
|
+
return qualityRatingFromNodeKey(relation.objectLineageId) ?? qualityRatingFromNodeKey(relation.objectId);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function qualityRatingFromNodeKey(value: string | null | undefined): number | null {
|
|
518
|
+
const match = /(?:quality[-_.]?rating[-_.]?)?([1-5])[-_.]?star/i.exec(value ?? "");
|
|
519
|
+
return match ? Number(match[1]) : null;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function pushMap<K, V>(map: Map<K, V[]>, key: K, value: V) {
|
|
523
|
+
const values = map.get(key) ?? [];
|
|
524
|
+
values.push(value);
|
|
525
|
+
map.set(key, values);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export const REFERENCE_SUMMARY_PREDICATES = [
|
|
529
|
+
"reference_summary_100_tokens",
|
|
530
|
+
"reference_summary_200_tokens",
|
|
531
|
+
"reference_summary_500_tokens",
|
|
532
|
+
] as const;
|
|
533
|
+
|
|
534
|
+
function isReferenceSummaryPredicate(predicate: string): boolean {
|
|
535
|
+
return REFERENCE_SUMMARY_PREDICATES.includes(predicate as typeof REFERENCE_SUMMARY_PREDICATES[number])
|
|
536
|
+
|| predicate.startsWith("reference_summary_");
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function summaryPredicateRank(predicate: string): number {
|
|
540
|
+
if (predicate === "reference_summary_100_tokens") return 0;
|
|
541
|
+
if (predicate === "reference_summary_200_tokens") return 1;
|
|
542
|
+
if (predicate === "reference_summary_500_tokens") return 2;
|
|
543
|
+
return 3;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function uniqueBy<T>(values: T[], key: (value: T) => string): T[] {
|
|
547
|
+
const seen = new Set<string>();
|
|
548
|
+
return values.filter((value) => {
|
|
549
|
+
const resolved = key(value);
|
|
550
|
+
if (seen.has(resolved)) return false;
|
|
551
|
+
seen.add(resolved);
|
|
552
|
+
return true;
|
|
553
|
+
});
|
|
554
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
getSiteBrand,
|
|
4
|
+
resolveRuntimeSiteBrandId,
|
|
5
|
+
resolveSiteBrandId,
|
|
6
|
+
type SiteBrand,
|
|
7
|
+
type SiteBrandId,
|
|
8
|
+
} from "./site-brand";
|
|
9
|
+
|
|
10
|
+
export const BRAND_OVERRIDE_COOKIE = "papyrus-site-brand-override";
|
|
11
|
+
|
|
12
|
+
export function resolveActiveSiteBrandId(
|
|
13
|
+
cookieOverride: string | undefined | null = null,
|
|
14
|
+
envBrandId: SiteBrandId = resolveSiteBrandId(),
|
|
15
|
+
): SiteBrandId {
|
|
16
|
+
return resolveRuntimeSiteBrandId(cookieOverride) ?? envBrandId;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function resolveActiveSiteBrand(
|
|
20
|
+
cookieOverride: string | undefined | null = null,
|
|
21
|
+
brandId?: SiteBrandId,
|
|
22
|
+
): SiteBrand {
|
|
23
|
+
const resolvedId = brandId ?? resolveActiveSiteBrandId(cookieOverride);
|
|
24
|
+
return getSiteBrand(resolvedId);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveBrandConfigPath(relativePath: string): string {
|
|
28
|
+
return path.isAbsolute(relativePath) ? relativePath : path.join(process.cwd(), relativePath);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getBrandSteeringConfigPath(brand: SiteBrand = resolveActiveSiteBrand()): string {
|
|
32
|
+
return resolveBrandConfigPath(brand.steeringConfigPath);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getBrandNewsroomSectionsConfigPath(brand: SiteBrand = resolveActiveSiteBrand()): string {
|
|
36
|
+
return resolveBrandConfigPath(brand.newsroomSectionsConfigPath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getBrandAnalysisProfilesPath(brand: SiteBrand = resolveActiveSiteBrand()): string {
|
|
40
|
+
return resolveBrandConfigPath(brand.analysisProfilesPath);
|
|
41
|
+
}
|