Commit ·
ce32f1f
1
Parent(s): 0c9d257
- updating the system to be able to work with all brands
Browse files
Messaging_system/Message_generator.py
CHANGED
|
@@ -181,6 +181,9 @@ class MessageGenerator:
|
|
| 181 |
Your task is to select the best 'header' and a 'message' for a {self.Core.get_instrument()} student as a push notification.
|
| 182 |
Based on the user instructions, you might need to **modify the selected option** very minimal and slightly to improve personalization if capable.
|
| 183 |
**Important Note**: header < {self.Core.config_file["header_limit"]} and message < {self.Core.config_file["message_limit"]} characters.
|
|
|
|
|
|
|
|
|
|
| 184 |
"""
|
| 185 |
|
| 186 |
else:
|
|
|
|
| 181 |
Your task is to select the best 'header' and a 'message' for a {self.Core.get_instrument()} student as a push notification.
|
| 182 |
Based on the user instructions, you might need to **modify the selected option** very minimal and slightly to improve personalization if capable.
|
| 183 |
**Important Note**: header < {self.Core.config_file["header_limit"]} and message < {self.Core.config_file["message_limit"]} characters.
|
| 184 |
+
|
| 185 |
+
Don't use below phrases:
|
| 186 |
+
- Hit the high notes / Hit those notes
|
| 187 |
"""
|
| 188 |
|
| 189 |
else:
|
Messaging_system/MultiMessage.py
CHANGED
|
@@ -424,50 +424,6 @@ We have previously sent these push notifications to the user and The user has no
|
|
| 424 |
self.Core.users_df.at[idx, k] = v
|
| 425 |
return self.Core.users_df.loc[idx]
|
| 426 |
|
| 427 |
-
# =======================================================================
|
| 428 |
-
# def fetch_recommendation_data(self, user, message):
|
| 429 |
-
# """
|
| 430 |
-
# Extracts recommendation data from user's recsys_result and merges it into the given
|
| 431 |
-
# message dictionary. Identical to single-message usage.
|
| 432 |
-
#
|
| 433 |
-
# :param user: The user row (with 'recsys_result', 'recommendation', etc.).
|
| 434 |
-
# :param message: Dictionary with at least "header" and "message".
|
| 435 |
-
# :return: Enriched dict (header, message, content_id, web_url_path, title, thumbnail_url)
|
| 436 |
-
# """
|
| 437 |
-
# user_id = user["user_id"]
|
| 438 |
-
# content_id = int(user["recommendation"])
|
| 439 |
-
# recsys_json_str = user["recsys_result"]
|
| 440 |
-
# recsys_data = json.loads(recsys_json_str)
|
| 441 |
-
#
|
| 442 |
-
# # Initialize variable to store found item
|
| 443 |
-
# found_item = None
|
| 444 |
-
# for category, items in recsys_data.items():
|
| 445 |
-
# for item in items:
|
| 446 |
-
# if item.get("content_id") == content_id:
|
| 447 |
-
# found_item = item
|
| 448 |
-
# break
|
| 449 |
-
# if found_item:
|
| 450 |
-
# break
|
| 451 |
-
#
|
| 452 |
-
# if not found_item:
|
| 453 |
-
# print(f"content_id {content_id} not found in recsys_data for user_id {user_id}.")
|
| 454 |
-
# return None
|
| 455 |
-
#
|
| 456 |
-
# web_url_path = found_item.get("web_url_path")
|
| 457 |
-
# title = found_item.get("title")
|
| 458 |
-
# thumbnail_url = found_item.get("thumbnail_url")
|
| 459 |
-
#
|
| 460 |
-
# # Construct final dictionary
|
| 461 |
-
# output_message = {
|
| 462 |
-
# "header": message.get("header"),
|
| 463 |
-
# "message": message.get("message", "").replace('\\', '').replace('"', ''),
|
| 464 |
-
# "content_id": content_id,
|
| 465 |
-
# "web_url_path": web_url_path,
|
| 466 |
-
# "title": title,
|
| 467 |
-
# "thumbnail_url": thumbnail_url
|
| 468 |
-
# }
|
| 469 |
-
# return output_message
|
| 470 |
-
|
| 471 |
# --------------------------------------------------------------
|
| 472 |
# --------------------------------------------------------------
|
| 473 |
|
|
@@ -484,6 +440,9 @@ We have previously sent these push notifications to the user and The user has no
|
|
| 484 |
Your task is to select the best 'header' and a 'message' for a {self.Core.get_instrument()} student as a push notification.
|
| 485 |
Based on the user instructions, you might need to **modify the selected option** very minimal and slightly to improve personalization if capable.
|
| 486 |
**Important Note**: header < {self.Core.config_file["header_limit"]} and message < {self.Core.config_file["message_limit"]} characters.
|
|
|
|
|
|
|
|
|
|
| 487 |
"""
|
| 488 |
|
| 489 |
else:
|
|
|
|
| 424 |
self.Core.users_df.at[idx, k] = v
|
| 425 |
return self.Core.users_df.loc[idx]
|
| 426 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
# --------------------------------------------------------------
|
| 428 |
# --------------------------------------------------------------
|
| 429 |
|
|
|
|
| 440 |
Your task is to select the best 'header' and a 'message' for a {self.Core.get_instrument()} student as a push notification.
|
| 441 |
Based on the user instructions, you might need to **modify the selected option** very minimal and slightly to improve personalization if capable.
|
| 442 |
**Important Note**: header < {self.Core.config_file["header_limit"]} and message < {self.Core.config_file["message_limit"]} characters.
|
| 443 |
+
|
| 444 |
+
Don't use below phrases:
|
| 445 |
+
- Hit the high notes / Hit those notes
|
| 446 |
"""
|
| 447 |
|
| 448 |
else:
|
Messaging_system/Permes.py
CHANGED
|
@@ -84,6 +84,9 @@ class Permes:
|
|
| 84 |
|
| 85 |
users_df = self._create_personalized_message(CoreConfig=personalize_message, progress_callback=progress_callback)
|
| 86 |
|
|
|
|
|
|
|
|
|
|
| 87 |
total_prompt_tokens = personalize_message.total_tokens["prompt_tokens"]
|
| 88 |
total_completion_tokens = personalize_message.total_tokens["completion_tokens"]
|
| 89 |
|
|
@@ -175,6 +178,10 @@ class Permes:
|
|
| 175 |
datacollect = DataCollector(CoreConfig)
|
| 176 |
CoreConfig = datacollect.gather_data()
|
| 177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
# generating recommendations for users, if we want to include recommendations in the message
|
| 179 |
if CoreConfig.involve_recsys_result and CoreConfig.messaging_mode != "message":
|
| 180 |
Recommender = LLMR(CoreConfig, random=True)
|
|
|
|
| 84 |
|
| 85 |
users_df = self._create_personalized_message(CoreConfig=personalize_message, progress_callback=progress_callback)
|
| 86 |
|
| 87 |
+
if users_df is None:
|
| 88 |
+
return None
|
| 89 |
+
|
| 90 |
total_prompt_tokens = personalize_message.total_tokens["prompt_tokens"]
|
| 91 |
total_completion_tokens = personalize_message.total_tokens["completion_tokens"]
|
| 92 |
|
|
|
|
| 178 |
datacollect = DataCollector(CoreConfig)
|
| 179 |
CoreConfig = datacollect.gather_data()
|
| 180 |
|
| 181 |
+
if len(CoreConfig.users_df) == 0:
|
| 182 |
+
print("There is no user to generate messages")
|
| 183 |
+
return None
|
| 184 |
+
|
| 185 |
# generating recommendations for users, if we want to include recommendations in the message
|
| 186 |
if CoreConfig.involve_recsys_result and CoreConfig.messaging_mode != "message":
|
| 187 |
Recommender = LLMR(CoreConfig, random=True)
|
Messaging_system/PromptGenerator.py
CHANGED
|
@@ -150,14 +150,15 @@ Below is the content we want to recommend to the user:
|
|
| 150 |
→ Recommended Content Details:
|
| 151 |
{user["recommendation_info"]}
|
| 152 |
|
| 153 |
-
|
|
|
|
| 154 |
|
| 155 |
1. **Title Usage**:
|
| 156 |
- Refer to the **CONTENT_TITLE** naturally in the message — paraphrase or describe it, but do *not* quote it or use it verbatim.
|
| 157 |
- Avoid making it feel like a promotion; frame it as something that *might interest* or *help* the user.
|
| 158 |
|
| 159 |
2. **Content Type Context**:
|
| 160 |
-
- Mention the **CONTENT_TYPE** (e.g., course, workout,
|
| 161 |
|
| 162 |
3. **Artist/Instructor Name**:
|
| 163 |
- If the full name of the **ARTIST** is available, mention it casually if appropriate (e.g., "led by Jordan Mitchell").
|
|
@@ -254,8 +255,8 @@ Goal: Make the recommendation feel personalized and casually relevant — not ge
|
|
| 254 |
**Expected output structure:**
|
| 255 |
|
| 256 |
{{
|
| 257 |
-
"header": "
|
| 258 |
-
"message": "
|
| 259 |
}}
|
| 260 |
|
| 261 |
{general_instructions}
|
|
|
|
| 150 |
→ Recommended Content Details:
|
| 151 |
{user["recommendation_info"]}
|
| 152 |
|
| 153 |
+
|
| 154 |
+
When incorporating this content into the message and header, follow these guidelines to keep them friendly, relevant, and casual (not too scripted):
|
| 155 |
|
| 156 |
1. **Title Usage**:
|
| 157 |
- Refer to the **CONTENT_TITLE** naturally in the message — paraphrase or describe it, but do *not* quote it or use it verbatim.
|
| 158 |
- Avoid making it feel like a promotion; frame it as something that *might interest* or *help* the user.
|
| 159 |
|
| 160 |
2. **Content Type Context**:
|
| 161 |
+
- Mention the **CONTENT_TYPE** (e.g., course, workout, lesson) only if it flows naturally in the message.
|
| 162 |
|
| 163 |
3. **Artist/Instructor Name**:
|
| 164 |
- If the full name of the **ARTIST** is available, mention it casually if appropriate (e.g., "led by Jordan Mitchell").
|
|
|
|
| 255 |
**Expected output structure:**
|
| 256 |
|
| 257 |
{{
|
| 258 |
+
"header": "output header considering instructions",
|
| 259 |
+
"message": "output message considering instructions",
|
| 260 |
}}
|
| 261 |
|
| 262 |
{general_instructions}
|
Messaging_system/SnowFlakeConnection.py
CHANGED
|
@@ -243,6 +243,12 @@ class SnowFlakeConn:
|
|
| 243 |
def close_connection(self):
|
| 244 |
self.session.close()
|
| 245 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
# ===============================================================
|
| 247 |
def get_users_in_campaign(self, brand, campaign_name="Singeo - Inactive Members (for 3 days) - Re-engagement", stage=1, campaign_view='singeo_re_engagement'):
|
| 248 |
"""
|
|
@@ -354,4 +360,33 @@ JOIN latest_msg l
|
|
| 354 |
"""
|
| 355 |
|
| 356 |
users_df = self.run_read_query(query, data=f"{brand}_campaign")
|
| 357 |
-
return users_df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
def close_connection(self):
|
| 244 |
self.session.close()
|
| 245 |
|
| 246 |
+
def get_inactive_users_by_brand(self, brand):
|
| 247 |
+
if brand == "singeo":
|
| 248 |
+
return self.get_users_in_campaign(brand=brand)
|
| 249 |
+
else:
|
| 250 |
+
return self._get_inactive_users(brand=brand)
|
| 251 |
+
|
| 252 |
# ===============================================================
|
| 253 |
def get_users_in_campaign(self, brand, campaign_name="Singeo - Inactive Members (for 3 days) - Re-engagement", stage=1, campaign_view='singeo_re_engagement'):
|
| 254 |
"""
|
|
|
|
| 360 |
"""
|
| 361 |
|
| 362 |
users_df = self.run_read_query(query, data=f"{brand}_campaign")
|
| 363 |
+
return users_df
|
| 364 |
+
|
| 365 |
+
def _get_inactive_users(self, brand: str):
|
| 366 |
+
"""
|
| 367 |
+
Return up to 50 USER_IDs in the requested brand whose last interaction is > 5 days ago,
|
| 368 |
+
restricted to users that exist in ONLINE_RECSYS.PREPROCESSED.USERS.
|
| 369 |
+
"""
|
| 370 |
+
query = f"""
|
| 371 |
+
WITH last_touch AS (
|
| 372 |
+
SELECT
|
| 373 |
+
USER_ID,
|
| 374 |
+
MAX(TIMESTAMP) AS last_interaction_at
|
| 375 |
+
FROM ONLINE_RECSYS.PREPROCESSED.RECSYS_INTEACTIONS
|
| 376 |
+
WHERE BRAND = '{brand}'
|
| 377 |
+
GROUP BY USER_ID
|
| 378 |
+
)
|
| 379 |
+
SELECT lt.USER_ID
|
| 380 |
+
FROM last_touch lt
|
| 381 |
+
INNER JOIN ONLINE_RECSYS.PREPROCESSED.USERS u
|
| 382 |
+
ON u.USER_ID = lt.USER_ID
|
| 383 |
+
AND u.BRAND = '{brand}'
|
| 384 |
+
WHERE lt.last_interaction_at < DATEADD('day', -5, CURRENT_TIMESTAMP())
|
| 385 |
+
ORDER BY lt.last_interaction_at ASC
|
| 386 |
+
LIMIT 50;
|
| 387 |
+
"""
|
| 388 |
+
|
| 389 |
+
users_df = self.run_read_query(query, data=f"{brand}_campaign")
|
| 390 |
+
return users_df
|
| 391 |
+
|
| 392 |
+
|
app.py
CHANGED
|
@@ -161,7 +161,7 @@ with st.sidebar:
|
|
| 161 |
st.session_state.session = Session.builder.configs(conn).create()
|
| 162 |
|
| 163 |
snowflake = SnowFlakeConn(session=st.session_state.session, brand=st.session_state.brand)
|
| 164 |
-
st.session_state.data = snowflake.
|
| 165 |
st.success("File loaded!")
|
| 166 |
|
| 167 |
st.markdown("---")
|
|
@@ -297,6 +297,10 @@ with tab2:
|
|
| 297 |
personalization=st.session_state.personalization
|
| 298 |
)
|
| 299 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
# ─ cache output
|
| 301 |
st.session_state.users_message = df_msg
|
| 302 |
st.session_state.csv_output = df_msg.to_csv(
|
|
|
|
| 161 |
st.session_state.session = Session.builder.configs(conn).create()
|
| 162 |
|
| 163 |
snowflake = SnowFlakeConn(session=st.session_state.session, brand=st.session_state.brand)
|
| 164 |
+
st.session_state.data = snowflake.get_inactive_users_by_brand(brand=st.session_state.brand)
|
| 165 |
st.success("File loaded!")
|
| 166 |
|
| 167 |
st.markdown("---")
|
|
|
|
| 297 |
personalization=st.session_state.personalization
|
| 298 |
)
|
| 299 |
|
| 300 |
+
if df_msg is None:
|
| 301 |
+
st.write("##### 👤 There were no eligible user to generate messages. Consider setting higher number of users.")
|
| 302 |
+
|
| 303 |
+
|
| 304 |
# ─ cache output
|
| 305 |
st.session_state.users_message = df_msg
|
| 306 |
st.session_state.csv_output = df_msg.to_csv(
|