Goaskbid/parleymap
GitHub: Goaskbid/parleymap
一个基于 AI 的关系情报平台,用于映射政治、企业及组织间的影响力网络并提供结构化的政要与事件数据管理。
Stars: 0 | Forks: 0
import fs from 'node:fs';
import { execSync } from 'node:child_process';
const INDEX_PATH = 'index.html';
const DEMO_PATH = 'data/demo.json';
const DIAG_DIR = 'data/diagnostics';
const REPORT_PATH = `${DIAG_DIR}/canonical-maintenance-report.json`;
const AUDIT_PATH = `${DIAG_DIR}/canonical-hard-audit-report.json`;
const ADSENSE_PATH = `${DIAG_DIR}/adsense-preserve-audit-report.json`;
const SUMMARY_PATH = `${DIAG_DIR}/LATEST_RUN_SUMMARY.md`;
const OPEN_TAG_RE = /';
const FAKE_EVENT_PATTERNS = [
/iaea\s+nuclear\s+diplomacy\s+watch/i,
/city\s+of\s+london\s+finance\s+diplomacy\s+watch/i,
/think[- ]?tank\s+leadership\s+events\s+watch/i,
/royal\s+diaries?\s+and\s+state[- ]?visit\s+watch/i,
/generic\s+source[- ]?watch/i,
/source[- ]?watch\s+card/i
];
const NON_EVENT_PAGE_RE = /\b(faq|frequently asked|foire aux questions|homepage|home page|profile page|biography|fact sheet|programme|program|privacy|terms|cookie|sitemap)\b/i;
const ANCHORS = [
{ key: 'rafael_grossi', any: ['rafael grossi', 'grossi', 'q7283122'], anchor: { city: 'Vienna', countryCode: 'AT', countryName: 'Austria', lat: 48.2082, lng: 16.3738, label: '维也纳机构驻地', orgMark: 'IAEA', organization: 'International Atomic Energy Agency', region: 'Europe' }, image: 'https://commons.wikimedia.org/wiki/Special:FilePath/Rafael%20Grossi%20IAEA%202024.jpg' },
{ key: 'pope_leo_xiv', any: ['pope leo xiv', 'leo xiv', 'robert prevost', 'pope xiv'], anchor: { city: 'Vatican City', countryCode: 'VA', countryName: 'Vatican City', lat: 41.9029, lng: 12.4534, label: '梵蒂冈机构驻地', orgMark: 'Holy See', organization: 'Holy See', region: 'Europe' }, image: 'https://commons.wikimedia.org/wiki/Special:FilePath/Pope%20Leo%20XIV%202025.jpg' },
{ key: 'claudia_sheinbaum', any: ['claudia sheinbaum', 'sheinbaum'], anchor: { city: 'Mexico City', countryCode: 'MX', countryName: 'Mexico', lat: 19.4326, lng: -99.1332, label: '墨西哥城机构驻地', orgMark: 'MX', organization: 'Mexico', region: 'North America' }, image: 'https://commons.wikimedia.org/wiki/Special:FilePath/Claudia%20Sheinbaum%20Pardo%20%28cropped%2C%20centered%29.jpg' },
{ key: 'prabowo_subianto', any: ['prabowo subianto', 'subianto'], anchor: { city: 'Jakarta', countryCode: 'ID', countryName: 'Indonesia', lat: -6.2088, lng: 106.8456, label: '雅加达机构驻地', orgMark: 'ID', organization: 'Indonesia', region: 'Asia' }, image: 'https://commons.wikimedia.org/wiki/Special:FilePath/Prabowo%20Subianto%202024%20official%20portrait.jpg' },
{ key: 'mohammed_bin_salman', any: ['mohammed bin salman', 'mohammad bin salman', 'muhammad bin salman', 'mbs'], anchor: { city: 'Riyadh', countryCode: 'SA', countryName: 'Saudi Arabia', lat: 24.7136, lng: 46.6753, label: '利雅得机构驻地', orgMark: 'SA', organization: 'Saudi Arabia', region: 'Middle East' } },
{ key: 'king_charles', any: ['king charles iii', 'charles iii', 'king charles'], anchor: { city: 'London', countryCode: 'GB', countryName: 'United Kingdom', lat: 51.5074, lng: -0.1278, label: '伦敦机构驻地', orgMark: 'GB', organization: 'British Royal Family', region: 'Europe' } },
{ key: 'mark_rutte', any: ['mark rutte'], anchor: { city: 'Brussels', countryCode: 'BE', countryName: 'Belgium', lat: 50.8798, lng: 4.4219, label: '布鲁塞尔 NATO 机构驻地', orgMark: 'NATO', organization: 'NATO', region: 'Europe' } },
{ key: 'antonio_guterres', any: ['antonio guterres', 'antónio guterres', 'guterres'], anchor: { city: 'New York', countryCode: 'US', countryName: 'United States', lat: 40.7499, lng: -73.968, label: '纽约 UN 机构驻地', orgMark: 'UN', organization: 'United Nations', region: 'North America' } },
{ key: 'emmanuel_macron', any: ['emmanuel macron', 'macron'], anchor: { city: 'Paris', countryCode: 'FR', countryName: 'France', lat: 48.8566, lng: 2.3522, label: '巴黎机构驻地', orgMark: 'FR', organization: 'France', region: 'Europe' } },
{ key: 'ursula_von_der_leyen', any: ['ursula von der leyen', 'von der leyen'], anchor: { city: 'Brussels', countryCode: 'BE', countryName: 'Belgium', lat: 50.8503, lng: 4.3517, label: '布鲁塞尔机构驻地', orgMark: 'EU', organization: 'European Commission', region: 'Europe' } },
{ key: 'kaja_kallas', any: ['kaja kallas'], anchor: { city: 'Brussels', countryCode: 'BE', countryName: 'Belgium', lat: 50.8503, lng: 4.3517, label: '布鲁塞尔机构驻地', orgMark: 'EU', organization: 'European Union', region: 'Europe' } }
];
const OFFICIAL_EVENTS = [
{
id: 'official-grossi-iaea-board-vienna-2026-03-02',
personId: 'r-057-rafael-grossi',
personName: 'Rafael Grossi',
startsAt: '2026-03-02T09:00:00+01:00',
endsAt: null,
status: 'VERIFIED_PAST',
confidence: 0.96,
confidenceLabel: '官方来源',
eventType: 'PUBLIC_STATEMENT',
title: 'IAEA 理事会声明,维也纳',
summary: 'Rafael Grossi 在维也纳发表了官方的 IAEA 理事会声明。',
significance: '官方公共机构活动。',
decisions: '',
location: { label: 'IAEA 总部,维也纳', city: 'Vienna', countryCode: 'AT', countryName: 'Austria', lat: 48.2333, lng: 16.4167, precision: 'city' },
venuePublic: true,
securityPrecision: '公共机构场馆;仅显示城市级别',
publicInterestScore: 72,
eventGroupId: 'eg-iaea-board-vienna-2026-03-02',
topics: ['核外交', 'IAEA'],
counterpartIds: [],
sourcePack: [{ type: 'official', reliability: 'primary', publisher: 'IAEA', url: 'https://www.iaea.org/newscenter/statements/iaea-director-generals-introductory-statement-to-the-board-of-governors-2-6-march-2026' }],
visual: { status: 'public source', policy: 'official public event only' },
lastCheckedAt: new Date().toISOString(),
marketImpact: { sectors: ['energy'], companies: [], countries: ['Austria'], confidence: 'medium' },
realEvent: true
},
{
id: 'official-pope-leo-xiv-spain-journey-2026-06-06',
personId: 'r-085-pope-leo-xiv',
personName: 'Pope Leo XIV',
startsAt: '2026-06-06T09:00:00+02:00',
endsAt: '2026-06-12T18:00:00+02:00',
status: 'ANNOUNCED_FUTURE',
confidence: 0.95,
confidenceLabel: '梵蒂冈官方来源',
eventType: 'APOSTOLIC_JOURNEY',
title: '前往西班牙的宗座之旅',
summary: '梵蒂冈发布的 Pope Leo XIV 在西班牙的公开行程。',
significance: '官方公开旅行行程。',
decisions: '',
location: { label: '西班牙行程', city: 'Madrid', countryCode: 'ES', countryName: 'Spain', lat: 40.4168, lng: -3.7038, precision: 'city' },
venuePublic: true,
securityPrecision: '公开行程;仅显示城市级别',
publicInterestScore: 78,
eventGroupId: 'eg-pope-spain-2026-06-06',
topics: ['宗教', '外交'],
counterpartIds: [],
sourcePack: [{ type: 'official', reliability: 'primary', publisher: 'Vatican', url: 'https://www.vatican.va/content/leo-xiv/en/travels/2026/documents/spagna-6-12giugno2026.html' }],
visual: { status: 'public source', policy: 'official public itinerary only' },
lastCheckedAt: new Date().toISOString(),
marketImpact: { sectors: [], companies: [], countries: ['Spain'], confidence: 'low' },
realEvent: true
},
{
id: 'official-king-charles-washington-address-2026-04-28',
personId: 'r-012-king-charles-iii',
personName: 'King Charles III',
startsAt: '2026-04-28T12:00:00-04:00',
endsAt: null,
status: 'VERIFIED_PAST',
confidence: 0.94,
confidenceLabel: '官方皇室来源',
eventType: 'STATE_VISIT_PUBLIC_ADDRESS',
title: '在华盛顿向国会发表的致辞',
summary: '英国皇室关于 King Charles III 在华盛顿致辞的官方记录。',
significance: '官方公共国事访问活动。',
decisions: '',
location: { label: '华盛顿', city: 'Washington', countryCode: 'US', countryName: 'United States', lat: 38.9072, lng: -77.0369, precision: 'city' },
venuePublic: true,
securityPrecision: '公共国家级活动;仅显示城市级别',
publicInterestScore: 75,
eventGroupId: 'eg-king-charles-washington-2026-04-28',
topics: ['皇室外交', '国事访问'],
counterpartIds: [],
sourcePack: [{ type: 'official', reliability: 'primary', publisher: 'Royal Family', url: 'https://www.royal.uk/news-and-activity/2026-04-28/the-kings-address-to-the-joint-meeting-of-congress-in-washington' }],
visual: { status: 'public source', policy: 'official public event only' },
lastCheckedAt: new Date().toISOString(),
marketImpact: { sectors: [], companies: [], countries: ['United States'], confidence: 'low' },
realEvent: true
}
];
function norm(value) {
return String(value || '')
.toLowerCase()
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[^a-z0-9]+/g, ' ')
.trim();
}
function textBlob(obj) {
if (!obj || typeof obj !== 'object') return '';
return norm([
obj.id, obj.slug, obj.name, obj.canonicalName, obj.wikiTitle, obj.wikidataId,
obj.roleTitle, obj.organization, obj.countryName, obj.countryFocus, obj.countryFocusCode,
obj.profileLine, Array.isArray(obj.profileLines) ? obj.profileLines.join(' ') : '',
obj.title, obj.summary
].join(' '));
}
function readIndexData() {
const html = fs.readFileSync(INDEX_PATH, 'utf8');
const openMatch = html.match(OPEN_TAG_RE);
if (!openMatch || openMatch.index === undefined) throw new Error('在 index.html 中未找到 demo-data 开始标签');
const openStart = openMatch.index;
const jsonStart = openStart + openMatch[0].length;
const closeIndex = html.indexOf(CLOSE_TAG, jsonStart);
if (closeIndex === -1) new Error('在 index.html 中未找到 demo-data 结束标签');
const jsonText = html.slice(jsonStart, closeIndex).trim();
const data = JSON.parse(jsonText);
return { html, openStart, jsonStart, closeIndex, data };
}
function writeIndexData(payload, data) {
const nextJson = JSON.stringify(data, null, 2);
const nextHtml = payload.html.slice(0, payload.jsonStart) + '\n' + nextJson + '\n' + payload.html.slice(payload.closeIndex);
fs.writeFileSync(INDEX_PATH, nextHtml);
fs.mkdirSync('data', { recursive: true });
fs.writeFileSync(DEMO_PATH, nextJson + '\n');
}
function count(data, key) { return Array.isArray(data[key]) ? data[key].length : null; }
function validateCore(data, label='data') {
for (const key of ['people','roster','expansionRoster','appearances','categories']) {
if (!Array.isArray(data[key])) throw new Error(`${label}: ${key} 必须是一个数组`);
}
if (data.people.length < 85) throw new Error(`${label}: people 数量过少`);
if (data.roster.length < 180) throw new Error(`${label}: roster 数量过少`);
if (data.expansionRoster.length < 95) throw new Error(`${label}: expansionRoster 数量过少`);
if (data.appearances.length < 480) throw new Error(`${label}: appearances 数量过少`);
if (data.categories.length < 8) throw new Error(`${label}: categories 数量过少`);
}
function isProfileLike(obj) {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false;
return Boolean(obj.id || obj.slug || obj.name || obj.canonicalName || obj.wikidataId || obj.roleTitle || obj.profileLine);
}
function targetFor(obj) {
const blob = textBlob(obj);
return ANCHORS.find(t => t.any.some(x => blob.includes(norm(x))));
}
function anchorObject(target) {
const a = target.anchor;
return { label: a.label, city: a.city, countryCode: a.countryCode, countryName: a.countryName, lat: a.lat, lng: a.lng, precision: 'city', type: 'institutional_base', privacy: '仅限城市级别的公共机构驻地' };
}
function patchProfile(obj, target) {
const a = target.anchor;
const anchor = anchorObject(target);
obj.countryFocus = a.countryCode;
obj.countryFocusCode = a.countryCode;
obj.countryCode = a.countryCode;
obj.countryName = a.countryName;
obj.country = a.countryName;
obj.homeRegion = a.region || obj.homeRegion || null;
obj.locationStatus = 'institutional_base_city_level';
obj.homeBases = [anchor];
obj.homeBase = anchor;
obj.mapAnchor = anchor;
obj.anchorLocation = anchor;
obj.baseLocation = anchor;
obj.institutionalBase = anchor;
obj.lat = a.lat; obj.lng = a.lng; obj.latitude = a.lat; obj.longitude = a.lng;
obj.homeLat = a.lat; obj.homeLng = a.lng; obj.mapLat = a.lat; obj.mapLng = a.lng;
obj.anchorLat = a.lat; obj.anchorLng = a.lng;
obj.coordinates = { lat: a.lat, lng: a.lng };
obj.geo = { lat: a.lat, lng: a.lng, city: a.city, countryCode: a.countryCode, countryName: a.countryName };
obj.flagAudit = { ...(obj.flagAudit || {}), code: a.countryCode, countryCode: a.countryCode, label: a.countryName, countryName: a.countryName, status: 'country flag' };
obj.flagCode = a.countryCode;
obj.countryFlagCode = a.countryCode;
if (target.key === 'rafael_grossi') {
obj.organization = 'International Atomic Energy Agency';
obj.orgMark = 'IAEA';
obj.orgIcon = obj.orgIcon || 'IAEA';
obj.countryFocus = 'AT';
obj.countryFocusCode = 'AT';
obj.countryName = 'Austria';
obj.roleTitle = obj.roleTitle || 'Director General of the IAEA';
}
if ((!obj.imageUrl || String(obj.imageUrl).includes('placeholder') || String(obj.imageUrl).trim() === '') && target.image) {
obj.imageUrl = target.image;
obj.imageProvider = 'Wikimedia Commons fallback';
obj.visualAuditStatus = 'fallback_image_set';
}
}
function isFakeEvent(obj) {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false;
const text = [obj.title, obj.summary, obj.status, obj.eventType, obj.kind, obj.type, obj.id].join(' ');
if (FAKE_EVENT_PATTERNS.some(re => re.test(text))) return true;
if (obj.startsAt && String(obj.status || '').toLowerCase().includes('source-watch')) return true;
if (obj.startsAt && NON_EVENT_PAGE_RE.test(text) && !obj.realEvent) return true;
return false;
}
function repairArray(arr, path, report) {
if (!Array.isArray(arr)) return arr;
const out = [];
const visibleGrossiHelperCollections = /^(topRoster|priorityExpansion|watchlistExamples|openCatalogs|structuredSourceWatch|eventAgendas|summits)$/;
const seenIds = new Set();
for (const item of arr) {
if (isFakeEvent(item)) {
report.fakeEventsRemoved.push({ path, id: item?.id || null, title: item?.title || null });
continue;
}
if (isProfileLike(item)) {
const t = targetFor(item);
if (t) {
patchProfile(item, t);
report.anchorRepairs.push({ target: t.key, path, id: item.id || null, name: item.canonicalName || item.name || item.title || null });
}
const itemId = String(item.id || '');
if (itemId === 'r-057-rafael-grossi' && visibleGrossiHelperCollections.test(path)) {
report.profileDuplicatesRemoved.push({ path, id: itemId, name: item.canonicalName || item.name || 'Rafael Grossi' });
continue;
}
if (itemId && seenIds.has(itemId) && (path === 'people' || path === 'roster' || path === 'expansionRoster')) {
report.profileDuplicatesRemoved.push({ path, id: itemId, name: item.canonicalName || item.name || null, reason: 'duplicate_id_same_collection' });
continue;
}
if (itemId) seenIds.add(itemId);
}
out.push(item);
}
return out;
}
function walkAndRepair(value, path, report) {
if (Array.isArray(value)) {
const repaired = repairArray(value, path, report);
for (const item of repaired) {
if (item && typeof item === 'object') walkAndRepair(item, path + '[]', report);
}
return repaired;
}
if (!value || typeof value !== 'object') return value;
if (isProfileLike(value)) {
const t = targetFor(value);
if (t) {
patchProfile(value, t);
report.anchorRepairs.push({ target: t.key, path, id: value.id || null, name: value.canonicalName || value.name || value.title || null });
}
}
for (const key of Object.keys(value)) {
const child = value[key];
if (child && typeof child === 'object') value[key] = walkAndRepair(child, path ? `${path}.${key}` : key, report);
}
return value;
}
function seedOfficialEvents(data, report) {
if (!Array.isArray(data.appearances)) data.appearances = [];
const byId = new Map(data.appearances.map(x => [String(x.id || ''), x]));
for (const event of OFFICIAL_EVENTS) {
if (!byId.has(event.id)) {
data.appearances.push(event);
report.officialEventsAdded.push({ id: event.id, personName: event.personName, title: event.title });
}
}
data.appearances.sort((a, b) => String(b.startsAt || '').localeCompare(String(a.startsAt || '')));
}
function installRuntimeGuard(html) {
if (html.includes('PARLEYMAP_CANONICAL_RUNTIME_GUARD')) return html;
const guard = `\n\n`;
const match = html.match(OPEN_TAG_RE);
if (match && match.index !== undefined) {
const close = html.indexOf(CLOSE_TAG, match.index + match[0].length);
if (close !== -1) return html.slice(0, close + CLOSE_TAG.length) + guard + html.slice(close + CLOSE_TAG.length);
}
return html.replace('', guard + '\n');
}
function recoverAdsense(html, report) {
const clientMatch = html.match(/ca-pub-[0-9]{10,24}/);
const pubMatch = html.match(/pub-[0-9]{10,24}/);
const client = clientMatch ? clientMatch[0] : (pubMatch ? 'ca-' + pubMatch[0] : null);
const publisherId = client ? client.replace(/^ca-/, '') : null;
const slots = [...new Set([...html.matchAll(/data-ad-slot=["']([0-9]{4,})["']/g)].map(m => m[1]))];
report.client = client;
report.publisherId = publisherId;
report.headerSlot = slots[0] || null;
report.sidebarSlot = slots[1] || null;
if (publisherId) fs.writeFileSync('ads.txt', `google.com, ${publisherId}, DIRECT, f08c47fec0942fa0\n`);
let next = html;
if (client && !next.includes('google-adsense-account')) {
next = next.replace(/]*>/i, m => `${m}\n`);
}
if (client && !next.includes('pagead2.googlesyndication.com/pagead/js/adsbygoogle.js')) {
next = next.replace(/]*>/i, m => `${m}\n`);
}
fs.writeFileSync(ADSENSE_PATH, JSON.stringify({ generatedAt: new Date().toISOString(), status: client && slots.length >= 2 ? 'adsense_preserved_and_audited' : 'adsense_ids_not_found_or_incomplete_no_fake_ids_injected', client, publisherId, headerSlot: slots[0] || null, sidebarSlot: slots[1] || null }, null, 2) + '\n');
return next;
}
function writeLegalPages() {
const commonStyle = '';
fs.writeFileSync('privacy.html', `<>${commonStyle}隐私政策 | ParleyMap 法律声明 | ParleyMap 关于我们 | ParleyMap 联系我们 | ParleyMap 方法论 | ParleyMap 数据来源 | ParleyMap
隐私政策
ParleyMap 提供基于公开来源的存在情报。我们不发布私人地址、泄露的行程、酒店住宿、医院、住宅或实时路线追踪。
本网站可能会使用必要的 cookie 以保存偏好设置。广告和分析 cookie 仅在按要求启用并接受时才会使用。
如果广告功能处于激活状态,可能由 Google AdSense 提供,并受 Google 政策和同意要求的约束。
联系方式:contact@parleymap.com
\n`); fs.writeFileSync('impressum.html', `${commonStyle}法律声明
ParleyMap 是一个基于公开来源情报的演示站点,用于展示映射的公开露面、会议和机构活动。
负责人联系方式:contact@parleymap.com
不提供任何私人追踪。数据仅限于公开来源记录以及城市级别的机构或活动地点。
\n`); fs.writeFileSync('about.html', `${commonStyle}关于 ParleyMap
ParleyMap 绘制公开露面、官方会议、峰会以及与机构相关的公共活动。
\n`); fs.writeFileSync('contact.html', `${commonStyle}联系我们
Email:contact@parleymap.com
\n`); fs.writeFileSync('methodology.html', `${commonStyle}方法论
ParleyMap 尽可能使用官方和公开的主办方来源。除非具备明确的人物、日期、地点和主要来源,否则通用的观察列表不会被视作活动。
\n`); fs.writeFileSync('data-sources.html', `${commonStyle}数据来源
来源包括公开的政府、多边机构、峰会主办方和官方组织网站。
\n`); } function audit(data) { const visible = ['people','roster','topRoster','expansionRoster','priorityExpansion','watchlistExamples']; const flat = []; for (const key of visible) { const arr = Array.isArray(data[key]) ? data[key] : []; for (const obj of arr) if (isProfileLike(obj)) flat.push({ key, obj }); } const grossiVisible = flat.filter(x => textBlob(x.obj).includes('rafael grossi') || String(x.obj.id || '') === 'r-057-rafael-grossi'); const grossiBad = grossiVisible.filter(x => String(x.obj.countryFocusCode || '').toUpperCase() === 'IA' || String(x.obj.flagAudit?.code || '').toUpperCase() === 'BI'); const grossiCanon = grossiVisible.filter(x => String(x.obj.countryFocusCode || '').toUpperCase() === 'AT'); const allRows = []; walkCollect(data, '', allRows); const fakeRows = allRows.filter(x => isFakeEvent(x.obj)); const problems = []; if (grossiCanon.length < 1) problems.push('未找到 Grossi AT/维也纳 标准化档案'); if (grossiBad.length > 0) problems.push('可见档案集合中 Grossi 仍存在 IA/BI 污染'); if (grossiVisible.length > 2) problems.push(`可见的 Grossi 档案行数过多:${grossiVisible.length}`); if (fakeRows.length > 0) problems.push(`残留虚假日期/观察列表行:${fakeRows.length}`); const required = [ ['pope leo xiv', 'VA'], ['claudia sheinbaum','MX'], ['prabowo subianto','ID'] ]; for (const [name, code] of required) { const matches = flat.filter(x => textBlob(x.obj).includes(name)); if (!matches.some(x => String(x.obj.countryFocusCode || '').toUpperCase() === code)) problems.push(`${name} 缺少 ${code} 锚点`); } return { status: problems.length ? 'audit_failed' : 'audit_passed', generatedAt: new Date().toISOString(), problems, grossiVisibleCount: grossiVisible.length, grossiCanonicalCount: grossiCanon.length, fakeRowsRemaining: fakeRows.length }; } function walkCollect(value, path, out) { if (Array.isArray(value)) { value.forEach((v, i) => walkCollect(v, `${path}[${i}]`, out)); return; } if (!value || typeof value !== 'object') return; out.push({ path, obj: value }); for (const [k, v] of Object.entries(value)) if (v && typeof v === 'object') walkCollect(v, path ? `${path}.${k}` : k, out); } function gitCommitIfPossible() { if (!process.env.GITHUB_ACTIONS) return; try { execSync('git config user.name "github-actions[bot]"'); execSync('git config user.email "41898282+github-actions[bot]@users.noreply.github.com"'); execSync('git add index.html ads.txt privacy.html impressum.html about.html contact.html methodology.html data-sources.html data/demo.json data/diagnostics/*.json data/diagnostics/*.md', { stdio: 'inherit' }); try { execSync('git diff --cached --quiet'); console.log('没有自动提交的更改。'); return; } catch {} execSync('git commit -m "Canonical repair ParleyMap data and AdSense surface"', { stdio: 'inherit' }); execSync('git push', { stdio: 'inherit' }); } catch (error) { console.warn('自动提交失败;workflow 自动提交可能仍会提交已索引的文件:', error.message); } } fs.mkdirSync(DIAG_DIR, { recursive: true }); const payload = readIndexData(); validateCore(payload.data, '执行前'); const before = { people: count(payload.data,'people'), roster: count(payload.data,'roster'), expansionRoster: count(payload.data,'expansionRoster'), appearances: count(payload.data,'appearances'), categories: count(payload.data,'categories') }; const report = { generatedAt: new Date().toISOString(), before, anchorRepairs: [], fakeEventsRemoved: [], profileDuplicatesRemoved: [], officialEventsAdded: [] }; const repaired = walkAndRepair(payload.data, '', report); seedOfficialEvents(repaired, report); repaired.meta = { ...(repaired.meta || {}), lastCanonicalMaintenance: new Date().toISOString(), canonicalMaintenanceStatus: '机构锚点、虚假活动、官方活动及 AdSense 表面已修复' }; validateCore(repaired, '执行后'); const after = { people: count(repaired,'people'), roster: count(repaired,'roster'), expansionRoster: count(repaired,'expansionRoster'), appearances: count(repaired,'appearances'), categories: count(repaired,'categories') }; if (after.roster < before.roster - 5) throw new Error('roster 数量下降过多'); if (after.appearances < 480) throw new Error('appearances 数量低于安全底线'); writeIndexData(payload, repaired); let html = fs.readFileSync(INDEX_PATH, 'utf8'); html = installRuntimeGuard(html); fs.writeFileSync(INDEX_PATH, html); html = recoverAdsense(html, {}); fs.writeFileSync(INDEX_PATH, html); writeLegalPages(); const verifyPayload = readIndexData(); const auditResult = audit(verifyPayload.data); fs.writeFileSync(AUDIT_PATH, JSON.stringify(auditResult, null, 2) + '\n'); report.after = after; report.status = auditResult.status === 'audit_passed' ? 'canonical_maintenance_repaired' : 'canonical_maintenance_repaired_with_audit_failures'; fs.writeFileSync(REPORT_PATH, JSON.stringify(report, null, 2) + '\n'); const summary = [ '# ParleyMap 标准化维护修复', '', `生成时间:${new Date().toISOString()}`, '', `审核状态:${auditResult.status}`, '', '## 统计', '', '| 数据集 | 执行前 | 执行后 |', '|---|---:|---:|', `| people | ${before.people} | ${after.people} |`, `| roster | ${before.roster} | ${after.roster} |`, `| expansionRoster | ${before.expansionRoster} | ${after.expansionRoster} |`, `| appearances | ${before.appearances} | ${after.appearances} |`, `| categories | ${before.categories} | ${after.categories} |`, '', '## 修复情况', '', `- 锚点修复:${report.anchorRepairs.length}`, `- 移除的虚假活动:${report.fakeEventsRemoved.length}`, `- 移除的重复档案行:${report.profileDuplicatesRemoved.length}`, `- 添加的官方活动:${report.officialEventsAdded.length}`, `- Grossi 可见计数:${auditResult.grossiVisibleCount}`, `- 残留的虚假行:${auditResult.fakeRowsRemaining}`, '', '## 审核问题', '', ...(auditResult.problems.length ? auditResult.problems.map(p => `- ${p}`) : ['- 无']) ].join('\n') + '\n'; fs.writeFileSync(SUMMARY_PATH, summary); console.log(summary); if (auditResult.status !== 'audit_passed') { throw new Error('标准化严格审核失败:' + auditResult.problems.join('; ')); } gitCommitIfPossible();标签:GNU通用公共许可证, Homebrew安装, MITM代理, Node.js, 关系网络分析, 后端开发, 多模态安全, 情报分析, 数据可视化, 网络诊断