-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathorder.ts
More file actions
210 lines (202 loc) · 10.2 KB
/
order.ts
File metadata and controls
210 lines (202 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/**
* @module Infrastructure/RenderLib/Aggregator/Order
* @category Intelligence Operations / Supporting Infrastructure
* @name Canonical narrative order for analysis artifacts
*
* @description
* Pure data + tiny pure functions describing how analysis artifacts are
* ordered in the rendered article and how filenames map to human-readable
* section titles. Zero filesystem access, zero markdown dependencies —
* just a static table and two string helpers.
*
* Round-5 split: extracted from the 1205-LOC `render-lib/aggregator.ts`.
*
* @author Hack23 AB (Infrastructure Team)
* @license Apache-2.0
*/
import path from 'path';
/**
* Canonical narrative order. Each file is emitted as an `<h2>`-prefixed
* section in the aggregated article. Unknown artifacts (e.g. supplementary
* PESTLE / black-swan studies) are appended after the core sections in the
* order they appear on disk.
*
* The order follows a journalist-optimal narrative arc for political-
* intelligence reporting:
*
* Phase A — Lead & headline judgments (BLUF)
* Phase B — Primary evidence (`documents/*` expanded after
* `significance-scoring.md`; see aggregate.ts step 3)
* Phase C — Actors & political arithmetic (stakeholders, coalition
* math, voter segmentation)
* Phase D — Forward trajectory (indicators, scenarios, election,
* cycle, parliamentary season)
* Phase E — Risk, threat & strategic posture (risk, SWOT, threat,
* STRIDE, wildcards, PESTLE)
* Phase F — Context & narrative environment (history, comparative,
* feasibility, media framing)
* Phase G — Critique (devil's advocate)
* Phase H — Audit appendix (classification, cross-refs, methodology,
* manifest)
*
* Rationale: readers form their own view of the substance first
* (Phases A→D), then weigh risk and context (Phases E→F), and finally
* see narrative-environment / influence-operations and devil's-advocate
* critique (late F + G) before the appendix. Anchoring per-document
* evidence right after the so-what ranking ("show your work") is the
* standard intelligence-product structure (cf. ICD 203, NIC NIE).
*/
export const AGGREGATION_ORDER: readonly string[] = [
// ─── Phase A — Lead & headline judgments (BLUF) ────────────────────
// Front-load the journalist's "fast answer" cluster: who/what/when/why,
// confidence-bearing Key Judgments, and the so-what ranking.
'executive-brief.md',
'synthesis-summary.md',
'intelligence-assessment.md',
'significance-scoring.md',
// ─── Phase B — Primary evidence (documents/* expanded here) ────────
// The aggregator injects per-document analyses immediately after
// significance-scoring so readers meet the actual primary sources
// (motions, propositions, committee reports, with their `dok_id`)
// BEFORE any interpretive lenses. This is the "show your work"
// pattern intelligence consumers expect.
// ─── Phase C — Actors & political arithmetic ───────────────────────
// Stakeholder lens, parliamentary arithmetic (who can pass it), and
// voter exposure (whose interests are at stake) — clustered so the
// "WHO" question is answered as one block.
'stakeholder-perspectives.md',
'stakeholder-impact.md',
'coalition-mathematics.md',
'voter-segmentation.md',
// ─── Phase D — Forward trajectory ──────────────────────────────────
// Dated watch items, probability-weighted scenarios, electoral
// implications, and (for long-horizon workflows) cycle trajectory
// and parliamentary calendar.
'forward-indicators.md',
'scenario-analysis.md',
'election-2026-analysis.md',
'election-cycle-analysis.md', // generalised alias for election-2026-analysis.md (filename-variant; canonical name as cycles roll over). De-duplicated at render time via FILENAME_ALIASES below — if both files exist in a folder, only the one encountered first in this order is emitted.
'election-2026-implications.md', // legacy filename variant — same alias group; listed here so it participates in AGGREGATION_ORDER de-dupe rather than being appended as a supplementary file.
'cycle-trajectory.md', // 24th artifact — election-cycle workflow ONLY
'parliamentary-season.md', // long-horizon workflows (quarter / year / cycle)
// ─── Phase E — Risk, threat & strategic posture ────────────────────
// All "what could go wrong" lenses clustered together so readers
// process them as a coherent risk register rather than as a random
// sprinkle between substance and context.
'risk-assessment.md',
'swot-analysis.md',
'quantitative-swot.md', // year-ahead + cycle blocking
'threat-analysis.md',
'political-stride-assessment.md', // cycle blocking
'wildcards-blackswans.md', // year-ahead + cycle blocking
'pestle-analysis.md', // year-ahead + cycle blocking; supplementary elsewhere
// ─── Phase F — Context & narrative environment ─────────────────────
// Historical parallels, peer-country comparison, implementation
// feasibility, then media-framing/influence-operations LAST in this
// cluster so readers form their own view of the substance first
// before being shown how the story is being framed.
'historical-parallels.md',
'comparative-international.md',
'implementation-feasibility.md',
'media-framing-analysis.md',
// ─── Phase G — Critique & alt hypotheses ───────────────────────────
'devils-advocate.md',
// ─── Phase H — Audit appendix ──────────────────────────────────────
'classification-results.md',
'political-classification.md',
'cross-reference-map.md',
'horizon-pir-rollforward.md', // long-horizon supplementary
'methodology-reflection.md',
'data-download-manifest.md',
];
/**
* Filename-variant aliases. Each key maps to a set of equivalent filenames
* that represent the same logical artifact. The aggregator emits at most
* **one** member of each alias group per folder — if two aliased filenames
* are present on disk (e.g. both `election-2026-analysis.md` and
* `election-cycle-analysis.md`), only the first one encountered in
* {@link AGGREGATION_ORDER} is rendered; the others are skipped.
*
* This guarantees backwards compatibility with ~50 existing run folders that
* use the legacy `election-2026-analysis.md` name while the cycle-agnostic
* `election-cycle-analysis.md` becomes the canonical name post-2026 rollover.
*/
export const FILENAME_ALIASES: ReadonlyArray<ReadonlySet<string>> = [
new Set(['election-2026-analysis.md', 'election-cycle-analysis.md', 'election-2026-implications.md']),
new Set(['stakeholder-perspectives.md', 'stakeholder-impact.md']),
new Set(['classification-results.md', 'political-classification.md']),
];
/**
* Resolve the alias group (if any) that a filename belongs to. Returns the
* set of equivalent filenames including `file` itself, or `null` if `file`
* has no aliases.
*/
export function aliasGroupFor(file: string): ReadonlySet<string> | null {
for (const group of FILENAME_ALIASES) {
if (group.has(file)) return group;
}
return null;
}
/**
* Human-readable English section titles for each artifact. The aggregator
* emits these as `## <title>` headings so the rendered article has a
* consistent outline independent of what the AI wrote inside the file.
* Unknown files fall back to a title derived from the filename via
* {@link prettifyFallbackTitle}.
*/
const SECTION_TITLES: Record<string, string> = {
'executive-brief.md': 'Executive Brief',
'synthesis-summary.md': 'Synthesis Summary',
'significance-scoring.md': 'Significance Scoring',
'stakeholder-perspectives.md': 'Stakeholder Perspectives',
'stakeholder-impact.md': 'Stakeholder Perspectives',
'swot-analysis.md': 'SWOT Analysis',
'risk-assessment.md': 'Risk Assessment',
'threat-analysis.md': 'Threat Analysis',
'election-2026-analysis.md': 'Election 2026 Analysis',
'election-cycle-analysis.md': 'Election Cycle Analysis',
'election-2026-implications.md': 'Election 2026 Analysis',
'cycle-trajectory.md': 'Cycle Trajectory',
'parliamentary-season.md': 'Parliamentary Season Outlook',
'pestle-analysis.md': 'PESTLE Analysis',
'wildcards-blackswans.md': 'Wildcards & Black Swans',
'quantitative-swot.md': 'Quantitative SWOT',
'political-stride-assessment.md': 'Political STRIDE Assessment',
'horizon-pir-rollforward.md': 'Horizon PIR Roll-Forward',
'coalition-mathematics.md': 'Coalition Mathematics',
'voter-segmentation.md': 'Voter Segmentation',
'scenario-analysis.md': 'Scenario Analysis',
'forward-indicators.md': 'Forward Indicators',
'comparative-international.md': 'Comparative International',
'historical-parallels.md': 'Historical Parallels',
'media-framing-analysis.md': 'Media Framing Analysis',
'implementation-feasibility.md': 'Implementation Feasibility',
'devils-advocate.md': "Devil's Advocate",
'intelligence-assessment.md': 'Intelligence Assessment — Key Judgments',
'classification-results.md': 'Classification Results',
'political-classification.md': 'Political Classification',
'cross-reference-map.md': 'Cross-Reference Map',
'methodology-reflection.md': 'Methodology Reflection & Limitations',
'data-download-manifest.md': 'Data Download Manifest',
};
/**
* Convert a filename like `pestle-analysis.md` into a human title
* `Pestle Analysis`. Used as the fallback for any artifact not in
* {@link SECTION_TITLES}.
*/
export function prettifyFallbackTitle(file: string): string {
const base = path.basename(file).replace(/\.md$/i, '');
return base
.split(/[-_]/)
.filter(Boolean)
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
.join(' ');
}
/**
* Resolve the human-readable section title for an artifact. Looks up
* the curated map first; falls back to {@link prettifyFallbackTitle}.
*/
export function titleForArtifact(file: string): string {
const base = path.basename(file);
return SECTION_TITLES[base] ?? prettifyFallbackTitle(base);
}