All files / scripts/political-intelligence daily-streams.ts

100% Statements 48/48
95.45% Branches 21/22
100% Functions 14/14
100% Lines 43/43

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                                            2x 2x   2x 2x                                               4x 4x 7x 7x 1x 6x 5x     4x         11443x 11442x 11442x 1264649x 431156x 431156x 431156x 10045x 421111x 421078x               11442x 409915x       32x 31x 31x 2015x 1953x 1922x   31x 1953x 1953x 13919x 11439x 12183x   1953x 11439x 11439x 11439x               11439x   1953x               31x    
/**
 * @module Infrastructure/PoliticalIntelligence/DailyStreams
 * @category Intelligence Operations / Supporting Infrastructure
 * @name Daily-stream artifact discovery
 *
 * @description
 * Walks `analysis/daily/<YYYY-MM-DD>/<stream>/` to produce a structured
 * `DailyDay[]` with one entry per date and one `DailyStream` per workflow
 * stream. Recursively counts `.md` / `.json` artifacts. Sorted newest-first.
 *
 * Round-6 split: extracted from `scripts/generate-political-intelligence.ts`.
 *
 * @author Hack23 AB (Infrastructure Team)
 * @license Apache-2.0
 */
 
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
 
import { buildGithubUrl } from './catalog.js';
 
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
 
const ROOT_DIR = path.join(__dirname, '..', '..');
const DAILY_DIR = path.join(ROOT_DIR, 'analysis', 'daily');
 
export interface DailyArtifact {
  readonly file: string;
  /** Path relative to analysis/daily (e.g. 2026-04-23/propositions/executive-brief.md). */
  readonly relative: string;
  readonly githubUrl: string;
}
 
export interface DailyStream {
  readonly name: string;
  readonly githubUrl: string;
  readonly artifactCount: number;
  readonly artifacts: DailyArtifact[];
}
 
export interface DailyDay {
  readonly date: string;
  readonly githubUrl: string;
  readonly streams: DailyStream[];
  readonly totalArtifacts: number;
}
 
export function countArtifactsRecursive(dir: string): number {
  let n = 0;
  for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
    const full = path.join(dir, e.name);
    if (e.isDirectory()) {
      n += countArtifactsRecursive(full);
    } else if (e.isFile() && /\.(md|json)$/i.test(e.name)) {
      n += 1;
    }
  }
  return n;
}
 
/** Recursively collect every `.md`/`.json` artifact under a stream folder, sorted by relative path. */
export function collectStreamArtifacts(streamDir: string, streamRelativeBase: string): DailyArtifact[] {
  if (!fs.existsSync(streamDir)) return [];
  const out: DailyArtifact[] = [];
  const walk = (dir: string, relBase: string): void => {
    for (const e of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
      const full = path.join(dir, e.name);
      const rel = relBase ? `${relBase}/${e.name}` : e.name;
      if (e.isDirectory()) {
        walk(full, rel);
      } else if (e.isFile() && /\.(md|json)$/i.test(e.name)) {
        out.push({
          file: rel,
          relative: `${streamRelativeBase}/${rel}`,
          githubUrl: buildGithubUrl('blob', `analysis/daily/${streamRelativeBase}/${rel}`),
        });
      }
    }
  };
  walk(streamDir, '');
  return out.sort((a, b) => a.file.localeCompare(b.file));
}
 
export function collectDailyDays(): DailyDay[] {
  if (!fs.existsSync(DAILY_DIR)) return [];
  const days: DailyDay[] = [];
  const dateEntries = fs.readdirSync(DAILY_DIR, { withFileTypes: true })
    .filter((e) => e.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(e.name))
    .map((e) => e.name)
    .sort((a, b) => b.localeCompare(a));
 
  for (const date of dateEntries) {
    const dateDir = path.join(DAILY_DIR, date);
    const streamNames = fs.readdirSync(dateDir, { withFileTypes: true })
      .filter((e) => e.isDirectory())
      .map((e) => e.name)
      .sort((a, b) => a.localeCompare(b));
 
    const streams: DailyStream[] = streamNames.map((name) => {
      const streamDir = path.join(dateDir, name);
      const artifacts = collectStreamArtifacts(streamDir, `${date}/${name}`);
      return {
        name,
        githubUrl: buildGithubUrl('tree', `analysis/daily/${date}/${name}`),
        artifactCount: artifacts.length || countArtifactsRecursive(streamDir),
        artifacts,
      };
    });
 
    const totalArtifacts = streams.reduce((a, s) => a + s.artifactCount, 0);
 
    days.push({
      date,
      githubUrl: buildGithubUrl('tree', `analysis/daily/${date}`),
      streams,
      totalArtifacts,
    });
  }
 
  return days;
}