Repository navigation
Expand file tree
/
Copy pathapp.py
More file actions
104 lines (93 loc) · 4.44 KB
/
Copy pathapp.py
File metadata and controls
104 lines (93 loc) · 4.44 KB
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
#!/usr/bin/env python3
"""JosePDFs — local web UI. Upload PDF(s), review the extracted fields, then
import to REDCap with one click. Nothing is uploaded until you press Import.
streamlit run app.py
"""
import os
import tempfile
import pandas as pd
import streamlit as st
st.set_page_config(page_title="JosePDFs — PDF to REDCap", page_icon="📜",
layout="wide")
with st.sidebar:
st.header("⚙️ Settings")
os.environ["REDCAP_API_URL"] = st.text_input(
"REDCap API URL", os.environ.get("REDCAP_API_URL", "")).strip()
os.environ["REDCAP_TOKEN"] = st.text_input(
"REDCap API token", os.environ.get("REDCAP_TOKEN", ""),
type="password").strip()
st.divider()
os.environ["LLM_BASE_URL"] = st.text_input(
"LLM base URL", os.environ.get("LLM_BASE_URL",
"http://localhost:11434/v1")).strip()
os.environ["LLM_MODEL"] = st.text_input(
"LLM model", os.environ.get("LLM_MODEL", "qwen2.5:14b-instruct")).strip()
os.environ["JOSEPDFS_DOMAIN"] = st.text_input(
"Domain module", os.environ.get("JOSEPDFS_DOMAIN", "domains.example")).strip()
st.title("📜 JosePDFs — from PDF to REDCap")
st.caption("Reads unstructured PDFs and turns them into validated, API-ready "
"REDCap records. Review each one before importing.")
if not (os.environ.get("REDCAP_API_URL") and os.environ.get("REDCAP_TOKEN")):
st.warning("Enter your REDCap API URL and token in the sidebar to begin.")
st.stop()
STRUCTURAL = {"record_id", "redcap_event_name", "redcap_repeat_instrument",
"redcap_repeat_instance"}
files = st.file_uploader("Drop PDF(s) here", type=["pdf"],
accept_multiple_files=True)
if st.button("🔍 Extract & preview", type="primary", disabled=not files):
from josepdfs.domain import load_domain
from josepdfs.pipeline import process
from josepdfs.redcap import LiveRecords, Schema
try:
with st.spinner("Connecting to REDCap…"):
domain = load_domain()
schema, live = Schema(), LiveRecords(domain)
except Exception as e: # noqa: BLE001
st.error(f"Could not connect. Check URL/token.\n\n{e}")
st.stop()
reports = []
for f in files:
tmp = os.path.join(tempfile.gettempdir(), f.name)
with open(tmp, "wb") as out:
out.write(f.getbuffer())
try:
rep = process(tmp, schema, live, domain, commit=False)
except Exception as e: # noqa: BLE001
rep = {"_error": str(e), "file": f.name}
reports.append(rep)
st.session_state["reports"] = reports
st.session_state["imported"] = {}
for idx, rep in enumerate(st.session_state.get("reports", [])):
if rep.get("_error"):
st.error(f"❌ {rep['file']}: {rep['_error']}")
continue
mode = ("🆕 NEW baseline" if rep["new_record"]
else "✏️ UPDATE existing visit" if rep["update_existing_visit"]
else "➕ NEW follow-up")
with st.expander(f"📄 {rep['file']} → record {rep['record_id']} · {mode}",
expanded=True):
if rep["review_flags"]:
st.warning("⚠️ **Verify before importing:**\n"
+ "\n".join(f"- {x}" for x in rep["review_flags"]))
data = {k: v for k, v in rep["fields_written"].items()
if k not in STRUCTURAL and not k.endswith("_complete")
and v not in ("", None)}
edited = st.data_editor(
pd.DataFrame([{"field": k, "value": str(v)} for k, v in data.items()]),
key=f"ed{idx}", hide_index=True, use_container_width=True,
disabled=["field"], num_rows="fixed")
if st.session_state.get("imported", {}).get(rep["file"]):
st.success("✅ Imported")
elif st.button("✅ Import this record", key=f"imp{idx}", type="primary"):
from josepdfs.redcap import import_row
row = dict(rep["fields_written"])
for _, r in edited.iterrows():
row[r["field"]] = r["value"].strip()
with st.spinner("Importing…"):
try:
res = import_row(row, force_autonumber=rep.get(
"force_autonumber", rep["new_record"]))
st.session_state["imported"][rep["file"]] = res
st.rerun()
except Exception as e: # noqa: BLE001
st.error(f"Import failed: {e}")