All files / src/generators/political-intelligence icons.ts

100% Statements 16/16
100% Branches 4/4
100% Functions 4/4
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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                                          7x                                                                                                                 7x                                 191x 191x 6594x 158x     33x       7x                           7x                           53x 53x 351x 50x     3x    
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
 
/**
 * @module Generators/PoliticalIntelligence/Icons
 * @description Heuristic emoji icon picking for political-intelligence
 * documents and daily analysis runs.
 *
 * The icons are chosen to visually differentiate the most common
 * artifact types (SWOT, PESTLE, threat matrices, coalition dynamics,
 * etc.) without depending on a heavy icon library. Rules are evaluated
 * in declaration order, so more-specific matches must come before more-
 * general ones (e.g. `intelligence-brief` before `intelligence`).
 *
 * Lifted out of `political-intelligence.ts` so the icon-picking rules
 * can be unit-tested in isolation and so future renderers (e.g.
 * `news-indexes`, sitemap HTML) can reuse them without pulling in the
 * 1500-LOC PI module.
 */
 
/** Ordered keyword โ†’ icon rules for analysis documents. */
const DOCUMENT_ICON_RULES: readonly [readonly string[], string][] = [
  [['readme'], '๐Ÿ“˜'],
  [['swot'], '๐Ÿงญ'],
  [['pestle'], '๐ŸŒ'],
  [['stride'], '๐Ÿ›ก๏ธ'],
  [['threat'], 'โš ๏ธ'],
  [['risk'], '๐Ÿ“Š'],
  [['coalition'], '๐Ÿค'],
  [['stakeholder'], '๐Ÿ‘ฅ'],
  [['actor'], '๐Ÿ‘ค'],
  [['impact'], '๐Ÿ’ฅ'],
  [['scenario', 'forecast', 'outlook', 'wildcard', 'blackswan'], '๐Ÿ”ฎ'],
  [['economic', 'imf', 'worldbank', 'fiscal', 'monetary'], '๐Ÿ’ถ'],
  [['trade', 'tariff'], '๐Ÿ›ณ๏ธ'],
  [['timeline', 'historical', 'parallel'], '๐Ÿ•ฐ๏ธ'],
  [['methodology', 'guide', 'style'], '๐Ÿงญ'],
  [['classification'], '๐Ÿท๏ธ'],
  [['intelligence-brief', 'brief'], '๐Ÿ—ž๏ธ'],
  [['intelligence'], '๐Ÿ”'],
  [['network'], '๐Ÿ•ธ๏ธ'],
  [['velocity'], 'โšก'],
  [['productivity', 'pipeline', 'workflow-audit', 'workflow'], '๐Ÿ”ง'],
  [['legislative', 'legislation'], 'โš–๏ธ'],
  [['motion'], '๐Ÿ—ณ๏ธ'],
  [['proposition', 'proposal'], '๐Ÿ“œ'],
  [['committee'], '๐Ÿ›๏ธ'],
  [['vote', 'voting'], '๐Ÿ—ณ๏ธ'],
  [['plenary', 'session', 'meeting'], '๐ŸŸ๏ธ'],
  [['procedure'], '๐Ÿ“‚'],
  [['event', 'schedule', 'agenda'], '๐Ÿ“…'],
  [['mep', 'parliamentarian'], '๐Ÿง‘\u200d๐Ÿ’ผ'],
  [['consequence'], '๐ŸŒฟ'],
  [['disruption'], '๐ŸŒ€'],
  [['reflection'], '๐Ÿชž'],
  [['reliability', 'audit', 'quality'], 'โœ…'],
  [['attack-surface', 'attack'], '๐Ÿ›ก๏ธ'],
  [['diagnostic', 'outage'], '๐Ÿš‘'],
  [['forces', 'influence'], 'โš”๏ธ'],
  [['osint', 'tradecraft'], '๐Ÿ•ต๏ธ'],
  [['catalog', 'index'], '๐Ÿ“š'],
  [['capital'], '๐Ÿ’ผ'],
  [['synthesis', 'cross-daily', 'summary'], '๐Ÿงฉ'],
  [['cross-session', 'cross-run'], '๐Ÿ”'],
  [['sentiment'], '๐Ÿ’ฌ'],
  [['baseline', 'precomputed'], '๐Ÿ“'],
  [['significance'], '๐ŸŽฏ'],
  [['devil', 'advocate'], '๐Ÿ˜ˆ'],
  [['media', 'framing'], '๐Ÿ“บ'],
  [['reform', 'anti-corruption'], '๐Ÿงน'],
  [['recess'], '๐ŸŒด'],
  [['per-file', 'per-artifact'], '๐Ÿ—‚๏ธ'],
  [['adopted'], '๐Ÿ“œ'],
  [['document'], '๐Ÿ“„'],
  [['artifact'], '๐Ÿ“‹'],
];
 
