Danialebrat commited on
Commit
5700222
·
1 Parent(s): f3f197e

- Keeping memory of previous instructions

Browse files

- avoid to log in for regenerating new messages
- generating new messages more straightforward

Files changed (4) hide show
  1. Messaging_system/Permes.py +2 -2
  2. app.py +81 -81
  3. app_test.py +395 -0
  4. messaging_main_test.py +6 -7
Messaging_system/Permes.py CHANGED
@@ -99,8 +99,8 @@ class Permes:
99
  total_prompt_tokens = personalize_message.total_tokens["prompt_tokens"]
100
  total_completion_tokens = personalize_message.total_tokens["completion_tokens"]
101
 
102
- total_cost = ((total_prompt_tokens / 1000000) * 0.15) + (
103
- (total_completion_tokens / 1000000) * 0.6) # Cost calculation estimation
104
  print(f"Estimated Cost (USD): {total_cost:.5f}")
105
 
106
  # Storing process can also happen after some evaluation steps
 
99
  total_prompt_tokens = personalize_message.total_tokens["prompt_tokens"]
100
  total_completion_tokens = personalize_message.total_tokens["completion_tokens"]
101
 
102
+ total_cost = ((total_prompt_tokens / 1000000) * 0.10) + (
103
+ (total_completion_tokens / 1000000) * 0.4) # Cost calculation estimation
104
  print(f"Estimated Cost (USD): {total_cost:.5f}")
105
 
106
  # Storing process can also happen after some evaluation steps
app.py CHANGED
@@ -28,6 +28,7 @@ def get_credential(key):
28
  # Authorized emails
