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 | 8x 24x 22x 17x 24x 24x 75x 64x 24x 16x 16x 16x 16x 8x 8x 8x 24x 24x 32x 16x 24x 11x 8x 24x | // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
/**
* @module Utils/Intelligence/Trends
* @description Trend detection across the intelligence index.
*/
import type { IntelligenceIndex, TrendDetection } from './types.js';
import { slugify } from './internals.js';
/** Minimum number of articles required to recognise a trend */
const MIN_TREND_ARTICLES = 2;
/**
* Resolve confidence level from the number of article references.
*
* @param count - Number of articles referencing the topic
* @returns Confidence level
*/
function resolveConfidence(count: number): TrendDetection['confidence'] {
if (count >= 5) return 'high';
if (count >= 3) return 'medium';
return 'low';
}
/**
* Resolve date range from a list of articles matching a set of IDs.
*
* @param index - The intelligence index
* @param articleIds - IDs of articles to consider
* @returns firstSeen and lastUpdated date strings
*/
function resolveDateRange(
index: IntelligenceIndex,
articleIds: string[]
): { firstSeen: string; lastUpdated: string } {
const fallback = new Date().toISOString().slice(0, 10);
const dates = index.articles
.filter((a) => articleIds.includes(a.id))
.map((a) => a.date)
.sort();
return {
firstSeen: dates[0] ?? fallback,
lastUpdated: dates[dates.length - 1] ?? fallback,
};
}
/**
* Build a topic-based trend entry.
*
* @param index - Intelligence index
* @param topic - Topic key
* @param articleIds - Article IDs covering this topic
* @returns TrendDetection entry
*/
function buildTopicTrend(
index: IntelligenceIndex,
topic: string,
articleIds: string[]
): TrendDetection {
const { firstSeen, lastUpdated } = resolveDateRange(index, articleIds);
const confidence = resolveConfidence(articleIds.length);
const direction: TrendDetection['direction'] =
articleIds.length >= 4 ? 'strengthening' : 'emerging';
return {
id: `trend-topic-${slugify(topic)}`,
name: `${topic} trend`,
category: 'political',
direction,
firstSeen,
lastUpdated,
articleReferences: [...articleIds],
evidence: [`Topic "${topic}" covered in ${articleIds.length} articles`],
confidence,
};
}
/**
* Build a procedure-based trend entry.
*
* @param index - Intelligence index
* @param proc - Procedure reference
* @param articleIds - Article IDs covering this procedure
* @returns TrendDetection entry
*/
function buildProcedureTrend(
index: IntelligenceIndex,
proc: string,
articleIds: string[]
): TrendDetection {
const { firstSeen, lastUpdated } = resolveDateRange(index, articleIds);
const confidence = resolveConfidence(articleIds.length);
return {
id: `trend-proc-${slugify(proc)}`,
name: `Procedure ${proc} tracking`,
category: 'legislative',
direction: 'stable',
firstSeen,
lastUpdated,
articleReferences: [...articleIds],
evidence: [`Procedure "${proc}" tracked across ${articleIds.length} articles`],
confidence,
};
}
/**
* Detect parliamentary trends from patterns across all indexed articles.
*
* A trend is formed when a topic or procedure appears in at least
* `MIN_TREND_ARTICLES` articles. The returned array replaces any
* previously detected trends stored in the index.
*
* @param index - Intelligence index to analyse
* @returns Array of detected {@link TrendDetection} objects
*/
export function detectTrends(index: IntelligenceIndex): TrendDetection[] {
const trends: TrendDetection[] = [];
for (const [topic, articleIds] of Object.entries(index.policyDomains)) {
if (articleIds.length >= MIN_TREND_ARTICLES) {
trends.push(buildTopicTrend(index, topic, articleIds));
}
}
for (const [proc, articleIds] of Object.entries(index.procedures)) {
if (articleIds.length >= MIN_TREND_ARTICLES) {
trends.push(buildProcedureTrend(index, proc, articleIds));
}
}
return trends;
}
|