Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Python - FastAPI, Postgres, tsvector"""

# Current Version
__version__ = "2.1.5"
__version__ = "2.1.6"
113 changes: 76 additions & 37 deletions app/api/llm/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,88 @@
def get_llm_records(
request: Request,
page: int = Query(1, ge=1, description="Page number (1-based)"),
page_size: int = Query(10, ge=1, le=100, description="Records per page")
, api_key: str = Depends(get_api_key)
page_size: int = Query(10, ge=1, le=100, description="Records per page"),
prospect_id: int = Query(None, description="Filter by prospect_id"),
api_key: str = Depends(get_api_key)
) -> dict:
"""GET /llm: Paginated list of LLM completions."""
try:
conn = get_db_connection_direct()
cur = conn.cursor()
offset = (page - 1) * page_size
cur.execute("SELECT COUNT(*) FROM llm;")
count_row = cur.fetchone()
total = count_row[0] if count_row and count_row[0] is not None else 0
cur.execute("""
SELECT id, prompt, completion, duration, time, data, model, prospect_id
FROM llm
ORDER BY id DESC
LIMIT %s OFFSET %s;
""", (page_size, offset))
records = [
{
"id": row[0],
"prompt": row[1],
"completion": row[2],
"duration": row[3],
"time": row[4].isoformat() if row[4] else None,
"data": row[5],
"model": row[6],
"prospect_id": row[7],
if prospect_id is not None:
# No pagination for single prospect_id lookup
select_query = """
SELECT id, prompt, completion, duration, time, data, model, prospect_id
FROM llm
WHERE prospect_id = %s
ORDER BY id DESC
"""
cur.execute(select_query, (prospect_id,))
rows = cur.fetchall()
records = [
{
"id": row[0],
"prompt": row[1],
"completion": row[2],
"duration": row[3],
"time": row[4].isoformat() if row[4] else None,
"data": row[5],
"model": row[6],
"prospect_id": row[7],
}
for row in rows
]
cur.close()
conn.close()
if records:
meta = make_meta("success", f"Found {len(records)} record(s) for prospect_id {prospect_id}")
return {
"meta": meta,
"data": records,
}
else:
meta = make_meta("warning", f"No records found for prospect_id {prospect_id}")
return {
"meta": meta,
"data": [],
}
else:
offset = (page - 1) * page_size
cur.execute("SELECT COUNT(*) FROM llm;")
count_row = cur.fetchone()
total = count_row[0] if count_row and count_row[0] is not None else 0
cur.execute("""
SELECT id, prompt, completion, duration, time, data, model, prospect_id
FROM llm
ORDER BY id DESC
LIMIT %s OFFSET %s;
""", (page_size, offset))
records = [
{
"id": row[0],
"prompt": row[1],
"completion": row[2],
"duration": row[3],
"time": row[4].isoformat() if row[4] else None,
"data": row[5],
"model": row[6],
"prospect_id": row[7],
}
for row in cur.fetchall()
]
cur.close()
conn.close()
meta = make_meta("success", f"LLM {len(records)} records (page {page})")
return {
"meta": meta,
"data": {
"page": page,
"page_size": page_size,
"total": total,
"pages": (total + page_size - 1) // page_size,
"data": records,
},
}
for row in cur.fetchall()
]
cur.close()
conn.close()
meta = make_meta("success", f"LLM {len(records)} records (page {page})")
return {
"meta": meta,
"data": {
"page": page,
"page_size": page_size,
"total": total,
"pages": (total + page_size - 1) // page_size,
"data": records,
},
}
except Exception as e:
meta = make_meta("error", f"DB error: {str(e)}")
return {"meta": meta, "data": {}}
Expand Down
2 changes: 2 additions & 0 deletions app/api/llm/sql/alter_add_type_column.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Migration: Add 'type' column to llm table
ALTER TABLE llm ADD COLUMN IF NOT EXISTS type TEXT DEFAULT 'default';
21 changes: 21 additions & 0 deletions app/api/llm/sql/run_alter_add_type_column.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Run this script to add a 'type' column to the llm table if it doesn't exist
from app.utils.db import get_db_connection_direct

def add_type_column():
conn = get_db_connection_direct()
cur = conn.cursor()
try:
cur.execute("""
ALTER TABLE llm ADD COLUMN IF NOT EXISTS type TEXT DEFAULT 'default';
""")
conn.commit()
print("'type' column added to llm table (if not already present).")
except Exception as e:
print(f"Error adding 'type' column: {e}")
conn.rollback()
finally:
cur.close()
conn.close()

if __name__ == "__main__":
add_type_column()
2 changes: 1 addition & 1 deletion app/api/prospects/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Prospect Routes"""

from .prospects import router as prospects_router
from .flagged import router as flagged_router
from .flag import router as flag_router
26 changes: 25 additions & 1 deletion app/api/prospects/flagged.py → app/api/prospects/flag.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@

from app import __version__
import os
from app.utils.make_meta import make_meta
from fastapi import APIRouter, Query, Path, Body, HTTPException
from fastapi.responses import JSONResponse
from app.utils.db import get_db_connection

router = APIRouter()
base_url = os.getenv("BASE_URL", "http://localhost:8000")

# Route to unflag all prospects
@router.post("/unflag-all")
def unflag_all_prospects():
"""Reset all flags in the prospects table to false."""
meta = make_meta("success", "All flags reset to false")
conn_gen = get_db_connection()
conn = next(conn_gen)
cur = conn.cursor()
try:
cur.execute("UPDATE prospects SET flag = FALSE WHERE flag IS TRUE;")
affected = cur.rowcount
conn.commit()
meta = make_meta("success", f"{affected} prospects unflagged.")
except Exception as e:
conn.rollback()
meta = make_meta("error", f"Failed to unflag all prospects: {str(e)}")
return JSONResponse(status_code=500, content={"meta": meta})
finally:
cur.close()
conn.close()
return {"meta": meta}


