@gscdump/analysis 1.4.3 → 1.4.5

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.
@@ -2,7 +2,8 @@ const SEVERITY_GLYPH = {
2
2
  info: "i",
3
3
  low: "·",
4
4
  medium: "!",
5
- high: "!!"
5
+ high: "!!",
6
+ unknown: "?"
6
7
  };
7
8
  function formatReport(report, opts = {}) {
8
9
  const lines = [];
@@ -33,7 +33,8 @@ const brandReport = defineReport({
33
33
  brandTerms,
34
34
  limit: 200
35
35
  },
36
- required: true
36
+ required: true,
37
+ feeds: ["brand-split"]
37
38
  }, {
38
39
  key: "concentration",
39
40
  type: "concentration",
@@ -39,7 +39,8 @@ const moversReport = defineReport({
39
39
  ...prev,
40
40
  limit: 200
41
41
  },
42
- required: true
42
+ required: true,
43
+ feeds: ["rising", "decliners"]
43
44
  },
44
45
  {
45
46
  key: "decay",
@@ -48,7 +49,8 @@ const moversReport = defineReport({
48
49
  ...cur,
49
50
  ...prev,
50
51
  limit: 100
51
- }
52
+ },
53
+ feeds: ["decliners"]
52
54
  },
53
55
  {
54
56
  key: "striking",
@@ -56,7 +58,8 @@ const moversReport = defineReport({
56
58
  params: {
57
59
  ...cur,
58
60
  limit: 100
59
- }
61
+ },
62
+ feeds: ["striking-distance"]
60
63
  }
61
64
  ];
62
65
  },
@@ -24,7 +24,8 @@ const opportunitiesReport = defineReport({
24
24
  ...dates,
25
25
  limit: 100
26
26
  },
27
- required: true
27
+ required: true,
28
+ feeds: ["striking-distance"]
28
29
  },
29
30
  {
30
31
  key: "opportunity",
@@ -32,7 +33,8 @@ const opportunitiesReport = defineReport({
32
33
  params: {
33
34
  ...dates,
34
35
  limit: 100
35
- }
36
+ },
37
+ feeds: ["low-ctr"]
36
38
  },
37
39
  {
38
40
  key: "zero-click",
@@ -31,14 +31,16 @@ const prePublishReport = defineReport({
31
31
  params: {
32
32
  ...dates,
33
33
  limit: 200
34
- }
34
+ },
35
+ feeds: ["cannibalization-risk"]
35
36
  }, {
36
37
  key: "striking",
37
38
  type: "striking-distance",
38
39
  params: {
39
40
  ...dates,
40
41
  limit: 200
41
- }
42
+ },
43
+ feeds: ["striking-peers"]
42
44
  }];
43
45
  },
44
46
  reduce: (results, ctx) => {
@@ -36,7 +36,8 @@ const priorityReport = defineReport({
36
36
  ...cmp,
37
37
  limit: 100
38
38
  },
39
- required: false
39
+ required: false,
40
+ feeds: ["priority"]
40
41
  }));
41
42
  },
42
43
  reduce: (results, ctx) => {
@@ -18,7 +18,9 @@ interface RunReportOptions<P extends ReportParams = ReportParams> {
18
18
  * Steps execute in parallel via `Promise.all`. The report's `reduce` is invoked
19
19
  * with a results bag that only contains successful steps — sections that
20
20
  * depended on a failed step should set their own `coverage: 'partial'` (the
21
- * runtime additionally marks `meta.degraded` when any step errored).
21
+ * runtime additionally marks `meta.degraded` when any step errored, and
22
+ * rewrites sections whose feeding steps ALL failed; see
23
+ * `markUnavailableSections`).
22
24
  *
23
25
  * The report's own `plan()` param-validation throws (`--target` etc.) are
24
26
  * defects from this core's perspective and still propagate; those are modelled
@@ -26,6 +26,32 @@ async function executeStep(source, analyzers, step) {
26
26
  };
27
27
  });
28
28
  }
29
+ function unavailableSection(section, failedKeys) {
30
+ return {
31
+ id: section.id,
32
+ title: section.title,
33
+ severity: "unknown",
34
+ summary: { magnitudeLabel: `unavailable — ${failedKeys.join(", ")} failed` },
35
+ findings: [],
36
+ coverage: "partial",
37
+ actions: []
38
+ };
39
+ }
40
+ function markUnavailableSections(sections, steps, outcomes) {
41
+ const failedKeys = new Set(outcomes.filter((o) => o.state.status === "error").map((o) => o.state.key));
42
+ if (failedKeys.size === 0) return [...sections];
43
+ const feedersBySection = /* @__PURE__ */ new Map();
44
+ for (const step of steps) for (const sectionId of step.feeds ?? [step.key]) {
45
+ const feeders = feedersBySection.get(sectionId);
46
+ if (feeders) feeders.push(step.key);
47
+ else feedersBySection.set(sectionId, [step.key]);
48
+ }
49
+ return sections.map((section) => {
50
+ const feeders = feedersBySection.get(section.id);
51
+ if (!feeders?.length || !feeders.every((k) => failedKeys.has(k))) return section;
52
+ return unavailableSection(section, feeders);
53
+ });
54
+ }
29
55
  async function runReportResult(report, opts) {
30
56
  const startedAt = Date.now();
31
57
  const generatedAt = new Date(startedAt).toISOString();
@@ -43,7 +69,7 @@ async function runReportResult(report, opts) {
43
69
  for (const o of errored) if (required.has(o.state.key)) return err(analysisErrors.requiredStepFailed(report.id, o.state.key, o.state.error ?? "unknown error", o.cause));
44
70
  const resultsByKey = {};
45
71
  for (const o of outcomes) if (o.result) resultsByKey[o.state.key] = o.result;
46
- const sections = report.reduce(resultsByKey, opts.ctx).sections;
72
+ const sections = markUnavailableSections(report.reduce(resultsByKey, opts.ctx).sections, steps, outcomes);
47
73
  const degraded = errored.length > 0;
48
74
  const stepStates = outcomes.map((o) => o.state);
49
75
  return ok({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/analysis",
3
3
  "type": "module",
4
- "version": "1.4.3",
4
+ "version": "1.4.5",
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",
@@ -57,9 +57,9 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "pluralize": "^8.0.0",
60
- "@gscdump/engine": "^1.4.3",
61
- "@gscdump/engine-gsc-api": "^1.4.3",
62
- "gscdump": "^1.4.3"
60
+ "@gscdump/engine": "^1.4.5",
61
+ "@gscdump/engine-gsc-api": "^1.4.5",
62
+ "gscdump": "^1.4.5"
63
63
  },
64
64
  "devDependencies": {
65
65
  "vitest": "^4.1.10"