@gscdump/analysis 0.25.5 → 0.25.7
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.d.mts +7 -0
- package/dist/analyzer/index.mjs +127 -96
- package/dist/default-registry.mjs +121 -90
- package/dist/index.mjs +121 -90
- package/package.json +4 -4
|
@@ -461,6 +461,13 @@ interface MoversSeriesPoint {
|
|
|
461
461
|
}
|
|
462
462
|
interface MoversResultRow extends MoverData {
|
|
463
463
|
direction: 'rising' | 'declining' | 'stable';
|
|
464
|
+
clicks: number;
|
|
465
|
+
impressions: number;
|
|
466
|
+
ctr: number;
|
|
467
|
+
position: number;
|
|
468
|
+
prevClicks: number;
|
|
469
|
+
prevImpressions: number;
|
|
470
|
+
prevPosition: number;
|
|
464
471
|
series?: MoversSeriesPoint[];
|
|
465
472
|
}
|
|
466
473
|
declare const moversAnalyzer: import("@gscdump/engine/analyzer").DefinedAnalyzer;
|
package/dist/analyzer/index.mjs
CHANGED
|
@@ -2388,6 +2388,34 @@ const dataQueryAnalyzer = defineAnalyzer({
|
|
|
2388
2388
|
};
|
|
2389
2389
|
}
|
|
2390
2390
|
});
|
|
2391
|
+
const DEFAULT_LIMIT = 1e3;
|
|
2392
|
+
const MAX_LIMIT = 5e4;
|
|
2393
|
+
function clampLimit(limit, fallback = DEFAULT_LIMIT) {
|
|
2394
|
+
const n = Number(limit ?? fallback);
|
|
2395
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
2396
|
+
return Math.min(n, MAX_LIMIT);
|
|
2397
|
+
}
|
|
2398
|
+
function clampOffset(offset) {
|
|
2399
|
+
const n = Number(offset ?? 0);
|
|
2400
|
+
if (!Number.isFinite(n) || n < 0) return 0;
|
|
2401
|
+
return Math.floor(n);
|
|
2402
|
+
}
|
|
2403
|
+
function paginateClause(input) {
|
|
2404
|
+
const l = clampLimit(input.limit);
|
|
2405
|
+
const o = clampOffset(input.offset);
|
|
2406
|
+
return o > 0 ? `LIMIT ${l} OFFSET ${o}` : `LIMIT ${l}`;
|
|
2407
|
+
}
|
|
2408
|
+
function paginateInMemory(rows, input) {
|
|
2409
|
+
const l = clampLimit(input.limit, rows.length);
|
|
2410
|
+
const o = clampOffset(input.offset);
|
|
2411
|
+
return rows.slice(o, o + l);
|
|
2412
|
+
}
|
|
2413
|
+
function resolveSort(input, allowed, defaults) {
|
|
2414
|
+
return {
|
|
2415
|
+
sortBy: input.sortBy && allowed.includes(input.sortBy) ? input.sortBy : defaults.sortBy,
|
|
2416
|
+
sortDir: input.sortDir === "asc" || input.sortDir === "desc" ? input.sortDir : defaults.sortDir
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2391
2419
|
const sortResults$1 = createMetricSorter("lostClicks", {
|
|
2392
2420
|
lostClicks: "desc",
|
|
2393
2421
|
declinePercent: "desc",
|
|
@@ -2441,7 +2469,6 @@ const decayAnalyzer = defineAnalyzer({
|
|
|
2441
2469
|
const { current: cur, previous: prev } = comparisonOf(params);
|
|
2442
2470
|
const minPreviousClicks = params.minPreviousClicks ?? 50;
|
|
2443
2471
|
const threshold = params.threshold ?? .2;
|
|
2444
|
-
const limit = params.limit ?? 2e3;
|
|
2445
2472
|
return {
|
|
2446
2473
|
sql: `
|
|
2447
2474
|
WITH cur AS (
|
|
@@ -2505,7 +2532,7 @@ const decayAnalyzer = defineAnalyzer({
|
|
|
2505
2532
|
FROM joined
|
|
2506
2533
|
WHERE declinePercent >= ? AND lostClicks > 0
|
|
2507
2534
|
ORDER BY lostClicks DESC
|
|
2508
|
-
LIMIT
|
|
2535
|
+
LIMIT 2000
|
|
2509
2536
|
`,
|
|
2510
2537
|
params: [
|
|
2511
2538
|
cur.startDate,
|
|
@@ -2529,25 +2556,28 @@ const decayAnalyzer = defineAnalyzer({
|
|
|
2529
2556
|
}
|
|
2530
2557
|
};
|
|
2531
2558
|
},
|
|
2532
|
-
reduceSql(rows) {
|
|
2533
|
-
const
|
|
2559
|
+
reduceSql(rows, params) {
|
|
2560
|
+
const mapped = (Array.isArray(rows) ? rows : []).map((r) => ({
|
|
2561
|
+
page: str$12(r.page),
|
|
2562
|
+
currentClicks: num(r.currentClicks),
|
|
2563
|
+
previousClicks: num(r.previousClicks),
|
|
2564
|
+
lostClicks: num(r.lostClicks),
|
|
2565
|
+
declinePercent: num(r.declinePercent),
|
|
2566
|
+
currentPosition: num(r.currentPosition),
|
|
2567
|
+
previousPosition: num(r.previousPosition),
|
|
2568
|
+
positionDrop: num(r.positionDrop),
|
|
2569
|
+
series: parseJsonList$8(r.seriesJson).map((s) => ({
|
|
2570
|
+
week: str$12(s.week),
|
|
2571
|
+
clicks: num(s.clicks),
|
|
2572
|
+
impressions: num(s.impressions)
|
|
2573
|
+
}))
|
|
2574
|
+
}));
|
|
2534
2575
|
return {
|
|
2535
|
-
results:
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
declinePercent: num(r.declinePercent),
|
|
2541
|
-
currentPosition: num(r.currentPosition),
|
|
2542
|
-
previousPosition: num(r.previousPosition),
|
|
2543
|
-
positionDrop: num(r.positionDrop),
|
|
2544
|
-
series: parseJsonList$8(r.seriesJson).map((s) => ({
|
|
2545
|
-
week: str$12(s.week),
|
|
2546
|
-
clicks: num(s.clicks),
|
|
2547
|
-
impressions: num(s.impressions)
|
|
2548
|
-
}))
|
|
2549
|
-
})),
|
|
2550
|
-
meta: { total: arr.length }
|
|
2576
|
+
results: paginateInMemory(mapped, {
|
|
2577
|
+
limit: params.limit ?? 2e3,
|
|
2578
|
+
offset: params.offset
|
|
2579
|
+
}),
|
|
2580
|
+
meta: { total: mapped.length }
|
|
2551
2581
|
};
|
|
2552
2582
|
},
|
|
2553
2583
|
buildRows(params) {
|
|
@@ -3151,6 +3181,45 @@ function parseJsonList$4(v) {
|
|
|
3151
3181
|
}
|
|
3152
3182
|
return [];
|
|
3153
3183
|
}
|
|
3184
|
+
function withCanonicalFields(r) {
|
|
3185
|
+
return {
|
|
3186
|
+
...r,
|
|
3187
|
+
clicks: r.recentClicks,
|
|
3188
|
+
impressions: r.recentImpressions,
|
|
3189
|
+
ctr: r.recentImpressions > 0 ? r.recentClicks / r.recentImpressions : 0,
|
|
3190
|
+
position: r.recentPosition,
|
|
3191
|
+
prevClicks: r.baselineClicks,
|
|
3192
|
+
prevImpressions: r.baselineImpressions,
|
|
3193
|
+
prevPosition: r.baselinePosition
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
function selectMoversDirection(rising, declining, stable, params) {
|
|
3197
|
+
if (params.direction) {
|
|
3198
|
+
const selected = params.direction === "rising" ? rising : declining;
|
|
3199
|
+
const total = selected.length;
|
|
3200
|
+
const offset = params.offset ?? 0;
|
|
3201
|
+
const limit = params.limit ?? total;
|
|
3202
|
+
return {
|
|
3203
|
+
results: selected.slice(offset, offset + limit),
|
|
3204
|
+
meta: {
|
|
3205
|
+
total,
|
|
3206
|
+
rising: rising.length,
|
|
3207
|
+
declining: declining.length,
|
|
3208
|
+
stable: stable.length
|
|
3209
|
+
}
|
|
3210
|
+
};
|
|
3211
|
+
}
|
|
3212
|
+
const combined = [...rising, ...declining];
|
|
3213
|
+
return {
|
|
3214
|
+
results: combined,
|
|
3215
|
+
meta: {
|
|
3216
|
+
total: combined.length,
|
|
3217
|
+
rising: rising.length,
|
|
3218
|
+
declining: declining.length,
|
|
3219
|
+
stable: stable.length
|
|
3220
|
+
}
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3154
3223
|
function analyzeMovers(input, options = {}) {
|
|
3155
3224
|
const { changeThreshold = .2, minImpressions = 50, sortBy = "clicksChange" } = options;
|
|
3156
3225
|
const normFactor = input.normalizationFactor ?? 1;
|
|
@@ -3223,7 +3292,7 @@ const moversAnalyzer = defineAnalyzer({
|
|
|
3223
3292
|
const { current: cur, previous: prev } = comparisonOf(params);
|
|
3224
3293
|
const minImpressions = params.minImpressions ?? 50;
|
|
3225
3294
|
const changeThreshold = params.changeThreshold ?? .2;
|
|
3226
|
-
const limit = params.limit ?? 2e3;
|
|
3295
|
+
const limit = params.direction ? 5e3 : params.limit ?? 2e3;
|
|
3227
3296
|
return {
|
|
3228
3297
|
sql: `
|
|
3229
3298
|
WITH cur AS (
|
|
@@ -3328,40 +3397,36 @@ const moversAnalyzer = defineAnalyzer({
|
|
|
3328
3397
|
}
|
|
3329
3398
|
};
|
|
3330
3399
|
},
|
|
3331
|
-
reduceSql(rows) {
|
|
3332
|
-
const normalized = (Array.isArray(rows) ? rows : []).map((r) =>
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
declining: declining.length,
|
|
3362
|
-
stable: stable.length
|
|
3363
|
-
}
|
|
3364
|
-
};
|
|
3400
|
+
reduceSql(rows, params) {
|
|
3401
|
+
const normalized = (Array.isArray(rows) ? rows : []).map((r) => {
|
|
3402
|
+
const recentClicks = num(r.recentClicks);
|
|
3403
|
+
const recentImpressions = num(r.recentImpressions);
|
|
3404
|
+
const recentPosition = num(r.recentPosition);
|
|
3405
|
+
const baselineClicks = Math.round(num(r.baselineClicks));
|
|
3406
|
+
const baselineImpressions = Math.round(num(r.baselineImpressions));
|
|
3407
|
+
const baselinePosition = num(r.baselinePosition);
|
|
3408
|
+
return {
|
|
3409
|
+
keyword: str$7(r.keyword),
|
|
3410
|
+
page: r.page == null ? null : str$7(r.page),
|
|
3411
|
+
recentClicks,
|
|
3412
|
+
recentImpressions,
|
|
3413
|
+
recentPosition,
|
|
3414
|
+
baselineClicks,
|
|
3415
|
+
baselineImpressions,
|
|
3416
|
+
baselinePosition,
|
|
3417
|
+
clicksChange: num(r.clicksChange),
|
|
3418
|
+
clicksChangePercent: num(r.clicksChangePercent),
|
|
3419
|
+
impressionsChangePercent: num(r.impressionsChangePercent),
|
|
3420
|
+
positionChange: num(r.positionChange),
|
|
3421
|
+
direction: str$7(r.direction),
|
|
3422
|
+
series: parseJsonList$4(r.seriesJson).map((s) => ({
|
|
3423
|
+
week: str$7(s.week),
|
|
3424
|
+
clicks: num(s.clicks),
|
|
3425
|
+
impressions: num(s.impressions)
|
|
3426
|
+
}))
|
|
3427
|
+
};
|
|
3428
|
+
}).map(withCanonicalFields);
|
|
3429
|
+
return selectMoversDirection(normalized.filter((r) => r.direction === "rising"), normalized.filter((r) => r.direction === "declining"), normalized.filter((r) => r.direction === "stable"), params);
|
|
3365
3430
|
},
|
|
3366
3431
|
buildRows(params) {
|
|
3367
3432
|
const { current, previous } = comparisonOf(params);
|
|
@@ -3382,49 +3447,15 @@ const moversAnalyzer = defineAnalyzer({
|
|
|
3382
3447
|
changeThreshold: params.changeThreshold,
|
|
3383
3448
|
minImpressions: params.minImpressions
|
|
3384
3449
|
});
|
|
3385
|
-
return {
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
}))],
|
|
3393
|
-
meta: {
|
|
3394
|
-
rising: result.rising.length,
|
|
3395
|
-
declining: result.declining.length
|
|
3396
|
-
}
|
|
3397
|
-
};
|
|
3450
|
+
return selectMoversDirection(result.rising.map((r) => withCanonicalFields({
|
|
3451
|
+
...r,
|
|
3452
|
+
direction: "rising"
|
|
3453
|
+
})), result.declining.map((r) => withCanonicalFields({
|
|
3454
|
+
...r,
|
|
3455
|
+
direction: "declining"
|
|
3456
|
+
})), [], params);
|
|
3398
3457
|
}
|
|
3399
3458
|
});
|
|
3400
|
-
const DEFAULT_LIMIT = 1e3;
|
|
3401
|
-
const MAX_LIMIT = 5e4;
|
|
3402
|
-
function clampLimit(limit, fallback = DEFAULT_LIMIT) {
|
|
3403
|
-
const n = Number(limit ?? fallback);
|
|
3404
|
-
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
3405
|
-
return Math.min(n, MAX_LIMIT);
|
|
3406
|
-
}
|
|
3407
|
-
function clampOffset(offset) {
|
|
3408
|
-
const n = Number(offset ?? 0);
|
|
3409
|
-
if (!Number.isFinite(n) || n < 0) return 0;
|
|
3410
|
-
return Math.floor(n);
|
|
3411
|
-
}
|
|
3412
|
-
function paginateClause(input) {
|
|
3413
|
-
const l = clampLimit(input.limit);
|
|
3414
|
-
const o = clampOffset(input.offset);
|
|
3415
|
-
return o > 0 ? `LIMIT ${l} OFFSET ${o}` : `LIMIT ${l}`;
|
|
3416
|
-
}
|
|
3417
|
-
function paginateInMemory(rows, input) {
|
|
3418
|
-
const l = clampLimit(input.limit, rows.length);
|
|
3419
|
-
const o = clampOffset(input.offset);
|
|
3420
|
-
return rows.slice(o, o + l);
|
|
3421
|
-
}
|
|
3422
|
-
function resolveSort(input, allowed, defaults) {
|
|
3423
|
-
return {
|
|
3424
|
-
sortBy: input.sortBy && allowed.includes(input.sortBy) ? input.sortBy : defaults.sortBy,
|
|
3425
|
-
sortDir: input.sortDir === "asc" || input.sortDir === "desc" ? input.sortDir : defaults.sortDir
|
|
3426
|
-
};
|
|
3427
|
-
}
|
|
3428
3459
|
const EXPECTED_CTR_BY_POSITION = {
|
|
3429
3460
|
1: .3,
|
|
3430
3461
|
2: .15,
|
|
@@ -2388,6 +2388,28 @@ const dataQueryAnalyzer = defineAnalyzer({
|
|
|
2388
2388
|
};
|
|
2389
2389
|
}
|
|
2390
2390
|
});
|
|
2391
|
+
const DEFAULT_LIMIT = 1e3;
|
|
2392
|
+
const MAX_LIMIT = 5e4;
|
|
2393
|
+
function clampLimit(limit, fallback = DEFAULT_LIMIT) {
|
|
2394
|
+
const n = Number(limit ?? fallback);
|
|
2395
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
2396
|
+
return Math.min(n, MAX_LIMIT);
|
|
2397
|
+
}
|
|
2398
|
+
function clampOffset(offset) {
|
|
2399
|
+
const n = Number(offset ?? 0);
|
|
2400
|
+
if (!Number.isFinite(n) || n < 0) return 0;
|
|
2401
|
+
return Math.floor(n);
|
|
2402
|
+
}
|
|
2403
|
+
function paginateClause(input) {
|
|
2404
|
+
const l = clampLimit(input.limit);
|
|
2405
|
+
const o = clampOffset(input.offset);
|
|
2406
|
+
return o > 0 ? `LIMIT ${l} OFFSET ${o}` : `LIMIT ${l}`;
|
|
2407
|
+
}
|
|
2408
|
+
function paginateInMemory(rows, input) {
|
|
2409
|
+
const l = clampLimit(input.limit, rows.length);
|
|
2410
|
+
const o = clampOffset(input.offset);
|
|
2411
|
+
return rows.slice(o, o + l);
|
|
2412
|
+
}
|
|
2391
2413
|
const sortResults$1 = createMetricSorter("lostClicks", {
|
|
2392
2414
|
lostClicks: "desc",
|
|
2393
2415
|
declinePercent: "desc",
|
|
@@ -2441,7 +2463,6 @@ const decayAnalyzer = defineAnalyzer({
|
|
|
2441
2463
|
const { current: cur, previous: prev } = comparisonOf(params);
|
|
2442
2464
|
const minPreviousClicks = params.minPreviousClicks ?? 50;
|
|
2443
2465
|
const threshold = params.threshold ?? .2;
|
|
2444
|
-
const limit = params.limit ?? 2e3;
|
|
2445
2466
|
return {
|
|
2446
2467
|
sql: `
|
|
2447
2468
|
WITH cur AS (
|
|
@@ -2505,7 +2526,7 @@ const decayAnalyzer = defineAnalyzer({
|
|
|
2505
2526
|
FROM joined
|
|
2506
2527
|
WHERE declinePercent >= ? AND lostClicks > 0
|
|
2507
2528
|
ORDER BY lostClicks DESC
|
|
2508
|
-
LIMIT
|
|
2529
|
+
LIMIT 2000
|
|
2509
2530
|
`,
|
|
2510
2531
|
params: [
|
|
2511
2532
|
cur.startDate,
|
|
@@ -2529,25 +2550,28 @@ const decayAnalyzer = defineAnalyzer({
|
|
|
2529
2550
|
}
|
|
2530
2551
|
};
|
|
2531
2552
|
},
|
|
2532
|
-
reduceSql(rows) {
|
|
2533
|
-
const
|
|
2553
|
+
reduceSql(rows, params) {
|
|
2554
|
+
const mapped = (Array.isArray(rows) ? rows : []).map((r) => ({
|
|
2555
|
+
page: str$12(r.page),
|
|
2556
|
+
currentClicks: num(r.currentClicks),
|
|
2557
|
+
previousClicks: num(r.previousClicks),
|
|
2558
|
+
lostClicks: num(r.lostClicks),
|
|
2559
|
+
declinePercent: num(r.declinePercent),
|
|
2560
|
+
currentPosition: num(r.currentPosition),
|
|
2561
|
+
previousPosition: num(r.previousPosition),
|
|
2562
|
+
positionDrop: num(r.positionDrop),
|
|
2563
|
+
series: parseJsonList$8(r.seriesJson).map((s) => ({
|
|
2564
|
+
week: str$12(s.week),
|
|
2565
|
+
clicks: num(s.clicks),
|
|
2566
|
+
impressions: num(s.impressions)
|
|
2567
|
+
}))
|
|
2568
|
+
}));
|
|
2534
2569
|
return {
|
|
2535
|
-
results:
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
declinePercent: num(r.declinePercent),
|
|
2541
|
-
currentPosition: num(r.currentPosition),
|
|
2542
|
-
previousPosition: num(r.previousPosition),
|
|
2543
|
-
positionDrop: num(r.positionDrop),
|
|
2544
|
-
series: parseJsonList$8(r.seriesJson).map((s) => ({
|
|
2545
|
-
week: str$12(s.week),
|
|
2546
|
-
clicks: num(s.clicks),
|
|
2547
|
-
impressions: num(s.impressions)
|
|
2548
|
-
}))
|
|
2549
|
-
})),
|
|
2550
|
-
meta: { total: arr.length }
|
|
2570
|
+
results: paginateInMemory(mapped, {
|
|
2571
|
+
limit: params.limit ?? 2e3,
|
|
2572
|
+
offset: params.offset
|
|
2573
|
+
}),
|
|
2574
|
+
meta: { total: mapped.length }
|
|
2551
2575
|
};
|
|
2552
2576
|
},
|
|
2553
2577
|
buildRows(params) {
|
|
@@ -3151,6 +3175,45 @@ function parseJsonList$4(v) {
|
|
|
3151
3175
|
}
|
|
3152
3176
|
return [];
|
|
3153
3177
|
}
|
|
3178
|
+
function withCanonicalFields(r) {
|
|
3179
|
+
return {
|
|
3180
|
+
...r,
|
|
3181
|
+
clicks: r.recentClicks,
|
|
3182
|
+
impressions: r.recentImpressions,
|
|
3183
|
+
ctr: r.recentImpressions > 0 ? r.recentClicks / r.recentImpressions : 0,
|
|
3184
|
+
position: r.recentPosition,
|
|
3185
|
+
prevClicks: r.baselineClicks,
|
|
3186
|
+
prevImpressions: r.baselineImpressions,
|
|
3187
|
+
prevPosition: r.baselinePosition
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
3190
|
+
function selectMoversDirection(rising, declining, stable, params) {
|
|
3191
|
+
if (params.direction) {
|
|
3192
|
+
const selected = params.direction === "rising" ? rising : declining;
|
|
3193
|
+
const total = selected.length;
|
|
3194
|
+
const offset = params.offset ?? 0;
|
|
3195
|
+
const limit = params.limit ?? total;
|
|
3196
|
+
return {
|
|
3197
|
+
results: selected.slice(offset, offset + limit),
|
|
3198
|
+
meta: {
|
|
3199
|
+
total,
|
|
3200
|
+
rising: rising.length,
|
|
3201
|
+
declining: declining.length,
|
|
3202
|
+
stable: stable.length
|
|
3203
|
+
}
|
|
3204
|
+
};
|
|
3205
|
+
}
|
|
3206
|
+
const combined = [...rising, ...declining];
|
|
3207
|
+
return {
|
|
3208
|
+
results: combined,
|
|
3209
|
+
meta: {
|
|
3210
|
+
total: combined.length,
|
|
3211
|
+
rising: rising.length,
|
|
3212
|
+
declining: declining.length,
|
|
3213
|
+
stable: stable.length
|
|
3214
|
+
}
|
|
3215
|
+
};
|
|
3216
|
+
}
|
|
3154
3217
|
function analyzeMovers(input, options = {}) {
|
|
3155
3218
|
const { changeThreshold = .2, minImpressions = 50, sortBy = "clicksChange" } = options;
|
|
3156
3219
|
const normFactor = input.normalizationFactor ?? 1;
|
|
@@ -3223,7 +3286,7 @@ const moversAnalyzer = defineAnalyzer({
|
|
|
3223
3286
|
const { current: cur, previous: prev } = comparisonOf(params);
|
|
3224
3287
|
const minImpressions = params.minImpressions ?? 50;
|
|
3225
3288
|
const changeThreshold = params.changeThreshold ?? .2;
|
|
3226
|
-
const limit = params.limit ?? 2e3;
|
|
3289
|
+
const limit = params.direction ? 5e3 : params.limit ?? 2e3;
|
|
3227
3290
|
return {
|
|
3228
3291
|
sql: `
|
|
3229
3292
|
WITH cur AS (
|
|
@@ -3328,40 +3391,36 @@ const moversAnalyzer = defineAnalyzer({
|
|
|
3328
3391
|
}
|
|
3329
3392
|
};
|
|
3330
3393
|
},
|
|
3331
|
-
reduceSql(rows) {
|
|
3332
|
-
const normalized = (Array.isArray(rows) ? rows : []).map((r) =>
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
declining: declining.length,
|
|
3362
|
-
stable: stable.length
|
|
3363
|
-
}
|
|
3364
|
-
};
|
|
3394
|
+
reduceSql(rows, params) {
|
|
3395
|
+
const normalized = (Array.isArray(rows) ? rows : []).map((r) => {
|
|
3396
|
+
const recentClicks = num(r.recentClicks);
|
|
3397
|
+
const recentImpressions = num(r.recentImpressions);
|
|
3398
|
+
const recentPosition = num(r.recentPosition);
|
|
3399
|
+
const baselineClicks = Math.round(num(r.baselineClicks));
|
|
3400
|
+
const baselineImpressions = Math.round(num(r.baselineImpressions));
|
|
3401
|
+
const baselinePosition = num(r.baselinePosition);
|
|
3402
|
+
return {
|
|
3403
|
+
keyword: str$7(r.keyword),
|
|
3404
|
+
page: r.page == null ? null : str$7(r.page),
|
|
3405
|
+
recentClicks,
|
|
3406
|
+
recentImpressions,
|
|
3407
|
+
recentPosition,
|
|
3408
|
+
baselineClicks,
|
|
3409
|
+
baselineImpressions,
|
|
3410
|
+
baselinePosition,
|
|
3411
|
+
clicksChange: num(r.clicksChange),
|
|
3412
|
+
clicksChangePercent: num(r.clicksChangePercent),
|
|
3413
|
+
impressionsChangePercent: num(r.impressionsChangePercent),
|
|
3414
|
+
positionChange: num(r.positionChange),
|
|
3415
|
+
direction: str$7(r.direction),
|
|
3416
|
+
series: parseJsonList$4(r.seriesJson).map((s) => ({
|
|
3417
|
+
week: str$7(s.week),
|
|
3418
|
+
clicks: num(s.clicks),
|
|
3419
|
+
impressions: num(s.impressions)
|
|
3420
|
+
}))
|
|
3421
|
+
};
|
|
3422
|
+
}).map(withCanonicalFields);
|
|
3423
|
+
return selectMoversDirection(normalized.filter((r) => r.direction === "rising"), normalized.filter((r) => r.direction === "declining"), normalized.filter((r) => r.direction === "stable"), params);
|
|
3365
3424
|
},
|
|
3366
3425
|
buildRows(params) {
|
|
3367
3426
|
const { current, previous } = comparisonOf(params);
|
|
@@ -3382,43 +3441,15 @@ const moversAnalyzer = defineAnalyzer({
|
|
|
3382
3441
|
changeThreshold: params.changeThreshold,
|
|
3383
3442
|
minImpressions: params.minImpressions
|
|
3384
3443
|
});
|
|
3385
|
-
return {
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
}))],
|
|
3393
|
-
meta: {
|
|
3394
|
-
rising: result.rising.length,
|
|
3395
|
-
declining: result.declining.length
|
|
3396
|
-
}
|
|
3397
|
-
};
|
|
3444
|
+
return selectMoversDirection(result.rising.map((r) => withCanonicalFields({
|
|
3445
|
+
...r,
|
|
3446
|
+
direction: "rising"
|
|
3447
|
+
})), result.declining.map((r) => withCanonicalFields({
|
|
3448
|
+
...r,
|
|
3449
|
+
direction: "declining"
|
|
3450
|
+
})), [], params);
|
|
3398
3451
|
}
|
|
3399
3452
|
});
|
|
3400
|
-
const DEFAULT_LIMIT = 1e3;
|
|
3401
|
-
const MAX_LIMIT = 5e4;
|
|
3402
|
-
function clampLimit(limit, fallback = DEFAULT_LIMIT) {
|
|
3403
|
-
const n = Number(limit ?? fallback);
|
|
3404
|
-
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
3405
|
-
return Math.min(n, MAX_LIMIT);
|
|
3406
|
-
}
|
|
3407
|
-
function clampOffset(offset) {
|
|
3408
|
-
const n = Number(offset ?? 0);
|
|
3409
|
-
if (!Number.isFinite(n) || n < 0) return 0;
|
|
3410
|
-
return Math.floor(n);
|
|
3411
|
-
}
|
|
3412
|
-
function paginateClause(input) {
|
|
3413
|
-
const l = clampLimit(input.limit);
|
|
3414
|
-
const o = clampOffset(input.offset);
|
|
3415
|
-
return o > 0 ? `LIMIT ${l} OFFSET ${o}` : `LIMIT ${l}`;
|
|
3416
|
-
}
|
|
3417
|
-
function paginateInMemory(rows, input) {
|
|
3418
|
-
const l = clampLimit(input.limit, rows.length);
|
|
3419
|
-
const o = clampOffset(input.offset);
|
|
3420
|
-
return rows.slice(o, o + l);
|
|
3421
|
-
}
|
|
3422
3453
|
const EXPECTED_CTR_BY_POSITION = {
|
|
3423
3454
|
1: .3,
|
|
3424
3455
|
2: .15,
|
package/dist/index.mjs
CHANGED
|
@@ -2754,6 +2754,28 @@ const dataQueryAnalyzer = defineAnalyzer$1({
|
|
|
2754
2754
|
};
|
|
2755
2755
|
}
|
|
2756
2756
|
});
|
|
2757
|
+
const DEFAULT_LIMIT$1 = 1e3;
|
|
2758
|
+
const MAX_LIMIT = 5e4;
|
|
2759
|
+
function clampLimit(limit, fallback = DEFAULT_LIMIT$1) {
|
|
2760
|
+
const n = Number(limit ?? fallback);
|
|
2761
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
2762
|
+
return Math.min(n, MAX_LIMIT);
|
|
2763
|
+
}
|
|
2764
|
+
function clampOffset(offset) {
|
|
2765
|
+
const n = Number(offset ?? 0);
|
|
2766
|
+
if (!Number.isFinite(n) || n < 0) return 0;
|
|
2767
|
+
return Math.floor(n);
|
|
2768
|
+
}
|
|
2769
|
+
function paginateClause(input) {
|
|
2770
|
+
const l = clampLimit(input.limit);
|
|
2771
|
+
const o = clampOffset(input.offset);
|
|
2772
|
+
return o > 0 ? `LIMIT ${l} OFFSET ${o}` : `LIMIT ${l}`;
|
|
2773
|
+
}
|
|
2774
|
+
function paginateInMemory(rows, input) {
|
|
2775
|
+
const l = clampLimit(input.limit, rows.length);
|
|
2776
|
+
const o = clampOffset(input.offset);
|
|
2777
|
+
return rows.slice(o, o + l);
|
|
2778
|
+
}
|
|
2757
2779
|
const sortResults$1 = createMetricSorter("lostClicks", {
|
|
2758
2780
|
lostClicks: "desc",
|
|
2759
2781
|
declinePercent: "desc",
|
|
@@ -2807,7 +2829,6 @@ const decayAnalyzer = defineAnalyzer$1({
|
|
|
2807
2829
|
const { current: cur, previous: prev } = comparisonOf$1(params);
|
|
2808
2830
|
const minPreviousClicks = params.minPreviousClicks ?? 50;
|
|
2809
2831
|
const threshold = params.threshold ?? .2;
|
|
2810
|
-
const limit = params.limit ?? 2e3;
|
|
2811
2832
|
return {
|
|
2812
2833
|
sql: `
|
|
2813
2834
|
WITH cur AS (
|
|
@@ -2871,7 +2892,7 @@ const decayAnalyzer = defineAnalyzer$1({
|
|
|
2871
2892
|
FROM joined
|
|
2872
2893
|
WHERE declinePercent >= ? AND lostClicks > 0
|
|
2873
2894
|
ORDER BY lostClicks DESC
|
|
2874
|
-
LIMIT
|
|
2895
|
+
LIMIT 2000
|
|
2875
2896
|
`,
|
|
2876
2897
|
params: [
|
|
2877
2898
|
cur.startDate,
|
|
@@ -2895,25 +2916,28 @@ const decayAnalyzer = defineAnalyzer$1({
|
|
|
2895
2916
|
}
|
|
2896
2917
|
};
|
|
2897
2918
|
},
|
|
2898
|
-
reduceSql(rows) {
|
|
2899
|
-
const
|
|
2919
|
+
reduceSql(rows, params) {
|
|
2920
|
+
const mapped = (Array.isArray(rows) ? rows : []).map((r) => ({
|
|
2921
|
+
page: str$12(r.page),
|
|
2922
|
+
currentClicks: num$1(r.currentClicks),
|
|
2923
|
+
previousClicks: num$1(r.previousClicks),
|
|
2924
|
+
lostClicks: num$1(r.lostClicks),
|
|
2925
|
+
declinePercent: num$1(r.declinePercent),
|
|
2926
|
+
currentPosition: num$1(r.currentPosition),
|
|
2927
|
+
previousPosition: num$1(r.previousPosition),
|
|
2928
|
+
positionDrop: num$1(r.positionDrop),
|
|
2929
|
+
series: parseJsonList$8(r.seriesJson).map((s) => ({
|
|
2930
|
+
week: str$12(s.week),
|
|
2931
|
+
clicks: num$1(s.clicks),
|
|
2932
|
+
impressions: num$1(s.impressions)
|
|
2933
|
+
}))
|
|
2934
|
+
}));
|
|
2900
2935
|
return {
|
|
2901
|
-
results:
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
declinePercent: num$1(r.declinePercent),
|
|
2907
|
-
currentPosition: num$1(r.currentPosition),
|
|
2908
|
-
previousPosition: num$1(r.previousPosition),
|
|
2909
|
-
positionDrop: num$1(r.positionDrop),
|
|
2910
|
-
series: parseJsonList$8(r.seriesJson).map((s) => ({
|
|
2911
|
-
week: str$12(s.week),
|
|
2912
|
-
clicks: num$1(s.clicks),
|
|
2913
|
-
impressions: num$1(s.impressions)
|
|
2914
|
-
}))
|
|
2915
|
-
})),
|
|
2916
|
-
meta: { total: arr.length }
|
|
2936
|
+
results: paginateInMemory(mapped, {
|
|
2937
|
+
limit: params.limit ?? 2e3,
|
|
2938
|
+
offset: params.offset
|
|
2939
|
+
}),
|
|
2940
|
+
meta: { total: mapped.length }
|
|
2917
2941
|
};
|
|
2918
2942
|
},
|
|
2919
2943
|
buildRows(params) {
|
|
@@ -3513,6 +3537,45 @@ function parseJsonList$4(v) {
|
|
|
3513
3537
|
}
|
|
3514
3538
|
return [];
|
|
3515
3539
|
}
|
|
3540
|
+
function withCanonicalFields(r) {
|
|
3541
|
+
return {
|
|
3542
|
+
...r,
|
|
3543
|
+
clicks: r.recentClicks,
|
|
3544
|
+
impressions: r.recentImpressions,
|
|
3545
|
+
ctr: r.recentImpressions > 0 ? r.recentClicks / r.recentImpressions : 0,
|
|
3546
|
+
position: r.recentPosition,
|
|
3547
|
+
prevClicks: r.baselineClicks,
|
|
3548
|
+
prevImpressions: r.baselineImpressions,
|
|
3549
|
+
prevPosition: r.baselinePosition
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3552
|
+
function selectMoversDirection(rising, declining, stable, params) {
|
|
3553
|
+
if (params.direction) {
|
|
3554
|
+
const selected = params.direction === "rising" ? rising : declining;
|
|
3555
|
+
const total = selected.length;
|
|
3556
|
+
const offset = params.offset ?? 0;
|
|
3557
|
+
const limit = params.limit ?? total;
|
|
3558
|
+
return {
|
|
3559
|
+
results: selected.slice(offset, offset + limit),
|
|
3560
|
+
meta: {
|
|
3561
|
+
total,
|
|
3562
|
+
rising: rising.length,
|
|
3563
|
+
declining: declining.length,
|
|
3564
|
+
stable: stable.length
|
|
3565
|
+
}
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
const combined = [...rising, ...declining];
|
|
3569
|
+
return {
|
|
3570
|
+
results: combined,
|
|
3571
|
+
meta: {
|
|
3572
|
+
total: combined.length,
|
|
3573
|
+
rising: rising.length,
|
|
3574
|
+
declining: declining.length,
|
|
3575
|
+
stable: stable.length
|
|
3576
|
+
}
|
|
3577
|
+
};
|
|
3578
|
+
}
|
|
3516
3579
|
function analyzeMovers(input, options = {}) {
|
|
3517
3580
|
const { changeThreshold = .2, minImpressions = 50, sortBy = "clicksChange" } = options;
|
|
3518
3581
|
const normFactor = input.normalizationFactor ?? 1;
|
|
@@ -3585,7 +3648,7 @@ const moversAnalyzer = defineAnalyzer$1({
|
|
|
3585
3648
|
const { current: cur, previous: prev } = comparisonOf$1(params);
|
|
3586
3649
|
const minImpressions = params.minImpressions ?? 50;
|
|
3587
3650
|
const changeThreshold = params.changeThreshold ?? .2;
|
|
3588
|
-
const limit = params.limit ?? 2e3;
|
|
3651
|
+
const limit = params.direction ? 5e3 : params.limit ?? 2e3;
|
|
3589
3652
|
return {
|
|
3590
3653
|
sql: `
|
|
3591
3654
|
WITH cur AS (
|
|
@@ -3690,40 +3753,36 @@ const moversAnalyzer = defineAnalyzer$1({
|
|
|
3690
3753
|
}
|
|
3691
3754
|
};
|
|
3692
3755
|
},
|
|
3693
|
-
reduceSql(rows) {
|
|
3694
|
-
const normalized = (Array.isArray(rows) ? rows : []).map((r) =>
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
declining: declining.length,
|
|
3724
|
-
stable: stable.length
|
|
3725
|
-
}
|
|
3726
|
-
};
|
|
3756
|
+
reduceSql(rows, params) {
|
|
3757
|
+
const normalized = (Array.isArray(rows) ? rows : []).map((r) => {
|
|
3758
|
+
const recentClicks = num$1(r.recentClicks);
|
|
3759
|
+
const recentImpressions = num$1(r.recentImpressions);
|
|
3760
|
+
const recentPosition = num$1(r.recentPosition);
|
|
3761
|
+
const baselineClicks = Math.round(num$1(r.baselineClicks));
|
|
3762
|
+
const baselineImpressions = Math.round(num$1(r.baselineImpressions));
|
|
3763
|
+
const baselinePosition = num$1(r.baselinePosition);
|
|
3764
|
+
return {
|
|
3765
|
+
keyword: str$7(r.keyword),
|
|
3766
|
+
page: r.page == null ? null : str$7(r.page),
|
|
3767
|
+
recentClicks,
|
|
3768
|
+
recentImpressions,
|
|
3769
|
+
recentPosition,
|
|
3770
|
+
baselineClicks,
|
|
3771
|
+
baselineImpressions,
|
|
3772
|
+
baselinePosition,
|
|
3773
|
+
clicksChange: num$1(r.clicksChange),
|
|
3774
|
+
clicksChangePercent: num$1(r.clicksChangePercent),
|
|
3775
|
+
impressionsChangePercent: num$1(r.impressionsChangePercent),
|
|
3776
|
+
positionChange: num$1(r.positionChange),
|
|
3777
|
+
direction: str$7(r.direction),
|
|
3778
|
+
series: parseJsonList$4(r.seriesJson).map((s) => ({
|
|
3779
|
+
week: str$7(s.week),
|
|
3780
|
+
clicks: num$1(s.clicks),
|
|
3781
|
+
impressions: num$1(s.impressions)
|
|
3782
|
+
}))
|
|
3783
|
+
};
|
|
3784
|
+
}).map(withCanonicalFields);
|
|
3785
|
+
return selectMoversDirection(normalized.filter((r) => r.direction === "rising"), normalized.filter((r) => r.direction === "declining"), normalized.filter((r) => r.direction === "stable"), params);
|
|
3727
3786
|
},
|
|
3728
3787
|
buildRows(params) {
|
|
3729
3788
|
const { current, previous } = comparisonOf$1(params);
|
|
@@ -3744,43 +3803,15 @@ const moversAnalyzer = defineAnalyzer$1({
|
|
|
3744
3803
|
changeThreshold: params.changeThreshold,
|
|
3745
3804
|
minImpressions: params.minImpressions
|
|
3746
3805
|
});
|
|
3747
|
-
return {
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
}))],
|
|
3755
|
-
meta: {
|
|
3756
|
-
rising: result.rising.length,
|
|
3757
|
-
declining: result.declining.length
|
|
3758
|
-
}
|
|
3759
|
-
};
|
|
3806
|
+
return selectMoversDirection(result.rising.map((r) => withCanonicalFields({
|
|
3807
|
+
...r,
|
|
3808
|
+
direction: "rising"
|
|
3809
|
+
})), result.declining.map((r) => withCanonicalFields({
|
|
3810
|
+
...r,
|
|
3811
|
+
direction: "declining"
|
|
3812
|
+
})), [], params);
|
|
3760
3813
|
}
|
|
3761
3814
|
});
|
|
3762
|
-
const DEFAULT_LIMIT$1 = 1e3;
|
|
3763
|
-
const MAX_LIMIT = 5e4;
|
|
3764
|
-
function clampLimit(limit, fallback = DEFAULT_LIMIT$1) {
|
|
3765
|
-
const n = Number(limit ?? fallback);
|
|
3766
|
-
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
3767
|
-
return Math.min(n, MAX_LIMIT);
|
|
3768
|
-
}
|
|
3769
|
-
function clampOffset(offset) {
|
|
3770
|
-
const n = Number(offset ?? 0);
|
|
3771
|
-
if (!Number.isFinite(n) || n < 0) return 0;
|
|
3772
|
-
return Math.floor(n);
|
|
3773
|
-
}
|
|
3774
|
-
function paginateClause(input) {
|
|
3775
|
-
const l = clampLimit(input.limit);
|
|
3776
|
-
const o = clampOffset(input.offset);
|
|
3777
|
-
return o > 0 ? `LIMIT ${l} OFFSET ${o}` : `LIMIT ${l}`;
|
|
3778
|
-
}
|
|
3779
|
-
function paginateInMemory(rows, input) {
|
|
3780
|
-
const l = clampLimit(input.limit, rows.length);
|
|
3781
|
-
const o = clampOffset(input.offset);
|
|
3782
|
-
return rows.slice(o, o + l);
|
|
3783
|
-
}
|
|
3784
3815
|
const EXPECTED_CTR_BY_POSITION = {
|
|
3785
3816
|
1: .3,
|
|
3786
3817
|
2: .15,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/analysis",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.25.
|
|
4
|
+
"version": "0.25.7",
|
|
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",
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"drizzle-orm": "1.0.0-rc.3",
|
|
73
|
-
"@gscdump/engine": "0.25.
|
|
74
|
-
"@gscdump/engine-gsc-api": "0.25.
|
|
75
|
-
"gscdump": "0.25.
|
|
73
|
+
"@gscdump/engine": "0.25.7",
|
|
74
|
+
"@gscdump/engine-gsc-api": "0.25.7",
|
|
75
|
+
"gscdump": "0.25.7"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"vitest": "^4.1.7"
|