Update app.py
Browse files
app.py
CHANGED
|
@@ -1533,12 +1533,31 @@ def rag_ui():
|
|
| 1533 |
# --------------------------
|
| 1534 |
# REPORTS: PDF builders
|
| 1535 |
# --------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1536 |
def build_classification_pdf_bytes(site: dict):
|
| 1537 |
"""
|
| 1538 |
Build classification-only PDF (returns bytes)
|
| 1539 |
"""
|
| 1540 |
buf = io.BytesIO()
|
| 1541 |
-
doc = SimpleDocTemplate(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1542 |
styles = getSampleStyleSheet()
|
| 1543 |
title = ParagraphStyle("title", parent=styles["Title"], fontSize=20, textColor=THEME["accent"], alignment=1)
|
| 1544 |
h1 = ParagraphStyle("h1", parent=styles["Heading1"], fontSize=14, textColor=THEME["accent"])
|
|
@@ -1546,46 +1565,60 @@ def build_classification_pdf_bytes(site: dict):
|
|
| 1546 |
|
| 1547 |
elems = []
|
| 1548 |
elems.append(Paragraph("Classification Report — GeoMate V2", title))
|
| 1549 |
-
elems.append(Spacer(1,8))
|
| 1550 |
elems.append(Paragraph(f"Site: {site.get('Site Name','-')}", h1))
|
| 1551 |
-
elems.append(
|
|
|
|
| 1552 |
|
| 1553 |
# Inputs table
|
| 1554 |
inputs = site.get("classifier_inputs", {})
|
| 1555 |
-
|
| 1556 |
-
|
| 1557 |
-
|
| 1558 |
-
|
| 1559 |
-
|
| 1560 |
-
|
| 1561 |
-
|
| 1562 |
-
|
| 1563 |
-
|
|
|
|
|
|
|
|
|
|
| 1564 |
|
| 1565 |
# Results
|
| 1566 |
elems.append(Paragraph("Results", h1))
|
| 1567 |
elems.append(Paragraph(f"USCS: {site.get('USCS','N/A')}", body))
|
| 1568 |
elems.append(Paragraph(f"AASHTO: {site.get('AASHTO','N/A')} (GI: {site.get('GI','N/A')})", body))
|
| 1569 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1570 |
|
| 1571 |
# GSD curve inclusion if present
|
| 1572 |
gsd = site.get("GSD")
|
| 1573 |
if gsd:
|
| 1574 |
-
elems.append(
|
|
|
|
| 1575 |
elems.append(Paragraph(f"D10: {gsd.get('D10')}, D30: {gsd.get('D30')}, D60: {gsd.get('D60')}", body))
|
| 1576 |
-
# If a plot image is stored in site, include (we store last plot as /tmp/gsd_plot.png)
|
| 1577 |
gsd_img_path = "/tmp/geomate_gsd_plot.png"
|
| 1578 |
if os.path.exists(gsd_img_path):
|
| 1579 |
-
elems.append(Spacer(1,6))
|
| 1580 |
elems.append(RLImage(gsd_img_path, width=150*mm, height=80*mm))
|
| 1581 |
-
|
|
|
|
| 1582 |
elems.append(Paragraph("Decision path", h1))
|
| 1583 |
elems.append(Paragraph(site.get("classifier_decision_path","Not recorded"), body))
|
|
|
|
| 1584 |
doc.build(elems)
|
| 1585 |
pdf = buf.getvalue()
|
| 1586 |
buf.close()
|
| 1587 |
return pdf
|
| 1588 |
|
|
|
|
|
|
|
|
|
|
| 1589 |
def build_full_geotech_pdf_bytes(sites_list: list, external_refs: list):
|
| 1590 |
"""
|
| 1591 |
Build a full geotechnical report covering all selected sites.
|
|
@@ -1593,7 +1626,11 @@ def build_full_geotech_pdf_bytes(sites_list: list, external_refs: list):
|
|
| 1593 |
Returns bytes of PDF.
|
| 1594 |
"""
|
| 1595 |
buf = io.BytesIO()
|
| 1596 |
-
doc = SimpleDocTemplate(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1597 |
styles = getSampleStyleSheet()
|
| 1598 |
title = ParagraphStyle("title", parent=styles["Title"], fontSize=20, textColor=THEME["accent"], alignment=1)
|
| 1599 |
h1 = ParagraphStyle("h1", parent=styles["Heading1"], fontSize=14, textColor=THEME["accent"])
|
|
@@ -1601,65 +1638,69 @@ def build_full_geotech_pdf_bytes(sites_list: list, external_refs: list):
|
|
| 1601 |
|
| 1602 |
elems = []
|
| 1603 |
elems.append(Paragraph("Full Geotechnical Investigation Report — GeoMate V2", title))
|
| 1604 |
-
elems.append(Spacer(1,6))
|
| 1605 |
elems.append(Paragraph(f"Date: {datetime.today().strftime('%Y-%m-%d')}", body))
|
| 1606 |
-
elems.append(Spacer(1,10))
|
| 1607 |
|
| 1608 |
-
# For each site
|
| 1609 |
for s in sites_list:
|
| 1610 |
elems.append(Paragraph(f"Site: {s.get('Site Name','Unnamed')}", h1))
|
| 1611 |
-
elems.append(Paragraph(f"
|
| 1612 |
-
elems.append(Spacer(1,6))
|
| 1613 |
-
|
| 1614 |
-
|
| 1615 |
-
|
| 1616 |
-
|
| 1617 |
-
|
| 1618 |
-
|
| 1619 |
-
|
| 1620 |
-
|
| 1621 |
-
|
| 1622 |
-
|
| 1623 |
-
|
| 1624 |
-
|
| 1625 |
-
|
| 1626 |
-
|
| 1627 |
-
elems.append(
|
| 1628 |
-
|
| 1629 |
-
|
| 1630 |
-
|
|
|
|
| 1631 |
gsd = s.get("GSD")
|
| 1632 |
if gsd:
|
| 1633 |
-
elems.append(
|
| 1634 |
-
elems.append(Paragraph(
|
| 1635 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1636 |
gsd_img = "/tmp/geomate_gsd_plot.png"
|
| 1637 |
if os.path.exists(gsd_img):
|
| 1638 |
-
elems.append(Spacer(1,6))
|
| 1639 |
elems.append(RLImage(gsd_img, width=150*mm, height=80*mm))
|
| 1640 |
-
|
| 1641 |
-
|
| 1642 |
-
|
| 1643 |
-
elems.append(Paragraph("
|
| 1644 |
-
|
| 1645 |
-
|
| 1646 |
-
elems.append(Paragraph(f"Topography: {json.dumps(s.get('Topography','Not provided'))[:300]}", body))
|
| 1647 |
-
elems.append(Spacer(1,8))
|
| 1648 |
-
|
| 1649 |
-
# Recommendations (simple placeholder text derived from site properties)
|
| 1650 |
-
elems.append(Paragraph("5. Recommendations (preliminary)", body))
|
| 1651 |
-
# Basic logic to create recommendations
|
| 1652 |
-
if s.get("USCS") and s.get("USCS").startswith("C"):
|
| 1653 |
-
elems.append(Paragraph(" - Soils have clayey character; consider consolidation and settlement checks. Use stiffened raft or piles for heavy loads.", body))
|
| 1654 |
else:
|
| 1655 |
-
elems.append(Paragraph(" -
|
| 1656 |
|
| 1657 |
elems.append(PageBreak())
|
| 1658 |
|
| 1659 |
-
#
|
| 1660 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1661 |
elems.append(Paragraph("References", h1))
|
| 1662 |
-
for r in
|
| 1663 |
elems.append(Paragraph(r, body))
|
| 1664 |
|
| 1665 |
doc.build(elems)
|
|
@@ -1676,12 +1717,21 @@ def reports_ui():
|
|
| 1676 |
# Classification-only
|
| 1677 |
st.subheader("Classification-only report")
|
| 1678 |
sites = ss.get("sites", [])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1679 |
site_names = [s.get("Site Name","Unnamed") for s in sites]
|
| 1680 |
sel_cls = st.selectbox("Select site", site_names, index=ss.get("active_site_idx",0))
|
| 1681 |
if st.button("Generate Classification PDF"):
|
| 1682 |
site = ss["sites"][site_names.index(sel_cls)]
|
| 1683 |
pdf_bytes = build_classification_pdf_bytes(site)
|
| 1684 |
-
st.download_button(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1685 |
|
| 1686 |
st.markdown("---")
|
| 1687 |
|
|
@@ -1697,8 +1747,13 @@ def reports_ui():
|
|
| 1697 |
ext_refs = [l.strip() for l in ext_refs_text.splitlines() if l.strip()]
|
| 1698 |
with st.spinner("Building PDF (this may take a few seconds)..."):
|
| 1699 |
pdf_bytes = build_full_geotech_pdf_bytes(chosen_sites, ext_refs)
|
| 1700 |
-
st.download_button(
|
| 1701 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1702 |
# --------------------------
|
| 1703 |
# Final UI main function (glue)
|
| 1704 |
# --------------------------
|
|
|
|
| 1533 |
# --------------------------
|
| 1534 |
# REPORTS: PDF builders
|
| 1535 |
# --------------------------
|
| 1536 |
+
import io, os, json
|
| 1537 |
+
from datetime import datetime
|
| 1538 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak, Image as RLImage
|
| 1539 |
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 1540 |
+
from reportlab.lib.pagesizes import A4
|
| 1541 |
+
from reportlab.lib.units import mm
|
| 1542 |
+
from reportlab.lib import colors
|
| 1543 |
+
import streamlit as st
|
| 1544 |
+
|
| 1545 |
+
# Theme colors
|
| 1546 |
+
THEME = {"accent": colors.HexColor("#FF6600")}
|
| 1547 |
+
|
| 1548 |
+
# --------------------------
|
| 1549 |
+
# CLASSIFICATION REPORT BUILDER
|
| 1550 |
+
# --------------------------
|
| 1551 |
def build_classification_pdf_bytes(site: dict):
|
| 1552 |
"""
|
| 1553 |
Build classification-only PDF (returns bytes)
|
| 1554 |
"""
|
| 1555 |
buf = io.BytesIO()
|
| 1556 |
+
doc = SimpleDocTemplate(
|
| 1557 |
+
buf, pagesize=A4,
|
| 1558 |
+
leftMargin=20*mm, rightMargin=20*mm,
|
| 1559 |
+
topMargin=20*mm, bottomMargin=20*mm
|
| 1560 |
+
)
|
| 1561 |
styles = getSampleStyleSheet()
|
| 1562 |
title = ParagraphStyle("title", parent=styles["Title"], fontSize=20, textColor=THEME["accent"], alignment=1)
|
| 1563 |
h1 = ParagraphStyle("h1", parent=styles["Heading1"], fontSize=14, textColor=THEME["accent"])
|
|
|
|
| 1565 |
|
| 1566 |
elems = []
|
| 1567 |
elems.append(Paragraph("Classification Report — GeoMate V2", title))
|
| 1568 |
+
elems.append(Spacer(1, 8))
|
| 1569 |
elems.append(Paragraph(f"Site: {site.get('Site Name','-')}", h1))
|
| 1570 |
+
elems.append(Paragraph(f"Coordinates: {site.get('Coordinates','-')}", body))
|
| 1571 |
+
elems.append(Spacer(1, 6))
|
| 1572 |
|
| 1573 |
# Inputs table
|
| 1574 |
inputs = site.get("classifier_inputs", {})
|
| 1575 |
+
if inputs:
|
| 1576 |
+
data = [["Parameter", "Value"]]
|
| 1577 |
+
for k, v in inputs.items():
|
| 1578 |
+
data.append([k, str(v)])
|
| 1579 |
+
t = Table(data, colWidths=[80*mm, 80*mm])
|
| 1580 |
+
t.setStyle(TableStyle([
|
| 1581 |
+
("GRID", (0,0), (-1,-1), 0.5, colors.grey),
|
| 1582 |
+
("BACKGROUND", (0,0), (-1,0), THEME["accent"]),
|
| 1583 |
+
("TEXTCOLOR", (0,0), (-1,0), colors.white)
|
| 1584 |
+
]))
|
| 1585 |
+
elems.append(t)
|
| 1586 |
+
elems.append(Spacer(1, 8))
|
| 1587 |
|
| 1588 |
# Results
|
| 1589 |
elems.append(Paragraph("Results", h1))
|
| 1590 |
elems.append(Paragraph(f"USCS: {site.get('USCS','N/A')}", body))
|
| 1591 |
elems.append(Paragraph(f"AASHTO: {site.get('AASHTO','N/A')} (GI: {site.get('GI','N/A')})", body))
|
| 1592 |
+
|
| 1593 |
+
# OCR data inclusion
|
| 1594 |
+
if site.get("ocr_text"):
|
| 1595 |
+
elems.append(Spacer(1, 8))
|
| 1596 |
+
elems.append(Paragraph("OCR Extracted Notes", h1))
|
| 1597 |
+
elems.append(Paragraph(site.get("ocr_text","No OCR data found."), body))
|
| 1598 |
|
| 1599 |
# GSD curve inclusion if present
|
| 1600 |
gsd = site.get("GSD")
|
| 1601 |
if gsd:
|
| 1602 |
+
elems.append(Spacer(1, 8))
|
| 1603 |
+
elems.append(Paragraph("Grain Size Distribution (GSD)", h1))
|
| 1604 |
elems.append(Paragraph(f"D10: {gsd.get('D10')}, D30: {gsd.get('D30')}, D60: {gsd.get('D60')}", body))
|
|
|
|
| 1605 |
gsd_img_path = "/tmp/geomate_gsd_plot.png"
|
| 1606 |
if os.path.exists(gsd_img_path):
|
| 1607 |
+
elems.append(Spacer(1, 6))
|
| 1608 |
elems.append(RLImage(gsd_img_path, width=150*mm, height=80*mm))
|
| 1609 |
+
|
| 1610 |
+
elems.append(Spacer(1, 10))
|
| 1611 |
elems.append(Paragraph("Decision path", h1))
|
| 1612 |
elems.append(Paragraph(site.get("classifier_decision_path","Not recorded"), body))
|
| 1613 |
+
|
| 1614 |
doc.build(elems)
|
| 1615 |
pdf = buf.getvalue()
|
| 1616 |
buf.close()
|
| 1617 |
return pdf
|
| 1618 |
|
| 1619 |
+
# --------------------------
|
| 1620 |
+
# FULL REPORT BUILDER
|
| 1621 |
+
# --------------------------
|
| 1622 |
def build_full_geotech_pdf_bytes(sites_list: list, external_refs: list):
|
| 1623 |
"""
|
| 1624 |
Build a full geotechnical report covering all selected sites.
|
|
|
|
| 1626 |
Returns bytes of PDF.
|
| 1627 |
"""
|
| 1628 |
buf = io.BytesIO()
|
| 1629 |
+
doc = SimpleDocTemplate(
|
| 1630 |
+
buf, pagesize=A4,
|
| 1631 |
+
leftMargin=20*mm, rightMargin=20*mm,
|
| 1632 |
+
topMargin=20*mm, bottomMargin=20*mm
|
| 1633 |
+
)
|
| 1634 |
styles = getSampleStyleSheet()
|
| 1635 |
title = ParagraphStyle("title", parent=styles["Title"], fontSize=20, textColor=THEME["accent"], alignment=1)
|
| 1636 |
h1 = ParagraphStyle("h1", parent=styles["Heading1"], fontSize=14, textColor=THEME["accent"])
|
|
|
|
| 1638 |
|
| 1639 |
elems = []
|
| 1640 |
elems.append(Paragraph("Full Geotechnical Investigation Report — GeoMate V2", title))
|
| 1641 |
+
elems.append(Spacer(1, 6))
|
| 1642 |
elems.append(Paragraph(f"Date: {datetime.today().strftime('%Y-%m-%d')}", body))
|
| 1643 |
+
elems.append(Spacer(1, 10))
|
| 1644 |
|
| 1645 |
+
# For each site
|
| 1646 |
for s in sites_list:
|
| 1647 |
elems.append(Paragraph(f"Site: {s.get('Site Name','Unnamed')}", h1))
|
| 1648 |
+
elems.append(Paragraph(f"Coordinates: {s.get('Coordinates','Not provided')}", body))
|
| 1649 |
+
elems.append(Spacer(1, 6))
|
| 1650 |
+
|
| 1651 |
+
# OCR notes
|
| 1652 |
+
if s.get("ocr_text"):
|
| 1653 |
+
elems.append(Paragraph("OCR Extracted Notes", h1))
|
| 1654 |
+
elems.append(Paragraph(s.get("ocr_text"), body))
|
| 1655 |
+
elems.append(Spacer(1, 6))
|
| 1656 |
+
|
| 1657 |
+
# Classification
|
| 1658 |
+
elems.append(Paragraph("Classification", h1))
|
| 1659 |
+
elems.append(Paragraph(f"USCS: {s.get('USCS','N/A')}", body))
|
| 1660 |
+
elems.append(Paragraph(f"AASHTO: {s.get('AASHTO','N/A')} (GI: {s.get('GI','N/A')})", body))
|
| 1661 |
+
|
| 1662 |
+
# Earth Engine / Map snapshots
|
| 1663 |
+
if s.get("map_snapshot") and os.path.exists(s["map_snapshot"]):
|
| 1664 |
+
elems.append(Spacer(1, 6))
|
| 1665 |
+
elems.append(Paragraph("Site Map Snapshot", h1))
|
| 1666 |
+
elems.append(RLImage(s["map_snapshot"], width=140*mm, height=80*mm))
|
| 1667 |
+
|
| 1668 |
+
# GSD
|
| 1669 |
gsd = s.get("GSD")
|
| 1670 |
if gsd:
|
| 1671 |
+
elems.append(Spacer(1, 6))
|
| 1672 |
+
elems.append(Paragraph("Grain Size Distribution", h1))
|
| 1673 |
+
elems.append(Paragraph(
|
| 1674 |
+
f"D10: {gsd.get('D10')}, D30: {gsd.get('D30')}, "
|
| 1675 |
+
f"D60: {gsd.get('D60')}, Cu: {gsd.get('Cu')}, Cc: {gsd.get('Cc')}",
|
| 1676 |
+
body
|
| 1677 |
+
))
|
| 1678 |
gsd_img = "/tmp/geomate_gsd_plot.png"
|
| 1679 |
if os.path.exists(gsd_img):
|
| 1680 |
+
elems.append(Spacer(1, 6))
|
| 1681 |
elems.append(RLImage(gsd_img, width=150*mm, height=80*mm))
|
| 1682 |
+
|
| 1683 |
+
# Recommendations (basic rules from classification)
|
| 1684 |
+
elems.append(Spacer(1, 8))
|
| 1685 |
+
elems.append(Paragraph("Recommendations", h1))
|
| 1686 |
+
if s.get("USCS") and s["USCS"].startswith("C"):
|
| 1687 |
+
elems.append(Paragraph(" - Clayey soils: check consolidation/settlement. Consider raft or pile foundations.", body))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1688 |
else:
|
| 1689 |
+
elems.append(Paragraph(" - Granular soils: shallow foundations possible with compaction and drainage.", body))
|
| 1690 |
|
| 1691 |
elems.append(PageBreak())
|
| 1692 |
|
| 1693 |
+
# References (FAISS RAG + manual)
|
| 1694 |
+
refs = []
|
| 1695 |
+
for s in sites_list:
|
| 1696 |
+
if s.get("rag_sources"):
|
| 1697 |
+
refs.extend(s["rag_sources"])
|
| 1698 |
+
refs = list(set(refs)) # remove duplicates
|
| 1699 |
+
refs.extend(external_refs)
|
| 1700 |
+
|
| 1701 |
+
if refs:
|
| 1702 |
elems.append(Paragraph("References", h1))
|
| 1703 |
+
for r in refs:
|
| 1704 |
elems.append(Paragraph(r, body))
|
| 1705 |
|
| 1706 |
doc.build(elems)
|
|
|
|
| 1717 |
# Classification-only
|
| 1718 |
st.subheader("Classification-only report")
|
| 1719 |
sites = ss.get("sites", [])
|
| 1720 |
+
if not sites:
|
| 1721 |
+
st.warning("No sites available.")
|
| 1722 |
+
return
|
| 1723 |
+
|
| 1724 |
site_names = [s.get("Site Name","Unnamed") for s in sites]
|
| 1725 |
sel_cls = st.selectbox("Select site", site_names, index=ss.get("active_site_idx",0))
|
| 1726 |
if st.button("Generate Classification PDF"):
|
| 1727 |
site = ss["sites"][site_names.index(sel_cls)]
|
| 1728 |
pdf_bytes = build_classification_pdf_bytes(site)
|
| 1729 |
+
st.download_button(
|
| 1730 |
+
"Download Classification PDF",
|
| 1731 |
+
data=pdf_bytes,
|
| 1732 |
+
file_name=f"classification_{sel_cls}.pdf",
|
| 1733 |
+
mime="application/pdf"
|
| 1734 |
+
)
|
| 1735 |
|
| 1736 |
st.markdown("---")
|
| 1737 |
|
|
|
|
| 1747 |
ext_refs = [l.strip() for l in ext_refs_text.splitlines() if l.strip()]
|
| 1748 |
with st.spinner("Building PDF (this may take a few seconds)..."):
|
| 1749 |
pdf_bytes = build_full_geotech_pdf_bytes(chosen_sites, ext_refs)
|
| 1750 |
+
st.download_button(
|
| 1751 |
+
"Download Full Geotechnical Report",
|
| 1752 |
+
data=pdf_bytes,
|
| 1753 |
+
file_name="geomate_full_report.pdf",
|
| 1754 |
+
mime="application/pdf"
|
| 1755 |
+
)
|
| 1756 |
+
|
| 1757 |
# --------------------------
|
| 1758 |
# Final UI main function (glue)
|
| 1759 |
# --------------------------
|