span-data 0.4.0__cp311-abi3-win_amd64.whl
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.
- span_data/__init__.py +5 -0
- span_data/c360_compiler/__init__.pyd +0 -0
- span_data/c360_compiler/benchmark_flags.pyd +0 -0
- span_data/c360_compiler/c360_compiler.pyd +0 -0
- span_data/c360_compiler/c360_result.pyd +0 -0
- span_data/c360_compiler/c360_table_result.pyd +0 -0
- span_data/c360_compiler/io/__init__.pyd +0 -0
- span_data/c360_compiler/io/config_loader.pyd +0 -0
- span_data/c360_compiler/io/duckdb_catalogue.pyd +0 -0
- span_data/c360_compiler/io/duckdb_filters.pyd +0 -0
- span_data/c360_compiler/io/output_adapter.pyd +0 -0
- span_data/c360_compiler/sml/__init__.pyd +0 -0
- span_data/c360_compiler/sml/generator.pyd +0 -0
- span_data/c360_compiler/sml/schema.pyd +0 -0
- span_data/c360_compiler/table_names.pyd +0 -0
- span_data/c360_compiler/transform/__init__.pyd +0 -0
- span_data/c360_compiler/transform/consent.pyd +0 -0
- span_data/c360_compiler/transform/demographics.pyd +0 -0
- span_data/c360_compiler/transform/enrichment.pyd +0 -0
- span_data/c360_compiler/transform/metrics.pyd +0 -0
- span_data/c360_compiler/transform/segments.pyd +0 -0
- span_data/c360_compiler/transform/utils.pyd +0 -0
- span_data/c360_compiler/types.pyd +0 -0
- span_data/c360_compiler/validation/__init__.pyd +0 -0
- span_data/c360_compiler/validation/config_checks.pyd +0 -0
- span_data/c360_compiler/validation/output_checks.pyd +0 -0
- span_data/cli/__init__.py +106 -0
- span_data/cli/__main__.py +6 -0
- span_data/id_grapher/__init__.pyd +0 -0
- span_data/id_grapher/blocking_diagnostics.pyd +0 -0
- span_data/id_grapher/clean_data.pyd +0 -0
- span_data/id_grapher/constants.pyd +0 -0
- span_data/id_grapher/field_definition_map.pyd +0 -0
- span_data/id_grapher/graph_result.pyd +0 -0
- span_data/id_grapher/id_grapher.pyd +0 -0
- span_data/id_grapher/overrides.pyd +0 -0
- span_data/id_grapher/profile_assignment_sql.pyd +0 -0
- span_data/id_grapher/span_config.pyd +0 -0
- span_data/io/__init__.pyd +0 -0
- span_data/io/config/__init__.pyd +0 -0
- span_data/io/config/adapters.pyd +0 -0
- span_data/io/config/errors.pyd +0 -0
- span_data/io/config/examples/__init__.pyd +0 -0
- span_data/io/config/examples/data/ANALYTICS_EVENTS_EMAIL_EVENTS.csv +3 -0
- span_data/io/config/examples/data/ANALYTICS_EVENTS_ORDER_EVENTS.csv +4 -0
- span_data/io/config/examples/data/ANALYTICS_EVENTS_SESSION_EVENTS.csv +3 -0
- span_data/io/config/examples/data/ANALYTICS_SOURCES_CRM_CONTACTS.csv +2 -0
- span_data/io/config/examples/data/ANALYTICS_SOURCES_PORTAL_USERS.csv +2 -0
- span_data/io/config/examples/data/ANALYTICS_SOURCES_RETAIL_CUSTOMERS.csv +3 -0
- span_data/io/config/examples/data/__init__.pyd +0 -0
- span_data/io/config/examples/example_span_config.yaml +279 -0
- span_data/io/config/loaders.pyd +0 -0
- span_data/io/config/models.pyd +0 -0
- span_data/io/config/pipeline.pyd +0 -0
- span_data/io/config/validate.pyd +0 -0
- span_data/io/data_source.pyd +0 -0
- span_data/io/duckdb_connection.pyd +0 -0
- span_data/io/duckdb_table_io.pyd +0 -0
- span_data/io/metric_window.pyd +0 -0
- span_data/io/table_result.pyd +0 -0
- span_data/packaging_info/CHANGELOG.md +154 -0
- span_data/packaging_info/LICENSE +29 -0
- span_data/packaging_info/THIRD_PARTY_NOTICES.md +44 -0
- span_data/packaging_info/USAGE.md +434 -0
- span_data/packaging_info/__init__.py +0 -0
- span_data-0.4.0.dist-info/METADATA +109 -0
- span_data-0.4.0.dist-info/RECORD +71 -0
- span_data-0.4.0.dist-info/WHEEL +5 -0
- span_data-0.4.0.dist-info/entry_points.txt +2 -0
- span_data-0.4.0.dist-info/licenses/span_data/packaging_info/LICENSE +29 -0
- span_data-0.4.0.dist-info/top_level.txt +1 -0
span_data/__init__.py
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Command-line interface for span_data (SpanConfig validate and local runs)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from span_data.io.config import load_span_config
|
|
10
|
+
from span_data.io.config.errors import SpanConfigError
|
|
11
|
+
from span_data.io.config.pipeline import run_local_span_config
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _cmd_validate_config(path: Path) -> int:
|
|
15
|
+
load_span_config(path)
|
|
16
|
+
print(f"OK: {path}")
|
|
17
|
+
return 0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _cmd_run(args: argparse.Namespace) -> int:
|
|
21
|
+
span_config = load_span_config(args.config)
|
|
22
|
+
run_c360: bool | None = False if args.skip_c360 else None
|
|
23
|
+
run_local_span_config(
|
|
24
|
+
span_config,
|
|
25
|
+
args.data_dir,
|
|
26
|
+
args.output_dir,
|
|
27
|
+
app_name=args.app_name,
|
|
28
|
+
run_id_graph=not args.skip_id_graph,
|
|
29
|
+
run_c360=run_c360,
|
|
30
|
+
output_format=args.format,
|
|
31
|
+
data_extension=args.data_extension,
|
|
32
|
+
)
|
|
33
|
+
print(f"Finished. Outputs written to {args.output_dir}")
|
|
34
|
+
return 0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main(argv: list[str] | None = None) -> int:
|
|
38
|
+
parser = argparse.ArgumentParser(prog="span-data", description="SPAN span_data CLI")
|
|
39
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
40
|
+
|
|
41
|
+
validate_parser = subparsers.add_parser(
|
|
42
|
+
"validate-config",
|
|
43
|
+
help="Validate a SpanConfig YAML file",
|
|
44
|
+
)
|
|
45
|
+
validate_parser.add_argument("config", type=Path, help="Path to span_config.yaml")
|
|
46
|
+
|
|
47
|
+
run_parser = subparsers.add_parser(
|
|
48
|
+
"run",
|
|
49
|
+
help="Run ID Graph and/or C360 from SpanConfig using local data files",
|
|
50
|
+
)
|
|
51
|
+
run_parser.add_argument("config", type=Path, help="Path to span_config.yaml")
|
|
52
|
+
run_parser.add_argument(
|
|
53
|
+
"--data-dir",
|
|
54
|
+
type=Path,
|
|
55
|
+
required=True,
|
|
56
|
+
help="Directory of input CSV/Parquet files (named by derive_source_name(dataset))",
|
|
57
|
+
)
|
|
58
|
+
run_parser.add_argument(
|
|
59
|
+
"--output-dir",
|
|
60
|
+
type=Path,
|
|
61
|
+
required=True,
|
|
62
|
+
help="Directory for pipeline outputs",
|
|
63
|
+
)
|
|
64
|
+
run_parser.add_argument(
|
|
65
|
+
"--app-name",
|
|
66
|
+
default="LOCAL",
|
|
67
|
+
help="Application database name for C360 ID Graph view reference (default: LOCAL)",
|
|
68
|
+
)
|
|
69
|
+
run_parser.add_argument(
|
|
70
|
+
"--format",
|
|
71
|
+
choices=("csv", "parquet"),
|
|
72
|
+
default="csv",
|
|
73
|
+
help="C360 output file format (default: csv)",
|
|
74
|
+
)
|
|
75
|
+
run_parser.add_argument(
|
|
76
|
+
"--data-extension",
|
|
77
|
+
choices=("csv", "parquet"),
|
|
78
|
+
default="csv",
|
|
79
|
+
help="Input file extension under --data-dir (default: csv)",
|
|
80
|
+
)
|
|
81
|
+
run_parser.add_argument(
|
|
82
|
+
"--skip-id-graph",
|
|
83
|
+
action="store_true",
|
|
84
|
+
help="Skip ID Graph; requires data-dir/id_graph.csv when C360 is enabled",
|
|
85
|
+
)
|
|
86
|
+
run_parser.add_argument(
|
|
87
|
+
"--skip-c360",
|
|
88
|
+
action="store_true",
|
|
89
|
+
help="Run ID Graph only (no C360 compile)",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
args = parser.parse_args(argv)
|
|
93
|
+
try:
|
|
94
|
+
if args.command == "validate-config":
|
|
95
|
+
return _cmd_validate_config(args.config)
|
|
96
|
+
if args.command == "run":
|
|
97
|
+
return _cmd_run(args)
|
|
98
|
+
except (SpanConfigError, ValueError, FileNotFoundError) as exc:
|
|
99
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
100
|
+
return 1
|
|
101
|
+
parser.error(f"unknown command: {args.command}")
|
|
102
|
+
return 1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
raise SystemExit(main())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
CUSTOMER_ID,EMAIL,FIRST_NAME,LAST_NAME,PHONE,ZIP_CODE,STATE,CITY,UPDATED_AT,CREATED_AT
|
|
2
|
+
R1,alice@example.com,Alice,Anderson,555-0001,10001,NY,New York,2024-02-15 12:00:00+00:00,2023-06-01 00:00:00+00:00
|
|
3
|
+
R2,bob@example.com,Bob,Brown,555-0002,10002,CA,Los Angeles,2024-03-01 09:00:00+00:00,2024-01-15 00:00:00+00:00
|
|
Binary file
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# SpanConfig (illustrative example)
|
|
2
|
+
# Symmetric top-level: id_graph (identity resolution) + c360 (customer 360).
|
|
3
|
+
# Source identity is the qualified dataset name — no short aliases.
|
|
4
|
+
# id_graph output dataset is NOT authored — injected at runtime
|
|
5
|
+
# (Native App: {app}.APP_PUBLIC.{view_name}; free tier: path from ID Graph run).
|
|
6
|
+
|
|
7
|
+
schema_version: "2.0"
|
|
8
|
+
config_name: example_span_config
|
|
9
|
+
# Optional for community, mandatory for Snowflake Native app
|
|
10
|
+
app_settings:
|
|
11
|
+
view_name: example_span_config
|
|
12
|
+
|
|
13
|
+
id_graph:
|
|
14
|
+
linkage:
|
|
15
|
+
blocking_rules:
|
|
16
|
+
- columns: [useremail]
|
|
17
|
+
- columns: [useremail, firstname]
|
|
18
|
+
- columns: [phone, firstname]
|
|
19
|
+
|
|
20
|
+
sources:
|
|
21
|
+
- dataset: ANALYTICS.SOURCES.CRM_CONTACTS
|
|
22
|
+
id_column: CONTACT_ID
|
|
23
|
+
fields:
|
|
24
|
+
useremail: { column: EMAIL, type: email }
|
|
25
|
+
firstname: { column: FIRST_NAME, type: text }
|
|
26
|
+
lastname: { column: LAST_NAME, type: text }
|
|
27
|
+
gender: { column: GENDER, type: text }
|
|
28
|
+
birthdate: { column: BIRTH_DATE, type: text }
|
|
29
|
+
zipcode: { column: ZIP_CODE, type: text }
|
|
30
|
+
state: { column: STATE, type: text }
|
|
31
|
+
phone: { column: PHONE, type: phonenumber }
|
|
32
|
+
|
|
33
|
+
- dataset: ANALYTICS.SOURCES.RETAIL_CUSTOMERS
|
|
34
|
+
id_column: CUSTOMER_ID
|
|
35
|
+
fields:
|
|
36
|
+
useremail: { column: EMAIL, type: email }
|
|
37
|
+
firstname: { column: FIRST_NAME, type: text }
|
|
38
|
+
lastname: { column: LAST_NAME, type: text }
|
|
39
|
+
phone: { column: PHONE, type: phonenumber }
|
|
40
|
+
zipcode: { column: ZIP_CODE, type: text }
|
|
41
|
+
state: { column: STATE, type: text }
|
|
42
|
+
city: { column: CITY, type: text }
|
|
43
|
+
|
|
44
|
+
- dataset: ANALYTICS.SOURCES.PORTAL_USERS
|
|
45
|
+
id_column: USER_ID
|
|
46
|
+
fields:
|
|
47
|
+
useremail: { column: EMAIL, type: email }
|
|
48
|
+
firstname: { column: FIRST_NAME, type: text }
|
|
49
|
+
lastname: { column: LAST_NAME, type: text }
|
|
50
|
+
zipcode: { column: ZIP_CODE, type: text }
|
|
51
|
+
|
|
52
|
+
# Optional ID Graph inputs (grant consumer tables before use)
|
|
53
|
+
# overrides:
|
|
54
|
+
# dataset: ANALYTICS.SOURCES.ID_GRAPH_OVERRIDES
|
|
55
|
+
# seed:
|
|
56
|
+
# dataset: ANALYTICS.SOURCES.ID_GRAPH_SEED
|
|
57
|
+
|
|
58
|
+
c360:
|
|
59
|
+
enabled: true
|
|
60
|
+
|
|
61
|
+
# Demographics keyed by id_graph source dataset
|
|
62
|
+
demographics:
|
|
63
|
+
ANALYTICS.SOURCES.CRM_CONTACTS:
|
|
64
|
+
email: EMAIL
|
|
65
|
+
first_name: FIRST_NAME
|
|
66
|
+
last_name: LAST_NAME
|
|
67
|
+
gender: GENDER
|
|
68
|
+
birthdate: BIRTH_DATE
|
|
69
|
+
zip_code: ZIP_CODE
|
|
70
|
+
state: STATE
|
|
71
|
+
phone: PHONE
|
|
72
|
+
updated_at: UPDATED_AT
|
|
73
|
+
effective_from: CREATED_AT
|
|
74
|
+
ANALYTICS.SOURCES.RETAIL_CUSTOMERS:
|
|
75
|
+
email: EMAIL
|
|
76
|
+
first_name: FIRST_NAME
|
|
77
|
+
last_name: LAST_NAME
|
|
78
|
+
phone: PHONE
|
|
79
|
+
zip_code: ZIP_CODE
|
|
80
|
+
city: CITY
|
|
81
|
+
state: STATE
|
|
82
|
+
updated_at: UPDATED_AT
|
|
83
|
+
effective_from: CREATED_AT
|
|
84
|
+
ANALYTICS.SOURCES.PORTAL_USERS:
|
|
85
|
+
email: EMAIL
|
|
86
|
+
first_name: FIRST_NAME
|
|
87
|
+
last_name: LAST_NAME
|
|
88
|
+
zip_code: ZIP_CODE
|
|
89
|
+
updated_at: UPDATED_AT
|
|
90
|
+
effective_from: CREATED_AT
|
|
91
|
+
|
|
92
|
+
sources:
|
|
93
|
+
- dataset: ANALYTICS.EVENTS.EMAIL_EVENTS
|
|
94
|
+
id_column: CRM_CONTACT_ID
|
|
95
|
+
date_column: SENT_AT
|
|
96
|
+
dim_source: ANALYTICS.SOURCES.CRM_CONTACTS
|
|
97
|
+
suffix: email_sends
|
|
98
|
+
|
|
99
|
+
- dataset: ANALYTICS.EVENTS.ORDER_EVENTS
|
|
100
|
+
id_column: RETAIL_CUSTOMER_ID
|
|
101
|
+
date_column: ORDER_AT
|
|
102
|
+
dim_source: ANALYTICS.SOURCES.RETAIL_CUSTOMERS
|
|
103
|
+
suffix: orders
|
|
104
|
+
|
|
105
|
+
- dataset: ANALYTICS.EVENTS.SESSION_EVENTS
|
|
106
|
+
id_column: PORTAL_USER_ID
|
|
107
|
+
date_column: SESSION_START_AT
|
|
108
|
+
dim_source: ANALYTICS.SOURCES.PORTAL_USERS
|
|
109
|
+
suffix: web_sessions
|
|
110
|
+
|
|
111
|
+
precedence:
|
|
112
|
+
default:
|
|
113
|
+
strategy: coalesce
|
|
114
|
+
sources:
|
|
115
|
+
- ANALYTICS.SOURCES.CRM_CONTACTS
|
|
116
|
+
- ANALYTICS.SOURCES.RETAIL_CUSTOMERS
|
|
117
|
+
- ANALYTICS.SOURCES.PORTAL_USERS
|
|
118
|
+
zip_code:
|
|
119
|
+
strategy: coalesce
|
|
120
|
+
sources:
|
|
121
|
+
- ANALYTICS.SOURCES.RETAIL_CUSTOMERS
|
|
122
|
+
- ANALYTICS.SOURCES.CRM_CONTACTS
|
|
123
|
+
- ANALYTICS.SOURCES.PORTAL_USERS
|
|
124
|
+
city:
|
|
125
|
+
strategy: coalesce
|
|
126
|
+
sources:
|
|
127
|
+
- ANALYTICS.SOURCES.RETAIL_CUSTOMERS
|
|
128
|
+
- ANALYTICS.SOURCES.CRM_CONTACTS
|
|
129
|
+
- ANALYTICS.SOURCES.PORTAL_USERS
|
|
130
|
+
state:
|
|
131
|
+
strategy: coalesce
|
|
132
|
+
sources:
|
|
133
|
+
- ANALYTICS.SOURCES.RETAIL_CUSTOMERS
|
|
134
|
+
- ANALYTICS.SOURCES.CRM_CONTACTS
|
|
135
|
+
- ANALYTICS.SOURCES.PORTAL_USERS
|
|
136
|
+
updated_at:
|
|
137
|
+
strategy: greatest
|
|
138
|
+
sources:
|
|
139
|
+
- ANALYTICS.SOURCES.CRM_CONTACTS
|
|
140
|
+
- ANALYTICS.SOURCES.RETAIL_CUSTOMERS
|
|
141
|
+
- ANALYTICS.SOURCES.PORTAL_USERS
|
|
142
|
+
effective_from:
|
|
143
|
+
strategy: least
|
|
144
|
+
sources:
|
|
145
|
+
- ANALYTICS.SOURCES.CRM_CONTACTS
|
|
146
|
+
- ANALYTICS.SOURCES.RETAIL_CUSTOMERS
|
|
147
|
+
- ANALYTICS.SOURCES.PORTAL_USERS
|
|
148
|
+
|
|
149
|
+
consent:
|
|
150
|
+
conflict_precedence: [denied, unknown, granted]
|
|
151
|
+
mappings:
|
|
152
|
+
- source: ANALYTICS.SOURCES.CRM_CONTACTS
|
|
153
|
+
effective_date: CREATED_AT
|
|
154
|
+
purpose: promotional
|
|
155
|
+
medium: email
|
|
156
|
+
status: granted
|
|
157
|
+
- source: ANALYTICS.SOURCES.CRM_CONTACTS
|
|
158
|
+
effective_date: CREATED_AT
|
|
159
|
+
purpose: promotional
|
|
160
|
+
medium: sms
|
|
161
|
+
status: granted
|
|
162
|
+
- source: ANALYTICS.EVENTS.ORDER_EVENTS
|
|
163
|
+
effective_date: ORDER_AT
|
|
164
|
+
purpose: transactional
|
|
165
|
+
medium: email
|
|
166
|
+
status: granted
|
|
167
|
+
|
|
168
|
+
output_names:
|
|
169
|
+
dimensions: dim_c360_demographic_example_span_config
|
|
170
|
+
metrics: dim_c360_metric_example_span_config
|
|
171
|
+
consent: fct_c360_consent_example_span_config
|
|
172
|
+
segments: dim_c360_segment_example_span_config
|
|
173
|
+
fact: fct_c360_example_span_config_
|
|
174
|
+
|
|
175
|
+
metrics:
|
|
176
|
+
ltv_6m:
|
|
177
|
+
source: ANALYTICS.EVENTS.ORDER_EVENTS
|
|
178
|
+
measure: "SUM(CASE WHEN ORDER_STATUS = 'refund' THEN 0 ELSE ORDER_AMOUNT END)"
|
|
179
|
+
window: 6m
|
|
180
|
+
ltv_12m:
|
|
181
|
+
source: ANALYTICS.EVENTS.ORDER_EVENTS
|
|
182
|
+
measure: "SUM(CASE WHEN ORDER_STATUS = 'refund' THEN 0 ELSE ORDER_AMOUNT END)"
|
|
183
|
+
window: 12m
|
|
184
|
+
ltv_lifetime:
|
|
185
|
+
source: ANALYTICS.EVENTS.ORDER_EVENTS
|
|
186
|
+
measure: "SUM(CASE WHEN ORDER_STATUS = 'refund' THEN 0 ELSE ORDER_AMOUNT END)"
|
|
187
|
+
window: lifetime
|
|
188
|
+
order_count:
|
|
189
|
+
source: ANALYTICS.EVENTS.ORDER_EVENTS
|
|
190
|
+
measure: "CASE WHEN COUNT(ORDER_ID) = 0 THEN NULL ELSE COUNT(DISTINCT ORDER_ID) END"
|
|
191
|
+
window: lifetime
|
|
192
|
+
last_purchase_at:
|
|
193
|
+
source: ANALYTICS.EVENTS.ORDER_EVENTS
|
|
194
|
+
measure: "MAX(ORDER_AT)"
|
|
195
|
+
window: lifetime
|
|
196
|
+
email_send_count:
|
|
197
|
+
source: ANALYTICS.EVENTS.EMAIL_EVENTS
|
|
198
|
+
measure: "CASE WHEN COUNT(EVENT_ID) = 0 THEN NULL ELSE COUNT(DISTINCT EVENT_ID) END"
|
|
199
|
+
window: lifetime
|
|
200
|
+
email_open_count:
|
|
201
|
+
source: ANALYTICS.EVENTS.EMAIL_EVENTS
|
|
202
|
+
measure: "SUM(CASE WHEN OPENED = TRUE THEN 1 ELSE 0 END)"
|
|
203
|
+
window: lifetime
|
|
204
|
+
email_click_count:
|
|
205
|
+
source: ANALYTICS.EVENTS.EMAIL_EVENTS
|
|
206
|
+
measure: "SUM(CASE WHEN CLICKED = TRUE THEN 1 ELSE 0 END)"
|
|
207
|
+
window: lifetime
|
|
208
|
+
last_email_opened_at:
|
|
209
|
+
source: ANALYTICS.EVENTS.EMAIL_EVENTS
|
|
210
|
+
measure: "MAX(CASE WHEN OPENED = TRUE THEN SENT_AT ELSE NULL END)"
|
|
211
|
+
window: lifetime
|
|
212
|
+
web_session_count:
|
|
213
|
+
source: ANALYTICS.EVENTS.SESSION_EVENTS
|
|
214
|
+
measure: "CASE WHEN COUNT(SESSION_ID) = 0 THEN NULL ELSE COUNT(DISTINCT SESSION_ID) END"
|
|
215
|
+
window: lifetime
|
|
216
|
+
web_session_engagement:
|
|
217
|
+
source: ANALYTICS.EVENTS.SESSION_EVENTS
|
|
218
|
+
measure: "SUM(SESSION_DURATION_SEC)"
|
|
219
|
+
window: lifetime
|
|
220
|
+
last_web_session_started_at:
|
|
221
|
+
source: ANALYTICS.EVENTS.SESSION_EVENTS
|
|
222
|
+
measure: "MAX(SESSION_START_AT)"
|
|
223
|
+
window: lifetime
|
|
224
|
+
email_open_rate:
|
|
225
|
+
source: derived
|
|
226
|
+
expression: "email_open_count / COALESCE(email_send_count, 1)"
|
|
227
|
+
email_click_rate:
|
|
228
|
+
source: derived
|
|
229
|
+
expression: "email_click_count / COALESCE(email_send_count, 1)"
|
|
230
|
+
avg_session_duration_seconds:
|
|
231
|
+
source: derived
|
|
232
|
+
expression: "web_session_engagement / COALESCE(web_session_count, 1)"
|
|
233
|
+
average_order_value:
|
|
234
|
+
source: derived
|
|
235
|
+
expression: "ltv_lifetime / COALESCE(order_count, 1)"
|
|
236
|
+
last_touched_at:
|
|
237
|
+
source: derived
|
|
238
|
+
expression: "GREATEST(last_purchase_at, last_web_session_started_at, last_email_opened_at)"
|
|
239
|
+
customer_engagement_score:
|
|
240
|
+
source: derived
|
|
241
|
+
expression: >
|
|
242
|
+
(COALESCE(email_open_count, 0) / MAX(COALESCE(email_open_count, 0)) OVER()) * 25 +
|
|
243
|
+
(COALESCE(web_session_count, 0) / MAX(COALESCE(web_session_count, 0)) OVER()) * 25 +
|
|
244
|
+
(COALESCE(ltv_lifetime, 0) / MAX(COALESCE(ltv_lifetime, 0)) OVER()) * 50
|
|
245
|
+
|
|
246
|
+
segments:
|
|
247
|
+
is_crm_contact:
|
|
248
|
+
type: source_based
|
|
249
|
+
source: ANALYTICS.SOURCES.CRM_CONTACTS
|
|
250
|
+
is_retail_customer:
|
|
251
|
+
type: source_based
|
|
252
|
+
source: ANALYTICS.SOURCES.RETAIL_CUSTOMERS
|
|
253
|
+
is_portal_user:
|
|
254
|
+
type: source_based
|
|
255
|
+
source: ANALYTICS.SOURCES.PORTAL_USERS
|
|
256
|
+
is_active_customer:
|
|
257
|
+
type: filter_based
|
|
258
|
+
filter: "ltv_6m > 0"
|
|
259
|
+
is_new_customer:
|
|
260
|
+
type: filter_based
|
|
261
|
+
filter: "last_purchase_at >= NOW() - INTERVAL '7 days'"
|
|
262
|
+
value_tier_click_rate:
|
|
263
|
+
type: categorical_cohort
|
|
264
|
+
source_expression: email_click_rate
|
|
265
|
+
boundaries:
|
|
266
|
+
type: fixed_thresholds
|
|
267
|
+
bands:
|
|
268
|
+
- { label: low_click_rate, below: 0.01 }
|
|
269
|
+
- { label: medium_click_rate, below: 0.05 }
|
|
270
|
+
- { label: high_click_rate }
|
|
271
|
+
engagement_level:
|
|
272
|
+
type: categorical_cohort
|
|
273
|
+
source_expression: customer_engagement_score
|
|
274
|
+
boundaries:
|
|
275
|
+
type: percentile_thresholds
|
|
276
|
+
bands:
|
|
277
|
+
- { label: low_engagement, below: 0.20 }
|
|
278
|
+
- { label: medium_engagement, below: 0.80 }
|
|
279
|
+
- { label: high_engagement }
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|