Initial commit: spec + squelette site + données causes/facteurs

Site perso comparant causes de décès réelles (CépiDc/CIM-10) et positions
des partis sur leurs facteurs de risque, en vue des présidentielles 2027.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMPw3qHuYEtf1msg2DhfE
This commit is contained in:
alexture 2026-08-21 19:52:19 +02:00
commit e19e3aeb96
10 changed files with 638 additions and 0 deletions

116
app.js Normal file
View file

@ -0,0 +1,116 @@
async function loadJSON(name) {
const res = await fetch(`data/${name}`);
return res.json();
}
const STANCE_LABEL = { pour: 'Pour', contre: 'Contre', divise: 'Divisé' };
function buildCauseNode(cause, causes, partis, facteurById, causeFacteurs, positions) {
const children = causes.filter(c => c.parent_id === cause.id);
const details = document.createElement('details');
const summary = document.createElement('summary');
summary.textContent = `${cause.nom} (${cause.code_cim10})`;
details.appendChild(summary);
if (children.length) {
for (const child of children) {
details.appendChild(buildCauseNode(child, causes, partis, facteurById, causeFacteurs, positions));
}
} else {
details.appendChild(buildFacteurTable(cause.id, partis, facteurById, causeFacteurs, positions));
}
return details;
}
function buildFacteurTable(causeId, partis, facteurById, causeFacteurs, positions) {
const table = document.createElement('table');
const facteurIds = causeFacteurs[causeId] || [];
const thead = document.createElement('tr');
thead.innerHTML = '<th>Facteur de risque</th>' +
partis.map(p => `<th style="background:${p.couleur}44">${p.nom}</th>`).join('');
table.appendChild(thead);
for (const facteurId of facteurIds) {
const tr = document.createElement('tr');
const facteurCell = document.createElement('td');
facteurCell.textContent = facteurById[facteurId].nom;
tr.appendChild(facteurCell);
for (const parti of partis) {
const td = document.createElement('td');
td.style.background = `${parti.couleur}22`;
const pos = positions.find(p => p.cause_id === causeId && p.facteur_id === facteurId && p.parti_id === parti.id);
if (pos) {
td.classList.add('pos-cell');
td.innerHTML = `<span class="pill pill-${pos.stance}">${STANCE_LABEL[pos.stance]}</span>`;
td.addEventListener('click', () => showDetail(parti.nom, pos));
} else {
td.textContent = '—';
}
tr.appendChild(td);
}
table.appendChild(tr);
}
return table;
}
function buildFacteursTable(facteurs, causeFacteurs, causes) {
const byId = Object.fromEntries(causes.map(c => [c.id, c]));
const causesByFacteur = {};
for (const [causeId, facteurIds] of Object.entries(causeFacteurs)) {
for (const facteurId of facteurIds) {
(causesByFacteur[facteurId] ??= []).push(causeId);
}
}
const rows = facteurs
.map(f => ({ facteur: f, causeIds: causesByFacteur[f.id] || [] }))
.filter(r => r.causeIds.length > 1)
.sort((a, b) => b.causeIds.length - a.causeIds.length);
const table = document.getElementById('facteurs-table');
const thead = document.createElement('tr');
thead.innerHTML = '<th>Facteur de risque</th><th>Nb causes touchées</th><th>Causes concernées</th>';
table.appendChild(thead);
for (const row of rows) {
const tr = document.createElement('tr');
const names = row.causeIds.map(id => byId[id].nom).join(', ');
tr.innerHTML = `<td>${row.facteur.nom}</td><td>${row.causeIds.length}</td><td>${names}</td>`;
table.appendChild(tr);
}
}
async function main() {
const [causes, partis, facteurs, causeFacteurs, positions] = await Promise.all([
loadJSON('causes.json'),
loadJSON('partis.json'),
loadJSON('facteurs.json'),
loadJSON('cause_facteurs.json'),
loadJSON('positions.json'),
]);
const facteurById = Object.fromEntries(facteurs.map(f => [f.id, f]));
const chapitres = causes
.filter(c => c.parent_id === null)
.sort((a, b) => (a.rang ?? 999) - (b.rang ?? 999));
const tree = document.getElementById('causes-tree');
for (const chapitre of chapitres) {
tree.appendChild(buildCauseNode(chapitre, causes, partis, facteurById, causeFacteurs, positions));
}
buildFacteursTable(facteurs, causeFacteurs, causes);
}
function showDetail(partiNom, pos) {
document.getElementById('detail-title').textContent = partiNom;
document.getElementById('detail-text').textContent = pos.position;
const source = document.getElementById('detail-source');
source.href = pos.source_url;
source.textContent = pos.source_url;
document.getElementById('detail').showModal();
}
main();