Files
playbook/antigravity-awesome-skills/tools/scripts/normalize_traffic_snapshots.js
T
2026-07-13 02:44:46 +00:00

542 lines
22 KiB
JavaScript

#!/usr/bin/env node
/**
* Normalize the read-only captures in .codex/traffic-snapshots.
*
* The source dashboards have different grains. GitHub supplies rolling daily
* rows, while Search Console can be a snapshot total and Bing can supply both
* totals and daily rows. This script deliberately keeps dashboard properties
* separate: a legacy Pages property and a current Pages property are not a
* time series that may safely be summed together.
*/
const fs = require("fs");
const path = require("path");
const DEFAULT_INPUT = path.resolve(__dirname, "..", "..", ".codex", "traffic-snapshots");
const DEFAULT_OUTPUT = path.join(DEFAULT_INPUT, "daily-normalized.json");
const SNAPSHOT_DIRECTORY = /^\d{4}-\d{2}-\d{2}$/;
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
const MONTH_NAMES = ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"];
const CURRENT_REPOSITORY = "sickn33/agentic-awesome-skills";
const REPOSITORY_ALIASES = new Map([
["sickn33/antigravity-awesome-skills", CURRENT_REPOSITORY],
[CURRENT_REPOSITORY, CURRENT_REPOSITORY],
]);
const CURRENT_PAGES_PROPERTY = "https://sickn33.github.io/agentic-awesome-skills/";
const LEGACY_PAGES_PROPERTY = "https://sickn33.github.io/antigravity-awesome-skills/";
const DASHBOARDS = [
{
file: "google-search-console.json",
source: "google_search_console",
dashboardHosts: new Set(["search.google.com"]),
dashboardPathPrefix: "/search-console/",
primaryPropertyParam: "resource_id",
requiredDailyMetrics: ["clicks", "impressions"],
requiredTotalMetrics: ["clicks", "impressions"],
dailyMetrics: (value) => ({
clicks: nonNegativeIntegerOrNull(value.clicks),
impressions: nonNegativeIntegerOrNull(value.impressions),
}),
totals: (value) => ({
clicks: nonNegativeIntegerOrNull(value?.totals?.clicks ?? value.clicks),
impressions: nonNegativeIntegerOrNull(value?.totals?.impressions ?? value?.totals?.impressions_visible ?? value.impressions),
ctr: value?.totals?.ctr ?? value.ctr ?? null,
average_position: nonNegativeNumberOrNull(value?.totals?.average_position ?? value?.totals?.position ?? value.average_position),
}),
},
{
file: "bing-webmaster-search-performance.json",
source: "bing_search",
dashboardHosts: new Set(["bing.com", "www.bing.com"]),
dashboardPathPrefix: "/webmasters/",
primaryPropertyParam: "siteUrl",
requiredDailyMetrics: ["clicks", "impressions"],
requiredTotalMetrics: ["clicks", "impressions"],
dailyMetrics: (value) => ({
clicks: nonNegativeIntegerOrNull(value.clicks),
impressions: nonNegativeIntegerOrNull(value.impressions),
}),
totals: (value) => ({
clicks: nonNegativeIntegerOrNull(value?.totals?.clicks ?? value.clicks),
impressions: nonNegativeIntegerOrNull(value?.totals?.impressions ?? value?.totals?.impressions_label ?? value.impressions),
ctr: value?.totals?.ctr ?? value.ctr ?? null,
}),
},
{
file: "bing-webmaster-ai-performance.json",
source: "bing_ai",
dashboardHosts: new Set(["bing.com", "www.bing.com"]),
dashboardPathPrefix: "/webmasters/",
primaryPropertyParam: "siteUrl",
requiredDailyMetrics: ["citations"],
requiredTotalMetrics: ["citations"],
dailyMetrics: (value) => ({
citations: nonNegativeIntegerOrNull(value.citations ?? value.total_citations),
avg_cited_pages: nonNegativeNumberOrNull(value.avg_cited_pages),
}),
totals: (value) => ({
citations: nonNegativeIntegerOrNull(value.total_citations ?? value?.totals?.citations),
avg_cited_pages: nonNegativeNumberOrNull(value.avg_cited_pages ?? value?.totals?.avg_cited_pages),
}),
},
];
function numberOrNull(value) {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value !== "string") return null;
const raw = value.trim().replace(/\s/g, "");
if (!raw) return null;
const hasK = /k$/i.test(raw);
let numeric = raw.replace(/k$/i, "");
// Dashboard captures use both 5.1K and Italian-style 39,4K labels.
if (hasK) {
numeric = numeric.replace(",", ".");
} else if (/^\d{1,3}(?:[.,]\d{3})+$/.test(numeric)) {
numeric = numeric.replace(/[.,]/g, "");
} else {
numeric = numeric.replace(",", ".");
}
const parsed = Number(numeric);
return Number.isFinite(parsed) ? (hasK ? parsed * 1000 : parsed) : null;
}
function nonNegativeNumberOrNull(value) {
const parsed = numberOrNull(value);
return parsed !== null && parsed >= 0 ? parsed : null;
}
function nonNegativeIntegerOrNull(value) {
const parsed = nonNegativeNumberOrNull(value);
return parsed !== null && Number.isInteger(parsed) ? parsed : null;
}
function isCompleteMetrics(metrics) {
return Object.values(metrics).some((value) => value !== null);
}
function normalizedDate(value) {
if (typeof value !== "string") return null;
const trimmed = value.trim();
if (ISO_DATE.test(trimmed)) return isValidIsoDate(trimmed) ? trimmed : null;
if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) {
const date = trimmed.slice(0, 10);
return isValidIsoDate(date) ? date : null;
}
const match = trimmed.match(
/^(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2}),?\s+(\d{4})$/i,
);
if (!match) return null;
const month = MONTH_NAMES.indexOf(match[1].toLowerCase()) + 1;
const date = `${match[3]}-${String(month).padStart(2, "0")}-${String(match[2]).padStart(2, "0")}`;
return isValidIsoDate(date) ? date : null;
}
function isValidIsoDate(value) {
if (!ISO_DATE.test(value)) return false;
const [year, month, day] = value.split("-").map(Number);
const parsed = new Date(Date.UTC(year, month - 1, day));
return parsed.getUTCFullYear() === year
&& parsed.getUTCMonth() === month - 1
&& parsed.getUTCDate() === day;
}
function canonicalProperty(candidate) {
if (typeof candidate !== "string" || !candidate.trim()) return null;
try {
const url = new URL(candidate.trim());
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
url.hash = "";
url.search = "";
const pathname = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`;
return `${url.protocol}//${url.host}${pathname}`;
} catch {
return null;
}
}
function propertyFromDashboardUrl(dashboardUrl, config) {
if (typeof dashboardUrl !== "string") return { valid: false, signals: [], reason: "missing dashboard_url" };
try {
const url = new URL(dashboardUrl);
const hostValid = url.protocol === "https:" && config.dashboardHosts.has(url.hostname.toLowerCase());
const pathValid = url.pathname.startsWith(config.dashboardPathPrefix);
const primary = canonicalProperty(url.searchParams.get(config.primaryPropertyParam));
if (!hostValid || !pathValid || !primary) {
return { valid: false, signals: [], reason: "dashboard host, path, or primary property parameter is invalid" };
}
const signals = [];
for (const param of ["resource_id", "siteUrl"]) {
const value = canonicalProperty(url.searchParams.get(param));
if (value) signals.push({ source: `dashboard_url:${param}`, value });
}
return { valid: true, signals, reason: null };
} catch {
return { valid: false, signals: [], reason: "dashboard_url is malformed" };
}
}
function sourceProperty(snapshot, config) {
const observed = [];
for (const [source, raw] of [
["source_property", snapshot.source_property],
["property_url", snapshot.property_url],
["propertyUrl", snapshot.propertyUrl],
["site_url", snapshot.site_url],
["siteUrl", snapshot.siteUrl],
]) {
const value = canonicalProperty(raw);
if (value) observed.push({ source, value });
}
const dashboard = propertyFromDashboardUrl(snapshot.dashboard_url, config);
if (snapshot.dashboard_url && !dashboard.valid) {
return { property: null, provenance: "invalid", conflict: true, reason: dashboard.reason };
}
observed.push(...dashboard.signals);
const distinct = [...new Set(observed.map((signal) => signal.value))];
if (distinct.length > 1) {
return { property: null, provenance: "conflict", conflict: true, reason: "observed property signals conflict" };
}
const intended = canonicalProperty(snapshot.intended_property);
if (distinct.length === 1) {
return {
property: distinct[0],
provenance: "observed",
conflict: false,
reason: intended && intended !== distinct[0] ? "intended property disagrees with observed property" : null,
};
}
if (intended) {
return { property: intended, provenance: "intended_only", conflict: false, reason: "property identity comes from intended_property only" };
}
return { property: null, provenance: "unknown", conflict: false, reason: "no property identity signal" };
}
function propertyIdentity(property) {
const value = canonicalProperty(property);
if (value === LEGACY_PAGES_PROPERTY) return "legacy";
if (value === CURRENT_PAGES_PROPERTY) return "current";
return "unknown";
}
function coverageEnd(snapshot) {
const dailyValues = Array.isArray(snapshot.daily_values) ? snapshot.daily_values : [];
const dailyDates = dailyValues.map((row) => normalizedDate(row?.date)).filter(Boolean).sort();
if (dailyDates.length) return dailyDates.at(-1);
const text = [
snapshot.date_range_visible,
snapshot.date_range,
snapshot.chart_visible?.description,
].filter((value) => typeof value === "string").join(" ");
const isoDates = (text.match(/\d{4}-\d{2}-\d{2}/g) || []).map(normalizedDate).filter(Boolean);
if (isoDates?.length) return isoDates.sort().at(-1);
const monthDates = [...text.matchAll(
/\b(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2}),\s+(\d{4})\b/gi,
)].map((match) => {
const month = MONTH_NAMES.indexOf(match[1].toLowerCase()) + 1;
return normalizedDate(`${match[3]}-${String(month).padStart(2, "0")}-${String(match[2]).padStart(2, "0")}`);
}).filter(Boolean);
return monthDates.length ? monthDates.sort().at(-1) : null;
}
function readJson(filePath, warnings, label) {
try {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch (error) {
warnings.push(`${label}: skipped malformed JSON (${error.message.split("\n")[0]})`);
return null;
}
}
function setLatest(map, key, observation) {
const previous = map.get(key);
if (!previous || observation.observed_from_snapshot > previous.observed_from_snapshot) {
map.set(key, observation);
}
}
function setBestDashboardEvidence(map, key, observation) {
const previous = map.get(key);
const strength = { observed: 2, intended_only: 1, unknown: 0 };
const candidateStrength = strength[observation.property_provenance] ?? 0;
const previousStrength = strength[previous?.property_provenance] ?? -1;
if (!previous
|| candidateStrength > previousStrength
|| (candidateStrength === previousStrength && observation.observed_from_snapshot > previous.observed_from_snapshot)) {
map.set(key, observation);
}
}
function snapshotDirectories(inputDirectory) {
if (!fs.existsSync(inputDirectory)) throw new Error(`input directory does not exist: ${inputDirectory}`);
if (!fs.statSync(inputDirectory).isDirectory()) throw new Error(`input path is not a directory: ${inputDirectory}`);
const snapshots = fs.readdirSync(inputDirectory, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && SNAPSHOT_DIRECTORY.test(entry.name) && isValidIsoDate(entry.name))
.map((entry) => entry.name)
.sort();
if (!snapshots.length) throw new Error(`input directory has no valid snapshot directories: ${inputDirectory}`);
return snapshots;
}
function canonicalRepository(value) {
if (typeof value !== "string" || !value.trim()) return null;
const normalized = value.trim().toLowerCase();
return REPOSITORY_ALIASES.get(normalized) || normalized;
}
function normalizeSnapshots(inputDirectory) {
const warnings = [];
const github = { views: new Map(), clones: new Map() };
const dashboardDaily = new Map();
const dashboardTotals = new Map();
const discoveredSnapshots = snapshotDirectories(inputDirectory);
const snapshots = [];
const sourceRepositories = new Set();
let repo = null;
let timezone = null;
for (const snapshotDate of discoveredSnapshots) {
const directory = path.join(inputDirectory, snapshotDate);
const manifestPath = path.join(directory, "manifest.json");
if (!fs.existsSync(manifestPath)) {
warnings.push(`${snapshotDate}/manifest.json: skipped snapshot without identity manifest`);
continue;
}
const manifest = readJson(manifestPath, warnings, `${snapshotDate}/manifest.json`);
const rawRepository = typeof manifest?.repo === "string" ? manifest.repo.trim() : null;
const snapshotRepository = canonicalRepository(rawRepository);
const snapshotTimezone = typeof manifest?.timezone === "string" ? manifest.timezone.trim() : null;
if (!snapshotRepository || !snapshotTimezone) {
warnings.push(`${snapshotDate}/manifest.json: skipped snapshot with missing repository or timezone identity`);
continue;
}
if ((repo && snapshotRepository !== repo) || (timezone && snapshotTimezone !== timezone)) {
warnings.push(`${snapshotDate}/manifest.json: skipped snapshot with repository or timezone mismatch`);
continue;
}
repo ||= snapshotRepository;
timezone ||= snapshotTimezone;
sourceRepositories.add(rawRepository);
snapshots.push(snapshotDate);
for (const [kind, file] of [["views", "views.json"], ["clones", "clones.json"]]) {
const filePath = path.join(directory, file);
if (!fs.existsSync(filePath)) continue;
const payload = readJson(filePath, warnings, `${snapshotDate}/${file}`);
const values = Array.isArray(payload?.[kind]) ? payload[kind] : null;
if (!values) {
warnings.push(`${snapshotDate}/${file}: skipped missing ${kind} array`);
continue;
}
for (const value of values) {
const date = normalizedDate(value?.timestamp);
const count = nonNegativeIntegerOrNull(value?.count);
const uniques = nonNegativeIntegerOrNull(value?.uniques);
if (!date || !ISO_DATE.test(date) || count === null || uniques === null) {
warnings.push(`${snapshotDate}/${file}: skipped malformed daily row`);
continue;
}
setLatest(github[kind], date, { count, uniques, observed_from_snapshot: snapshotDate });
}
}
for (const config of DASHBOARDS) {
const filePath = path.join(directory, config.file);
if (!fs.existsSync(filePath)) continue;
const payload = readJson(filePath, warnings, `${snapshotDate}/${config.file}`);
if (!payload) continue;
if (payload.status !== "success") {
warnings.push(`${snapshotDate}/${config.file}: skipped non-success capture`);
continue;
}
const propertyEvidence = sourceProperty(payload, config);
if (propertyEvidence.conflict) {
warnings.push(`${snapshotDate}/${config.file}: skipped capture because ${propertyEvidence.reason}`);
continue;
}
const property = propertyEvidence.property;
if (propertyEvidence.reason) warnings.push(`${snapshotDate}/${config.file}: ${propertyEvidence.reason}`);
const identity = propertyIdentity(property);
if (identity === "unknown") {
warnings.push(`${snapshotDate}/${config.file}: property identity is unknown and remains isolated`);
}
const end = coverageEnd(payload);
const base = {
source_property: property,
property_provenance: propertyEvidence.provenance,
property_identity: identity,
coverage_end: end,
observed_from_snapshot: snapshotDate,
};
const propertyKey = property || `unknown:${snapshotDate}`;
const dailyValues = Array.isArray(payload.daily_values) ? payload.daily_values : [];
for (const value of dailyValues) {
const date = normalizedDate(value?.date);
if (!date) {
warnings.push(`${snapshotDate}/${config.file}: skipped malformed dashboard daily row`);
continue;
}
const metrics = config.dailyMetrics(value);
if (!isCompleteMetrics(metrics)) {
warnings.push(`${snapshotDate}/${config.file}: skipped dashboard daily row without metrics`);
continue;
}
// Never stitch a partial payload together with an older snapshot. A
// dashboard row is useful only when its source-specific core metrics
// arrived in the same source observation.
if (config.requiredDailyMetrics.some((metric) => metrics[metric] === null)) {
warnings.push(`${snapshotDate}/${config.file}: skipped partial dashboard daily row`);
continue;
}
const key = `${config.source}\u0000${propertyKey}\u0000${date}`;
setBestDashboardEvidence(dashboardDaily, key, { source: config.source, date, ...base, ...metrics });
}
const totals = config.totals(payload);
if (config.requiredTotalMetrics.every((metric) => totals[metric] !== null)) {
const key = `${config.source}\u0000${propertyKey}\u0000${end || "unknown"}`;
setBestDashboardEvidence(dashboardTotals, key, { source: config.source, ...base, ...totals });
} else if (isCompleteMetrics(totals)) {
warnings.push(`${snapshotDate}/${config.file}: skipped partial dashboard totals`);
}
}
}
if (!snapshots.length) {
throw new Error(`no snapshots with a consistent repository and timezone identity were accepted from: ${inputDirectory}`);
}
const rowsByDate = new Map();
const ensureRow = (date) => {
if (!rowsByDate.has(date)) rowsByDate.set(date, { date });
return rowsByDate.get(date);
};
for (const [kind, values] of Object.entries(github)) {
for (const [date, observation] of values) {
const row = ensureRow(date);
row.github ||= {};
row.github[kind] = observation;
}
}
for (const observation of dashboardDaily.values()) {
const row = ensureRow(observation.date);
row[observation.source] ||= [];
const { source, date, ...record } = observation;
row[source].push(record);
}
const rows = [...rowsByDate.values()].sort((left, right) => left.date.localeCompare(right.date));
for (const row of rows) {
for (const source of DASHBOARDS.map((config) => config.source)) {
if (Array.isArray(row[source])) {
row[source].sort((left, right) =>
`${left.property_identity}\u0000${left.source_property || ""}\u0000${left.observed_from_snapshot}`.localeCompare(
`${right.property_identity}\u0000${right.source_property || ""}\u0000${right.observed_from_snapshot}`,
),
);
}
}
}
const snapshot_totals = {};
for (const config of DASHBOARDS) {
snapshot_totals[config.source] = [...dashboardTotals.values()]
.filter((record) => record.source === config.source)
.map(({ source, ...record }) => record)
.sort((left, right) =>
`${left.property_identity}\u0000${left.source_property || ""}\u0000${left.coverage_end || ""}\u0000${left.observed_from_snapshot}`.localeCompare(
`${right.property_identity}\u0000${right.source_property || ""}\u0000${right.coverage_end || ""}\u0000${right.observed_from_snapshot}`,
),
);
}
return {
schema_version: "2.0.0",
repo,
source_repositories: [...sourceRepositories].sort(),
timezone,
grain: "date",
caveats: [
"GitHub rolling-window rows are deduplicated by date and retain the latest snapshot observation.",
"Dashboard data is separated by source_property and property_identity; legacy and current properties are never summed or overwritten.",
"coverage_end describes the latest date visible in a dashboard capture, not a promise of complete data.",
"Malformed, partial, or non-success captures are skipped with warnings rather than merged into another observation.",
],
discovered_snapshots: discoveredSnapshots,
snapshots,
rows,
snapshot_totals,
warnings: warnings.sort(),
};
}
function atomicWrite(outputPath, value) {
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
const temporary = path.join(
path.dirname(outputPath),
`.${path.basename(outputPath)}.${process.pid}.${Date.now()}.tmp`,
);
try {
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, "utf8");
fs.renameSync(temporary, outputPath);
} finally {
if (fs.existsSync(temporary)) fs.unlinkSync(temporary);
}
}
function parseArgs(args) {
const options = { input: DEFAULT_INPUT, output: DEFAULT_OUTPUT };
for (let index = 0; index < args.length; index += 1) {
const option = args[index];
if (option === "--input" || option === "--output") {
const value = args[index + 1];
if (!value || value.startsWith("--")) throw new Error(`${option} requires a value`);
options[option.slice(2)] = path.resolve(value);
index += 1;
} else if (option === "--help" || option === "-h") {
options.help = true;
} else {
throw new Error(`Unknown option: ${option}`);
}
}
return options;
}
function main() {
const options = parseArgs(process.argv.slice(2));
if (options.help) {
console.log("Usage: node tools/scripts/normalize_traffic_snapshots.js [--input DIR] [--output FILE]");
return;
}
const normalized = normalizeSnapshots(options.input);
atomicWrite(options.output, normalized);
console.log(`Normalized ${normalized.snapshots.length} snapshot directories to ${options.output}`);
}
if (require.main === module) {
try {
main();
} catch (error) {
console.error(`traffic normalization failed: ${error.message}`);
process.exitCode = 1;
}
}
module.exports = {
DEFAULT_INPUT,
DEFAULT_OUTPUT,
atomicWrite,
normalizeSnapshots,
parseArgs,
propertyIdentity,
sourceProperty,
};