euparliamentmonitor 0.8.4
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 +201 -0
- package/README.md +1005 -0
- package/SECURITY.md +151 -0
- package/package.json +131 -0
- package/scripts/constants/committee-indicator-map.d.ts +199 -0
- package/scripts/constants/committee-indicator-map.d.ts.map +1 -0
- package/scripts/constants/committee-indicator-map.js +1224 -0
- package/scripts/constants/committee-indicator-map.js.map +1 -0
- package/scripts/constants/config.d.ts +38 -0
- package/scripts/constants/config.d.ts.map +1 -0
- package/scripts/constants/config.js +66 -0
- package/scripts/constants/config.js.map +1 -0
- package/scripts/constants/language-articles.d.ts +84 -0
- package/scripts/constants/language-articles.d.ts.map +1 -0
- package/scripts/constants/language-articles.js +6771 -0
- package/scripts/constants/language-articles.js.map +1 -0
- package/scripts/constants/language-core.d.ts +38 -0
- package/scripts/constants/language-core.d.ts.map +1 -0
- package/scripts/constants/language-core.js +90 -0
- package/scripts/constants/language-core.js.map +1 -0
- package/scripts/constants/language-ui.d.ts +82 -0
- package/scripts/constants/language-ui.d.ts.map +1 -0
- package/scripts/constants/language-ui.js +889 -0
- package/scripts/constants/language-ui.js.map +1 -0
- package/scripts/constants/languages.d.ts +14 -0
- package/scripts/constants/languages.d.ts.map +1 -0
- package/scripts/constants/languages.js +15 -0
- package/scripts/constants/languages.js.map +1 -0
- package/scripts/generators/analysis-builders.d.ts +266 -0
- package/scripts/generators/analysis-builders.d.ts.map +1 -0
- package/scripts/generators/analysis-builders.js +2903 -0
- package/scripts/generators/analysis-builders.js.map +1 -0
- package/scripts/generators/breaking-content.d.ts +45 -0
- package/scripts/generators/breaking-content.d.ts.map +1 -0
- package/scripts/generators/breaking-content.js +530 -0
- package/scripts/generators/breaking-content.js.map +1 -0
- package/scripts/generators/committee-helpers.d.ts +54 -0
- package/scripts/generators/committee-helpers.d.ts.map +1 -0
- package/scripts/generators/committee-helpers.js +154 -0
- package/scripts/generators/committee-helpers.js.map +1 -0
- package/scripts/generators/dashboard-content.d.ts +95 -0
- package/scripts/generators/dashboard-content.d.ts.map +1 -0
- package/scripts/generators/dashboard-content.js +630 -0
- package/scripts/generators/dashboard-content.js.map +1 -0
- package/scripts/generators/deep-analysis-content.d.ts +23 -0
- package/scripts/generators/deep-analysis-content.d.ts.map +1 -0
- package/scripts/generators/deep-analysis-content.js +831 -0
- package/scripts/generators/deep-analysis-content.js.map +1 -0
- package/scripts/generators/mindmap-content.d.ts +55 -0
- package/scripts/generators/mindmap-content.d.ts.map +1 -0
- package/scripts/generators/mindmap-content.js +512 -0
- package/scripts/generators/mindmap-content.js.map +1 -0
- package/scripts/generators/motions-content.d.ts +50 -0
- package/scripts/generators/motions-content.d.ts.map +1 -0
- package/scripts/generators/motions-content.js +391 -0
- package/scripts/generators/motions-content.js.map +1 -0
- package/scripts/generators/news-enhanced.d.ts +14 -0
- package/scripts/generators/news-enhanced.d.ts.map +1 -0
- package/scripts/generators/news-enhanced.js +169 -0
- package/scripts/generators/news-enhanced.js.map +1 -0
- package/scripts/generators/news-indexes.d.ts +31 -0
- package/scripts/generators/news-indexes.d.ts.map +1 -0
- package/scripts/generators/news-indexes.js +410 -0
- package/scripts/generators/news-indexes.js.map +1 -0
- package/scripts/generators/pipeline/fetch-stage.d.ts +352 -0
- package/scripts/generators/pipeline/fetch-stage.d.ts.map +1 -0
- package/scripts/generators/pipeline/fetch-stage.js +1522 -0
- package/scripts/generators/pipeline/fetch-stage.js.map +1 -0
- package/scripts/generators/pipeline/generate-stage.d.ts +43 -0
- package/scripts/generators/pipeline/generate-stage.d.ts.map +1 -0
- package/scripts/generators/pipeline/generate-stage.js +204 -0
- package/scripts/generators/pipeline/generate-stage.js.map +1 -0
- package/scripts/generators/pipeline/output-stage.d.ts +48 -0
- package/scripts/generators/pipeline/output-stage.d.ts.map +1 -0
- package/scripts/generators/pipeline/output-stage.js +145 -0
- package/scripts/generators/pipeline/output-stage.js.map +1 -0
- package/scripts/generators/pipeline/transform-stage.d.ts +57 -0
- package/scripts/generators/pipeline/transform-stage.d.ts.map +1 -0
- package/scripts/generators/pipeline/transform-stage.js +111 -0
- package/scripts/generators/pipeline/transform-stage.js.map +1 -0
- package/scripts/generators/propositions-content.d.ts +29 -0
- package/scripts/generators/propositions-content.d.ts.map +1 -0
- package/scripts/generators/propositions-content.js +90 -0
- package/scripts/generators/propositions-content.js.map +1 -0
- package/scripts/generators/sankey-content.d.ts +45 -0
- package/scripts/generators/sankey-content.d.ts.map +1 -0
- package/scripts/generators/sankey-content.js +227 -0
- package/scripts/generators/sankey-content.js.map +1 -0
- package/scripts/generators/sitemap.d.ts +66 -0
- package/scripts/generators/sitemap.d.ts.map +1 -0
- package/scripts/generators/sitemap.js +562 -0
- package/scripts/generators/sitemap.js.map +1 -0
- package/scripts/generators/strategies/article-strategy.d.ts +146 -0
- package/scripts/generators/strategies/article-strategy.d.ts.map +1 -0
- package/scripts/generators/strategies/article-strategy.js +4 -0
- package/scripts/generators/strategies/article-strategy.js.map +1 -0
- package/scripts/generators/strategies/breaking-news-strategy.d.ts +64 -0
- package/scripts/generators/strategies/breaking-news-strategy.d.ts.map +1 -0
- package/scripts/generators/strategies/breaking-news-strategy.js +246 -0
- package/scripts/generators/strategies/breaking-news-strategy.js.map +1 -0
- package/scripts/generators/strategies/committee-reports-strategy.d.ts +93 -0
- package/scripts/generators/strategies/committee-reports-strategy.d.ts.map +1 -0
- package/scripts/generators/strategies/committee-reports-strategy.js +447 -0
- package/scripts/generators/strategies/committee-reports-strategy.js.map +1 -0
- package/scripts/generators/strategies/month-ahead-strategy.d.ts +60 -0
- package/scripts/generators/strategies/month-ahead-strategy.d.ts.map +1 -0
- package/scripts/generators/strategies/month-ahead-strategy.js +175 -0
- package/scripts/generators/strategies/month-ahead-strategy.js.map +1 -0
- package/scripts/generators/strategies/monthly-review-strategy.d.ts +66 -0
- package/scripts/generators/strategies/monthly-review-strategy.d.ts.map +1 -0
- package/scripts/generators/strategies/monthly-review-strategy.js +204 -0
- package/scripts/generators/strategies/monthly-review-strategy.js.map +1 -0
- package/scripts/generators/strategies/motions-strategy.d.ts +61 -0
- package/scripts/generators/strategies/motions-strategy.d.ts.map +1 -0
- package/scripts/generators/strategies/motions-strategy.js +215 -0
- package/scripts/generators/strategies/motions-strategy.js.map +1 -0
- package/scripts/generators/strategies/propositions-strategy.d.ts +60 -0
- package/scripts/generators/strategies/propositions-strategy.d.ts.map +1 -0
- package/scripts/generators/strategies/propositions-strategy.js +257 -0
- package/scripts/generators/strategies/propositions-strategy.js.map +1 -0
- package/scripts/generators/strategies/week-ahead-strategy.d.ts +57 -0
- package/scripts/generators/strategies/week-ahead-strategy.d.ts.map +1 -0
- package/scripts/generators/strategies/week-ahead-strategy.js +178 -0
- package/scripts/generators/strategies/week-ahead-strategy.js.map +1 -0
- package/scripts/generators/strategies/weekly-review-strategy.d.ts +63 -0
- package/scripts/generators/strategies/weekly-review-strategy.d.ts.map +1 -0
- package/scripts/generators/strategies/weekly-review-strategy.js +211 -0
- package/scripts/generators/strategies/weekly-review-strategy.js.map +1 -0
- package/scripts/generators/swot-content.d.ts +42 -0
- package/scripts/generators/swot-content.d.ts.map +1 -0
- package/scripts/generators/swot-content.js +366 -0
- package/scripts/generators/swot-content.js.map +1 -0
- package/scripts/generators/week-ahead-content.d.ts +103 -0
- package/scripts/generators/week-ahead-content.d.ts.map +1 -0
- package/scripts/generators/week-ahead-content.js +610 -0
- package/scripts/generators/week-ahead-content.js.map +1 -0
- package/scripts/index.d.ts +40 -0
- package/scripts/index.d.ts.map +1 -0
- package/scripts/index.js +53 -0
- package/scripts/index.js.map +1 -0
- package/scripts/mcp/ep-mcp-client.d.ts +471 -0
- package/scripts/mcp/ep-mcp-client.d.ts.map +1 -0
- package/scripts/mcp/ep-mcp-client.js +734 -0
- package/scripts/mcp/ep-mcp-client.js.map +1 -0
- package/scripts/mcp/mcp-connection.d.ts +264 -0
- package/scripts/mcp/mcp-connection.d.ts.map +1 -0
- package/scripts/mcp/mcp-connection.js +790 -0
- package/scripts/mcp/mcp-connection.js.map +1 -0
- package/scripts/mcp/mcp-health.d.ts +75 -0
- package/scripts/mcp/mcp-health.d.ts.map +1 -0
- package/scripts/mcp/mcp-health.js +78 -0
- package/scripts/mcp/mcp-health.js.map +1 -0
- package/scripts/mcp/mcp-retry.d.ts +94 -0
- package/scripts/mcp/mcp-retry.d.ts.map +1 -0
- package/scripts/mcp/mcp-retry.js +127 -0
- package/scripts/mcp/mcp-retry.js.map +1 -0
- package/scripts/mcp/wb-mcp-client.d.ts +38 -0
- package/scripts/mcp/wb-mcp-client.d.ts.map +1 -0
- package/scripts/mcp/wb-mcp-client.js +112 -0
- package/scripts/mcp/wb-mcp-client.js.map +1 -0
- package/scripts/templates/article-template.d.ts +9 -0
- package/scripts/templates/article-template.d.ts.map +1 -0
- package/scripts/templates/article-template.js +378 -0
- package/scripts/templates/article-template.js.map +1 -0
- package/scripts/templates/section-builders.d.ts +28 -0
- package/scripts/templates/section-builders.d.ts.map +1 -0
- package/scripts/templates/section-builders.js +142 -0
- package/scripts/templates/section-builders.js.map +1 -0
- package/scripts/types/analysis.d.ts +115 -0
- package/scripts/types/analysis.d.ts.map +1 -0
- package/scripts/types/analysis.js +4 -0
- package/scripts/types/analysis.js.map +1 -0
- package/scripts/types/common.d.ts +584 -0
- package/scripts/types/common.d.ts.map +1 -0
- package/scripts/types/common.js +96 -0
- package/scripts/types/common.js.map +1 -0
- package/scripts/types/generation.d.ts +104 -0
- package/scripts/types/generation.d.ts.map +1 -0
- package/scripts/types/generation.js +4 -0
- package/scripts/types/generation.js.map +1 -0
- package/scripts/types/index.d.ts +24 -0
- package/scripts/types/index.d.ts.map +1 -0
- package/scripts/types/index.js +16 -0
- package/scripts/types/index.js.map +1 -0
- package/scripts/types/intelligence.d.ts +129 -0
- package/scripts/types/intelligence.d.ts.map +1 -0
- package/scripts/types/intelligence.js +4 -0
- package/scripts/types/intelligence.js.map +1 -0
- package/scripts/types/mcp.d.ts +418 -0
- package/scripts/types/mcp.d.ts.map +1 -0
- package/scripts/types/mcp.js +4 -0
- package/scripts/types/mcp.js.map +1 -0
- package/scripts/types/parliament.d.ts +388 -0
- package/scripts/types/parliament.d.ts.map +1 -0
- package/scripts/types/parliament.js +4 -0
- package/scripts/types/parliament.js.map +1 -0
- package/scripts/types/quality.d.ts +114 -0
- package/scripts/types/quality.d.ts.map +1 -0
- package/scripts/types/quality.js +4 -0
- package/scripts/types/quality.js.map +1 -0
- package/scripts/types/stakeholder.d.ts +88 -0
- package/scripts/types/stakeholder.d.ts.map +1 -0
- package/scripts/types/stakeholder.js +16 -0
- package/scripts/types/stakeholder.js.map +1 -0
- package/scripts/types/visualization.d.ts +708 -0
- package/scripts/types/visualization.d.ts.map +1 -0
- package/scripts/types/visualization.js +4 -0
- package/scripts/types/visualization.js.map +1 -0
- package/scripts/types/world-bank.d.ts +85 -0
- package/scripts/types/world-bank.d.ts.map +1 -0
- package/scripts/types/world-bank.js +4 -0
- package/scripts/types/world-bank.js.map +1 -0
- package/scripts/utils/article-category.d.ts +18 -0
- package/scripts/utils/article-category.d.ts.map +1 -0
- package/scripts/utils/article-category.js +49 -0
- package/scripts/utils/article-category.js.map +1 -0
- package/scripts/utils/article-quality-scorer.d.ts +87 -0
- package/scripts/utils/article-quality-scorer.d.ts.map +1 -0
- package/scripts/utils/article-quality-scorer.js +1048 -0
- package/scripts/utils/article-quality-scorer.js.map +1 -0
- package/scripts/utils/content-metadata.d.ts +34 -0
- package/scripts/utils/content-metadata.d.ts.map +1 -0
- package/scripts/utils/content-metadata.js +249 -0
- package/scripts/utils/content-metadata.js.map +1 -0
- package/scripts/utils/content-validator.d.ts +94 -0
- package/scripts/utils/content-validator.d.ts.map +1 -0
- package/scripts/utils/content-validator.js +489 -0
- package/scripts/utils/content-validator.js.map +1 -0
- package/scripts/utils/copy-test-reports.d.ts +9 -0
- package/scripts/utils/copy-test-reports.d.ts.map +1 -0
- package/scripts/utils/copy-test-reports.js +508 -0
- package/scripts/utils/copy-test-reports.js.map +1 -0
- package/scripts/utils/file-utils.d.ts +144 -0
- package/scripts/utils/file-utils.d.ts.map +1 -0
- package/scripts/utils/file-utils.js +374 -0
- package/scripts/utils/file-utils.js.map +1 -0
- package/scripts/utils/fix-articles.d.ts +27 -0
- package/scripts/utils/fix-articles.d.ts.map +1 -0
- package/scripts/utils/fix-articles.js +510 -0
- package/scripts/utils/fix-articles.js.map +1 -0
- package/scripts/utils/generate-docs-index.d.ts +8 -0
- package/scripts/utils/generate-docs-index.d.ts.map +1 -0
- package/scripts/utils/generate-docs-index.js +275 -0
- package/scripts/utils/generate-docs-index.js.map +1 -0
- package/scripts/utils/html-sanitize.d.ts +18 -0
- package/scripts/utils/html-sanitize.d.ts.map +1 -0
- package/scripts/utils/html-sanitize.js +57 -0
- package/scripts/utils/html-sanitize.js.map +1 -0
- package/scripts/utils/intelligence-analysis.d.ts +173 -0
- package/scripts/utils/intelligence-analysis.d.ts.map +1 -0
- package/scripts/utils/intelligence-analysis.js +936 -0
- package/scripts/utils/intelligence-analysis.js.map +1 -0
- package/scripts/utils/intelligence-index.d.ts +126 -0
- package/scripts/utils/intelligence-index.d.ts.map +1 -0
- package/scripts/utils/intelligence-index.js +731 -0
- package/scripts/utils/intelligence-index.js.map +1 -0
- package/scripts/utils/metadata-utils.d.ts +14 -0
- package/scripts/utils/metadata-utils.d.ts.map +1 -0
- package/scripts/utils/metadata-utils.js +18 -0
- package/scripts/utils/metadata-utils.js.map +1 -0
- package/scripts/utils/news-metadata.d.ts +47 -0
- package/scripts/utils/news-metadata.d.ts.map +1 -0
- package/scripts/utils/news-metadata.js +259 -0
- package/scripts/utils/news-metadata.js.map +1 -0
- package/scripts/utils/validate-articles.d.ts +2 -0
- package/scripts/utils/validate-articles.d.ts.map +1 -0
- package/scripts/utils/validate-articles.js +284 -0
- package/scripts/utils/validate-articles.js.map +1 -0
- package/scripts/utils/validate-ep-api.d.ts +51 -0
- package/scripts/utils/validate-ep-api.d.ts.map +1 -0
- package/scripts/utils/validate-ep-api.js +160 -0
- package/scripts/utils/validate-ep-api.js.map +1 -0
- package/scripts/utils/world-bank-data.d.ts +84 -0
- package/scripts/utils/world-bank-data.d.ts.map +1 -0
- package/scripts/utils/world-bank-data.js +311 -0
- package/scripts/utils/world-bank-data.js.map +1 -0
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* @module Generators/DeepAnalysisContent
|
|
5
|
+
* @description Pure functions for building the deep political analysis HTML
|
|
6
|
+
* section using the "5W + Impact" framework. This section is injected into
|
|
7
|
+
* every article type to provide parliament-intelligence-grade analysis.
|
|
8
|
+
*
|
|
9
|
+
* The framework covers:
|
|
10
|
+
* - **What**: Core subject
|
|
11
|
+
* - **Who**: Key actors (political groups, rapporteurs, MEPs, institutions)
|
|
12
|
+
* - **When**: Timeline and key dates
|
|
13
|
+
* - **Why**: Root causes and strategic motivations
|
|
14
|
+
* - **Winners/Losers**: Stakeholder impact assessment
|
|
15
|
+
* - **Impact**: Multi-perspective consequences (political, economic, social, legal, geopolitical)
|
|
16
|
+
* - **Actions → Consequences**: Causal chains from decisions to outcomes
|
|
17
|
+
* - **Mistakes**: Miscalculations and missed opportunities
|
|
18
|
+
* - **Outlook**: Strategic forward look
|
|
19
|
+
*/
|
|
20
|
+
import { escapeHTML, isSafeURL } from '../utils/file-utils.js';
|
|
21
|
+
import { getLocalizedString, DEEP_ANALYSIS_STRINGS } from '../constants/languages.js';
|
|
22
|
+
import { ALL_STAKEHOLDER_TYPES } from '../types/index.js';
|
|
23
|
+
// ─── Sub-section builders ────────────────────────────────────────────────────
|
|
24
|
+
/**
|
|
25
|
+
* Build the "What" sub-section
|
|
26
|
+
*
|
|
27
|
+
* @param what - Description of what happened
|
|
28
|
+
* @param heading - Localized heading
|
|
29
|
+
* @param contentLang - Language of the content text (omit when same as display language)
|
|
30
|
+
* @returns HTML string
|
|
31
|
+
*/
|
|
32
|
+
function buildWhatSection(what, heading, contentLang) {
|
|
33
|
+
if (!what)
|
|
34
|
+
return '';
|
|
35
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
36
|
+
return `
|
|
37
|
+
<div class="analysis-what">
|
|
38
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
39
|
+
<p${langAttr}>${escapeHTML(what)}</p>
|
|
40
|
+
</div>`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build the "Who" sub-section with key actors list
|
|
44
|
+
*
|
|
45
|
+
* @param who - Array of actor names/descriptions
|
|
46
|
+
* @param heading - Localized heading
|
|
47
|
+
* @param contentLang - Language of the content text (omit when same as display language)
|
|
48
|
+
* @returns HTML string
|
|
49
|
+
*/
|
|
50
|
+
function buildWhoSection(who, heading, contentLang) {
|
|
51
|
+
if (who.length === 0)
|
|
52
|
+
return '';
|
|
53
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
54
|
+
const items = who.map((actor) => `<li>${escapeHTML(actor)}</li>`).join('\n ');
|
|
55
|
+
return `
|
|
56
|
+
<div class="analysis-who">
|
|
57
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
58
|
+
<ul class="actor-list"${langAttr}>
|
|
59
|
+
${items}
|
|
60
|
+
</ul>
|
|
61
|
+
</div>`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build the "When" sub-section with timeline
|
|
65
|
+
*
|
|
66
|
+
* @param when - Array of date/milestone descriptions
|
|
67
|
+
* @param heading - Localized heading
|
|
68
|
+
* @param contentLang - Language of the content text (omit when same as display language)
|
|
69
|
+
* @returns HTML string
|
|
70
|
+
*/
|
|
71
|
+
function buildWhenSection(when, heading, contentLang) {
|
|
72
|
+
if (when.length === 0)
|
|
73
|
+
return '';
|
|
74
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
75
|
+
const items = when
|
|
76
|
+
.map((milestone) => `<li class="timeline-item">${escapeHTML(milestone)}</li>`)
|
|
77
|
+
.join('\n ');
|
|
78
|
+
return `
|
|
79
|
+
<div class="analysis-when">
|
|
80
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
81
|
+
<ol class="timeline-list"${langAttr}>
|
|
82
|
+
${items}
|
|
83
|
+
</ol>
|
|
84
|
+
</div>`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build the "Why" sub-section
|
|
88
|
+
*
|
|
89
|
+
* @param why - Root cause analysis text
|
|
90
|
+
* @param heading - Localized heading
|
|
91
|
+
* @param contentLang - Language of the content text (omit when same as display language)
|
|
92
|
+
* @returns HTML string
|
|
93
|
+
*/
|
|
94
|
+
function buildWhySection(why, heading, contentLang) {
|
|
95
|
+
if (!why)
|
|
96
|
+
return '';
|
|
97
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
98
|
+
return `
|
|
99
|
+
<div class="analysis-why">
|
|
100
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
101
|
+
<p${langAttr}>${escapeHTML(why)}</p>
|
|
102
|
+
</div>`;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Map stakeholder outcome to CSS class
|
|
106
|
+
*
|
|
107
|
+
* @param outcome - Winner/loser/neutral
|
|
108
|
+
* @returns CSS class name
|
|
109
|
+
*/
|
|
110
|
+
function outcomeClass(outcome) {
|
|
111
|
+
return `stakeholder-${outcome}`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get localized label for stakeholder outcome
|
|
115
|
+
*
|
|
116
|
+
* @param outcome - Winner/loser/neutral
|
|
117
|
+
* @param strings - Localized strings
|
|
118
|
+
* @param strings.winnerLabel - Label for winning stakeholders
|
|
119
|
+
* @param strings.loserLabel - Label for losing stakeholders
|
|
120
|
+
* @param strings.neutralLabel - Label for neutral stakeholders
|
|
121
|
+
* @returns Localized label
|
|
122
|
+
*/
|
|
123
|
+
function outcomeLabel(outcome, strings) {
|
|
124
|
+
switch (outcome) {
|
|
125
|
+
case 'winner':
|
|
126
|
+
return strings.winnerLabel;
|
|
127
|
+
case 'loser':
|
|
128
|
+
return strings.loserLabel;
|
|
129
|
+
default:
|
|
130
|
+
return strings.neutralLabel;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Build the "Winners & Losers" sub-section
|
|
135
|
+
*
|
|
136
|
+
* @param outcomes - Stakeholder assessments
|
|
137
|
+
* @param heading - Localized heading
|
|
138
|
+
* @param strings - Localized label strings
|
|
139
|
+
* @param strings.winnerLabel - Label for winning stakeholders
|
|
140
|
+
* @param strings.loserLabel - Label for losing stakeholders
|
|
141
|
+
* @param strings.neutralLabel - Label for neutral stakeholders
|
|
142
|
+
* @param contentLang - Language of the actor/reason text (omit when same as display language)
|
|
143
|
+
* @returns HTML string
|
|
144
|
+
*/
|
|
145
|
+
function buildStakeholderSection(outcomes, heading, strings, contentLang) {
|
|
146
|
+
if (outcomes.length === 0)
|
|
147
|
+
return '';
|
|
148
|
+
const contentLangAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
149
|
+
const items = outcomes
|
|
150
|
+
.map((s) => `<li class="stakeholder-item ${outcomeClass(s.outcome)}">` +
|
|
151
|
+
`<span class="stakeholder-badge">${escapeHTML(outcomeLabel(s.outcome, strings))}</span> ` +
|
|
152
|
+
`<span${contentLangAttr}><strong>${escapeHTML(s.actor)}</strong>: ${escapeHTML(s.reason)}</span>` +
|
|
153
|
+
`</li>`)
|
|
154
|
+
.join('\n ');
|
|
155
|
+
return `
|
|
156
|
+
<div class="analysis-stakeholders">
|
|
157
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
158
|
+
<ul class="stakeholder-list">
|
|
159
|
+
${items}
|
|
160
|
+
</ul>
|
|
161
|
+
</div>`;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Build the multi-perspective "Impact Assessment" sub-section
|
|
165
|
+
*
|
|
166
|
+
* @param impact - Impact strings per perspective
|
|
167
|
+
* @param heading - Localized heading
|
|
168
|
+
* @param labels - Localized perspective labels
|
|
169
|
+
* @param labels.politicalLabel - Label for political perspective
|
|
170
|
+
* @param labels.economicLabel - Label for economic perspective
|
|
171
|
+
* @param labels.socialLabel - Label for social perspective
|
|
172
|
+
* @param labels.legalLabel - Label for legal perspective
|
|
173
|
+
* @param labels.geopoliticalLabel - Label for geopolitical perspective
|
|
174
|
+
* @param contentLang - Language of the content text (omit when same as display language)
|
|
175
|
+
* @returns HTML string
|
|
176
|
+
*/
|
|
177
|
+
function buildImpactSection(impact, heading, labels, contentLang) {
|
|
178
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
179
|
+
const perspectives = [
|
|
180
|
+
{ label: labels.politicalLabel, text: impact.political, css: 'impact-political' },
|
|
181
|
+
{ label: labels.economicLabel, text: impact.economic, css: 'impact-economic' },
|
|
182
|
+
{ label: labels.socialLabel, text: impact.social, css: 'impact-social' },
|
|
183
|
+
{ label: labels.legalLabel, text: impact.legal, css: 'impact-legal' },
|
|
184
|
+
{ label: labels.geopoliticalLabel, text: impact.geopolitical, css: 'impact-geopolitical' },
|
|
185
|
+
].filter((p) => p.text);
|
|
186
|
+
if (perspectives.length === 0)
|
|
187
|
+
return '';
|
|
188
|
+
const items = perspectives
|
|
189
|
+
.map((p) => `<div class="impact-card ${p.css}">` +
|
|
190
|
+
`<h4>${escapeHTML(p.label)}</h4>` +
|
|
191
|
+
`<p${langAttr}>${escapeHTML(p.text)}</p>` +
|
|
192
|
+
`</div>`)
|
|
193
|
+
.join('\n ');
|
|
194
|
+
return `
|
|
195
|
+
<div class="analysis-impact">
|
|
196
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
197
|
+
<div class="impact-grid">
|
|
198
|
+
${items}
|
|
199
|
+
</div>
|
|
200
|
+
</div>`;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get localized severity label
|
|
204
|
+
*
|
|
205
|
+
* @param severity - Severity level
|
|
206
|
+
* @param strings - Localized strings
|
|
207
|
+
* @param strings.severityLow - Label for low severity
|
|
208
|
+
* @param strings.severityMedium - Label for medium severity
|
|
209
|
+
* @param strings.severityHigh - Label for high severity
|
|
210
|
+
* @param strings.severityCritical - Label for critical severity
|
|
211
|
+
* @returns Localized label
|
|
212
|
+
*/
|
|
213
|
+
function severityLabel(severity, strings) {
|
|
214
|
+
switch (severity) {
|
|
215
|
+
case 'low':
|
|
216
|
+
return strings.severityLow;
|
|
217
|
+
case 'medium':
|
|
218
|
+
return strings.severityMedium;
|
|
219
|
+
case 'high':
|
|
220
|
+
return strings.severityHigh;
|
|
221
|
+
case 'critical':
|
|
222
|
+
return strings.severityCritical;
|
|
223
|
+
default:
|
|
224
|
+
return String(severity);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Build the "Actions → Consequences" sub-section
|
|
229
|
+
*
|
|
230
|
+
* @param items - Action-consequence pairs
|
|
231
|
+
* @param heading - Localized heading
|
|
232
|
+
* @param labels - Localized column labels
|
|
233
|
+
* @param labels.actionLabel - Column header for action
|
|
234
|
+
* @param labels.consequenceLabel - Column header for consequence
|
|
235
|
+
* @param labels.severityColumnLabel - Column header for severity
|
|
236
|
+
* @param strings - Localized severity strings
|
|
237
|
+
* @param strings.severityLow - Label for low severity
|
|
238
|
+
* @param strings.severityMedium - Label for medium severity
|
|
239
|
+
* @param strings.severityHigh - Label for high severity
|
|
240
|
+
* @param strings.severityCritical - Label for critical severity
|
|
241
|
+
* @param contentLang - Language of the content text (omit when same as display language)
|
|
242
|
+
* @returns HTML string
|
|
243
|
+
*/
|
|
244
|
+
function buildConsequencesSection(items, heading, labels, strings, contentLang) {
|
|
245
|
+
if (items.length === 0)
|
|
246
|
+
return '';
|
|
247
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
248
|
+
const rows = items
|
|
249
|
+
.map((item) => `<tr class="consequence-row severity-${escapeHTML(item.severity)}">` +
|
|
250
|
+
`<td class="action-cell"${langAttr}>${escapeHTML(item.action)}</td>` +
|
|
251
|
+
`<td class="arrow-cell">→</td>` +
|
|
252
|
+
`<td class="consequence-cell"${langAttr}>${escapeHTML(item.consequence)}</td>` +
|
|
253
|
+
`<td class="severity-cell"><span class="severity-badge severity-${escapeHTML(item.severity)}">${escapeHTML(severityLabel(item.severity, strings))}</span></td>` +
|
|
254
|
+
`</tr>`)
|
|
255
|
+
.join('\n ');
|
|
256
|
+
return `
|
|
257
|
+
<div class="analysis-consequences">
|
|
258
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
259
|
+
<table class="consequences-table" role="table">
|
|
260
|
+
<thead>
|
|
261
|
+
<tr>
|
|
262
|
+
<th scope="col">${escapeHTML(labels.actionLabel)}</th>
|
|
263
|
+
<th scope="col" aria-hidden="true"></th>
|
|
264
|
+
<th scope="col">${escapeHTML(labels.consequenceLabel)}</th>
|
|
265
|
+
<th scope="col">${escapeHTML(labels.severityColumnLabel)}</th>
|
|
266
|
+
</tr>
|
|
267
|
+
</thead>
|
|
268
|
+
<tbody>
|
|
269
|
+
${rows}
|
|
270
|
+
</tbody>
|
|
271
|
+
</table>
|
|
272
|
+
</div>`;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Build the "Mistakes" sub-section
|
|
276
|
+
*
|
|
277
|
+
* @param mistakes - Political mistake assessments
|
|
278
|
+
* @param heading - Localized heading
|
|
279
|
+
* @param alternativeLabel - Localized "should have" label
|
|
280
|
+
* @param contentLang - Language of the description/alternative text (omit when same as display language)
|
|
281
|
+
* @returns HTML string
|
|
282
|
+
*/
|
|
283
|
+
function buildMistakesSection(mistakes, heading, alternativeLabel, contentLang) {
|
|
284
|
+
if (mistakes.length === 0)
|
|
285
|
+
return '';
|
|
286
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
287
|
+
const items = mistakes
|
|
288
|
+
.map((m) => `<div class="mistake-card">` +
|
|
289
|
+
`<p class="mistake-actor"><strong>${escapeHTML(m.actor)}</strong></p>` +
|
|
290
|
+
`<p class="mistake-description"${langAttr}>${escapeHTML(m.description)}</p>` +
|
|
291
|
+
`<p class="mistake-alternative"><em>${escapeHTML(alternativeLabel)}:</em> <span${langAttr}>${escapeHTML(m.alternative)}</span></p>` +
|
|
292
|
+
`</div>`)
|
|
293
|
+
.join('\n ');
|
|
294
|
+
return `
|
|
295
|
+
<div class="analysis-mistakes">
|
|
296
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
297
|
+
${items}
|
|
298
|
+
</div>`;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Build the "Strategic Outlook" sub-section
|
|
302
|
+
*
|
|
303
|
+
* @param outlook - Forward-looking analysis text
|
|
304
|
+
* @param heading - Localized heading
|
|
305
|
+
* @param contentLang - Language of the content text (omit when same as display language)
|
|
306
|
+
* @returns HTML string
|
|
307
|
+
*/
|
|
308
|
+
function buildOutlookSection(outlook, heading, contentLang) {
|
|
309
|
+
if (!outlook)
|
|
310
|
+
return '';
|
|
311
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
312
|
+
return `
|
|
313
|
+
<div class="analysis-outlook">
|
|
314
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
315
|
+
<p${langAttr}>${escapeHTML(outlook)}</p>
|
|
316
|
+
</div>`;
|
|
317
|
+
}
|
|
318
|
+
// ─── Enhanced analysis section builders ──────────────────────────────────────
|
|
319
|
+
/**
|
|
320
|
+
* Type guard — checks whether an analysis object carries enhanced fields
|
|
321
|
+
*
|
|
322
|
+
* @param a - Base deep analysis to test
|
|
323
|
+
* @returns `true` when the object is an `EnhancedDeepAnalysis`
|
|
324
|
+
*/
|
|
325
|
+
function isEnhancedDeepAnalysis(a) {
|
|
326
|
+
if (typeof a !== 'object' || a === null)
|
|
327
|
+
return false;
|
|
328
|
+
return ('qualityMetadata' in a ||
|
|
329
|
+
'scenarioPlanning' in a ||
|
|
330
|
+
'reasoningChains' in a ||
|
|
331
|
+
'executiveSummary' in a);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Build a confidence badge with emoji indicator and text label
|
|
335
|
+
*
|
|
336
|
+
* @param confidence - Confidence level
|
|
337
|
+
* @param strings - Localized strings
|
|
338
|
+
* @returns HTML span element
|
|
339
|
+
*/
|
|
340
|
+
function buildConfidenceBadge(confidence, strings) {
|
|
341
|
+
let emoji;
|
|
342
|
+
let label;
|
|
343
|
+
switch (confidence) {
|
|
344
|
+
case 'high':
|
|
345
|
+
emoji = '🟢';
|
|
346
|
+
label = strings.confidenceHigh;
|
|
347
|
+
break;
|
|
348
|
+
case 'medium':
|
|
349
|
+
emoji = '🟡';
|
|
350
|
+
label = strings.confidenceMedium;
|
|
351
|
+
break;
|
|
352
|
+
default:
|
|
353
|
+
emoji = '🔴';
|
|
354
|
+
label = strings.confidenceLow;
|
|
355
|
+
}
|
|
356
|
+
return `<span class="confidence-badge confidence-${escapeHTML(confidence)}" aria-label="${escapeHTML(label)}">${emoji} ${escapeHTML(label)}</span>`;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Build the executive summary section
|
|
360
|
+
*
|
|
361
|
+
* @param summary - Executive summary text
|
|
362
|
+
* @param confidence - Optional overall confidence level
|
|
363
|
+
* @param heading - Localized heading
|
|
364
|
+
* @param strings - Localized strings
|
|
365
|
+
* @param contentLang - Language of the content text
|
|
366
|
+
* @returns HTML string
|
|
367
|
+
*/
|
|
368
|
+
function buildExecutiveSummarySection(summary, confidence, heading, strings, contentLang) {
|
|
369
|
+
if (!summary)
|
|
370
|
+
return '';
|
|
371
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
372
|
+
const badge = confidence ? buildConfidenceBadge(confidence, strings) : '';
|
|
373
|
+
return `
|
|
374
|
+
<div class="analysis-executive-summary">
|
|
375
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
376
|
+
<div class="summary-header">
|
|
377
|
+
<p${langAttr}>${escapeHTML(summary)}</p>${badge}
|
|
378
|
+
</div>
|
|
379
|
+
</div>`;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Build the reasoning chains section
|
|
383
|
+
*
|
|
384
|
+
* @param chains - Reasoning chain items
|
|
385
|
+
* @param heading - Localized heading
|
|
386
|
+
* @param strings - Localized strings
|
|
387
|
+
* @param contentLang - Language of the content text
|
|
388
|
+
* @returns HTML string
|
|
389
|
+
*/
|
|
390
|
+
function buildReasoningChainSection(chains, heading, strings, contentLang) {
|
|
391
|
+
if (chains.length === 0)
|
|
392
|
+
return '';
|
|
393
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
394
|
+
const cards = chains
|
|
395
|
+
.map((chain) => {
|
|
396
|
+
const evidenceItems = chain.evidence
|
|
397
|
+
.map((ref) => {
|
|
398
|
+
const dateText = ref.date ? ` (${escapeHTML(ref.date)})` : '';
|
|
399
|
+
if (ref.url && isSafeURL(ref.url)) {
|
|
400
|
+
return `<li${langAttr}><a href="${escapeHTML(ref.url)}" target="_blank" rel="noopener noreferrer">${escapeHTML(ref.title)}${dateText}</a></li>`;
|
|
401
|
+
}
|
|
402
|
+
return `<li${langAttr}>${escapeHTML(ref.title)}${dateText}</li>`;
|
|
403
|
+
})
|
|
404
|
+
.join('\n ');
|
|
405
|
+
const evidenceHtml = chain.evidence.length > 0
|
|
406
|
+
? `<div class="evidence-refs-block">
|
|
407
|
+
<h4>${escapeHTML(strings.evidenceRefsHeading)}</h4>
|
|
408
|
+
<ul class="evidence-refs">
|
|
409
|
+
${evidenceItems}
|
|
410
|
+
</ul>
|
|
411
|
+
</div>`
|
|
412
|
+
: '';
|
|
413
|
+
const counterItems = chain.counterArguments
|
|
414
|
+
.map((ca) => `<li${langAttr}>${escapeHTML(ca)}</li>`)
|
|
415
|
+
.join('\n ');
|
|
416
|
+
const counterHtml = chain.counterArguments.length > 0
|
|
417
|
+
? `<div class="counter-args-block">
|
|
418
|
+
<h4>${escapeHTML(strings.counterArgumentsHeading)}</h4>
|
|
419
|
+
<ul class="counter-arguments">
|
|
420
|
+
${counterItems}
|
|
421
|
+
</ul>
|
|
422
|
+
</div>`
|
|
423
|
+
: '';
|
|
424
|
+
return `<div class="reasoning-chain-card">
|
|
425
|
+
<p><strong>${escapeHTML(strings.premiseLabel)}</strong> <span${langAttr}>${escapeHTML(chain.premise)}</span></p>
|
|
426
|
+
${evidenceHtml}
|
|
427
|
+
<p><strong>${escapeHTML(strings.inferenceLabel)}</strong> <span${langAttr}>${escapeHTML(chain.inference)}</span></p>
|
|
428
|
+
${buildConfidenceBadge(chain.confidence, strings)}
|
|
429
|
+
${counterHtml}
|
|
430
|
+
<p class="chain-conclusion"><strong>${escapeHTML(strings.conclusionLabel)}</strong> <span${langAttr}>${escapeHTML(chain.conclusion)}</span></p>
|
|
431
|
+
</div>`;
|
|
432
|
+
})
|
|
433
|
+
.join('\n ');
|
|
434
|
+
return `
|
|
435
|
+
<div class="analysis-reasoning-chains">
|
|
436
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
437
|
+
${cards}
|
|
438
|
+
</div>`;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Build the scenario planning section
|
|
442
|
+
*
|
|
443
|
+
* @param scenarios - Scenario planning data
|
|
444
|
+
* @param heading - Localized heading
|
|
445
|
+
* @param strings - Localized strings
|
|
446
|
+
* @param contentLang - Language of the content text
|
|
447
|
+
* @returns HTML string
|
|
448
|
+
*/
|
|
449
|
+
function buildScenarioPlanningSection(scenarios, heading, strings, contentLang) {
|
|
450
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
451
|
+
function renderScenario(scenario, cssClass, label) {
|
|
452
|
+
const rawPct = Number.isFinite(scenario.probability) ? scenario.probability * 100 : 0;
|
|
453
|
+
const pct = Math.max(0, Math.min(100, Math.round(rawPct)));
|
|
454
|
+
const triggerItems = scenario.triggers
|
|
455
|
+
.map((t) => `<li${langAttr}>${escapeHTML(t)}</li>`)
|
|
456
|
+
.join('\n ');
|
|
457
|
+
const impactItems = scenario.implications
|
|
458
|
+
.map((imp) => `<li class="scenario-impact scenario-severity-${escapeHTML(imp.severity)}">` +
|
|
459
|
+
`<strong>${escapeHTML(imp.stakeholder)}</strong>: <span${langAttr}>${escapeHTML(imp.impact)}</span>` +
|
|
460
|
+
`</li>`)
|
|
461
|
+
.join('\n ');
|
|
462
|
+
return `<div class="scenario-card ${escapeHTML(cssClass)}">
|
|
463
|
+
<h4>${escapeHTML(label)}</h4>
|
|
464
|
+
<p${langAttr}>${escapeHTML(scenario.description)}</p>
|
|
465
|
+
<div class="scenario-probability">
|
|
466
|
+
<span>${escapeHTML(strings.probabilityLabel)}: ${pct}%</span>
|
|
467
|
+
<div class="probability-bar" style="width:${pct}%" role="progressbar" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100" aria-label="${escapeHTML(label)} ${pct}%"></div>
|
|
468
|
+
</div>
|
|
469
|
+
${scenario.triggers.length > 0
|
|
470
|
+
? `<details class="scenario-triggers">
|
|
471
|
+
<summary>${escapeHTML(strings.triggersLabel)}</summary>
|
|
472
|
+
<ul>${triggerItems}</ul>
|
|
473
|
+
</details>`
|
|
474
|
+
: ''}
|
|
475
|
+
${scenario.implications.length > 0
|
|
476
|
+
? `<details class="scenario-impacts">
|
|
477
|
+
<summary>${escapeHTML(strings.impliedImpactsLabel)}</summary>
|
|
478
|
+
<ul class="scenario-impact-list">${impactItems}</ul>
|
|
479
|
+
</details>`
|
|
480
|
+
: ''}
|
|
481
|
+
<p class="scenario-timeline"><strong>${escapeHTML(strings.timelineLabel)}:</strong> <span${langAttr}>${escapeHTML(scenario.timeline)}</span></p>
|
|
482
|
+
</div>`;
|
|
483
|
+
}
|
|
484
|
+
const wildcardItems = scenarios.wildcards
|
|
485
|
+
.map((w) => `<li${langAttr}>${escapeHTML(w)}</li>`)
|
|
486
|
+
.join('\n ');
|
|
487
|
+
const wildcardHtml = scenarios.wildcards.length > 0
|
|
488
|
+
? `<div class="scenario-wildcards">
|
|
489
|
+
<h4>${escapeHTML(strings.wildcardsLabel)}</h4>
|
|
490
|
+
<ul class="wildcard-list">${wildcardItems}</ul>
|
|
491
|
+
</div>`
|
|
492
|
+
: '';
|
|
493
|
+
return `
|
|
494
|
+
<div class="analysis-scenario-planning">
|
|
495
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
496
|
+
<div class="scenario-grid">
|
|
497
|
+
${renderScenario(scenarios.bestCase, 'scenario-best', strings.bestCaseLabel)}
|
|
498
|
+
${renderScenario(scenarios.mostLikely, 'scenario-likely', strings.mostLikelyLabel)}
|
|
499
|
+
${renderScenario(scenarios.worstCase, 'scenario-worst', strings.worstCaseLabel)}
|
|
500
|
+
</div>
|
|
501
|
+
${wildcardHtml}
|
|
502
|
+
</div>`;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Map iteration type to localized label
|
|
506
|
+
*
|
|
507
|
+
* @param type - Iteration type
|
|
508
|
+
* @param strings - Localized strings
|
|
509
|
+
* @returns Localized label
|
|
510
|
+
*/
|
|
511
|
+
function iterationTypeLabel(type, strings) {
|
|
512
|
+
switch (type) {
|
|
513
|
+
case 'initial':
|
|
514
|
+
return strings.iterationInitial;
|
|
515
|
+
case 'stakeholder_challenge':
|
|
516
|
+
return strings.iterationStakeholderChallenge;
|
|
517
|
+
case 'evidence_validation':
|
|
518
|
+
return strings.iterationEvidenceValidation;
|
|
519
|
+
default:
|
|
520
|
+
return strings.iterationSynthesis;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Map evidence strength to localized label
|
|
525
|
+
*
|
|
526
|
+
* @param strength - Evidence strength
|
|
527
|
+
* @param strings - Localized strings
|
|
528
|
+
* @returns Localized label
|
|
529
|
+
*/
|
|
530
|
+
function evidenceStrengthLabel(strength, strings) {
|
|
531
|
+
switch (strength) {
|
|
532
|
+
case 'strong':
|
|
533
|
+
return strings.evidenceStrong;
|
|
534
|
+
case 'moderate':
|
|
535
|
+
return strings.evidenceModerate;
|
|
536
|
+
default:
|
|
537
|
+
return strings.evidenceWeak;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Build the analysis methodology section
|
|
542
|
+
*
|
|
543
|
+
* @param metadata - Quality metadata
|
|
544
|
+
* @param heading - Localized heading
|
|
545
|
+
* @param strings - Localized strings
|
|
546
|
+
* @param contentLang - Language of the content text
|
|
547
|
+
* @returns HTML string
|
|
548
|
+
*/
|
|
549
|
+
function buildAnalysisMethodologySection(metadata, heading, strings, contentLang) {
|
|
550
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
551
|
+
const iterationItems = metadata.iterations
|
|
552
|
+
.map((iter) => {
|
|
553
|
+
const findingItems = iter.findings
|
|
554
|
+
.map((f) => `<li${langAttr}>${escapeHTML(f)}</li>`)
|
|
555
|
+
.join('\n ');
|
|
556
|
+
const refinementItems = iter.refinements
|
|
557
|
+
.map((r) => `<li${langAttr}>${escapeHTML(r)}</li>`)
|
|
558
|
+
.join('\n ');
|
|
559
|
+
return `<div class="iteration-item">
|
|
560
|
+
<div class="iteration-header">
|
|
561
|
+
<span class="iteration-pass">Pass ${escapeHTML(String(Number.isFinite(iter.pass) ? iter.pass : 0))}</span>
|
|
562
|
+
<span class="iteration-type">${escapeHTML(iterationTypeLabel(iter.type, strings))}</span>
|
|
563
|
+
${buildConfidenceBadge(iter.confidence, strings)}
|
|
564
|
+
</div>
|
|
565
|
+
${iter.findings.length > 0
|
|
566
|
+
? `<ul class="iteration-findings">${findingItems}</ul>`
|
|
567
|
+
: ''}
|
|
568
|
+
${iter.refinements.length > 0
|
|
569
|
+
? `<ul class="iteration-refinements">${refinementItems}</ul>`
|
|
570
|
+
: ''}
|
|
571
|
+
</div>`;
|
|
572
|
+
})
|
|
573
|
+
.join('\n ');
|
|
574
|
+
return `
|
|
575
|
+
<div class="analysis-methodology">
|
|
576
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
577
|
+
<dl class="methodology-stats">
|
|
578
|
+
<dt>${escapeHTML(strings.overallConfidenceLabel)}</dt>
|
|
579
|
+
<dd>${buildConfidenceBadge(metadata.overallConfidence, strings)}</dd>
|
|
580
|
+
<dt>${escapeHTML(strings.evidenceStrengthLabel)}</dt>
|
|
581
|
+
<dd>${escapeHTML(evidenceStrengthLabel(metadata.evidenceStrength, strings))}</dd>
|
|
582
|
+
<dt>${escapeHTML(strings.iterationCountLabel)}</dt>
|
|
583
|
+
<dd>${escapeHTML(String(Number.isFinite(metadata.iterationCount) ? metadata.iterationCount : 0))}</dd>
|
|
584
|
+
</dl>
|
|
585
|
+
${metadata.iterations.length > 0 ? `<div class="iteration-timeline">${iterationItems}</div>` : ''}
|
|
586
|
+
</div>`;
|
|
587
|
+
}
|
|
588
|
+
// ─── Main builder ────────────────────────────────────────────────────────────
|
|
589
|
+
/**
|
|
590
|
+
* Map a StakeholderPerspective impact to a CSS class suffix.
|
|
591
|
+
*
|
|
592
|
+
* @param impact - Stakeholder impact direction
|
|
593
|
+
* @returns CSS class suffix
|
|
594
|
+
*/
|
|
595
|
+
function perspectiveImpactClass(impact) {
|
|
596
|
+
return `perspective-${impact}`;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Map a stakeholder type to its localized display label.
|
|
600
|
+
*
|
|
601
|
+
* @param stakeholder - Internal stakeholder type identifier
|
|
602
|
+
* @param strings - Localized label strings
|
|
603
|
+
* @returns Localized stakeholder label
|
|
604
|
+
*/
|
|
605
|
+
function localizedStakeholderLabel(stakeholder, strings) {
|
|
606
|
+
const map = {
|
|
607
|
+
political_groups: strings.politicalGroupsLabel,
|
|
608
|
+
civil_society: strings.civilSocietyLabel,
|
|
609
|
+
industry: strings.industryLabel,
|
|
610
|
+
national_govts: strings.nationalGovtsLabel,
|
|
611
|
+
citizens: strings.citizensLabel,
|
|
612
|
+
eu_institutions: strings.euInstitutionsLabel,
|
|
613
|
+
};
|
|
614
|
+
return map[stakeholder];
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Map a stakeholder impact direction to its localized display label.
|
|
618
|
+
*
|
|
619
|
+
* @param impact - Impact direction value
|
|
620
|
+
* @param strings - Localized label strings
|
|
621
|
+
* @returns Localized impact label
|
|
622
|
+
*/
|
|
623
|
+
function localizedImpactLabel(impact, strings) {
|
|
624
|
+
const map = {
|
|
625
|
+
positive: strings.positiveLabel,
|
|
626
|
+
negative: strings.negativeLabel,
|
|
627
|
+
neutral: strings.neutralLabel,
|
|
628
|
+
mixed: strings.mixedLabel,
|
|
629
|
+
};
|
|
630
|
+
return map[impact];
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Map a severity level to its localized display label.
|
|
634
|
+
*
|
|
635
|
+
* @param severity - Severity level value
|
|
636
|
+
* @param strings - Localized label strings
|
|
637
|
+
* @returns Localized severity label
|
|
638
|
+
*/
|
|
639
|
+
function localizedSeverityLabel(severity, strings) {
|
|
640
|
+
const map = {
|
|
641
|
+
high: strings.severityHigh,
|
|
642
|
+
medium: strings.severityMedium,
|
|
643
|
+
low: strings.severityLow,
|
|
644
|
+
};
|
|
645
|
+
return map[severity];
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Map an outcome value to its localized display label.
|
|
649
|
+
*
|
|
650
|
+
* @param outcome - Outcome value (winner/loser/neutral)
|
|
651
|
+
* @param strings - Localized label strings
|
|
652
|
+
* @returns Localized outcome label
|
|
653
|
+
*/
|
|
654
|
+
function localizedOutcomeLabel(outcome, strings) {
|
|
655
|
+
const map = {
|
|
656
|
+
winner: strings.winnerLabel,
|
|
657
|
+
loser: strings.loserLabel,
|
|
658
|
+
neutral: strings.neutralLabel,
|
|
659
|
+
};
|
|
660
|
+
return map[outcome] ?? outcome;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Build the "Multi-Stakeholder Perspectives" sub-section.
|
|
664
|
+
* Renders a card grid with one card per stakeholder group showing
|
|
665
|
+
* impact direction, severity, reasoning, and evidence.
|
|
666
|
+
*
|
|
667
|
+
* @param perspectives - Array of stakeholder perspectives
|
|
668
|
+
* @param heading - Localized section heading
|
|
669
|
+
* @param strings - Localized label strings for stakeholder names, impact, and severity
|
|
670
|
+
* @param contentLang - Language of the reasoning/evidence text
|
|
671
|
+
* @returns HTML string, or empty string if no perspectives provided
|
|
672
|
+
*/
|
|
673
|
+
function buildStakeholderPerspectivesSection(perspectives, heading, strings, contentLang) {
|
|
674
|
+
if (!perspectives || perspectives.length === 0)
|
|
675
|
+
return '';
|
|
676
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
677
|
+
const cards = perspectives
|
|
678
|
+
.map((p) => {
|
|
679
|
+
const evidenceItems = p.evidence.map((e) => `<li>${escapeHTML(e)}</li>`).join('');
|
|
680
|
+
const evidenceHtml = evidenceItems
|
|
681
|
+
? `<ul class="perspective-evidence">${evidenceItems}</ul>`
|
|
682
|
+
: '';
|
|
683
|
+
return (`<div class="stakeholder-perspective-card ${escapeHTML(perspectiveImpactClass(p.impact))} severity-${escapeHTML(p.severity)}">` +
|
|
684
|
+
`<div class="perspective-header">` +
|
|
685
|
+
`<span class="perspective-stakeholder">${escapeHTML(localizedStakeholderLabel(p.stakeholder, strings))}</span>` +
|
|
686
|
+
`<span class="perspective-impact-badge perspective-impact-${escapeHTML(p.impact)}">${escapeHTML(localizedImpactLabel(p.impact, strings))}</span>` +
|
|
687
|
+
`<span class="perspective-severity-badge severity-${escapeHTML(p.severity)}">${escapeHTML(localizedSeverityLabel(p.severity, strings))}</span>` +
|
|
688
|
+
`</div>` +
|
|
689
|
+
`<p class="perspective-reasoning"${langAttr}>${escapeHTML(p.reasoning)}</p>` +
|
|
690
|
+
evidenceHtml +
|
|
691
|
+
`</div>`);
|
|
692
|
+
})
|
|
693
|
+
.join('\n ');
|
|
694
|
+
return `
|
|
695
|
+
<div class="analysis-stakeholder-perspectives">
|
|
696
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
697
|
+
<div class="stakeholder-perspectives-grid">
|
|
698
|
+
${cards}
|
|
699
|
+
</div>
|
|
700
|
+
</div>`;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Build the "Stakeholder Outcome Matrix" sub-section.
|
|
704
|
+
* Renders an accessible table mapping each action to winner/loser/neutral
|
|
705
|
+
* outcomes per stakeholder group.
|
|
706
|
+
*
|
|
707
|
+
* @param matrix - Array of stakeholder outcome matrix rows
|
|
708
|
+
* @param heading - Localized section heading
|
|
709
|
+
* @param strings - Localized label strings for columns and stakeholder groups
|
|
710
|
+
* @param contentLang - Language of the action text
|
|
711
|
+
* @returns HTML string, or empty string if no matrix rows provided
|
|
712
|
+
*/
|
|
713
|
+
function buildStakeholderOutcomeMatrixSection(matrix, heading, strings, contentLang) {
|
|
714
|
+
if (!matrix || matrix.length === 0)
|
|
715
|
+
return '';
|
|
716
|
+
const langAttr = contentLang ? ` lang="${escapeHTML(contentLang)}"` : '';
|
|
717
|
+
const headerCells = ALL_STAKEHOLDER_TYPES.map((s) => `<th scope="col">${escapeHTML(localizedStakeholderLabel(s, strings))}</th>`).join('');
|
|
718
|
+
const rows = matrix
|
|
719
|
+
.map((row) => {
|
|
720
|
+
const cells = ALL_STAKEHOLDER_TYPES.map((s) => {
|
|
721
|
+
// eslint-disable-next-line security/detect-object-injection -- key from const array
|
|
722
|
+
const outcome = row.outcomes[s];
|
|
723
|
+
return `<td class="matrix-cell outcome-${escapeHTML(outcome)}">${escapeHTML(localizedOutcomeLabel(outcome, strings))}</td>`;
|
|
724
|
+
}).join('');
|
|
725
|
+
return (`<tr>` +
|
|
726
|
+
`<th scope="row" class="matrix-action"${langAttr}>${escapeHTML(row.action)}</th>` +
|
|
727
|
+
`<td class="matrix-confidence confidence-${escapeHTML(row.confidence)}">${escapeHTML(localizedSeverityLabel(row.confidence, strings))}</td>` +
|
|
728
|
+
cells +
|
|
729
|
+
`</tr>`);
|
|
730
|
+
})
|
|
731
|
+
.join('\n ');
|
|
732
|
+
return `
|
|
733
|
+
<div class="analysis-outcome-matrix">
|
|
734
|
+
<h3>${escapeHTML(heading)}</h3>
|
|
735
|
+
<div class="outcome-matrix-scroll">
|
|
736
|
+
<table class="outcome-matrix-table" role="table">
|
|
737
|
+
<thead>
|
|
738
|
+
<tr>
|
|
739
|
+
<th scope="col">${escapeHTML(strings.actionLabel)}</th>
|
|
740
|
+
<th scope="col">${escapeHTML(strings.confidenceLabel)}</th>
|
|
741
|
+
${headerCells}
|
|
742
|
+
</tr>
|
|
743
|
+
</thead>
|
|
744
|
+
<tbody>
|
|
745
|
+
${rows}
|
|
746
|
+
</tbody>
|
|
747
|
+
</table>
|
|
748
|
+
</div>
|
|
749
|
+
</div>`;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Build the complete deep political analysis section HTML.
|
|
753
|
+
*
|
|
754
|
+
* This section provides parliament-intelligence-grade analysis using the
|
|
755
|
+
* "5W + Impact" framework. When the input is an `EnhancedDeepAnalysis` it
|
|
756
|
+
* additionally renders an executive summary, reasoning chains, scenario
|
|
757
|
+
* planning, and analysis methodology.
|
|
758
|
+
*
|
|
759
|
+
* Returns an empty string if the analysis object is null/undefined or
|
|
760
|
+
* contains no meaningful content.
|
|
761
|
+
*
|
|
762
|
+
* @param analysis - Deep analysis data (null/undefined returns empty string).
|
|
763
|
+
* Accepts both `DeepAnalysis` and `EnhancedDeepAnalysis`.
|
|
764
|
+
* @param lang - BCP 47 language code for localized headings
|
|
765
|
+
* @param contentLang - BCP 47 language code for the content text; when it
|
|
766
|
+
* differs from `lang`, each content element gets a `lang` attribute so
|
|
767
|
+
* screen readers and translation tools handle the language switch correctly.
|
|
768
|
+
* Defaults to `lang` (no extra attributes added).
|
|
769
|
+
* @returns HTML section string or empty string
|
|
770
|
+
*/
|
|
771
|
+
export function buildDeepAnalysisSection(analysis, lang, contentLang = lang) {
|
|
772
|
+
if (!analysis)
|
|
773
|
+
return '';
|
|
774
|
+
const strings = getLocalizedString(DEEP_ANALYSIS_STRINGS, lang);
|
|
775
|
+
const cl = contentLang !== lang ? contentLang : undefined;
|
|
776
|
+
// ─── Enhanced sections (before/after base sections) ────────────────────
|
|
777
|
+
let executiveSummaryHtml = '';
|
|
778
|
+
let reasoningChainsHtml = '';
|
|
779
|
+
let scenarioPlanningHtml = '';
|
|
780
|
+
let methodologyHtml = '';
|
|
781
|
+
if (isEnhancedDeepAnalysis(analysis)) {
|
|
782
|
+
if (analysis.executiveSummary) {
|
|
783
|
+
executiveSummaryHtml = buildExecutiveSummarySection(analysis.executiveSummary, analysis.qualityMetadata?.overallConfidence, strings.executiveSummaryHeading, strings, cl);
|
|
784
|
+
}
|
|
785
|
+
if (analysis.reasoningChains && analysis.reasoningChains.length > 0) {
|
|
786
|
+
reasoningChainsHtml = buildReasoningChainSection(analysis.reasoningChains, strings.reasoningChainsHeading, strings, cl);
|
|
787
|
+
}
|
|
788
|
+
if (analysis.scenarioPlanning) {
|
|
789
|
+
scenarioPlanningHtml = buildScenarioPlanningSection(analysis.scenarioPlanning, strings.scenarioPlanningHeading, strings, cl);
|
|
790
|
+
}
|
|
791
|
+
if (analysis.qualityMetadata) {
|
|
792
|
+
methodologyHtml = buildAnalysisMethodologySection(analysis.qualityMetadata, strings.analysisMethodologyHeading, strings, cl);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// ─── Base "5W + Impact" sections ───────────────────────────────────────
|
|
796
|
+
const whatHtml = buildWhatSection(analysis.what, strings.whatHeading, cl);
|
|
797
|
+
const whoHtml = buildWhoSection(analysis.who, strings.whoHeading, cl);
|
|
798
|
+
const whenHtml = buildWhenSection(analysis.when, strings.whenHeading, cl);
|
|
799
|
+
const whyHtml = buildWhySection(analysis.why, strings.whyHeading, cl);
|
|
800
|
+
const stakeholderHtml = buildStakeholderSection(analysis.stakeholderOutcomes, strings.stakeholderHeading, strings, cl);
|
|
801
|
+
const impactHtml = buildImpactSection(analysis.impactAssessment, strings.impactHeading, strings, cl);
|
|
802
|
+
const consequencesHtml = buildConsequencesSection(analysis.actionConsequences, strings.consequencesHeading, strings, strings, cl);
|
|
803
|
+
const mistakesHtml = buildMistakesSection(analysis.mistakes, strings.mistakesHeading, strings.alternativeLabel, cl);
|
|
804
|
+
const outlookHtml = buildOutlookSection(analysis.outlook, strings.outlookHeading, cl);
|
|
805
|
+
const perspectivesHtml = buildStakeholderPerspectivesSection(analysis.stakeholderPerspectives, strings.perspectivesHeading, strings, cl);
|
|
806
|
+
const outcomeMatrixHtml = buildStakeholderOutcomeMatrixSection(analysis.stakeholderOutcomeMatrix, strings.outcomeMatrixHeading, strings, cl);
|
|
807
|
+
const innerContent = executiveSummaryHtml +
|
|
808
|
+
whatHtml +
|
|
809
|
+
whoHtml +
|
|
810
|
+
whenHtml +
|
|
811
|
+
whyHtml +
|
|
812
|
+
reasoningChainsHtml +
|
|
813
|
+
stakeholderHtml +
|
|
814
|
+
impactHtml +
|
|
815
|
+
consequencesHtml +
|
|
816
|
+
mistakesHtml +
|
|
817
|
+
outlookHtml +
|
|
818
|
+
scenarioPlanningHtml +
|
|
819
|
+
perspectivesHtml +
|
|
820
|
+
outcomeMatrixHtml +
|
|
821
|
+
methodologyHtml;
|
|
822
|
+
// If all sub-sections are empty, return nothing
|
|
823
|
+
if (!innerContent.trim())
|
|
824
|
+
return '';
|
|
825
|
+
return `
|
|
826
|
+
<section class="deep-analysis" lang="${escapeHTML(lang)}">
|
|
827
|
+
<h2>${escapeHTML(strings.sectionHeading)}</h2>
|
|
828
|
+
${innerContent}
|
|
829
|
+
</section>`;
|
|
830
|
+
}
|
|
831
|
+
//# sourceMappingURL=deep-analysis-content.js.map
|