Spaces:
Sleeping
Sleeping
File size: 8,621 Bytes
8160245 166e4a6 8160245 f4c95fc 8160245 166e4a6 8160245 166e4a6 8160245 f4c95fc 8160245 166e4a6 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 166e4a6 8160245 166e4a6 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 f4c95fc 8160245 166e4a6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | # app.py
import os
import io
import json
import time
import traceback
from typing import List
from io import BytesIO
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv
import PyPDF2
import openai
import requests
from pdf2image import convert_from_bytes
from google.cloud import vision_v1 as vision
# βββββ Load .env (locally) ββββββββββββββββββββββββββββββββββββ
# In HF Spaces you'll set these as Secrets under Settings β Variables & secrets
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
gcv_api_key = os.getenv("GCV_API_KEY")
brightdata_api_key = os.getenv("BRIGHTDATA_API_KEY")
brightdata_dataset_id = "gd_l1viktl72bvl7bjuj0"
if not openai.api_key:
raise RuntimeError("Missing OPENAI_API_KEY")
if not gcv_api_key:
raise RuntimeError("Missing GCV_API_KEY")
if not brightdata_api_key:
raise RuntimeError("Missing BRIGHTDATA_API_KEY")
# βββββ FastAPI setup ββββββββββββββββββββββββββββββββββββββββββ
app = FastAPI(title="Aliro Data Extraction API")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# βββββ A simple root so GET / won't 404 βββββββββββββββββββββββ
@app.get("/")
def read_root():
return {"message": "Aliro Data Extraction API β POST your files to /extract"}
# βββββ PDF/OCR processing βββββββββββββββββββββββββββββββββββββ
def process_buffers(buffers: List[BytesIO]) -> List[str]:
client = vision.ImageAnnotatorClient(client_options={"api_key": gcv_api_key})
results: List[str] = []
for buf in buffers:
name = getattr(buf, "name", "").lower()
data = buf.getvalue()
# PDF: try text β fallback to page-by-page OCR
if name.endswith(".pdf"):
# 1) PyPDF2 text
try:
reader = PyPDF2.PdfReader(io.BytesIO(data))
text = "".join(page.extract_text() or "" for page in reader.pages)
if text.strip():
results.append(text)
continue
except:
pass
# 2) Fallback: render pages β OCR
try:
for img in convert_from_bytes(data):
img_buf = io.BytesIO()
img.save(img_buf, format="JPEG")
resp = client.text_detection(image=vision.Image(content=img_buf.getvalue()))
desc = resp.text_annotations[0].description if resp.text_annotations else ""
if desc:
results.append(desc)
continue
except:
pass
# Image β always OCR
if name.endswith((".png", ".jpg", ".jpeg")):
try:
resp = client.text_detection(image=vision.Image(content=data))
desc = resp.text_annotations[0].description if resp.text_annotations else ""
if desc:
results.append(desc)
continue
except:
pass
# Otherwise unsupported
results.append(f"[Unsupported file type: {name}]")
return results
# βββββ LinkedIn scrape via BrightData βββββββββββββββββββββββββ
def scrape_linkedin(url: str) -> str:
if not url:
return ""
try:
# Step 1: Trigger data collection
trigger_url = "https://api.brightdata.com/datasets/v3/trigger"
headers = {
"Authorization": f"Bearer {brightdata_api_key}",
"Content-Type": "application/json",
}
params = {
"dataset_id": brightdata_dataset_id,
"include_errors": "true",
}
trigger_response = requests.post(
trigger_url,
headers=headers,
params=params,
json=[{"url": url}],
timeout=30
)
if not trigger_response.ok:
raise Exception(f"BrightData trigger failed: {trigger_response.status_code} - {trigger_response.text}")
trigger_data = trigger_response.json()
snapshot_id = trigger_data.get('snapshot_id')
if not snapshot_id:
raise Exception("No snapshot_id received from BrightData")
print(f"BrightData collection triggered, snapshot_id: {snapshot_id}")
# Step 2: Poll for completion (max 5 minutes)
max_attempts = 30
for attempt in range(max_attempts):
time.sleep(10) # Wait 10 seconds between checks
progress_url = f"https://api.brightdata.com/datasets/v3/progress/{snapshot_id}"
progress_response = requests.get(progress_url, headers=headers, timeout=30)
if progress_response.ok:
progress_data = progress_response.json()
status = progress_data.get('status')
print(f"Progress check {attempt + 1}: status = {status}")
if status == 'ready':
# Step 3: Fetch results
result_url = f"https://api.brightdata.com/datasets/v3/snapshot/{snapshot_id}"
result_params = {"format": "json"}
result_response = requests.get(result_url, headers=headers, params=result_params, timeout=30)
if result_response.ok:
linkedin_data = result_response.json()
return json.dumps(linkedin_data, indent=2)
else:
raise Exception(f"Failed to fetch results: {result_response.status_code} - {result_response.text}")
elif status == 'failed':
raise Exception("BrightData collection failed")
# Continue polling if status is 'running'
else:
print(f"Progress check failed: {progress_response.status_code}")
raise Exception("Timeout waiting for BrightData collection to complete")
except Exception as e:
raise Exception(f"LinkedIn scraping error: {str(e)}")
# βββββ Summarize via OpenAI βββββββββββββββββββββββββββββββββ
def make_summary(chunks: List[str]) -> str:
if not chunks:
return "No data extracted."
prompt = "\n\n---\n\n".join(chunks)
resp = openai.ChatCompletion.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": (
"Extract and structure personal, educational, and professional details "
"into a clear, logical and relational hierarchy. Translate non-English "
"content, remove duplicates, and output in English."
)
},
{"role": "user", "content": f"Summarize the following data:\n\n{prompt}"},
],
max_tokens=1500,
)
return resp.choices[0].message.content.strip()
# βββββ The POST /extract endpoint βββββββββββββββββββββββββββββ
@app.post("/extract")
async def extract_endpoint(
files: List[UploadFile] = File(default=[]),
linkedin_url: str = Form(default="")
):
try:
# Wrap each UploadFile in a BytesIO (so we can peek at .name & .getvalue())
buffers: List[BytesIO] = []
for f in files:
data = await f.read()
bio = BytesIO(data)
bio.name = f.filename
buffers.append(bio)
# 1) OCR/PDF text
texts = process_buffers(buffers)
# 2) LinkedIn JSON
if linkedin_url:
texts.append(scrape_linkedin(linkedin_url))
# 3) Summarize
summary = make_summary(texts)
return {"summary": summary}
except requests.HTTPError as e:
raise HTTPException(status_code=502, detail=f"LinkedIn scrape failed: {e}")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Processing error: {str(e)}")
|