app/__pycache__/assistants.cpython-312.pyc CHANGED
Binary files a/app/__pycache__/assistants.cpython-312.pyc and b/app/__pycache__/assistants.cpython-312.pyc differ
 
app/__pycache__/flows.cpython-312.pyc CHANGED
Binary files a/app/__pycache__/flows.cpython-312.pyc and b/app/__pycache__/flows.cpython-312.pyc differ
 
app/__pycache__/main.cpython-312.pyc CHANGED
Binary files a/app/__pycache__/main.cpython-312.pyc and b/app/__pycache__/main.cpython-312.pyc differ
 
app/__pycache__/user.cpython-312.pyc CHANGED
Binary files a/app/__pycache__/user.cpython-312.pyc and b/app/__pycache__/user.cpython-312.pyc differ
 
app/__pycache__/utils.cpython-312.pyc CHANGED
Binary files a/app/__pycache__/utils.cpython-312.pyc and b/app/__pycache__/utils.cpython-312.pyc differ
 
app/assistants.py CHANGED
@@ -13,7 +13,7 @@ import psycopg2
13
  from psycopg2 import sql
14
  import pytz
15
 
16
- from app.exceptions import AssistantError, BaseOurcoachException, OpenAIRequestError
17
  from app.utils import get_booked_gg_sessions, get_growth_guide, get_growth_guide_summary, get_user_subscriptions, get_users_mementos, print_log
18
  from app.flows import FOLLOW_UP_STATE, GENERAL_COACHING_STATE, MICRO_ACTION_STATE, REFLECTION_STATE
19
 
@@ -311,8 +311,6 @@ class Assistant:
311
  return func(self, *args, **kwargs)
312
  except (BaseOurcoachException) as e:
313
  raise e
314
- except openai.BadRequestError as e:
315
- raise OpenAIRequestError(user_id=self.cm.user.user_id, message="Error getting response from OpenAI", e=str(e), run_id=self.recent_run.id)
316
  except Exception as e:
317
  # Handle other exceptions
318
  logger.error(f"An unexpected error occurred in Assistant: {e}")
@@ -324,7 +322,6 @@ class Assistant:
324
  self.cm = cm
325
  self.recent_run = None
326
 
327
- @catch_error
328
  def cancel_run(self, run, thread):
