rukeshpaudel commited on
Commit
bc4f6b8
·
1 Parent(s): 3f986e6

Delete utils

Browse files
utils/app_common.py DELETED
@@ -1,205 +0,0 @@
1
- import re
2
- from bson import ObjectId
3
- from dotenv import load_dotenv
4
- import os
5
- from interviewer_utils.interviewer import Interviewer
6
- from utils.study import Study
7
- from utils.study_repository import StudyRepository
8
- from typing import Union, List
9
- from utils.database_helper import DatabaseIO
10
- import gradio as gr
11
-
12
- load_dotenv()
13
- INTERVIEW_USERS_COLLECTION = os.environ['MONGO_COLLECTION_USERS']
14
- RESEARCHER_USERS_COLLECTION = os.environ['MONGO_COLLECTION_USERS']
15
- STUDIES_COLLECTION = os.environ['MONGO_COLLECTION_STUDIES']
16
-
17
-
18
- def is_valid_email(email):
19
- # Simple regex pattern for email validation
20
- pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
21
- return bool(re.match(pattern, email))
22
-
23
-
24
- def set_active_study(this_study_repository,
25
- study_id):
26
- """
27
- Using a repository object creates a study object for the given id
28
- returns the study object and a status message
29
- """
30
-
31
- study_id = study_id.split(':')[0]
32
- this_study = this_study_repository.get_studies(study_id=study_id)
33
-
34
- if not this_study:
35
- return {}, "study does not exist"
36
-
37
- if type(this_study) == list and len(this_study):
38
- this_study = this_study[0]
39
-
40
- # initialize a new study with this study in memory!
41
- try:
42
- # study validation is here! if db schema is older, will be notified during dev
43
- active_study_obj = Study(**this_study)
44
- except Exception as e:
45
- return None, f"Study not found due to {e}"
46
-
47
- return active_study_obj, ""
48
-
49
-
50
- def configure_interviewer(this_interviewer: Interviewer,
51
- this_study_obj: Study,
52
- history: List[List[str]]):
53
- """
54
- takes an existing interviewer object and initializes it with details from the provided study object
55
-
56
- :param history:
57
- :param this_interviewer:
58
- :param this_study_obj:
59
- :return: interviewer object with the right initializations
60
- """
61
- this_status = "Successfully configured interviewer object"
62
-
63
- if this_study_obj is None or type(this_study_obj) != Study:
64
- this_status = "The research study you are participating in does not" \
65
- " exist or is not ready, please contact the researcher"
66
-
67
- # if this study has
68
-
69
- return this_interviewer, this_status
70
-
71
-
72
- def lookup_userid_by_email(email):
73
- """
74
- Looks up email address and finds the right user and returns the user_id
75
- :param email:
76
- :return:
77
- """
78
- if email is None or not is_valid_email(email):
79
- return None, "Try with a valid email please!"
80
-
81
- with DatabaseIO(collection_name=INTERVIEW_USERS_COLLECTION) as db_io:
82
- user = db_io.collection.find_one({"email": email}, {"_id": 1})
83
- if user:
84
- return str(user["_id"]), "Found correct user"
85
- else:
86
- return None, "Error finding user"
87
-
88
-
89
- def process_transcript_to_chatbot_history(this_transcript):
90
- messages_in_chatbot_format = []
91
- for i in range(0, len(this_transcript), 2):
92
- human_turn = this_transcript[i]['text']
93
- ai_turn = this_transcript[i + 1]['text']
94
- messages_in_chatbot_format.append([human_turn, ai_turn])
95
- return messages_in_chatbot_format
96
-
97
-
98
- def set_user_and_study(
99
- this_study_repository,
100
- this_study_code,
101
- this_user_email,
102
-
103
- ):
104
- this_user_id, user_exists_message = lookup_userid_by_email(this_user_email)
105
-
106
- interaction_disabled = gr.update( interactive=False)
107
- interaction_enabled = gr.update( interactive=True)
108
-
109
- if not this_user_id or not len(this_user_id):
110
- this_return_message = user_exists_message
111
- return None, "", this_return_message, *([interaction_disabled] * 5)
112
-
113
- this_active_study, study_exists_message = set_active_study(this_study_repository, study_id=str(this_study_code))
114
- if not this_active_study:
115
- this_return_message = study_exists_message
116
- return {}, this_user_id, this_return_message, *([interaction_disabled] * 5)
117
- this_return_message = user_exists_message + " " + study_exists_message
118
-
119
- return this_active_study, this_user_id, this_return_message, *([interaction_enabled] * 5)
120
-
121
-
122
- def setup_conversation_context(this_active_study, this_study_repository, this_user_id):
123
- """
124
- Populates the interview object
125
- :param this_study_repository:
126
- :param this_active_study:
127
- :param this_user_id:
128
- :return:
129
- """
130
-
131
- if not this_active_study or not type(this_active_study) == Study:
132
- return [], None, "Study code or user id is invalid"
133
- this_transcript = load_previous_transcript(this_active_study,
134
- this_user_id,
135
- this_study_repository)
136
- this_user_id_str = str(this_user_id)
137
- this_interviewer = Interviewer(
138
- research_topic=this_active_study.study_topic,
139
- research_objectives=this_active_study.study_objective,
140
- research_questions=this_active_study.research_questions,
141
- interview_guidelines=this_active_study.interview_guidelines,
142
- existing_conversation_history=this_transcript
143
- )
144
-
145
- if not this_transcript or not len(this_transcript):
146
- # no previous conversation
147
- ai_response = this_interviewer.chat_handler.converse('Hello')
148
- this_constructed_transcript = [{'turn': 'Human', 'text': 'Hello'}, {'turn': 'AI', 'text': ai_response}]
149
- # transcript did not exist, so must create the user info in database
150
-
151
- this_view_chat_history = process_transcript_to_chatbot_history(this_constructed_transcript)
152
- #
153
- if 'human_interviews' not in this_active_study.interviews or not this_active_study.interviews['human_interviews']:
154
- this_active_study.interviews['human_interviews'] = {}
155
- this_previous_interview = this_active_study.interviews['human_interviews'].get(this_user_id)
156
- # If not found, initialize an empty one
157
- if not this_previous_interview:
158
- this_previous_interview = {
159
- "AI_interviewer": "Sophia (AI Researcher)",
160
- "Human_Respondent": {
161
- "participant_id": this_user_id_str
162
- },
163
- "transcript": []
164
- }
165
- this_active_study.interviews['human_interviews'][this_user_id_str] = this_previous_interview
166
- this_active_study.interviews['human_interviews'][str(this_user_id)]['transcript'] = this_constructed_transcript
167
- this_study_repository.update_study(study_id=this_active_study._id, updated_data=this_active_study.to_dict())
168
- this_message = "First time talking to you ... "
169
- else:
170
- this_view_chat_history = process_transcript_to_chatbot_history(this_transcript)
171
- this_message = "Welcome back, lets start where we left off!"
172
-
173
- return this_view_chat_history, this_interviewer, this_message
174
-
175
-
176
- def load_previous_transcript(this_study: Study,
177
- this_user_id: Union[ObjectId | str],
178
- this_study_repository: StudyRepository):
179
- """
180
- loads existing interview
181
- :param this_study:
182
- :param this_user_id:
183
- :param this_study_repository
184
- :return:
185
- """
186
- this_user_id_str = str(this_user_id)
187
-
188
- # Ensure 'human_interviews' exists and is initialized properly
189
-
190
- try:
191
- # Try to get the interview of the participant
192
- human_interviews = this_study.interviews['human_interviews']
193
- this_previous_interview = human_interviews.get(this_user_id_str, None)
194
- return this_previous_interview['transcript']
195
-
196
- except (AttributeError, KeyError, Exception) as ae:
197
- return []
198
-
199
-
200
- if __name__ == "__main__":
201
- # unit tests
202
- study_repository = StudyRepository(collection_name_studies=STUDIES_COLLECTION)
203
- sample_study_id = "64dc833a12e518bb5d4ea2b4"
204
- sample_email = "paudyalprajwal@qualz.net"
205
- interviewer = Interviewer(existing_conversation_history=['a', 'b'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/database_helper.py DELETED
@@ -1,92 +0,0 @@
1
- from datetime import datetime
2
-
3
- import pymongo
4
- from pymongo.errors import PyMongoError
5
- import os
6
- import dotenv
7
- from pymongo.server_api import ServerApi
8
-
9
- class DatabaseIO:
10
- def __init__(self, db_name=None, collection_name=None):
11
- dotenv.load_dotenv()
12
-
13
- mongo_username = os.environ['MONGO_USERNAME']
14
- mongo_password = os.environ['MONGO_PASSWORD']
15
- client_url = os.environ['MONGO_CLIENT_URL_DEV']
16
- uri = f"mongodb+srv://{mongo_username}:{mongo_password}@{client_url}/?retryWrites=true&w=majority"
17
-
18
- if not db_name:
19
- db_name = os.environ['MONGO_DATABASE_NAME']
20
- if not collection_name:
21
- collection_name = os.environ['MONGO_COLLECTION']
22
-
23
- self.client = pymongo.MongoClient(uri, server_api=ServerApi('1'))
24
- self.db = self.client[db_name]
25
- self.collection = self.db[collection_name]
26
-
27
- def insert_document(self, article, collection=None, unique_on='_id', upsert=False):
28
-
29
- if not collection:
30
- collection = self.collection
31
-
32
- article['date_modified'] = datetime.now().utcnow()
33
- existing_document = collection.find_one({unique_on: article[unique_on]})
34
- if existing_document:
35
- # there is something like with the reddit id already
36
-
37
- if upsert:
38
- collection.update_one({unique_on: article[unique_on]}, {"$set": article})
39
- else:
40
- article['date_created'] = datetime.now().utcnow()
41
- collection.insert_one(article)
42
-
43
- def __enter__(self):
44
- return self
45
-
46
- def __exit__(self, exc_type, exc_value, traceback):
47
- try:
48
- self.client.close()
49
- except PyMongoError as e:
50
- print(f"An error occurred while closing the database connection: {e}")
51
- raise
52
-
53
- def __del__(self):
54
- try:
55
- self.client.close()
56
- except Exception as e:
57
- print(e)
58
-
59
- def read_documents(self, query=None, sort_by=None, sort_order=None):
60
- if query is None:
61
- query = {}
62
- try:
63
- if sort_by:
64
- if not sort_order or sort_order not in [1, -1]:
65
- sort_order= 1
66
- for article in self.collection.find(query).sort(sort_by, sort_order):
67
- yield article
68
- else:
69
- for article in self.collection.find(query):
70
- yield article
71
- except Exception as e:
72
- print(e)
73
-
74
- def count_documents(self, query=None):
75
- if query is None:
76
- query = {}
77
- try:
78
- return self.collection.count_documents(query)
79
- except Exception as e:
80
- print(e)
81
-
82
- def update_documents(self, query, update, upsert=True):
83
- try:
84
- self.collection.update_one(query, update, upsert=upsert)
85
- except Exception as e:
86
- print(e)
87
-
88
- def delete_document(self, query):
89
- try:
90
- self.collection.delete_one(query)
91
- except Exception as e:
92
- print(e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/eleven_voice_cache.py DELETED
@@ -1,65 +0,0 @@
1
- # Save default voices to avoid querying the API for unathorized users
2
-
3
- import os
4
- import re
5
- from typing import Iterator, List, Optional, Union
6
-
7
- from elevenlabs.api import TTS, Model, Voice, VoiceClone, Voices, VoiceSettings
8
-
9
-
10
- VOICES_CACHE = [
11
- Voice(
12
- voice_id="21m00Tcm4TlvDq8ikWAM",
13
- name="Rachel",
14
- category="premade",
15
- settings=VoiceSettings(stability=0.75, similarity_boost=0.75),
16
- ),
17
- Voice(
18
- voice_id="AZnzlk1XvdvUeBnXmlld",
19
- name="Domi",
20
- category="premade",
21
- settings=VoiceSettings(stability=0.1, similarity_boost=0.75),
22
- ),
23
- Voice(
24
- voice_id="EXAVITQu4vr4xnSDxMaL",
25
- name="Bella",
26
- category="premade",
27
- settings=VoiceSettings(stability=0.245, similarity_boost=0.75),
28
- ),
29
- Voice(
30
- voice_id="ErXwobaYiN019PkySvjV",
31
- name="Antoni",
32
- category="premade",
33
- settings=VoiceSettings(stability=0.195, similarity_boost=0.75),
34
- ),
35
- Voice(
36
- voice_id="MF3mGyEYCl7XYWbV9V6O",
37
- name="Elli",
38
- category="premade",
39
- settings=VoiceSettings(stability=0.755, similarity_boost=0.75),
40
- ),
41
- Voice(
42
- voice_id="TxGEqnHWrfWFTfGW9XjX",
43
- name="Josh",
44
- category="premade",
45
- settings=VoiceSettings(stability=0.15, similarity_boost=0.51),
46
- ),
47
- Voice(
48
- voice_id="VR6AewLTigWG4xSOukaG",
49
- name="Arnold",
50
- category="premade",
51
- settings=VoiceSettings(stability=0.15, similarity_boost=0.75),
52
- ),
53
- Voice(
54
- voice_id="pNInz6obpgDQGcFmaJgB",
55
- name="Adam",
56
- category="premade",
57
- settings=VoiceSettings(stability=0.2, similarity_boost=0.75),
58
- ),
59
- Voice(
60
- voice_id="yoZ06aMxZJJ28mfd3POQ",
61
- name="Sam",
62
- category="premade",
63
- settings=VoiceSettings(stability=0.25, similarity_boost=0.75),
64
- ),
65
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/llm_helper.py DELETED
@@ -1,243 +0,0 @@
1
- from langchain.schema import SystemMessage, HumanMessage
2
- from langchain.chat_models import ChatOpenAI
3
- import ast
4
- import warnings
5
- from pathlib import Path
6
- import os, json
7
- from ra_utils.synthetic_panel_datamodel import parse_synthetic_panel_output
8
- from langchain.memory import ConversationBufferMemory
9
- from langchain import LLMChain
10
- import dotenv
11
-
12
-
13
- class LLMChatHandler:
14
- def __init__(self,
15
- llm_model_name: str = None,
16
- llm_temperature: float = None,
17
- expertise_message: str = ""):
18
-
19
- dotenv.load_dotenv()
20
- if not llm_model_name:
21
- llm_model_name = os.environ['OPENAI_MODEL']
22
- if not llm_temperature:
23
- llm_temperature=os.environ['OPENAI_MODEL_TEMPERATURE']
24
-
25
- self.llm = ChatOpenAI(temperature=llm_temperature, model_name=llm_model_name)
26
-
27
- project_dir = Path(os.environ['PROJECT_DIR'])
28
- assert project_dir.exists()
29
- with open(project_dir / 'study_prompts.json') as sp:
30
- self.definition_objective_maps = json.load(sp)
31
-
32
- if not len(expertise_message):
33
- expertise_message = f"Assistant is a world renowned expert in qualitative research." \
34
- f" Assistant is well known in your field worldwide and has published several books " \
35
- f"and white-papers. Assistant is also an expert qualitative research analyst " \
36
- f"who is thorough and methodical."
37
- self.expertise_message = expertise_message
38
-
39
- def predict(self, messages):
40
- return self.llm.predict_messages(messages)
41
-
42
- def guess_research_questions(self,
43
- research_name: str,
44
- research_topic: str,
45
- research_objective: str,
46
- research_framework: str,
47
- research_questions_draft: str):
48
- guess_research_questions_message = f"""
49
- Based on the study named {research_name} focusing on the topic {research_topic}, and objectives for the
50
- research as {research_objective} and a theoretical framework as {research_framework}, please help
51
- me craft some research questions. Research questions are overarching broad questions that help me understand
52
- about the research objective. You shouldn't suggest more than 3 questions.
53
- Also take note of the specific instructions or draft enclosed within
54
- triple backticks.
55
- ```INSTRUCTIONS: {research_questions_draft}```
56
- """ + """ Your output should be formatted as a dictionary with two keys such as:
57
- {
58
- "research_questions": A single string with all the research questions you recommend
59
- separated by a semi-colon.
60
- It is critical to include only questions here and not any explanation or discussion
61
- "reasoning": A brief explanation on the reasoning for choosing the questions.
62
- Here you can include any additional things you
63
- want to add including commentary of any kind
64
- }
65
-
66
- If there are any concerns or difficulties in crafting the questions,
67
- please mention them under "reasoning" """
68
- messages = [SystemMessage(content=self.expertise_message),
69
- HumanMessage(content=guess_research_questions_message)]
70
- research_questions_output = self.predict(messages)
71
-
72
- try:
73
-
74
- research_questions_dict = ast.literal_eval(research_questions_output.content)
75
- if not isinstance(research_questions_dict, dict):
76
- raise ValueError("Parsing output as dictionary did not work")
77
-
78
- return research_questions_dict.get("research_questions", ""), \
79
- research_questions_dict.get("reasoning", "Please try again. Are the study details descriptive enough?")
80
- except (SyntaxError, ValueError) as e:
81
- print(f"Error parsing output: {e}")
82
- return "", "Please try again. Are the study details descriptive enough?"
83
-
84
- def create_synthetic_panel(self,
85
- research_topic: str,
86
- research_objective: str,
87
- research_framework: str,
88
- instructions: str = "",
89
- num_panelists: int = 5):
90
-
91
- panel_creation_prompt_template = self.definition_objective_maps.get('sample_selection').get('prompt_template')
92
-
93
- if num_panelists > 15:
94
- warnings.warn("Only returning 15 panelists for now")
95
- num_panelists = 15
96
-
97
- m_custom_message = f"The profiles should be created for a qualitative research study with these details. " \
98
- f"Study Topic: {research_topic}" \
99
- f"Study Objective: {research_objective}" \
100
- f"Study theoretical framework: {research_framework}" \
101
- f"Please create {num_panelists} of profiles and consider these " \
102
- f"instructions while creating the included within triple backticks: " \
103
- f"```INSTRUCTIONS FOR PROFILE CREATION: {instructions}```."
104
-
105
- # existing_synthetic_panelist_message = ""
106
- # existing_panelists = self.synthetic_panelists
107
- #
108
- # if existing_panelists and len(existing_panelists) > 0:
109
- # existing_synthetic_panelist_message = " and Here is a list of existing panelists " \
110
- # "that should be modified according to the criteria I will state"
111
-
112
- # panel_creation_message = panel_creation_prompt_template + existing_synthetic_panelist_message + m_custom_message
113
-
114
- panel_creation_message = panel_creation_prompt_template + m_custom_message
115
-
116
- messages = [SystemMessage(content=self.expertise_message),
117
- HumanMessage(content=panel_creation_message)]
118
- synthetic_panel_output = self.predict(messages).content
119
-
120
- user_panel, rationale, discussion = parse_synthetic_panel_output(synthetic_panel_output)
121
-
122
- return user_panel, rationale, discussion
123
-
124
- def guess_interview_guidelines(self,
125
- research_topic: str,
126
- research_objective: str,
127
- research_framework: str,
128
- research_questions: str,
129
- interview_guidelines_draft_or_instructions: str, ):
130
- # Chat logic here
131
- guess_study_objective_message = f"""
132
- For a qualitative study focusing on the topic {research_topic}, please help
133
- come up with some interview guidelines and script.
134
- The objective of the research is to {research_objective}
135
- The theoretical framework for the study is {research_framework}
136
- The main research questions we have identified are: {research_questions}.
137
- I will also include some draft question or additional instructions below within
138
- triple backticks. Pay attention to these and use them accordingly.
139
- Your output should be a dictionary with two keys:
140
-
141
- 1. "interview_script": Provide a script to guide the interview process.
142
- The purpose is for for a researcher to use this to guide an interview session. The aim of the script is to
143
- ensure appropriate questions as asked to fulfill the study objective and research questions. The interview
144
- script should be a plain string and NOT a dictionary or any other data structure. If any sub-sections
145
- are needed, use call caps for the subsection name.
146
-
147
- 2. "reasoning": This should be an overview of the rationale behind the questions, and be no longer than two
148
- sentences.
149
-
150
- If there are any concerns or difficulties in crafting the interview script, please mention them under "reasoning"
151
-
152
- ```INSTRUCTIONS: {interview_guidelines_draft_or_instructions}```"""
153
-
154
- messages = [SystemMessage(content=self.expertise_message),
155
- HumanMessage(content=guess_study_objective_message)]
156
- study_objective_output = self.predict(messages)
157
- try:
158
-
159
- study_objective_dict = ast.literal_eval(study_objective_output.content)
160
- if not isinstance(study_objective_dict, dict):
161
- raise ValueError("Parsing output as dictionary did not work")
162
-
163
- return study_objective_dict.get("interview_script", ""), \
164
- study_objective_dict.get("reasoning", "Please try again. Are the study details descriptive enough?")
165
- except (SyntaxError, ValueError) as e:
166
- print(f"Error parsing output: {e}")
167
- return "", "Please try again. Are the study details descriptive enough?"
168
-
169
- def guess_study_objective(self, research_name: str,
170
- research_topic: str,
171
- study_objective_draft_or_instructions: str):
172
- # Chat logic here
173
- guess_study_objective_message = f"""
174
- Based on the study named {research_name} focusing on the topic {research_topic}, please help
175
- me improve my study objective. Take note of the specific instructions and draft enclosed within
176
- triple backticks. Your output should be a dictionary with two keys:
177
-
178
- 1. "study_objective": Provide a concise research objective statement that is limited to one sentence.
179
-
180
- 2. "reasoning": This should detail the rationale behind the chosen objective, and be no longer than two
181
- sentences.
182
-
183
- If there are any concerns or difficulties in crafting the objective, please mention them under "reasoning"
184
-
185
- ```INSTRUCTIONS: {study_objective_draft_or_instructions}```"""
186
-
187
- messages = [SystemMessage(content=self.expertise_message),
188
- HumanMessage(content=guess_study_objective_message)]
189
- study_objective_output = self.predict(messages)
190
- try:
191
-
192
- study_objective_dict = ast.literal_eval(study_objective_output.content)
193
- if not isinstance(study_objective_dict, dict):
194
- raise ValueError("Parsing output as dictionary did not work")
195
-
196
- return study_objective_dict.get("study_objective", ""), \
197
- study_objective_dict.get("reasoning", "Please try again. Are the study details descriptive enough?")
198
- except (SyntaxError, ValueError) as e:
199
- print(f"Error parsing output: {e}")
200
- return "", "Please try again. Are the study details descriptive enough?"
201
-
202
- def guess_theoretical_framework(self, research_name: str,
203
- research_topic: str,
204
- research_objective: str):
205
- # Chat logic here
206
- guess_theoretical_framework_message = f"""
207
- Based on the given research details:
208
- - Research Name: {research_name}
209
- - Research Topic: {research_topic}
210
- - Study Objective: {research_objective}
211
-
212
- Please recommend a suitable theoretical framework for the study. The framework should be
213
- one among the following: Ethnographic, Narrative,
214
- Grounded Theory, Case Study, Phenomenological, and Exploratory. """ + """You output should
215
- be a python dictionary with two keys and nothing else. Here is the format:
216
- {
217
- "theoretical_framework": Recommended framework from the list above.
218
- It is critical to include only the framework and not any explanation
219
- "reasoning": A brief explanation on why you think the chosen
220
- framework is the most suitable for the study. Here you can include any additional things you
221
- want to add including commentary of any kind
222
- }
223
-
224
- If there are any concerns or difficulties in crafting the objective,
225
- please mention them under "reasoning"
226
- """
227
-
228
- messages = [SystemMessage(content=self.expertise_message),
229
- HumanMessage(content=guess_theoretical_framework_message)]
230
- study_guess_theoretical_framework_output = self.predict(messages)
231
- try:
232
-
233
- study_guess_theoretical_framework_dict = ast.literal_eval(study_guess_theoretical_framework_output.content)
234
- if not isinstance(study_guess_theoretical_framework_dict, dict):
235
- raise ValueError("Parsing output as dictionary did not work")
236
-
237
- return study_guess_theoretical_framework_dict.get("theoretical_framework", ""), \
238
- study_guess_theoretical_framework_dict.get("reasoning",
239
- "Please try again. "
240
- "Are the study details descriptive enough?")
241
- except (SyntaxError, ValueError) as e:
242
- print(f"Error parsing output: {e}")
243
- return "", "Please try again. Are the study details descriptive enough?"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/study.py DELETED
@@ -1,148 +0,0 @@
1
- from typing import List, Dict, Optional, Union
2
- from pathlib import Path
3
- import os
4
- import json
5
- import warnings
6
- from langchain.chat_models import ChatOpenAI
7
- from bson import ObjectId
8
- from utils.llm_helper import LLMChatHandler
9
-
10
-
11
- class InterviewSyntheticPanel:
12
- def __init__(self, api_config: dict):
13
- # Initialize API
14
- pass
15
-
16
- def create_panel(self, study_details: dict):
17
- # Call the API with study details and create synthetic panel
18
- pass
19
-
20
-
21
- class Study:
22
- def __init__(self,
23
- _id: str,
24
- study_descriptive_name: str,
25
- study_objective: str,
26
- study_framework: str,
27
- study_topic: str,
28
- research_questions: str,
29
- interview_guidelines: Optional[str] = None,
30
- interviews: Optional[Dict['str', List[Dict]]] = None,
31
- overall_codes: Optional[Dict[str, List[str]]] = None,
32
- synthetic_panel_pending_interviews: Optional [List[Dict]] = None,
33
- **kwargs,
34
- ):
35
- self.study_descriptive_name = study_descriptive_name
36
- self.study_topic = study_topic
37
- self._id = _id
38
- self.study_objective = study_objective
39
- self.study_framework = study_framework
40
- self.interview_guidelines = interview_guidelines if interview_guidelines else ""
41
- self.research_questions = research_questions
42
- self.interviews = interviews if interviews else {}
43
- self.overall_codes = overall_codes if overall_codes else {}
44
-
45
- self.additional_fields = kwargs
46
- self.chat_handler = LLMChatHandler()
47
-
48
- self.synthetic_panel_pending_interviews = synthetic_panel_pending_interviews if\
49
- synthetic_panel_pending_interviews else []
50
- # Check if synthetic_interviews exists and is not empty
51
- # if 'synthetic_interviews' in self.interviews and len(self.interviews.get('synthetic_interviews', "")):
52
- # # Extract AI_Respondent from each item in synthetic_interviews and append to self.synthetic_panelists
53
- # self.synthetic_panelists = [item['AI_Respondent'] for item in interviews.get('synthetic_interviews') if
54
- # 'AI_Respondent' in item]
55
-
56
- project_dir = Path(os.environ['PROJECT_DIR'])
57
- assert project_dir.exists()
58
- with open(project_dir / 'study_prompts.json') as sp:
59
- self.definition_objective_maps = json.load(sp)
60
-
61
- def add_interview(self, interview: Dict):
62
- """
63
- Add an interview to the study.
64
-
65
- :param interview: The interview data to add.
66
- """
67
- self.interviews.append(interview)
68
-
69
-
70
- def save_synthetic_panelists(self):
71
- """
72
- attemp
73
- :return:
74
- """
75
- def remove_interview(self, interview_index: int):
76
- """
77
- Remove an interview from the study based on its index.
78
-
79
- :param interview_index: Index of the interview to remove.
80
- """
81
- if 0 <= interview_index < len(self.interviews):
82
- del self.interviews[interview_index]
83
-
84
- def update_overall_codes(self, code: str, quotes: List[str]):
85
- """
86
- Update or add a code and its associated quotes to the overall_codes.
87
-
88
- :param code: The code to update or add.
89
- :param quotes: The associated quotes.
90
- """
91
- if code in self.overall_codes:
92
- self.overall_codes[code].extend(quotes)
93
- else:
94
- self.overall_codes[code] = quotes
95
-
96
- def to_dict(self) -> dict:
97
- """
98
- Convert the object to a dictionary representation.
99
-
100
- Returns:
101
- dict: A dictionary representation of the object.
102
- """
103
- return {
104
- '_id': self._id,
105
- 'study_descriptive_name': self.study_descriptive_name,
106
- 'study_topic': self.study_topic,
107
- 'study_objective': self.study_objective,
108
- 'study_framework': self.study_framework,
109
- 'interview_guidelines': self.interview_guidelines,
110
- 'research_questions': self.research_questions,
111
- 'interviews': self.interviews,
112
- 'overall_codes': self.overall_codes,
113
- 'synthetic_panel_pending_interviews': self.synthetic_panel_pending_interviews,
114
- **self.additional_fields
115
- }
116
-
117
-
118
- @staticmethod
119
- def from_dict(data: Dict):
120
- """
121
- Create a Study object from a dictionary.
122
-
123
- :param data: The dictionary containing study data.
124
- :return: A Study object.
125
- """
126
- return Study(
127
- study_id=data["id"],
128
- objective=data["objective"],
129
- study_framework=data["study_framework"],
130
- study_descriptive_name=data["study_descriptive_name"],
131
- study_topic=data["study_topic"],
132
- interviews=data.get("interviews", []),
133
- overall_codes=data.get("overall_codes", {})
134
- )
135
-
136
-
137
- # Usage
138
- # study_data = {
139
- # "id": "12345",
140
- # "objective": "Test Objective",
141
- # "study_framework": "Framework1",
142
- # "study_descriptive_name": "Study1",
143
- # "study_topic": "Topic1",
144
- # "interviews": [],
145
- # "overall_codes": {}
146
- # }
147
- # study = Study.from_dict(study_data)
148
- # study_dict = study.to_dict()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/study_repository.py DELETED
@@ -1,200 +0,0 @@
1
- import dotenv
2
-
3
- import os
4
-
5
- from typing import Optional, List, Tuple
6
- from typing import Dict, Any
7
- from utils.study import Study
8
- from utils.database_helper import DatabaseIO
9
- from bson import ObjectId
10
- from typing import Union
11
-
12
- from utils.llm_helper import LLMChatHandler
13
-
14
-
15
- class StudyRepository:
16
- def __init__(self,
17
- db_name=None,
18
- collection_name_studies=None,
19
- collection_name_users=None
20
- ):
21
- """
22
- Initialize the StudyRepository with a database connection or client.
23
-
24
- :param db: A mock database connection or client.
25
- """
26
-
27
- dotenv.load_dotenv()
28
-
29
- if not db_name:
30
- db_name = os.environ['MONGO_DATABASE_NAME']
31
- if not collection_name_studies:
32
- collection_name_studies = os.environ['MONGO_COLLECTION_STUDIES']
33
- if not collection_name_users:
34
- collection_name_users = os.environ['MONGO_COLLECTION_USERS']
35
-
36
-
37
-
38
- self.db_name = db_name
39
- self.collection_name_studies = collection_name_studies
40
- self.collection_name_users = collection_name_users
41
- self.chat_handler = LLMChatHandler()
42
- # self.db = self.client[db_name]
43
- # self.collection = self.db.get_collection(self.collection_name) # Assuming 'study_v03' is the collection name
44
-
45
- def create_study(self, study_data: Dict[str, Any]) -> Dict[str, Any]:
46
- # Validate the study data using the Study's constructor
47
- study_data_with_default_id = study_data.copy()
48
- study_data_with_default_id["_id"] = None
49
-
50
- try:
51
- study_instance = Study(**study_data_with_default_id)
52
- except TypeError as e:
53
- # Here, we're assuming the TypeError arises from a mismatch in the fields.
54
- # This might not cover all validation cases, but it's a start.
55
- raise ValueError(f"Validation error: {str(e)}")
56
- except Exception as e:
57
- raise Exception(f"Other exception: {str(e)}")
58
-
59
- # If validation passes, insert the study into the database
60
-
61
- with DatabaseIO(self.db_name, self.collection_name_studies) as db_io:
62
- study_id = db_io.collection.insert_one(study_data).inserted_id
63
- return study_id
64
-
65
- def append_to_transcript(self, study_id, user_id, human_text, ai_response):
66
- """
67
- Append the recent conversation to the transcript in the database.
68
-
69
- :param db_url: MongoDB database URL.
70
- :param db_name: Name of the MongoDB database.
71
- :param collection_name: Name of the collection storing the transcripts.
72
- :param participant_id: ID of the participant for whom the transcript is being updated.
73
- :param human_text: The text/message from the human.
74
- :param ai_response: The response from the AI.
75
- """
76
-
77
- # Initialize the MongoDB client and select the database and collection
78
- user_id = str(user_id)
79
- # Define the conversation entries to be appended
80
- new_entries = [
81
- {
82
- "turn": "Human",
83
- "text": human_text,
84
- },
85
- {
86
- "turn": "AI",
87
- "text": ai_response,
88
- }
89
- ]
90
-
91
- # Append new entries to the transcript
92
- with DatabaseIO(collection_name=self.collection_name_studies) as db_io:
93
- query = {"_id": ObjectId(study_id)}
94
-
95
- # Append new entries to the transcript using a dynamic field update
96
- update_field = f"interviews.human_interviews.{user_id}.transcript"
97
- update_action = {"$push": {update_field: {"$each": new_entries}}}
98
-
99
- # Execute the update
100
- db_io.collection.update_one(query, update_action)
101
-
102
-
103
- def get_studies(self,
104
- study_id: Optional[str] = None,
105
- user_id: Optional[str] = None,
106
- to_filter_by_user_id: bool = True) -> Union[
107
- List[Dict], Dict, Tuple[List[Dict], str], Tuple[int, str]]:
108
- """Retrieve studies from the database.
109
-
110
- :param study_id: (Optional) If provided, retrieve only the study with this ID.
111
- :param user_id: (Optional) If provided, filter results to studies the user is authorized to view.
112
- :param to_filter_by_user_id: (Optional) If False, ignores the user_id and
113
- returns all studies (todo remove in prod)
114
- :return: The studies or a specific study data, or a tuple with a message if an error occurs.
115
- """
116
-
117
- if study_id:
118
- study_id = ObjectId(study_id)
119
- with DatabaseIO(db_name=self.db_name, collection_name=self.collection_name_studies) as db_io:
120
- study = db_io.collection.find_one({"_id": study_id})
121
- if not study:
122
- return {}
123
- return study
124
-
125
- # If user_id is provided and filter is desired, return studies the user is authorized to view.
126
- if user_id and to_filter_by_user_id:
127
- user_id = ObjectId(user_id)
128
- with DatabaseIO(db_name=self.db_name, collection_name=self.collection_name_users) as db_io:
129
- this_user = db_io.collection.find_one({'_id': user_id})
130
- if not this_user:
131
- return [], "User not found, please try authentication again."
132
-
133
- user_study_ids = this_user.get('study_id', [])
134
- user_study_ids = [ObjectId(uid) for uid in user_study_ids]
135
- with DatabaseIO(db_name=self.db_name, collection_name=self.collection_name_studies) as db_io:
136
- matching_studies = list(db_io.collection.find({'_id': {'$in': user_study_ids}}))
137
- return matching_studies
138
-
139
- # If neither is provided, return all studies.
140
- with DatabaseIO(db_name=self.db_name, collection_name=self.collection_name_studies) as db_io:
141
- return list(db_io.collection.find({}))
142
-
143
- def update_study(self, study_id: str, updated_data: Dict):
144
- """
145
- Update an existing study by its ID.
146
-
147
- :param study_id: The ID of the study to update.
148
- :param updated_data: The new data for the study.
149
- """
150
-
151
- study_id = ObjectId(study_id)
152
-
153
- # validate all data is of the right type
154
- updated_data['_id'] = study_id
155
- try:
156
- study_instance = Study(**updated_data)
157
- except TypeError as e:
158
- # Here, we're assuming the TypeError arises from a mismatch in the fields.
159
- # This might not cover all validation cases, but it's a start.
160
- raise ValueError(f"Validation error: {str(e)}")
161
- except Exception as e:
162
- raise Exception(f"Other exception: {str(e)}")
163
-
164
- # Fetch the current study data
165
- with DatabaseIO(self.db_name, self.collection_name_studies) as db_io:
166
- current_data = db_io.collection.find_one({"_id": study_id})
167
-
168
- if not current_data:
169
- raise ValueError(f"No study found with id: {study_id}")
170
-
171
- # Construct a dictionary with only the changed values
172
- changes_to_update = {}
173
- for key, value in updated_data.items():
174
- if current_data.get(key, "") != value:
175
- changes_to_update[key] = value
176
-
177
- # If there are changes, update the study in the database
178
- if changes_to_update:
179
- with DatabaseIO(self.db_name, self.collection_name_studies) as db_io:
180
- result = db_io.collection.update_one({"_id": study_id}, {"$set": changes_to_update})
181
-
182
- if result.acknowledged and result.matched_count == 1 and result.modified_count == 1:
183
- return_msg = f"Succesfully updated study with id {study_id}"
184
- else:
185
- return_msg = f"Couldn't confirm updates, please try again or refresh page to see latest"
186
- else:
187
- return_msg = f"No changes found, study info is up to date"
188
-
189
- return return_msg
190
-
191
-
192
- def delete_study(self, study_id: str):
193
- """
194
- Delete a study by its ID.
195
-
196
- :param study_id: The ID of the study to delete.
197
- """
198
- study_id = ObjectId(study_id)
199
- with DatabaseIO(self.db_name, self.collection_name_studies) as db_io:
200
- db_io.collection.delete_one({"_id": study_id})