← back to Professional Directory
src/enrich/ad_signals.js
124 lines
/**
* Ad / social-tracking signal detector — Pass 1 (regex only, no HTTP, no LLM).
*
* Ported from ventura-corridor's src/enrich/ad_signals.ts. Schema deltas:
* - business_id → organization_id
* - business_enrichment → org_enrichment
* - front_page_audits → unchanged (same shape)
*
* Detected signals:
* google_ads, google_analytics, meta_pixel, tiktok_pixel, pinterest_tag,
* linkedin_insight, twitter_pixel, reddit_pixel, bing_uet, snap_pixel,
* gtm_container (GTM detected — pixels may be loaded indirectly)
*
* paid_ads_count = sum of paid-channel booleans (excludes google_analytics,
* which is measurement-only, and gtm_container which is a wrapper).
*
* Compliance: internal sales-targeting only. Never publish per-doctor ad
* spend. Never assert dollar amounts — these are presence flags only.
*/
const fs = require('node:fs');
const path = require('node:path');
const { pool, query } = require('../../agents/shared/db');
const REPO_ROOT = path.resolve(__dirname, '../..');
function detect(html) {
const s = {
google_ads: /googleadservices\.com|gtag\([^)]*['"]AW-|google_conversion_id|googletagmanager\.com\/gtag\/js\?id=AW-/i.test(html),
google_analytics: /gtag\([^)]*['"]G-|googletagmanager\.com\/gtm\.js|google-analytics\.com\/analytics\.js|googletagmanager\.com\/gtag\/js\?id=G-|gtag\([^)]*['"]UA-/i.test(html),
meta_pixel: /connect\.facebook\.net\/[^"']*\/fbevents\.js|fbq\(['"]init['"]|fbq\(['"]track['"]|facebook\.com\/tr\?id=/i.test(html),
tiktok_pixel: /analytics\.tiktok\.com|business-api\.tiktok\.com|ttq\.load|TiktokAnalyticsObject/i.test(html),
pinterest_tag: /s\.pinimg\.com\/ct\.js|pintrk\(['"]load|pintrk\(['"]track/i.test(html),
linkedin_insight: /snap\.licdn\.com\/li\.lms-analytics|_linkedin_partner_id|_linkedin_data_partner_ids/i.test(html),
twitter_pixel: /static\.ads-twitter\.com\/uwt\.js|analytics\.twitter\.com\/i\/adsct|t\.co\/i\/adsct|twq\(['"]init|twq\(['"]track/i.test(html),
reddit_pixel: /www\.redditstatic\.com\/ads\/pixel\.js|rdt\(['"]init|rdt\(['"]track/i.test(html),
bing_uet: /bat\.bing\.com|bat\.r\.msn\.com|window\.uetq|window\["uetq"\]|uet\.gif/i.test(html),
snap_pixel: /sc-static\.net\/scevent|snaptr\(['"]init/i.test(html),
gtm_container: /googletagmanager\.com\/gtm\.js\?id=GTM-/i.test(html),
paid_ads_count: 0,
};
s.paid_ads_count =
Number(s.google_ads) +
Number(s.meta_pixel) +
Number(s.tiktok_pixel) +
Number(s.pinterest_tag) +
Number(s.twitter_pixel) +
Number(s.reddit_pixel) +
Number(s.bing_uet) +
Number(s.snap_pixel) +
Number(s.linkedin_insight);
return s;
}
async function main() {
const r = await query(`
SELECT DISTINCT ON (a.organization_id) a.organization_id, a.raw_html_path
FROM front_page_audits a
WHERE a.raw_html_path IS NOT NULL
ORDER BY a.organization_id, a.audited_at DESC
`);
console.log(`[ad-signals] mining ${r.rowCount} HTML files…`);
let withGoogleAds = 0, withMeta = 0, withTikTok = 0, withPinterest = 0,
withLinkedIn = 0, withTwitter = 0, withReddit = 0, withBing = 0,
withSnap = 0, withGA = 0, anyPaid = 0, processed = 0;
for (const row of r.rows) {
const rel = row.raw_html_path.startsWith('data/') ? row.raw_html_path : 'data/' + row.raw_html_path;
let html;
try {
html = fs.readFileSync(path.join(REPO_ROOT, rel), 'utf8');
} catch {
continue;
}
const s = detect(html);
if (s.google_ads) withGoogleAds++;
if (s.meta_pixel) withMeta++;
if (s.tiktok_pixel) withTikTok++;
if (s.pinterest_tag) withPinterest++;
if (s.linkedin_insight) withLinkedIn++;
if (s.twitter_pixel) withTwitter++;
if (s.reddit_pixel) withReddit++;
if (s.bing_uet) withBing++;
if (s.snap_pixel) withSnap++;
if (s.google_analytics) withGA++;
if (s.paid_ads_count > 0) anyPaid++;
processed++;
await query(
`
INSERT INTO org_enrichment (organization_id, ad_signals, signals_updated_at)
VALUES ($1, $2, now())
ON CONFLICT (organization_id) DO UPDATE
SET ad_signals = COALESCE(org_enrichment.ad_signals, '{}'::jsonb) || EXCLUDED.ad_signals,
signals_updated_at = now()
`,
[row.organization_id, JSON.stringify(s)]
);
}
console.log(`[ad-signals] processed ${processed} orgs · paid-ad signals:`);
console.log(` google_ads : ${withGoogleAds}`);
console.log(` meta_pixel : ${withMeta}`);
console.log(` tiktok_pixel : ${withTikTok}`);
console.log(` pinterest_tag : ${withPinterest}`);
console.log(` linkedin_insight: ${withLinkedIn}`);
console.log(` twitter_pixel : ${withTwitter}`);
console.log(` reddit_pixel : ${withReddit}`);
console.log(` bing_uet : ${withBing}`);
console.log(` snap_pixel : ${withSnap}`);
console.log(` any_paid_ads : ${anyPaid}`);
console.log(` google_analytics: ${withGA} (not paid; just measurement)`);
await pool.end();
}
if (require.main === module) {
main().catch((e) => {
console.error('[ad-signals]', e);
process.exit(1);
});
}
module.exports = { detect };