329
  logger.info(f"(asst) Attempting to cancel run: {run} for thread: {thread}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
330
  if isinstance(run, str):
@@ -338,7 +335,9 @@ class Assistant:
338
  logger.warning(f"Thread {thread} already deleted: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
339
  return True
340
  if isinstance(run, PseudoRun):
341
- return True
 
 
342
  try:
343
  logger.info(f"Attempting to cancel run: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
344
  if run.status != 'completed':
@@ -366,16 +365,17 @@ class Assistant:
366
 
367
  @catch_error
368
  def process(self, thread, text):
369
- # template_search = self.cm.add_message_to_thread(thread.id, "assistant", f"Pay attention to the current state you are in and the flow template to respond to the users query:")
370
- message = self.cm.add_message_to_thread(thread.id, "user", text)
371
-
372
- run = self.cm.client.beta.threads.runs.create_and_poll(
373
- thread_id=thread.id,
374
- assistant_id=self.id,
375
- model="gpt-4o-mini",
376
- )
377
- just_finished_intro = False
378
  try:
 
 
 
 
 
 
 
 
 
 
379
  if run.status == 'completed':
380
  logger.info(f"Run Completed: {run.status}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
381
  self.recent_run = run
@@ -393,12 +393,14 @@ class Assistant:
393
  reccursion += 1
394
  if reccursion > 10:
395
  logger.warning(f"Run has exceeded maximum recussrion depth({10}) for function_call: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
396
- self.cancel_run(run, thread)
397
  raise OpenAIRequestError(user_id=self.cm.id, message="Tool Call Reccursion Depth Reached")
398
- if run.status == 'cancelled':
399
  logger.warning(f"RUN NOT COMPLETED: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
400
  self.cancel_run(run, thread)
401
- break
 
 
 
402
  elif run.status == 'completed':
403
  logger.info(f"Run Completed: {run.status}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
404
  self.recent_run = run
@@ -406,344 +408,348 @@ class Assistant:
406
  elif run.status == 'failed':
407
  raise OpenAIRequestError(user_id=self.cm.id, message="Run failed")
408
  return run, just_finished_intro, message
409
- except openai.BadRequestError as e:
410
- raise OpenAIRequestError(user_id=self.cm.user, message="Error getting response from OpenAI", e=str(e))
 
 
 
 
 
 
411
 
412
- @catch_error
413
  def call_tool(self, run, thread):
414
- tool_outputs = []
415
- logger.info(f"Required actions: {list(map(lambda x: f'{x.function.name}({x.function.arguments})', run.required_action.submit_tool_outputs.tool_calls))}",
416
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_call_tool"})
417
- just_finish_intro = False
418
- for tool in run.required_action.submit_tool_outputs.tool_calls:
419
- if tool.function.name == "transition":
420
- transitions = json.loads(tool.function.arguments)
421
- logger.info(f"Transition: {transitions['from']} -> {transitions['to']}",
422
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_transition"})
423
-
424
- if transitions['from'] == "PLANNING STATE":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  tool_outputs.append({
426
  "tool_call_id": tool.id,
427
- "output": f"help the user set a new goal"
428
  })
429
- # logger.info(f"Exiting the introduction state",
430
- # extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_transition"})
431
- # just_finish_intro = True
432
- # # run = self.cancel_run(run, thread)
433
- # # logger.info(f"Successfully cancelled run",
434
- # # extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_transition"})
435
- # tool_outputs.append({
436
- # "tool_call_id": tool.id,
437
- # "output": "true"
438
- # })
439
- else:
440
- flow_instructions = ""
441
- if transitions['to'] == "REFLECTION STATE":
442
- flow_instructions = REFLECTION_STATE
443
-
444
- next = self.cm.matters_most.next()
445
- logger.info(f"Successfully moved to bext matters most: {next}")
446
- question_format = random.choice(['[Option 1] Likert-Scale Objective Question','[Option 2] Multiple-Choice Question','[Option 3] Yes-No Question'])
447
-
448
- flow_instructions = f"""Today's opening question format is: {question_format}. Send a WARM, SUCCINCT and PERSONALIZED (according to my PROFILE) first message in this format! The most warm, succinct & personalized message will get rewarded!\n
449
- The reflection topic is: {next}. YOU MUST SEND CREATIVE, PERSONALIZED (only mention specific terms that are related to the reflection topic/area), AND SUCCINCT MESSAGES!! The most creative message will be rewarded! And make every new reflection fresh and not boring! \n
450
 
451
- """ + flow_instructions
 
452
 
453
- elif transitions['to'] == "FOLLOW UP STATE":
454
- flow_instructions = FOLLOW_UP_STATE
 
 
 
 
455
 
456
- elif transitions['to'] == "GENERAL COACHING STATE":
457
- flow_instructions = GENERAL_COACHING_STATE
 
 
 
 
 
 
458
 
459
  tool_outputs.append({
460
  "tool_call_id": tool.id,
461
- "output": f"{flow_instructions}\n\n" + f"** Follow the above flow template to respond to the user **"
462
  })
463
- elif tool.function.name == "get_date":
464
- # print(f"[DATETIME]: {get_current_datetime()}")
465
- # self.cm.state['date'] = 'date': pd.Timestamp.now().strftime("%Y-%m-%d %a %H:%M:%S")
466
- # get and update the current time to self.cm.state['date'] but keep the date component
467
- current_time = get_current_datetime(self.cm.user.user_id)
468
- # replace time component of self.cm.state['date'] with the current time
469
- self.cm.state['date'] = str(pd.to_datetime(self.cm.state['date']).replace(hour=current_time.hour, minute=current_time.minute, second=current_time.second))
470
- logger.info(f"Current datetime: {self.cm.state['date']}",
471
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_date"})
472
-
473
- tool_outputs.append({
474
- "tool_call_id": tool.id,
475
- "output": f"{self.cm.state['date']}"
476
- })
477
- elif tool.function.name == "create_goals" or tool.function.name == "create_memento":
478
- json_string = json.loads(tool.function.arguments)
479
- json_string['created'] = str(self.cm.state['date'])
480
- json_string['updated'] = None
481
- logger.info(f"New event: {json_string}",
482
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_create_event"})
483
-
484
- # Create a folder for the user's mementos if it doesn't exist
485
- user_mementos_folder = os.path.join("mementos", "to_upload", self.cm.user.user_id)
486
-
487
- # Ensure the directory exists
488
- os.makedirs(user_mementos_folder, exist_ok=True)
489
-
490
- # Construct the full file path for the JSON file
491
- file_path = os.path.join(user_mementos_folder, f"{json_string['title']}.json")
492
-
493
- # Save the JSON string as a file
494
- with open(file_path, "w") as json_file:
495
- json.dump(json_string, json_file)
496
-
497
- tool_outputs.append({
498
- "tool_call_id": tool.id,
499
- "output": f"** [Success]: Added event to the user's vector store**"
500
- })
501
- elif tool.function.name == "msearch":
502
- queries = json.loads(tool.function.arguments)['queries']
503
- logger.info(f"Searching for: {queries}",
504
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_msearch"})
505
-
506
- tool_outputs.append({
507
- "tool_call_id": tool.id,
508
- "output": f"** retrieve any files related to: {queries} **"
509
- })
510
- elif tool.function.name == "get_mementos":
511
- logger.info(f"Getting mementos", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
512
- args = json.loads(tool.function.arguments)
513
- logger.info(f"ARGS: {args}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
514
- queries = args['queries']
515
-
516
- if 'on' not in args:
517
- on = pd.to_datetime(self.cm.state['date']).date()
518
- else:
519
- on = args['on']
520
- if on == '':
521
  on = pd.to_datetime(self.cm.state['date']).date()
522
  else:
523
- on = pd.to_datetime(args['on']).date()
524
-
525
-
526
- logger.info(f"Query date: {on}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
527
- # query the user memento db for all mementos where follow_up_on is equal to the query date
528
- mementos = get_users_mementos(self.cm.user.user_id, on)
529
-
530
- # if on == "":
531
- # instruction = f"** Fetch all files (mementos) from this thread's Memento vector_store ([id={self.cm.user_personal_memory.id}]) **"
532
- # else:
533
- # instruction = f"** Fetch files (mementos) from this thread's Memento vector_store ([id={self.cm.user_personal_memory.id}]) where the follow_up_on field matches the query date: {on} (ignore the time component, focus only on the date)**"
534
- # # f"** File search this threads' Memento vector_store ([id={self.cm.user_personal_memory.id}]) for the most relevant mementos based on the recent conversation history and context:{context} **"
535
-
536
- logger.info(f"Finish Getting mementos: {mementos}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
537
- tool_outputs.append({
538
- "tool_call_id": tool.id,
539
- "output": f"Today's mementos: {mementos}" if len(mementos) else "No mementos to follow up today."
540
- })
541
- elif tool.function.name == "get_feedback_types":
542
- print_log("WARNING","Calling get_feedback_types", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
543
- logger.warning("Calling get_feedback_types", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
544
- feedbacks = [
545
- ("Inspirational Quotes", "📜", "Short, impactful quotes from the user's chosen Legendary Persona"),
546
- ("Tips & Advice", "💡", "Practical suggestions or strategies for personal growth (no need to say \"Tips:\" in the beggining of your tips)"),
547
- ("Encouragement", "🌈", "Positive affirmations and supportive statements"),
548
- ("Personalized Recommendations", "🧩", "Tailored suggestions based on user progress"),
549
- ("Affirmations", "✨", "Positive statements for self-belief and confidence"),
550
- ("Mindfulness/Meditation", "🧘‍♀️", "Guided prompts for mindfulness practice"),
551
- ("Book/Podcast", "📚🎧", "Suggestions aligned with user interests"),
552
- ("Habit Tip", "🔄", "Tips for building and maintaining habits"),
553
- ("Seasonal Content", "🌸", "Time and theme-relevant interactions"),
554
- ("Fun Fact", "🎉", "Interesting and inspiring facts (no need to say \"Fun Fact:\" in the beggining of your tips)"),
555
- ("Time Management", "⏳", "Tips for effective time management")
556
- ]
557
- sample_feedbacks = random.sample(feedbacks, 3)
558
- print_log("INFO",f"Feedback types: {sample_feedbacks}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
559
- logger.info(f"Feedback types: {sample_feedbacks}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
560
- tool_outputs.append({
561
- "tool_call_id": tool.id,
562
- "output": "Generate a final coach message (feedback message) using these 3 feedback types (together with the stated emoji at the beginning of each feedback): " + str(sample_feedbacks)
563
- })
564
- elif tool.function.name == "search_resource":
565
- type = json.loads(tool.function.arguments)['resource_type']
566
- query = json.loads(tool.function.arguments)['query']
567
- logger.info(f"Getting microaction theme: {type} - {query}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_microaction_theme"})
568
- relevant_context = SearchEngine.search(type, query)
569
- logger.info(f"Finish Getting microaction theme: {relevant_context}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_microaction_theme"})
570
- tool_outputs.append({
571
- "tool_call_id": tool.id,
572
- "output": f"** Relevant context: {relevant_context} **"
573
- })
574
- elif tool.function.name == "end_conversation":
575
- day_n = json.loads(tool.function.arguments)['day_n']
576
- completed_micro_action = json.loads(tool.function.arguments)['completed_micro_action']
577
- area_of_deep_reflection = json.loads(tool.function.arguments)['area_of_deep_reflection']
578
-
579
- self.cm.user.update_micro_action_status(completed_micro_action)
580
- self.cm.user.trigger_deep_reflection_point(area_of_deep_reflection)
581
-
582
- # NOTE: we will record whether the user has completed the theme for the day
583
- # NOTE: if no, we will include a short followup message to encourage the user the next day
584
- logger.info(f"Ending conversation after {day_n} days. Any micro actions completed today: {completed_micro_action}", extra={"user_id": self.cm.user.user_id, "endpoint": "end_conversation"})
585
- tool_outputs.append({
586
- "tool_call_id": tool.id,
587
- "output": "true"
588
- })
589
- elif tool.function.name == "create_smart_goal":
590
- print_log("WARNING", f"Creating a SMART goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
591
- logger.warning(f"Creating a SMART goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
592
 
593
- user_goal = json.loads(tool.function.arguments)['goal']
594
- user_goal_area = json.loads(tool.function.arguments)['area']
595
 
596
- self.cm.user.set_goal(user_goal, user_goal_area)
 
 
597
 
598
- print_log("INFO", f"SMART goal approved: {user_goal}", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
599
- logger.info(f"SMART goal approved: {user_goal}", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
 
 
 
600
 
601
- tool_outputs.append({
602
- "tool_call_id": tool.id,
603
- "output": "true"
604
- })
605
- elif tool.function.name == "start_now":
606
- logger.info(f"Starting Growth Plan on Day 0",
607
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_start_now"})
608
- # set intro finish to true
609
- just_finish_intro = True
610
-
611
- # cancel current run
612
- run = self.cancel_run(run, thread)
613
- run = PseudoRun(status="cancelled", metadata={"message": "start_now"})
614
- return run, just_finish_intro
615
- elif tool.function.name == "change_goal":
616
- logger.info(f"Changing user goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_change_goal"})
617
-
618
- # switch back to the intro assistant, so we set just_finish_intro to False again
619
- just_finish_intro = False
620
-
621
- # cancel current run
622
- run = self.cancel_run(run, thread)
623
- run = PseudoRun(status="cancelled", metadata={"message": "change_goal"})
624
- return run, just_finish_intro
625
- elif tool.function.name == "complete_goal":
626
- logger.info(f"Completing user goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
627
- goal = self.cm.user.update_goal(None, 'COMPLETED')
628
- logger.info(f"Marked users' goal: {goal} as COMPLETED", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
629
- tool_outputs.append({
630
- "tool_call_id": tool.id,
631
- "output": f"Marked users' goal: {goal} as COMPLETED"
632
- })
633
- elif tool.function.name == "process_reminder":
634
- reminder = json.loads(tool.function.arguments)["content"]
635
- timestamp = json.loads(tool.function.arguments)["timestamp"]
636
- recurrence = json.loads(tool.function.arguments)["recurrence"]
637
- action = json.loads(tool.function.arguments)["action"]
638
- logger.info(f"Setting reminder: {reminder} for {timestamp} with recurrence: {recurrence}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
639
- # timestamp is a string like: YYYY-mm-ddTHH:MM:SSZ (2025-01-05T11:00:00Z)
640
- # convert to datetime object
641
- timestamp = pd.to_datetime(timestamp, format="%Y-%m-%dT%H:%M:%SZ")
642
- logger.info(f"Formatted timestamp: {timestamp}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
643
-
644
- output = f"({recurrence if recurrence else 'One-Time'}) Reminder ({reminder}) set for ({timestamp})"
645
- logger.info(output,
646
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_set_reminder"})
647
- self.cm.user.set_reminder({"reminder": reminder, "timestamp": timestamp, 'recurrence': recurrence, 'action': action})
648
- tool_outputs.append({
649
  "tool_call_id": tool.id,
650
- "output": f"** {output} **"
651
- })
652
- elif tool.function.name == "get_user_info":
653
- category = json.loads(tool.function.arguments)['category']
654
- # one of [
655
- # "personal",
656
- # "challenges",
657
- # "recommended_actions",
658
- # "micro_actions",
659
- # "other_focusses",
660
- # "reminders",
661
- # "goal",
662
- # "growth_guide_session",
663
- # "life_score",
664
- # "recent_wins",
665
- # "subscription_info"
666
- # ]
667
- logger.info(f"Getting user information: {category}",
668
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
669
- user_info = '** If the user asks for their progress, also include a link to their Revelation Dashboard: {OURCOACH_DASHBOARD_URL} so that they can find out more **\n\n'
670
- if category == "personal":
671
- user_info += f"** Personal Information **\n\n{self.cm.user.user_info}"
672
- user_info += f"\n\n** User's Mantra This Week**\n\n{self.cm.user.mantra}"
673
- elif category == "challenges":
674
- user_info += f"** User's Challenges (prioritise ONGOING challenges) **\n\n{self.cm.user.challenges}\n\nLet the user know that ongoing challenges from their growth guide will be integrated into their day-to-day interaction."
675
- elif category == "recommended_actions":
676
- user_info += f"** User's Recommended Actions (upcoming microactions, recommended by growth guide) **\n\n{self.cm.user.recommended_micro_actions}\n\nLet the user know that these microactions from their growth guide will be integrated into their day-to-day interaction."
677
- elif category == "micro_actions":
678
- user_info += f"** User's Micro Actions (already introduced microactions) **\n\n{self.cm.user.micro_actions}"
679
- elif category == "other_focusses":
680
- user_info += f"** User's Other Focusses (other areas of focus) **\n\n{self.cm.user.other_focusses}\n\nLet the user know that other areas of focus from their growth guide will be integrated into their day-to-day interaction."
681
- elif category == "reminders":
682
- user_info += f"** User's Reminders **\n\n{self.cm.user.reminders}"
683
- elif category == "goal":
684
- user_info += f"** User's Goal (prioritise the latest [last item in the array] goal). Do not mention the status of their goal. **\n\n{self.cm.user.goal}"
685
- elif category == "growth_guide_session":
686
- growth_guide = get_growth_guide(self.cm.user.user_id)
687
- user_info += f"** Users' Growth Guide (always refer to the Growth Guide as 'Growth Guide' unless the user specifically wants to know the name/who their growth guide is) **\n{growth_guide}"
688
- logger.info(f"User's growth guide: {growth_guide}",
689
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
690
 
691
- booked_sessions = get_booked_gg_sessions(self.cm.user.user_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
 
693
- # for each booking, if the booking has completed, fetch the zoom_ai_summary and gg_report from
694
- for booking in booked_sessions:
695
- if booking['status'] == "completed":
696
- summary_data = get_growth_guide_summary(self.cm.user.user_id, booking['booking_id'])
697
- logger.info(f"Summary data for booking: {booking['booking_id']} - {summary_data}",
 
698
  extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
699
- if summary_data:
700
- booking['zoom_ai_summary'] = summary_data['zoom_ai_summary']
701
- booking['gg_report'] = summary_data['gg_report']
702
- else:
703
- booking['zoom_ai_summary'] = "Growth Guide has not uploaded the report yet"
704
- booking['gg_report'] = "Growth Guide has not uploaded the report yet"
705
-
706
- logger.info(f"User's booked sessions: {booked_sessions}",
707
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
708
-
709
- if len(booked_sessions):
710
- # booked session is an array of jsons
711
- # convers it to have i) json where i = 1...N where N is the len of booked_sessions
712
- # join the entire array into 1 string with each item seperated by a newline
713
- formatted_sessions = "\n".join([f"** Session {i+1} **\n{json.dumps(session, indent=4)}" for i, session in enumerate(booked_sessions)])
714
- logger.info(f"Formatted booked sessions: {formatted_sessions}",
715
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
716
 
717
- user_info += f"\n** GG Session Bookings & Summaries (most recent first) **\n{formatted_sessions}"
718
- else:
719
- user_info += f"\n** GG Session Summaries **\nNo GG yet. Let the user know they can book one now through their Revelation Dashboard: {OURCOACH_DASHBOARD_URL}! (When including links, DO **NOT** use a hyperlink format. Just show the link as plain text. Example: \"Revelation Dashboard: https://...\")"
720
- user_info += f"\n** Suggested Growth Guide Topics **\n{self.cm.user.recommended_gg_topics}\nOnly suggest 1-2 topics and let the user know they can can find more suggestions on their dashboard"
721
- elif category == "life_score":
722
- user_info += f"** User's Life scores for each area **\n\n Personal Growth: {self.cm.user.personal_growth_score} || Career: {self.cm.user.career_growth_score} || Health/Wellness: {self.cm.user.health_and_wellness_score} || Relationships: {self.cm.user.relationship_score} || Mental Health: {self.cm.user.mental_well_being_score}"
723
- elif category == "recent_wins":
724
- user_info += f"** User's Recent Wins / Achievements **\n\n {self.cm.user.recent_wins}"
725
- elif category == "subscription_info":
726
- subscription_history = get_user_subscriptions(self.cm.user.user_id)
727
- logger.info(f"User's subscription history: {subscription_history}",
728
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
729
- user_info += f"** User's Subscription Information **\n\n This is a sorted list (most recent first, which also represent the current subscription status) of the users subscription history:\n{subscription_history}\nNote that the stripe_status is one of 'trialing' = (user is on a free trial), 'cancelled' = (user has cancelled their subscription), 'active' = (user is a Premium user). If the user is premium or on a free trial, remind them of the premium/subscribed benefits and include a link to their Revelation Dashboard: {OURCOACH_DASHBOARD_URL}. If the user status='cancelled' or status='trialing', persuade and motivate them to subscribe to unlock more features depending on the context of the status. Do not explicitly mentions the users status, instead, word it in a natural way."
730
- logger.info(f"Finish Getting user information: {user_info}",
731
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
732
- tool_outputs.append({
733
- "tool_call_id": tool.id,
734
- "output": f"** User Info:\n\n{user_info} **"
735
- })
736
- elif tool.function.name == "extend_two_weeks":
737
- logger.info(f"Changing plan from 1 week to 2 weeks...", extra={"user_id": self.cm.user.user_id, "endpoint": "extend_two_weeks"})
738
- goal = self.cm.user.extend_growth_plan()
739
- tool_outputs.append({
740
- "tool_call_id": tool.id,
741
- "output": f"Changed plan from 1 week to 2 weeks."
742
- })
743
 
744
- # Submit all tool outputs at once after collecting them in a list
745
- if tool_outputs:
746
- try:
747
  run = self.cm.client.beta.threads.runs.submit_tool_outputs_and_poll(
748
  thread_id=thread.id,
749
  run_id=run.id,
@@ -752,17 +758,22 @@ class Assistant:
752
  logger.info("Tool outputs submitted successfully",
753
  extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_submit_tools"})
754
  return run, just_finish_intro
755
- except Exception as e:
756
- raise OpenAIRequestError(user_id=self.cm.user.id, message="Error submitting tool outputs", e=str(e), run_id=run.id)
757
- else:
758
- logger.warning("No tool outputs to submit",
759
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_submit_tools"})
760
- run = PseudoRun(status="completed", metadata={"message": "No tool outputs to submit"})
761
- return run, just_finish_intro
 
 
 
 
 
762
 
763
  class PseudoRun:
764
- def __init__(self, status, metadata=None):
765
- self.id = "pseudo_run"
766
  self.status = status
767
  self.metadata = metadata or {}
768
 
 
13
  from psycopg2 import sql
14
  import pytz
15
 
16
+ from app.exceptions import AssistantError, BaseOurcoachException, OpenAIRequestError, UtilsError
17
  from app.utils import get_booked_gg_sessions, get_growth_guide, get_growth_guide_summary, get_user_subscriptions, get_users_mementos, print_log
18
  from app.flows import FOLLOW_UP_STATE, GENERAL_COACHING_STATE, MICRO_ACTION_STATE, REFLECTION_STATE
19
 
 
311
  return func(self, *args, **kwargs)
312
  except (BaseOurcoachException) as e:
313
  raise e
 
 
314
  except Exception as e:
315
  # Handle other exceptions
316
  logger.error(f"An unexpected error occurred in Assistant: {e}")
 
322
  self.cm = cm
323
  self.recent_run = None
324
 
 
325
  def cancel_run(self, run, thread):
326
  logger.info(f"(asst) Attempting to cancel run: {run} for thread: {thread}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
327
  if isinstance(run, str):
 
335
  logger.warning(f"Thread {thread} already deleted: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
336
  return True
337
  if isinstance(run, PseudoRun):
338
+ if run.id == "pseudo_run":
339
+ logger.info(f"Run already completed: {run.id}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
340
+ return True
341
  try:
342
  logger.info(f"Attempting to cancel run: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
343
  if run.status != 'completed':
 
365
 
366
  @catch_error
367
  def process(self, thread, text):
 
 
 
 
 
 
 
 
 
368
  try:
369
+ # template_search = self.cm.add_message_to_thread(thread.id, "assistant", f"Pay attention to the current state you are in and the flow template to respond to the users query:")
370
+ message = self.cm.add_message_to_thread(thread.id, "user", text)
371
+
372
+ run = self.cm.client.beta.threads.runs.create_and_poll(
373
+ thread_id=thread.id,
374
+ assistant_id=self.id,
375
+ model="gpt-4o-mini",
376
+ )
377
+ just_finished_intro = False
378
+
379
  if run.status == 'completed':
380
  logger.info(f"Run Completed: {run.status}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
381
  self.recent_run = run
 
393
  reccursion += 1
394
  if reccursion > 10:
395
  logger.warning(f"Run has exceeded maximum recussrion depth({10}) for function_call: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
 
396
  raise OpenAIRequestError(user_id=self.cm.id, message="Tool Call Reccursion Depth Reached")
397
+ if run.status == 'cancel':
398
  logger.warning(f"RUN NOT COMPLETED: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
399
  self.cancel_run(run, thread)
400
+ run.status = 'cancelled'
401
+ logger.warning(f"Yeap Run Cancelled: {run.status}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
402
+ return run, just_finished_intro, message
403
+
404
  elif run.status == 'completed':
405
  logger.info(f"Run Completed: {run.status}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
406
  self.recent_run = run
 
408
  elif run.status == 'failed':
409
  raise OpenAIRequestError(user_id=self.cm.id, message="Run failed")
410
  return run, just_finished_intro, message
411
+ except Exception as e:
412
+ # Cancel the run
413
+ logger.error(f"Error in process: {e}", extra={"user_id": self.cm.user.user_id, "endpoint": 'assistant_process'})
414
+ logger.error(f"Cancelling run {run.id} for thread {thread.id}", extra={"user_id": self.cm.user.user_id, "endpoint": 'cancel_erroneous_run'})
415
+
416
+ self.cancel_run(run, thread)
417
+ logger.error(f"Run {run.id} cancelled for thread {thread.id}", extra={"user_id": self.cm.user.user_id, "endpoint": 'cancel_erroneous_run'})
418
+ raise e
419
 
 
420
  def call_tool(self, run, thread):
421
+ try:
422
+ tool_outputs = []
423
+ logger.info(f"Required actions: {list(map(lambda x: f'{x.function.name}({x.function.arguments})', run.required_action.submit_tool_outputs.tool_calls))}",
424
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_call_tool"})
425
+ just_finish_intro = False
426
+ for tool in run.required_action.submit_tool_outputs.tool_calls:
427
+ if tool.function.name == "transition":
428
+ transitions = json.loads(tool.function.arguments)
429
+ logger.info(f"Transition: {transitions['from']} -> {transitions['to']}",
430
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_transition"})
431
+
432
+ if transitions['from'] == "PLANNING STATE":
433
+ tool_outputs.append({
434
+ "tool_call_id": tool.id,
435
+ "output": f"help the user set a new goal"
436
+ })
437
+ # logger.info(f"Exiting the introduction state",
438
+ # extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_transition"})
439
+ # just_finish_intro = True
440
+ # # run = self.cancel_run(run, thread)
441
+ # # logger.info(f"Successfully cancelled run",
442
+ # # extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_transition"})
443
+ # tool_outputs.append({
444
+ # "tool_call_id": tool.id,
445
+ # "output": "true"
446
+ # })
447
+ else:
448
+ flow_instructions = ""
449
+ if transitions['to'] == "REFLECTION STATE":
450
+ flow_instructions = REFLECTION_STATE
451
+
452
+ next = self.cm.matters_most.next()
453
+ logger.info(f"Successfully moved to bext matters most: {next}")
454
+ question_format = random.choice(['[Option 1] Likert-Scale Objective Question','[Option 2] Multiple-Choice Question','[Option 3] Yes-No Question'])
455
+
456
+ flow_instructions = f"""Today's opening question format is: {question_format}. Send a WARM, SUCCINCT and PERSONALIZED (according to my PROFILE) first message in this format! The most warm, succinct & personalized message will get rewarded!\n
457
+ The reflection topic is: {next}. YOU MUST SEND CREATIVE, PERSONALIZED (only mention specific terms that are related to the reflection topic/area), AND SUCCINCT MESSAGES!! The most creative message will be rewarded! And make every new reflection fresh and not boring! \n
458
+
459
+ """ + flow_instructions
460
+
461
+ elif transitions['to'] == "FOLLOW UP STATE":
462
+ flow_instructions = FOLLOW_UP_STATE
463
+
464
+ elif transitions['to'] == "GENERAL COACHING STATE":
465
+ flow_instructions = GENERAL_COACHING_STATE
466
+
467
+ tool_outputs.append({
468
+ "tool_call_id": tool.id,
469
+ "output": f"{flow_instructions}\n\n" + f"** Follow the above flow template to respond to the user **"
470
+ })
471
+ elif tool.function.name == "get_date":
472
+ # print(f"[DATETIME]: {get_current_datetime()}")
473
+ # self.cm.state['date'] = 'date': pd.Timestamp.now().strftime("%Y-%m-%d %a %H:%M:%S")
474
+ # get and update the current time to self.cm.state['date'] but keep the date component
475
+ current_time = get_current_datetime(self.cm.user.user_id)
476
+ # replace time component of self.cm.state['date'] with the current time
477
+ self.cm.state['date'] = str(pd.to_datetime(self.cm.state['date']).replace(hour=current_time.hour, minute=current_time.minute, second=current_time.second))
478
+ logger.info(f"Current datetime: {self.cm.state['date']}",
479
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_date"})
480
+
481
  tool_outputs.append({
482
  "tool_call_id": tool.id,
483
+ "output": f"{self.cm.state['date']}"
484
  })
485
+ elif tool.function.name == "create_goals" or tool.function.name == "create_memento":
486
+ json_string = json.loads(tool.function.arguments)
487
+ json_string['created'] = str(self.cm.state['date'])
488
+ json_string['updated'] = None
489
+ logger.info(f"New event: {json_string}",
490
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_create_event"})
491
+
492
+ # Create a folder for the user's mementos if it doesn't exist
493
+ user_mementos_folder = os.path.join("mementos", "to_upload", self.cm.user.user_id)
 
 
 
 
 
 
 
 
 
 
 
 
494
 
495
+ # Ensure the directory exists
496
+ os.makedirs(user_mementos_folder, exist_ok=True)
497
 
498
+ # Construct the full file path for the JSON file
499
+ file_path = os.path.join(user_mementos_folder, f"{json_string['title']}.json")
500
+
501
+ # Save the JSON string as a file
502
+ with open(file_path, "w") as json_file:
503
+ json.dump(json_string, json_file)
504
 
505
+ tool_outputs.append({
506
+ "tool_call_id": tool.id,
507
+ "output": f"** [Success]: Added event to the user's vector store**"
508
+ })
509
+ elif tool.function.name == "msearch":
510
+ queries = json.loads(tool.function.arguments)['queries']
511
+ logger.info(f"Searching for: {queries}",
512
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_msearch"})
513
 
514
  tool_outputs.append({
515
  "tool_call_id": tool.id,
516
+ "output": f"** retrieve any files related to: {queries} **"
517
  })
518
+ elif tool.function.name == "get_mementos":
519
+ logger.info(f"Getting mementos", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
520
+ args = json.loads(tool.function.arguments)
521
+ logger.info(f"ARGS: {args}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
522
+ queries = args['queries']
523
+
524
+ if 'on' not in args:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  on = pd.to_datetime(self.cm.state['date']).date()
526
  else:
527
+ on = args['on']
528
+ if on == '':
529
+ on = pd.to_datetime(self.cm.state['date']).date()
530
+ else:
531
+ on = pd.to_datetime(args['on']).date()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
 
 
 
533
 
534
+ logger.info(f"Query date: {on}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
535
+ # query the user memento db for all mementos where follow_up_on is equal to the query date
536
+ mementos = get_users_mementos(self.cm.user.user_id, on)
537
 
538
+ # if on == "":
539
+ # instruction = f"** Fetch all files (mementos) from this thread's Memento vector_store ([id={self.cm.user_personal_memory.id}]) **"
540
+ # else:
541
+ # instruction = f"** Fetch files (mementos) from this thread's Memento vector_store ([id={self.cm.user_personal_memory.id}]) where the follow_up_on field matches the query date: {on} (ignore the time component, focus only on the date)**"
542
+ # # f"** File search this threads' Memento vector_store ([id={self.cm.user_personal_memory.id}]) for the most relevant mementos based on the recent conversation history and context:{context} **"
543
 
544
+ logger.info(f"Finish Getting mementos: {mementos}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
545
+ tool_outputs.append({
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
546
  "tool_call_id": tool.id,
547
+ "output": f"Today's mementos: {mementos}" if len(mementos) else "No mementos to follow up today."
548
+ })
549
+ elif tool.function.name == "get_feedback_types":
550
+ print_log("WARNING","Calling get_feedback_types", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
551
+ logger.warning("Calling get_feedback_types", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
552
+ feedbacks = [
553
+ ("Inspirational Quotes", "📜", "Short, impactful quotes from the user's chosen Legendary Persona"),
554
+ ("Tips & Advice", "💡", "Practical suggestions or strategies for personal growth (no need to say \"Tips:\" in the beggining of your tips)"),
555
+ ("Encouragement", "🌈", "Positive affirmations and supportive statements"),
556
+ ("Personalized Recommendations", "🧩", "Tailored suggestions based on user progress"),
557
+ ("Affirmations", "", "Positive statements for self-belief and confidence"),
558
+ ("Mindfulness/Meditation", "🧘‍♀️", "Guided prompts for mindfulness practice"),
559
+ ("Book/Podcast", "📚🎧", "Suggestions aligned with user interests"),
560
+ ("Habit Tip", "🔄", "Tips for building and maintaining habits"),
561
+ ("Seasonal Content", "🌸", "Time and theme-relevant interactions"),
562
+ ("Fun Fact", "🎉", "Interesting and inspiring facts (no need to say \"Fun Fact:\" in the beggining of your tips)"),
563
+ ("Time Management", "⏳", "Tips for effective time management")
564
+ ]
565
+ sample_feedbacks = random.sample(feedbacks, 3)
566
+ print_log("INFO",f"Feedback types: {sample_feedbacks}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
567
+ logger.info(f"Feedback types: {sample_feedbacks}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
568
+ tool_outputs.append({
569
+ "tool_call_id": tool.id,
570
+ "output": "Generate a final coach message (feedback message) using these 3 feedback types (together with the stated emoji at the beginning of each feedback): " + str(sample_feedbacks)
571
+ })
572
+ elif tool.function.name == "search_resource":
573
+ type = json.loads(tool.function.arguments)['resource_type']
574
+ query = json.loads(tool.function.arguments)['query']
575
+ logger.info(f"Getting microaction theme: {type} - {query}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_microaction_theme"})
576
+ relevant_context = SearchEngine.search(type, query)
577
+ logger.info(f"Finish Getting microaction theme: {relevant_context}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_microaction_theme"})
578
+ tool_outputs.append({
579
+ "tool_call_id": tool.id,
580
+ "output": f"** Relevant context: {relevant_context} **"
581
+ })
582
+ elif tool.function.name == "end_conversation":
583
+ day_n = json.loads(tool.function.arguments)['day_n']
584
+ completed_micro_action = json.loads(tool.function.arguments)['completed_micro_action']
585
+ area_of_deep_reflection = json.loads(tool.function.arguments)['area_of_deep_reflection']
586
+
587
+ self.cm.user.update_micro_action_status(completed_micro_action)
588
+ self.cm.user.trigger_deep_reflection_point(area_of_deep_reflection)
589
+
590
+ # NOTE: we will record whether the user has completed the theme for the day
591
+ # NOTE: if no, we will include a short followup message to encourage the user the next day
592
+ logger.info(f"Ending conversation after {day_n} days. Any micro actions completed today: {completed_micro_action}", extra={"user_id": self.cm.user.user_id, "endpoint": "end_conversation"})
593
+ tool_outputs.append({
594
+ "tool_call_id": tool.id,
595
+ "output": "true"
596
+ })
597
+ elif tool.function.name == "create_smart_goal":
598
+ print_log("WARNING", f"Creating a SMART goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
599
+ logger.warning(f"Creating a SMART goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
600
+
601
+ user_goal = json.loads(tool.function.arguments)['goal']
602
+ user_goal_area = json.loads(tool.function.arguments)['area']
603
+
604
+ self.cm.user.set_goal(user_goal, user_goal_area)
605
+
606
+ print_log("INFO", f"SMART goal approved: {user_goal}", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
607
+ logger.info(f"SMART goal approved: {user_goal}", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
608
+
609
+ tool_outputs.append({
610
+ "tool_call_id": tool.id,
611
+ "output": "true"
612
+ })
613
+ elif tool.function.name == "start_now":
614
+ logger.info(f"Starting Growth Plan on Day 0",
615
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_start_now"})
616
+ # set intro finish to true
617
+ just_finish_intro = True
618
+
619
+ # cancel current run
620
+ run = PseudoRun(id=run.id, status="cancel", metadata={"message": "start_now"})
621
+ return run, just_finish_intro
622
+ elif tool.function.name == "change_goal":
623
+ logger.info(f"Changing user goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_change_goal"})
624
 
625
+ # switch back to the intro assistant, so we set just_finish_intro to False again
626
+ just_finish_intro = False
627
+ self.cm.user.reset_cumulative_plan_day()
628
+
629
+ # cancel current run
630
+ run = PseudoRun(id=run.id, status="cancel", metadata={"message": "change_goal"})
631
+ return run, just_finish_intro
632
+ elif tool.function.name == "complete_goal":
633
+ logger.info(f"Completing user goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
634
+ goal = self.cm.user.update_goal(None, 'COMPLETED')
635
+ logger.info(f"Marked users' goal: {goal} as COMPLETED", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
636
+ tool_outputs.append({
637
+ "tool_call_id": tool.id,
638
+ "output": f"Marked users' goal: {goal} as COMPLETED"
639
+ })
640
+ elif tool.function.name == "process_reminder":
641
+ reminder = json.loads(tool.function.arguments)["content"]
642
+ timestamp = json.loads(tool.function.arguments)["timestamp"]
643
+ recurrence = json.loads(tool.function.arguments)["recurrence"]
644
+ action = json.loads(tool.function.arguments)["action"]
645
+ logger.info(f"Setting reminder: {reminder} for {timestamp} with recurrence: {recurrence}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
646
+ # timestamp is a string like: YYYY-mm-ddTHH:MM:SSZ (2025-01-05T11:00:00Z)
647
+ # convert to datetime object
648
+ timestamp = pd.to_datetime(timestamp, format="%Y-%m-%dT%H:%M:%SZ")
649
+ logger.info(f"Formatted timestamp: {timestamp}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
650
+
651
+ output = f"({recurrence if recurrence else 'One-Time'}) Reminder ({reminder}) set for ({timestamp})"
652
+ logger.info(output,
653
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_set_reminder"})
654
+ self.cm.user.set_reminder({"reminder": reminder, "timestamp": timestamp, 'recurrence': recurrence, 'action': action})
655
+ tool_outputs.append({
656
+ "tool_call_id": tool.id,
657
+ "output": f"** {output} **"
658
+ })
659
+ elif tool.function.name == "get_user_info":
660
+ category = json.loads(tool.function.arguments)['category']
661
+ # one of [
662
+ # "personal",
663
+ # "challenges",
664
+ # "recommended_actions",
665
+ # "micro_actions",
666
+ # "other_focusses",
667
+ # "reminders",
668
+ # "goal",
669
+ # "growth_guide_session",
670
+ # "life_score",
671
+ # "recent_wins",
672
+ # "subscription_info"
673
+ # ]
674
+ logger.info(f"Getting user information: {category}",
675
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
676
+ user_info = '** If the user asks for their progress, also include a link to their Revelation Dashboard: {OURCOACH_DASHBOARD_URL} so that they can find out more **\n\n'
677
+ if category == "personal":
678
+ user_info += f"** Personal Information **\n\n{self.cm.user.user_info}"
679
+ user_info += f"\n\n** User's Mantra This Week**\n\n{self.cm.user.mantra}"
680
+ elif category == "challenges":
681
+ user_info += f"** User's Challenges (prioritise ONGOING challenges) **\n\n{self.cm.user.challenges}\n\nLet the user know that ongoing challenges from their growth guide will be integrated into their day-to-day interaction."
682
+ elif category == "recommended_actions":
683
+ user_info += f"** User's Recommended Actions (upcoming microactions, recommended by growth guide) **\n\n{self.cm.user.recommended_micro_actions}\n\nLet the user know that these microactions from their growth guide will be integrated into their day-to-day interaction."
684
+ elif category == "micro_actions":
685
+ user_info += f"** User's Micro Actions (already introduced microactions) **\n\n{self.cm.user.micro_actions}"
686
+ elif category == "other_focusses":
687
+ user_info += f"** User's Other Focusses (other areas of focus) **\n\n{self.cm.user.other_focusses}\n\nLet the user know that other areas of focus from their growth guide will be integrated into their day-to-day interaction."
688
+ elif category == "reminders":
689
+ user_info += f"** User's Reminders **\n\n{self.cm.user.reminders}"
690
+ elif category == "goal":
691
+ user_info += f"** User's Goal (prioritise the latest [last item in the array] goal). Do not mention the status of their goal. **\n\n{self.cm.user.goal}"
692
+ elif category == "growth_guide_session":
693
+ growth_guide = get_growth_guide(self.cm.user.user_id)
694
+ user_info += f"** Users' Growth Guide (always refer to the Growth Guide as 'Growth Guide <Name>') **\n{growth_guide}"
695
+ logger.info(f"User's growth guide: {growth_guide}",
696
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
697
+
698
+ booked_sessions = get_booked_gg_sessions(self.cm.user.user_id)
699
+
700
+ # for each booking, if the booking has completed, fetch the zoom_ai_summary and gg_report from
701
+ for booking in booked_sessions:
702
+ if booking['status'] == "completed":
703
+ summary_data = get_growth_guide_summary(self.cm.user.user_id, booking['booking_id'])
704
+ logger.info(f"Summary data for booking: {booking['booking_id']} - {summary_data}",
705
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
706
+ if summary_data:
707
+ booking['zoom_ai_summary'] = summary_data['zoom_ai_summary']
708
+ booking['gg_report'] = summary_data['gg_report']
709
+ else:
710
+ booking['zoom_ai_summary'] = "Growth Guide has not uploaded the report yet"
711
+ booking['gg_report'] = "Growth Guide has not uploaded the report yet"
712
+
713
+ logger.info(f"User's booked sessions: {booked_sessions}",
714
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
715
 
716
+ if len(booked_sessions):
717
+ # booked session is an array of jsons
718
+ # convers it to have i) json where i = 1...N where N is the len of booked_sessions
719
+ # join the entire array into 1 string with each item seperated by a newline
720
+ formatted_sessions = "\n".join([f"** Session {i+1} **\n{json.dumps(session, indent=4)}" for i, session in enumerate(booked_sessions)])
721
+ logger.info(f"Formatted booked sessions: {formatted_sessions}",
722
  extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
723
 
724
+ user_info += f"\n** GG Session Bookings & Summaries (most recent first, time is in users' local timezone but no need to mention this) **\n{formatted_sessions}"
725
+ else:
726
+ user_info += f"\n** GG Session Summaries **\nNo GG yet. Let the user know they can book one now through their Revelation Dashboard: {OURCOACH_DASHBOARD_URL}! (When including links, DO **NOT** use a hyperlink format. Just show the link as plain text. Example: \"Revelation Dashboard: https://...\")"
727
+ user_info += f"\n** Suggested Growth Guide Topics **\n{self.cm.user.recommended_gg_topics}\nOnly suggest 1-2 topics and let the user know they can can find more suggestions on their dashboard"
728
+ elif category == "life_score":
729
+ user_info += f"** User's Life scores for each area **\n\n Personal Growth: {self.cm.user.personal_growth_score} || Career: {self.cm.user.career_growth_score} || Health/Wellness: {self.cm.user.health_and_wellness_score} || Relationships: {self.cm.user.relationship_score} || Mental Health: {self.cm.user.mental_well_being_score}"
730
+ elif category == "recent_wins":
731
+ user_info += f"** User's Recent Wins / Achievements **\n\n {self.cm.user.recent_wins}"
732
+ elif category == "subscription_info":
733
+ subscription_history = get_user_subscriptions(self.cm.user.user_id)
734
+ logger.info(f"User's subscription history: {subscription_history}",
735
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
736
+ user_info += f"** User's Subscription Information **\n\n This is a sorted list (most recent first, which also represent the current subscription status) of the users subscription history:\n{subscription_history}\nNote that the stripe_status is one of 'trialing' = (user is on a free trial), 'cancelled' = (user has cancelled their subscription), 'active' = (user is a Premium user). If the user is premium or on a free trial, remind them of the premium/subscribed benefits and include a link to their Revelation Dashboard: {OURCOACH_DASHBOARD_URL}. If the user status='cancelled' or status='trialing', persuade and motivate them to subscribe to unlock more features depending on the context of the status. Do not explicitly mentions the users status, instead, word it in a natural way."
737
+ logger.info(f"Finish Getting user information: {user_info}",
738
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
739
+ tool_outputs.append({
740
+ "tool_call_id": tool.id,
741
+ "output": f"** User Info:\n\n{user_info} **"
742
+ })
743
+ elif tool.function.name == "extend_two_weeks":
744
+ logger.info(f"Changing plan from 1 week to 2 weeks...", extra={"user_id": self.cm.user.user_id, "endpoint": "extend_two_weeks"})
745
+ goal = self.cm.user.extend_growth_plan()
746
+ tool_outputs.append({
747
+ "tool_call_id": tool.id,
748
+ "output": f"Changed plan from 1 week to 2 weeks."
749
+ })
750
 
751
+ # Submit all tool outputs at once after collecting them in a list
752
+ if tool_outputs:
 
753
  run = self.cm.client.beta.threads.runs.submit_tool_outputs_and_poll(
754
  thread_id=thread.id,
755
  run_id=run.id,
 
758
  logger.info("Tool outputs submitted successfully",
759
  extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_submit_tools"})
760
  return run, just_finish_intro
761
+ else:
762
+ logger.warning("No tool outputs to submit",
763
+ extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_submit_tools"})
764
+ run = PseudoRun(status="completed", metadata={"message": "No tool outputs to submit"})
765
+ return run, just_finish_intro
766
+ except Exception as e:
767
+ # Cancel the run
768
+ logger.error(f"Yeap Error in call_tool: {e}", extra={"user_id": self.cm.user.user_id, "endpoint": 'assistant_call_tool'})
769
+ logger.error(f"Cancelling run {run.id} for thread {thread.id}", extra={"user_id": self.cm.user.user_id, "endpoint": 'cancel_erroneous_run'})
770
+ self.cancel_run(run, thread)
771
+ logger.error(f"Run {run.id} cancelled for thread {thread.id}", extra={"user_id": self.cm.user.user_id, "endpoint": 'cancel_erroneous_run'})
772
+ raise e
773
 
774
  class PseudoRun:
775
+ def __init__(self, status, id='pseudo_run', metadata=None):
776
+ self.id = id
777
  self.status = status
778
  self.metadata = metadata or {}
779
 
app/conversation_manager.py CHANGED
@@ -128,7 +128,11 @@ class ConversationManager:
128
  elif message == 'change_goal':
129
  self.intro_done = False
130
  logger.info(f"Changing goal, reset to intro assistant", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
131
-
 
 
 
 
132
 
133
  if hidden:
134
  self.client.beta.threads.messages.delete(message_id=message.id, thread_id=thread.id)
 
128
  elif message == 'change_goal':
129
  self.intro_done = False
130
  logger.info(f"Changing goal, reset to intro assistant", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
131
+ # Actually dont need this
132
+ elif message == 'error':
133
+ logger.error(f"Run was cancelled due to error", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
134
+ # self.add_message_to_thread(thread.id, "assistant", run.metadata['content'])
135
+ # return self._get_current_thread_history(remove_system_message=False)[-1], run
136
 
137
  if hidden:
138
  self.client.beta.threads.messages.delete(message_id=message.id, thread_id=thread.id)
app/flows.py CHANGED
@@ -137,7 +137,7 @@ Step 1:
137
  - **One Micro-Action Only:** Suggest only one action per day.
138
  - **Minimal Questions:** Use questions only when absolutely necessary and limit to one question in total per day.
139
  - **Avoid Over-Questioning:** Keep questions minimal to prevent overwhelming the user.
140
- - **Lists:** Provide a maximum of three items in any list.
141
 
142
  ---
143
 
@@ -179,7 +179,7 @@ Step 1:
179
  - **If** the user has completed yesterday's micro-action:
180
  - Do **not** ask anything and proceed to step 2
181
  - **If** the user hasn't completed it:
182
- - Ask if they're going to do it today.
183
  - **Unless** they've specified a different day—then proceed to Step 2.
184
 
185
  2. **Share Knowledge/Tips:**
@@ -191,7 +191,7 @@ Step 1:
191
  3. **Conclude:**
192
  - After the user replies, immediately call `end_conversation()`.
193
  - Conclude with a strong, motivational statement that reinforces their commitment, channeling the energy, mindset, and knowledge of your persona.
194
- - Keep your message short (like Whatsapp texting length)
195
 
196
  **If the user has an upcoming reminder:**
197
 
@@ -239,7 +239,7 @@ Step 1:
239
 
240
  - **Avoid Over-Questioning:** Keep questions creative and sparing; avoid overwhelming the user.
241
 
242
- - **Lists:** Provide a maximum of three items in any list.
243
 
244
  - **Quoting your persona:**
245
  - Do **not** mention the name of your persona when quoting.
@@ -279,7 +279,7 @@ User’s Context
279
  2. Ask no more than **three questions** per day. Limit to **one question per message** and encapsulate it with asterisks (*).
280
  3. Avoid overwhelming the user; keep your suggestions conversational and focused.
281
  4. Always guide the user with specific suggestions rather than seeking ideas from them.
282
- 5. Limit lists to a maximum of three items.
283
  6. Structure responses as if texting: short, friendly, and actionable.
284
  """
285
 
@@ -318,7 +318,7 @@ Objective: Assist users in reflecting on their progress, recognizing strengths,
318
  - **Encapsulate questions with asterisks:** like this *question* (Replace "question" with the actual question.)
319
 
320
  - **Guided Coaching:** Provide suggestions assertively rather than asking for the user's ideas or plans. Remember, you are the coach!
321
- - **Lists:** When listing actions or insights, limit to no more than three items.
322
 
323
  ---
324
 
@@ -400,7 +400,7 @@ Important Rules
400
  • Never explicitly tell the function that you are calling to the user (just do the function calling in the background)
401
  • Question Format: When you ask a question, encapsulate it with asterisks (e.g., What’s one thing you can commit to today?). Use only one question mark per message.
402
  • Avoid Over-Questioning: Keep questions creative, sparing, and avoid overwhelming the user.
403
- • Lists: Provide a maximum of three items in any list.
404
 
405
  When you want to mention a quote from your persona, you must not say the name of your persona. You should paraphrase the quote and say it like it's your own message!
406
  Bad Example: Hey <user name>! As <your persona> said, "<quote>"
@@ -469,7 +469,10 @@ Objective: To summarize the user's progress and achievements during the coaching
469
  Users Goal: {{}}
470
  The user is currently on day {{}}/{{}} of their journey.
471
 
472
- User's Last Growth Guide Session:
 
 
 
473
  {{}}
474
 
475
  ## ** GUIDELINE ** :
@@ -478,7 +481,7 @@ User's Last Growth Guide Session:
478
 
479
  - Highlight the user's progress and achievements precisely and concisely. IF the user has completed a growth guide session (see above), incorporate it into your response and reference that it was acheived during their growth guide session. Use bullet points to list down your items.
480
 
481
- - Explain to the user that coaching is a continuous journey, so the user has the option to either continue with the same goal or create a new plan.
482
 
483
  - Respond empathetically and adapt based on what the user shares.
484
 
@@ -492,15 +495,14 @@ User's Last Growth Guide Session:
492
 
493
  ## Example of Quality Interaction:
494
 
495
- - Congratulate the user for completing the growth plan until the last day (mention the current day, e.g. day 4)
496
-
497
- - Highlight the user's progress and achievements in bullet points. IF the user has completed a growth guide session (see above), incorporate it into your response and reference that it was acheived during their growth guide session. And tell them to check out their progress in the Revelation Dashboard here: https://app.staging.ourcoach.ai/
498
-
499
  - IF the user has not completed a growth guide session yet (see above), encourage the user to book a Growth Guide session and explain its benefits. Really upsell this by being like a Growth Guide salesman!
500
  - ELSE IF the user has booked a growth guide session (see above), tell them you hope it went well and they found it beneficial and let the use know that they could also book another via their Revelation Dashboard: https://app.staging.ourcoach.ai/
501
 
502
- - Wait for the user's response.
503
 
 
504
  - **IF** the user says they want to book a growth guide session, you reply with this message:
505
 
506
  Amazing! 🎉
@@ -525,11 +527,11 @@ And remember, a Growth Guide can help you reflect, strategize, and take things t
525
 
526
  Keep the momentum going—you’ve got this! 💥
527
 
528
- - If the user wants to extend the plan for another 2 weeks, call the extend_two_weeks() function!
529
 
530
- - If they want to set a new goal, confirm to the user first, and then call the change_goal() function if the user confirms the goal change!
531
 
532
- - If the user said that they've completed their goal, call the complete_goal() function!
533
 
534
  ** DO NOT ASK ANY OTHER QUESTION IN THIS STATE **
535
 
@@ -574,7 +576,7 @@ Important Rules
574
  • Never explicitly tell the function that you are calling to the user (just do the function calling in the background)
575
  • Question Format: When you ask a question, encapsulate it with asterisks (e.g., What’s one thing you can commit to today?). Use only one question mark per message.
576
  • Avoid Over-Questioning: Keep questions creative, sparing, and avoid overwhelming the user.
577
- • Lists: Provide a maximum of three items in any list.
578
  • Be concise! Use Whatsapp texting length!
579
 
580
  When you want to mention a quote from your persona, you must not say the name of your persona. You should paraphrase the quote and say it like it's your own message!
 
137
  - **One Micro-Action Only:** Suggest only one action per day.
138
  - **Minimal Questions:** Use questions only when absolutely necessary and limit to one question in total per day.
139
  - **Avoid Over-Questioning:** Keep questions minimal to prevent overwhelming the user.
140
+ - **Lists:** Provide a maximum of three items in any list. Use italic (single asterisk) instead of bold (double asterisk) in your list!
141
 
142
  ---
143
 
 
179
  - **If** the user has completed yesterday's micro-action:
180
  - Do **not** ask anything and proceed to step 2
181
  - **If** the user hasn't completed it:
182
+ - Only ask one question: are they going to do it today?
183
  - **Unless** they've specified a different day—then proceed to Step 2.
184
 
185
  2. **Share Knowledge/Tips:**
 
191
  3. **Conclude:**
192
  - After the user replies, immediately call `end_conversation()`.
193
  - Conclude with a strong, motivational statement that reinforces their commitment, channeling the energy, mindset, and knowledge of your persona.
194
+ - Keep your message short (like Whatsapp texting length) and don't ask more than 1 (one) question in one message!
195
 
196
  **If the user has an upcoming reminder:**
197
 
 
239
 
240
  - **Avoid Over-Questioning:** Keep questions creative and sparing; avoid overwhelming the user.
241
 
242
+ - **Lists:** Provide a maximum of three items in any list. Use italic (single asterisk) instead of bold (double asterisk) in your list!
243
 
244
  - **Quoting your persona:**
245
  - Do **not** mention the name of your persona when quoting.
 
279
  2. Ask no more than **three questions** per day. Limit to **one question per message** and encapsulate it with asterisks (*).
280
  3. Avoid overwhelming the user; keep your suggestions conversational and focused.
281
  4. Always guide the user with specific suggestions rather than seeking ideas from them.
282
+ 5. Limit lists to a maximum of three items. Use italic (single asterisk) instead of bold (double asterisk) in your list!
283
  6. Structure responses as if texting: short, friendly, and actionable.
284
  """
285
 
 
318
  - **Encapsulate questions with asterisks:** like this *question* (Replace "question" with the actual question.)
319
 
320
  - **Guided Coaching:** Provide suggestions assertively rather than asking for the user's ideas or plans. Remember, you are the coach!
321
+ - **Lists:** When listing actions or insights, limit to no more than three items. Use italic (single asterisk) instead of bold (double asterisk) in your list!
322
 
323
  ---
324
 
 
400
  • Never explicitly tell the function that you are calling to the user (just do the function calling in the background)
401
  • Question Format: When you ask a question, encapsulate it with asterisks (e.g., What’s one thing you can commit to today?). Use only one question mark per message.
402
  • Avoid Over-Questioning: Keep questions creative, sparing, and avoid overwhelming the user.
403
+ • Lists: Provide a maximum of three items in any list. Use italic (single asterisk) instead of bold (double asterisk) in your list!
404
 
405
  When you want to mention a quote from your persona, you must not say the name of your persona. You should paraphrase the quote and say it like it's your own message!
406
  Bad Example: Hey <user name>! As <your persona> said, "<quote>"
 
469
  Users Goal: {{}}
470
  The user is currently on day {{}}/{{}} of their journey.
471
 
472
+ The Growth Guide (always refer to the Growth Guide as 'your Growth Guide <Name>' or simply just their <Name>):
473
+ {{}}
474
+
475
+ User's Past Growth Guide Session(s) (most recent first):
476
  {{}}
477
 
478
  ## ** GUIDELINE ** :
 
481
 
482
  - Highlight the user's progress and achievements precisely and concisely. IF the user has completed a growth guide session (see above), incorporate it into your response and reference that it was acheived during their growth guide session. Use bullet points to list down your items.
483
 
484
+ - Explain to the user that growth is a continuous journey, so the user has the option to either continue with the same goal or create a new plan.
485
 
486
  - Respond empathetically and adapt based on what the user shares.
487
 
 
495
 
496
  ## Example of Quality Interaction:
497
 
498
+ 1. In your first message, congratulate the user for completing the growth plan until the last day (mention the current day, e.g. day 4)
499
+ Highlight the user's progress and achievements in bullet points. IF the user has completed a growth guide session (see above), incorporate it into your response and reference that it was acheived during their growth guide session. And tell them to check out their progress in the Revelation Dashboard here: https://app.staging.ourcoach.ai/
 
 
500
  - IF the user has not completed a growth guide session yet (see above), encourage the user to book a Growth Guide session and explain its benefits. Really upsell this by being like a Growth Guide salesman!
501
  - ELSE IF the user has booked a growth guide session (see above), tell them you hope it went well and they found it beneficial and let the use know that they could also book another via their Revelation Dashboard: https://app.staging.ourcoach.ai/
502
 
503
+ 2. *Wait for the user's response.*
504
 
505
+ 3. In your second message,
506
  - **IF** the user says they want to book a growth guide session, you reply with this message:
507
 
508
  Amazing! 🎉
 
527
 
528
  Keep the momentum going—you’ve got this! 💥
529
 
530
+ 4. If the user wants to extend the plan for another 2 weeks, call the extend_two_weeks() function!
531
 
532
+ 5. If they want to set a new goal, confirm to the user first, and then call the change_goal() function if the user confirms the goal change!
533
 
534
+ 6. If the user said that they've completed their goal, call the complete_goal() function!
535
 
536
  ** DO NOT ASK ANY OTHER QUESTION IN THIS STATE **
537
 
 
576
  • Never explicitly tell the function that you are calling to the user (just do the function calling in the background)
577
  • Question Format: When you ask a question, encapsulate it with asterisks (e.g., What’s one thing you can commit to today?). Use only one question mark per message.
578
  • Avoid Over-Questioning: Keep questions creative, sparing, and avoid overwhelming the user.
579
+ • Lists: Provide a maximum of three items in any list. Use italic (single asterisk) instead of bold (double asterisk) in your list!
580
  • Be concise! Use Whatsapp texting length!
581
 
582
  When you want to mention a quote from your persona, you must not say the name of your persona. You should paraphrase the quote and say it like it's your own message!
app/main.py CHANGED
@@ -294,6 +294,7 @@ class AssistantItem(BaseModel):
294
  class ChangeDateItem(BaseModel):
295
  user_id: str
296
  date: str
 
297
 
298
  class BookingItem(BaseModel):
299
  booking_id: str
@@ -320,15 +321,15 @@ def catch_endpoint_error(func):
320
  'endpoint': func.__name__
321
  })
322
  # Extract thread_id and run_id from error message
323
- thread_match = re.search(r'thread_(\w+)', str(e))
324
- run_match = re.search(r'run_(\w+)', str(e))
325
- if thread_match and run_match:
326
- thread_id = f"thread_{thread_match.group(1)}"
327
- run_id = f"run_{run_match.group(1)}"
328
- user = get_user(e.user_id)
329
- logger.info(f"Cancelling run {run_id} for thread {thread_id}", extra={"user_id": e.user_id, "endpoint": func.__name__})
330
- user.cancel_run(run_id, thread_id)
331
- logger.info(f"Run {run_id} cancelled for thread {thread_id}", extra={"user_id": e.user_id, "endpoint": func.__name__})
332
 
333
  raise HTTPException(
334
  status_code=status.HTTP_502_BAD_GATEWAY,
@@ -512,6 +513,7 @@ async def migrate_user(
512
  if hasattr(old_user_object, "growth_plan"): setattr(user, "growth_plan", old_user_object.growth_plan)
513
  if hasattr(old_user_object, "user_interaction_guidelines"): setattr(user, "user_interaction_guidelines", old_user_object.user_interaction_guidelines)
514
  if hasattr(old_user_object, "score_history"): setattr(user, "score_history", old_user_object.score_history)
 
515
 
516
  api_response = {
517
  "user": user.user_info,
@@ -531,7 +533,8 @@ async def migrate_user(
531
  "recommended_gg_topics": user.recommended_gg_topics,
532
  "growth_plan": user.growth_plan,
533
  "user_interaction_guidelines": user.user_interaction_guidelines,
534
- "score_history": user.score_history
 
535
  }
536
 
537
  add_to_cache(user)
@@ -812,6 +815,17 @@ async def create_user(
812
  logger.info(f"Successfully created user", extra={"user_id": request.user_id, "endpoint": "/create_user"})
813
  return {"message": {"info": f"[OK] User created: {user}", "messages": user.get_messages()}}
814
 
 
 
 
 
 
 
 
 
 
 
 
815
  @app.post("/chat")
816
  @catch_endpoint_error
817
  async def chat(
 
294
  class ChangeDateItem(BaseModel):
295
  user_id: str
296
  date: str
297
+ day: Optional[int] = None,
298
 
299
  class BookingItem(BaseModel):
300
  booking_id: str
 
321
  'endpoint': func.__name__
322
  })
323
  # Extract thread_id and run_id from error message
324
+ # thread_match = re.search(r'thread_(\w+)', str(e))
325
+ # run_match = re.search(r'run_(\w+)', str(e))
326
+ # if thread_match and run_match:
327
+ # thread_id = f"thread_{thread_match.group(1)}"
328
+ # run_id = f"run_{run_match.group(1)}"
329
+ # user = get_user(e.user_id)
330
+ # logger.info(f"Cancelling run {run_id} for thread {thread_id}", extra={"user_id": e.user_id, "endpoint": func.__name__})
331
+ # user.cancel_run(run_id, thread_id)
332
+ # logger.info(f"Run {run_id} cancelled for thread {thread_id}", extra={"user_id": e.user_id, "endpoint": func.__name__})
333
 
334
  raise HTTPException(
335
  status_code=status.HTTP_502_BAD_GATEWAY,
 
513
  if hasattr(old_user_object, "growth_plan"): setattr(user, "growth_plan", old_user_object.growth_plan)
514
  if hasattr(old_user_object, "user_interaction_guidelines"): setattr(user, "user_interaction_guidelines", old_user_object.user_interaction_guidelines)
515
  if hasattr(old_user_object, "score_history"): setattr(user, "score_history", old_user_object.score_history)
516
+ if hasattr(old_user_object, "cumulative_plan_day"): setattr(user, "cumulative_plan_day", old_user_object.cumulative_plan_day)
517
 
518
  api_response = {
519
  "user": user.user_info,
 
533
  "recommended_gg_topics": user.recommended_gg_topics,
534
  "growth_plan": user.growth_plan,
535
  "user_interaction_guidelines": user.user_interaction_guidelines,
536
+ "score_history": user.score_history,
537
+ "cumulative_plan_day": user.cumulative_plan_day
538
  }
539
 
540
  add_to_cache(user)
 
815
  logger.info(f"Successfully created user", extra={"user_id": request.user_id, "endpoint": "/create_user"})
816
  return {"message": {"info": f"[OK] User created: {user}", "messages": user.get_messages()}}
817
 
818
+ @app.post("/fetch_daily_alert")
819
+ @catch_endpoint_error
820
+ async def fetch_daily_alert(
821
+ request: ChangeDateItem,
822
+ api_key: str = Depends(get_api_key) # Change Security to Depends
823
+ ):
824
+ logger.info(f"Upselling GG for day: {request.day}", extra={"user_id": request.user_id, "endpoint": "/upsell_gg"})
825
+ user = get_user(request.user_id)
826
+ response = user.get_alerts(request.date, request.day)
827
+ return {"response": response}
828
+
829
  @app.post("/chat")
830
  @catch_endpoint_error
831
  async def chat(
app/user.py CHANGED
@@ -19,10 +19,11 @@ from app.flows import FINAL_SUMMARY_STATE, FINAL_SUMMARY_STATE, MICRO_ACTION_STA
19
  from pydantic import BaseModel
20
  from datetime import datetime
21
 
22
- from app.utils import generate_uuid, get_booked_gg_sessions, get_growth_guide_summary, update_growth_guide_summary
23
 
24
  import dotenv
25
  import re
 
26
  dotenv.load_dotenv()
27
 
28
  OURCOACH_DASHBOARD_URL = os.getenv("OURCOACH_DASHBOARD_URL")
@@ -126,6 +127,7 @@ class User:
126
  self.conversations = ConversationManager(client, self, asst_id)
127
 
128
  self.score_history = []
 
129
 
130
  @catch_error
131
  def extend_growth_plan(self):
@@ -195,6 +197,11 @@ class User:
195
  logger.info(f"Success.", extra={"user_id": self.user_id, "endpoint": "extend_growth_plan"})
196
  return True
197
 
 
 
 
 
 
198
  @catch_error
199
  def add_recent_wins(self, wins, context = None):
200
  prompt = f"""
@@ -612,10 +619,109 @@ class User:
612
  def set_intro_done(self):
613
  self.conversations.intro_done = True
614
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
  @catch_error
616
  def do_theme(self, theme, date, day, last_msg_is_answered = True):
617
  logger.info(f"Doing theme: {theme}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
618
 
 
 
 
 
619
  if self.reminders is not None and len(self.reminders):
620
  logger.info(f"ALL Upcoming Reminders: {self.reminders}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
621
  reminders = list(filter(lambda x : x['recurrence'] == 'postponed', self.reminders))
@@ -635,40 +741,59 @@ class User:
635
  logger.info(f"No reminders found for today ({pd.to_datetime(date).date()})", extra={"user_id": self.user_id, "endpoint": "do_theme"})
636
 
637
  if theme == "MOTIVATION_INSPIRATION_STATE":
638
- formatted_message = MOTIVATION_INSPIRATION_STATE.format(self.get_current_goal(), day, len(self.growth_plan.array))
639
  elif theme == "PROGRESS_REFLECTION_STATE":
640
- formatted_message = PROGRESS_REFLECTION_STATE.format(self.get_current_goal(), day, len(self.growth_plan.array))
641
  if len(self.challenges):
642
  challenge = self.challenges.pop(0)
643
  formatted_message += f"\n\n** IMPORTANT: Today, reflect on the users' challenge of: {challenge.content}, which they brought up during their growth guide session (let the user know we are bringing it up because of this) **"
644
  elif theme == "MICRO_ACTION_STATE":
645
  reminder_message = "\n".join([f"{i+1}. {reminder}" for i, reminder in enumerate(reminders)]) if reminders else "User has no postponed micro-actions"
646
 
647
- formatted_message = MICRO_ACTION_STATE.format(self.get_current_goal(), day, len(self.growth_plan.array), reminder_message)
648
  if len(self.recommended_micro_actions):
649
  todays_micro_action = self.recommended_micro_actions.pop(0)
650
  formatted_message += f"\n\n** IMPORTANT: Today's Micro Action is: {todays_micro_action.content}, which was recommended during their growth guide session (let the user know we are bringing it up because of this) **"
651
  elif theme == "OPEN_DISCUSSION_STATE":
652
- formatted_message = OPEN_DISCUSSION_STATE.format(self.get_current_goal(), day, len(self.growth_plan.array))
653
  if len(self.other_focusses):
654
  focus = self.other_focusses.pop(0)
655
  formatted_message += f"\n\n** IMPORTANT: Today, focus the discussion on: {focus.content}, which they discussed during their growth guide session (let the user know we are bringing it up because of this) **"
656
  elif theme == "PROGRESS_SUMMARY_STATE":
657
- formatted_message = PROGRESS_SUMMARY_STATE.format(self.get_current_goal(), day, len(self.growth_plan.array))
658
  elif theme == "FINAL_SUMMARY_STATE":
659
- gg_summary = "<User has not had a Growth Guide session yet>"
660
- if self.last_gg_session:
661
- gg_summary = get_growth_guide_summary(self.user_id, self.last_gg_session)
662
- formatted_message = FINAL_SUMMARY_STATE.format(self.get_current_goal(), day, len(self.growth_plan.array), gg_summary)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
663
  elif theme == "EDUCATION_STATE":
664
- formatted_message = EDUCATION_STATE.format(self.get_current_goal(), day, len(self.growth_plan.array))
665
  elif theme == "FOLLUP_ACTION_STATE":
666
  reminder_message = "\n".join([f"{i+1}. {reminder}" for i, reminder in enumerate(reminders)]) if reminders else "User has no postponed micro-actions"
667
- formatted_message = FOLLUP_ACTION_STATE.format(self.get_current_goal(), day, len(self.growth_plan.array), reminder_message)
668
  elif theme == "FUNFACT_STATE":
669
  topics = ["Fun Fact about the User's Goal", "How Personality Type is affecting/shaping their behaviour towards the goal", "How Love Language may impact and be relevant toward the goal"]
670
  randomized_topic = random.choice(topics)
671
- formatted_message = FUNFACT_STATE.format(self.get_current_goal(), day, len(self.growth_plan.array), randomized_topic)
672
 
673
  # prompt = f"""** It is a new day: {date} **
674
  # Additional System Instruction:
@@ -709,44 +834,8 @@ class User:
709
  If the answer above is "False", you must immediately ask something like (but warmer) "Hey <user name>, I've noticed that you haven't answered my latest question. Do you still want to continue the growth plan?". If the user says "no", then ask if they want to set a new goal (therefore later, call the change_goal() function)
710
  But if the user says "yes", then proceed to do the instruction below.
711
 
712
- Additional System Instruction:
713
- ** Always remember to incorporate your personality (based on your persona) into all of your responses. **
714
- 1. Focus on giving more wisdom than questions:
715
- - Provide assertive guidance, encouragement, validation, and different viewpoints.
716
- - Be critical and challenge the user's arguments as needed.
717
- - Avoid asking too many questions.
718
- - Offer strategies instead of asking for the user's strategies.
719
 
720
- 2. Keep messages concise & use Whatsapp texting length:
721
- - Use natural, casual language that's easy to understand.
722
-
723
- 3. Limit interaction to three messages per day (until the user says "it is a new day"):
724
- - Send each message one at a time.
725
- - Warmly end the daily interaction in the third message.
726
-
727
- 4. Include zero or one question per message (optional):
728
- - If including a question, place it after a line break and encapsulate with one asterisk: *question*
729
-
730
- 5. Ensure messages are personalized and creative:
731
- - Enrich content with user information.
732
- - Avoid being a "boring" coach with generic questions or advice.
733
-
734
- 6. Channel the energy, wisdom, and mindset of your persona:
735
- - Reiterate your famous sayings.
736
- - Explain your thinking philosophy in a natural, casual conversational tone (not like a lecture).
737
-
738
- 7. You may greet the user by name on a new day, but no need to use their name in every message.
739
-
740
- 8. You **MUST** call the process_reminder() function if the user wants to postpone their micro-action
741
-
742
- 9. When talking about Growth Guide, you must try to upsell the growth guide. Meaning that you are always encouraging the user to book a Growth Guide session and explain it benefits. Be like the Growth Guide's salesman!
743
-
744
- 10. If the user asks a question to you, you must answer the user's question directly & concisely, without asking a follow-up question.
745
-
746
- 11. If you want to bold your message, always use one (1) asterisk (do not use the markdown bolding method or double asterisk)
747
-
748
- 12. When your message includes a link, you **must** always mention the link directly without using hyperlinks (example: [link](link.com) or [link.com](link.com) are wrong). Hence, provide the website URL as plain text! (example: "Hi there, you can access it in this link: link.com", is correct)
749
-
750
  Today's Theme:
751
  {formatted_message}
752
  """
 
19
  from pydantic import BaseModel
20
  from datetime import datetime
21
 
22
+ from app.utils import generate_uuid, get_booked_gg_sessions, get_growth_guide, get_growth_guide_summary, get_user_subscriptions, update_growth_guide_summary
23
 
24
  import dotenv
25
  import re
26
+ import math
27
  dotenv.load_dotenv()
28
 
29
  OURCOACH_DASHBOARD_URL = os.getenv("OURCOACH_DASHBOARD_URL")
 
127
  self.conversations = ConversationManager(client, self, asst_id)
128
 
129
  self.score_history = []
130
+ self.cumulative_plan_day = 0
131
 
132
  @catch_error
133
  def extend_growth_plan(self):
 
197
  logger.info(f"Success.", extra={"user_id": self.user_id, "endpoint": "extend_growth_plan"})
198
  return True
199
 
200
+ @catch_error
201
+ def reset_cumulative_plan_day(self):
202
+ logger.info(f"Reseting cumulative_plan_day", extra={"user_id": self.user_id, "endpoint": "reset_cumulative_plan_day"})
203
+ self.cumulative_plan_day = 0
204
+
205
  @catch_error
206
  def add_recent_wins(self, wins, context = None):
207
  prompt = f"""
 
619
  def set_intro_done(self):
620
  self.conversations.intro_done = True
621
 
622
+ @catch_error
623
+ def get_alerts(self, date, day=None):
624
+ # responses = []
625
+ if day is None:
626
+ day = self.cumulative_plan_day
627
+ if day == 2 or day == 5:
628
+ # upsell the GG
629
+ growth_guide = get_growth_guide(self.user_id)
630
+
631
+ upsell_prompt = "introduce WHO their grwoth guide is and how a GG can help them" if day == 2 else "Let the user know that their Growth Guide <Name> (no need to re-introduce them) is available to enhance their current growth journey with you and based on the converstaion history so far and the users personal information, challenges and goals suggest WHAT they can discuss with their growth guide."
632
+
633
+ prompt = f"""You are an expert ambassador/salesman of Growth Guide sessions.
634
+ The users' growth guide is {growth_guide}.
635
+
636
+ Respond with a enthusiatic hello! Then, based on the current day ({day}), succintly:
637
+ {upsell_prompt}
638
+ Frame your response like a you are telling the user a fun fact, but dont explicitly mention "fun fact".
639
+ """
640
+
641
+ # send upsell gg alert at 7pm
642
+ timestamp = pd.Timestamp.now().replace(hour=19, minute=0, second=0, microsecond=0).strftime("%d-%m-%Y %a %H:%M:%S")
643
+ elif day == 8:
644
+ # alert the user that we are always collecting feedback in order to improve. Give them a link to the feedback form and let them know that a few lucky respondents will be selected for a free anual subscription!
645
+ prompt = f"""You are an expert ambassador/salesman of ourcoach whose objective is to upsell the ourcoach subscription based on the following context:
646
+ We are always collecting feedback in order to improve our services. Please take a moment to fill out our feedback form: http://feedback_form. A few lucky respondents will be selected for a free anual subscription!"""
647
+ timestamp = pd.Timestamp.now().replace(hour=19, minute=0, second=0, microsecond=0).strftime("%d-%m-%Y %a %H:%M:%S")
648
+ elif day == 12:
649
+ growth_guide = get_growth_guide(self.user_id)['full_name']
650
+
651
+ subscription = get_user_subscriptions(self.user_id)[0]
652
+
653
+ subscription_end_date = pd.to_datetime(subscription['subscription_end_date'])
654
+
655
+ # get difference between subscription end date and date
656
+ date = pd.to_datetime(date)
657
+ days_left = (subscription_end_date - date).days
658
+ logger.info(f"{subscription_end_date} - {date} = Days left: {days_left}", extra={"user_id": self.user_id, "endpoint": "get_alerts"})
659
+ if days_left <= 2:
660
+ subscription_alert = f"""** User's Subscription Information **
661
+ Users current subscription:
662
+ {subscription}
663
+
664
+ Users growth guide:
665
+ {growth_guide}
666
+
667
+ Alert the user that their free trial is ending in {days_left} days and Sell them to subscribe via their Revelation Dashboard: {OURCOACH_DASHBOARD_URL} to continue chatting with you, receiving personalized advice and guidance and access to Growth Guide sessions. Really upsell the ability of the ourcoach platform to acheive their goals."
668
+ """
669
+
670
+ prompt = f"""You are an expert ambassador/salesman of ourcoach (product) whose objective is to upsell the ourcoach subscription based on the following context:
671
+ {subscription_alert}
672
+ """
673
+ # send reminder alert at 7pm
674
+ timestamp = pd.Timestamp.now().replace(hour=19, minute=0, second=0, microsecond=0).strftime("%d-%m-%Y %a %H:%M:%S")
675
+ else:
676
+ return []
677
+ elif day == 14:
678
+ growth_guide = get_growth_guide(self.user_id)['full_name']
679
+
680
+ subscription = get_user_subscriptions(self.user_id)[0]
681
+
682
+ subscription_end_date = pd.to_datetime(subscription['subscription_end_date'])
683
+
684
+ # get difference between subscription end date and date
685
+ date = pd.to_datetime(date)
686
+ days_left = (subscription_end_date - date).days
687
+ logger.info(f"{subscription_end_date} - {date} = Days left: {days_left}", extra={"user_id": self.user_id, "endpoint": "get_alerts"})
688
+ if days_left <= 0:
689
+ subscription_alert = f"""** User's Subscription Information **
690
+ Users current subscription:
691
+ {subscription}
692
+
693
+ Users growth guide:
694
+ {growth_guide}
695
+
696
+ OMG the users subscription is ending today! If you lose this user you and your family will not be able to survive!
697
+ You have to persuade the user to stay so use the best of your salesman abilities and sell them to continue to subscribe to ourcoach, otherwise, how will you put food on the table???"
698
+ """
699
+ prompt = f"""You are an expert ambassador/salesman of ourcoach whose objective is to upsell the ourcoach subscription based on the following context:
700
+ {subscription_alert}
701
+ """
702
+ # send reminder alert at 7pm
703
+ timestamp = pd.Timestamp.now().replace(hour=19, minute=0, second=0, microsecond=0).strftime("%d-%m-%Y %a %H:%M:%S")
704
+ else:
705
+ return []
706
+ else:
707
+ return []
708
+
709
+ response, run = self.conversations._run_current_thread(prompt, hidden=True)
710
+ message = run.metadata.get("message", "No message")
711
+ logger.info(f"Message: {message}", extra={"user_id": self.user_id, "endpoint": "upsell_gg"})
712
+
713
+ # make timestamp ISO
714
+ response['timestamp'] = pd.to_datetime(timestamp).isoformat()
715
+ return [response]
716
+
717
  @catch_error
718
  def do_theme(self, theme, date, day, last_msg_is_answered = True):
719
  logger.info(f"Doing theme: {theme}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
720
 
721
+ # Add 1 day to cumulative_plan_day
722
+ self.cumulative_plan_day += 1
723
+ final_day = (math.ceil((self.cumulative_plan_day+7)/14) * 14) - 7
724
+
725
  if self.reminders is not None and len(self.reminders):
726
  logger.info(f"ALL Upcoming Reminders: {self.reminders}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
727
  reminders = list(filter(lambda x : x['recurrence'] == 'postponed', self.reminders))
 
741
  logger.info(f"No reminders found for today ({pd.to_datetime(date).date()})", extra={"user_id": self.user_id, "endpoint": "do_theme"})
742
 
743
  if theme == "MOTIVATION_INSPIRATION_STATE":
744
+ formatted_message = MOTIVATION_INSPIRATION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
745
  elif theme == "PROGRESS_REFLECTION_STATE":
746
+ formatted_message = PROGRESS_REFLECTION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
747
  if len(self.challenges):
748
  challenge = self.challenges.pop(0)
749
  formatted_message += f"\n\n** IMPORTANT: Today, reflect on the users' challenge of: {challenge.content}, which they brought up during their growth guide session (let the user know we are bringing it up because of this) **"
750
  elif theme == "MICRO_ACTION_STATE":
751
  reminder_message = "\n".join([f"{i+1}. {reminder}" for i, reminder in enumerate(reminders)]) if reminders else "User has no postponed micro-actions"
752
 
753
+ formatted_message = MICRO_ACTION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day, reminder_message)
754
  if len(self.recommended_micro_actions):
755
  todays_micro_action = self.recommended_micro_actions.pop(0)
756
  formatted_message += f"\n\n** IMPORTANT: Today's Micro Action is: {todays_micro_action.content}, which was recommended during their growth guide session (let the user know we are bringing it up because of this) **"
757
  elif theme == "OPEN_DISCUSSION_STATE":
758
+ formatted_message = OPEN_DISCUSSION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
759
  if len(self.other_focusses):
760
  focus = self.other_focusses.pop(0)
761
  formatted_message += f"\n\n** IMPORTANT: Today, focus the discussion on: {focus.content}, which they discussed during their growth guide session (let the user know we are bringing it up because of this) **"
762
  elif theme == "PROGRESS_SUMMARY_STATE":
763
+ formatted_message = PROGRESS_SUMMARY_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
764
  elif theme == "FINAL_SUMMARY_STATE":
765
+ past_gg_summary = "<User has not had a Growth Guide session yet>"
766
+ growth_guide = get_growth_guide(self.user_id)
767
+
768
+ booked_sessions = get_booked_gg_sessions(self.user_id)
769
+
770
+ # filter out only completed (past) sessions
771
+ past_sessions = [session for session in booked_sessions if session['status'] == "completed"]
772
+
773
+ # for each past booking, fetch the zoom_ai_summary and gg_report from
774
+ for booking in past_sessions:
775
+ summary_data = get_growth_guide_summary(self.user_id, booking['booking_id'])
776
+ logger.info(f"Summary data for booking: {booking['booking_id']} - {summary_data}",
777
+ extra={"user_id": self.user_id, "endpoint": "assistant_get_user_info"})
778
+ if summary_data:
779
+ booking['zoom_ai_summary'] = summary_data['zoom_ai_summary']
780
+ booking['gg_report'] = summary_data['gg_report']
781
+ else:
782
+ booking['zoom_ai_summary'] = "Growth Guide has not uploaded the report yet"
783
+ booking['gg_report'] = "Growth Guide has not uploaded the report yet"
784
+ if len(past_sessions):
785
+ past_gg_summary = "\n".join([f"** Session {i+1} **\n{json.dumps(session, indent=4)}" for i, session in enumerate(past_sessions)])
786
+
787
+ formatted_message = FINAL_SUMMARY_STATE.format(self.get_current_goal(), day, len(self.growth_plan.array), growth_guide, past_gg_summary)
788
  elif theme == "EDUCATION_STATE":
789
+ formatted_message = EDUCATION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
790
  elif theme == "FOLLUP_ACTION_STATE":
791
  reminder_message = "\n".join([f"{i+1}. {reminder}" for i, reminder in enumerate(reminders)]) if reminders else "User has no postponed micro-actions"
792
+ formatted_message = FOLLUP_ACTION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day, reminder_message)
793
  elif theme == "FUNFACT_STATE":
794
  topics = ["Fun Fact about the User's Goal", "How Personality Type is affecting/shaping their behaviour towards the goal", "How Love Language may impact and be relevant toward the goal"]
795
  randomized_topic = random.choice(topics)
796
+ formatted_message = FUNFACT_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day, randomized_topic)
797
 
798
  # prompt = f"""** It is a new day: {date} **
799
  # Additional System Instruction:
 
834
  If the answer above is "False", you must immediately ask something like (but warmer) "Hey <user name>, I've noticed that you haven't answered my latest question. Do you still want to continue the growth plan?". If the user says "no", then ask if they want to set a new goal (therefore later, call the change_goal() function)
835
  But if the user says "yes", then proceed to do the instruction below.
836
 
837
+ Today is day {self.cumulative_plan_day} of the user's growth journey (out of {final_day} days). You may (or may not) mention this occasionally in your first message of the day.
 
 
 
 
 
 
838
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
839
  Today's Theme:
840
  {formatted_message}
841
  """
app/utils.py CHANGED
@@ -425,13 +425,14 @@ def get_user_summary(user_id, update_rec_topics=False):
425
  ### **2. User's Growth Guide Preparation Brief**
426
 
427
  **Objective**: Guide the user on what to discuss with the Growth Guide, providing actionable advice and highlighting key areas to focus on during their session, covering the five key areas.
428
- You must use the user's current **challenges** and **life goal** to make the preparation brief **personalized**!
429
 
430
  Important Rules:
431
  1. **ALWAYS** be succinct, valuable and personalized! Do **NOT** ask generic question. Ask a personalized question! And bold the key parts of the user brief!
432
  2. **Session Length Awareness**: Be realistic about what can be effectively discussed in a 30-minute session. Prioritize the areas that are most pressing or offer the greatest opportunity for positive change.
433
  3. **Guidance for Interaction**: Provide specific suggestions for topics to discuss with the **Growth Guide**, you are encouraged to use phrases like "Discuss with your Growth Guide how to...".
434
  4. And for the second time, please be succinct and concise!!!
 
435
 
436
  **Format**:
437
 
@@ -1087,7 +1088,7 @@ def get_user_info(user_id):
1087
  try:
1088
  with psycopg2.connect(**db_params) as conn:
1089
  with conn.cursor() as cursor:
1090
- query = sql.SQL("SELECT * FROM {table} WHERE id = %s").format(table=sql.Identifier('public', 'users'))
1091
  cursor.execute(query, (user_id,))
1092
  row = cursor.fetchone()
1093
  if (row):
@@ -1103,6 +1104,7 @@ def get_user_info(user_id):
1103
  ### USER PROFILE ###
1104
 
1105
  Name: {user_data_clean.get('firstName', '')}
 
1106
  {user_data_clean.get('firstName', '')}'s challenges (You **must** use this information for the PLANNING STATE):
1107
  {challenges}
1108
  Persona:
 
425
  ### **2. User's Growth Guide Preparation Brief**
426
 
427
  **Objective**: Guide the user on what to discuss with the Growth Guide, providing actionable advice and highlighting key areas to focus on during their session, covering the five key areas.
428
+ You must use the user's current **challenges** and **life goal** to make the preparation brief **personalized**! You **must** bold some words that you think is important! but it does **not** have to be the first few words!
429
 
430
  Important Rules:
431
  1. **ALWAYS** be succinct, valuable and personalized! Do **NOT** ask generic question. Ask a personalized question! And bold the key parts of the user brief!
432
  2. **Session Length Awareness**: Be realistic about what can be effectively discussed in a 30-minute session. Prioritize the areas that are most pressing or offer the greatest opportunity for positive change.
433
  3. **Guidance for Interaction**: Provide specific suggestions for topics to discuss with the **Growth Guide**, you are encouraged to use phrases like "Discuss with your Growth Guide how to...".
434
  4. And for the second time, please be succinct and concise!!!
435
+ 5. You **must** bold some words that you think is important! but it does **not** have to be the first few words!
436
 
437
  **Format**:
438
 
 
1088
  try:
1089
  with psycopg2.connect(**db_params) as conn:
1090
  with conn.cursor() as cursor:
1091
+ query = sql.SQL("SELECT left(onboarding,length(onboarding)-1)||',\"growth_guide_name\":\"'||coalesce(b.full_name,'')||'\"}}' onboarding FROM {table} a LEFT JOIN {coach_tbl} b ON a.assign_coach_id = b.id WHERE a.id = %s").format(table=sql.Identifier('public', 'users'), coach_tbl = sql.Identifier('public','coach'))
1092
  cursor.execute(query, (user_id,))
1093
  row = cursor.fetchone()
1094
  if (row):
 
1104
  ### USER PROFILE ###
1105
 
1106
  Name: {user_data_clean.get('firstName', '')}
1107
+ Growth Guide Name: {user_data_clean.get('growth_guide_name', '')}
1108
  {user_data_clean.get('firstName', '')}'s challenges (You **must** use this information for the PLANNING STATE):
1109
  {challenges}
1110
  Persona: