Vivek0912 commited on
Commit
d779a9b
·
1 Parent(s): a09d4b8

api code changes for schema reader

Browse files
backend/app/api/endpoints.py CHANGED
@@ -23,7 +23,7 @@ class QueryRequest(BaseModel):
23
  from fastapi import HTTPException
24
 
25
  @router.post("/process-text")
26
- def ask_query(req: QueryRequest):
27
  try:
28
  sql = generate_sql_query(req.question)
29
 
@@ -34,6 +34,7 @@ def ask_query(req: QueryRequest):
34
 
35
  # Use utility method to check if the string is a valid SQL query
36
  formattedSqlQuery = UtilityClass.is_valid_sql_query(sql)
 
37
  if not formattedSqlQuery:
38
  return {"message": str(sql) if sql else "No SQL query could be generated for your question."}
39
 
@@ -50,13 +51,73 @@ def ask_query(req: QueryRequest):
50
 
51
  # Prepare heading and records JSON using LLM
52
  result_json = UtilityClass.prepare_llm_heading_and_records(req.question, df)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
  return {
55
  "sql": sql,
56
  "rows": result_json["records"],
57
- "heading": result_json["heading"],
 
58
  "chart": chart
59
  }
60
  except Exception as e:
61
  raise HTTPException(status_code=400, detail=f"An error occurred: {str(e)}")
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  from fastapi import HTTPException
24
 
25
  @router.post("/process-text")
26
+ def process_text(req: QueryRequest):
27
  try:
28
  sql = generate_sql_query(req.question)
29
 
 
34
 
35
  # Use utility method to check if the string is a valid SQL query
36
  formattedSqlQuery = UtilityClass.is_valid_sql_query(sql)
37
+ print(f"Formatted SQL Query: {formattedSqlQuery}")
38
  if not formattedSqlQuery:
39
  return {"message": str(sql) if sql else "No SQL query could be generated for your question."}
40
 
 
51
 
52
  # Prepare heading and records JSON using LLM
53
  result_json = UtilityClass.prepare_llm_heading_and_records(req.question, df)
54
+
55
+ # Parse the heading if it's a JSON string containing heading and summary
56
+ heading_text = result_json["heading"]
57
+ summary_text = ""
58
+
59
+ if isinstance(heading_text, str):
60
+ try:
61
+ import json
62
+ # Check if it's wrapped in markdown code block
63
+ if heading_text.strip().startswith('```json') and heading_text.strip().endswith('```'):
64
+ # Extract JSON from markdown code block
65
+ json_content = heading_text.strip()
66
+ # Remove ```json from start and ``` from end
67
+ json_content = json_content[7:-3].strip() # Remove ```json and ```
68
+ heading_data = json.loads(json_content)
69
+ else:
70
+ # Try to parse directly
71
+ heading_data = json.loads(heading_text)
72
+
73
+ if isinstance(heading_data, dict):
74
+ heading_text = heading_data.get("heading", heading_text)
75
+ summary_text = heading_data.get("summary", summary_text)
76
+ except (json.JSONDecodeError, ValueError):
77
+ # If not JSON, use as-is
78
+ pass
79
 
80
  return {
81
  "sql": sql,
82
  "rows": result_json["records"],
83
+ "heading": heading_text, # Send just the heading text
84
+ "summary": summary_text, # Send just the summary text
85
  "chart": chart
86
  }
87
  except Exception as e:
88
  raise HTTPException(status_code=400, detail=f"An error occurred: {str(e)}")
89
 
90
+
91
+ @router.get("/health")
92
+ def health_check():
93
+ """
94
+ Health check endpoint to verify API and system status.
95
+
96
+ Returns:
97
+ dict: Health status information including system checks and timestamp
98
+ """
99
+ try:
100
+ health_status = UtilityClass.get_health_status()
101
+
102
+ # Return appropriate HTTP status based on health
103
+ if health_status["status"] == "healthy":
104
+ return health_status
105
+ else:
106
+ # Return 503 Service Unavailable if any critical component is unhealthy
107
+ from fastapi import Response
108
+ import json
109
+ return Response(
110
+ content=json.dumps(health_status),
111
+ media_type="application/json",
112
+ status_code=503
113
+ )
114
+
115
+ except Exception as e:
116
+ # Return 503 if health check itself fails
117
+ from datetime import datetime
118
+ error_response = {
119
+ "status": "unhealthy",
120
+ "message": f"Health check failed: {str(e)}",
121
+ "timestamp": datetime.utcnow().isoformat()
122
+ }
123
+ raise HTTPException(status_code=503, detail=error_response)
backend/app/db/db_connector.py CHANGED
@@ -24,16 +24,23 @@ def get_connection():
24
  if not os.path.isfile(db_path):
