All files / scripts/mcp-client coverage.ts

100% Statements 22/22
93.75% Branches 30/32
100% Functions 7/7
100% Lines 19/19

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                    243x 28x 28x             48x             48x 76x 41x     7x             67x 243x                               67x   48x 67x             67x       67x 1x     47x                               107x                                               32x                  
/**
 * @module mcp-client/coverage
 * @description Coverage-state and provenance helpers for riksdag-regering MCP data.
 */
 
import { isPersonProfileText } from '../data-transformers/helpers.js';
import type { MCPCoverageState, MCPProvenance, MCPStructuredSignal } from '../types/mcp.js';
import { FULL_TEXT_MIN_LENGTH as SUBSTANTIVE_TEXT_MIN_LENGTH } from '../parliamentary-data/full-text-threshold.js';
 
function asCleanString(value: unknown): string {
  if (typeof value !== 'string') return '';
  const trimmed = value.trim();
  return isPersonProfileText(trimmed) ? '' : trimmed;
}
 
/**
 * Extract the best-effort primary date for a document-like payload.
 */
export function extractDocumentDate(record: Record<string, unknown>): string | null {
  const candidates = [
    record['datum'],
    record['inlämnad'],
    record['inlamnad'],
    record['publicerad'],
    record['date'],
  ];
  for (const candidate of candidates) {
    if (typeof candidate === 'string' && candidate.trim().length >= 10) {
      return candidate.trim().slice(0, 10);
    }
  }
  return null;
}
 
/**
 * Returns true when the payload carries meaningful document text.
 */
export function hasSubstantiveFullText(record: Record<string, unknown>): boolean {
  const contentFields = ['fullText', 'fullContent', 'text', 'html'];
  return contentFields.some((field) => asCleanString(record[field]).length > SUBSTANTIVE_TEXT_MIN_LENGTH);
}
 
/**
 * Infer the machine-readable MCP coverage state for a document payload.
 *
 * The `requestedDate` should be the current analysis/run date (i.e. "today"),
 * NOT the document's own publication date. Only when the document was published
 * on the same day as the analysis run and still lacks full text do we infer
 * `not_indexed` (indexing lag). For older documents without full text, the
 * correct state is `metadata_only`.
 */
export function inferDocumentCoverageState(
  record: Record<string, unknown>,
  options: { requestedDate?: string | null; fullTextRequested?: boolean } = {},
): MCPCoverageState {
  if (hasSubstantiveFullText(record)) return 'full_text';
 
  const requestedDate = options.requestedDate?.slice(0, 10) ?? null;
  const documentDate = extractDocumentDate(record);
 
  // Only infer `not_indexed` when:
  // 1. Full text was requested
  // 2. We have BOTH a run date and a document date
  // 3. The document was published on the SAME day as the analysis run
  // This means the document is brand new and likely not yet indexed.
  const sameDay = Boolean(
    requestedDate && documentDate && requestedDate === documentDate,
  );
 
  if (options.fullTextRequested && sameDay) {
    return 'not_indexed';
  }
 
  return 'metadata_only';
}
 
/**
 * Build the standardised MCP provenance block for an MCP-derived payload.
 */
export function buildMcpProvenance(options: {
  endpoint: string;
  tool: string;
  query: Record<string, unknown>;
  resultCount: number;
  coverageState: MCPCoverageState;
  retrieval?: 'live' | 'retry_queue' | 'cache';
  retrievedAt?: string;
  signals?: MCPStructuredSignal[];
}): MCPProvenance {
  return {
    provider: 'riksdag-regering',
    endpoint: options.endpoint,
    tool: options.tool,
    query: { ...options.query },
    resultCount: options.resultCount,
    coverageState: options.coverageState,
    retrieval: options.retrieval ?? 'live',
    retrievedAt: options.retrievedAt ?? new Date().toISOString(),
    ...(options.signals && options.signals.length > 0 ? { signals: options.signals } : {}),
  };
}
 
/**
 * Attach coverage-state metadata to an arbitrary record.
 */
export function attachCoverageMetadata<T extends Record<string, unknown>>(
  record: T,
  provenance: MCPProvenance,
): T & {
  mcpCoverageState: MCPCoverageState;
  mcpProvenance: MCPProvenance;
  mcpSignals?: MCPStructuredSignal[];
} {
  return {
    ...record,
    mcpCoverageState: provenance.coverageState,
    mcpProvenance: provenance,
    ...(provenance.signals && provenance.signals.length > 0
      ? { mcpSignals: provenance.signals }
      : {}),
  };
}