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.
Files changed (276) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1005 -0
  3. package/SECURITY.md +151 -0
  4. package/package.json +131 -0
  5. package/scripts/constants/committee-indicator-map.d.ts +199 -0
  6. package/scripts/constants/committee-indicator-map.d.ts.map +1 -0
  7. package/scripts/constants/committee-indicator-map.js +1224 -0
  8. package/scripts/constants/committee-indicator-map.js.map +1 -0
  9. package/scripts/constants/config.d.ts +38 -0
  10. package/scripts/constants/config.d.ts.map +1 -0
  11. package/scripts/constants/config.js +66 -0
  12. package/scripts/constants/config.js.map +1 -0
  13. package/scripts/constants/language-articles.d.ts +84 -0
  14. package/scripts/constants/language-articles.d.ts.map +1 -0
  15. package/scripts/constants/language-articles.js +6771 -0
  16. package/scripts/constants/language-articles.js.map +1 -0
  17. package/scripts/constants/language-core.d.ts +38 -0
  18. package/scripts/constants/language-core.d.ts.map +1 -0
  19. package/scripts/constants/language-core.js +90 -0
  20. package/scripts/constants/language-core.js.map +1 -0
  21. package/scripts/constants/language-ui.d.ts +82 -0
  22. package/scripts/constants/language-ui.d.ts.map +1 -0
  23. package/scripts/constants/language-ui.js +889 -0
  24. package/scripts/constants/language-ui.js.map +1 -0
  25. package/scripts/constants/languages.d.ts +14 -0
  26. package/scripts/constants/languages.d.ts.map +1 -0
  27. package/scripts/constants/languages.js +15 -0
  28. package/scripts/constants/languages.js.map +1 -0
  29. package/scripts/generators/analysis-builders.d.ts +266 -0
  30. package/scripts/generators/analysis-builders.d.ts.map +1 -0
  31. package/scripts/generators/analysis-builders.js +2903 -0
  32. package/scripts/generators/analysis-builders.js.map +1 -0
  33. package/scripts/generators/breaking-content.d.ts +45 -0
  34. package/scripts/generators/breaking-content.d.ts.map +1 -0
  35. package/scripts/generators/breaking-content.js +530 -0
  36. package/scripts/generators/breaking-content.js.map +1 -0
  37. package/scripts/generators/committee-helpers.d.ts +54 -0
  38. package/scripts/generators/committee-helpers.d.ts.map +1 -0
  39. package/scripts/generators/committee-helpers.js +154 -0
  40. package/scripts/generators/committee-helpers.js.map +1 -0
  41. package/scripts/generators/dashboard-content.d.ts +95 -0
  42. package/scripts/generators/dashboard-content.d.ts.map +1 -0
  43. package/scripts/generators/dashboard-content.js +630 -0
  44. package/scripts/generators/dashboard-content.js.map +1 -0
  45. package/scripts/generators/deep-analysis-content.d.ts +23 -0
  46. package/scripts/generators/deep-analysis-content.d.ts.map +1 -0
  47. package/scripts/generators/deep-analysis-content.js +831 -0
  48. package/scripts/generators/deep-analysis-content.js.map +1 -0
  49. package/scripts/generators/mindmap-content.d.ts +55 -0
  50. package/scripts/generators/mindmap-content.d.ts.map +1 -0
  51. package/scripts/generators/mindmap-content.js +512 -0
  52. package/scripts/generators/mindmap-content.js.map +1 -0
  53. package/scripts/generators/motions-content.d.ts +50 -0
  54. package/scripts/generators/motions-content.d.ts.map +1 -0
  55. package/scripts/generators/motions-content.js +391 -0
  56. package/scripts/generators/motions-content.js.map +1 -0
  57. package/scripts/generators/news-enhanced.d.ts +14 -0
  58. package/scripts/generators/news-enhanced.d.ts.map +1 -0
  59. package/scripts/generators/news-enhanced.js +169 -0
  60. package/scripts/generators/news-enhanced.js.map +1 -0
  61. package/scripts/generators/news-indexes.d.ts +31 -0
  62. package/scripts/generators/news-indexes.d.ts.map +1 -0
  63. package/scripts/generators/news-indexes.js +410 -0
  64. package/scripts/generators/news-indexes.js.map +1 -0
  65. package/scripts/generators/pipeline/fetch-stage.d.ts +352 -0
  66. package/scripts/generators/pipeline/fetch-stage.d.ts.map +1 -0
  67. package/scripts/generators/pipeline/fetch-stage.js +1522 -0
  68. package/scripts/generators/pipeline/fetch-stage.js.map +1 -0
  69. package/scripts/generators/pipeline/generate-stage.d.ts +43 -0
  70. package/scripts/generators/pipeline/generate-stage.d.ts.map +1 -0
  71. package/scripts/generators/pipeline/generate-stage.js +204 -0
  72. package/scripts/generators/pipeline/generate-stage.js.map +1 -0
  73. package/scripts/generators/pipeline/output-stage.d.ts +48 -0
  74. package/scripts/generators/pipeline/output-stage.d.ts.map +1 -0
  75. package/scripts/generators/pipeline/output-stage.js +145 -0
  76. package/scripts/generators/pipeline/output-stage.js.map +1 -0
  77. package/scripts/generators/pipeline/transform-stage.d.ts +57 -0
  78. package/scripts/generators/pipeline/transform-stage.d.ts.map +1 -0
  79. package/scripts/generators/pipeline/transform-stage.js +111 -0
  80. package/scripts/generators/pipeline/transform-stage.js.map +1 -0
  81. package/scripts/generators/propositions-content.d.ts +29 -0
  82. package/scripts/generators/propositions-content.d.ts.map +1 -0
  83. package/scripts/generators/propositions-content.js +90 -0
  84. package/scripts/generators/propositions-content.js.map +1 -0
  85. package/scripts/generators/sankey-content.d.ts +45 -0
  86. package/scripts/generators/sankey-content.d.ts.map +1 -0
  87. package/scripts/generators/sankey-content.js +227 -0
  88. package/scripts/generators/sankey-content.js.map +1 -0
  89. package/scripts/generators/sitemap.d.ts +66 -0
  90. package/scripts/generators/sitemap.d.ts.map +1 -0
  91. package/scripts/generators/sitemap.js +562 -0
  92. package/scripts/generators/sitemap.js.map +1 -0
  93. package/scripts/generators/strategies/article-strategy.d.ts +146 -0
  94. package/scripts/generators/strategies/article-strategy.d.ts.map +1 -0
  95. package/scripts/generators/strategies/article-strategy.js +4 -0
  96. package/scripts/generators/strategies/article-strategy.js.map +1 -0
  97. package/scripts/generators/strategies/breaking-news-strategy.d.ts +64 -0
  98. package/scripts/generators/strategies/breaking-news-strategy.d.ts.map +1 -0
  99. package/scripts/generators/strategies/breaking-news-strategy.js +246 -0
  100. package/scripts/generators/strategies/breaking-news-strategy.js.map +1 -0
  101. package/scripts/generators/strategies/committee-reports-strategy.d.ts +93 -0
  102. package/scripts/generators/strategies/committee-reports-strategy.d.ts.map +1 -0
  103. package/scripts/generators/strategies/committee-reports-strategy.js +447 -0
  104. package/scripts/generators/strategies/committee-reports-strategy.js.map +1 -0
  105. package/scripts/generators/strategies/month-ahead-strategy.d.ts +60 -0
  106. package/scripts/generators/strategies/month-ahead-strategy.d.ts.map +1 -0
  107. package/scripts/generators/strategies/month-ahead-strategy.js +175 -0
  108. package/scripts/generators/strategies/month-ahead-strategy.js.map +1 -0
  109. package/scripts/generators/strategies/monthly-review-strategy.d.ts +66 -0
  110. package/scripts/generators/strategies/monthly-review-strategy.d.ts.map +1 -0
  111. package/scripts/generators/strategies/monthly-review-strategy.js +204 -0
  112. package/scripts/generators/strategies/monthly-review-strategy.js.map +1 -0
  113. package/scripts/generators/strategies/motions-strategy.d.ts +61 -0
  114. package/scripts/generators/strategies/motions-strategy.d.ts.map +1 -0
  115. package/scripts/generators/strategies/motions-strategy.js +215 -0
  116. package/scripts/generators/strategies/motions-strategy.js.map +1 -0
  117. package/scripts/generators/strategies/propositions-strategy.d.ts +60 -0
  118. package/scripts/generators/strategies/propositions-strategy.d.ts.map +1 -0
  119. package/scripts/generators/strategies/propositions-strategy.js +257 -0
  120. package/scripts/generators/strategies/propositions-strategy.js.map +1 -0
  121. package/scripts/generators/strategies/week-ahead-strategy.d.ts +57 -0
  122. package/scripts/generators/strategies/week-ahead-strategy.d.ts.map +1 -0
  123. package/scripts/generators/strategies/week-ahead-strategy.js +178 -0
  124. package/scripts/generators/strategies/week-ahead-strategy.js.map +1 -0
  125. package/scripts/generators/strategies/weekly-review-strategy.d.ts +63 -0
  126. package/scripts/generators/strategies/weekly-review-strategy.d.ts.map +1 -0
  127. package/scripts/generators/strategies/weekly-review-strategy.js +211 -0
  128. package/scripts/generators/strategies/weekly-review-strategy.js.map +1 -0
  129. package/scripts/generators/swot-content.d.ts +42 -0
  130. package/scripts/generators/swot-content.d.ts.map +1 -0
  131. package/scripts/generators/swot-content.js +366 -0
  132. package/scripts/generators/swot-content.js.map +1 -0
  133. package/scripts/generators/week-ahead-content.d.ts +103 -0
  134. package/scripts/generators/week-ahead-content.d.ts.map +1 -0
  135. package/scripts/generators/week-ahead-content.js +610 -0
  136. package/scripts/generators/week-ahead-content.js.map +1 -0
  137. package/scripts/index.d.ts +40 -0
  138. package/scripts/index.d.ts.map +1 -0
  139. package/scripts/index.js +53 -0
  140. package/scripts/index.js.map +1 -0
  141. package/scripts/mcp/ep-mcp-client.d.ts +471 -0
  142. package/scripts/mcp/ep-mcp-client.d.ts.map +1 -0
  143. package/scripts/mcp/ep-mcp-client.js +734 -0
  144. package/scripts/mcp/ep-mcp-client.js.map +1 -0
  145. package/scripts/mcp/mcp-connection.d.ts +264 -0
  146. package/scripts/mcp/mcp-connection.d.ts.map +1 -0
  147. package/scripts/mcp/mcp-connection.js +790 -0
  148. package/scripts/mcp/mcp-connection.js.map +1 -0
  149. package/scripts/mcp/mcp-health.d.ts +75 -0
  150. package/scripts/mcp/mcp-health.d.ts.map +1 -0
  151. package/scripts/mcp/mcp-health.js +78 -0
  152. package/scripts/mcp/mcp-health.js.map +1 -0
  153. package/scripts/mcp/mcp-retry.d.ts +94 -0
  154. package/scripts/mcp/mcp-retry.d.ts.map +1 -0
  155. package/scripts/mcp/mcp-retry.js +127 -0
  156. package/scripts/mcp/mcp-retry.js.map +1 -0
  157. package/scripts/mcp/wb-mcp-client.d.ts +38 -0
  158. package/scripts/mcp/wb-mcp-client.d.ts.map +1 -0
  159. package/scripts/mcp/wb-mcp-client.js +112 -0
  160. package/scripts/mcp/wb-mcp-client.js.map +1 -0
  161. package/scripts/templates/article-template.d.ts +9 -0
  162. package/scripts/templates/article-template.d.ts.map +1 -0
  163. package/scripts/templates/article-template.js +378 -0
  164. package/scripts/templates/article-template.js.map +1 -0
  165. package/scripts/templates/section-builders.d.ts +28 -0
  166. package/scripts/templates/section-builders.d.ts.map +1 -0
  167. package/scripts/templates/section-builders.js +142 -0
  168. package/scripts/templates/section-builders.js.map +1 -0
  169. package/scripts/types/analysis.d.ts +115 -0
  170. package/scripts/types/analysis.d.ts.map +1 -0
  171. package/scripts/types/analysis.js +4 -0
  172. package/scripts/types/analysis.js.map +1 -0
  173. package/scripts/types/common.d.ts +584 -0
  174. package/scripts/types/common.d.ts.map +1 -0
  175. package/scripts/types/common.js +96 -0
  176. package/scripts/types/common.js.map +1 -0
  177. package/scripts/types/generation.d.ts +104 -0
  178. package/scripts/types/generation.d.ts.map +1 -0
  179. package/scripts/types/generation.js +4 -0
  180. package/scripts/types/generation.js.map +1 -0
  181. package/scripts/types/index.d.ts +24 -0
  182. package/scripts/types/index.d.ts.map +1 -0
  183. package/scripts/types/index.js +16 -0
  184. package/scripts/types/index.js.map +1 -0
  185. package/scripts/types/intelligence.d.ts +129 -0
  186. package/scripts/types/intelligence.d.ts.map +1 -0
  187. package/scripts/types/intelligence.js +4 -0
  188. package/scripts/types/intelligence.js.map +1 -0
  189. package/scripts/types/mcp.d.ts +418 -0
  190. package/scripts/types/mcp.d.ts.map +1 -0
  191. package/scripts/types/mcp.js +4 -0
  192. package/scripts/types/mcp.js.map +1 -0
  193. package/scripts/types/parliament.d.ts +388 -0
  194. package/scripts/types/parliament.d.ts.map +1 -0
  195. package/scripts/types/parliament.js +4 -0
  196. package/scripts/types/parliament.js.map +1 -0
  197. package/scripts/types/quality.d.ts +114 -0
  198. package/scripts/types/quality.d.ts.map +1 -0
  199. package/scripts/types/quality.js +4 -0
  200. package/scripts/types/quality.js.map +1 -0
  201. package/scripts/types/stakeholder.d.ts +88 -0
  202. package/scripts/types/stakeholder.d.ts.map +1 -0
  203. package/scripts/types/stakeholder.js +16 -0
  204. package/scripts/types/stakeholder.js.map +1 -0
  205. package/scripts/types/visualization.d.ts +708 -0
  206. package/scripts/types/visualization.d.ts.map +1 -0
  207. package/scripts/types/visualization.js +4 -0
  208. package/scripts/types/visualization.js.map +1 -0
  209. package/scripts/types/world-bank.d.ts +85 -0
  210. package/scripts/types/world-bank.d.ts.map +1 -0
  211. package/scripts/types/world-bank.js +4 -0
  212. package/scripts/types/world-bank.js.map +1 -0
  213. package/scripts/utils/article-category.d.ts +18 -0
  214. package/scripts/utils/article-category.d.ts.map +1 -0
  215. package/scripts/utils/article-category.js +49 -0
  216. package/scripts/utils/article-category.js.map +1 -0
  217. package/scripts/utils/article-quality-scorer.d.ts +87 -0
  218. package/scripts/utils/article-quality-scorer.d.ts.map +1 -0
  219. package/scripts/utils/article-quality-scorer.js +1048 -0
  220. package/scripts/utils/article-quality-scorer.js.map +1 -0
  221. package/scripts/utils/content-metadata.d.ts +34 -0
  222. package/scripts/utils/content-metadata.d.ts.map +1 -0
  223. package/scripts/utils/content-metadata.js +249 -0
  224. package/scripts/utils/content-metadata.js.map +1 -0
  225. package/scripts/utils/content-validator.d.ts +94 -0
  226. package/scripts/utils/content-validator.d.ts.map +1 -0
  227. package/scripts/utils/content-validator.js +489 -0
  228. package/scripts/utils/content-validator.js.map +1 -0
  229. package/scripts/utils/copy-test-reports.d.ts +9 -0
  230. package/scripts/utils/copy-test-reports.d.ts.map +1 -0
  231. package/scripts/utils/copy-test-reports.js +508 -0
  232. package/scripts/utils/copy-test-reports.js.map +1 -0
  233. package/scripts/utils/file-utils.d.ts +144 -0
  234. package/scripts/utils/file-utils.d.ts.map +1 -0
  235. package/scripts/utils/file-utils.js +374 -0
  236. package/scripts/utils/file-utils.js.map +1 -0
  237. package/scripts/utils/fix-articles.d.ts +27 -0
  238. package/scripts/utils/fix-articles.d.ts.map +1 -0
  239. package/scripts/utils/fix-articles.js +510 -0
  240. package/scripts/utils/fix-articles.js.map +1 -0
  241. package/scripts/utils/generate-docs-index.d.ts +8 -0
  242. package/scripts/utils/generate-docs-index.d.ts.map +1 -0
  243. package/scripts/utils/generate-docs-index.js +275 -0
  244. package/scripts/utils/generate-docs-index.js.map +1 -0
  245. package/scripts/utils/html-sanitize.d.ts +18 -0
  246. package/scripts/utils/html-sanitize.d.ts.map +1 -0
  247. package/scripts/utils/html-sanitize.js +57 -0
  248. package/scripts/utils/html-sanitize.js.map +1 -0
  249. package/scripts/utils/intelligence-analysis.d.ts +173 -0
  250. package/scripts/utils/intelligence-analysis.d.ts.map +1 -0
  251. package/scripts/utils/intelligence-analysis.js +936 -0
  252. package/scripts/utils/intelligence-analysis.js.map +1 -0
  253. package/scripts/utils/intelligence-index.d.ts +126 -0
  254. package/scripts/utils/intelligence-index.d.ts.map +1 -0
  255. package/scripts/utils/intelligence-index.js +731 -0
  256. package/scripts/utils/intelligence-index.js.map +1 -0
  257. package/scripts/utils/metadata-utils.d.ts +14 -0
  258. package/scripts/utils/metadata-utils.d.ts.map +1 -0
  259. package/scripts/utils/metadata-utils.js +18 -0
  260. package/scripts/utils/metadata-utils.js.map +1 -0
  261. package/scripts/utils/news-metadata.d.ts +47 -0
  262. package/scripts/utils/news-metadata.d.ts.map +1 -0
  263. package/scripts/utils/news-metadata.js +259 -0
  264. package/scripts/utils/news-metadata.js.map +1 -0
  265. package/scripts/utils/validate-articles.d.ts +2 -0
  266. package/scripts/utils/validate-articles.d.ts.map +1 -0
  267. package/scripts/utils/validate-articles.js +284 -0
  268. package/scripts/utils/validate-articles.js.map +1 -0
  269. package/scripts/utils/validate-ep-api.d.ts +51 -0
  270. package/scripts/utils/validate-ep-api.d.ts.map +1 -0
  271. package/scripts/utils/validate-ep-api.js +160 -0
  272. package/scripts/utils/validate-ep-api.js.map +1 -0
  273. package/scripts/utils/world-bank-data.d.ts +84 -0
  274. package/scripts/utils/world-bank-data.d.ts.map +1 -0
  275. package/scripts/utils/world-bank-data.js +311 -0
  276. 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