PPN1 commited on
Commit
f4c95fc
Β·
verified Β·
1 Parent(s): 91164ba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +39 -45
app.py CHANGED
@@ -16,13 +16,12 @@ import requests
16
  from pdf2image import convert_from_bytes
17
  from google.cloud import vision_v1 as vision
18
 
19
- # ───── Load .env locally (in HF Spaces secrets are injected automatically) ─────
 
20
  load_dotenv()
21
-
22
- # ───── Configure your API keys ─────────────────────────────────────────────────
23
- openai.api_key = os.getenv("OPENAI_API_KEY")
24
- gcv_api_key = os.getenv("GCV_API_KEY")
25
- proxycurl_api_key = os.getenv("PROXYCURL_API_KEY")
26
 
27
  if not openai.api_key:
28
  raise RuntimeError("Missing OPENAI_API_KEY")
@@ -31,9 +30,8 @@ if not gcv_api_key:
31
  if not proxycurl_api_key:
32
  raise RuntimeError("Missing PROXYCURL_API_KEY")
33
 
34
- # ───── FastAPI Setup ───────────────────────────────────────────────────────────
35
  app = FastAPI(title="Aliro Data Extraction API")
36
-
37
  app.add_middleware(
38
  CORSMiddleware,
39
  allow_origins=["*"],
@@ -41,17 +39,15 @@ app.add_middleware(
41
  allow_headers=["*"],
42
  )
43
 
44
- # ───── Helper: Try to pull raw bytes out of whatever UploadFile gives you ───────
45
- def get_bytes(f: UploadFile) -> bytes:
46
- data = f.file.read()
47
- # rewind so Starlette can close cleanly
48
- f.file.seek(0)
49
- return data
50
 
51
- # ───── Extract text from PDF or image buffers ─────────────────────────────────
52
  def process_buffers(buffers: List[BytesIO]) -> List[str]:
53
  client = vision.ImageAnnotatorClient(client_options={"api_key": gcv_api_key})
54
- results = []
55
 
56
  for buf in buffers:
57
  name = getattr(buf, "name", "").lower()
@@ -59,47 +55,46 @@ def process_buffers(buffers: List[BytesIO]) -> List[str]:
59
 
60
  # PDF: try text β†’ fallback to page-by-page OCR
61
  if name.endswith(".pdf"):
62
- # 1) text via PyPDF2
63
  try:
64
  reader = PyPDF2.PdfReader(io.BytesIO(data))
65
- txt = "".join(page.extract_text() or "" for page in reader.pages)
66
- if txt.strip():
67
- results.append(txt)
68
  continue
69
  except:
70
  pass
71
 
72
- # 2) fallback: render each page β†’ OCR
73
  try:
74
- images = convert_from_bytes(data)
75
- for img in images:
76
  img_buf = io.BytesIO()
77
  img.save(img_buf, format="JPEG")
78
  resp = client.text_detection(image=vision.Image(content=img_buf.getvalue()))
79
- text = resp.text_annotations[0].description or ""
80
- if text:
81
- results.append(text)
82
  continue
83
  except:
84
  pass
85
 
86
- # Image β†’ OCR
87
  if name.endswith((".png", ".jpg", ".jpeg")):
88
  try:
89
  resp = client.text_detection(image=vision.Image(content=data))
90
- text = resp.text_annotations[0].description or ""
91
- if text:
92
- results.append(text)
93
  continue
94
  except:
95
  pass
96
 
97
- # unsupported
98
  results.append(f"[Unsupported file type: {name}]")
99
 
100
  return results
101
 
102
- # ───── Scrape LinkedIn via Proxycurl ────────────────────────────────────────────
103
  def scrape_linkedin(url: str) -> str:
104
  if not url:
105
  return ""
@@ -112,7 +107,7 @@ def scrape_linkedin(url: str) -> str:
112
  resp.raise_for_status()
113
  return json.dumps(resp.json(), indent=2)
114
 
115
- # ───── Summarize via OpenAI ────────────────────────────────────────────────────
116
  def make_summary(chunks: List[str]) -> str:
117
  if not chunks:
118
  return "No data extracted."
@@ -124,24 +119,24 @@ def make_summary(chunks: List[str]) -> str:
124
  "role": "system",
125
  "content": (
126
  "Extract and structure personal, educational, and professional details "
127
- "into a clear, logical hierarchy. Translate non-English content, remove "
128
- "duplicates, and output in English."
129
  )
130
  },
131
- {"role": "user", "content": f"Summarize the following data:\n\n{prompt}"}
132
  ],
133
- max_tokens=1500
134
  )
135
  return resp.choices[0].message.content.strip()
136
 
137
- # ───── The /extract endpoint ───────────────────────────────────────────────────
138
  @app.post("/extract")
139
  async def extract_endpoint(
140
  files: List[UploadFile] = File(default=[]),
141
- linkedin_url: str = Form(default="")
142
  ):
143
  try:
144
- # read & wrap each UploadFile in a BytesIO
145
  buffers: List[BytesIO] = []
146
  for f in files:
147
  data = await f.read()
