source.fact.ngo a coherence.ngo project

scripts/emergence-page.js

raw ↗ · AGPL-3.0

import { squarify } from '../assets/emergence-layout.js'; import { projectEqualEarth } from '../assets/emergence-projection.js'; const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c])); const addedAt = e => e.distillation?.distilled_at || null; const addedTag = e => { const at = addedAt(e); if (!at) return ''; const t = new Date(at); return `<time class="em-added" datetime="${esc(at)}" data-added="${esc(at)}">added ${String(t.getUTCHours()).padStart(2, '0')}:${String(t.getUTCMinutes()).padStart(2, '0')} UTC</time>`; }; const plural = n => `${n} report${n === 1 ? '' : 's'}`; const route = (...parts) => '#t/' + parts.map(encodeURIComponent).join('/'); const sourceNames = { aljazeera: 'Al Jazeera', 'bbc-world': 'BBC World', 'guardian-world': 'The Guardian World', reuters: 'Reuters' }; const snapshotEssence = (snap, id) => (snap.essences || []).find(e => e.id === id && !e.failure) || null; // All record appearances share this identity and source treatment, including samples. function record(e, { boundary = false, anchor = false } = {}) { const a = e.article || e; const uncertainty = `<div class="em-uncertainty"><p class="em-label">Ambiguous</p><p>${esc(e.uncertainty || 'No uncertainty note supplied.')}</p></div>`; const claim = `<div class="em-claim"><p class="em-label">Report</p><p>${esc(e.essence)}</p></div>`; return `<article class="em-record${boundary ? ' em-boundary-record' : ''}" data-record-id="${esc(e.id)}"${anchor ? ` id="record-${esc(e.id)}"` : ''}> <div class="em-record-identity"><span class="em-status">not fact-checked · summarized from news reports</span><span class="em-record-type">${esc(e.event_type || 'report')}</span>${addedTag(e)}</div> <h3>${esc(a.title || 'Source report')}</h3> <a class="em-source" href="${esc(a.url)}" target="_blank" rel="noopener noreferrer">${esc(a.source_name)} ↗</a> ${boundary ? uncertainty + claim : claim + uncertainty} <details class="em-record-context"><summary>Context</summary><dl> <dt>Principles · AI interpretation</dt><dd>${(e.principles || []).map(esc).join(' · ') || 'None supplied'}</dd> <dt>Actors</dt><dd>${(e.actors || []).map(esc).join(' · ') || 'None supplied'}</dd> <dt>Places · country-level</dt><dd>${(e.locations || []).map(l => esc(l.name)).join(' · ') || 'None supplied'}</dd> </dl></details> </article>`; } const eventsCount = t => t.events ?? t.count; const tileValue = t => t.score ?? t.count; const tileSummary = t => t.events != null ? `${t.events} event${t.events === 1 ? '' : 's'} · ${plural(t.count)}` : plural(t.count); function topicTiles(tree) { return squarify(tree.filter(t => t.count > 0).map((t, i) => ({ value: tileValue(t), t, i }))).map(tile => `<a class="em-tile em-tone-${tile.i % 4}" href="#topic-${esc(tile.t.topic)}" aria-label="${esc(tile.t.name)}, ${esc(tileSummary(tile.t))}. Explore topic." title="${esc(tile.t.name)} · ${esc(tileSummary(tile.t))}" data-label="${esc(tile.t.name)}" data-count="${eventsCount(tile.t)}"${tile.value < 0.04 ? ' data-compact' : ''} style="left:${tile.x * 100}%;top:${tile.y * 100}%;width:${tile.w * 100}%;height:${tile.h * 100}%" data-go="${esc(route(tile.t.topic))}"><span class="em-tile-copy"><b>${esc(tile.t.name)}</b><span>${esc(tileSummary(tile.t))} <span aria-hidden="true">↗</span></span></span><span class="em-tile-marker" aria-hidden="true">${eventsCount(tile.t)}</span></a>` ).join(''); } const TOPIC_SHORT = { 'conflict-security': 'conflict', 'politics-governance': 'politics', 'economy-business': 'economy', 'society-culture': 'society', 'health-medicine': 'health', 'environment-climate': 'environment', 'science-discovery': 'science', 'technology-ai': 'tech', 'arts-sports': 'arts', 'other': 'other' }; const topicClass = t => t && TOPIC_SHORT[t] ? 'em-t-' + t : 'em-t-other'; const topicShort = t => TOPIC_SHORT[t || 'other'] || 'other'; function worldMap(countries, world, snap_places = []) { const p = world.projection; const sized = countries.map(c => c.events ?? c.count); const max = Math.max(1, ...sized); const fills = countries.map((c, i) => world.countries[c.country_code] ? `<a href="#country-${esc(c.country_code)}" tabindex="-1" aria-hidden="true"><path class="em-hot" fill-opacity="${(0.14 + 0.34 * sized[i] / max).toFixed(2)}" d="${world.countries[c.country_code]}"/></a>` : '').join(''); const places = (snap_places || []); const maxPlaceWeight = Math.max(1, ...places.map(p => p.weight)); const placeMarks = places.map(pl => { const [px, py] = projectEqualEarth(pl.lon, pl.lat, p); const summary = `${pl.events} event${pl.events === 1 ? '' : 's'} · ${pl.reports} report${pl.reports === 1 ? '' : 's'} · ${topicShort(pl.dominant_topic)}`; const sample = pl.essences[0] ? `${pl.essences[0].essence.replace(/\s+/g, ' ').slice(0, 160)}${pl.essences[0].essence.length > 160 ? '…' : ''}` : ''; return `<a class="${topicClass(pl.dominant_topic)}" href="#country-${esc(pl.country_code)}" aria-label="${esc(pl.name)}: ${esc(summary)}" data-topic="${esc(pl.dominant_topic || '')}" data-map-name="${esc(pl.name)}" data-map-summary="${esc(summary)}" data-map-sample="${esc(sample)}" data-map-source="${esc(pl.essences[0]?.source_name || '')}" data-map-url="${esc(pl.essences[0]?.url || '')}" data-map-lon="${pl.lon}" data-map-lat="${pl.lat}" data-last-update="${pl.last_update_at || ''}" data-map-first="${pl.first_seen_at || ''}" data-map-place><title>${esc(pl.name)}: ${esc(summary)}</title><circle class="em-place" cx="${px.toFixed(2)}" cy="${py.toFixed(2)}" r="${(1.1 + 1.8 * Math.sqrt(pl.weight / maxPlaceWeight)).toFixed(2)}"/></a>`; }).join(''); const marks = countries.map((c, i) => { const summary = c.events != null ? `${c.events} event${c.events === 1 ? '' : 's'} · ${c.count} report${c.count === 1 ? '' : 's'} · ${topicShort(c.dominant_topic)}` : `${c.count} report${c.count === 1 ? '' : 's'}`; const sample = c.essences[0] ? `${c.essences[0].essence.replace(/\s+/g, ' ').slice(0, 160)}${c.essences[0].essence.length > 160 ? '…' : ''}` : ''; const [x, y] = projectEqualEarth(c.lon, c.lat, p); return `<a class="${topicClass(c.dominant_topic)}" href="#country-${esc(c.country_code)}" aria-label="${esc(c.name)}: ${esc(summary)}" data-topic="${esc(c.dominant_topic || '')}" data-map-name="${esc(c.name)}" data-map-summary="${esc(summary)}" data-map-lon="${c.lon}" data-map-lat="${c.lat}" data-last-update="${c.last_update_at || ''}" data-map-first="${c.first_seen_at || ''}" data-map-sample="${esc(sample)}" data-map-source="${esc(c.essences[0]?.source_name || '')}" data-map-url="${esc(c.essences[0]?.url || '')}"><title>${esc(c.name)}: ${esc(summary)}</title><circle class="em-dot" cx="${x.toFixed(2)}" cy="${y.toFixed(2)}" r="${(3.5 + 8.5 * Math.sqrt(sized[i] / max)).toFixed(1)}"/></a>`; }).join(''); const regions = world.regions.map(r => `<a href="#map-region-${esc(r.id)}" class="em-map-region" data-region="${esc(r.id)}" aria-label="${esc(r.name)} · ${esc(r.category)} in reference data. Read boundary notes."><title>${esc(r.name)} · ${esc(r.category)}</title><path class="em-contested-ground" d="${r.d}"/><path class="em-contested-hatch" d="${r.d}"/></a>`).join(''); const labels = {ordinary:'Reference boundary',disputed:'Disputed boundary',control:'Control or ceasefire line',uncertain:'Boundary uncertain',claim:'Claim outline',zone:'Buffer or reference zone'}; const lines = Object.entries(world.boundaries).map(([kind, d]) => kind === 'ordinary' ? `<path class="em-border em-border-ordinary" d="${d}" aria-hidden="true"/>` : `<a href="#map-boundary-notes" aria-label="${labels[kind]} · reference map. Read the key."><path class="em-border em-border-${kind}" d="${d}"/></a>`).join(''); return `<svg class="em-map" viewBox="0 0 ${p.W} ${p.H}" role="group" aria-label="Equal Earth attention map. Country counts describe reporting, not sovereignty. Hatched areas have boundary or status notes."> <defs><pattern id="em-dispute-hatch" width="5" height="5" patternUnits="userSpaceOnUse"><path d="M-1,1L1,-1M0,5L5,0M4,6L6,4" class="em-hatch-line"/></pattern><mask id="em-attention-mask" maskUnits="userSpaceOnUse" x="0" y="0" width="${p.W}" height="${p.H}"><rect width="${p.W}" height="${p.H}" fill="white"/>${world.regions.map(r => `<path d="${r.d}" fill="black"/>`).join('')}</mask></defs> <path class="em-land" d="${world.land}"/><g mask="url(#em-attention-mask)">${fills}</g>${regions}${lines}${marks}${placeMarks}</svg>`; } function mapBoundaryNotes(world) { const external = (label, url) => `<a href="${esc(url)}" target="_blank" rel="noopener noreferrer">${esc(label)} ↗</a>`; const key = (kind, label) => `<li><svg class="em-key-line" viewBox="0 0 36 8" aria-hidden="true"><path class="em-border em-border-${kind}" d="M1,4L35,4"/></svg>${label}</li>`; return `<details class="em-map-notes" id="map-boundary-notes"><summary>Boundaries &amp; sources</summary> <p>Equal Earth preserves relative areas. Dashed lines and hatching mark disputed boundaries, claims or status in the reference data; they do not assign ownership.</p> <ul class="em-map-key">${key('disputed','Disputed boundary')}${key('claim','Claim outline')}${key('control','Control / ceasefire line')}${key('uncertain','Boundary uncertain')}${key('zone','Buffer / reference zone')}</ul> <p class="em-note">Reference geometry: Natural Earth 5.1.2 · corrections checked 2 Oct 2026. Some features remain reference-only. No live front lines or complete maritime claims. Legal recognition, administration and claims are distinct.</p> <p>${external('Source boundary policy',world.review.referenceUrl)} · <a href="../assets/emergence-world.md">Provenance &amp; license</a> · <a href="../assets/emergence-world.json" download>Download map data</a></p> <div class="em-map-region-list">${world.regions.map(r => `<details class="em-region-note" id="map-region-${esc(r.id)}"><summary><span>${esc(r.name)}</span><span class="em-region-status">${r.reviewedAt ? 'Source note' : 'Reference only'}</span></summary><p class="em-note">${esc(r.category)}</p><p>${esc(r.note)}</p>${r.reviewedAt ? `<p class="em-note">Source note checked <time datetime="${r.reviewedAt}">2 Oct 2026</time>; geometry retains its reference date.</p>` : ''}<p class="em-region-sources">${[...r.sources.map(s => external(s.label,s.url)),external('Natural Earth geometry','https://github.com/nvkelso/natural-earth-vector/tree/'+world.sources.commit+'/geojson')].join(' · ')}</p><button type="button" class="em-map-locate" data-map-bounds="${r.bounds.join(',')}" data-map-region="${esc(r.id)}" hidden>Show on map</button></details>`).join('')}</div> <details class="em-map-corrections"><summary>Corrections &amp; omissions</summary><p>Leases and historical reference lines are not labelled as active disputes.</p><ul>${world.review.corrections.map(c => `<li>${esc(c.reason)} ${external('Source',c.source)}</li>`).join('')}</ul></details> </details>`; } export function emergencePreview(snap) { const records = snap.essences.filter(e => !e.failure), topics = snap.topics.tree.filter(t => t.count > 0); const events = snap.events || []; const tiles = squarify(topics.map((t, i) => ({ value: tileValue(t), t, i }))).map(tile => `<a class="fact-day-tile fact-day-tone-${tile.i % 4}" href="emergence/${route(tile.t.topic)}" aria-label="${esc(tile.t.name)}: ${esc(tileSummary(tile.t))}. Open dashboard." title="${esc(tile.t.name)} · ${esc(tileSummary(tile.t))}" style="left:${tile.x * 100}%;top:${tile.y * 100}%;width:${tile.w * 100}%;height:${tile.h * 100}%"><span>${tile.value > .07 ? `<b>${esc(tile.t.name)}</b>` : ''}<span>${tile.t.events ?? tile.t.count}</span></span></a>`).join(''); const date = new Date(snap.date + 'T00:00Z').toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' }); return `<section class="fact-day-dashboard" aria-labelledby="fact-day-title"><div class="fact-day-heading"><h2 id="fact-day-title">Emergence</h2><time datetime="${esc(snap.date)}">${esc(date)} · UTC</time></div><p class="fact-day-description">Global sensor net of newsroom reporting.</p><p class="fact-day-status">not fact-checked · summarized from news reports</p><dl class="fact-day-stats"><div><dt>Events</dt><dd>${events.length}</dd></div><div><dt>Reports</dt><dd>${records.length}</dd></div><div><dt>Countries</dt><dd>${(snap.gis.countries || []).length}</dd></div><div><dt>Verified · inactive</dt><dd>0</dd></div></dl><div class="fact-day-portrait" aria-label="Latest day's coverage by primary topic">${tiles}</div><p class="fact-day-caption">Area = event weight (coverage, corroboration, nuance), never importance or truth.</p><a class="fact-day-open" href="emergence/">Open dashboard →</a></section>`; } export function emergenceBody(snap, world, v = "") { const records = snap.essences.filter(e => !e.failure), tree = snap.topics.tree.filter(t => t.count > 0); const countries = snap.gis.countries || [], byId = new Map(records.map(e => [e.id, e])); const dateLabel = new Date(snap.date + 'T00:00:00Z').toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' }); const typeCounts = {}; for (const e of records) { const t = e.event_type || 'report'; typeCounts[t] = (typeCounts[t] || 0) + 1; } const types = Object.keys(typeCounts).sort((a, b) => typeCounts[b] - typeCounts[a]); const initialType = types[0]; const initialRecords = records.filter(e => (e.event_type || 'report') === initialType); // Match the pipeline's primary-topic rollup and list each full record once. const groups = [...tree.map(t => ({ ...t, records: records.filter(e => e.topics?.[0]?.topic === t.topic) })), { topic: 'unmapped', name: 'Other / unmapped', records: records.filter(e => !tree.some(t => t.topic === e.topics?.[0]?.topic)) }].filter(t => t.records.length); const groupHtml = groups.map(t => `<details class="em-group" id="topic-${esc(t.topic)}"><summary><span>${esc(t.name)}</span><span>${t.records.length} records <span aria-hidden="true">+</span></span></summary><div class="em-record-grid">${t.records.map(e => record(e, { anchor: true })).join('')}</div></details>`).join(''); const countryHtml = countries.map((c, i) => `<details class="em-country" id="country-${esc(c.country_code)}"${i === 0 ? ' open' : ''}><summary><span>${esc(c.name)}</span><span>${c.events != null ? `${c.events} event${c.events === 1 ? '' : 's'} · ${c.count}` : c.count}</span></summary><p class="em-note">${c.essences.length} of ${c.count} previews${c.events != null ? ` · ${c.events} deduplicated event${c.events === 1 ? '' : 's'}` : ''}</p>${c.essences.map(sample => { const e = byId.get(sample.id) || sample, a = e.article || e; return `<article class="em-record-reference" data-record-id="${esc(e.id)}"><p class="em-status">not fact-checked · summarized from news reports</p><p>${esc(e.essence)}</p><a class="em-source" href="${esc(a.url)}" target="_blank" rel="noopener noreferrer">${esc(a.source_name)} ↗</a>${byId.has(e.id) ? `<a class="em-read-record" href="#record-${esc(e.id)}">Inspect →</a>` : ''}</article>`; }).join('')}</details>`).join(''); const sourceRows = Object.entries(snap.day.sources).map(([id, s]) => `<li><span>${esc(sourceNames[id] || id)}</span><span>${esc(s.status)}${s.status === 'ok' ? ` · ${s.fetched} / ${s.new}` : ''}</span></li>`).join(''); const dateShort = new Date(snap.date + 'T00:00:00Z').toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' }); const feedsOk = Object.values(snap.day.sources).filter(s => s.status === 'ok').length; const railStats = [ ['Events', (snap.events || []).length], ['Reports', records.length], ['Countries', countries.length], ['Feeds', `${feedsOk} ok`], ['Spend', `$${(snap.day.spend.usd || 0).toFixed(2)} / $1`], ].map(([k, v]) => `<div><dt>${esc(k)}</dt><dd>${esc(String(v))}</dd></div>`).join(''); return `<div class="em-shell" data-shell data-shell-default="map"> <aside class="em-rail" aria-label="Net status"> <div class="em-rail-id"><h1>Emergence</h1><p class="em-status">not fact-checked · summarized from news reports</p></div> <p class="em-rail-date"><time datetime="${esc(snap.date)}">${esc(dateShort)}</time> · UTC</p> <dl class="em-rail-stats">${railStats}</dl> <p class="em-rail-verified">Fact-checking council: <b>inactive</b> · 0 verified records</p> <p class="em-rail-note">Summaries by glm-5.3-flash · sources attributed per record</p> <p class="em-rail-cycle" id="em-cycle" hidden></p> </aside> <section class="em-head" aria-labelledby="em-title"> <div class="em-edition"><p class="em-label">Global sensor net</p><p><time datetime="${esc(snap.date)}">${esc(dateLabel)}</time> · UTC</p></div> <div class="em-introduction"><h1 id="em-title">Emergence</h1><p class="em-note">not fact-checked · summarized from news reports</p></div> <p class="em-app-intro">News reports gathered into a daily dashboard. Explore coverage, claims and sources.</p> <dl class="em-stats"><div><dt>Events</dt><dd>${(snap.events || []).length}</dd></div><div><dt>Reports</dt><dd>${records.length}</dd></div><div><dt>Countries</dt><dd>${countries.length}</dd></div><div><dt>Feeds</dt><dd>${Object.values(snap.day.sources).filter(s => s.status === 'ok').length}</dd></div><div><dt>Verified</dt><dd>0 <a href="#verified">Inactive ↗</a></dd></div></dl> </section> <nav class="em-lens-nav" aria-label="Explore this snapshot" data-shell-nav><a href="#map">Map</a><a href="#day">Topics</a><a href="#boundaries">Ambiguous</a><a href="#topics">Records</a><a href="#compare">Over time</a><a href="#method">Method</a><a href="#about">About</a></nav> <section class="em-section" id="map" aria-labelledby="map-title" data-pane> <div class="em-section-heading"><h2 id="map-title">Map</h2><p class="em-section-aside">Size = attention, never importance or truth.</p></div> <figure class="em-world">${worldMap(countries, world, snap.gis.places || [])}<div class="em-map-key" role="group" aria-label="Key: colour shows the dominant topic of reporting; shade shows the age of the newest report, solid new to faded older."><div class="em-k-row">${(() => { const present = [...new Set([...countries.map(c => c.dominant_topic), ...(snap.gis.places || []).map(pl => pl.dominant_topic)].filter(Boolean))].sort(); return present.map(t => `<span><i class="em-t-${t}"></i>${topicShort(t)}</span>`).join(""); })()}</div><div class="em-k-row em-k-age"><span>new<i class="em-k-shade" style="opacity:1"></i><em class="em-k-bar"></em><i class="em-k-shade" style="opacity:.18"></i>older</span></div></div><figcaption><span><i aria-hidden="true"></i> Country-level mentions</span><span class="em-place-legend">Pins: named places</span><a class="em-map-legend-link" href="#map-boundary-notes">Dashed / hatched: boundary notes</a></figcaption></figure> ${mapBoundaryNotes(world)} <details class="em-country-list"><summary>${countries.length} countries</summary><div class="em-countries">${countryHtml}</div></details> </section> <section class="em-section" id="day" aria-labelledby="day-title" data-pane data-pane-prefixes="t"> <div class="em-section-heading"><h2 id="day-title">Topics</h2><p class="em-section-aside">Area = event weight: coverage, corroboration, nuance. Never importance or truth.</p></div> <div class="em-portrait-layout"><div class="em-portrait"><nav class="em-crumb" id="em-crumb" aria-label="Topic zoom path"><span aria-current="page">All topics</span></nav><div class="em-treemap" id="em-treemap">${topicTiles(tree)}<div class="em-tile-preview" id="em-tile-preview" role="tooltip" aria-hidden="true" hidden></div></div><div class="em-detail" id="em-detail" hidden></div><p class="em-note em-portrait-caption" id="em-drill-status" role="status" hidden></p></div> <aside class="em-topic-index"><p class="em-label">Primary topics</p><ol id="em-topic-index">${tree.map((t, i) => `<li><a href="#topic-${esc(t.topic)}" data-go="${esc(route(t.topic))}"><span class="em-index-dot em-tone-${i % 4}" aria-hidden="true"></span><span>${esc(t.name)}</span><span>${t.events ?? t.count}</span></a></li>`).join('')}</ol></aside></div> </section> <section class="em-section" id="boundaries" aria-labelledby="boundary-title" data-pane> <div class="em-section-heading"><h2 id="boundary-title">Ambiguous</h2><div class="em-control" id="em-boundary-control" hidden><label class="em-visually-hidden" for="em-event-type">Event type</label><select id="em-event-type">${types.map(type => `<option value="${esc(type)}"${type === initialType ? ' selected' : ''}>${esc(type)}</option>`).join('')}</select></div></div> <p class="em-note" id="em-boundary-status" role="status">${Math.min(2, initialRecords.length)} of ${initialRecords.length} · snapshot order</p> <div class="em-record-grid em-boundary-grid" id="em-boundary-records">${initialRecords.slice(0, 2).map(e => record(e, { boundary: true })).join('')}</div> </section> <section class="em-section" id="topics" aria-labelledby="records-title" data-pane> <div class="em-section-heading"><h2 id="records-title">Records</h2><p class="em-section-aside">${records.length} · by primary topic</p></div> <div class="em-search" id="em-search-controls" hidden><label for="em-search">Search</label><input id="em-search" type="search" placeholder="Claims, actors, principles…"><p class="em-note" id="em-search-status" role="status">${records.length} records</p></div> <div id="em-all-records">${groupHtml}</div><p id="em-empty" class="em-empty" hidden>No matching records. Try another name or phrase.</p> </section> ${(() => { const threads = (snap.events || []).filter(e => e.thread_days > 1).sort((a, b) => b.thread_days - a.thread_days || b.weight - a.weight); const threadHtml = threads.length ? `<p class="em-note">${threads.length} ongoing events · historical comparison unavailable.</p><ol class="em-thread-list">${threads.map(e => { const seed = snapshotEssence(snap, e.seed), article = seed?.article || seed; return `<li class="em-thread-item"><p class="em-status">not fact-checked · summarized from news reports</p><a class="em-thread-open" href="#record-${esc(e.seed)}">Day ${e.thread_days} · ${(e.sources || []).length} source${(e.sources || []).length === 1 ? '' : 's'} · ${e.reports} report${e.reports === 1 ? '' : 's'} →</a>${seed ? `<p class="em-thread-essence">${esc(seed.essence.slice(0, 160))}${seed.essence.length > 160 ? '…' : ''}</p><a class="em-source" href="${esc(article.url)}" target="_blank" rel="noopener noreferrer">${esc(article.source_name)} ↗</a>` : ''}</li>`; }).join('')}</ol>` : `<p class="em-note">Historical comparison unavailable. No continuing events in this snapshot.</p>`; return `<section class="em-section em-compare" id="compare" aria-labelledby="compare-title" data-pane><div><h2 id="compare-title">Over time</h2>${threadHtml}</div><div class="em-timeline"><span class="em-timeline-dot" aria-hidden="true"></span><time datetime="${esc(snap.date)}">${esc(dateLabel)}</time></div></section>`; })()} <section class="em-section" id="method" aria-labelledby="method-title" data-pane> <div class="em-section-heading"><h2 id="method-title">Method & status</h2><a class="em-inline-link" href="data.json">Snapshot JSON ↗</a></div> <div class="em-method-grid"><div><p>Summaries by <strong>glm-5.3-flash</strong> · Cloudflare Workers AI.</p><details class="em-method-details"><summary>Method & limitations</summary><p>Newsroom teasers; one AI call per article, fixed neutral-framing prompt. Summaries and tags can contain errors.</p><p>Primary-topic rollups. Named places where the gazetteer resolves them; otherwise country centroids, not event locations. Unlit places are not evidence of inactivity.</p><p>Basemap: vendored Natural Earth, public domain.</p></details><dl class="em-method-stats"><div><dt>Articles / essences / failures</dt><dd>${snap.day.counts.articles} / ${snap.day.counts.essences} / ${snap.day.counts.failures}</dd></div><div><dt>Spend · USD</dt><dd>$${(snap.day.spend.usd || 0).toFixed(3)} <span class="em-note">/ $1 daily cap</span></dd></div><div><dt>Tokens in / out</dt><dd>${snap.day.spend.tokens_in.toLocaleString('en')} / ${snap.day.spend.tokens_out.toLocaleString('en')}</dd></div></dl></div> <div><p class="em-label">Feeds · latest cycle · fetched / new</p><ul class="em-source-status">${sourceRows}</ul><p class="em-note">Reuters deferred: no legitimate feed route.</p></div></div> <aside class="em-verified" id="verified" aria-labelledby="verified-title"><div class="em-verified-count">0<span>verified records</span></div><div><p class="em-label">Built · inactive</p><h3 id="verified-title">Verified lane</h3><p>AI council inactive by design.</p></div></aside> </section><section class="em-section" id="about" aria-labelledby="about-title" data-pane data-pane-hosts-footer> <div class="em-section-heading"><h2 id="about-title">About</h2><p class="em-section-aside">How the pipe works, the sources, and the basis for using them.</p></div> <div class="em-method-grid"> <div> <p class="em-label">How the pipe works</p> <ol class="em-pipe-steps"> <li><b>Take in.</b> Reputable newsrooms' public feeds, at the depth they offer: titles and summaries. No full-text scraping, no paywall or bot-gate bypass.</li> <li><b>Summarize.</b> One AI call per article (glm-5.3-flash) turns each report into a neutral summary. Nothing here is fact-checked.</li> <li><b>Group.</b> Reports about the same happening become one event; coverage repeated on following days becomes a thread.</li> <li><b>Publish.</b> This dashboard. Every record links to its original article. Cost this day: $${(snap.day.spend.usd || 0).toFixed(2)}, hard-capped at $1.</li> </ol> </div> <div> <p class="em-label">Sources we cover</p> <ul class="em-source-list"> ${Object.entries(snap.day.sources).map(([id, s]) => `<li class="em-source-row"> <div class="em-source-head"><a href="${esc(s.site || "#")}" target="_blank" rel="noopener noreferrer">${esc(s.name || id)} ↗</a><span class="em-source-status-chip">${esc(s.status)}</span></div> ${s.takes ? `<p class="em-source-takes">${esc(s.takes)}</p>` : ""} ${s.basis ? `<p class="em-source-basis">${esc(s.basis)}</p>` : ""} </li>`).join("")} </ul> <p class="em-note">Roadmap: country-level lanes that add reputable local outlets, for finer-grained local aggregation.</p> <p class="em-note">Your read marks stay in this browser only (local storage). fact.ngo keeps no record of what you read.</p> </div> </div> <p class="em-note">Funding, licensing, and attribution fineprint below.</p> </section> </div><script>window.EMERGENCE_ASSET_V = ${JSON.stringify(v || "")}; window.EMERGENCE_LIVE_URL = "https://emergence-net.zauh.workers.dev/latest.json";</script><script type="module" src="../assets/emergence.js?v=${esc(v || "")}"></script>`; }