@liiift-studio/sanity-visitor-insights 0.30.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.js +22 -7
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +22 -7
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/capture.ts +48 -4
- package/src/server/reports/measurementHealth.ts +54 -6
- package/src/server/reports/reports.test.ts +10 -2
- package/src/studio/panels.test.tsx +32 -0
package/dist/server.js
CHANGED
|
@@ -1057,7 +1057,7 @@ function fromOrders(ga4Purchases, sanityOrders) {
|
|
|
1057
1057
|
rate: ga4Purchases / sanityOrders,
|
|
1058
1058
|
observed: ga4Purchases,
|
|
1059
1059
|
actual: sanityOrders,
|
|
1060
|
-
note: `GA4 purchases against the ${sanityOrders} orders that exist \u2014 the same event on both sides. Small numbers move it a long way: one more or fewer
|
|
1060
|
+
note: `GA4 purchases against the ${sanityOrders} orders that exist \u2014 the same event on both sides. Small numbers move it a long way: one more or fewer purchase seen by GA4 shifts this rate by about ${swing} points.`
|
|
1061
1061
|
};
|
|
1062
1062
|
}
|
|
1063
1063
|
function fromPageviews(ga4Pageviews, vercelPageviews) {
|
|
@@ -1080,6 +1080,13 @@ function fromEmail(ga4Sessions, mailchimpClicks) {
|
|
|
1080
1080
|
note: "GA4 sessions from the campaign against Mailchimp\u2019s unique clicks. A cohort whose true size is known exactly."
|
|
1081
1081
|
};
|
|
1082
1082
|
}
|
|
1083
|
+
function samplingInterval(estimate) {
|
|
1084
|
+
const n = estimate.actual;
|
|
1085
|
+
if (!Number.isFinite(n) || n <= 0) return { low: estimate.rate, high: estimate.rate };
|
|
1086
|
+
const p = Math.min(1, Math.max(0, estimate.rate));
|
|
1087
|
+
const error = 2 * Math.sqrt(Math.max(p * (1 - p), 0.02) / n);
|
|
1088
|
+
return { low: Math.max(0, p - error), high: Math.min(1, p + error) };
|
|
1089
|
+
}
|
|
1083
1090
|
var TRUST = ["orders", "email", "pageviews"];
|
|
1084
1091
|
function captureModel(candidates) {
|
|
1085
1092
|
const estimates = candidates.filter((e) => e !== null).sort((a, b) => TRUST.indexOf(a.basis) - TRUST.indexOf(b.basis));
|
|
@@ -1088,9 +1095,8 @@ function captureModel(candidates) {
|
|
|
1088
1095
|
}
|
|
1089
1096
|
const rates = estimates.map((e) => e.rate);
|
|
1090
1097
|
const rate = estimates[0].rate;
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
return { estimates, rate, low, high, discrepancy: describeDisagreement(estimates) };
|
|
1098
|
+
const spread = estimates.length > 1 ? { low: Math.min(...rates), high: Math.max(...rates) } : samplingInterval(estimates[0]);
|
|
1099
|
+
return { estimates, rate, low: spread.low, high: spread.high, discrepancy: describeDisagreement(estimates) };
|
|
1094
1100
|
}
|
|
1095
1101
|
function describeDisagreement(estimates) {
|
|
1096
1102
|
if (estimates.length < 2) return null;
|
|
@@ -1147,7 +1153,7 @@ function interpret(ga4Views, vercelViews, shortfall, consent) {
|
|
|
1147
1153
|
return `${base} How much of that is consent refusal cannot be measured until the consent_granted event is instrumented, so the difference is currently unexplained rather than attributed.`;
|
|
1148
1154
|
}
|
|
1149
1155
|
const consentPercent = Math.round((1 - consent.value / 100) * 100);
|
|
1150
|
-
return `${base}
|
|
1156
|
+
return `${base} Of the visitors GA4 did record, at least ${consentPercent}% declined analytics consent. That is measured inside GA4's own sample, so it does not explain the visitors missing from it \u2014 anyone who refused before being counted is in neither figure. The gap itself stays unexplained; ad-blocking and bot filtering are plausible and are not separately measurable.`;
|
|
1151
1157
|
}
|
|
1152
1158
|
function calendarDays(start, end) {
|
|
1153
1159
|
const first = Date.parse(`${start}T00:00:00Z`);
|
|
@@ -1316,9 +1322,18 @@ async function measurementHealth(input) {
|
|
|
1316
1322
|
revenue = unavailable("source_error");
|
|
1317
1323
|
}
|
|
1318
1324
|
}
|
|
1319
|
-
const shortfallRatio = ga4Pageviews.status !== "unavailable" && vercelPageviews.status !== "unavailable" && vercelPageviews.value > 0 ? (vercelPageviews.value - ga4Pageviews.value) / vercelPageviews.value : null;
|
|
1320
1325
|
const vercelDates = Object.keys(vercelByDate);
|
|
1321
|
-
const vercelIsDaily = vercelDates.length
|
|
1326
|
+
const vercelIsDaily = vercelDates.length > 0 && vercelDates.length > daysInRange(range) * 0.7;
|
|
1327
|
+
const unsettled = new Set(provisionalDates(range));
|
|
1328
|
+
const settledGa4 = [...ga4ByDate.entries()].filter(([date]) => !unsettled.has(date));
|
|
1329
|
+
const settledVercel = vercelIsDaily ? Object.entries(vercelByDate).filter(([date]) => !unsettled.has(date)) : [];
|
|
1330
|
+
const settledShortfall = (() => {
|
|
1331
|
+
if (settledGa4.length === 0 || settledVercel.length === 0) return null;
|
|
1332
|
+
const ga4Total = settledGa4.reduce((sum, [, value]) => sum + value, 0);
|
|
1333
|
+
const vercelTotal = settledVercel.reduce((sum, [, value]) => sum + (value ?? 0), 0);
|
|
1334
|
+
return vercelTotal > 0 ? (vercelTotal - ga4Total) / vercelTotal : null;
|
|
1335
|
+
})();
|
|
1336
|
+
const shortfallRatio = settledShortfall !== null ? settledShortfall : ga4Pageviews.status !== "unavailable" && vercelPageviews.status !== "unavailable" && vercelPageviews.value > 0 ? (vercelPageviews.value - ga4Pageviews.value) / vercelPageviews.value : null;
|
|
1322
1337
|
const seriesDates = vercelIsDaily ? Array.from(/* @__PURE__ */ new Set([...ga4ByDate.keys(), ...vercelDates])) : Array.from(ga4ByDate.keys());
|
|
1323
1338
|
const daily = seriesDates.sort().map((date) => ({
|
|
1324
1339
|
date,
|