Add hierarchical region data to candidate and center records

This commit is contained in:
2026-07-20 11:50:51 +08:00 Unverified
parent 352f471ed0
commit c5446d5241
21 changed files with 387 additions and 64 deletions
+55
View File
@@ -0,0 +1,55 @@
import { readFile, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
const input = resolve(process.argv[2] || 'ok_data_level3.csv');
const output = resolve(process.argv[3] || 'src/data/china-regions.mjs');
function parseCsvLine(line) {
const values = [];
let value = '';
let quoted = false;
for (let index = 0; index < line.length; index += 1) {
const char = line[index];
if (char === '"') {
if (quoted && line[index + 1] === '"') { value += '"'; index += 1; }
else quoted = !quoted;
} else if (char === ',' && !quoted) {
values.push(value);
value = '';
} else value += char;
}
values.push(value);
return values;
}
const source = await readFile(input, 'utf8');
const rows = source.replace(/^\uFEFF/, '').trim().split(/\r?\n/).slice(1).map(line => {
const [id, pid, deep, , , , extId, extName] = parseCsvLine(line);
return { id, pid, deep: Number(deep), code: extId.slice(0, 6), name: extName };
});
const provinces = rows.filter(item => item.deep === 0 && item.code !== '0').map(province => ({
code: province.code,
name: province.name,
cities: rows.filter(city => city.deep === 1 && city.pid === province.id).map(city => ({
code: city.code,
name: city.name,
districts: rows.filter(district => district.deep === 2 && district.pid === city.id).map(district => ({
code: district.code,
name: district.name
}))
}))
}));
const hotan = provinces.find(item => item.code === '650000')?.cities.find(item => item.code === '653200');
for (const district of [
{ code: '653228', name: '和康县' },
{ code: '653229', name: '和安县' }
]) {
if (hotan && !hotan.districts.some(item => item.code === district.code)) hotan.districts.push(district);
}
hotan?.districts.sort((a, b) => a.code.localeCompare(b.code));
const banner = `// Generated from AreaCity-JsSpider-StatsGov release 2025.251231.260403.\n// Source snapshot: 国家地名信息库 2025-12-31; generated 2026-07-20.\n// Manual official additions: 和康县 653228, 和安县 653229.\n`;
await writeFile(output, `${banner}export const chinaRegionsVersion = '2025-12-31';\nexport const chinaRegions = ${JSON.stringify(provinces)};\n`, 'utf8');
console.log(`Generated ${provinces.length} provinces at ${output}`);
+27
View File
@@ -0,0 +1,27 @@
import { pbkdf2Sync, randomBytes } from 'node:crypto';
import { rm } from 'node:fs/promises';
import { isAbsolute, join, relative, resolve } from 'node:path';
import { createDatabase } from '../database.mjs';
import { createSeedDatabase } from '../src/data/seed.mjs';
const root = resolve(process.cwd());
const databasePath = join(root, 'data', 'exam.sqlite');
const relativePath = relative(root, databasePath);
if (!relativePath || relativePath.startsWith('..') || isAbsolute(relativePath)) throw new Error('拒绝重建工作区以外的数据库');
for (const path of [databasePath, `${databasePath}-shm`, `${databasePath}-wal`]) await rm(path, { force: true });
function hashPassword(password, salt = randomBytes(16).toString('hex')) {
const hash = pbkdf2Sync(password, salt, 120000, 32, 'sha256').toString('hex');
return `${salt}:${hash}`;
}
process.env.DATABASE_CLIENT = 'sqlite';
process.env.SQLITE_PATH = databasePath;
const database = await createDatabase({
root,
seed: () => createSeedDatabase({ nowIso: () => new Date().toISOString(), hashPassword })
});
const state = await database.read();
await database.close();
console.log(`Rebuilt ${databasePath} (schema ${state.meta.version}, ${state.candidateProfiles.length} candidates, ${state.testCenters.length} centers)`);