29
  AUTHORIZED_EMAILS = {
30
  "danial@musora.com",
 
31
  "simon@musora.com",
32
  "una@musora.com",
33
  "mark@musora.com",
@@ -37,7 +38,7 @@ AUTHORIZED_EMAILS = {
37
 
38
 
39
  # --- one env var that holds the shared password / token ---------------------
40
- VALID_TOKEN = get_credential("APP_TOKEN") # e.g. APP_TOKEN="My$ecret42"
41
 
42
  # --- small utility -----------------------------------------------------------
43
  def verify_login(email: str, token: str) -> bool:
@@ -205,15 +206,20 @@ with st.sidebar:
205
  )
206
 
207
  st.markdown("---")
208
- generate = st.button("🚀 Generate messages")
209
- st.session_state["generate_clicked"] = generate
 
 
 
 
 
210
 
211
 
212
  # ──────────────────────────────────────────────────────────────────────────────
213
  # MAIN AREA – three tabs
214
  # ──────────────────────────────────────────────────────────────────────────────
215
- tab0, tab1, tab2 = st.tabs(
216
- ["📊 Data preview", "🛠️ Configure", "📨 Results"])
217
 
218
  # ------------------------------------------------------------------ TAB 0 ---#
219
  with tab0:
@@ -223,15 +229,6 @@ with tab0:
223
  else:
224
  st.info("Upload a CSV to preview it here.")
225
 
226
- # ------------------------------------------------------------------ TAB 1 ---#
227
- with tab1:
228
- st.header("🛠️ Configure & launch")
229
- if st.session_state.data is None:
230
- st.warning("Upload a CSV first ⬅")
231
- elif not generate:
232
- st.info("Adjust settings in the sidebar, then hit *Generate*.")
233
- else:
234
- st.success("Parameters captured – see **Results** tab.")
235
 
236
  # ------------------------------------------------------------------ TAB 2 ---#
237
  with tab2:
@@ -244,77 +241,80 @@ with tab2:
244
  st.error("CTA, Segment info, and brand are mandatory 🚫")
245
  st.stop()
246
 
247
- # build Snowflake session
248
- conn = dict(
249
- user=get_credential("snowflake_user"),
250
- password=get_credential("snowflake_password"),
251
- account=get_credential("snowflake_account"),
252
- role=get_credential("snowflake_role"),
253
- database=get_credential("snowflake_database"),
254
- warehouse=get_credential("snowflake_warehouse"),
255
- schema=get_credential("snowflake_schema")
256
- )
257
- config = load_config_("Config_files/message_system_config.json")
258
- session = Session.builder.configs(conn).create()
 
 
259
 
260
- # ─ prepare parameters
261
- st.session_state.messaging_mode = (
262
- "recsys_result" if st.session_state.include_recommendation
263
- else "message"
264
- )
265
- st.session_state.involve_recsys_result = st.session_state.include_recommendation
266
- st.session_state.instructionset = {
267
- i: st.session_state.get(f"instr_{i}")
268
- for i in range(1, st.session_state.number_of_messages + 1)
269
- if st.session_state.get(f"instr_{i}", "").strip()
270
- }
271
-
272
- # ─ progress callback
273
- prog = st.progress(0)
274
- status = st.empty()
275
-
276
- def cb(done, total):
277
- pct = int(done / total * 100)
278
- prog.progress(pct)
279
- status.write(f"{pct}%")
280
-
281
- permes = Permes()
282
- df_msg = permes.create_personalize_messages(
283
- session=session,
284
- users=st.session_state.data,
285
- brand=st.session_state.brand,
286
- config_file=config,
287
- openai_api_key=get_credential("OPENAI_API"),
288
- CTA=st.session_state.CTA,
289
- segment_info=st.session_state.segment_info,
290
- number_of_samples=st.session_state.number_of_samples,
291
- message_style=st.session_state.message_style,
292
- sample_example=st.session_state.sample_example,
293
- selected_input_features=st.session_state.selected_features,
294
- selected_source_features=st.session_state.selected_source_features,
295
- additional_instructions=st.session_state.additional_instructions,
296
- platform=st.session_state.messaging_type,
297
- involve_recsys_result=st.session_state.involve_recsys_result,
298
- messaging_mode=st.session_state.messaging_mode,
299
- identifier_column=st.session_state.identifier_column,
300
- target_column=st.session_state.target_column,
301
- recsys_contents=st.session_state.recsys_contents,
302
- progress_callback=cb,
303
- number_of_messages=st.session_state.number_of_messages,
304
- instructionset=st.session_state.instructionset,
305
- segment_name=st.session_state.segment_name
306
- )
307
 
308
- # ─ cache output
309
- st.session_state.users_message = df_msg
310
- st.session_state.csv_output = df_msg.to_csv(
311
- index=False, encoding="utf-8-sig")
312
- st.session_state.generated = True
313
- prog.empty(); status.empty()
314
- st.balloons()
 
315
 
316
  # =============================================================
317
- if st.session_state.generated:
318
  df = st.session_state.users_message
319
  id_col = st.session_state.identifier_column or ""
320
  id_col_lower = id_col.lower()
 
28
  # Authorized emails
29
  AUTHORIZED_EMAILS = {
30
  "danial@musora.com",
31
+ "danial.ebrat@gmail.com",
32
  "simon@musora.com",
33
  "una@musora.com",
34
  "mark@musora.com",
 
38
 
39
 
40
  # --- one env var that holds the shared password / token ---------------------
41
+ VALID_TOKEN = get_credential("APP_TOKEN")
42
 
43
  # --- small utility -----------------------------------------------------------
44
  def verify_login(email: str, token: str) -> bool:
 
206
  )
207
 
208
  st.markdown("---")
209
+
210
+ if st.button("🚀 Generate messages", key="generate"):
211
+ st.session_state.generate_clicked = True # ask for a new run
212
+ st.session_state.generated = False # forget old results
213
+
214
+ # generate = st.button("🚀 Generate messages")
215
+ # st.session_state["generate_clicked"] = generate
216
 
217
 
218
  # ──────────────────────────────────────────────────────────────────────────────
219
  # MAIN AREA – three tabs
220
  # ──────────────────────────────────────────────────────────────────────────────
221
+ tab0, tab2 = st.tabs(
222
+ ["📊 Data preview", "📨 Results"])
223
 
224
  # ------------------------------------------------------------------ TAB 0 ---#
225
  with tab0:
 
229
  else:
230
  st.info("Upload a CSV to preview it here.")
231
 
 
 
 
 
 
 
 
 
 
232
 
233
  # ------------------------------------------------------------------ TAB 2 ---#
234
  with tab2:
 
241
  st.error("CTA, Segment info, and brand are mandatory 🚫")
242
  st.stop()
243
 
244
+ if st.session_state.get("generate_clicked", False):
245
+
246
+ # ─ build Snowflake session
247
+ conn = dict(
248
+ user=get_credential("snowflake_user"),
249
+ password=get_credential("snowflake_password"),
250
+ account=get_credential("snowflake_account"),
251
+ role=get_credential("snowflake_role"),
252
+ database=get_credential("snowflake_database"),
253
+ warehouse=get_credential("snowflake_warehouse"),
254
+ schema=get_credential("snowflake_schema")
255
+ )
256
+ config = load_config_("Config_files/message_system_config.json")
257
+ session = Session.builder.configs(conn).create()
258
 
259
+ # ─ prepare parameters
260
+ st.session_state.messaging_mode = (
261
+ "recsys_result" if st.session_state.include_recommendation
262
+ else "message"
263
+ )
264
+ st.session_state.involve_recsys_result = st.session_state.include_recommendation
265
+ st.session_state.instructionset = {
266
+ i: st.session_state.get(f"instr_{i}")
267
+ for i in range(1, st.session_state.number_of_messages + 1)
268
+ if st.session_state.get(f"instr_{i}", "").strip()
269
+ }
270
+
271
+ # ─ progress callback
272
+ prog = st.progress(0)
273
+ status = st.empty()
274
+
275
+ def cb(done, total):
276
+ pct = int(done / total * 100)
277
+ prog.progress(pct)
278
+ status.write(f"{pct}%")
279
+
280
+ permes = Permes()
281
+ df_msg = permes.create_personalize_messages(
282
+ session=session,
283
+ users=st.session_state.data,
284
+ brand=st.session_state.brand,
285
+ config_file=config,
286
+ openai_api_key=get_credential("OPENAI_API"),
287
+ CTA=st.session_state.CTA,
288
+ segment_info=st.session_state.segment_info,
289
+ number_of_samples=st.session_state.number_of_samples,
290
+ message_style=st.session_state.message_style,
291
+ sample_example=st.session_state.sample_example,
292
+ selected_input_features=st.session_state.selected_features,
293
+ selected_source_features=st.session_state.selected_source_features,
294
+ additional_instructions=st.session_state.additional_instructions,
295
+ platform=st.session_state.messaging_type,
296
+ involve_recsys_result=st.session_state.involve_recsys_result,
297
+ messaging_mode=st.session_state.messaging_mode,
298
+ identifier_column=st.session_state.identifier_column,
299
+ target_column=st.session_state.target_column,
300
+ recsys_contents=st.session_state.recsys_contents,
301
+ progress_callback=cb,
302
+ number_of_messages=st.session_state.number_of_messages,
303
+ instructionset=st.session_state.instructionset,
304
+ segment_name=st.session_state.segment_name
305
+ )
306
 
307
+ # ─ cache output
308
+ st.session_state.users_message = df_msg
309
+ st.session_state.csv_output = df_msg.to_csv(
310
+ index=False, encoding="utf-8-sig")
311
+ st.session_state.generated = True
312
+ st.session_state.generate_clicked = False
313
+ prog.empty(); status.empty()
314
+ st.balloons()
315
 
316
  # =============================================================
317
+ if st.session_state.get("generated", False):
318
  df = st.session_state.users_message
319
  id_col = st.session_state.identifier_column or ""
320
  id_col_lower = id_col.lower()
app_test.py ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json, os
2
+ from io import StringIO
3
+ import pandas as pd
4
+ import streamlit as st
5
+ from snowflake.snowpark import Session
6
+ from bs4 import BeautifulSoup
7
+ from Messaging_system.Permes import Permes
8
+ from dotenv import load_dotenv
9
+ load_dotenv()
10
+
11
+
12
+ # ──────────────────────────────────────────────────────────────────────────────
13
+ # Helpers
14
+ # ──────────────────────────────────────────────────────────────────────────────
15
+ @st.cache_data
16
+ def load_data(buf) -> pd.DataFrame:
17
+ return pd.read_csv(buf)
18
+
19
+ def load_config_(file_path: str) -> dict:
20
+ with open(file_path) as f:
21
+ return json.load(f)
22
+
23
+ def get_credential(key):
24
+ return os.getenv(key) or st.secrets.get(key)
25
+
26
+
27
+
28
+ # Authorized emails
29
+ AUTHORIZED_EMAILS = {
30
+ "danial@musora.com",
31
+ "danial.ebrat@gmail.com",
32
+ "simon@musora.com",
33
+ "una@musora.com",
34
+ "mark@musora.com",
35
+ "gabriel@musora.com",
36
+ "nikki@musora.com"
37
+ }
38
+
39
+
40
+ # --- one env var that holds the shared password / token ---------------------
41
+ VALID_TOKEN = get_credential("APP_TOKEN")
42
+
43
+ # --- small utility -----------------------------------------------------------
44
+ def verify_login(email: str, token: str) -> bool:
45
+ """True ⇢ both the address and the token are valid."""
46
+ return (email.lower().strip() in AUTHORIZED_EMAILS) and (token == VALID_TOKEN)
47
+
48
+ # --- UI: show a form until the user is authenticated ------------------------
49
+ if not st.session_state.get("authenticated", False):
50
+
51
+ st.title("🔐 Sign-in")
52
+ st.markdown(
53
+ "Access is limited to authorised users. "
54
+ "Enter your **e-mail** and the shared **access token**."
55
+ )
56
+
57
+ with st.form("login"):
58
+ email = st.text_input("e-mail")
59
+ token = st.text_input("access token", type="password")
60
+ submitted = st.form_submit_button("Log in")
61
+
62
+ if submitted:
63
+ if verify_login(email, token):
64
+ st.session_state.authenticated = True
65
+ st.session_state.user_email = email
66
+ st.success("Login successful – redirecting…")
67
+ st.rerun() # reloads the app, now “inside”
68
+ else:
69
+ st.error("⛔ Invalid e-mail or token")
70
+
71
+ # IMPORTANT: stop executing the rest of the app while not logged-in
72
+ st.stop()
73
+
74
+
75
+
76
+
77
+ def init_state() -> None:
78
+ defaults = dict(
79
+ involve_recsys_result=False,
80
+ involve_last_interaction=False,
81
+ valid_instructions="",
82
+ invalid_instructions="",
83
+ messaging_type="push",
84
+ generated=False,
85
+ include_recommendation=False,
86
+ data=None, brand=None, recsys_contents=[], csv_output=None,
87
+ users_message=None, messaging_mode=None, target_column=None,
88
+ ugc_column=None, identifier_column=None, input_validator=None,
89
+ selected_input_features=None, selected_features=None,
90
+ additional_instructions=None, segment_info="", message_style="",
91
+ sample_example="", CTA="", all_features=None, number_of_messages=1,
92
+ instructionset={}, segment_name="", number_of_samples=10,
93
+ selected_source_features=[], platform=None, generate_clicked=False,
94
+ )
95
+ for k, v in defaults.items():
96
+ st.session_state.setdefault(k, v)
97
+
98
+ # ──────────────────────────────────────────────────────────────────────────────
99
+ # PAGE CONFIG + THEME
100
+ # ──────────────────────────────────────────────────────────────────────────────
101
+ st.set_page_config(
102
+ page_title="Personalized Message Generator",
103
+ page_icon="📬",
104
+ layout="wide",
105
+ initial_sidebar_state="expanded"
106
+ )
107
+
108
+ st.markdown(
109
+ """
110
+ <style>
111
+ html, body, [class*="css"] {
112
+ background-color:#0d0d0d;
113
+ color:#ffd700;
114
+ }
115
+ .stButton>button, .stDownloadButton>button {
116
+ border-radius:8px;
117
+ background:#ffd700;
118
+ color:#0d0d0d;
119
+ font-weight:600;
120
+ }
121
+ .stTabs [data-baseweb="tab"] {
122
+ font-weight:600;
123
+ }
124
+ .stTabs [aria-selected="true"] {
125
+ color:#ffd700;
126
+ }
127
+ h1, h2, h3 {color:#ffd700;}
128
+ .small {font-size:0.85rem; opacity:0.7;}
129
+ </style>
130
+ """,
131
+ unsafe_allow_html=True
132
+ )
133
+
134
+ # ──────────────────────────────────────────────────────────────────────────────
135
+ # SIDEBAR – the “control panel”
136
+ # ─────────────���────────────────────────────────────────────────────────────────
137
+ init_state()
138
+ with st.sidebar:
139
+ # st.header("📂 Upload your CSV")
140
+ # uploaded_file = st.file_uploader("Choose file", type="csv")
141
+ uploaded_file = "Data/Singeo_Camp.csv"
142
+ if uploaded_file:
143
+ st.session_state.data = load_data(uploaded_file)
144
+ st.success("File loaded!")
145
+
146
+ st.markdown("---")
147
+
148
+ if st.session_state.data is not None:
149
+ # ─ Identifier
150
+ # id_col = st.selectbox(
151
+ # "Identifier column",
152
+ # st.session_state.data.columns,
153
+ # key="identifier_column"
154
+ # )
155
+ id_col = "user_id"
156
+ st.session_state.identifier_column = id_col
157
+
158
+
159
+ # ─ Brand
160
+ st.selectbox(
161
+ "Brand *",
162
+ ["drumeo", "pianote", "guitareo", "singeo"],
163
+ key="brand",
164
+ )
165
+
166
+ # ─ Personalisation
167
+ st.text_area("Segment info *", key="segment_info")
168
+ st.text_area("CTA (Call to Action) *", key="CTA")
169
+ with st.expander("🔧 Optional tone & examples"):
170
+ st.text_area("Message style", key="message_style",
171
+ placeholder="Be kind and friendly…")
172
+ st.text_area("Additional instructions", key="additional_instructions",
173
+ placeholder="e.g. Mention the number weeks since their last practice")
174
+ st.text_area("Sample example", key="sample_example",
175
+ placeholder="Hello! We have crafted…")
176
+ st.number_input("Number of samples (default = 10)", 1, 50,
177
+ key="number_of_samples")
178
+
179
+ # ─ Sequential messages
180
+ st.number_input("Sequential messages / user", 1, 12, 1,
181
+ key="number_of_messages")
182
+ st.text_input("Segment name", key="segment_name",
183
+ placeholder="no_recent_activity")
184
+ if st.session_state.number_of_messages > 1:
185
+ st.caption("Additional per-message instructions")
186
+ for i in range(1, st.session_state.number_of_messages + 1):
187
+ st.text_input(f"Message {i} instruction",
188
+ key=f"instr_{i}")
189
+
190
+ # ─ Source feature selection
191
+ st.multiselect(
192
+ "Source features",
193
+ ["instrument", "weeks_since_last_interaction",
194
+ "birthday_reminder"],
195
+ default=["instrument"],
196
+ key="selected_source_features"
197
+ )
198
+
199
+ # ─ Rec-sys
200
+ st.checkbox("Include content recommendation", key="include_recommendation")
201
+ if st.session_state.include_recommendation:
202
+ st.multiselect(
203
+ "Recommendation types",
204
+ ["song", "workout", "quick_tips", "course"],
205
+ key="recsys_contents"
206
+ )
207
+
208
+ st.markdown("---")
209
+
210
+ if st.button("🚀 Generate messages", key="generate"):
211
+ st.session_state.generate_clicked = True # ask for a new run
212
+ st.session_state.generated = False # forget old results
213
+
214
+ # generate = st.button("🚀 Generate messages")
215
+ # st.session_state["generate_clicked"] = generate
216
+
217
+
218
+ # ──────────────────────────────────────────────────────────────────────────────
219
+ # MAIN AREA – three tabs
220
+ # ──────────────────────────────────────────────────────────────────────────────
221
+ tab0, tab2 = st.tabs(
222
+ ["📊 Data preview", "📨 Results"])
223
+
224
+ # ------------------------------------------------------------------ TAB 0 ---#
225
+ with tab0:
226
+ st.header("📊 Data preview")
227
+ if st.session_state.data is not None:
228
+ st.dataframe(st.session_state.data.head(100))
229
+ else:
230
+ st.info("Upload a CSV to preview it here.")
231
+
232
+
233
+ # ------------------------------------------------------------------ TAB 2 ---#
234
+ with tab2:
235
+ st.header("📨 Generated messages")
236
+ # Run generation only once per click
237
+ if st.session_state.generate_clicked and not st.session_state.generated:
238
+
239
+ # ─ simple validation
240
+ if not st.session_state.CTA.strip() or not st.session_state.segment_info.strip() or not st.session_state.brand.strip():
241
+ st.error("CTA, Segment info, and brand are mandatory 🚫")
242
+ st.stop()
243
+
244
+ if st.session_state.get("generate_clicked", False):
245
+
246
+ # ─ build Snowflake session
247
+ conn = dict(
248
+ user=get_credential("snowflake_user"),
249
+ password=get_credential("snowflake_password"),
250
+ account=get_credential("snowflake_account"),
251
+ role=get_credential("snowflake_role"),
252
+ database=get_credential("snowflake_database"),
253
+ warehouse=get_credential("snowflake_warehouse"),
254
+ schema=get_credential("snowflake_schema")
255
+ )
256
+ config = load_config_("Config_files/message_system_config.json")
257
+ session = Session.builder.configs(conn).create()
258
+
259
+ # ─ prepare parameters
260
+ st.session_state.messaging_mode = (
261
+ "recsys_result" if st.session_state.include_recommendation
262
+ else "message"
263
+ )
264
+ st.session_state.involve_recsys_result = st.session_state.include_recommendation
265
+ st.session_state.instructionset = {
266
+ i: st.session_state.get(f"instr_{i}")
267
+ for i in range(1, st.session_state.number_of_messages + 1)
268
+ if st.session_state.get(f"instr_{i}", "").strip()
269
+ }
270
+
271
+ # ─ progress callback
272
+ prog = st.progress(0)
273
+ status = st.empty()
274
+
275
+ def cb(done, total):
276
+ pct = int(done / total * 100)
277
+ prog.progress(pct)
278
+ status.write(f"{pct}%")
279
+
280
+ permes = Permes()
281
+ df_msg = permes.create_personalize_messages(
282
+ session=session,
283
+ users=st.session_state.data,
284
+ brand=st.session_state.brand,
285
+ config_file=config,
286
+ openai_api_key=get_credential("OPENAI_API"),
287
+ CTA=st.session_state.CTA,
288
+ segment_info=st.session_state.segment_info,
289
+ number_of_samples=st.session_state.number_of_samples,
290
+ message_style=st.session_state.message_style,
291
+ sample_example=st.session_state.sample_example,
292
+ selected_input_features=st.session_state.selected_features,
293
+ selected_source_features=st.session_state.selected_source_features,
294
+ additional_instructions=st.session_state.additional_instructions,
295
+ platform=st.session_state.messaging_type,
296
+ involve_recsys_result=st.session_state.involve_recsys_result,
297
+ messaging_mode=st.session_state.messaging_mode,
298
+ identifier_column=st.session_state.identifier_column,
299
+ target_column=st.session_state.target_column,
300
+ recsys_contents=st.session_state.recsys_contents,
301
+ progress_callback=cb,
302
+ number_of_messages=st.session_state.number_of_messages,
303
+ instructionset=st.session_state.instructionset,
304
+ segment_name=st.session_state.segment_name
305
+ )
306
+
307
+ # ─ cache output
308
+ st.session_state.users_message = df_msg
309
+ st.session_state.csv_output = df_msg.to_csv(
310
+ index=False, encoding="utf-8-sig")
311
+ st.session_state.generated = True
312
+ st.session_state.generate_clicked = False
313
+ prog.empty(); status.empty()
314
+ st.balloons()
315
+
316
+ # =============================================================
317
+ if st.session_state.get("generated", False):
318
+ df = st.session_state.users_message
319
+ id_col = st.session_state.identifier_column or ""
320
+ id_col_lower = id_col.lower()
321
+
322
+ # expandable per-user cards
323
+ for i, (_, row) in enumerate(df.iterrows(), start=1):
324
+ user_id = row.get(id_col_lower, "(no ID)")
325
+ with st.expander(f"{i}. User ID: {user_id}", expanded=(i == 1)):
326
+ # --- Features
327
+ st.write("##### 👤 Features")
328
+ feats = st.session_state.selected_source_features or []
329
+ cols = st.columns(3)
330
+ for idx, feature in enumerate(feats):
331
+ val = row.get(feature, "—")
332
+ cols[idx % 3].markdown(f"**{feature}**: {val}")
333
+
334
+ st.markdown("---")
335
+
336
+ # --- Messages
337
+ st.write("##### ✉️ Messages")
338
+ raw = row.get("message", "")
339
+ # try to parse JSON if it's a str
340
+ if isinstance(raw, str):
341
+ try:
342
+ blob = json.loads(raw)
343
+ except json.JSONDecodeError:
344
+ st.error(f"Could not parse JSON for user {user_id}")
345
+ continue
346
+ elif isinstance(raw, dict) or isinstance(raw, list):
347
+ blob = raw
348
+ else:
349
+ blob = {}
350
+
351
+ # extract sequence
352
+ if isinstance(blob, dict):
353
+ seq = blob.get("messages_sequence", [])
354
+ elif isinstance(blob, list):
355
+ seq = blob
356
+ else:
357
+ seq = []
358
+
359
+ # make sure it's a list
360
+ if not isinstance(seq, list):
361
+ seq = [seq]
362
+
363
+ # render each message
364
+ for j, msg in enumerate(seq, start=1):
365
+ if not isinstance(msg, dict):
366
+ # if it's just a string or number, render it plainly
367
+ st.markdown(f"**{j}. (no header)**")
368
+ st.markdown(str(msg))
369
+ st.markdown("---")
370
+ continue
371
+
372
+ header = msg.get("header", "(no header)")
373
+ st.markdown(f"**{j}. {header}**")
374
+
375
+ # optional title
376
+ title = msg.get("title")
377
+ if title:
378
+ st.markdown(f"**Title:** {title}")
379
+
380
+ # thumbnail (per-message or fallback per-user)
381
+ thumb = msg.get("thumbnail_url") or row.get("thumbnail_url")
382
+ if thumb:
383
+ st.image(thumb, width=150)
384
+
385
+ # the main message body
386
+ body = msg.get("message", "")
387
+ st.markdown(body)
388
+
389
+ # optional "read more" link
390
+ url = msg.get("web_url_path")
391
+ if url:
392
+ st.markdown(f"[Read more]({url})")
393
+
394
+ st.markdown("---")
395
+
messaging_main_test.py CHANGED
@@ -78,7 +78,7 @@ def filter_validated_users(users):
78
  # --------------------------------------------------------------
79
  if __name__ == "__main__":
80
  # path to sample data
81
- path = "Data/not_active_drumeo_camp.csv"
82
 
83
 
84
  # loading sample data
@@ -111,12 +111,10 @@ if __name__ == "__main__":
111
 
112
  session = Session.builder.configs(conn).create()
113
 
114
- brand = "drumeo"
115
  identifier_column = "user_id"
116
 
117
- segment_info = """This is a Drumeo user who have been inactive for 30 days or more. This means
118
- they have not taken any actions on the platform (e.g. lesson, songs, etc.) that would cause them to advance their
119
- musical skills. We want to re-motivate the user so they can get back on track to reach their drumming goals"""
120
 
121
  # sample inputs
122
 
@@ -124,7 +122,8 @@ if __name__ == "__main__":
124
 
125
  # sample_example = """we have crafted a perfect set of courses just for you! come and check it out!"""
126
 
127
- additional_instructions = """Include weeks_since _last_interaction in the message if you can create a better message to re-engage the user."""
 
128
 
129
  recsys_contents = ["workout", "course", "quick_tips"]
130
 
@@ -209,5 +208,5 @@ if __name__ == "__main__":
209
  sample_example=sample_example,
210
  segment_name=segment_name)
211
 
212
- users_message.to_csv(f"drumeo_not_active_complete.csv", encoding='utf-8-sig', index=False)
213
 
 
78
  # --------------------------------------------------------------
79
  if __name__ == "__main__":
80
  # path to sample data
81
+ path = "Data/Singeo_Camp.csv"
82
 
83
 
84
  # loading sample data
 
111
 
112
  session = Session.builder.configs(conn).create()
113
 
114
+ brand = "singeo"
115
  identifier_column = "user_id"
116
 
117
+ segment_info = """This is a singeo user who didn't practiced for a while."""
 
 
118
 
119
  # sample inputs
120
 
 
122
 
123
  # sample_example = """we have crafted a perfect set of courses just for you! come and check it out!"""
124
 
125
+ # additional_instructions = """Include weeks_since _last_interaction in the message if you can create a better message to re-engage the user."""
126
+ additional_instructions = None
127
 
128
  recsys_contents = ["workout", "course", "quick_tips"]
129
 
 
208
  sample_example=sample_example,
209
  segment_name=segment_name)
210
 
211
+ users_message.to_csv(f"Singeo_camp.csv", encoding='utf-8-sig', index=False)
212