25
  raise FileNotFoundError(f"Database file not found at {db_path}. Set DB_PATH env variable or .env to the correct location.")
26
  try:
27
- conn = sqlite3.connect(db_path, timeout=30, check_same_thread=False)
 
 
28
  conn.row_factory = sqlite3.Row # access columns by name
29
  return conn
30
  except sqlite3.OperationalError as e:
31
  raise Exception(f"Error connecting to DB at {db_path}: {e}")
32
 
33
- # Create engine
34
  engine = create_engine(
35
  DATABASE_URL,
36
- connect_args={"check_same_thread": False} # Needed for SQLite threading
 
 
 
 
 
37
  )
38
 
39
  # Session factory
 
24
  if not os.path.isfile(db_path):
25
  raise FileNotFoundError(f"Database file not found at {db_path}. Set DB_PATH env variable or .env to the correct location.")
26
  try:
27
+ # Remove timeout restriction to allow long-running queries
28
+ # Complex AI-generated queries may take time and should not be interrupted
29
+ conn = sqlite3.connect(db_path, check_same_thread=False)
30
  conn.row_factory = sqlite3.Row # access columns by name
31
  return conn
32
  except sqlite3.OperationalError as e:
33
  raise Exception(f"Error connecting to DB at {db_path}: {e}")
34
 
35
+ # Create engine with no timeout restrictions for complex queries
36
  engine = create_engine(
37
  DATABASE_URL,
38
+ connect_args={
39
+ "check_same_thread": False, # Needed for SQLite threading
40
+ "timeout": 0 # No timeout - wait indefinitely for query completion
41
+ },
42
+ pool_timeout=None, # No pool timeout
43
+ pool_recycle=-1 # No connection recycling timeout
44
  )
45
 
46
  # Session factory
backend/app/db/schema_reader.py CHANGED
@@ -24,27 +24,3 @@ def get_schema():
24
  cursor.close()
25
  conn.close()
26
 
27
- def get_schema2():
28
- """Fetch database schema information (tables & columns)."""
29
- engine = get_db_engine()
30
- inspector = inspect(engine)
31
-
32
- schema = {}
33
- for table_name in inspector.get_table_names():
34
- columns = [
35
- {"name": col["name"], "type": str(col["type"])}
36
- for col in inspector.get_columns(table_name)
37
- ]
38
- schema[table_name] = columns
39
-
40
- return schema
41
-
42
- # def get_schema():
43
- # engine = get_db_engine()
44
- # inspector = inspect(engine)
45
-
46
- # schema_info = {}
47
- # for table in inspector.get_table_names():
48
- # columns = [col["name"] for col in inspector.get_columns(table)]
49
- # schema_info[table] = columns
50
- # return schema_info
 
24
  cursor.close()
25
  conn.close()
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/app/services/query_executor.py CHANGED
@@ -23,7 +23,12 @@ def run_and_handle_sql_query(sql_query: str, user_question: str):
23
  try:
24
  rows = execute_sql_query(sql_query)
25
  if not rows:
26
- llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=settings.OPENAI_API_KEY)
 
 
 
 
 
