@cyber-dash-tech/revela 0.17.0 → 0.17.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/lib/commands/narrative.ts +13 -4
- package/lib/commands/review.ts +14 -10
- package/lib/media/download.ts +23 -3
- package/lib/media/save.ts +1 -0
- package/lib/media/types.ts +1 -0
- package/lib/narrative-state/display.ts +74 -4
- package/lib/narrative-state/map-html.ts +242 -107
- package/lib/narrative-state/render-plan.ts +125 -9
- package/lib/qa/checks.ts +206 -5
- package/lib/qa/measure.ts +63 -1
- package/lib/refine/server.ts +157 -20
- package/package.json +1 -1
- package/skill/SKILL.md +6 -1
- package/tools/narrative-view.ts +16 -0
|
@@ -147,7 +147,7 @@ export function buildNarrativeViewPrompt(options: { workspaceRoot: string; langu
|
|
|
147
147
|
relations: map.claimRelations.map((relation) => ({ id: relation.id, fromClaimId: relation.fromClaimId, toClaimId: relation.toClaimId, relation: relation.relation, rationale: relation.rationale, inferred: relation.inferred })),
|
|
148
148
|
objections: map.objections.map((objection) => ({ id: objection.id, claimId: objection.claimId, priority: objection.priority, text: objection.text, response: objection.response })),
|
|
149
149
|
risks: map.risks.map((risk) => ({ id: risk.id, claimId: risk.claimId, severity: risk.severity, text: risk.text, mitigation: risk.mitigation })),
|
|
150
|
-
researchGaps: map.researchGaps.map((gap) => ({ id: gap.id, targetType: gap.targetType, targetId: gap.targetId, status: gap.status, priority: gap.priority, question: gap.question })),
|
|
150
|
+
researchGaps: map.researchGaps.map((gap) => ({ id: gap.id, targetType: gap.targetType, targetId: gap.targetId, status: gap.status, priority: gap.priority, question: gap.question, findingsFile: gap.findingsFile, evidenceBindingIds: gap.evidenceBindingIds, notes: gap.notes })),
|
|
151
151
|
artifactCoverage: map.artifactCoverage.map((artifact) => ({ type: artifact.type, outputPath: artifact.outputPath, stale: artifact.stale, coverageStatus: artifact.coverageStatus, affectedClaimIds: artifact.affectedClaimIds, missingClaimIds: artifact.missingClaimIds, slideRefs: artifact.slideRefs.map((ref) => ({ claimId: ref.claimId, slideIndex: ref.slideIndex, role: ref.role, match: ref.match, location: ref.location })) })),
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -165,18 +165,27 @@ Hard rules:
|
|
|
165
165
|
- Do not invent new claims, evidence, relations, slide coverage, source paths, findings files, quotes, or caveats.
|
|
166
166
|
- Preserve every claimId exactly.
|
|
167
167
|
- Preserve every relation endpoint exactly: fromClaimId, toClaimId, relation.
|
|
168
|
-
- You may only organize and localize display copy for the UI: pageTitle, summaryLine, section labels, claim card displayTitle, roleLabel, narrativeJob, evidenceSummary, supportRationale, supportedScope, unsupportedScope, objectionsSummary, risksSummary, riskOrGapSummary, researchGapsSummary, relation displayLabel, and relation displayRationale.
|
|
168
|
+
- You may only organize and localize display copy for the UI: pageTitle, summaryLine, section labels, claim card displayTitle, roleLabel, narrativeJob, evidenceSummary, supportRationale, supportedScope, unsupportedScope, objectionsSummary, risksSummary, riskOrGapSummary, researchGapsSummary, research gap displayQuestion, relation displayLabel, and relation displayRationale.
|
|
169
169
|
- For inferred relations, do not provide relation displayLabel or displayRationale; inferred relations are unconfirmed order notes, not causal/support/dependency judgments.
|
|
170
170
|
- relation displayRationale may only localize or clarify an existing canonical relation rationale. If relation.rationale is missing or the relation is inferred, do not provide displayRationale; the UI will show the missing or inferred status.
|
|
171
171
|
- Keep source paths, findings files, claim IDs, narrative hash, and numbers unchanged.
|
|
172
|
-
- Translate normal UI/display text into the target language request: pageTitle, summaryLine, labels, claim displayTitle, roleLabel, narrativeJob, evidenceSummary, supportRationale, supportedScope, unsupportedScope, objectionsSummary, risksSummary, riskOrGapSummary, researchGapsSummary, relation displayLabel, and relation displayRationale.
|
|
172
|
+
- Translate normal UI/display text into the target language request: pageTitle, summaryLine, labels, claim displayTitle, roleLabel, narrativeJob, evidenceSummary, supportRationale, supportedScope, unsupportedScope, objectionsSummary, risksSummary, riskOrGapSummary, researchGapsSummary, research gap displayQuestion, relation displayLabel, and relation displayRationale.
|
|
173
173
|
- For every claim in a non-English target language, provide displayTitle so the selected-claim panel does not fall back to canonical English claim text.
|
|
174
|
-
- For every
|
|
174
|
+
- For every evidence/detail field that has canonical user-facing text, provide the matching localized display field when it exists: supportedScope for supported scope, unsupportedScope for evidence boundaries, supportRationale for why the evidence supports the claim, objectionsSummary for objections, risksSummary for risks, and researchGapsSummary for canonical research gaps.
|
|
175
175
|
- Do not translate claim IDs, relation endpoints, narrative hash, source paths, findings files, URLs, numbers, or quoted/source facts.
|
|
176
|
+
- For every canonical research gap in a non-English target language, provide researchGapCards[].displayQuestion so gap cards do not fall back to canonical English question text. Preserve gapId exactly and do not change the gap meaning.
|
|
176
177
|
- Use natural business and manufacturing terminology in the target language, not word-by-word machine translation.
|
|
177
178
|
- If a fact is missing, describe it as missing instead of filling it in.
|
|
178
179
|
- If vaultDiagnostics.blockers is present in the compact map, keep Story read-only but surface the blocker state in summaryLine and the relevant claim card riskOrGapSummary when applicable. Include diagnostic file/nodeId/code/message/suggestedAction in display copy; do not invent missing evidence, source trace, quotes, or caveats to hide the blocker.
|
|
179
180
|
- Do not turn Story into a workflow dashboard: diagnostic copy is for reading context only, not command suggestions or mutation planning.
|
|
181
|
+
- Story UI is evidence-first: clicking a claim shows evidence/gap cards; evidence cards must carry the evidence description, why it supports the claim, and source trace; clicking evidence shows only canonical gaps linked to that evidence.
|
|
182
|
+
- The selected-claim panel is only an evidence/research-gap card list. Do not repeat canonical claim text there; the claim itself is already visible in Claim Flow.
|
|
183
|
+
- Claim cards should not explain gaps or risks through riskOrGapSummary when the same topic belongs in evidence/gap cards. Keep claim cards focused on role, narrative job, and evidence summary.
|
|
184
|
+
- Do not make evidence detail a schema dump. Do not default to unsupportedScope, caveat, IDs, target fields, or evidenceBindingIds for evidence cards; evidence cards should explain what the source says, why it supports the claim, and where it came from.
|
|
185
|
+
- Do not invent, infer, or soften new gaps in display copy. Show research gaps only when they exist in compactMap.researchGaps; do not turn unsupportedScope, weak evidence, caveats, objections, risks, or your own judgment into a gap.
|
|
186
|
+
- researchGapsSummary and riskOrGapSummary may only summarize existing canonical research gaps or diagnostics. If no canonical gap exists for the claim, leave gap-specific display copy empty instead of implying a gap.
|
|
187
|
+
- Localize new Story labels when useful: selectedEvidence, evidenceList, gap, gaps, noEvidence, selectEvidencePrompt, sourceTrace, evidenceSource, whyThisSupports, linkedGaps, selectedGap, and noLinkedGaps.
|
|
188
|
+
- Gap detail may only use canonical research gap fields from the compact map: id, targetType, targetId, status, priority, question, findingsFile, evidenceBindingIds, and notes.
|
|
180
189
|
|
|
181
190
|
Chinese localization rules when the target language request is Chinese, zh, zh-CN, --cn, 中文, or Simplified Chinese:
|
|
182
191
|
- Use natural business/manufacturing Chinese, not word-by-word machine translation.
|
package/lib/commands/review.ts
CHANGED
|
@@ -94,15 +94,16 @@ Workflow:
|
|
|
94
94
|
3. If the read summary returned \`markdownQa.blockers\` or \`vaultDiagnostics.blockers\`, stop before deck planning and report Markdown QA repair cards separately from compile diagnostics with file/node/code/message and smallest repair or suggested next action. If narrative readiness is \`approved\`, continue. If it is \`ready_for_approval\`, ask the user for explicit approval before continuing. If it is blocked, stale, or needs research, stop and report the smallest next action. Do not call \`approveNarrative\` unless the user explicitly approves or requests a render override.
|
|
95
95
|
4. After approval or explicit render override exists, call \`revela-decks\` action \`compileDeckPlan\`. This projects canonical narrative claims and evidence bindings into compatibility \`slides[]\` and \`slides[].evidence[]\`; it must not write HTML.
|
|
96
96
|
5. If \`compileDeckPlan\` returns \`skipped\`, stop and report the reason. Do not invent slide specs manually to bypass approval.
|
|
97
|
-
6.
|
|
98
|
-
7.
|
|
99
|
-
8.
|
|
100
|
-
9.
|
|
101
|
-
10.
|
|
102
|
-
11.
|
|
103
|
-
12.
|
|
104
|
-
13.
|
|
105
|
-
14.
|
|
97
|
+
6. Treat each compiled slide's \`visuals[]\` and \`content.data.visualIntent\` as required render instructions, not optional decoration. Do not downgrade a planned chart, metric card, evidence table, comparison grid, risk matrix, steps view, or media brief into generic bullets unless the plan is revised and reconfirmed.
|
|
98
|
+
7. Present the compiled deck plan to the user and include a low-fidelity layout sketch for every slide. The plan must identify the chapter structure first: 3-5 chapter headings, each chapter's slide range, and which non-structural slides belong to each chapter. The sketch is ASCII/text structure only; do not generate visual images or HTML mockups.
|
|
99
|
+
8. Stop after presenting the plan. Ask the user to confirm or request changes. Do not call \`revela-decks review\`, do not fetch design context, and do not write HTML in the same turn unless the user had already explicitly confirmed the current plan before this command.
|
|
100
|
+
9. Only after explicit user confirmation of the current slide plan, call \`revela-decks\` action \`confirmDeckPlan\` with \`approvalBy=user\` and a compact \`approvalNote\`.
|
|
101
|
+
10. After confirmation is recorded, ask for or confirm visual design only after the narrative deck plan exists. Fetch required design layouts/components with \`revela-designs read\` as needed.
|
|
102
|
+
11. Update only deck/artifact metadata through \`revela-decks upsertDeck\` / \`upsertSlides\` when required by confirmed design/layout choices. Do not change canonical narrative claims unless the user asks to revise the narrative.
|
|
103
|
+
12. Call \`revela-decks\` action \`review\` as the artifact gate. It computes \`writeReadiness\` and review snapshots for deck HTML writing. If it reports \`slide_plan_unconfirmed\`, stop and ask for explicit deck-plan confirmation.
|
|
104
|
+
13. Write \`decks/*.html\` only if the deck/artifact gate is ready and all deck HTML contract requirements can be satisfied. Generate the artifact chapter by chapter instead of drafting all content slides in one broad pass. Keep the HTML file valid after every write, preserve already-written slides, and update one chapter's slide sections at a time.
|
|
105
|
+
14. For each chapter, make every content slide carry a distinct claim, evidence item, comparison, risk, or action. If a chapter lacks enough substance for its allocated slides, merge weak slides or reduce the slide count instead of creating sparse filler.
|
|
106
|
+
15. After each HTML write, the system automatically runs artifact QA before opening Review. If post-write artifact QA reports hard errors, fix them and let QA run again. Review opens only after hard errors pass. Density warnings about thin claim/evidence substance should be reported and improved when useful, but they do not block Review.
|
|
106
107
|
|
|
107
108
|
Deck plan report format:
|
|
108
109
|
- Start with \`Deck plan: awaiting confirmation\` when a plan was compiled and has not yet been confirmed.
|
|
@@ -111,7 +112,7 @@ Deck plan report format:
|
|
|
111
112
|
- Include whether \`compileDeckPlan\` compiled or skipped.
|
|
112
113
|
- Include \`Required structure: Cover + Table of Contents + Closing\` and do not omit any of those slides.
|
|
113
114
|
- Include a \`Chapters\` section before the slide list. It must list 3-5 TOC headings, their slide ranges, and the non-structural slides assigned to each chapter.
|
|
114
|
-
- For every slide, include: slide index, title, purpose, narrative role, low-fidelity layout sketch, layout, components, primary/supporting claim ids, evidence binding ids or source summary, visual intent
|
|
115
|
+
- For every slide, include: slide index, title, purpose, narrative role, low-fidelity layout sketch, layout, components, primary/supporting claim ids, evidence binding ids or source summary, visual intent from \`content.data.visualIntent\`, visual brief from \`visuals[]\`, and caveats/unsupported scope.
|
|
115
116
|
- Use this sketch style or similarly simple ASCII boxes:
|
|
116
117
|
|
|
117
118
|
\`\`\`text
|
|
@@ -133,6 +134,8 @@ Components:
|
|
|
133
134
|
Primary claim:
|
|
134
135
|
Supporting claims:
|
|
135
136
|
Evidence bindings:
|
|
137
|
+
Visual intent:
|
|
138
|
+
Visual brief:
|
|
136
139
|
Caveats / unsupported scope:
|
|
137
140
|
\`\`\`
|
|
138
141
|
- End by asking the user to confirm the deck plan or request changes.
|
|
@@ -146,6 +149,7 @@ Report format before any HTML write after confirmation:
|
|
|
146
149
|
|
|
147
150
|
Rules:
|
|
148
151
|
- \`compileDeckPlan\` is the canonical narrative-to-deck planning path. Do not manually invent slide specs to avoid it.
|
|
152
|
+
- Visual intent is part of the confirmed plan. During HTML generation, satisfy the planned component/visual brief using fetched design components; do not collapse planned visuals into prose-only bullets.
|
|
149
153
|
- Deck slide specs are render-target projections. Canonical narrative remains the authority for audience, decision, claims, evidence boundaries, objections, risks, and approval.
|
|
150
154
|
- Cover, Table of Contents, and Closing are mandatory deck structure. TOC chapter headings must match the chapter grouping used for generation.
|
|
151
155
|
- Do not generate the complete deck content in one broad pass after confirmation. Work chapter by chapter while keeping the artifact valid after each write.
|
package/lib/media/download.ts
CHANGED
|
@@ -12,6 +12,10 @@ const MIME_TO_EXT: Record<string, string> = {
|
|
|
12
12
|
|
|
13
13
|
const ALLOWED_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".svg", ".webp", ".gif", ".ico"])
|
|
14
14
|
const DEFAULT_DOWNLOAD_TIMEOUT_MS = 10_000
|
|
15
|
+
const PRODUCT_USER_AGENT = "Revela/0.17 asset-save"
|
|
16
|
+
const BROWSER_USER_AGENT =
|
|
17
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " +
|
|
18
|
+
"(KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
|
|
15
19
|
|
|
16
20
|
function normalizeExtension(ext: string): string {
|
|
17
21
|
const value = ext.toLowerCase()
|
|
@@ -47,6 +51,24 @@ export async function downloadImageFromUrl(
|
|
|
47
51
|
throw new Error("INVALID_URL")
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
const userAgents = [PRODUCT_USER_AGENT, BROWSER_USER_AGENT]
|
|
55
|
+
let lastError: unknown
|
|
56
|
+
for (const userAgent of userAgents) {
|
|
57
|
+
try {
|
|
58
|
+
return await downloadWithUserAgent(parsed, userAgent, options)
|
|
59
|
+
} catch (error) {
|
|
60
|
+
lastError = error
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function downloadWithUserAgent(
|
|
68
|
+
parsed: URL,
|
|
69
|
+
userAgent: string,
|
|
70
|
+
options: { timeoutMs?: number },
|
|
71
|
+
): Promise<{ buffer: Buffer; contentType: string | null; extension: string }> {
|
|
50
72
|
const controller = new AbortController()
|
|
51
73
|
let timedOut = false
|
|
52
74
|
const timer = setTimeout(() => {
|
|
@@ -60,9 +82,7 @@ export async function downloadImageFromUrl(
|
|
|
60
82
|
response = await fetch(parsed, {
|
|
61
83
|
headers: {
|
|
62
84
|
Accept: "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
|
|
63
|
-
"User-Agent":
|
|
64
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " +
|
|
65
|
-
"(KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
|
|
85
|
+
"User-Agent": userAgent,
|
|
66
86
|
},
|
|
67
87
|
signal: controller.signal,
|
|
68
88
|
})
|
package/lib/media/save.ts
CHANGED
package/lib/media/types.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface NarrativeDisplayModel {
|
|
|
9
9
|
summaryLine?: string
|
|
10
10
|
labels?: Partial<NarrativeDisplayLabels>
|
|
11
11
|
claimCards?: NarrativeDisplayClaimCard[]
|
|
12
|
+
researchGapCards?: NarrativeDisplayResearchGapCard[]
|
|
12
13
|
relations?: NarrativeDisplayRelation[]
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -17,6 +18,18 @@ export interface NarrativeDisplayLabels {
|
|
|
17
18
|
claimFlow: string
|
|
18
19
|
flowNote: string
|
|
19
20
|
selectedClaim: string
|
|
21
|
+
selectedEvidence: string
|
|
22
|
+
evidenceList: string
|
|
23
|
+
gap: string
|
|
24
|
+
gaps: string
|
|
25
|
+
noEvidence: string
|
|
26
|
+
selectEvidencePrompt: string
|
|
27
|
+
sourceTrace: string
|
|
28
|
+
evidenceSource: string
|
|
29
|
+
whyThisSupports: string
|
|
30
|
+
linkedGaps: string
|
|
31
|
+
selectedGap: string
|
|
32
|
+
noLinkedGaps: string
|
|
20
33
|
claim: string
|
|
21
34
|
claimId: string
|
|
22
35
|
status: string
|
|
@@ -48,6 +61,11 @@ export interface NarrativeDisplayClaimCard {
|
|
|
48
61
|
researchGapsSummary?: string
|
|
49
62
|
}
|
|
50
63
|
|
|
64
|
+
export interface NarrativeDisplayResearchGapCard {
|
|
65
|
+
gapId: string
|
|
66
|
+
displayQuestion?: string
|
|
67
|
+
}
|
|
68
|
+
|
|
51
69
|
export interface NarrativeDisplayRelation {
|
|
52
70
|
fromClaimId: string
|
|
53
71
|
toClaimId: string
|
|
@@ -63,6 +81,7 @@ export interface ValidatedNarrativeDisplayModel {
|
|
|
63
81
|
summaryLine?: string
|
|
64
82
|
labels: NarrativeDisplayLabels
|
|
65
83
|
claimCards: Map<string, NarrativeDisplayClaimCard>
|
|
84
|
+
researchGapCards: Map<string, NarrativeDisplayResearchGapCard>
|
|
66
85
|
relations: Map<string, NarrativeDisplayRelation>
|
|
67
86
|
}
|
|
68
87
|
|
|
@@ -71,8 +90,20 @@ export function defaultNarrativeDisplayLabels(language: NarrativeViewLanguage):
|
|
|
71
90
|
return {
|
|
72
91
|
eyebrow: "只读主张流",
|
|
73
92
|
claimFlow: "主张推进",
|
|
74
|
-
flowNote: "
|
|
93
|
+
flowNote: "点击主张查看论据和真实存在的缺口;点击论据查看它关联的缺口。",
|
|
75
94
|
selectedClaim: "当前主张",
|
|
95
|
+
selectedEvidence: "当前论据",
|
|
96
|
+
evidenceList: "论据",
|
|
97
|
+
gap: "缺口",
|
|
98
|
+
gaps: "缺口",
|
|
99
|
+
noEvidence: "没有绑定论据",
|
|
100
|
+
selectEvidencePrompt: "选择一条论据或缺口查看详情",
|
|
101
|
+
sourceTrace: "来源追踪",
|
|
102
|
+
evidenceSource: "来源",
|
|
103
|
+
whyThisSupports: "为什么支撑论点",
|
|
104
|
+
linkedGaps: "这条论据关联的缺口",
|
|
105
|
+
selectedGap: "当前缺口",
|
|
106
|
+
noLinkedGaps: "这条论据没有关联缺口",
|
|
76
107
|
claim: "主张",
|
|
77
108
|
claimId: "主张 ID",
|
|
78
109
|
status: "状态",
|
|
@@ -93,8 +124,20 @@ export function defaultNarrativeDisplayLabels(language: NarrativeViewLanguage):
|
|
|
93
124
|
return {
|
|
94
125
|
eyebrow: "読み取り専用クレームフロー",
|
|
95
126
|
claimFlow: "クレームフロー",
|
|
96
|
-
flowNote: "
|
|
127
|
+
flowNote: "クレームをクリックして根拠と実在するギャップを確認し、根拠をクリックして紐づくギャップを確認します。",
|
|
97
128
|
selectedClaim: "選択中のクレーム",
|
|
129
|
+
selectedEvidence: "選択中の根拠",
|
|
130
|
+
evidenceList: "根拠",
|
|
131
|
+
gap: "ギャップ",
|
|
132
|
+
gaps: "ギャップ",
|
|
133
|
+
noEvidence: "紐づいた根拠はありません",
|
|
134
|
+
selectEvidencePrompt: "根拠またはギャップを選択して詳細を確認してください",
|
|
135
|
+
sourceTrace: "出典トレース",
|
|
136
|
+
evidenceSource: "出典",
|
|
137
|
+
whyThisSupports: "この根拠がクレームを支える理由",
|
|
138
|
+
linkedGaps: "この根拠に紐づくギャップ",
|
|
139
|
+
selectedGap: "選択中のギャップ",
|
|
140
|
+
noLinkedGaps: "この根拠に紐づくギャップはありません",
|
|
98
141
|
claim: "クレーム",
|
|
99
142
|
claimId: "クレーム ID",
|
|
100
143
|
status: "ステータス",
|
|
@@ -114,8 +157,20 @@ export function defaultNarrativeDisplayLabels(language: NarrativeViewLanguage):
|
|
|
114
157
|
return {
|
|
115
158
|
eyebrow: "Read-only claim flow board",
|
|
116
159
|
claimFlow: "Claim Flow",
|
|
117
|
-
flowNote: "Click a claim to
|
|
160
|
+
flowNote: "Click a claim to read its evidence and real gaps; click evidence to see gaps linked to that evidence.",
|
|
118
161
|
selectedClaim: "Selected claim",
|
|
162
|
+
selectedEvidence: "Selected evidence",
|
|
163
|
+
evidenceList: "Evidence",
|
|
164
|
+
gap: "Gap",
|
|
165
|
+
gaps: "Gaps",
|
|
166
|
+
noEvidence: "No evidence bound",
|
|
167
|
+
selectEvidencePrompt: "Select evidence or a gap to inspect details.",
|
|
168
|
+
sourceTrace: "Source trace",
|
|
169
|
+
evidenceSource: "Source",
|
|
170
|
+
whyThisSupports: "Why this supports the claim",
|
|
171
|
+
linkedGaps: "Gaps linked to evidence",
|
|
172
|
+
selectedGap: "Selected gap",
|
|
173
|
+
noLinkedGaps: "No gaps linked to this evidence.",
|
|
119
174
|
claim: "Claim",
|
|
120
175
|
claimId: "Claim ID",
|
|
121
176
|
status: "Status",
|
|
@@ -140,6 +195,7 @@ export function validateNarrativeDisplayModel(map: NarrativeMap, input: Narrativ
|
|
|
140
195
|
if (input.language !== language) throw new Error(`Narrative display model language must be ${language}.`)
|
|
141
196
|
|
|
142
197
|
const claimIds = new Set(map.claimFlow.map((claim) => claim.id))
|
|
198
|
+
const gapIds = new Set(map.researchGaps.map((gap) => gap.id))
|
|
143
199
|
const relationByKey = new Map(map.claimRelations.map((relation) => [relationKey(relation), relation]))
|
|
144
200
|
const claimCards = new Map<string, NarrativeDisplayClaimCard>()
|
|
145
201
|
for (const card of input.claimCards ?? []) {
|
|
@@ -147,6 +203,12 @@ export function validateNarrativeDisplayModel(map: NarrativeMap, input: Narrativ
|
|
|
147
203
|
claimCards.set(card.claimId, cleanClaimCard(card))
|
|
148
204
|
}
|
|
149
205
|
|
|
206
|
+
const researchGapCards = new Map<string, NarrativeDisplayResearchGapCard>()
|
|
207
|
+
for (const card of input.researchGapCards ?? []) {
|
|
208
|
+
if (!gapIds.has(card.gapId)) throw new Error(`Unknown display gapId: ${card.gapId}`)
|
|
209
|
+
researchGapCards.set(card.gapId, cleanResearchGapCard(card))
|
|
210
|
+
}
|
|
211
|
+
|
|
150
212
|
const relations = new Map<string, NarrativeDisplayRelation>()
|
|
151
213
|
for (const relation of input.relations ?? []) {
|
|
152
214
|
const key = relationKey(relation)
|
|
@@ -162,12 +224,13 @@ export function validateNarrativeDisplayModel(map: NarrativeMap, input: Narrativ
|
|
|
162
224
|
summaryLine: clean(input.summaryLine),
|
|
163
225
|
labels: mergeLabels(defaults, input.labels),
|
|
164
226
|
claimCards,
|
|
227
|
+
researchGapCards,
|
|
165
228
|
relations,
|
|
166
229
|
}
|
|
167
230
|
}
|
|
168
231
|
|
|
169
232
|
export function emptyDisplayModel(language: NarrativeViewLanguage, labels = defaultNarrativeDisplayLabels(language)): ValidatedNarrativeDisplayModel {
|
|
170
|
-
return { version: 1, language, labels, claimCards: new Map(), relations: new Map() }
|
|
233
|
+
return { version: 1, language, labels, claimCards: new Map(), researchGapCards: new Map(), relations: new Map() }
|
|
171
234
|
}
|
|
172
235
|
|
|
173
236
|
export function relationKey(relation: Pick<NarrativeDisplayRelation, "fromClaimId" | "toClaimId" | "relation">): string {
|
|
@@ -210,6 +273,13 @@ function cleanClaimCard(card: NarrativeDisplayClaimCard): NarrativeDisplayClaimC
|
|
|
210
273
|
}
|
|
211
274
|
}
|
|
212
275
|
|
|
276
|
+
function cleanResearchGapCard(card: NarrativeDisplayResearchGapCard): NarrativeDisplayResearchGapCard {
|
|
277
|
+
return {
|
|
278
|
+
gapId: card.gapId,
|
|
279
|
+
displayQuestion: clean(card.displayQuestion),
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
213
283
|
function cleanRelation(relation: NarrativeDisplayRelation, canonical: NarrativeMapClaimRelation): NarrativeDisplayRelation {
|
|
214
284
|
const displayLabel = clean(relation.displayLabel)
|
|
215
285
|
const displayRationale = clean(relation.displayRationale)
|