@gscdump/analysis 0.19.3 → 0.19.4
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/analyzer/index.mjs +156 -0
- package/dist/default-registry.mjs +156 -0
- package/dist/index.mjs +156 -0
- package/dist/query/index.d.mts +16 -1
- package/dist/query/index.mjs +137 -1
- package/package.json +4 -4
package/dist/analyzer/index.mjs
CHANGED
|
@@ -2115,6 +2115,142 @@ function buildDataQueryPlan(params, options) {
|
|
|
2115
2115
|
extraQueries
|
|
2116
2116
|
};
|
|
2117
2117
|
}
|
|
2118
|
+
function totalsState(state) {
|
|
2119
|
+
return {
|
|
2120
|
+
...state,
|
|
2121
|
+
dimensions: [],
|
|
2122
|
+
orderBy: void 0,
|
|
2123
|
+
rowLimit: 1
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
function readTotals(row) {
|
|
2127
|
+
return {
|
|
2128
|
+
clicks: Number(row?.clicks ?? 0),
|
|
2129
|
+
impressions: Number(row?.impressions ?? 0),
|
|
2130
|
+
ctr: Number(row?.ctr ?? 0),
|
|
2131
|
+
position: Number(row?.position ?? 0)
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
function buildDataQueryRows(params) {
|
|
2135
|
+
const state = requireBuilderState(params.q, "data-query");
|
|
2136
|
+
if (state.dimensions.includes("date")) throw new Error("data-query: date dimension not supported; use data-detail");
|
|
2137
|
+
const queries = {
|
|
2138
|
+
main: state,
|
|
2139
|
+
totals: totalsState(state)
|
|
2140
|
+
};
|
|
2141
|
+
const prev = optionalBuilderState(params.qc, "data-query", "qc");
|
|
2142
|
+
if (prev) {
|
|
2143
|
+
queries.prevMain = prev;
|
|
2144
|
+
queries.prevTotals = totalsState(prev);
|
|
2145
|
+
}
|
|
2146
|
+
return queries;
|
|
2147
|
+
}
|
|
2148
|
+
function shapeDataQueryRowResults(rowMap, params) {
|
|
2149
|
+
const main = (rowMap.main ?? []).map((r) => coerceNumericCols(r));
|
|
2150
|
+
const totals = readTotals(rowMap.totals?.[0]);
|
|
2151
|
+
if (params.qc == null) return {
|
|
2152
|
+
results: main,
|
|
2153
|
+
meta: {
|
|
2154
|
+
totalCount: main.length,
|
|
2155
|
+
totals
|
|
2156
|
+
}
|
|
2157
|
+
};
|
|
2158
|
+
const state = requireBuilderState(params.q, "data-query");
|
|
2159
|
+
const dims = state.dimensions;
|
|
2160
|
+
const keyOf = (row) => dims.map((d) => String(row[d] ?? "")).join("\0");
|
|
2161
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
2162
|
+
for (const r of rowMap.prevMain ?? []) prevByKey.set(keyOf(r), r);
|
|
2163
|
+
const filter = params.comparisonFilter;
|
|
2164
|
+
const merged = [];
|
|
2165
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2166
|
+
for (const cur of main) {
|
|
2167
|
+
const key = keyOf(cur);
|
|
2168
|
+
seen.add(key);
|
|
2169
|
+
const prev = prevByKey.get(key);
|
|
2170
|
+
const row = {
|
|
2171
|
+
...cur,
|
|
2172
|
+
prevClicks: Number(prev?.clicks ?? 0),
|
|
2173
|
+
prevImpressions: Number(prev?.impressions ?? 0),
|
|
2174
|
+
prevCtr: Number(prev?.ctr ?? 0),
|
|
2175
|
+
prevPosition: Number(prev?.position ?? 0)
|
|
2176
|
+
};
|
|
2177
|
+
if (passesComparisonFilter(filter, {
|
|
2178
|
+
isNew: !prev,
|
|
2179
|
+
isLost: false,
|
|
2180
|
+
clicksChange: Number(cur.clicks ?? 0) - Number(prev?.clicks ?? 0)
|
|
2181
|
+
})) merged.push(row);
|
|
2182
|
+
}
|
|
2183
|
+
for (const prev of rowMap.prevMain ?? []) {
|
|
2184
|
+
const p = prev;
|
|
2185
|
+
const key = keyOf(p);
|
|
2186
|
+
if (seen.has(key)) continue;
|
|
2187
|
+
const row = {
|
|
2188
|
+
...p,
|
|
2189
|
+
clicks: 0,
|
|
2190
|
+
impressions: 0,
|
|
2191
|
+
ctr: 0,
|
|
2192
|
+
position: 0,
|
|
2193
|
+
prevClicks: Number(p.clicks ?? 0),
|
|
2194
|
+
prevImpressions: Number(p.impressions ?? 0),
|
|
2195
|
+
prevCtr: Number(p.ctr ?? 0),
|
|
2196
|
+
prevPosition: Number(p.position ?? 0)
|
|
2197
|
+
};
|
|
2198
|
+
if (passesComparisonFilter(filter, {
|
|
2199
|
+
isNew: false,
|
|
2200
|
+
isLost: true,
|
|
2201
|
+
clicksChange: -Number(p.clicks ?? 0)
|
|
2202
|
+
})) merged.push(row);
|
|
2203
|
+
}
|
|
2204
|
+
if (state.orderBy) {
|
|
2205
|
+
const { column, dir } = state.orderBy;
|
|
2206
|
+
merged.sort((a, b) => {
|
|
2207
|
+
const av = Number(a[column]) || 0;
|
|
2208
|
+
const bv = Number(b[column]) || 0;
|
|
2209
|
+
return dir === "asc" ? av - bv : bv - av;
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
return {
|
|
2213
|
+
results: merged.map((r) => coerceNumericCols(r)),
|
|
2214
|
+
meta: {
|
|
2215
|
+
totalCount: merged.length,
|
|
2216
|
+
totals
|
|
2217
|
+
}
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
function passesComparisonFilter(filter, ctx) {
|
|
2221
|
+
switch (filter) {
|
|
2222
|
+
case "new": return ctx.isNew;
|
|
2223
|
+
case "lost": return ctx.isLost;
|
|
2224
|
+
case "improving": return ctx.clicksChange > 0;
|
|
2225
|
+
case "declining": return ctx.clicksChange < 0;
|
|
2226
|
+
default: return true;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
function buildDataDetailRows(params) {
|
|
2230
|
+
const state = requireBuilderState(params.q, "data-detail");
|
|
2231
|
+
if (!state.dimensions.includes("date")) throw new Error("data-detail: `date` dimension is required");
|
|
2232
|
+
const queries = {
|
|
2233
|
+
main: state,
|
|
2234
|
+
totals: totalsState(state)
|
|
2235
|
+
};
|
|
2236
|
+
const prev = optionalBuilderState(params.qc, "data-detail", "qc");
|
|
2237
|
+
if (prev) queries.prevTotals = totalsState(prev);
|
|
2238
|
+
return queries;
|
|
2239
|
+
}
|
|
2240
|
+
function shapeDataDetailRowResults(rowMap, params) {
|
|
2241
|
+
const { startDate, endDate } = extractDateRange(requireBuilderState(params.q, "data-detail").filter);
|
|
2242
|
+
const coerced = (rowMap.main ?? []).map((r) => coerceNumericCols(r));
|
|
2243
|
+
const daily = startDate && endDate ? padTimeseries(coerced, {
|
|
2244
|
+
startDate,
|
|
2245
|
+
endDate
|
|
2246
|
+
}) : coerced;
|
|
2247
|
+
const meta = { totals: readTotals(rowMap.totals?.[0]) };
|
|
2248
|
+
if (rowMap.prevTotals) meta.previousTotals = readTotals(rowMap.prevTotals[0]);
|
|
2249
|
+
return {
|
|
2250
|
+
results: daily,
|
|
2251
|
+
meta
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2118
2254
|
function shapeDataQueryRows(rows, params, extras) {
|
|
2119
2255
|
return shapeDataQuery(rows, extras, { hasPrev: params.qc != null });
|
|
2120
2256
|
}
|
|
@@ -2200,6 +2336,16 @@ const dataDetailAnalyzer = defineAnalyzer({
|
|
|
2200
2336
|
results,
|
|
2201
2337
|
meta
|
|
2202
2338
|
};
|
|
2339
|
+
},
|
|
2340
|
+
buildRows(params) {
|
|
2341
|
+
return buildDataDetailRows(params);
|
|
2342
|
+
},
|
|
2343
|
+
reduceRows(rows, params) {
|
|
2344
|
+
const { results, meta } = shapeDataDetailRowResults(Array.isArray(rows) ? {} : rows, params);
|
|
2345
|
+
return {
|
|
2346
|
+
results,
|
|
2347
|
+
meta
|
|
2348
|
+
};
|
|
2203
2349
|
}
|
|
2204
2350
|
});
|
|
2205
2351
|
const dataQueryAnalyzer = defineAnalyzer({
|
|
@@ -2230,6 +2376,16 @@ const dataQueryAnalyzer = defineAnalyzer({
|
|
|
2230
2376
|
results,
|
|
2231
2377
|
meta
|
|
2232
2378
|
};
|
|
2379
|
+
},
|
|
2380
|
+
buildRows(params) {
|
|
2381
|
+
return buildDataQueryRows(params);
|
|
2382
|
+
},
|
|
2383
|
+
reduceRows(rows, params) {
|
|
2384
|
+
const { results, meta } = shapeDataQueryRowResults(Array.isArray(rows) ? {} : rows, params);
|
|
2385
|
+
return {
|
|
2386
|
+
results,
|
|
2387
|
+
meta
|
|
2388
|
+
};
|
|
2233
2389
|
}
|
|
2234
2390
|
});
|
|
2235
2391
|
const sortResults$1 = createMetricSorter("lostClicks", {
|
|
@@ -2115,6 +2115,142 @@ function buildDataQueryPlan(params, options) {
|
|
|
2115
2115
|
extraQueries
|
|
2116
2116
|
};
|
|
2117
2117
|
}
|
|
2118
|
+
function totalsState(state) {
|
|
2119
|
+
return {
|
|
2120
|
+
...state,
|
|
2121
|
+
dimensions: [],
|
|
2122
|
+
orderBy: void 0,
|
|
2123
|
+
rowLimit: 1
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
function readTotals(row) {
|
|
2127
|
+
return {
|
|
2128
|
+
clicks: Number(row?.clicks ?? 0),
|
|
2129
|
+
impressions: Number(row?.impressions ?? 0),
|
|
2130
|
+
ctr: Number(row?.ctr ?? 0),
|
|
2131
|
+
position: Number(row?.position ?? 0)
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
function buildDataQueryRows(params) {
|
|
2135
|
+
const state = requireBuilderState(params.q, "data-query");
|
|
2136
|
+
if (state.dimensions.includes("date")) throw new Error("data-query: date dimension not supported; use data-detail");
|
|
2137
|
+
const queries = {
|
|
2138
|
+
main: state,
|
|
2139
|
+
totals: totalsState(state)
|
|
2140
|
+
};
|
|
2141
|
+
const prev = optionalBuilderState(params.qc, "data-query", "qc");
|
|
2142
|
+
if (prev) {
|
|
2143
|
+
queries.prevMain = prev;
|
|
2144
|
+
queries.prevTotals = totalsState(prev);
|
|
2145
|
+
}
|
|
2146
|
+
return queries;
|
|
2147
|
+
}
|
|
2148
|
+
function shapeDataQueryRowResults(rowMap, params) {
|
|
2149
|
+
const main = (rowMap.main ?? []).map((r) => coerceNumericCols(r));
|
|
2150
|
+
const totals = readTotals(rowMap.totals?.[0]);
|
|
2151
|
+
if (params.qc == null) return {
|
|
2152
|
+
results: main,
|
|
2153
|
+
meta: {
|
|
2154
|
+
totalCount: main.length,
|
|
2155
|
+
totals
|
|
2156
|
+
}
|
|
2157
|
+
};
|
|
2158
|
+
const state = requireBuilderState(params.q, "data-query");
|
|
2159
|
+
const dims = state.dimensions;
|
|
2160
|
+
const keyOf = (row) => dims.map((d) => String(row[d] ?? "")).join("\0");
|
|
2161
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
2162
|
+
for (const r of rowMap.prevMain ?? []) prevByKey.set(keyOf(r), r);
|
|
2163
|
+
const filter = params.comparisonFilter;
|
|
2164
|
+
const merged = [];
|
|
2165
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2166
|
+
for (const cur of main) {
|
|
2167
|
+
const key = keyOf(cur);
|
|
2168
|
+
seen.add(key);
|
|
2169
|
+
const prev = prevByKey.get(key);
|
|
2170
|
+
const row = {
|
|
2171
|
+
...cur,
|
|
2172
|
+
prevClicks: Number(prev?.clicks ?? 0),
|
|
2173
|
+
prevImpressions: Number(prev?.impressions ?? 0),
|
|
2174
|
+
prevCtr: Number(prev?.ctr ?? 0),
|
|
2175
|
+
prevPosition: Number(prev?.position ?? 0)
|
|
2176
|
+
};
|
|
2177
|
+
if (passesComparisonFilter(filter, {
|
|
2178
|
+
isNew: !prev,
|
|
2179
|
+
isLost: false,
|
|
2180
|
+
clicksChange: Number(cur.clicks ?? 0) - Number(prev?.clicks ?? 0)
|
|
2181
|
+
})) merged.push(row);
|
|
2182
|
+
}
|
|
2183
|
+
for (const prev of rowMap.prevMain ?? []) {
|
|
2184
|
+
const p = prev;
|
|
2185
|
+
const key = keyOf(p);
|
|
2186
|
+
if (seen.has(key)) continue;
|
|
2187
|
+
const row = {
|
|
2188
|
+
...p,
|
|
2189
|
+
clicks: 0,
|
|
2190
|
+
impressions: 0,
|
|
2191
|
+
ctr: 0,
|
|
2192
|
+
position: 0,
|
|
2193
|
+
prevClicks: Number(p.clicks ?? 0),
|
|
2194
|
+
prevImpressions: Number(p.impressions ?? 0),
|
|
2195
|
+
prevCtr: Number(p.ctr ?? 0),
|
|
2196
|
+
prevPosition: Number(p.position ?? 0)
|
|
2197
|
+
};
|
|
2198
|
+
if (passesComparisonFilter(filter, {
|
|
2199
|
+
isNew: false,
|
|
2200
|
+
isLost: true,
|
|
2201
|
+
clicksChange: -Number(p.clicks ?? 0)
|
|
2202
|
+
})) merged.push(row);
|
|
2203
|
+
}
|
|
2204
|
+
if (state.orderBy) {
|
|
2205
|
+
const { column, dir } = state.orderBy;
|
|
2206
|
+
merged.sort((a, b) => {
|
|
2207
|
+
const av = Number(a[column]) || 0;
|
|
2208
|
+
const bv = Number(b[column]) || 0;
|
|
2209
|
+
return dir === "asc" ? av - bv : bv - av;
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
return {
|
|
2213
|
+
results: merged.map((r) => coerceNumericCols(r)),
|
|
2214
|
+
meta: {
|
|
2215
|
+
totalCount: merged.length,
|
|
2216
|
+
totals
|
|
2217
|
+
}
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
function passesComparisonFilter(filter, ctx) {
|
|
2221
|
+
switch (filter) {
|
|
2222
|
+
case "new": return ctx.isNew;
|
|
2223
|
+
case "lost": return ctx.isLost;
|
|
2224
|
+
case "improving": return ctx.clicksChange > 0;
|
|
2225
|
+
case "declining": return ctx.clicksChange < 0;
|
|
2226
|
+
default: return true;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
function buildDataDetailRows(params) {
|
|
2230
|
+
const state = requireBuilderState(params.q, "data-detail");
|
|
2231
|
+
if (!state.dimensions.includes("date")) throw new Error("data-detail: `date` dimension is required");
|
|
2232
|
+
const queries = {
|
|
2233
|
+
main: state,
|
|
2234
|
+
totals: totalsState(state)
|
|
2235
|
+
};
|
|
2236
|
+
const prev = optionalBuilderState(params.qc, "data-detail", "qc");
|
|
2237
|
+
if (prev) queries.prevTotals = totalsState(prev);
|
|
2238
|
+
return queries;
|
|
2239
|
+
}
|
|
2240
|
+
function shapeDataDetailRowResults(rowMap, params) {
|
|
2241
|
+
const { startDate, endDate } = extractDateRange(requireBuilderState(params.q, "data-detail").filter);
|
|
2242
|
+
const coerced = (rowMap.main ?? []).map((r) => coerceNumericCols(r));
|
|
2243
|
+
const daily = startDate && endDate ? padTimeseries(coerced, {
|
|
2244
|
+
startDate,
|
|
2245
|
+
endDate
|
|
2246
|
+
}) : coerced;
|
|
2247
|
+
const meta = { totals: readTotals(rowMap.totals?.[0]) };
|
|
2248
|
+
if (rowMap.prevTotals) meta.previousTotals = readTotals(rowMap.prevTotals[0]);
|
|
2249
|
+
return {
|
|
2250
|
+
results: daily,
|
|
2251
|
+
meta
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2118
2254
|
function shapeDataQueryRows(rows, params, extras) {
|
|
2119
2255
|
return shapeDataQuery(rows, extras, { hasPrev: params.qc != null });
|
|
2120
2256
|
}
|
|
@@ -2200,6 +2336,16 @@ const dataDetailAnalyzer = defineAnalyzer({
|
|
|
2200
2336
|
results,
|
|
2201
2337
|
meta
|
|
2202
2338
|
};
|
|
2339
|
+
},
|
|
2340
|
+
buildRows(params) {
|
|
2341
|
+
return buildDataDetailRows(params);
|
|
2342
|
+
},
|
|
2343
|
+
reduceRows(rows, params) {
|
|
2344
|
+
const { results, meta } = shapeDataDetailRowResults(Array.isArray(rows) ? {} : rows, params);
|
|
2345
|
+
return {
|
|
2346
|
+
results,
|
|
2347
|
+
meta
|
|
2348
|
+
};
|
|
2203
2349
|
}
|
|
2204
2350
|
});
|
|
2205
2351
|
const dataQueryAnalyzer = defineAnalyzer({
|
|
@@ -2230,6 +2376,16 @@ const dataQueryAnalyzer = defineAnalyzer({
|
|
|
2230
2376
|
results,
|
|
2231
2377
|
meta
|
|
2232
2378
|
};
|
|
2379
|
+
},
|
|
2380
|
+
buildRows(params) {
|
|
2381
|
+
return buildDataQueryRows(params);
|
|
2382
|
+
},
|
|
2383
|
+
reduceRows(rows, params) {
|
|
2384
|
+
const { results, meta } = shapeDataQueryRowResults(Array.isArray(rows) ? {} : rows, params);
|
|
2385
|
+
return {
|
|
2386
|
+
results,
|
|
2387
|
+
meta
|
|
2388
|
+
};
|
|
2233
2389
|
}
|
|
2234
2390
|
});
|
|
2235
2391
|
const sortResults$1 = createMetricSorter("lostClicks", {
|
package/dist/index.mjs
CHANGED
|
@@ -2332,6 +2332,142 @@ function buildDataQueryPlan(params, options) {
|
|
|
2332
2332
|
extraQueries
|
|
2333
2333
|
};
|
|
2334
2334
|
}
|
|
2335
|
+
function totalsState(state) {
|
|
2336
|
+
return {
|
|
2337
|
+
...state,
|
|
2338
|
+
dimensions: [],
|
|
2339
|
+
orderBy: void 0,
|
|
2340
|
+
rowLimit: 1
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
function readTotals(row) {
|
|
2344
|
+
return {
|
|
2345
|
+
clicks: Number(row?.clicks ?? 0),
|
|
2346
|
+
impressions: Number(row?.impressions ?? 0),
|
|
2347
|
+
ctr: Number(row?.ctr ?? 0),
|
|
2348
|
+
position: Number(row?.position ?? 0)
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
function buildDataQueryRows(params) {
|
|
2352
|
+
const state = requireBuilderState(params.q, "data-query");
|
|
2353
|
+
if (state.dimensions.includes("date")) throw new Error("data-query: date dimension not supported; use data-detail");
|
|
2354
|
+
const queries = {
|
|
2355
|
+
main: state,
|
|
2356
|
+
totals: totalsState(state)
|
|
2357
|
+
};
|
|
2358
|
+
const prev = optionalBuilderState(params.qc, "data-query", "qc");
|
|
2359
|
+
if (prev) {
|
|
2360
|
+
queries.prevMain = prev;
|
|
2361
|
+
queries.prevTotals = totalsState(prev);
|
|
2362
|
+
}
|
|
2363
|
+
return queries;
|
|
2364
|
+
}
|
|
2365
|
+
function shapeDataQueryRowResults(rowMap, params) {
|
|
2366
|
+
const main = (rowMap.main ?? []).map((r) => coerceNumericCols(r));
|
|
2367
|
+
const totals = readTotals(rowMap.totals?.[0]);
|
|
2368
|
+
if (params.qc == null) return {
|
|
2369
|
+
results: main,
|
|
2370
|
+
meta: {
|
|
2371
|
+
totalCount: main.length,
|
|
2372
|
+
totals
|
|
2373
|
+
}
|
|
2374
|
+
};
|
|
2375
|
+
const state = requireBuilderState(params.q, "data-query");
|
|
2376
|
+
const dims = state.dimensions;
|
|
2377
|
+
const keyOf = (row) => dims.map((d) => String(row[d] ?? "")).join("\0");
|
|
2378
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
2379
|
+
for (const r of rowMap.prevMain ?? []) prevByKey.set(keyOf(r), r);
|
|
2380
|
+
const filter = params.comparisonFilter;
|
|
2381
|
+
const merged = [];
|
|
2382
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2383
|
+
for (const cur of main) {
|
|
2384
|
+
const key = keyOf(cur);
|
|
2385
|
+
seen.add(key);
|
|
2386
|
+
const prev = prevByKey.get(key);
|
|
2387
|
+
const row = {
|
|
2388
|
+
...cur,
|
|
2389
|
+
prevClicks: Number(prev?.clicks ?? 0),
|
|
2390
|
+
prevImpressions: Number(prev?.impressions ?? 0),
|
|
2391
|
+
prevCtr: Number(prev?.ctr ?? 0),
|
|
2392
|
+
prevPosition: Number(prev?.position ?? 0)
|
|
2393
|
+
};
|
|
2394
|
+
if (passesComparisonFilter(filter, {
|
|
2395
|
+
isNew: !prev,
|
|
2396
|
+
isLost: false,
|
|
2397
|
+
clicksChange: Number(cur.clicks ?? 0) - Number(prev?.clicks ?? 0)
|
|
2398
|
+
})) merged.push(row);
|
|
2399
|
+
}
|
|
2400
|
+
for (const prev of rowMap.prevMain ?? []) {
|
|
2401
|
+
const p = prev;
|
|
2402
|
+
const key = keyOf(p);
|
|
2403
|
+
if (seen.has(key)) continue;
|
|
2404
|
+
const row = {
|
|
2405
|
+
...p,
|
|
2406
|
+
clicks: 0,
|
|
2407
|
+
impressions: 0,
|
|
2408
|
+
ctr: 0,
|
|
2409
|
+
position: 0,
|
|
2410
|
+
prevClicks: Number(p.clicks ?? 0),
|
|
2411
|
+
prevImpressions: Number(p.impressions ?? 0),
|
|
2412
|
+
prevCtr: Number(p.ctr ?? 0),
|
|
2413
|
+
prevPosition: Number(p.position ?? 0)
|
|
2414
|
+
};
|
|
2415
|
+
if (passesComparisonFilter(filter, {
|
|
2416
|
+
isNew: false,
|
|
2417
|
+
isLost: true,
|
|
2418
|
+
clicksChange: -Number(p.clicks ?? 0)
|
|
2419
|
+
})) merged.push(row);
|
|
2420
|
+
}
|
|
2421
|
+
if (state.orderBy) {
|
|
2422
|
+
const { column, dir } = state.orderBy;
|
|
2423
|
+
merged.sort((a, b) => {
|
|
2424
|
+
const av = Number(a[column]) || 0;
|
|
2425
|
+
const bv = Number(b[column]) || 0;
|
|
2426
|
+
return dir === "asc" ? av - bv : bv - av;
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
return {
|
|
2430
|
+
results: merged.map((r) => coerceNumericCols(r)),
|
|
2431
|
+
meta: {
|
|
2432
|
+
totalCount: merged.length,
|
|
2433
|
+
totals
|
|
2434
|
+
}
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
function passesComparisonFilter(filter, ctx) {
|
|
2438
|
+
switch (filter) {
|
|
2439
|
+
case "new": return ctx.isNew;
|
|
2440
|
+
case "lost": return ctx.isLost;
|
|
2441
|
+
case "improving": return ctx.clicksChange > 0;
|
|
2442
|
+
case "declining": return ctx.clicksChange < 0;
|
|
2443
|
+
default: return true;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
function buildDataDetailRows(params) {
|
|
2447
|
+
const state = requireBuilderState(params.q, "data-detail");
|
|
2448
|
+
if (!state.dimensions.includes("date")) throw new Error("data-detail: `date` dimension is required");
|
|
2449
|
+
const queries = {
|
|
2450
|
+
main: state,
|
|
2451
|
+
totals: totalsState(state)
|
|
2452
|
+
};
|
|
2453
|
+
const prev = optionalBuilderState(params.qc, "data-detail", "qc");
|
|
2454
|
+
if (prev) queries.prevTotals = totalsState(prev);
|
|
2455
|
+
return queries;
|
|
2456
|
+
}
|
|
2457
|
+
function shapeDataDetailRowResults(rowMap, params) {
|
|
2458
|
+
const { startDate, endDate } = extractDateRange(requireBuilderState(params.q, "data-detail").filter);
|
|
2459
|
+
const coerced = (rowMap.main ?? []).map((r) => coerceNumericCols(r));
|
|
2460
|
+
const daily = startDate && endDate ? padTimeseries$1(coerced, {
|
|
2461
|
+
startDate,
|
|
2462
|
+
endDate
|
|
2463
|
+
}) : coerced;
|
|
2464
|
+
const meta = { totals: readTotals(rowMap.totals?.[0]) };
|
|
2465
|
+
if (rowMap.prevTotals) meta.previousTotals = readTotals(rowMap.prevTotals[0]);
|
|
2466
|
+
return {
|
|
2467
|
+
results: daily,
|
|
2468
|
+
meta
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2335
2471
|
function shapeDataQueryRows(rows, params, extras) {
|
|
2336
2472
|
return shapeDataQuery(rows, extras, { hasPrev: params.qc != null });
|
|
2337
2473
|
}
|
|
@@ -2566,6 +2702,16 @@ const dataDetailAnalyzer = defineAnalyzer$1({
|
|
|
2566
2702
|
results,
|
|
2567
2703
|
meta
|
|
2568
2704
|
};
|
|
2705
|
+
},
|
|
2706
|
+
buildRows(params) {
|
|
2707
|
+
return buildDataDetailRows(params);
|
|
2708
|
+
},
|
|
2709
|
+
reduceRows(rows, params) {
|
|
2710
|
+
const { results, meta } = shapeDataDetailRowResults(Array.isArray(rows) ? {} : rows, params);
|
|
2711
|
+
return {
|
|
2712
|
+
results,
|
|
2713
|
+
meta
|
|
2714
|
+
};
|
|
2569
2715
|
}
|
|
2570
2716
|
});
|
|
2571
2717
|
const dataQueryAnalyzer = defineAnalyzer$1({
|
|
@@ -2596,6 +2742,16 @@ const dataQueryAnalyzer = defineAnalyzer$1({
|
|
|
2596
2742
|
results,
|
|
2597
2743
|
meta
|
|
2598
2744
|
};
|
|
2745
|
+
},
|
|
2746
|
+
buildRows(params) {
|
|
2747
|
+
return buildDataQueryRows(params);
|
|
2748
|
+
},
|
|
2749
|
+
reduceRows(rows, params) {
|
|
2750
|
+
const { results, meta } = shapeDataQueryRowResults(Array.isArray(rows) ? {} : rows, params);
|
|
2751
|
+
return {
|
|
2752
|
+
results,
|
|
2753
|
+
meta
|
|
2754
|
+
};
|
|
2599
2755
|
}
|
|
2600
2756
|
});
|
|
2601
2757
|
const sortResults$1 = createMetricSorter("lostClicks", {
|
package/dist/query/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ResolverOptions } from "@gscdump/engine/resolver";
|
|
2
|
+
import { BuilderState } from "gscdump/query";
|
|
2
3
|
import { AnalysisParams } from "@gscdump/engine/analysis-types";
|
|
3
4
|
import { Row } from "@gscdump/engine/contracts";
|
|
4
5
|
interface QueryAnalyzerExtraQuery {
|
|
@@ -13,6 +14,20 @@ interface QueryAnalyzerPlan<TK extends string = string> {
|
|
|
13
14
|
extraQueries?: QueryAnalyzerExtraQuery[];
|
|
14
15
|
}
|
|
15
16
|
declare function buildDataQueryPlan<TK extends string>(params: AnalysisParams, options: ResolverOptions<TK>): QueryAnalyzerPlan<TK>;
|
|
17
|
+
/** Row-query plan for `data-query`. Comparison (`qc`) joins two periods. */
|
|
18
|
+
declare function buildDataQueryRows(params: AnalysisParams): Record<string, BuilderState>;
|
|
19
|
+
/** Post-processing for `data-query` row results. Mirrors `shapeDataQuery`. */
|
|
20
|
+
declare function shapeDataQueryRowResults(rowMap: Record<string, Row[]>, params: AnalysisParams): {
|
|
21
|
+
results: Row[];
|
|
22
|
+
meta: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
/** Row-query plan for `data-detail`. `qc` adds a previous-period totals query. */
|
|
25
|
+
declare function buildDataDetailRows(params: AnalysisParams): Record<string, BuilderState>;
|
|
26
|
+
/** Post-processing for `data-detail` row results. Mirrors `shapeDataDetailRows`. */
|
|
27
|
+
declare function shapeDataDetailRowResults(rowMap: Record<string, Row[]>, params: AnalysisParams): {
|
|
28
|
+
results: Row[];
|
|
29
|
+
meta: Record<string, unknown>;
|
|
30
|
+
};
|
|
16
31
|
/**
|
|
17
32
|
* Pure post-processing for `data-query` rows. Adapter-free so reducers can
|
|
18
33
|
* call it without re-running the SQL plan.
|
|
@@ -35,4 +50,4 @@ declare function shapeDataDetailRows(rows: Row[], params: AnalysisParams, extras
|
|
|
35
50
|
* Idempotent: `normalizeQuery(normalizeQuery(q)) === normalizeQuery(q)`.
|
|
36
51
|
*/
|
|
37
52
|
declare function normalizeQuery(query: string): string;
|
|
38
|
-
export { type QueryAnalyzerExtraQuery, type QueryAnalyzerPlan, buildDataDetailPlan, buildDataQueryPlan, normalizeQuery, shapeDataDetailRows, shapeDataQueryRows };
|
|
53
|
+
export { type QueryAnalyzerExtraQuery, type QueryAnalyzerPlan, buildDataDetailPlan, buildDataDetailRows, buildDataQueryPlan, buildDataQueryRows, normalizeQuery, shapeDataDetailRowResults, shapeDataDetailRows, shapeDataQueryRowResults, shapeDataQueryRows };
|
package/dist/query/index.mjs
CHANGED
|
@@ -99,6 +99,142 @@ function buildDataQueryPlan(params, options) {
|
|
|
99
99
|
extraQueries
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
|
+
function totalsState(state) {
|
|
103
|
+
return {
|
|
104
|
+
...state,
|
|
105
|
+
dimensions: [],
|
|
106
|
+
orderBy: void 0,
|
|
107
|
+
rowLimit: 1
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function readTotals(row) {
|
|
111
|
+
return {
|
|
112
|
+
clicks: Number(row?.clicks ?? 0),
|
|
113
|
+
impressions: Number(row?.impressions ?? 0),
|
|
114
|
+
ctr: Number(row?.ctr ?? 0),
|
|
115
|
+
position: Number(row?.position ?? 0)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function buildDataQueryRows(params) {
|
|
119
|
+
const state = requireBuilderState(params.q, "data-query");
|
|
120
|
+
if (state.dimensions.includes("date")) throw new Error("data-query: date dimension not supported; use data-detail");
|
|
121
|
+
const queries = {
|
|
122
|
+
main: state,
|
|
123
|
+
totals: totalsState(state)
|
|
124
|
+
};
|
|
125
|
+
const prev = optionalBuilderState(params.qc, "data-query", "qc");
|
|
126
|
+
if (prev) {
|
|
127
|
+
queries.prevMain = prev;
|
|
128
|
+
queries.prevTotals = totalsState(prev);
|
|
129
|
+
}
|
|
130
|
+
return queries;
|
|
131
|
+
}
|
|
132
|
+
function shapeDataQueryRowResults(rowMap, params) {
|
|
133
|
+
const main = (rowMap.main ?? []).map((r) => coerceNumericCols(r));
|
|
134
|
+
const totals = readTotals(rowMap.totals?.[0]);
|
|
135
|
+
if (params.qc == null) return {
|
|
136
|
+
results: main,
|
|
137
|
+
meta: {
|
|
138
|
+
totalCount: main.length,
|
|
139
|
+
totals
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const state = requireBuilderState(params.q, "data-query");
|
|
143
|
+
const dims = state.dimensions;
|
|
144
|
+
const keyOf = (row) => dims.map((d) => String(row[d] ?? "")).join("\0");
|
|
145
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
146
|
+
for (const r of rowMap.prevMain ?? []) prevByKey.set(keyOf(r), r);
|
|
147
|
+
const filter = params.comparisonFilter;
|
|
148
|
+
const merged = [];
|
|
149
|
+
const seen = /* @__PURE__ */ new Set();
|
|
150
|
+
for (const cur of main) {
|
|
151
|
+
const key = keyOf(cur);
|
|
152
|
+
seen.add(key);
|
|
153
|
+
const prev = prevByKey.get(key);
|
|
154
|
+
const row = {
|
|
155
|
+
...cur,
|
|
156
|
+
prevClicks: Number(prev?.clicks ?? 0),
|
|
157
|
+
prevImpressions: Number(prev?.impressions ?? 0),
|
|
158
|
+
prevCtr: Number(prev?.ctr ?? 0),
|
|
159
|
+
prevPosition: Number(prev?.position ?? 0)
|
|
160
|
+
};
|
|
161
|
+
if (passesComparisonFilter(filter, {
|
|
162
|
+
isNew: !prev,
|
|
163
|
+
isLost: false,
|
|
164
|
+
clicksChange: Number(cur.clicks ?? 0) - Number(prev?.clicks ?? 0)
|
|
165
|
+
})) merged.push(row);
|
|
166
|
+
}
|
|
167
|
+
for (const prev of rowMap.prevMain ?? []) {
|
|
168
|
+
const p = prev;
|
|
169
|
+
const key = keyOf(p);
|
|
170
|
+
if (seen.has(key)) continue;
|
|
171
|
+
const row = {
|
|
172
|
+
...p,
|
|
173
|
+
clicks: 0,
|
|
174
|
+
impressions: 0,
|
|
175
|
+
ctr: 0,
|
|
176
|
+
position: 0,
|
|
177
|
+
prevClicks: Number(p.clicks ?? 0),
|
|
178
|
+
prevImpressions: Number(p.impressions ?? 0),
|
|
179
|
+
prevCtr: Number(p.ctr ?? 0),
|
|
180
|
+
prevPosition: Number(p.position ?? 0)
|
|
181
|
+
};
|
|
182
|
+
if (passesComparisonFilter(filter, {
|
|
183
|
+
isNew: false,
|
|
184
|
+
isLost: true,
|
|
185
|
+
clicksChange: -Number(p.clicks ?? 0)
|
|
186
|
+
})) merged.push(row);
|
|
187
|
+
}
|
|
188
|
+
if (state.orderBy) {
|
|
189
|
+
const { column, dir } = state.orderBy;
|
|
190
|
+
merged.sort((a, b) => {
|
|
191
|
+
const av = Number(a[column]) || 0;
|
|
192
|
+
const bv = Number(b[column]) || 0;
|
|
193
|
+
return dir === "asc" ? av - bv : bv - av;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
results: merged.map((r) => coerceNumericCols(r)),
|
|
198
|
+
meta: {
|
|
199
|
+
totalCount: merged.length,
|
|
200
|
+
totals
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function passesComparisonFilter(filter, ctx) {
|
|
205
|
+
switch (filter) {
|
|
206
|
+
case "new": return ctx.isNew;
|
|
207
|
+
case "lost": return ctx.isLost;
|
|
208
|
+
case "improving": return ctx.clicksChange > 0;
|
|
209
|
+
case "declining": return ctx.clicksChange < 0;
|
|
210
|
+
default: return true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function buildDataDetailRows(params) {
|
|
214
|
+
const state = requireBuilderState(params.q, "data-detail");
|
|
215
|
+
if (!state.dimensions.includes("date")) throw new Error("data-detail: `date` dimension is required");
|
|
216
|
+
const queries = {
|
|
217
|
+
main: state,
|
|
218
|
+
totals: totalsState(state)
|
|
219
|
+
};
|
|
220
|
+
const prev = optionalBuilderState(params.qc, "data-detail", "qc");
|
|
221
|
+
if (prev) queries.prevTotals = totalsState(prev);
|
|
222
|
+
return queries;
|
|
223
|
+
}
|
|
224
|
+
function shapeDataDetailRowResults(rowMap, params) {
|
|
225
|
+
const { startDate, endDate } = extractDateRange(requireBuilderState(params.q, "data-detail").filter);
|
|
226
|
+
const coerced = (rowMap.main ?? []).map((r) => coerceNumericCols(r));
|
|
227
|
+
const daily = startDate && endDate ? padTimeseries(coerced, {
|
|
228
|
+
startDate,
|
|
229
|
+
endDate
|
|
230
|
+
}) : coerced;
|
|
231
|
+
const meta = { totals: readTotals(rowMap.totals?.[0]) };
|
|
232
|
+
if (rowMap.prevTotals) meta.previousTotals = readTotals(rowMap.prevTotals[0]);
|
|
233
|
+
return {
|
|
234
|
+
results: daily,
|
|
235
|
+
meta
|
|
236
|
+
};
|
|
237
|
+
}
|
|
102
238
|
function shapeDataQueryRows(rows, params, extras) {
|
|
103
239
|
return shapeDataQuery(rows, extras, { hasPrev: params.qc != null });
|
|
104
240
|
}
|
|
@@ -305,4 +441,4 @@ const WHITESPACE_RE = /\s+/g;
|
|
|
305
441
|
function normalizeQuery(query) {
|
|
306
442
|
return query.toLowerCase().replace(SEPARATOR_RE, " ").replace(WHITESPACE_RE, " ").trim().split(" ").filter(Boolean).map((token) => SYNONYMS[token] ?? token).filter(Boolean).map(depluralize).sort().join(" ");
|
|
307
443
|
}
|
|
308
|
-
export { buildDataDetailPlan, buildDataQueryPlan, normalizeQuery, shapeDataDetailRows, shapeDataQueryRows };
|
|
444
|
+
export { buildDataDetailPlan, buildDataDetailRows, buildDataQueryPlan, buildDataQueryRows, normalizeQuery, shapeDataDetailRowResults, shapeDataDetailRows, shapeDataQueryRowResults, shapeDataQueryRows };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/analysis",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.19.
|
|
4
|
+
"version": "0.19.4",
|
|
5
5
|
"description": "GSC analyzers — striking-distance, opportunity, movers, decay, brand, clustering, concentration, seasonality. Pure row-based + DuckDB-native.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
77
|
"drizzle-orm": "^0.45.2",
|
|
78
|
-
"@gscdump/engine": "0.19.
|
|
79
|
-
"@gscdump/engine-gsc-api": "0.19.
|
|
80
|
-
"gscdump": "0.19.
|
|
78
|
+
"@gscdump/engine": "0.19.4",
|
|
79
|
+
"@gscdump/engine-gsc-api": "0.19.4",
|
|
80
|
+
"gscdump": "0.19.4"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"vitest": "^4.1.6"
|