27
  no_result_prompt = (
28
  f"The following SQL query was generated for the user's question, but it returned no results. "
29
  f"User question: {user_question}\nSQL query: {sql_query}\n"
 
23
  try:
24
  rows = execute_sql_query(sql_query)
25
  if not rows:
26
+ llm = ChatOpenAI(
27
+ model="gpt-4o-mini",
28
+ temperature=0,
29
+ api_key=settings.OPENAI_API_KEY,
30
+ request_timeout=None # No timeout for API requests
31
+ )
32
  no_result_prompt = (
33
  f"The following SQL query was generated for the user's question, but it returned no results. "
34
  f"User question: {user_question}\nSQL query: {sql_query}\n"
backend/app/services/sql_generator.py CHANGED
@@ -6,55 +6,116 @@ from langchain.prompts import PromptTemplate, ChatPromptTemplate
6
  from app.db.schema_reader import get_schema
7
  from app.db.db_connector import get_db_engine
8
  from app.db.db_connector import get_connection
 
9
 
10
- # llm = ChatOpenAI(model="gpt-4", temperature=0, api_key=settings.OPENAI_API_KEY)
11
  llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=settings.OPENAI_API_KEY)
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  def generate_sql_query(user_question: str):
15
  schema = get_schema()
16
- schema_str = "\n".join(
17
- [f"{table}: {', '.join(cols)}" for table, cols in schema.items()]
18
- )
19
 
20
  prompt = ChatPromptTemplate.from_template("""
21
- You are an expert SQLite query generator.
22
- Use ONLY the following tables and columns:
23
-
24
- {schema}
25
-
26
- Generate a correct SQLite query (no explanations, only SQL).
27
- Do not add single quotes or any quotes around table names or column names.
28
- If the user asks for data, generate a SELECT query and that outcome should have only
29
- sqlite compatible, don't include any other text.
30
- If you cannot answer, say: "I cannot generate this query."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  User question: {question}
33
  """)
34
 
35
- messages = prompt.format_messages(schema=schema_str, question=user_question)
36
  sql_query = llm.invoke(messages).content.strip()
37
 
38
  # If the LLM says it cannot generate a query, treat as normal chat
39
  if sql_query.lower().startswith("i cannot generate this query"):
40
  # Use the LLM as a chatbot for normal conversation
41
- chat_prompt = f"You are a helpful assistant. Respond conversationally to: {user_question}"
 
 
 
 
42
  chat_response = llm.invoke([{"role": "user", "content": chat_prompt}]).content.strip()
43
  return {"chat_message": chat_response}
44
 
45
  return sql_query
46
 
47
- def generate_sql_query2(natural_language_query: str) -> str:
48
- """Generate SQL query from natural language using LangChain."""
49
- engine = get_db_engine()
50
- db = SQLDatabase(engine)
51
 
52
- # You can swap ChatOpenAI with LLaMA wrapper
53
- llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=settings.OPENAI_API_KEY)
54
-
55
- sql_chain = create_sql_query_chain(llm, db)
56
-
57
- sql_query = sql_chain.invoke({"question": natural_language_query})
58
- return sql_query
59
 
60
 
 
6
  from app.db.schema_reader import get_schema
7
  from app.db.db_connector import get_db_engine
8
  from app.db.db_connector import get_connection
9
+ import re
10
 
11
+ # llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=settings.OPENAI_API_KEY)
12
  llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=settings.OPENAI_API_KEY)
13
 
14
+ def detect_foreign_key_relationships_only(schema):
15
+ """Only detect foreign key relationships without any assumptions about data types"""
16
+ relationships = []
17
+
18
+ for table_name, columns in schema.items():
19
+ for column in columns:
20
+ # Look for foreign key patterns: *_id columns
21
+ if column.endswith('_id') and column != 'id':
22
+ # Extract potential referenced table name
23
+ ref_name = column[:-3] # Remove '_id'
24
+
25
+ # Check for exact match or plural/singular variations
26
+ possible_refs = [ref_name, ref_name + 's', ref_name[:-1] if ref_name.endswith('s') else ref_name + 's']
27
+
28
+ for possible_ref in possible_refs:
29
+ if possible_ref in schema:
30
+ relationships.append({
31
+ 'from_table': table_name,
32
+ 'from_column': column,
33
+ 'to_table': possible_ref,
34
+ 'to_column': 'id'
35
+ })
36
+ break
37
+
38
+ return relationships
39
+
40
+ def generate_pure_schema_context(schema):
41
+ """Generate schema context without any assumptions - let AI figure out everything"""
42
+
43
+ # Just provide the raw schema structure
44
+ context = "DATABASE SCHEMA:\n\n"
45
+
46
+ # List all tables and their columns
47
+ context += "TABLES AND COLUMNS:\n"
48
+ for table_name, columns in schema.items():
49
+ context += f"- {table_name}: {', '.join(columns)}\n"
50
+
51
+ # Detect only foreign key relationships (no assumptions about data types)
52
+ relationships = detect_foreign_key_relationships_only(schema)
53
+
54
+ # Show detected relationships
55
+ if relationships:
56
+ context += "\nDETECTED FOREIGN KEY RELATIONSHIPS:\n"
57
+ for rel in relationships:
58
+ context += f"- {rel['from_table']}.{rel['from_column']} → {rel['to_table']}.{rel['to_column']}\n"
59
+
60
+ return context
61
+
62
 
