ga4-export-fixer 0.5.2-dev.4 → 0.5.2-dev.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ga4-export-fixer",
3
- "version": "0.5.2-dev.4",
3
+ "version": "0.5.2-dev.5",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -17,13 +17,14 @@
17
17
  "createTable.js"
18
18
  ],
19
19
  "scripts": {
20
- "test": "node tests/ga4EventsEnhanced.test.js && node tests/mergeSQLConfigurations.test.js && node tests/preOperations.test.js && node tests/documentation.test.js && node tests/inputValidation.test.js && node tests/createTable.test.js",
20
+ "test": "node tests/ga4EventsEnhanced.test.js && node tests/assertions.test.js && node tests/mergeSQLConfigurations.test.js && node tests/preOperations.test.js && node tests/documentation.test.js && node tests/inputValidation.test.js && node tests/createTable.test.js",
21
21
  "test:summary": "node tests/testRunner.js",
22
22
  "test:docs": "node tests/documentation.test.js",
23
23
  "test:preops": "node tests/preOperations.test.js",
24
24
  "test:events": "node tests/ga4EventsEnhanced.test.js",
25
25
  "test:merge": "node tests/mergeSQLConfigurations.test.js",
26
26
  "test:validation": "node tests/inputValidation.test.js",
27
+ "test:assertions": "node tests/assertions.test.js",
27
28
  "test:createTable": "node tests/createTable.test.js",
28
29
  "test:integration": "node tests/integration/integration.test.js",
29
30
  "release:dev": "./scripts/release-dev.sh",
@@ -0,0 +1,5 @@
1
+ const { generateItemRevenueAssertionSql } = require('./itemRevenue.js');
2
+
3
+ module.exports = {
4
+ itemRevenue: generateItemRevenueAssertionSql,
5
+ };
@@ -0,0 +1,135 @@
1
+ const helpers = require('../../../helpers/index.js');
2
+ const utils = require('../../../utils.js');
3
+ const { ga4EventsEnhancedConfig } = require('../config.js');
4
+ const { validateEnhancedEventsConfig } = require('../validation.js');
5
+
6
+ const defaultConfig = { ...ga4EventsEnhancedConfig };
7
+
8
+ // Ecommerce events that carry item data (excluding refund — refunds reverse revenue
9
+ // and are handled separately in some pipelines, but item_revenue on refund rows
10
+ // should still reconcile 1:1 between enhanced and raw).
11
+ const ecommerceEvents = helpers.ga4EcommerceEvents.map(e => `'${e}'`).join(', ');
12
+
13
+ /**
14
+ * Builds a _table_suffix date filter for the assertion's raw-side query.
15
+ *
16
+ * Uses the low-level ga4ExportDateFilter() helper per enabled export type
17
+ * with a fixed 5-day lookback window. This is intentionally separate from
18
+ * the pipeline's ga4ExportDateFilters() which depends on incremental state
19
+ * and BigQuery pre-operation variables.
20
+ *
21
+ * @param {Object} includedExportTypes - { daily: boolean, fresh: boolean, intraday: boolean }
22
+ * @returns {string} SQL fragment for a WHERE clause
23
+ */
24
+ const buildAssertionDateFilter = (includedExportTypes) => {
25
+ const start = 'date_sub(current_date(), interval 5 day)';
26
+ const end = 'current_date()';
27
+
28
+ const filters = [
29
+ includedExportTypes.daily ? helpers.ga4ExportDateFilter('daily', start, end) : null,
30
+ includedExportTypes.fresh ? helpers.ga4ExportDateFilter('fresh', start, end) : null,
31
+ includedExportTypes.intraday ? helpers.ga4ExportDateFilter('intraday', start, end) : null,
32
+ ].filter(Boolean);
33
+
34
+ return filters.join(' or ');
35
+ };
36
+
37
+ /**
38
+ * Generates a SQL assertion query that reconciles item_revenue between the
39
+ * enhanced events table and the raw GA4 export data.
40
+ *
41
+ * The query compares item_revenue grouped by (event_date, item_id) for the
42
+ * last 5 days of final data. Returns mismatched rows — 0 rows means the
43
+ * assertion passes.
44
+ *
45
+ * @param {string} tableRef - Fully qualified reference to the enhanced table
46
+ * (e.g., ctx.ref('ga4_events_enhanced_123456789') in Dataform, or a backtick-quoted string).
47
+ * @param {Object} mergedConfig - Merged table configuration (after merge + validation).
48
+ * @returns {string} SQL query returning violating rows
49
+ */
50
+ const _generateItemRevenueAssertionSql = (tableRef, mergedConfig) => {
51
+ // excluded events filter (same logic as the enhanced table pipeline)
52
+ const excludedEvents = mergedConfig.excludedEvents;
53
+ const excludedEventsSQL = excludedEvents.length > 0
54
+ ? `and event_name not in (${excludedEvents.map(e => `'${e}'`).join(', ')})`
55
+ : '';
56
+
57
+ // data_is_final condition for the raw side
58
+ const dataIsFinalCondition = helpers.isFinalData(
59
+ mergedConfig.dataIsFinal.detectionMethod,
60
+ mergedConfig.dataIsFinal.dayThreshold
61
+ );
62
+
63
+ // date filter for the raw side (per-export-type, fixed 5-day window)
64
+ const dateFilter = buildAssertionDateFilter(mergedConfig.includedExportTypes);
65
+
66
+ return `with enhanced_revenue as (
67
+ select
68
+ event_date,
69
+ item.item_id,
70
+ sum(item.item_revenue) as total_item_revenue,
71
+ count(*) as item_count
72
+ from
73
+ ${tableRef},
74
+ unnest(items) as item
75
+ where
76
+ data_is_final = true
77
+ and event_date >= date_sub(current_date(), interval 5 day)
78
+ and event_name in (${ecommerceEvents})
79
+ group by event_date, item.item_id
80
+ ),
81
+ raw_revenue as (
82
+ select
83
+ cast(event_date as date format 'YYYYMMDD') as event_date,
84
+ item.item_id,
85
+ sum(item.item_revenue) as total_item_revenue,
86
+ count(*) as item_count
87
+ from
88
+ ${mergedConfig.sourceTable},
89
+ unnest(items) as item
90
+ where
91
+ (${dateFilter})
92
+ ${excludedEventsSQL}
93
+ and event_name in (${ecommerceEvents})
94
+ and ${dataIsFinalCondition}
95
+ and cast(event_date as date format 'YYYYMMDD') >= date_sub(current_date(), interval 5 day)
96
+ group by event_date, item.item_id
97
+ )
98
+ select
99
+ coalesce(e.event_date, r.event_date) as event_date,
100
+ coalesce(e.item_id, r.item_id) as item_id,
101
+ e.total_item_revenue as enhanced_revenue,
102
+ r.total_item_revenue as raw_revenue,
103
+ e.item_count as enhanced_count,
104
+ r.item_count as raw_count
105
+ from
106
+ enhanced_revenue e
107
+ full outer join
108
+ raw_revenue r using(event_date, item_id)
109
+ where
110
+ round(coalesce(e.total_item_revenue, 0), 2) != round(coalesce(r.total_item_revenue, 0), 2)
111
+ or e.item_count != r.item_count
112
+ or e.event_date is null
113
+ or r.event_date is null`;
114
+ };
115
+
116
+ /**
117
+ * Generates an item_revenue reconciliation assertion SQL query.
118
+ *
119
+ * Merges the provided config with defaults, validates, then generates a SQL
120
+ * query comparing item_revenue between the enhanced table and raw export data.
121
+ *
122
+ * @param {string} tableRef - Fully qualified reference to the enhanced table.
123
+ * @param {Object} config - User-provided table configuration.
124
+ * @returns {string} SQL query returning violating rows (0 rows = pass)
125
+ */
126
+ const generateItemRevenueAssertionSql = (tableRef, config) => {
127
+ if (!tableRef || typeof tableRef !== 'string' || !tableRef.trim()) {
128
+ throw new Error('assertions.itemRevenue: tableRef is required and must be a non-empty string (e.g., ctx.ref(\'table_name\') or \'`project.dataset.table`\').');
129
+ }
130
+ const mergedConfig = utils.mergeSQLConfigurations(defaultConfig, config);
131
+ validateEnhancedEventsConfig(mergedConfig, { skipDataformContextFields: true });
132
+ return _generateItemRevenueAssertionSql(tableRef, mergedConfig);
133
+ };
134
+
135
+ module.exports = { generateItemRevenueAssertionSql };
@@ -7,6 +7,7 @@ const { validateEnhancedEventsConfig } = require('./validation.js');
7
7
  const documentation = require('../../documentation.js');
8
8
  const { createTable } = require('../../createTable.js');
9
9
  const { getTableDescriptionSections } = require('./tableDescription.js');
10
+ const assertions = require('./assertions/index.js');
10
11
 
11
12
  // Column metadata for the GA4 Events Enhanced table
12
13
  const columnMetadata = {
@@ -421,5 +422,6 @@ module.exports = {
421
422
  generateSql: generateEnhancedEventsSQL,
422
423
  setPreOperations: setPreOperations,
423
424
  getColumnDescriptions: getColumnDescriptions,
424
- getTableDescription: getTableDescription
425
+ getTableDescription: getTableDescription,
426
+ assertions,
425
427
  }