feat: columna ad_daily.link_clicks (Meta clics enlace reales)

Persistimos link_clicks en warehouse (con backfill desde __lc), sync escribe la columna y creatividades la leen sin romper si falta.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-29 10:50:55 +02:00
parent 82a910330d
commit 8c5acb626f
5 changed files with 176 additions and 22 deletions

View File

@@ -365,27 +365,47 @@ async function replaceDaily(
);
}
// chunk inserts (link_clicks → ad_name marker until column exists)
// chunk inserts — prefer link_clicks column; fallback to Meta `__lc:N` marker
for (let i = 0; i < rows.length; i += 400) {
const chunk = rows.slice(i, i + 400).map((r) => {
const link = Math.round(r.link_clicks ?? r.clicks);
const { link_clicks: _lc, ...rest } = r;
if (r.platform === 'meta') {
return { ...rest, ad_name: `__lc:${link}` };
}
return rest;
});
const r = await fetch(`${base}/rest/v1/ad_daily`, {
const full = rows.slice(i, i + 400).map((r) => ({
...r,
link_clicks: Math.round(r.link_clicks ?? r.clicks),
ad_name: r.ad_name || '',
}));
let r = await fetch(`${base}/rest/v1/ad_daily`, {
method: 'POST',
headers: {
...headers,
'Content-Type': 'application/json',
Prefer: 'return=minimal',
},
body: JSON.stringify(chunk),
body: JSON.stringify(full),
});
if (!r.ok) {
throw new Error(`insert ad_daily: ${(await r.text()).slice(0, 220)}`);
const errText = await r.text();
if (!/link_clicks/i.test(errText)) {
throw new Error(`insert ad_daily: ${errText.slice(0, 220)}`);
}
const legacy = full.map((row) => {
const link = Math.round(row.link_clicks ?? row.clicks);
const { link_clicks: _lc, ...rest } = row;
if (row.platform === 'meta') {
return { ...rest, ad_name: `__lc:${link}` };
}
return rest;
});
r = await fetch(`${base}/rest/v1/ad_daily`, {
method: 'POST',
headers: {
...headers,
'Content-Type': 'application/json',
Prefer: 'return=minimal',
},
body: JSON.stringify(legacy),
});
if (!r.ok) {
throw new Error(`insert ad_daily: ${(await r.text()).slice(0, 220)}`);
}
}
}
}

View File

@@ -29,13 +29,13 @@ export type AdDailyRow = {
tenant_id?: string;
};
/** Meta link clicks stored as ad_name `__lc:N` until link_clicks column is used. */
/** Meta link clicks: column first, else `__lc:N` in ad_name (legacy), else all clicks. */
export function linkClicksOf(r: AdDailyRow): number {
const m = String(r.ad_name || '').match(/^__lc:(\d+(?:\.\d+)?)$/);
if (m) return Number(m[1]) || 0;
if (r.link_clicks != null && Number.isFinite(Number(r.link_clicks))) {
return Number(r.link_clicks) || 0;
}
const m = String(r.ad_name || '').match(/^__lc:(\d+(?:\.\d+)?)$/);
if (m) return Number(m[1]) || 0;
return Number(r.clicks || 0) || 0;
}