Soumyajit94298 commited on
Commit
8419546
·
1 Parent(s): 59d415d
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ venv
2
+ .env
3
+ __pycache__
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+ ENV PATH="/home/user/.local/bin:$PATH"
6
+
7
+ WORKDIR /app
8
+
9
+ COPY --chown=user ./requirements.txt requirements.txt
10
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
+
12
+ COPY --chown=user . /app
13
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uvicorn
2
+
3
+ from server.utils import config
4
+
5
+ if __name__ == "__main__":
6
+
7
+ uvicorn.run(
8
+ "main:app",
9
+ host="0.0.0.0",
10
+ port=7860,
11
+ lifespan="on",
12
+ workers=3,
13
+ reload=bool(config.debug),
14
+ )
config/config.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic_settings import BaseSettings
2
+ import os
3
+
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ class Settings(BaseSettings):
9
+
10
+ ACCESS_TOKEN_EXPIRES_IN: str = os.getenv("ACCESS_TOKEN_EXPIRES_IN")
11
+ REFRESH_TOKEN_EXPIRES_IN: str = os.getenv("REFRESH_TOKEN_EXPIRES_IN")
12
+ JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM")
13
+ FASTAPI_CONFIG: str = os.getenv("FASTAPI_CONFIG")
14
+ JWT_PRIVATE_KEY: str = os.getenv("JWT_PRIVATE_KEY")
15
+ JWT_PUBLIC_KEY: str = os.getenv("JWT_PUBLIC_KEY")
16
+ TBB_DB_URL: str = os.getenv("TBB_DB_URL")
17
+ UTILS_API_KEY: str = os.getenv("UTILS_API_KEY")
18
+ DEBUG: str = os.getenv("DEBUG")
19
+ ACCESS_KEY: str = os.getenv("ACCESS_KEY")
20
+ SECRET_KEY: str = os.getenv("SECRET_KEY")
21
+ AWS_REGION: str = os.getenv("AWS_REGION")
22
+ EMAIL_FROM: str = os.getenv("EMAIL_FROM")
23
+ EMAIL_HOST: str = os.getenv("EMAIL_HOST")
24
+ EMAIL_PASSWORD: str = os.getenv("EMAIL_PASSWORD")
25
+ EMAIL_PORT: str = os.getenv("EMAIL_PORT")
26
+ CLIENT_HOST: str = os.getenv("CLIENT_HOST")
27
+ MAIL_CHECK_KEY: str = os.getenv("MAIL_CHECK_KEY")
28
+ OPENAI_API_KEY: str=os.getenv("OPENAI_API_KEY")
29
+ HIAGO_DB: str=os.getenv("HIAGO_DB")
30
+
31
+ settings = Settings()
main.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, Security, status
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.middleware.trustedhost import TrustedHostMiddleware
4
+ from fastapi.security import OAuth2PasswordBearer
5
+ from fastapi.security.api_key import APIKeyHeader
6
+ from contextlib import asynccontextmanager
7
+
8
+ from server.routes.mail import router as mailRouter
9
+ from server.utils.database import Base, SessionLocal, engine
10
+
11
+ @asynccontextmanager
12
+ async def lifespan(app: FastAPI):
13
+ # Startup logic
14
+ Base.metadata.create_all(engine)
15
+ yield
16
+ # Shutdown logic
17
+ db = SessionLocal()
18
+ await db.close()
19
+
20
+ app = FastAPI(
21
+ title="Hiago Docs",
22
+ description="This is the documentation for Hiago's API.",
23
+ version="1.0",
24
+ basePath="/v1",
25
+ lifespan=lifespan,
26
+ )
27
+
28
+
29
+
30
+ app.add_middleware(
31
+ TrustedHostMiddleware,
32
+ allowed_hosts=["*"],
33
+ )
34
+ origins = ["*"]
35
+
36
+ app.add_middleware(
37
+ CORSMiddleware,
38
+ allow_origins=origins,
39
+ allow_credentials=True,
40
+ allow_methods=["GET", "POST", "PUT", "DELETE"],
41
+ allow_headers=["*"],
42
+ )
43
+
44
+ @app.get("/")
45
+ def home():
46
+ return {"Hiago server is running"}
47
+
48
+
49
+ app.include_router(
50
+ mailRouter,
51
+ tags=["mail"],
52
+ prefix="/v1",
53
+ )
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ SQLAlchemy
3
+ uvicorn
4
+ setuptools
5
+ openai
6
+ tqdm
7
+ requests
8
+ certifi
9
+ anyio
10
+ pydantic
11
+ pydantic[email]
12
+ pydantic-settings
13
+ pydantic_core
14
+ dotenv
15
+ psycopg2
16
+ openai
server/controllers/__init__.py ADDED
File without changes
server/controllers/mail.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, EmailStr
2
+ from typing import Optional
3
+ from datetime import datetime
4
+
5
+
6
+ class MailContactFilter(BaseModel):
7
+ contact_id: Optional[int] = None
8
+ user_email: Optional[EmailStr] = None
9
+
10
+ class Config:
11
+ orm_mode = True
12
+
13
+ class MailSummary(BaseModel):
14
+ full_mail: Optional[str]=None
15
+ result: Optional[dict]=None
16
+
17
+ class MailDate(BaseModel):
18
+ start_date: Optional[datetime]=None
19
+ end_date: Optional[datetime]=None
server/crud/__init__.py ADDED
File without changes
server/crud/contacts.py ADDED
File without changes
server/crud/mail.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy.orm import Session
2
+ from server.models.mail import mail
3
+ from server.models.contact import Contact
4
+ from datetime import datetime
5
+
6
+ def get_all_mails(db: Session):
7
+ return db.query(mail).all()
8
+
9
+ def get_top_n_mails(db: Session, topk: int):
10
+ return db.query(mail).limit(topk).all()
11
+
12
+ def get_mails_by_contact_id(db: Session, contact_id: int):
13
+ return db.query(mail).filter(mail.contactId == contact_id).all()
14
+
15
+ def get_contact_id_by_email(db: Session, email: str):
16
+ contact = db.query(Contact).filter(Contact.email == email).first()
17
+ return contact.id if contact else None
18
+
19
+ def get_mails_by_date_range(db: Session, start_date: datetime, end_date: datetime): ### format (2022, 4, 25)
20
+ return db.query(mail).filter(mail.date.between(start_date, end_date)).all()
server/crud/users.py ADDED
File without changes
server/models/__init__.py ADDED
File without changes
server/models/contact.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, Integer, String
2
+ from sqlalchemy.ext.declarative import declarative_base
3
+
4
+ Base = declarative_base()
5
+
6
+ class Contact(Base):
7
+ __tablename__ = "contacts"
8
+
9
+ id = Column(Integer, primary_key=True, index=True)
10
+ email = Column(String, unique=True, index=True)
server/models/mail.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, String, ForeignKey, Integer
2
+ from sqlalchemy.dialects.postgresql import UUID as pgUUID
3
+ from sqlalchemy.ext.declarative import declarative_base
4
+ from sqlalchemy import DateTime
5
+ import uuid
6
+
7
+ Base = declarative_base()
8
+
9
+ class mail(Base):
10
+ __tablename__ = "emails"
11
+
12
+ id = Column(pgUUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
13
+ subject = Column(String)
14
+ bodyPlainRedacted = Column(String)
15
+ contactId = Column(Integer, ForeignKey("contacts.id"))
16
+ date = Column(DateTime(timezone=True))
17
+
server/routes/__init__.py ADDED
File without changes
server/routes/mail.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, Body
2
+ from sqlalchemy.orm import Session
3
+ from server.utils.database import get_db
4
+ from fastapi.encoders import jsonable_encoder
5
+ from server.controllers.mail import MailContactFilter, MailSummary,MailDate
6
+ from server.utils.summarize import summarize,summarize_week
7
+ import json
8
+ from server.crud.mail import (
9
+ get_all_mails,
10
+ get_top_n_mails,
11
+ get_contact_id_by_email,
12
+ get_mails_by_contact_id,
13
+ get_mails_by_date_range,
14
+ )
15
+
16
+ router = APIRouter(prefix="/mails")
17
+
18
+ @router.get("/all")
19
+ def read_all_mails(db: Session = Depends(get_db)):
20
+ mails = get_all_mails(db)
21
+ return jsonable_encoder([
22
+ {
23
+ "id": str(mail.id),
24
+ "bodyPlainRedacted": mail.bodyPlainRedacted
25
+ }
26
+ for mail in mails
27
+ ])
28
+
29
+
30
+ @router.get("/topk")
31
+ def read_all_mails(db: Session = Depends(get_db),topk: int=10):
32
+ mails = get_top_n_mails(db,topk=topk)
33
+ return jsonable_encoder([
34
+ {
35
+ "id": str(mail.id),
36
+ "bodyPlainRedacted": mail.bodyPlainRedacted
37
+ }
38
+ for mail in mails
39
+ ])
40
+
41
+
42
+ @router.post("/by-contact")
43
+ def get_mails_for_contact(payload: MailContactFilter, db: Session = Depends(get_db))-> MailSummary :
44
+ contact_id = payload.contact_id
45
+ user_email = payload.user_email
46
+
47
+ if not contact_id and not user_email:
48
+ raise HTTPException(status_code=400, detail="Either contact_id or user_email must be provided")
49
+
50
+ if user_email:
51
+ contact_id = get_contact_id_by_email(db, user_email)
52
+ if contact_id is None:
53
+ raise HTTPException(status_code=404, detail="User email not found")
54
+
55
+ mails = get_mails_by_contact_id(db, contact_id)
56
+ full_text=""
57
+ for i in mails:
58
+ full_text+=i.bodyPlainRedacted
59
+ obj=MailSummary()
60
+ obj.full_mail=full_text
61
+ obj.result=summarize(full_text)
62
+ return obj
63
+
64
+
65
+ @router.post("/weekly")
66
+ def get_summary_weekly(payload:MailDate, db: Session=Depends(get_db))->MailSummary:
67
+ star_date=payload.start_date
68
+ end_date=payload.end_date
69
+ mails=get_mails_by_date_range(db,start_date=star_date, end_date=end_date)
70
+ full_text=""
71
+ for i in mails:
72
+ full_text+=i.bodyPlainRedacted
73
+ obj=MailSummary()
74
+ res=summarize_week(full_text)
75
+ if res:
76
+ obj.result={"summary":res}
77
+ return obj
78
+ else:
79
+ obj.result={"summary":"No mail found"}
80
+ return obj
81
+
82
+
83
+
84
+
server/utils/__init__.py ADDED
File without changes
server/utils/config.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ debug: bool = os.getenv("DEBUG")
8
+
9
+ OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY")
10
+ X_API_KEY = os.getenv("X_API_KEY")
11
+ HIAGO_DB: str = os.getenv("HIAGO_DB")
server/utils/database.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from dotenv import load_dotenv
4
+ from sqlalchemy import create_engine
5
+ from sqlalchemy.orm import declarative_base, sessionmaker
6
+
7
+ load_dotenv()
8
+
9
+ engine = create_engine(os.getenv("HIAGO_DB"), echo=False)
10
+
11
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
12
+ Base = declarative_base()
13
+
14
+
15
+ def get_db():
16
+ db = SessionLocal()
17
+ try:
18
+ yield db
19
+ finally:
20
+ db.close()
server/utils/summarize.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import openai
3
+ import os
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+ OPENAI_API_KEY=os.getenv("OPENAI_API_KEY")
8
+
9
+
10
+ def summarize(all_text: str):
11
+ MAX_WORDS = 90000 # Keep buffer for OpenAI token limit
12
+ all_text = " ".join(all_text.split()[:MAX_WORDS])
13
+ new_text=" ".join(all_text.split())
14
+ print(len(new_text))
15
+
16
+ model_name = "gpt-4o-mini"
17
+ client = openai.OpenAI(api_key=OPENAI_API_KEY)
18
+ response = client.chat.completions.create(
19
+ model=model_name,
20
+ response_format={"type": "json_object"},
21
+ messages=[
22
+ {"role": "system", "content": "You are an AI that summarizes long emails related to the topic'. You must respond in a structured JSON format."},
23
+ {"role": "user", "content": f"Summarize this text seeing the context: \n{new_text}. Format the response as follows and give 5 headings and descriptions according to the topic from the context:\n\n{{'response': [{{'heading': 'Title', 'description': 'Summary'}}]}}"}
24
+ ],
25
+ max_tokens=2000
26
+ )
27
+
28
+ # Extract structured JSON response
29
+ final_summary = response.choices[0].message.content.strip()
30
+ parsed_summary = json.loads(final_summary) # Convert string to JSON
31
+ return parsed_summary
32
+
33
+
34
+
35
+ def summarize_week(all_text: str):
36
+ MAX_WORDS = 90000 # Keep buffer for OpenAI token limit
37
+ all_text = " ".join(all_text.split()[:MAX_WORDS])
38
+ new_text=" ".join(all_text.split())
39
+ print(len(new_text))
40
+ if len(new_text)==0:
41
+ return None
42
+
43
+ model_name = "gpt-4o-mini"
44
+ client = openai.OpenAI(api_key=OPENAI_API_KEY)
45
+ response = client.chat.completions.create(
46
+ model=model_name,
47
+ messages=[
48
+ {"role": "system", "content": "You are an AI that summarizes long emails related to the topic and write an article"},
49
+ {"role": "user", "content": f"The context is: {new_text} \n Summarize the above mentioned context and write a beautiful breife article understanding from the context"}
50
+ ],
51
+ max_tokens=2000
52
+ )
53
+
54
+ # Extract structured JSON response
55
+ final_summary = response.choices[0].message.content.strip()
56
+ return final_summary
57
+
58
+
59
+
60
+
61
+ if __name__=="__main__":
62
+ text="""
63
+ Hello City Council,
64
+
65
+ In advance of the planning meeting tonight, I would like to second my colleague, Dr. Harman Paintal, and his advocacy a focus on youth sports fields with turf and lighting. Please see his email below which I fully support.
66
+
67
+ In addition, I would strongly recommend that the City review its timeline for field allocation and consider moving that process forward 8 weeks. As the President of Stanford Strikers FC, our parents are constantly frustrated that we are not able to tell them when and where their children are practicing until perhaps 2 weeks before our season begins in early August. This process could happen much earlier and all youth sports would benefit from being able to have a schedule set well before signups for their activities each season. As you are aware, scheduling a family these days is a complex task, and our community would greatly appreciate knowing what the fall schedules will look like in early summer so they can plan accordingly.
68
+
69
+ Thank you for your consideration of these items and including the youth sports community in the planning process for the next year.
70
+
71
+ Sincerely,
72
+ Sara Filipek
73
+ Menlo Park Mom
74
+ President of the Board, Stanford Strikers FC
75
+
76
+ Email from Dr. Harman Paintal:
77
+
78
+ Dear esteemed Menlo Park City Council members,
79
+
80
+ I believe that increasing the availability of a flood lit turf field for youth sports should be a city priority. Specifically, field space should be required as part of re-development of the 60 acre SRI campus. I urge you to consider the community amenity aspect of this development.
81
+
82
+ A dedication similar to El Camino Park in Palo Alto across from Stanford Mall or Mayfield Soccer Fields at the corner of Page Mill & El Camino is needed.
83
+
84
+ At Stanford Strikers, a youth community soccer organization, the issues that we constantly face in Menlo Park are:
85
+
86
+ 1. Frequent disruption of youth sports due to weather affecting grass fields.
87
+ 2. Menlo Park youth driving to other cities due to lack of local field availability.
88
+ 3. Abbreviated practices for 6 months of the year due to absence of lighted fields.
89
+
90
+ This is in contrast to our neighboring cities up and down the peninsula that support:
91
+
92
+ 1. More regulation size athletic and recreation fields with all-weather turf surfaces.
93
+ 2. Flood lit fields that allow sports till 9 PM
94
+ 3. Usage of school fields
95
+
96
+ This is critical to the Menlo Park community as we know the benefits of youth sports:
97
+
98
+ 1. The most important solution to the mental health of our kids is the ability to be outside, to interact and play with other similar aged kids.
99
+ 2. Prevention of pre-diabetes and other metabolic risk factors
100
+ 3. Improved school attendance
101
+
102
+ Lack of field space in Menlo Park is directly affecting the health and mental being of our kids. Are there other priorities that rank above the physical, emotional and social health of our youth?
103
+
104
+ I believe that this must be a priority for our city. I think that the SRI development requires a dedication of a regulation size floodlit athletic field. I also think that the city pursue placing turf and lighting at Burgess Park to improve access for all sports.
105
+ Thank you
106
+ """
107
+ print(summarize(text))