@@ -149,18 +144,17 @@ async def extract_endpoint(
149
  bio.name = f.filename
150
  buffers.append(bio)
151
 
152
- # extract and summarize
153
  texts = process_buffers(buffers)
154
-
155
- # LinkedIn scrape, if provided
156
  if linkedin_url:
157
  texts.append(scrape_linkedin(linkedin_url))
158
-
159
  summary = make_summary(texts)
160
  return {"summary": summary}
161
 
162
  except requests.HTTPError as e:
163
  raise HTTPException(status_code=502, detail=f"LinkedIn scrape failed: {e}")
164
- except Exception as e:
165
  tb = traceback.format_exc()
166
  raise HTTPException(status_code=500, detail=f"Processing error:\n\n{tb}")
 
16
  from pdf2image import convert_from_bytes
17
  from google.cloud import vision_v1 as vision
18
 
19
+ # ───── Load .env (locally) ────────────────────────────────────
20
+ # In HF Spaces you'll set these as Secrets under Settings β†’ Variables & secrets
21
  load_dotenv()
22
+ openai.api_key = os.getenv("OPENAI_API_KEY")
23
+ gcv_api_key = os.getenv("GCV_API_KEY")
24
+ proxycurl_api_key = os.getenv("PROXYCURL_API_KEY")
 
 
25
 
26
  if not openai.api_key:
27
  raise RuntimeError("Missing OPENAI_API_KEY")
 
30
  if not proxycurl_api_key:
31
  raise RuntimeError("Missing PROXYCURL_API_KEY")
32
 
33
+ # ───── FastAPI setup ──────────────────────────────────────────
34
  app = FastAPI(title="Aliro Data Extraction API")
 
35
  app.add_middleware(
36
  CORSMiddleware,
37
  allow_origins=["*"],
 
39
  allow_headers=["*"],
40
  )
41
 
42
+ # ───── A simple root so GET / won’t 404 ───────────────────────
43
+ @app.get("/")
44
+ def read_root():
45
+ return {"message": "Aliro Data Extraction API – POST your files to /extract"}
 
 
46
 
47
+ # ───── PDF/OCR processing ─────────────────────────────────────
48
  def process_buffers(buffers: List[BytesIO]) -> List[str]:
49
  client = vision.ImageAnnotatorClient(client_options={"api_key": gcv_api_key})
50
+ results: List[str] = []
51
 
52
  for buf in buffers:
53
  name = getattr(buf, "name", "").lower()
 
55
 
56
  # PDF: try text β†’ fallback to page-by-page OCR
57
  if name.endswith(".pdf"):
58
+ # 1) PyPDF2 text
59
  try:
60
  reader = PyPDF2.PdfReader(io.BytesIO(data))
61
+ text = "".join(page.extract_text() or "" for page in reader.pages)
62
+ if text.strip():
63
+ results.append(text)
64
  continue
65
  except:
66
  pass
67
 
68
+ # 2) Fallback: render pages β†’ OCR
69
  try:
70
+ for img in convert_from_bytes(data):
 
71
  img_buf = io.BytesIO()
72
  img.save(img_buf, format="JPEG")
73
  resp = client.text_detection(image=vision.Image(content=img_buf.getvalue()))
74
+ desc = resp.text_annotations[0].description if resp.text_annotations else ""
75
+ if desc:
76
+ results.append(desc)
77
  continue
78
  except:
79
  pass
80
 
81
+ # Image β†’ always OCR
82
  if name.endswith((".png", ".jpg", ".jpeg")):
83
  try:
84
  resp = client.text_detection(image=vision.Image(content=data))
85
+ desc = resp.text_annotations[0].description if resp.text_annotations else ""
86
+ if desc:
87
+ results.append(desc)
88
  continue
89
  except:
90
  pass
91
 
92
+ # Otherwise unsupported
93
  results.append(f"[Unsupported file type: {name}]")
94
 
95
  return results
96
 
97
+ # ───── LinkedIn scrape via Proxycurl ─────────────────────────
98
  def scrape_linkedin(url: str) -> str:
99
  if not url:
100
  return ""
 
107
  resp.raise_for_status()
108
  return json.dumps(resp.json(), indent=2)
109
 
110
+ # ───── Summarize via OpenAI ─────────────────────────────────
111
  def make_summary(chunks: List[str]) -> str:
112
  if not chunks:
113
  return "No data extracted."
 
119
  "role": "system",
120
  "content": (
121
  "Extract and structure personal, educational, and professional details "
122
+ "into a clear, logical and relational hierarchy. Translate non-English "
123
+ "content, remove duplicates, and output in English."
124
  )
125
  },
126
+ {"role": "user", "content": f"Summarize the following data:\n\n{prompt}"},
127
  ],
128
+ max_tokens=1500,
129
  )
130
  return resp.choices[0].message.content.strip()
131
 
132
+ # ───── The POST /extract endpoint ─────────────────────────────
133
  @app.post("/extract")
134
  async def extract_endpoint(
135
  files: List[UploadFile] = File(default=[]),
136
+ linkedin_url: str = Form(default="")
137
  ):
138
  try:
139
+ # Wrap each UploadFile in a BytesIO (so we can peek at .name & .getvalue())
140
  buffers: List[BytesIO] = []
141
  for f in files:
142
  data = await f.read()
 
144
  bio.name = f.filename
145
  buffers.append(bio)
146
 
147
+ # 1) OCR/PDF text
148
  texts = process_buffers(buffers)
149
+ # 2) LinkedIn JSON
 
150
  if linkedin_url:
151
  texts.append(scrape_linkedin(linkedin_url))
152
+ # 3) Summarize
153
  summary = make_summary(texts)
154
  return {"summary": summary}
155
 
156
  except requests.HTTPError as e:
157
  raise HTTPException(status_code=502, detail=f"LinkedIn scrape failed: {e}")
158
+ except Exception:
159
  tb = traceback.format_exc()
160
  raise HTTPException(status_code=500, detail=f"Processing error:\n\n{tb}")