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 = //i; const CLOSE_TAG = ''; 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 提供基于公开来源的存在情报。我们不发布私人地址、泄露的行程、酒店住宿、医院、住宅或实时路线追踪。

本网站可能会使用必要的 cookie 以保存偏好设置。广告和分析 cookie 仅在按要求启用并接受时才会使用。

如果广告功能处于激活状态,可能由 Google AdSense 提供,并受 Google 政策和同意要求的约束。

联系方式:contact@parleymap.com

\n`); fs.writeFileSync('impressum.html', `${commonStyle}法律声明 | ParleyMap

法律声明

ParleyMap 是一个基于公开来源情报的演示站点,用于展示映射的公开露面、会议和机构活动。

负责人联系方式:contact@parleymap.com

不提供任何私人追踪。数据仅限于公开来源记录以及城市级别的机构或活动地点。

\n`); fs.writeFileSync('about.html', `${commonStyle}关于我们 | ParleyMap

关于 ParleyMap

ParleyMap 绘制公开露面、官方会议、峰会以及与机构相关的公共活动。

\n`); fs.writeFileSync('contact.html', `${commonStyle}联系我们 | ParleyMap

联系我们

Email:contact@parleymap.com

\n`); fs.writeFileSync('methodology.html', `${commonStyle}方法论 | ParleyMap

方法论

ParleyMap 尽可能使用官方和公开的主办方来源。除非具备明确的人物、日期、地点和主要来源,否则通用的观察列表不会被视作活动。

\n`); fs.writeFileSync('data-sources.html', `${commonStyle}数据来源 | ParleyMap

数据来源

来源包括公开的政府、多边机构、峰会主办方和官方组织网站。

\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, 关系网络分析, 后端开发, 多模态安全, 情报分析, 数据可视化, 网络诊断