63
  def generate_sql_query(user_question: str):
64
  schema = get_schema()
65
+
66
+ # Generate pure schema context - no assumptions, let AI figure it out
67
+ schema_context = generate_pure_schema_context(schema)
68
 
69
  prompt = ChatPromptTemplate.from_template("""
70
+ You are an expert SQLite query generator with advanced analytical capabilities.
71
+
72
+ {schema_context}
73
+
74
+ INTELLIGENT ANALYSIS INSTRUCTIONS:
75
+ 1. EXAMINE the schema above - look at table names and column names to understand what data each table contains
76
+ 2. ANALYZE the column names to infer what type of information is stored (names, addresses, amounts, dates, etc.)
77
+ 3. USE the detected foreign key relationships to understand how tables connect
78
+ 4. For queries needing data from multiple tables, follow the relationship paths and use JOINs
79
+ 5. INFER from context - if user asks about location and you see city/address columns, use those tables
80
+ 6. THINK logically about which tables would contain the requested information
81
+
82
+ QUERY GENERATION STRATEGY:
83
+ - Read the user's question carefully
84
+ - Identify what data they want (customers, transactions, locations, etc.)
85
+ - Look at the schema to find which tables likely contain that data
86
+ - Check if multiple tables are needed and use the foreign key relationships
87
+ - Generate appropriate SQL with JOINs if data spans multiple tables
88
+
89
+ RESPONSE FORMAT:
90
+ - Return ONLY valid SQLite syntax
91
+ - No explanations, comments, or extra text
92
+ - Use proper JOIN syntax when needed
93
+ - If you cannot determine how to query the available schema, respond: "I cannot generate this query."
94
+
95
+ Example thinking process:
96
+ - User asks "customers in New York" → Look for tables with customer info AND location info → Use relationships to JOIN them
97
+ - User asks "transactions over $1000" → Look for tables with transaction/amount data
98
+ - User asks "branch managers" → Look for tables with employee/staff info and management roles
99
 
100
  User question: {question}
101
  """)
102
 
103
+ messages = prompt.format_messages(schema_context=schema_context, question=user_question)
104
  sql_query = llm.invoke(messages).content.strip()
105
 
106
  # If the LLM says it cannot generate a query, treat as normal chat
107
  if sql_query.lower().startswith("i cannot generate this query"):
108
  # Use the LLM as a chatbot for normal conversation
109
+ chat_prompt = (
110
+ "You are an AI assistant whose primary role is to generate SQL queries from natural language questions. "
111
+ "If the user's question is not related to SQL or databases, respond conversationally as a helpful assistant."
112
+ f"\n\nUser question: {user_question}"
113
+ )
114
  chat_response = llm.invoke([{"role": "user", "content": chat_prompt}]).content.strip()
115
  return {"chat_message": chat_response}
116
 
117
  return sql_query
118
 
 
 
 
 
119
 
 
 
 
 
 
 
 
120
 
121
 
backend/app/services/utility.py CHANGED
@@ -1,11 +1,10 @@
1
- import dropbox
2
  import openai
3
  from config.settings import settings
4
  from langchain_openai import ChatOpenAI
5
  import sqlparse
6
  import re
7
  import os
8
- import tempfile
9
  class UtilityClass:
10
  """
11
  Classifies user input as 'sql' (SQL/data context) or 'chat' (normal conversation) using an LLM.
@@ -52,35 +51,12 @@ class UtilityClass:
52
  heading = llm.invoke([{"role": "user", "content": heading_prompt}]).content.strip()
53
  return {"heading": heading, "records": rows}
54
 
55
- @staticmethod
56
- def is_valid_sql_query33(sql: str) -> bool:
57
- """
58
- Returns True if the string is a valid SQL statement (not just a keyword in text).
59
- Uses sqlparse to check for a valid statement structure.
60
- Accepts queries starting with 'sql ' followed by a valid SQL statement.
61
- """
62
- if not sql or not isinstance(sql, str):
63
- return False
64
- sql_strip = sql.strip()
65
- # Remove leading 'sql' if present
66
- if sql_strip.lower().startswith('sql'):
67
- sql_strip = sql_strip[4:].lstrip()
68
- parsed = sqlparse.parse(sql_strip)
69
- if not parsed or not parsed[0].tokens:
70
- return False
71
- stmt = parsed[0]
72
- first_token = stmt.token_first(skip_cm=True, skip_ws=True)
73
- if first_token is None:
74
- return False
75
- # Accept only if the first token is a SQL keyword and there is more than one token
76
- return first_token.ttype in sqlparse.tokens.Keyword.DML and len(stmt.tokens) > 1
77
-
78
  @staticmethod
79
  def is_valid_sql_query(text: str):
80
  if not text or not text.strip():
81
  return False
82
 
83
- # Remove markdown formatting like ```sql ... ```
84
  text = re.sub(r"```sql|```", "", text, flags=re.IGNORECASE).strip()
85
 
86
  # Collapse multiple spaces & line breaks into a single space
@@ -91,10 +67,7 @@ class UtilityClass:
91
  r"^SELECT\s+.+\s+FROM\s+.+", # SELECT ... FROM ...
92
  r"^INSERT\s+INTO\s+.+\s+VALUES\s*\(", # INSERT INTO ... VALUES (...)
93
  r"^UPDATE\s+.+\s+SET\s+.+", # UPDATE ... SET ...
94
- r"^DELETE\s+FROM\s+.+", # DELETE FROM ...
95
  r"^CREATE\s+(TABLE|DATABASE)\s+.+", # CREATE TABLE/DATABASE ...
96
- r"^DROP\s+(TABLE|DATABASE)\s+.+", # DROP TABLE/DATABASE ...
97
- r"^ALTER\s+TABLE\s+.+", # ALTER TABLE ...
98
  r"^WITH\s+.+\s+AS\s*\(.+\)" # WITH ... AS (...)
99
  ]
100
 
@@ -106,23 +79,81 @@ class UtilityClass:
106
  return False
107
 
108
  @staticmethod
109
- def download_sqlite_db_from_dropbox(dropbox_token: str, dropbox_db_path: str, local_db_path: str = None) -> str:
110
  """
111
- Downloads a SQLite DB file from Dropbox and returns the local file path.
112
- :param dropbox_token: Dropbox API access token.
113
- :param dropbox_db_path: Path to the DB file in Dropbox (e.g., '/folder/mydb.db').
114
- :param local_db_path: Local path to save the file. If None, saves to './downloaded_db.db'.
115
- :return: Local file path of the downloaded DB.
116
  """
117
- if local_db_path is None:
118
- print("Is /tmp writable?", os.access('/tmp', os.W_OK))
119
- print("Is /mnt writable?", os.access('/mnt', os.W_OK))
120
- print("Is /space writable?", os.access('/space', os.W_OK))
121
- temp_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "space")
122
- os.makedirs(temp_dir, exist_ok=True)
123
- local_db_path = os.path.join(temp_dir, "downloaded_db.db")
124
- dbx = dropbox.Dropbox(dropbox_token)
125
- with open(local_db_path, 'wb') as f:
126
- metadata, res = dbx.files_download(path=dropbox_db_path)
127
- f.write(res.content)
128
- return local_db_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # import dropbox
2
  import openai
3
  from config.settings import settings
4
  from langchain_openai import ChatOpenAI
5
  import sqlparse
6
  import re
7
  import os
 
8
  class UtilityClass:
9
  """
10
  Classifies user input as 'sql' (SQL/data context) or 'chat' (normal conversation) using an LLM.
 
51
  heading = llm.invoke([{"role": "user", "content": heading_prompt}]).content.strip()
52
  return {"heading": heading, "records": rows}
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  @staticmethod
55
  def is_valid_sql_query(text: str):
56
  if not text or not text.strip():
57
  return False
58
 
59
+ # Remove markdown formatting like ```sql ... ```
60
  text = re.sub(r"```sql|```", "", text, flags=re.IGNORECASE).strip()
61
 
62
  # Collapse multiple spaces & line breaks into a single space
 
