gvlktejaswi commited on
Commit
2760712
Β·
verified Β·
1 Parent(s): 1c455d2

Update page_files/categorized/Backend/PDF_DataExtraction.py

Browse files
page_files/categorized/Backend/PDF_DataExtraction.py CHANGED
@@ -6,8 +6,8 @@ source verification: every extracted property's value must be found in the PDF
6
  first (tolerant numeric match), then its property+material meaning is checked
7
  against the sentence(s) that contained that value.
8
 
9
- Output: one table β€” Gemini's extracted properties, each flagged source_verified
10
- True/False, downloadable as CSV.
11
 
12
  Run: streamlit run FinalVerdict.py
13
  Needs GEMINI_API_KEY in .env
@@ -36,6 +36,11 @@ EMBED_MODEL_NAME = "allenai/scibert_scivocab_uncased" # matches DocToDB_eval
36
  VALUE_REL_TOL = 0.005 # 0.5% relative tolerance for numeric matching
37
  MEANING_THRESHOLD = 0.35 # cosine similarity floor for property/material meaning check
38
 
 
 
 
 
 
39
  # ─────────────────────────────────────────────────────────────────────────────
40
  # SCHEMA / PROMPT
41
  # ─────────────────────────────────────────────────────────────────────────────
@@ -45,6 +50,7 @@ SCHEMA = {
45
  "properties": {
46
  "material_name": {"type": "STRING"},
47
  "material_abbreviation": {"type": "STRING"},
 
48
  "mechanical_properties": {
49
  "type": "ARRAY",
50
  "items": {
@@ -66,8 +72,11 @@ SCHEMA = {
66
 
67
  EXTRACTION_PROMPT = (
68
  "You are an expert materials scientist. From the attached PDF, extract the material name, "
69
- "abbreviation, and ALL properties across categories (Mechanical, Thermal, Electrical, Physical, "
70
- "Optical, Rheological, etc.). Return them as 'mechanical_properties' (a single list). "
 
 
 
71
  "For each property, you MUST extract:\n"
72
  "- section (category)\n- property_name\n- value (or range)\n- unit\n"
73
  "- english (converted or alternate units, e.g., psi, Β°F, inches; write '' if not provided)\n"
@@ -78,6 +87,7 @@ EXTRACTION_PROMPT = (
78
  "{\n"
79
  ' "material_name": "",\n'
80
  ' "material_abbreviation": "",\n'
 
81
  ' "mechanical_properties": [\n'
82
  ' {"section":"","property_name":"","value":"","unit":"","english":"","test_condition":"","comments":""}\n'
83
  " ]\n"
@@ -93,6 +103,58 @@ def make_abbreviation(name: str) -> str:
93
  return abbr or name[:6].upper()
94
 
95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  # ─────────────────────────────────────────────────────────────────────────────
97
  # GEMINI CALL β€” single call, whole PDF, no chunking
98
  # ─────────────────────────────────────────────────────────────────────────────
@@ -137,7 +199,7 @@ def call_gemini_from_bytes(pdf_bytes: bytes) -> Optional[Dict[str, Any]]:
137
  return None
138
 
139
 
140
- def convert_to_dataframe(data: Dict[str, Any]) -> pd.DataFrame:
141
  if not data:
142
  return pd.DataFrame()
143
  mat_name = data.get("material_name", "") or ""
@@ -148,6 +210,7 @@ def convert_to_dataframe(data: Dict[str, Any]) -> pd.DataFrame:
148
  rows = []
149
  for item in data.get("mechanical_properties", []):
150
  rows.append({
 
151
  "material_name": mat_name,
152
  "material_abbreviation": mat_abbr,
153
  "section": item.get("section", "") or "Mechanical",
@@ -271,10 +334,11 @@ def verify_dataframe(df: pd.DataFrame, sentences: List[Dict[str, Any]]) -> pd.Da
271
 
272
  def main():
273
  st.set_page_config(page_title="Gemini Extraction β€” Source Verified", layout="wide")
274
- st.title("πŸ§ͺ Gemini Extraction β€” Source Verified")
275
  st.caption(
276
  "Straight one-call Gemini extraction β€” no chunking, no ranking, no batching. "
277
- "Every property is checked against the PDF: value found first, then meaning matched."
 
278
  )
279
 
280
  pdf_file = st.file_uploader("PDF to extract", type=["pdf"], key="fv_pdf")
@@ -291,24 +355,30 @@ def main():
291
  st.session_state["fv_sig"] = sig
292
  st.session_state["fv_result"] = None
293
 
294
- if st.button("πŸš€ Run Extraction", type="primary", use_container_width=True):
295
  with st.spinner("Extracting…"):
296
  data = call_gemini_from_bytes(pdf_bytes)
297
- df = convert_to_dataframe(data)
 
298
  sentences = _extract_sentences(pdf_bytes)
299
  st.session_state["fv_result"] = verify_dataframe(df, sentences)
 
300
 
301
  result = st.session_state.get("fv_result")
302
  if result is None:
303
  st.info("Click **Run Extraction** to start.")
304
  return
305
 
 
306
  n_verified = int(result["source_verified"].sum()) if "source_verified" in result.columns else 0
307
- st.caption(f"{len(result)} properties extracted β€” {n_verified} source-verified.")
 
 
 
308
  st.dataframe(result, use_container_width=True, hide_index=True)
309
 
310
  if not result.empty:
311
- st.download_button("⬇️ Download CSV", result.to_csv(index=False).encode(),
312
  f"{stem}_gemini_verified.csv", "text/csv")
313
 
314
 
 
6
  first (tolerant numeric match), then its property+material meaning is checked
7
  against the sentence(s) that contained that value.
8
 
9
+ Output: one table β€” Gemini's extracted properties, each with the paper DOI and
10
+ flagged source_verified True/False, downloadable as CSV.
11
 
12
  Run: streamlit run FinalVerdict.py
13
  Needs GEMINI_API_KEY in .env
 
36
  VALUE_REL_TOL = 0.005 # 0.5% relative tolerance for numeric matching
37
  MEANING_THRESHOLD = 0.35 # cosine similarity floor for property/material meaning check
38
 
39
+ # DOIs follow a strict format (10.<registrant>/<suffix>), so regex over the raw
40
+ # PDF text is far more reliable than an LLM guess. Used as the primary source;
41
+ # Gemini's own DOI (from the schema) is only a fallback.
42
+ DOI_CORE_RE = re.compile(r'10\.\d{4,9}/[^\s"<>\]\)]+', re.IGNORECASE)
43
+
44
  # ─────────────────────────────────────────────────────────────────────────────
45
  # SCHEMA / PROMPT
46
  # ─────────────────────────────────────────────────────────────────────────────
 
50
  "properties": {
51
  "material_name": {"type": "STRING"},
52
  "material_abbreviation": {"type": "STRING"},
53
+ "doi": {"type": "STRING"},
54
  "mechanical_properties": {
55
  "type": "ARRAY",
56
  "items": {
 
72
 
73
  EXTRACTION_PROMPT = (
74
  "You are an expert materials scientist. From the attached PDF, extract the material name, "
75
+ "abbreviation, the paper's DOI, and ALL properties across categories (Mechanical, Thermal, "
76
+ "Electrical, Physical, Optical, Rheological, etc.). Return the properties as "
77
+ "'mechanical_properties' (a single list). "
78
+ "For the DOI, extract the Digital Object Identifier exactly as printed (e.g., "
79
+ "10.1021/acsami.0c01234); write '' if it is not present. "
80
  "For each property, you MUST extract:\n"
81
  "- section (category)\n- property_name\n- value (or range)\n- unit\n"
82
  "- english (converted or alternate units, e.g., psi, Β°F, inches; write '' if not provided)\n"
 
87
  "{\n"
88
  ' "material_name": "",\n'
89
  ' "material_abbreviation": "",\n'
90
+ ' "doi": "",\n'
91
  ' "mechanical_properties": [\n'
92
  ' {"section":"","property_name":"","value":"","unit":"","english":"","test_condition":"","comments":""}\n'
93
  " ]\n"
 
103
  return abbr or name[:6].upper()
104
 
105
 
106
+ # ─────────────────────────────────────────────────────────────────────────────
107
+ # DOI EXTRACTION β€” regex over raw PDF text (primary), Gemini doi (fallback)
108
+ # ─────────────────────────────────────────────────────────────────────────────
109
+
110
+ def _clean_doi(doi: str) -> str:
111
+ """Strip common prefixes and trailing punctuation that regex can over-capture."""
112
+ if not doi:
113
+ return ""
114
+ doi = doi.strip()
115
+ doi = re.sub(r'^(?:https?://)?(?:dx\.)?doi\.org/', '', doi, flags=re.IGNORECASE)
116
+ doi = re.sub(r'^doi[:\s]+', '', doi, flags=re.IGNORECASE)
117
+ doi = doi.rstrip(' .,;:)]}>"\'')
118
+ return doi
119
+
120
+
121
+ def _extract_doi_from_pdf(pdf_bytes: bytes) -> str:
122
+ """Find the paper DOI in the raw PDF text. Prefers an explicit doi.org URL or
123
+ a 'doi:' label, then falls back to the first bare 10.xxxx/... token."""
124
+ try:
125
+ with fitz.open(stream=pdf_bytes, filetype="pdf") as doc:
126
+ text = "\n".join((page.get_text("text") or "") for page in doc)
127
+ except Exception:
128
+ return ""
129
+
130
+ # 1) explicit doi.org URL
131
+ m = re.search(r'(?:https?://)?(?:dx\.)?doi\.org/(10\.\d{4,9}/[^\s"<>\]\)]+)', text, re.IGNORECASE)
132
+ if m:
133
+ return _clean_doi(m.group(1))
134
+
135
+ # 2) 'doi:' or 'DOI ' labelled
136
+ m = re.search(r'\bdoi[:\s]+\s*(10\.\d{4,9}/[^\s"<>\]\)]+)', text, re.IGNORECASE)
137
+ if m:
138
+ return _clean_doi(m.group(1))
139
+
140
+ # 3) any bare DOI token
141
+ m = DOI_CORE_RE.search(text)
142
+ if m:
143
+ return _clean_doi(m.group(0))
144
+
145
+ return ""
146
+
147
+
148
+ def resolve_doi(pdf_bytes: bytes, gemini_data: Optional[Dict[str, Any]]) -> str:
149
+ """Prefer the regex-extracted DOI (format-verified). Fall back to Gemini's."""
150
+ doi = _extract_doi_from_pdf(pdf_bytes)
151
+ if doi:
152
+ return doi
153
+ if gemini_data:
154
+ return _clean_doi(str(gemini_data.get("doi", "") or ""))
155
+ return ""
156
+
157
+
158
  # ─────────────────────────────────────────────────────────────────────────────
159
  # GEMINI CALL β€” single call, whole PDF, no chunking
160
  # ─────────────────────────────────────────────────────────────────────────────
 
199
  return None
200
 
201
 
202
+ def convert_to_dataframe(data: Dict[str, Any], doi: str = "") -> pd.DataFrame:
203
  if not data:
204
  return pd.DataFrame()
205
  mat_name = data.get("material_name", "") or ""
 
210
  rows = []
211
  for item in data.get("mechanical_properties", []):
212
  rows.append({
213
+ "doi": doi,
214
  "material_name": mat_name,
215
  "material_abbreviation": mat_abbr,
216
  "section": item.get("section", "") or "Mechanical",
 
334
 
335
  def main():
336
  st.set_page_config(page_title="Gemini Extraction β€” Source Verified", layout="wide")
337
+ st.title(" Gemini Extraction β€” Source Verified")
338
  st.caption(
339
  "Straight one-call Gemini extraction β€” no chunking, no ranking, no batching. "
340
+ "Every property is checked against the PDF: value found first, then meaning matched. "
341
+ "Paper DOI is pulled from the PDF text."
342
  )
343
 
344
  pdf_file = st.file_uploader("PDF to extract", type=["pdf"], key="fv_pdf")
 
355
  st.session_state["fv_sig"] = sig
356
  st.session_state["fv_result"] = None
357
 
358
+ if st.button(" Run Extraction", type="primary", use_container_width=True):
359
  with st.spinner("Extracting…"):
360
  data = call_gemini_from_bytes(pdf_bytes)
361
+ doi = resolve_doi(pdf_bytes, data)
362
+ df = convert_to_dataframe(data, doi=doi)
363
  sentences = _extract_sentences(pdf_bytes)
364
  st.session_state["fv_result"] = verify_dataframe(df, sentences)
365
+ st.session_state["fv_doi"] = doi
366
 
367
  result = st.session_state.get("fv_result")
368
  if result is None:
369
  st.info("Click **Run Extraction** to start.")
370
  return
371
 
372
+ doi = st.session_state.get("fv_doi", "")
373
  n_verified = int(result["source_verified"].sum()) if "source_verified" in result.columns else 0
374
+ st.caption(
375
+ f"DOI: {doi or '(not found)'} β€” "
376
+ f"{len(result)} properties extracted β€” {n_verified} source-verified."
377
+ )
378
  st.dataframe(result, use_container_width=True, hide_index=True)
379
 
380
  if not result.empty:
381
+ st.download_button(" Download CSV", result.to_csv(index=False).encode(),
382
  f"{stem}_gemini_verified.csv", "text/csv")
383
 
384