import fs from 'node:fs'; import { fileURLToPath } from 'node:url'; const docsDir = new URL('.', import.meta.url); const inputPath = new URL('./checked-sites-revision-audit-2026-06-04.json', docsDir); const outputPath = new URL('./checked-sites-revision-audit-2026-06-04.html', docsDir); const rows = JSON.parse(fs.readFileSync(inputPath, 'utf8')); const existingRows = rows.filter((row) => row.exists !== false); const activeRows = existingRows.filter((row) => row.hasActiveRevision === 'Yes'); const noActiveRows = existingRows.filter((row) => row.hasActiveRevision !== 'Yes'); const r00BilledRows = existingRows.filter((row) => row.r00Billed !== 'No'); const r00UnbilledRows = existingRows.filter((row) => row.r00Billed === 'No'); const activeStatusCounts = activeRows.reduce((acc, row) => { const key = row.activeStatus || 'unknown'; acc[key] = (acc[key] || 0) + 1; return acc; }, {}); const revisionDepthCounts = activeRows.reduce((acc, row) => { const key = row.activeRevision || '—'; acc[key] = (acc[key] || 0) + 1; return acc; }, {}); const maxStatusCount = Math.max(1, ...Object.values(activeStatusCounts)); const maxDepthCount = Math.max(1, ...Object.values(revisionDepthCounts)); const esc = (value) => String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); function badgeClassForStatus(status) { if (status === 'client_review') return 'review'; if (status === 'client_approved') return 'approved'; if (status === 'not_started') return 'todo'; if (status === 'in_progress') return 'progress'; return 'neutral'; } function barChart(entries, maxValue) { return entries .map(([label, count]) => { const pct = Math.max(4, Math.round((count / maxValue) * 100)); return `
${esc(label)}
${count}
`; }) .join(''); } function renderTableRows(items) { return items .map((row) => { const billedClass = row.r00Billed === 'No' ? 'warn' : 'ok'; const activeClass = badgeClassForStatus(row.activeStatus); return ` ${esc(row.site)} ${esc(row.dbTitle)} ${esc(row.internalTaskNumber)} ${esc(row.r00Completed)} ${esc(row.r00Billed)} ${esc(row.hasActiveRevision)} ${esc(row.activeRevision)} ${esc(row.activeStatus)} ${esc(row.completedRevisions)} ${esc(row.billedVersions)} ${esc(row.versionSummary)} `; }) .join(''); } const html = ` Checked Sites Revision Audit
Fourge Portal Audit

Checked Sites Revision Audit

This report treats your checkmark as “R00 was completed,” then separates that from billing and from any newer active revisions. It is designed to make client-billing comparison easier at a glance.

Checked Sites
${existingRows.length}
Sites found in the database from your checked list.
Active Revisions
${activeRows.length}
Checked sites that now have an active R01+.
R00 Billed
${r00BilledRows.length}
Checked sites where the new-book unit is already on an invoice.
R00 Unbilled
${r00UnbilledRows.length}
Checked sites completed at R00 but not yet billed.

Active Revision Status

${barChart(Object.entries(activeStatusCounts), maxStatusCount)}

Active Revision Depth

${barChart(Object.entries(revisionDepthCounts), maxDepthCount)}

Sites With Active Revisions

These are the ones where R00 may be done, but a newer version is still active or awaiting review/approval.

${renderTableRows(activeRows)}
Site # DB Title Internal # R00 Done R00 Billed Active R# Active Status Completed Revisions Billed Versions Version Summary

Checked Sites With No Active Revision

These are simpler to read: R00 completed, and no current revision chain is active right now.

${renderTableRows(noActiveRows)}
Site # DB Title Internal # R00 Done R00 Billed Active R# Active Status Completed Revisions Billed Versions Version Summary
`; fs.writeFileSync(outputPath, html); console.log(fileURLToPath(outputPath));