-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathvariable_substitution.py
More file actions
216 lines (167 loc) · 6.7 KB
/
Copy pathvariable_substitution.py
File metadata and controls
216 lines (167 loc) · 6.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Variable Substitution - Replace %Variable% patterns with actual values"""
import re
from typing import Dict, List, Optional
import pandas as pd
def detect_variables(text: str) -> List[str]:
"""
Detect variables in message text using %Variable% pattern
Args:
text: Message text with variables
Returns:
List of unique variable names (without % symbols)
"""
pattern = r'%([a-zA-Z0-9_]+)%'
variables = re.findall(pattern, text)
# Return unique variables, preserving order
return list(dict.fromkeys(variables))
def substitute_variables(message: str, contact_data: Dict, variable_mapping: Dict[str, str]) -> str:
"""
Replace %Variable% placeholders with actual values from contact data
Args:
message: Message template with %Variable% placeholders
contact_data: Dictionary containing contact data (e.g., row from DataFrame)
variable_mapping: Mapping of variable names to CSV column names
Example: {'Name': 'Artist Name', 'Location': 'City'}
Returns:
Message with variables replaced
Example:
message = "Hi %Name%, love your music from %Location%!"
contact_data = {'Artist Name': 'John', 'City': 'LA'}
variable_mapping = {'Name': 'Artist Name', 'Location': 'City'}
Result: "Hi John, love your music from LA!"
"""
result = message
# Replace each variable
for variable, csv_column in variable_mapping.items():
placeholder = f'%{variable}%'
# Skip if column not in contact data
if csv_column not in contact_data:
# Remove the placeholder
result = result.replace(placeholder, '')
continue
# Get value from contact data
value = str(contact_data[csv_column]).strip()
# Handle empty or NaN values
if value and value.lower() not in ['nan', 'none', '']:
result = result.replace(placeholder, value)
else:
# Remove placeholder if no value
result = result.replace(placeholder, '')
# Clean up extra spaces created by removed variables
result = ' '.join(result.split())
return result
def create_auto_mapping(variables: List[str], df_columns: List[str]) -> Dict[str, str]:
"""
Automatically create variable mapping by matching variable names to column names
Args:
variables: List of variable names from campaign
df_columns: List of column names from DataFrame
Returns:
Dictionary mapping variables to columns
Variables that can't be auto-matched are excluded
Example:
variables = ['Name', 'Location', 'Genre']
df_columns = ['Instagram', 'Artist Name', 'City', 'Music Genre']
Result: {'Name': 'Artist Name', 'Location': 'City', 'Genre': 'Music Genre'}
"""
mapping = {}
# Create lowercase lookup
column_lookup = {col.lower(): col for col in df_columns}
for variable in variables:
var_lower = variable.lower()
# Try exact match (case-insensitive)
if var_lower in column_lookup:
mapping[variable] = column_lookup[var_lower]
continue
# Try partial match - check if variable is in any column name
found = False
for col_lower, col_original in column_lookup.items():
if var_lower in col_lower or col_lower in var_lower:
mapping[variable] = col_original
found = True
break
# If still not found, skip this variable
# User will need to manually map it
return mapping
def validate_mapping(variables: List[str], df_columns: List[str],
variable_mapping: Dict[str, str]) -> tuple[bool, List[str]]:
"""
Validate that all variables can be mapped to DataFrame columns
Args:
variables: List of variable names from campaign
df_columns: List of column names from DataFrame
variable_mapping: Proposed mapping of variables to columns
Returns:
(is_valid, list_of_missing_variables)
Example:
If mapping is incomplete, returns (False, ['Genre', 'EventName'])
If all variables mapped, returns (True, [])
"""
missing_variables = []
for variable in variables:
# Check if variable is mapped
if variable not in variable_mapping:
missing_variables.append(variable)
continue
# Check if mapped column exists in DataFrame
mapped_column = variable_mapping[variable]
if mapped_column not in df_columns:
missing_variables.append(variable)
is_valid = len(missing_variables) == 0
return is_valid, missing_variables
def preview_substitution(message: str, df: pd.DataFrame,
variable_mapping: Dict[str, str],
num_samples: int = 3) -> List[Dict]:
"""
Generate preview of substituted messages for sample contacts
Args:
message: Message template
df: DataFrame with contact data
variable_mapping: Variable to column mapping
num_samples: Number of samples to generate
Returns:
List of preview dictionaries with 'original', 'substituted', 'contact'
"""
previews = []
# Get sample rows
sample_df = df.head(min(num_samples, len(df)))
for idx, row in sample_df.iterrows():
contact_data = row.to_dict()
substituted = substitute_variables(message, contact_data, variable_mapping)
previews.append({
'original': message,
'substituted': substituted,
'contact': contact_data.get('Instagram', f'Contact {idx + 1}')
})
return previews
def get_missing_variable_info(variables: List[str],
df_columns: List[str],
variable_mapping: Dict[str, str]) -> Dict:
"""
Get detailed information about missing or unmapped variables
Args:
variables: List of variable names from campaign
df_columns: List of column names from DataFrame
variable_mapping: Current variable to column mapping
Returns:
Dictionary with 'missing', 'unmapped', and 'available_columns'
"""
missing = []
unmapped = []
for variable in variables:
if variable not in variable_mapping:
unmapped.append(variable)
else:
mapped_column = variable_mapping[variable]
if mapped_column not in df_columns:
missing.append({
'variable': variable,
'mapped_to': mapped_column
})
return {
'missing': missing,
'unmapped': unmapped,
'available_columns': df_columns
}