/** Default icon used by {@link pickDocumentIcon} when no rule matches */
export const DEFAULT_DOCUMENT_ICON = '๐Ÿ“„';
 
/**
 * Heuristically pick an icon for an analysis document/slug. The icons
 * are chosen to visually differentiate the most common artifact types
 * without depending on a heavy icon library.
 *
 * Rules are evaluated in declaration order โ€” first match wins. To add
 * a new rule, add a `[hints, icon]` tuple at the **most-specific
 * position**: e.g. `intelligence-brief` is matched before
 * `intelligence` so the brief-specific newspaper icon (๐Ÿ—ž๏ธ) takes
 * precedence over the generic magnifying-glass (๐Ÿ”).
 *
 * @param stem - File/directory name stem (will be lowercased internally)
 * @returns A single emoji character (or grapheme cluster for ZWJ-joined emoji)
 */
export function pickDocumentIcon(stem: string): string {
  const s = stem.toLowerCase();
  for (const [hints, icon] of DOCUMENT_ICON_RULES) {
    if (hints.some((h) => s.includes(h))) {
      return icon;
    }
  }
  return DEFAULT_DOCUMENT_ICON;
}
 
/** Ordered slug-prefix โ†’ icon rules for daily runs. */
const RUN_ICON_RULES: readonly [readonly string[], string][] = [
  [['breaking'], '๐Ÿšจ'],
  [['week-ahead', 'month-ahead', 'year-ahead'], '๐Ÿ”ญ'],
  [['week-in-review', 'weekly-review'], '๐Ÿ“…'],
  [['month-in-review', 'monthly-review'], '๐Ÿ—“๏ธ'],
  [['year-in-review'], '๐Ÿ“œ'],
  [['motions'], '๐Ÿ—ณ๏ธ'],
  [['propositions'], 'โš–๏ธ'],
  [['committee-reports', 'committee'], '๐Ÿ›๏ธ'],
  [['translate'], '๐ŸŒ'],
  [['deep'], '๐Ÿ”ฌ'],
];
 
/** Default icon used by {@link pickRunIcon} when no rule matches */
export const DEFAULT_RUN_ICON = '๐Ÿ“‚';
 
/**
 * Pick an icon for a daily run based on its slug prefix.
 *
 * Unlike {@link pickDocumentIcon}, this matches by `startsWith` (not
 * `includes`) โ€” runs are named with a canonical prefix per article
 * type, so prefix-matching is precise enough and avoids false positives
 * from words appearing inside run-id suffixes.
 *
 * @param slug - Run slug such as `breaking-run190` or `motions-run46`
 * @returns A single emoji character (or grapheme cluster for ZWJ-joined emoji)
 */
export function pickRunIcon(slug: string): string {
  const s = slug.toLowerCase();
  for (const [prefixes, icon] of RUN_ICON_RULES) {
    if (prefixes.some((p) => s.startsWith(p))) {
      return icon;
    }
  }
  return DEFAULT_RUN_ICON;
}