67
  r"^SELECT\s+.+\s+FROM\s+.+", # SELECT ... FROM ...
68
  r"^INSERT\s+INTO\s+.+\s+VALUES\s*\(", # INSERT INTO ... VALUES (...)
69
  r"^UPDATE\s+.+\s+SET\s+.+", # UPDATE ... SET ...
 
70
  r"^CREATE\s+(TABLE|DATABASE)\s+.+", # CREATE TABLE/DATABASE ...
 
 
71
  r"^WITH\s+.+\s+AS\s*\(.+\)" # WITH ... AS (...)
72
  ]
73
 
 
79
  return False
80
 
81
  @staticmethod
82
+ def get_health_status() -> dict:
83
  """
84
+ Get the health status of the application and its dependencies.
85
+
86
+ Returns:
87
+ dict: Health status information including API status, database connectivity, and timestamp
 
88
  """
89
+ import time
90
+ from datetime import datetime
91
+ from app.db.db_connector import get_connection
92
+
93
+ health_data = {
94
+ "status": "healthy",
95
+ "timestamp": datetime.utcnow().isoformat(),
96
+ "version": "1.0.0",
97
+ "checks": {}
98
+ }
99
+
100
+ # Check database connectivity
101
+ try:
102
+ with get_connection() as conn:
103
+ cursor = conn.cursor()
104
+ cursor.execute("SELECT 1")
105
+ result = cursor.fetchone()
106
+ if result:
107
+ health_data["checks"]["database"] = {
108
+ "status": "healthy",
109
+ "message": "Database connection successful"
110
+ }
111
+ else:
112
+ health_data["checks"]["database"] = {
113
+ "status": "unhealthy",
114
+ "message": "Database query failed"
115
+ }
116
+ health_data["status"] = "unhealthy"
117
+ except Exception as e:
118
+ health_data["checks"]["database"] = {
119
+ "status": "unhealthy",
120
+ "message": f"Database connection failed: {str(e)}"
121
+ }
122
+ health_data["status"] = "unhealthy"
123
+
124
+ # Check OpenAI API (if configured)
125
+ try:
126
+ if hasattr(settings, 'OPENAI_API_KEY') and settings.OPENAI_API_KEY:
127
+ health_data["checks"]["openai_api"] = {
128
+ "status": "configured",
129
+ "message": "OpenAI API key is configured"
130
+ }
131
+ else:
132
+ health_data["checks"]["openai_api"] = {
133
+ "status": "warning",
134
+ "message": "OpenAI API key not configured"
135
+ }
136
+ except Exception as e:
137
+ health_data["checks"]["openai_api"] = {
138
+ "status": "error",
139
+ "message": f"OpenAI API check failed: {str(e)}"
140
+ }
141
+
142
+ return health_data
143
+
144
+ # @staticmethod
145
+ # def download_sqlite_db_from_dropbox(dropbox_token: str, dropbox_db_path: str, local_db_path: str = None) -> str:
146
+ # """
147
+ # Downloads a SQLite DB file from Dropbox and returns the local file path.
148
+ # :param dropbox_token: Dropbox API access token.
149
+ # :param dropbox_db_path: Path to the DB file in Dropbox (e.g., '/folder/mydb.db').
150
+ # :param local_db_path: Local path to save the file. If None, saves to './downloaded_db.db'.
151
+ # :return: Local file path of the downloaded DB.
152
+ # """
153
+ # if local_db_path is None:
154
+ # local_db_path = os.path.join(os.getcwd(), "downloaded_db.db")
155
+ # dbx = dropbox.Dropbox(dropbox_token)
156
+ # with open(local_db_path, 'wb') as f:
157
+ # metadata, res = dbx.files_download(path=dropbox_db_path)
158
+ # f.write(res.content)
159
+ # return local_db_path
backend/requirements.txt CHANGED
@@ -12,4 +12,8 @@ faiss-cpu # optional for embeddings
12
  pandas
13
  matplotlib
14
  python-dotenv
15
- dropbox
 
 
 
 
 
12
  pandas
13
  matplotlib
14
  python-dotenv
15
+ pytest==8.4.1
16
+ pytest-cov==6.2.1
17
+ pytest-mock==3.14.1
18
+ coverage==7.10.6
19
+ # dropbox