[FEATURE] Add Logic to Calculate and Get Life Score

#1
by LittleKnife - opened
Files changed (7) hide show
  1. .gitignore +2 -1
  2. app/assistants.py +14 -1
  3. app/flows.py +3 -1
  4. app/main.py +38 -2
  5. app/user.py +121 -14
  6. app/utils.py +24 -0
  7. test.txt +0 -2
.gitignore CHANGED
@@ -2,4 +2,5 @@ logs/
2
  mementos/
3
  users/
4
  templates/
5
- .env
 
 
2
  mementos/
3
  users/
4
  templates/
5
+ .env
6
+ __pycache__
app/assistants.py CHANGED
@@ -496,8 +496,10 @@ class Assistant:
496
  elif tool.function.name == "end_conversation":
497
  day_n = json.loads(tool.function.arguments)['day_n']
498
  completed_micro_action = json.loads(tool.function.arguments)['completed_micro_action']
 
499
 
500
  self.cm.user.update_micro_action_status(completed_micro_action)
 
501
 
502
  # NOTE: we will record whether the user has completed the theme for the day
503
  # NOTE: if no, we will include a short followup message to encourage the user the next day
@@ -511,7 +513,10 @@ class Assistant:
511
  logger.warning(f"Creating a SMART goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
512
 
513
  user_goal = json.loads(tool.function.arguments)['goal']
514
- self.cm.user.set_goal(user_goal)
 
 
 
515
 
516
  print_log("INFO", f"SMART goal approved: {user_goal}", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
517
  logger.info(f"SMART goal approved: {user_goal}", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
@@ -545,6 +550,14 @@ class Assistant:
545
 
546
  elif tool.function.name == "change_goal":
547
  pass
 
 
 
 
 
 
 
 
548
 
549
  # Submit all tool outputs at once after collecting them in a list
550
  if tool_outputs:
 
496
  elif tool.function.name == "end_conversation":
497
  day_n = json.loads(tool.function.arguments)['day_n']
498
  completed_micro_action = json.loads(tool.function.arguments)['completed_micro_action']
499
+ area_of_deep_reflection = json.loads(tool.function.arguments)['area_of_deep_reflection']
500
 
501
  self.cm.user.update_micro_action_status(completed_micro_action)
502
+ self.cm.user.trigger_deep_reflection_point(area_of_deep_reflection)
503
 
504
  # NOTE: we will record whether the user has completed the theme for the day
505
  # NOTE: if no, we will include a short followup message to encourage the user the next day
 
513
  logger.warning(f"Creating a SMART goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
514
 
515
  user_goal = json.loads(tool.function.arguments)['goal']
516
+ user_goal_area = json.loads(tool.function.arguments)['area']
517
+ user_goal_status = 'PENDING'
518
+
519
+ self.cm.user.set_goal(user_goal, user_goal_area, user_goal_status)
520
 
521
  print_log("INFO", f"SMART goal approved: {user_goal}", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
522
  logger.info(f"SMART goal approved: {user_goal}", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
 
550
 
551
  elif tool.function.name == "change_goal":
552
  pass
553
+
554
+ elif tool.function.name == "complete_goal":
555
+ self.cm.user.update_goal_status()
556
+ logger.info(f"Updated recent goal status as COMPLETED.", extra={"user_id": self.cm.user.user_id, "endpoint": "complete_goal"})
557
+ tool_outputs.append({
558
+ "tool_call_id": tool.id,
559
+ "output": "true"
560
+ })
561
 
562
  # Submit all tool outputs at once after collecting them in a list
563
  if tool_outputs:
app/flows.py CHANGED
@@ -382,7 +382,9 @@ The user is currently on day {{}}/{{}} of their journey.
382
 
383
  - Highlight the user's progress and achievements in bullet points
384
 
385
- - Ask the user whether they want to continue the plan for another 2 weeks, or create a new goal
 
 
386
 
387
  - Finally, suggest to the user to speak to a Growth Guide for deeper support by booking a Growth Guide schedule through ourcoach web app
388
 
 
382
 
383
  - Highlight the user's progress and achievements in bullet points
384
 
385
+ - Ask the user whether they have **accomplished** they goal. If they have accomplished their goal, call the complete_goal() function!
386
+
387
+ - Ask the user whether they want to continue the plan for another 2 weeks, or create a new goal. If they want to create a new goal, call the change_goal() function!
388
 
389
  - Finally, suggest to the user to speak to a Growth Guide for deeper support by booking a Growth Guide schedule through ourcoach web app
390
 
app/main.py CHANGED
@@ -15,7 +15,7 @@ from openai import OpenAI
15
  import psycopg2
16
  from psycopg2 import sql
17
  import os
18
- from app.utils import get_api_key, get_user_info, get_growth_guide_session, pop_cache, print_log, update_user, upload_file_to_s3, get_user, upload_mementos_to_db, get_user_summary, get_user_life_status
19
  from dotenv import load_dotenv
20
  import logging.config
21
  import time
@@ -336,7 +336,31 @@ def get_user_by_id(user_id: str, api_key: str = Security(get_api_key)):
336
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
337
  detail=str(e)
338
  )
 
 
 
 
 
 
 
 
 
339
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
 
341
  @app.post("/add_ai_message")
342
  def add_ai_message(request: ChatItem, api_key: str = Security(get_api_key)):
@@ -751,4 +775,16 @@ def get_life_status_by_id(user_id: str, api_key: str = Security(get_api_key)):
751
  raise HTTPException(
752
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
753
  detail=str(e)
754
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  import psycopg2
16
  from psycopg2 import sql
17
  import os
18
+ from app.utils import get_api_key, get_user_info, get_growth_guide_session, pop_cache, print_log, update_user, upload_file_to_s3, get_user, upload_mementos_to_db, get_user_summary, get_user_life_status, get_life_score
19
  from dotenv import load_dotenv
20
  import logging.config
21
  import time
 
336
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
337
  detail=str(e)
338
  )
339
+
340
+ @app.get("/get_user_life_score")
341
+ def life_score_by_id(user_id: str, api_key: str = Security(get_api_key)):
342
+ print_log("INFO", "Getting user life score", extra={"user_id": user_id, "endpoint": "/get_user_life_score"})
343
+ logger.info("Getting user life score", extra={"user_id": user_id, "endpoint": "/get_user_life_score"})
344
+ try:
345
+ life_score = get_life_score(user_id)
346
+ print_log("INFO", "Successfully retrieved user life score", extra={"user_id": user_id, "endpoint": "/get_user_life_score"})
347
+ logger.info("Successfully retrieved user life score", extra={"user_id": user_id, "endpoint": "/get_user_life_score"})
348
 
349
+ return life_score
350
+ except LookupError:
351
+ print_log("ERROR", "User not found", extra={"user_id": user_id, "endpoint": "/get_user_life_score"})
352
+ logger.error("User not found", extra={"user_id": user_id, "endpoint": "/get_user_life_score"})
353
+ raise HTTPException(
354
+ status_code=status.HTTP_404_NOT_FOUND,
355
+ detail=f"User with ID {user_id} not found"
356
+ )
357
+ except Exception as e:
358
+ print_log("ERROR",f"Error getting user: {str(e)}", extra={"user_id": user_id, "endpoint": "/get_user_life_score"}, exc_info=True)
359
+ logger.error(f"Error getting user: {str(e)}", extra={"user_id": user_id, "endpoint": "/get_user_life_score"}, exc_info=True)
360
+ raise HTTPException(
361
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
362
+ detail=str(e)
363
+ )
364
 
365
  @app.post("/add_ai_message")
366
  def add_ai_message(request: ChatItem, api_key: str = Security(get_api_key)):
 
775
  raise HTTPException(
776
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
777
  detail=str(e)
778
+ )
779
+
780
+ @app.post("/add_booking_point")
781
+ def add_booking_point_by_user(user_id: str, api_key: str = Security(get_api_key)):
782
+ user = get_user(user_id)
783
+ user.add_point_for_booking()
784
+ return {"response": "ok"}
785
+
786
+ @app.post("/add_session_completion_point")
787
+ def add_session_completion_point_by_user(user_id: str, api_key: str = Security(get_api_key)):
788
+ user = get_user(user_id)
789
+ user.add_point_for_completing_session()
790
+ return {"response": "ok"}
app/user.py CHANGED
@@ -273,6 +273,11 @@ class User:
273
  self.micro_actions = []
274
  self.challenges = []
275
  self.other_focusses = []
 
 
 
 
 
276
 
277
  # Read growth_plan.json and store it
278
  growth_plan = {"growthPlan": [
@@ -344,17 +349,56 @@ class User:
344
  self.user_interaction_guidelines = self.generate_user_interaction_guidelines(user_info, client)
345
  self.conversations = ConversationManager(client, self, asst_id)
346
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  def get_current_goal(self):
348
  return self.goal[-1] if self.goal else None
349
 
350
- def set_goal(self, goal):
351
- self.goal.append({"goal": goal, "created_at": pd.Timestamp.now().strftime("%d-%m-%Y %a %H:%M:%S")})
352
-
 
353
  def update_micro_action_status(self, completed_micro_action):
354
  if completed_micro_action:
355
  self.micro_actions[-1]["status"] = "COMPLETE"
356
  self.micro_actions[-1]["updated_at"] = pd.Timestamp.now().strftime("%d-%m-%Y %a %H:%M:%S")
357
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  def add_ai_message(self, text):
359
  self.conversations._add_ai_message(text)
360
  return text
@@ -505,6 +549,13 @@ class User:
505
  self.conversations.state['date'] = date
506
 
507
  action = self.growth_plan.current()
 
 
 
 
 
 
 
508
  logger.info(f"Today's action is {action}", extra={"user_id": self.user_id, "endpoint": "user_change_date"})
509
 
510
  if action['day'] == 1:
@@ -639,22 +690,78 @@ class User:
639
  self._update_goal(gg_report[4]['answer'])
640
 
641
  def _update_goal(self, goal_text):
642
- prompt = (
643
- f"The user has a current goal: {self.get_current_goal()}.\n"
644
- f"The user provided a new goal: {goal_text}.\n"
645
- "Determine if the new goal is the same as the current goal or if it's a new one.\n"
646
- "If it's the same, respond with the current goal.\n"
647
- "If it's a new goal, respond with the new goal.\n"
648
- "Only output the final goal."
649
- )
 
 
 
 
 
 
 
 
 
 
 
 
650
  response = self.client.chat.completions.create(
651
  model="gpt-4o",
652
  messages=[{"role": "user", "content": prompt}],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
  temperature=0.2
654
  )
655
- final_goal = response.choices[0].message.content.strip()
656
- if final_goal != self.get_current_goal():
657
- self.set_goal(final_goal)
 
 
 
 
 
 
 
658
  logger.info(f"User goal updated to: {final_goal}", extra={"user_id": self.user_id, "endpoint": "_update_goal"})
659
  else:
660
  logger.info(f"User goal remains unchanged.", extra={"user_id": self.user_id, "endpoint": "_update_goal"})
 
273
  self.micro_actions = []
274
  self.challenges = []
275
  self.other_focusses = []
276
+ self.personal_growth_score = 0
277
+ self.career_growth_score = 0
278
+ self.relationship_score = 0
279
+ self.mental_well_being_score = 0
280
+ self.health_and_wellness_score = 0
281
 
282
  # Read growth_plan.json and store it
283
  growth_plan = {"growthPlan": [
 
349
  self.user_interaction_guidelines = self.generate_user_interaction_guidelines(user_info, client)
350
  self.conversations = ConversationManager(client, self, asst_id)
351
 
352
+ def add_life_score_point(self, variable, points_added, notes):
353
+ if variable == 'Personal Growth':
354
+ self.personal_growth_score += points_added
355
+ logger.info(f"Added {points_added} points to Personal Growth for {notes}", extra={"user_id": self.user_id, "endpoint": "add_life_score_point"})
356
+ elif variable == 'Career Growth':
357
+ self.career_growth_score += points_added
358
+ logger.info(f"Added {points_added} points to Career Growth for {notes}", extra={"user_id": self.user_id, "endpoint": "add_life_score_point"})
359
+ elif variable == 'Health and Wellness':
360
+ self.health_and_wellness_score += points_added
361
+ logger.info(f"Added {points_added} points to Health and Wellness for {notes}", extra={"user_id": self.user_id, "endpoint": "add_life_score_point"})
362
+ elif variable == 'Mental Well-Being':
363
+ self.mental_well_being_score += points_added
364
+ logger.info(f"Added {points_added} points to Mental Well Being for {notes}", extra={"user_id": self.user_id, "endpoint": "add_life_score_point"})
365
+ elif variable == 'Relationship':
366
+ self.relationship_score += points_added
367
+ logger.info(f"Added {points_added} points to Relationship for {notes}", extra={"user_id": self.user_id, "endpoint": "add_life_score_point"})
368
+
369
  def get_current_goal(self):
370
  return self.goal[-1] if self.goal else None
371
 
372
+ def set_goal(self, goal, area, status):
373
+ self.goal.append({"goal": goal, "area": area, "status": status, "created_at": pd.Timestamp.now().strftime("%d-%m-%Y %a %H:%M:%S"), "updated_at": pd.Timestamp.now().strftime("%d-%m-%Y %a %H:%M:%S")})
374
+ self.add_life_score_point(variable = area, points_added = 10, notes = "Setting a Goal")
375
+
376
  def update_micro_action_status(self, completed_micro_action):
377
  if completed_micro_action:
378
  self.micro_actions[-1]["status"] = "COMPLETE"
379
  self.micro_actions[-1]["updated_at"] = pd.Timestamp.now().strftime("%d-%m-%Y %a %H:%M:%S")
380
 
381
+ num_of_micro_actions_completed = sum(1 for item in self.micro_actions if item['status'] == 'COMPLETE')
382
+
383
+ if (num_of_micro_actions_completed in (1,3,5)) or (num_of_micro_actions_completed % 10 == 0 and num_of_micro_actions_completed != 0):
384
+ self.add_life_score_point(variable = self.get_current_goal()['area'], points_added = 10, notes = f"Completing the {num_of_micro_actions_completed}-th micro-action")
385
+
386
+ def trigger_deep_reflection_point(self, area_of_deep_reflection):
387
+ if len(area_of_deep_reflection)>0:
388
+ for area in area_of_deep_reflection:
389
+ self.add_life_score_point(variable = area, points_added = 5, notes = f"Doing a deep reflection about {area}")
390
+
391
+ def update_goal_status(self):
392
+ self.goal[-1]["status"] = "COMPLETE"
393
+ self.goal[-1]["updated_at"] = pd.Timestamp.now().strftime("%d-%m-%Y %a %H:%M:%S")
394
+ self.add_life_score_point(variable = self.get_current_goal()['area'], points_added = 30, notes = "Completing a Goal")
395
+
396
+ def add_point_for_booking(self):
397
+ self.add_life_score_point(variable = self.get_current_goal()['area'], points_added = 5, notes = "Booking a GG session")
398
+
399
+ def add_point_for_completing_session(self):
400
+ self.add_life_score_point(variable = self.get_current_goal()['area'], points_added = 20, notes = "Completing a GG session")
401
+
402
  def add_ai_message(self, text):
403
  self.conversations._add_ai_message(text)
404
  return text
 
549
  self.conversations.state['date'] = date
550
 
551
  action = self.growth_plan.current()
552
+
553
+ ## ADD POINT FOR CHANGE DATE
554
+ if self.growth_plan.current()['day'] == 7:
555
+ self.add_life_score_point(variable = self.get_current_goal()['area'], points_added = 5, notes = "Reaching Day 7")
556
+ elif self.growth_plan.current()['day'] == 14:
557
+ self.add_life_score_point(variable = self.get_current_goal()['area'], points_added = 10, notes = "Reaching Day 14")
558
+
559
  logger.info(f"Today's action is {action}", extra={"user_id": self.user_id, "endpoint": "user_change_date"})
560
 
561
  if action['day'] == 1:
 
690
  self._update_goal(gg_report[4]['answer'])
691
 
692
  def _update_goal(self, goal_text):
693
+ prompt = f"""
694
+ The user has a current goal: {self.get_current_goal()}
695
+ The user provided a new goal: {goal_text}
696
+
697
+ Determine if the new goal is the same as the current goal or if it's a new one.
698
+ If it's the same, respond with the current goal, and set same_or_not == True
699
+ If it's a new goal, respond with the new goal, and set same_or_not == False
700
+
701
+ Your response will be the final goal. You will also need to determine the area of this
702
+ final goal by choosing one of these areas that suits the final goal:
703
+ "Personal Growth", "Career Growth", "Relationship", "Mental Well-Being", "Health and Wellness"
704
+
705
+ Only output a JSON with this schema below:
706
+ {{
707
+ same_or_not: bool (whether the new goal is the same or not with the current goal),
708
+ goal: str (the final goal),
709
+ area: str (the area of the goal)
710
+ }}
711
+ """
712
+
713
  response = self.client.chat.completions.create(
714
  model="gpt-4o",
715
  messages=[{"role": "user", "content": prompt}],
716
+ response_format = {
717
+ "type": "json_schema",
718
+ "json_schema": {
719
+ "name": "goal_determination",
720
+ "strict": True,
721
+ "schema": {
722
+ "type": "object",
723
+ "properties": {
724
+ "same_or_not": {
725
+ "type": "boolean",
726
+ "description": "Indicates whether the new goal is the same as the current goal."
727
+ },
728
+ "goal": {
729
+ "type": "string",
730
+ "description": "The final goal determined based on input."
731
+ },
732
+ "area": {
733
+ "type": "string",
734
+ "description": "The area of the goal.",
735
+ "enum": [
736
+ "Personal Growth",
737
+ "Career Growth",
738
+ "Relationship",
739
+ "Mental Well-Being",
740
+ "Health and Wellness"
741
+ ]
742
+ }
743
+ },
744
+ "required": [
745
+ "same_or_not",
746
+ "goal",
747
+ "area"
748
+ ],
749
+ "additionalProperties": False
750
+ }
751
+ }
752
+ },
753
  temperature=0.2
754
  )
755
+
756
+ final_goal = json.loads(response.choices[0].message.content)['goal']
757
+ final_goal_area = json.loads(response.choices[0].message.content)['area']
758
+ if json.loads(response.choices[0].message.content)['same_or_not']:
759
+ final_goal_status = self.get_current_goal()['status']
760
+ else:
761
+ final_goal_status = 'PENDING'
762
+
763
+ if json.loads(response.choices[0].message.content)['same_or_not'] == False:
764
+ self.set_goal(final_goal, final_goal_area, final_goal_status)
765
  logger.info(f"User goal updated to: {final_goal}", extra={"user_id": self.user_id, "endpoint": "_update_goal"})
766
  else:
767
  logger.info(f"User goal remains unchanged.", extra={"user_id": self.user_id, "endpoint": "_update_goal"})
app/utils.py CHANGED
@@ -118,6 +118,30 @@ def get_user(user_id):
118
  raise ReferenceError(f"User {user_id} pickle still being created")
119
  raise LookupError(f"User [{user_id}] has not onboarded yet")
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  def get_user_summary(user_id):
122
  function_name = get_user_summary.__name__
123
  logger.info(f"Generating user summary for user {user_id}", extra={'user_id': user_id, 'endpoint': function_name})
 
118
  raise ReferenceError(f"User {user_id} pickle still being created")
119
  raise LookupError(f"User [{user_id}] has not onboarded yet")
120
 
121
+ def get_life_score(user_id):
122
+ function_name = get_life_score.__name__
123
+ logger.info(f"Generating user life score for user {user_id}", extra={'user_id': user_id, 'endpoint': function_name})
124
+
125
+ # Step 1: Call get_user to get user's info
126
+ try:
127
+ user = get_user(user_id)
128
+ # user_info = user.user_info
129
+ # user_messages = user.get_messages()
130
+ ## all the life scores here
131
+
132
+ life_score = {
133
+ "personal_growth_score": user.personal_growth_score,
134
+ "career_growth_score": user.career_growth_score,
135
+ "relationship_score": user.relationship_score,
136
+ "mental_well_being_score": user.mental_well_being_score,
137
+ "health_and_wellness_score": user.health_and_wellness_score
138
+ }
139
+ except LookupError as e:
140
+ logger.error(f"Error fetching user data: {e}", extra={'user_id': user_id, 'endpoint': function_name})
141
+ raise e
142
+
143
+ return life_score
144
+
145
  def get_user_summary(user_id):
146
  function_name = get_user_summary.__name__
147
  logger.info(f"Generating user summary for user {user_id}", extra={'user_id': user_id, 'endpoint': function_name})
test.txt CHANGED
@@ -1,3 +1 @@
1
  just a testing file
2
-
3
- testing
 
1
  just a testing file