BMCVRN commited on
Commit
c6a83fd
·
verified ·
1 Parent(s): af93e5f

Shaggy_HF_Feedback (#13)

Browse files

- Growth Guide fix for Final Day (287763fb9556a6340fc1111854a85f53b1aa8365)
- Added Alerts and other HF feedback (24c93b67d1934a620f8d8ff36dcca2cdc4b17575)
- Merge https://huggingface.co/spaces/ourcoach-ai/fastapi-v2 into pr/13 (5c0f3749e4d32534e17765cc1cf4285d8a188f6b)
- Small fix for fetch alerts (b7b7ad3cf26aa91908c2cac6836b1ac774b07ee4)

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,345 +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
- self.cm.user.reset_cumulative_plan_day()
621
-
622
- # cancel current run
623
- run = self.cancel_run(run, thread)
624
- run = PseudoRun(status="cancelled", metadata={"message": "change_goal"})
625
- return run, just_finish_intro
626
- elif tool.function.name == "complete_goal":
627
- logger.info(f"Completing user goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
628
- goal = self.cm.user.update_goal(None, 'COMPLETED')
629
- logger.info(f"Marked users' goal: {goal} as COMPLETED", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
630
- tool_outputs.append({
631
- "tool_call_id": tool.id,
632
- "output": f"Marked users' goal: {goal} as COMPLETED"
633
- })
634
- elif tool.function.name == "process_reminder":
635
- reminder = json.loads(tool.function.arguments)["content"]
636
- timestamp = json.loads(tool.function.arguments)["timestamp"]
637
- recurrence = json.loads(tool.function.arguments)["recurrence"]
638
- action = json.loads(tool.function.arguments)["action"]
639
- logger.info(f"Setting reminder: {reminder} for {timestamp} with recurrence: {recurrence}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
640
- # timestamp is a string like: YYYY-mm-ddTHH:MM:SSZ (2025-01-05T11:00:00Z)
641
- # convert to datetime object
642
- timestamp = pd.to_datetime(timestamp, format="%Y-%m-%dT%H:%M:%SZ")
643
- logger.info(f"Formatted timestamp: {timestamp}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
644
-
645
- output = f"({recurrence if recurrence else 'One-Time'}) Reminder ({reminder}) set for ({timestamp})"
646
- logger.info(output,
647
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_set_reminder"})
648
- self.cm.user.set_reminder({"reminder": reminder, "timestamp": timestamp, 'recurrence': recurrence, 'action': action})
649
- tool_outputs.append({
650
  "tool_call_id": tool.id,
651
- "output": f"** {output} **"
652
- })
653
- elif tool.function.name == "get_user_info":
654
- category = json.loads(tool.function.arguments)['category']
655
- # one of [
656
- # "personal",
657
- # "challenges",
658
- # "recommended_actions",
659
- # "micro_actions",
660
- # "other_focusses",
661
- # "reminders",
662
- # "goal",
663
- # "growth_guide_session",
664
- # "life_score",
665
- # "recent_wins",
666
- # "subscription_info"
667
- # ]
668
- logger.info(f"Getting user information: {category}",
669
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
670
- 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'
671
- if category == "personal":
672
- user_info += f"** Personal Information **\n\n{self.cm.user.user_info}"
673
- user_info += f"\n\n** User's Mantra This Week**\n\n{self.cm.user.mantra}"
674
- elif category == "challenges":
675
- 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."
676
- elif category == "recommended_actions":
677
- 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."
678
- elif category == "micro_actions":
679
- user_info += f"** User's Micro Actions (already introduced microactions) **\n\n{self.cm.user.micro_actions}"
680
- elif category == "other_focusses":
681
- 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."
682
- elif category == "reminders":
683
- user_info += f"** User's Reminders **\n\n{self.cm.user.reminders}"
684
- elif category == "goal":
685
- 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}"
686
- elif category == "growth_guide_session":
687
- growth_guide = get_growth_guide(self.cm.user.user_id)
688
- 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}"
689
- logger.info(f"User's growth guide: {growth_guide}",
690
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
691
 
692
- booked_sessions = get_booked_gg_sessions(self.cm.user.user_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
 
694
- # for each booking, if the booking has completed, fetch the zoom_ai_summary and gg_report from
695
- for booking in booked_sessions:
696
- if booking['status'] == "completed":
697
- summary_data = get_growth_guide_summary(self.cm.user.user_id, booking['booking_id'])
698
- logger.info(f"Summary data for booking: {booking['booking_id']} - {summary_data}",
 
699
  extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
700
- if summary_data:
701
- booking['zoom_ai_summary'] = summary_data['zoom_ai_summary']
702
- booking['gg_report'] = summary_data['gg_report']
703
- else:
704
- booking['zoom_ai_summary'] = "Growth Guide has not uploaded the report yet"
705
- booking['gg_report'] = "Growth Guide has not uploaded the report yet"
706
-
707
- logger.info(f"User's booked sessions: {booked_sessions}",
708
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
709
-
710
- if len(booked_sessions):
711
- # booked session is an array of jsons
712
- # convers it to have i) json where i = 1...N where N is the len of booked_sessions
713
- # join the entire array into 1 string with each item seperated by a newline
714
- formatted_sessions = "\n".join([f"** Session {i+1} **\n{json.dumps(session, indent=4)}" for i, session in enumerate(booked_sessions)])
715
- logger.info(f"Formatted booked sessions: {formatted_sessions}",
716
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
717
 
718
- user_info += f"\n** GG Session Bookings & Summaries (most recent first) **\n{formatted_sessions}"
719
- else:
720
- 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://...\")"
721
- 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"
722
- elif category == "life_score":
723
- 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}"
724
- elif category == "recent_wins":
725
- user_info += f"** User's Recent Wins / Achievements **\n\n {self.cm.user.recent_wins}"
726
- elif category == "subscription_info":
727
- subscription_history = get_user_subscriptions(self.cm.user.user_id)
728
- logger.info(f"User's subscription history: {subscription_history}",
729
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
730
- 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."
731
- logger.info(f"Finish Getting user information: {user_info}",
732
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
733
- tool_outputs.append({
734
- "tool_call_id": tool.id,
735
- "output": f"** User Info:\n\n{user_info} **"
736
- })
737
- elif tool.function.name == "extend_two_weeks":
738
- logger.info(f"Changing plan from 1 week to 2 weeks...", extra={"user_id": self.cm.user.user_id, "endpoint": "extend_two_weeks"})
739
- goal = self.cm.user.extend_growth_plan()
740
- tool_outputs.append({
741
- "tool_call_id": tool.id,
742
- "output": f"Changed plan from 1 week to 2 weeks."
743
- })
744
 
745
- # Submit all tool outputs at once after collecting them in a list
746
- if tool_outputs:
747
- try:
748
  run = self.cm.client.beta.threads.runs.submit_tool_outputs_and_poll(
749
  thread_id=thread.id,
750
  run_id=run.id,
@@ -753,17 +758,22 @@ class Assistant:
753
  logger.info("Tool outputs submitted successfully",
754
  extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_submit_tools"})
755
  return run, just_finish_intro
756
- except Exception as e:
757
- raise OpenAIRequestError(user_id=self.cm.user.id, message="Error submitting tool outputs", e=str(e), run_id=run.id)
758
- else:
759
- logger.warning("No tool outputs to submit",
760
- extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_submit_tools"})
761
- run = PseudoRun(status="completed", metadata={"message": "No tool outputs to submit"})
762
- return run, just_finish_intro
 
 
 
 
 
763
 
764
  class PseudoRun:
765
- def __init__(self, status, metadata=None):
766
- self.id = "pseudo_run"
767
  self.status = status
768
  self.metadata = metadata or {}
769
 
 
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
@@ -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 ** :
 
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 ** :
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,
@@ -814,6 +815,17 @@ async def create_user(
814
  logger.info(f"Successfully created user", extra={"user_id": request.user_id, "endpoint": "/create_user"})
815
  return {"message": {"info": f"[OK] User created: {user}", "messages": user.get_messages()}}
816
 
 
 
 
 
 
 
 
 
 
 
 
817
  @app.post("/chat")
818
  @catch_endpoint_error
819
  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,
 
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,7 +19,7 @@ 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
@@ -619,6 +619,101 @@ class User:
619
  def set_intro_done(self):
620
  self.conversations.intro_done = True
621
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
622
  @catch_error
623
  def do_theme(self, theme, date, day, last_msg_is_answered = True):
624
  logger.info(f"Doing theme: {theme}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
@@ -667,10 +762,29 @@ class User:
667
  elif theme == "PROGRESS_SUMMARY_STATE":
668
  formatted_message = PROGRESS_SUMMARY_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
669
  elif theme == "FINAL_SUMMARY_STATE":
670
- gg_summary = "<User has not had a Growth Guide session yet>"
671
- if self.last_gg_session:
672
- gg_summary = get_growth_guide_summary(self.user_id, self.last_gg_session)
673
- formatted_message = FINAL_SUMMARY_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day, gg_summary)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
674
  elif theme == "EDUCATION_STATE":
675
  formatted_message = EDUCATION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
676
  elif theme == "FOLLUP_ACTION_STATE":
 
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
 
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"})
 
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":