import os
import json
from datetime import datetime, timedelta
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_community.utilities import SQLDatabase
from langchain_core.tools import Tool
from langchain.agents import create_openai_tools_agent, AgentExecutor
from langchain.prompts import (
PromptTemplate,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate,
MessagesPlaceholder,
ChatPromptTemplate,
)
import pytz
import uvicorn
# Load environment variables
load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
if not OPENAI_API_KEY:
raise ValueError("OPENAI_API_KEY is not set.")
app = FastAPI()
# Database connection
db_instance = SQLDatabase.from_uri(
"mssql+pyodbc://PMOlogbook_Usr:aPMO86#iaxh@103.239.89.100:21433/PMO360_DB?driver=ODBC Driver 17 for SQL Server",
include_tables=["Logbook", "LogbookHistory", "User", "tbl_Sprint", "tblMs_Projects"]
)
# Helper: get current week range
def get_week_range():
today = datetime.now(pytz.timezone("Asia/Kolkata"))
start = today - timedelta(days=today.weekday())
end = start + timedelta(days=6)
return start.strftime("%Y-%m-%d"), end.strftime("%Y-%m-%d")
# Helper: SQL Query Builder
def build_sql(first_name: str, last_name: str = None, start_date: str = None, end_date: str = None) -> str:
if not start_date or not end_date:
start_date, end_date = get_week_range()
name_filter = f"u.FirstName = '{first_name}'"
if last_name:
name_filter += f" AND u.LastName = '{last_name}'"
return f"""
SELECT
COALESCE(u.FirstName + ' ' + u.LastName, 'Unknown') AS Name,
lh.LogbookId,
lh.CreatedDate,
p.ProjectName,
s.Track,
s.SubTrack,
lh.Description,
lh.Comment
FROM LogbookHistory lh
LEFT JOIN [User] u ON lh.AssisgnTo = u.UserId
LEFT JOIN [Logbook] l ON lh.LogbookId = l.LogbookId
LEFT JOIN [tblMs_Projects] p ON l.ProjectName = p.ProjectID
LEFT JOIN [tbl_Sprint] s ON lh.LogbookId = s.LogbookId
WHERE {name_filter}
AND lh.CreatedDate BETWEEN '{start_date}' AND '{end_date}'
ORDER BY lh.CreatedDate ASC
"""
# SQL tool for agent
query_weekly_progress = Tool(
name="query_weekly_progress",
func=lambda query: db_instance.run(query) if query.lower().strip().startswith("select") else "Only SELECT queries are allowed.",
description="Runs a SQL SELECT query to retrieve user progress report."
)
# System prompt for clean HTML output
system_prompt = SystemMessagePromptTemplate(
prompt=PromptTemplate(
input_variables=[],
template="""
You are a weekly reporting agent for PMO360.
Return a clean HTML
using SQL output:
- Fields: Name, LogbookId, Date, Project Name, Track, Sub Track, Description, Comment
- Include headers
- Align text to the left
- Add table borders using a constant CSS class
- No extra explanation, only output the HTML table
"""
)
)
# Human prompt
human_prompt = HumanMessagePromptTemplate(
prompt=PromptTemplate(input_variables=["input"], template="{input}")
)
# Chat prompt
chat_prompt = ChatPromptTemplate.from_messages([
system_prompt,
MessagesPlaceholder(variable_name="chat_history", optional=True),
human_prompt,
MessagesPlaceholder(variable_name="agent_scratchpad")
])
# Setup LLM + agent executor
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0, openai_api_key=OPENAI_API_KEY)
agent = create_openai_tools_agent(llm=llm, tools=[query_weekly_progress], prompt=chat_prompt)
agent_executor = AgentExecutor(agent=agent, tools=[query_weekly_progress], verbose=True)
# Name extractor LLM
name_extractor = ChatOpenAI(model="gpt-3.5-turbo", temperature=0, openai_api_key=OPENAI_API_KEY)
name_prompt = PromptTemplate(
input_variables=["text"],
template="""
Extract the first name, last name, and optional date range from the following text. Respond ONLY with a dictionary (no strings or quotes):
Examples:
"Show Rahul Kumar's report from April 10 to April 17" → {{"first_name": "Rahul", "last_name": "Kumar", "start_date": "2025-04-10", "end_date": "2025-04-17"}}
"DPR for Anirban this week" → {{"first_name": "Anirban"}}
"DPR is daily progress report"
Prompt: {text}
"""
)
# FastAPI model for prompt input
class PromptRequest(BaseModel):
prompt: str
# API endpoint
@app.post("/weekly-dpr")
async def get_prompt_based_dpr(request: PromptRequest):
try:
prompt_text = request.prompt.strip()
name_response = name_extractor.invoke(name_prompt.format_prompt(text=prompt_text).to_string())
print("LLM raw output:", name_response.content)
# Parse extracted names
parsed = name_response.content.strip()
if parsed.startswith("\"") or parsed.startswith("'"):
parsed = parsed[1:-1]
name_json = json.loads(parsed.replace("'", '"'))
first = name_json.get("first_name")
last = name_json.get("last_name")
start_date = name_json.get("start_date")
end_date = name_json.get("end_date")
if not first:
raise ValueError("No valid first name extracted.")
sql = build_sql(first, last, start_date, end_date)
context = f"Generate HTML weekly report for: {first} {last or ''} using this query:\n{sql}. Table must include Name, LogbookId, CreatedDate, ProjectName, Track, SubTrack, Description, Comment, and use a CSS class for bordered tables."
response = agent_executor.invoke({"input": context})
return response.get("output", "No output generated.")
except Exception as e:
raise HTTPException(status_code=500, detail=f"DPR Error: {e}")
# Run the app
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=True)