# Refactored GET /prospects endpoint to return paginated, filtered, and ordered results
# Gel all flagged prspects
@router.get("/prospects/flagged")
def get_prospects(
page: int = Query(1, ge=1, description="Page number (1-based)"),
Expand Down
92 changes: 2 additions & 90 deletions app/api/prospects/prospects.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
@router.get("/prospects")
def get_prospects(
page: int = Query(1, ge=1, description="Page number (1-based)"),
limit: int = Query(50, ge=1, le=500, description="Records per page (default 50, max 500)"),
limit: int = Query(100, ge=1, le=500, description="Records per page (default 100, max 500)"),
search: str = Query(None, description="Search term for first or last name (case-insensitive, partial match)")
) -> dict:
"""Return paginated, filtered, and ordered prospects (flagged first, then alphabetical by first_name), filtered by search if provided."""
"""Return paginated, filtered, and ordered prospects (then alphabetical by first_name), filtered by search if provided."""
meta = make_meta("success", "Read paginated prospects")
conn_gen = get_db_connection()
conn = next(conn_gen)
Expand Down Expand Up @@ -80,94 +80,6 @@ class ProspectUpdate(BaseModel):
flag: Optional[bool] = None
hide: Optional[bool] = None


# endpoint: /prospects/init
@router.get("/prospects/init")
def prospects_init() -> dict:
"""Initialize prospects and return real total count."""
meta = make_meta("success", "Init prospects")
conn_gen = get_db_connection()
conn = next(conn_gen)
cur = conn.cursor()
title = []
total_unique_title = 0
seniority = []
total_unique_seniority = 0
sub_departments = []
total_unique_sub_departments = 0
try:
cur.execute('SELECT COUNT(*) FROM prospects WHERE hide IS NOT TRUE;')
row = cur.fetchone()
total = row[0] if row is not None else 0

# Get unique titles and their counts (column is 'title')
cur.execute('SELECT title, COUNT(*) FROM prospects WHERE title IS NOT NULL AND hide IS NOT TRUE GROUP BY title ORDER BY COUNT(*) DESC;')
title_rows = cur.fetchall()
def slugify(text):
import re
text = str(text).lower()
text = re.sub(r'[^a-z0-9]+', '-', text)
return text.strip('-')

title = [
{"label": str(t[0]), "value": slugify(t[0])}
for t in title_rows
if t[0] is not None and str(t[0]).strip() != "" and slugify(t[0]) != ""
]
total_unique_title = len(title)

# Get unique seniority and their counts (column is 'seniority')
cur.execute('SELECT seniority, COUNT(*) FROM prospects WHERE seniority IS NOT NULL AND hide IS NOT TRUE GROUP BY seniority ORDER BY COUNT(*) DESC;')
seniority_rows = cur.fetchall()
seniority = [
{"label": str(s[0]), "value": slugify(s[0])}
for s in seniority_rows
if s[0] is not None and str(s[0]).strip() != "" and slugify(s[0]) != ""
]
total_unique_seniority = len(seniority)

# Get unique sub_departments and their counts (column is 'sub_departments')
cur.execute('SELECT sub_departments, COUNT(*) FROM prospects WHERE sub_departments IS NOT NULL AND hide IS NOT TRUE GROUP BY sub_departments ORDER BY COUNT(*) DESC;')
sub_department_rows = cur.fetchall()
sub_departments = [
{"label": str(sd[0]), "value": slugify(sd[0])}
for sd in sub_department_rows
if sd[0] is not None and str(sd[0]).strip() != "" and slugify(sd[0]) != ""
]
total_unique_sub_departments = len(sub_departments)
except Exception:
total = 0
title = []
total_unique_title = 0
seniority = []
total_unique_seniority = 0
sub_departments = []
total_unique_sub_departments = 0
finally:
cur.close()
conn.close()
data = {
"total": total,
"groups": {
"seniority": {
"total": total_unique_seniority,
"list": seniority
},
"title": {
"total": total_unique_title,
"list": title
},
"sub_departments": {
"total": total_unique_sub_departments,
"list": sub_departments
}
},
"message": "This is a placeholder for prospects/init."
}
return {"meta": meta, "data": data}


# endpoint: /prospects/{id}
# endpoint: /prospects/{id}
@router.get("/prospects/{id}")
def prospects_read_one(id: int = Path(..., description="ID of the prospect to retrieve")) -> dict:
Expand Down
4 changes: 2 additions & 2 deletions app/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from app.api.resend.resend import router as resend_router
from app.api.llm.llm import router as llm_router
from app.api.prospects.prospects import router as prospects_router
from app.api.prospects.flagged import router as flagged_router
from app.api.prospects.flag import router as flag_router
from app.api.llm.llm import router as gemini_router

router.include_router(root_router)
router.include_router(resend_router)
router.include_router(health_router)
router.include_router(llm_router)
router.include_router(flagged_router)
router.include_router(flag_router)
router.include_router(prospects_router)
router.include_router(gemini_router)
11 changes: 11 additions & 0 deletions tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,15 @@ def test_health_returns_ok() -> None:
assert response.status_code == 200
assert response.json() == {"status": "ok"}

def test_unflag_all_prospects():
"""POST /unflag-all should reset all flags to false and return success meta."""
# First, flag some prospects (if needed) - skipping setup for brevity
response = client.post("/unflag-all")
assert response.status_code == 200
json_data = response.json()
# The meta dict uses 'severity' for status, not 'status'
assert json_data["meta"].get("severity") == "success"
# Accept any success message in the title
assert json_data["meta"].get("title", "").endswith("unflagged.")


Loading