Spaces:
Sleeping
Sleeping
Commit ·
8419546
1
Parent(s): 59d415d
initial
Browse files- .gitignore +3 -0
- Dockerfile +13 -0
- app.py +14 -0
- config/config.py +31 -0
- main.py +53 -0
- requirements.txt +16 -0
- server/controllers/__init__.py +0 -0
- server/controllers/mail.py +19 -0
- server/crud/__init__.py +0 -0
- server/crud/contacts.py +0 -0
- server/crud/mail.py +20 -0
- server/crud/users.py +0 -0
- server/models/__init__.py +0 -0
- server/models/contact.py +10 -0
- server/models/mail.py +17 -0
- server/routes/__init__.py +0 -0
- server/routes/mail.py +84 -0
- server/utils/__init__.py +0 -0
- server/utils/config.py +11 -0
- server/utils/database.py +20 -0
- server/utils/summarize.py +107 -0
.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))
|