mirror of
https://github.com/core-hn/pseudobsidian-ization.git
synced 2026-07-22 17:30:30 +00:00
147 lines
4.7 KiB
JavaScript
147 lines
4.7 KiB
JavaScript
|
|
/**
|
||
|
|
* Script one-shot : génère assets/cities.dict.json depuis GeoAPI INSEE.
|
||
|
|
* Usage : node scripts/build-cities.mjs
|
||
|
|
* Requiert Node >= 18 (fetch natif).
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { writeFileSync, mkdirSync } from 'fs';
|
||
|
|
import { join, dirname } from 'path';
|
||
|
|
import { fileURLToPath } from 'url';
|
||
|
|
|
||
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
|
|
const OUT_PATH = join(__dirname, '..', 'assets', 'cities.dict.json');
|
||
|
|
const GEOAPI_URL =
|
||
|
|
'https://geo.api.gouv.fr/communes?fields=nom,population,departement,region&format=json';
|
||
|
|
|
||
|
|
// Conditions de classification — ordre important : elles sont évaluées en séquence,
|
||
|
|
// la première condition vraie l'emporte.
|
||
|
|
const CONDITIONS = [
|
||
|
|
{ field: 'population', op: 'lt', value: 2000, class: 'Village' },
|
||
|
|
{ field: 'population', op: 'lt', value: 10000, class: 'Petite_Ville' },
|
||
|
|
{ field: 'population', op: 'lt', value: 100000, class: 'Ville' },
|
||
|
|
{ field: 'population', op: 'lt', value: 500000, class: 'Grande_Ville' },
|
||
|
|
{ field: 'population', op: 'gte', value: 500000, class: 'Métropole' },
|
||
|
|
];
|
||
|
|
|
||
|
|
function applyOp(entryValue, op, condValue) {
|
||
|
|
switch (op) {
|
||
|
|
case 'lt': return entryValue < condValue;
|
||
|
|
case 'lte': return entryValue <= condValue;
|
||
|
|
case 'gt': return entryValue > condValue;
|
||
|
|
case 'gte': return entryValue >= condValue;
|
||
|
|
case 'eq': return entryValue === condValue;
|
||
|
|
case 'neq': return entryValue !== condValue;
|
||
|
|
case 'contains': return String(entryValue).includes(String(condValue));
|
||
|
|
default: return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function resolveClass(entry) {
|
||
|
|
for (const cond of CONDITIONS) {
|
||
|
|
const v = entry[cond.field];
|
||
|
|
if (v != null && applyOp(v, cond.op, cond.value)) return cond.class;
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
console.log('Téléchargement GeoAPI INSEE…');
|
||
|
|
const res = await fetch(GEOAPI_URL);
|
||
|
|
if (!res.ok) throw new Error(`GeoAPI HTTP ${res.status}`);
|
||
|
|
|
||
|
|
/** @type {Array<{nom:string, population?:number, departement?:{code:string,nom:string}, region?:{code:string,nom:string}}>} */
|
||
|
|
const communes = await res.json();
|
||
|
|
console.log(`${communes.length} communes reçues.`);
|
||
|
|
|
||
|
|
const entries = communes
|
||
|
|
.filter((c) => c.population != null && c.population > 0)
|
||
|
|
.map((c) => {
|
||
|
|
const entry = {
|
||
|
|
value: c.nom,
|
||
|
|
type: 'place',
|
||
|
|
population: c.population,
|
||
|
|
departement: c.departement?.code ?? null,
|
||
|
|
region: c.region?.code ?? null,
|
||
|
|
};
|
||
|
|
const resolved = resolveClass(entry);
|
||
|
|
if (resolved) entry.sizeClass = resolved;
|
||
|
|
return entry;
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log(`${entries.length} entrées avec population.`);
|
||
|
|
|
||
|
|
const dict = {
|
||
|
|
schemaVersion: '1.1',
|
||
|
|
dictionaryId: 'fr-communes',
|
||
|
|
label: 'Communes françaises (GeoAPI INSEE)',
|
||
|
|
type: 'place',
|
||
|
|
language: 'fr',
|
||
|
|
source: 'https://geo.api.gouv.fr/communes',
|
||
|
|
license: 'Licence Ouverte v2.0 / Open Licence v2.0',
|
||
|
|
author: 'Institut national de la statistique et des études économiques (INSEE)',
|
||
|
|
doi: null,
|
||
|
|
|
||
|
|
roles: {
|
||
|
|
detection: true,
|
||
|
|
replacement: true,
|
||
|
|
classes: true,
|
||
|
|
},
|
||
|
|
|
||
|
|
configSchema: [
|
||
|
|
{
|
||
|
|
key: 'incrementScope',
|
||
|
|
label: "Portée de l'incrémentation",
|
||
|
|
type: 'enum',
|
||
|
|
values: ['file', 'folder', 'vault'],
|
||
|
|
default: 'file',
|
||
|
|
recommended: 'file',
|
||
|
|
description:
|
||
|
|
"Définit dans quelle portée les index ({index}) sont uniques. 'file' est recommandé pour maximiser l'anonymisation.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'replacementPattern',
|
||
|
|
label: 'Format du pseudonyme généré',
|
||
|
|
type: 'string',
|
||
|
|
default: '{class}_{index}',
|
||
|
|
description:
|
||
|
|
'Variables disponibles : {class} (classe attribuée), {index} (numéro d\'incrémentation dans la portée).',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'caseSensitive',
|
||
|
|
label: 'Sensible à la casse',
|
||
|
|
type: 'boolean',
|
||
|
|
values: [true, false],
|
||
|
|
default: false,
|
||
|
|
description: "Si false, 'paris' et 'Paris' sont détectés comme la même entité.",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
|
||
|
|
config: {
|
||
|
|
classificationMode: 'conditions',
|
||
|
|
conditions: CONDITIONS,
|
||
|
|
incrementScope: 'file',
|
||
|
|
replacementPattern: '{class}_{index}',
|
||
|
|
caseSensitive: false,
|
||
|
|
},
|
||
|
|
|
||
|
|
entries,
|
||
|
|
};
|
||
|
|
|
||
|
|
mkdirSync(join(__dirname, '..', 'assets'), { recursive: true });
|
||
|
|
writeFileSync(OUT_PATH, JSON.stringify(dict), 'utf-8');
|
||
|
|
console.log(`Dictionnaire écrit : ${OUT_PATH}`);
|
||
|
|
|
||
|
|
// Statistiques par classe
|
||
|
|
const stats = {};
|
||
|
|
for (const e of entries) {
|
||
|
|
const cls = e.sizeClass ?? '(sans classe)';
|
||
|
|
stats[cls] = (stats[cls] ?? 0) + 1;
|
||
|
|
}
|
||
|
|
console.log('\nRépartition par classe :');
|
||
|
|
for (const [cls, count] of Object.entries(stats).sort((a, b) => b[1] - a[1])) {
|
||
|
|
console.log(` ${cls.padEnd(15)} ${count}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
main().catch((e) => { console.error(e); process.exit(1); });
|