Update app.py
Browse files
app.py
CHANGED
|
@@ -1864,16 +1864,201 @@ REPORT_FIELDS = [
|
|
| 1864 |
("Allowable Bearing (kPa)", "kPa"),
|
| 1865 |
]
|
| 1866 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1867 |
def reports_page():
|
| 1868 |
st.header("📑 Reports — Classification & Full Geotechnical")
|
| 1869 |
site = st.session_state["sites"][st.session_state["active_site"]]
|
| 1870 |
|
|
|
|
| 1871 |
st.subheader("Classification-only report")
|
| 1872 |
if site.get("classifier_decision"):
|
| 1873 |
st.markdown("You have a saved classification for this site.")
|
| 1874 |
if st.button("Generate Classification PDF"):
|
| 1875 |
fname = f"classification_{site['Site Name'].replace(' ','_')}.pdf"
|
| 1876 |
-
# simple PDF
|
| 1877 |
buffer = io.BytesIO()
|
| 1878 |
doc = SimpleDocTemplate(buffer, pagesize=A4)
|
| 1879 |
elems = []
|
|
@@ -1883,25 +2068,34 @@ def reports_page():
|
|
| 1883 |
elems.append(Spacer(1,6))
|
| 1884 |
elems.append(Paragraph("Classification result:", getSampleStyleSheet()['Heading2']))
|
| 1885 |
elems.append(Paragraph(site.get("classifier_decision","-"), getSampleStyleSheet()['BodyText']))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1886 |
doc.build(elems)
|
| 1887 |
buffer.seek(0)
|
| 1888 |
st.download_button("Download Classification PDF", buffer, file_name=fname, mime="application/pdf")
|
| 1889 |
else:
|
| 1890 |
st.info("No classification saved for this site yet. Use the Classifier page.")
|
| 1891 |
|
| 1892 |
-
#
|
| 1893 |
st.markdown("### Quick report form (edit values and request LLM analysis)")
|
| 1894 |
-
site = st.session_state["sites"][st.session_state["active_site"]]
|
| 1895 |
-
|
| 1896 |
-
# Build form-style table
|
| 1897 |
with st.form(key="report_quick_form"):
|
| 1898 |
-
cols = st.columns([2,1,1])
|
| 1899 |
-
# header row visually
|
| 1900 |
cols[0].markdown("**Parameter**")
|
| 1901 |
cols[1].markdown("**Value**")
|
| 1902 |
cols[2].markdown("**Unit / Notes**")
|
| 1903 |
|
| 1904 |
-
# build inputs dynamically from REPORT_FIELDS
|
| 1905 |
inputs = {}
|
| 1906 |
for (fld, unit) in REPORT_FIELDS:
|
| 1907 |
c1, c2, c3 = st.columns([2,1,1])
|
|
@@ -1917,10 +2111,9 @@ def reports_page():
|
|
| 1917 |
site[fld] = val if val != "" else "Not provided"
|
| 1918 |
st.success("Saved quick report values to active site.")
|
| 1919 |
|
| 1920 |
-
# LLM
|
| 1921 |
st.markdown("#### LLM-powered analysis")
|
| 1922 |
if st.button("Ask GeoMate (generate analysis & recommendations)"):
|
| 1923 |
-
# prepare context for the LLM from the site
|
| 1924 |
context = {
|
| 1925 |
"site_name": site.get("Site Name"),
|
| 1926 |
"project": site.get("Project Name"),
|
|
@@ -1938,58 +2131,45 @@ def reports_page():
|
|
| 1938 |
"options and 4) short design notes. Provide any numeric outputs in the format [[FIELD: value unit]].\n\n"
|
| 1939 |
f"Context: {json.dumps(context)}\n\nAnswer concisely and professionally."
|
| 1940 |
)
|
| 1941 |
-
# NOTE: Assuming groq_generate is a function you have defined elsewhere
|
| 1942 |
# resp = groq_generate(prompt, model=st.session_state["llm_model"], max_tokens=600)
|
| 1943 |
-
resp = "This is a placeholder response
|
| 1944 |
-
|
| 1945 |
-
# display
|
| 1946 |
st.markdown("**GeoMate analysis**")
|
| 1947 |
st.markdown(resp)
|
| 1948 |
-
|
| 1949 |
matches = re.findall(r"\[\[([A-Za-z0-9 _/-]+):\s*([0-9.+-eE]+)\s*([A-Za-z%\/]*)\]\]", resp)
|
| 1950 |
for m in matches:
|
| 1951 |
-
field = m[0].strip()
|
| 1952 |
-
val = m[1].strip()
|
| 1953 |
-
unit = m[2].strip()
|
| 1954 |
-
# map likely field names:
|
| 1955 |
if "bearing" in field.lower():
|
| 1956 |
site["Load Bearing Capacity"] = f"{val} {unit}"
|
| 1957 |
elif "skin" in field.lower():
|
| 1958 |
site["Skin Shear Strength"] = f"{val} {unit}"
|
| 1959 |
elif "compaction" in field.lower():
|
| 1960 |
site["Relative Compaction"] = f"{val} {unit}"
|
| 1961 |
-
|
| 1962 |
site["LLM_Report_Text"] = resp
|
| 1963 |
st.success("LLM analysis saved to site under 'LLM_Report_Text'.")
|
| 1964 |
|
|
|
|
| 1965 |
st.markdown("---")
|
| 1966 |
st.subheader("Full Geotechnical Report (chatbot will gather missing fields)")
|
| 1967 |
if st.button("Start Report Chatbot"):
|
| 1968 |
st.session_state["sites"][st.session_state["active_site"]]["report_convo_state"] = 0
|
| 1969 |
st.rerun()
|
| 1970 |
|
| 1971 |
-
# Conversational data collection
|
| 1972 |
state = site.get("report_convo_state", -1)
|
| 1973 |
if state >= 0:
|
| 1974 |
st.markdown("Chatbot will ask for missing fields. You can answer or type 'skip' to leave blank.")
|
| 1975 |
-
|
| 1976 |
-
st.write("Current key parameters (live):")
|
| 1977 |
-
show_table = []
|
| 1978 |
-
for f,_ in REPORT_FIELDS:
|
| 1979 |
-
show_table.append((f, site.get(f, "Not provided")))
|
| 1980 |
st.table(show_table)
|
| 1981 |
-
|
| 1982 |
-
# continue conversation step-by-step
|
| 1983 |
if state < len(REPORT_FIELDS):
|
| 1984 |
field, unit = REPORT_FIELDS[state]
|
| 1985 |
ans = st.text_input(f"GeoMate — Please provide '{field}' ({unit})", key=f"report_in_{state}")
|
| 1986 |
c1, c2 = st.columns([1,1])
|
| 1987 |
with c1:
|
| 1988 |
if st.button("Submit", key=f"report_submit_{state}"):
|
| 1989 |
-
|
| 1990 |
-
site[field] = "Not provided"
|
| 1991 |
-
else:
|
| 1992 |
-
site[field] = ans.strip()
|
| 1993 |
site["report_convo_state"] = state + 1
|
| 1994 |
st.rerun()
|
| 1995 |
with c2:
|
|
@@ -1999,20 +2179,25 @@ def reports_page():
|
|
| 1999 |
st.rerun()
|
| 2000 |
else:
|
| 2001 |
st.success("All report questions asked. You can generate the full report now.")
|
| 2002 |
-
|
| 2003 |
-
|
| 2004 |
-
|
| 2005 |
-
|
| 2006 |
-
|
| 2007 |
-
|
| 2008 |
-
|
| 2009 |
-
|
| 2010 |
-
|
| 2011 |
-
|
| 2012 |
-
|
| 2013 |
-
|
| 2014 |
-
|
|
|
|
|
|
|
| 2015 |
|
|
|
|
|
|
|
|
|
|
| 2016 |
# 8) Page router
|
| 2017 |
if "page" not in st.session_state:
|
| 2018 |
st.session_state["page"] = "Home"
|
|
|
|
| 1864 |
("Allowable Bearing (kPa)", "kPa"),
|
| 1865 |
]
|
| 1866 |
|
| 1867 |
+
# -------------------------------
|
| 1868 |
+
# Imports
|
| 1869 |
+
# -------------------------------
|
| 1870 |
+
import io, re, json, tempfile
|
| 1871 |
+
from datetime import datetime
|
| 1872 |
+
from typing import Dict, Any, Optional, List
|
| 1873 |
+
|
| 1874 |
+
import streamlit as st
|
| 1875 |
+
from reportlab.platypus import (
|
| 1876 |
+
SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle, Image as RLImage
|
| 1877 |
+
)
|
| 1878 |
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 1879 |
+
from reportlab.lib import colors
|
| 1880 |
+
from reportlab.lib.pagesizes import A4
|
| 1881 |
+
from reportlab.lib.units import mm
|
| 1882 |
+
|
| 1883 |
+
# -------------------------------
|
| 1884 |
+
# PDF Builder
|
| 1885 |
+
# -------------------------------
|
| 1886 |
+
def build_full_geotech_pdf(
|
| 1887 |
+
site: Dict[str, Any],
|
| 1888 |
+
filename: str,
|
| 1889 |
+
include_map_image: Optional[bytes] = None,
|
| 1890 |
+
ext_refs: Optional[List[str]] = None
|
| 1891 |
+
):
|
| 1892 |
+
"""
|
| 1893 |
+
Build a professional PDF report using site data + references.
|
| 1894 |
+
"""
|
| 1895 |
+
styles = getSampleStyleSheet()
|
| 1896 |
+
title_style = ParagraphStyle(
|
| 1897 |
+
"title", parent=styles["Title"], fontSize=20, alignment=1,
|
| 1898 |
+
textColor=colors.HexColor("#FF7A00")
|
| 1899 |
+
)
|
| 1900 |
+
h1 = ParagraphStyle(
|
| 1901 |
+
"h1", parent=styles["Heading1"], fontSize=14,
|
| 1902 |
+
textColor=colors.HexColor("#1F4E79"), spaceAfter=6
|
| 1903 |
+
)
|
| 1904 |
+
body = ParagraphStyle("body", parent=styles["BodyText"], fontSize=10.5, leading=13)
|
| 1905 |
+
bullet = ParagraphStyle("bullet", parent=body, leftIndent=12, bulletIndent=6)
|
| 1906 |
+
|
| 1907 |
+
doc = SimpleDocTemplate(
|
| 1908 |
+
filename, pagesize=A4,
|
| 1909 |
+
leftMargin=18*mm, rightMargin=18*mm,
|
| 1910 |
+
topMargin=18*mm, bottomMargin=18*mm
|
| 1911 |
+
)
|
| 1912 |
+
elems = []
|
| 1913 |
+
|
| 1914 |
+
# --- Title page ---
|
| 1915 |
+
elems.append(Paragraph("GEOTECHNICAL INVESTIGATION REPORT", title_style))
|
| 1916 |
+
elems.append(Spacer(1, 12))
|
| 1917 |
+
company = site.get("Company Name", "Client / Company: Not provided")
|
| 1918 |
+
contact = site.get("Company Contact", "")
|
| 1919 |
+
elems.append(Paragraph(f"<b>{company}</b>", body))
|
| 1920 |
+
if contact:
|
| 1921 |
+
elems.append(Paragraph(contact, body))
|
| 1922 |
+
elems.append(Spacer(1, 12))
|
| 1923 |
+
elems.append(Paragraph(f"<b>Project:</b> {site.get('Project Name','-')}", body))
|
| 1924 |
+
elems.append(Paragraph(f"<b>Site:</b> {site.get('Site Name','-')}", body))
|
| 1925 |
+
elems.append(Paragraph(f"<b>Date:</b> {datetime.today().strftime('%Y-%m-%d')}", body))
|
| 1926 |
+
elems.append(PageBreak())
|
| 1927 |
+
|
| 1928 |
+
# --- Table of contents ---
|
| 1929 |
+
elems.append(Paragraph("TABLE OF CONTENTS", h1))
|
| 1930 |
+
toc_items = [
|
| 1931 |
+
"1.0 Introduction",
|
| 1932 |
+
"2.0 Site description and geology",
|
| 1933 |
+
"3.0 Field investigation & laboratory testing",
|
| 1934 |
+
"4.0 Evaluation of geotechnical properties",
|
| 1935 |
+
"5.0 Provisional site classification",
|
| 1936 |
+
"6.0 Recommendations",
|
| 1937 |
+
"7.0 LLM Analysis",
|
| 1938 |
+
"8.0 Figures & Tables",
|
| 1939 |
+
"9.0 Appendices & References"
|
| 1940 |
+
]
|
| 1941 |
+
for i, t in enumerate(toc_items, start=1):
|
| 1942 |
+
elems.append(Paragraph(f"{i}. {t}", body))
|
| 1943 |
+
elems.append(PageBreak())
|
| 1944 |
+
|
| 1945 |
+
# --- Summary ---
|
| 1946 |
+
elems.append(Paragraph("SUMMARY", h1))
|
| 1947 |
+
summary_bullets = [
|
| 1948 |
+
f"Site: {site.get('Site Name','-')}.",
|
| 1949 |
+
f"General geology: {site.get('Soil Profile','Not provided')}.",
|
| 1950 |
+
f"Key lab tests: {', '.join([r.get('sampleId','') for r in site.get('Laboratory Results',[])]) if site.get('Laboratory Results') else 'No lab results provided.'}",
|
| 1951 |
+
f"Classification: USCS = {site.get('USCS','Not provided')}; AASHTO = {site.get('AASHTO','Not provided')}.",
|
| 1952 |
+
"Primary recommendation: See Recommendations section."
|
| 1953 |
+
]
|
| 1954 |
+
for s in summary_bullets:
|
| 1955 |
+
elems.append(Paragraph(f"• {s}", bullet))
|
| 1956 |
+
elems.append(PageBreak())
|
| 1957 |
+
|
| 1958 |
+
# --- Introduction ---
|
| 1959 |
+
elems.append(Paragraph("1.0 INTRODUCTION", h1))
|
| 1960 |
+
intro_text = site.get("Project Description", "Project description not provided.")
|
| 1961 |
+
elems.append(Paragraph(intro_text, body))
|
| 1962 |
+
|
| 1963 |
+
# --- Site description & geology ---
|
| 1964 |
+
elems.append(Paragraph("2.0 SITE DESCRIPTION AND GEOLOGY", h1))
|
| 1965 |
+
site_geo = [
|
| 1966 |
+
f"Topography: {site.get('Topography','Not provided')}",
|
| 1967 |
+
f"Drainage: {site.get('Drainage','Not provided')}",
|
| 1968 |
+
f"Current land use: {site.get('Current Land Use','Not provided')}",
|
| 1969 |
+
f"Regional geology: {site.get('Regional Geology','Not provided')}"
|
| 1970 |
+
]
|
| 1971 |
+
for t in site_geo:
|
| 1972 |
+
elems.append(Paragraph(t, body))
|
| 1973 |
+
elems.append(PageBreak())
|
| 1974 |
+
|
| 1975 |
+
# --- Field & lab testing ---
|
| 1976 |
+
elems.append(Paragraph("3.0 FIELD INVESTIGATION & LABORATORY TESTING", h1))
|
| 1977 |
+
if site.get("Field Investigation"):
|
| 1978 |
+
for item in site["Field Investigation"]:
|
| 1979 |
+
elems.append(Paragraph(f"<b>{item.get('id','Test')}</b> — depth {item.get('depth','-')}", body))
|
| 1980 |
+
for layer in item.get("layers", []):
|
| 1981 |
+
elems.append(Paragraph(f"- {layer.get('depth','')} : {layer.get('description','')}", body))
|
| 1982 |
+
else:
|
| 1983 |
+
elems.append(Paragraph("No field investigation data supplied.", body))
|
| 1984 |
+
|
| 1985 |
+
lab_rows = site.get("Laboratory Results", [])
|
| 1986 |
+
if lab_rows:
|
| 1987 |
+
elems.append(Spacer(1, 6))
|
| 1988 |
+
elems.append(Paragraph("Laboratory Results", h1))
|
| 1989 |
+
data = [["Sample ID","Material","LL","PI","Linear Shrinkage","%Clay","%Silt","%Sand","%Gravel","Expansiveness"]]
|
| 1990 |
+
for r in lab_rows:
|
| 1991 |
+
data.append([
|
| 1992 |
+
r.get("sampleId","-"), r.get("material","-"),
|
| 1993 |
+
str(r.get("liquidLimit","-")), str(r.get("plasticityIndex","-")),
|
| 1994 |
+
str(r.get("linearShrinkage","-")), str(r.get("percentClay","-")),
|
| 1995 |
+
str(r.get("percentSilt","-")), str(r.get("percentSand","-")),
|
| 1996 |
+
str(r.get("percentGravel","-")), r.get("potentialExpansiveness","-")
|
| 1997 |
+
])
|
| 1998 |
+
t = Table(data, repeatRows=1, colWidths=[40*mm,40*mm,18*mm,18*mm,22*mm,20*mm,20*mm,20*mm,20*mm,30*mm])
|
| 1999 |
+
t.setStyle(TableStyle([
|
| 2000 |
+
('BACKGROUND',(0,0),(-1,0),colors.HexColor("#1F4E79")),
|
| 2001 |
+
('TEXTCOLOR',(0,0),(-1,0),colors.white),
|
| 2002 |
+
('GRID',(0,0),(-1,-1),0.4,colors.grey),
|
| 2003 |
+
('BOX',(0,0),(-1,-1),1,colors.HexColor("#FF7A00"))
|
| 2004 |
+
]))
|
| 2005 |
+
elems.append(t)
|
| 2006 |
+
elems.append(PageBreak())
|
| 2007 |
+
|
| 2008 |
+
# --- Evaluation & classification ---
|
| 2009 |
+
elems.append(Paragraph("4.0 EVALUATION OF GEOTECHNICAL PROPERTIES", h1))
|
| 2010 |
+
elems.append(Paragraph(site.get("Evaluation","Evaluation not provided."), body))
|
| 2011 |
+
elems.append(Paragraph("5.0 PROVISIONAL SITE CLASSIFICATION", h1))
|
| 2012 |
+
elems.append(Paragraph(site.get("Provisional Classification","Not provided."), body))
|
| 2013 |
+
elems.append(Paragraph("6.0 RECOMMENDATIONS", h1))
|
| 2014 |
+
elems.append(Paragraph(site.get("Recommendations","Not provided."), body))
|
| 2015 |
+
|
| 2016 |
+
# --- LLM Analysis ---
|
| 2017 |
+
elems.append(Paragraph("7.0 LLM ANALYSIS (GeoMate)", h1))
|
| 2018 |
+
llm_text = site.get("LLM_Report_Text", None)
|
| 2019 |
+
if llm_text:
|
| 2020 |
+
elems.append(Paragraph(llm_text.replace("\n","\n\n"), body))
|
| 2021 |
+
else:
|
| 2022 |
+
elems.append(Paragraph("No LLM analysis saved for this site.", body))
|
| 2023 |
+
|
| 2024 |
+
# --- Map snapshot ---
|
| 2025 |
+
if include_map_image:
|
| 2026 |
+
try:
|
| 2027 |
+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
|
| 2028 |
+
tmp.write(include_map_image)
|
| 2029 |
+
tmp.flush()
|
| 2030 |
+
elems.append(PageBreak())
|
| 2031 |
+
elems.append(Paragraph("Map Snapshot", h1))
|
| 2032 |
+
elems.append(RLImage(tmp.name, width=160*mm, height=90*mm))
|
| 2033 |
+
except Exception:
|
| 2034 |
+
pass
|
| 2035 |
+
|
| 2036 |
+
# --- References ---
|
| 2037 |
+
elems.append(PageBreak())
|
| 2038 |
+
elems.append(Paragraph("9.0 APPENDICES & REFERENCES", h1))
|
| 2039 |
+
if ext_refs:
|
| 2040 |
+
for r in ext_refs:
|
| 2041 |
+
elems.append(Paragraph(f"- {r}", body))
|
| 2042 |
+
else:
|
| 2043 |
+
elems.append(Paragraph("- No external references provided.", body))
|
| 2044 |
+
|
| 2045 |
+
doc.build(elems)
|
| 2046 |
+
return filename
|
| 2047 |
+
|
| 2048 |
+
|
| 2049 |
+
# -------------------------------
|
| 2050 |
+
# Reports Page
|
| 2051 |
+
# -------------------------------
|
| 2052 |
def reports_page():
|
| 2053 |
st.header("📑 Reports — Classification & Full Geotechnical")
|
| 2054 |
site = st.session_state["sites"][st.session_state["active_site"]]
|
| 2055 |
|
| 2056 |
+
# ---------------- Classification Report ----------------
|
| 2057 |
st.subheader("Classification-only report")
|
| 2058 |
if site.get("classifier_decision"):
|
| 2059 |
st.markdown("You have a saved classification for this site.")
|
| 2060 |
if st.button("Generate Classification PDF"):
|
| 2061 |
fname = f"classification_{site['Site Name'].replace(' ','_')}.pdf"
|
|
|
|
| 2062 |
buffer = io.BytesIO()
|
| 2063 |
doc = SimpleDocTemplate(buffer, pagesize=A4)
|
| 2064 |
elems = []
|
|
|
|
| 2068 |
elems.append(Spacer(1,6))
|
| 2069 |
elems.append(Paragraph("Classification result:", getSampleStyleSheet()['Heading2']))
|
| 2070 |
elems.append(Paragraph(site.get("classifier_decision","-"), getSampleStyleSheet()['BodyText']))
|
| 2071 |
+
|
| 2072 |
+
# Add FAISS citations if present in rag_history
|
| 2073 |
+
if "rag_history" in st.session_state and site.get("Site ID") in st.session_state["rag_history"]:
|
| 2074 |
+
refs = []
|
| 2075 |
+
for h in st.session_state["rag_history"][site["Site ID"]]:
|
| 2076 |
+
if h["who"]=="bot" and "[ref:" in h["text"]:
|
| 2077 |
+
for m in re.findall(r"\[ref:([^\]]+)\]", h["text"]):
|
| 2078 |
+
refs.append(m)
|
| 2079 |
+
if refs:
|
| 2080 |
+
elems.append(Spacer(1,12))
|
| 2081 |
+
elems.append(Paragraph("References:", getSampleStyleSheet()['Heading2']))
|
| 2082 |
+
for r in set(refs):
|
| 2083 |
+
elems.append(Paragraph(f"- {r}", getSampleStyleSheet()['Normal']))
|
| 2084 |
+
|
| 2085 |
doc.build(elems)
|
| 2086 |
buffer.seek(0)
|
| 2087 |
st.download_button("Download Classification PDF", buffer, file_name=fname, mime="application/pdf")
|
| 2088 |
else:
|
| 2089 |
st.info("No classification saved for this site yet. Use the Classifier page.")
|
| 2090 |
|
| 2091 |
+
# ---------------- Quick Report Form ----------------
|
| 2092 |
st.markdown("### Quick report form (edit values and request LLM analysis)")
|
|
|
|
|
|
|
|
|
|
| 2093 |
with st.form(key="report_quick_form"):
|
| 2094 |
+
cols = st.columns([2,1,1])
|
|
|
|
| 2095 |
cols[0].markdown("**Parameter**")
|
| 2096 |
cols[1].markdown("**Value**")
|
| 2097 |
cols[2].markdown("**Unit / Notes**")
|
| 2098 |
|
|
|
|
| 2099 |
inputs = {}
|
| 2100 |
for (fld, unit) in REPORT_FIELDS:
|
| 2101 |
c1, c2, c3 = st.columns([2,1,1])
|
|
|
|
| 2111 |
site[fld] = val if val != "" else "Not provided"
|
| 2112 |
st.success("Saved quick report values to active site.")
|
| 2113 |
|
| 2114 |
+
# ---------------- LLM Analysis ----------------
|
| 2115 |
st.markdown("#### LLM-powered analysis")
|
| 2116 |
if st.button("Ask GeoMate (generate analysis & recommendations)"):
|
|
|
|
| 2117 |
context = {
|
| 2118 |
"site_name": site.get("Site Name"),
|
| 2119 |
"project": site.get("Project Name"),
|
|
|
|
| 2131 |
"options and 4) short design notes. Provide any numeric outputs in the format [[FIELD: value unit]].\n\n"
|
| 2132 |
f"Context: {json.dumps(context)}\n\nAnswer concisely and professionally."
|
| 2133 |
)
|
|
|
|
| 2134 |
# resp = groq_generate(prompt, model=st.session_state["llm_model"], max_tokens=600)
|
| 2135 |
+
resp = "This is a placeholder response with citations [ref:Soil_Manual_2020] [[Load Bearing Capacity: 180 kPa]]"
|
| 2136 |
+
|
|
|
|
| 2137 |
st.markdown("**GeoMate analysis**")
|
| 2138 |
st.markdown(resp)
|
| 2139 |
+
|
| 2140 |
matches = re.findall(r"\[\[([A-Za-z0-9 _/-]+):\s*([0-9.+-eE]+)\s*([A-Za-z%\/]*)\]\]", resp)
|
| 2141 |
for m in matches:
|
| 2142 |
+
field, val, unit = m[0].strip(), m[1].strip(), m[2].strip()
|
|
|
|
|
|
|
|
|
|
| 2143 |
if "bearing" in field.lower():
|
| 2144 |
site["Load Bearing Capacity"] = f"{val} {unit}"
|
| 2145 |
elif "skin" in field.lower():
|
| 2146 |
site["Skin Shear Strength"] = f"{val} {unit}"
|
| 2147 |
elif "compaction" in field.lower():
|
| 2148 |
site["Relative Compaction"] = f"{val} {unit}"
|
| 2149 |
+
|
| 2150 |
site["LLM_Report_Text"] = resp
|
| 2151 |
st.success("LLM analysis saved to site under 'LLM_Report_Text'.")
|
| 2152 |
|
| 2153 |
+
# ---------------- Full Report Chatbot ----------------
|
| 2154 |
st.markdown("---")
|
| 2155 |
st.subheader("Full Geotechnical Report (chatbot will gather missing fields)")
|
| 2156 |
if st.button("Start Report Chatbot"):
|
| 2157 |
st.session_state["sites"][st.session_state["active_site"]]["report_convo_state"] = 0
|
| 2158 |
st.rerun()
|
| 2159 |
|
|
|
|
| 2160 |
state = site.get("report_convo_state", -1)
|
| 2161 |
if state >= 0:
|
| 2162 |
st.markdown("Chatbot will ask for missing fields. You can answer or type 'skip' to leave blank.")
|
| 2163 |
+
show_table = [(f, site.get(f, "Not provided")) for f,_ in REPORT_FIELDS]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2164 |
st.table(show_table)
|
| 2165 |
+
|
|
|
|
| 2166 |
if state < len(REPORT_FIELDS):
|
| 2167 |
field, unit = REPORT_FIELDS[state]
|
| 2168 |
ans = st.text_input(f"GeoMate — Please provide '{field}' ({unit})", key=f"report_in_{state}")
|
| 2169 |
c1, c2 = st.columns([1,1])
|
| 2170 |
with c1:
|
| 2171 |
if st.button("Submit", key=f"report_submit_{state}"):
|
| 2172 |
+
site[field] = ans.strip() if ans.strip() not in ("skip","don't know","dont know","na","n/a","") else "Not provided"
|
|
|
|
|
|
|
|
|
|
| 2173 |
site["report_convo_state"] = state + 1
|
| 2174 |
st.rerun()
|
| 2175 |
with c2:
|
|
|
|
| 2179 |
st.rerun()
|
| 2180 |
else:
|
| 2181 |
st.success("All report questions asked. You can generate the full report now.")
|
| 2182 |
+
ext_ref_text = st.text_area("Optional: External references (one per line)", value="")
|
| 2183 |
+
ext_refs = [r.strip() for r in ext_ref_text.splitlines() if r.strip()]
|
| 2184 |
+
|
| 2185 |
+
faiss_refs = []
|
| 2186 |
+
if "rag_history" in st.session_state and site.get("Site ID") in st.session_state["rag_history"]:
|
| 2187 |
+
for h in st.session_state["rag_history"][site["Site ID"]]:
|
| 2188 |
+
if h["who"]=="bot" and "[ref:" in h["text"]:
|
| 2189 |
+
for m in re.findall(r"\[ref:([^\]]+)\]", h["text"]):
|
| 2190 |
+
faiss_refs.append(m)
|
| 2191 |
+
all_refs = list(set(ext_refs + faiss_refs))
|
| 2192 |
+
|
| 2193 |
+
outname = f"Full_Geotech_Report_{site.get('Site Name','site')}.pdf"
|
| 2194 |
+
mapimg = site.get("map_snapshot")
|
| 2195 |
+
|
| 2196 |
+
build_full_geotech_pdf(site, outname, include_map_image=mapimg, ext_refs=all_refs)
|
| 2197 |
|
| 2198 |
+
with open(outname, "rb") as f:
|
| 2199 |
+
st.download_button("Download Full Geotechnical Report", f, file_name=outname, mime="application/pdf")
|
| 2200 |
+
|
| 2201 |
# 8) Page router
|
| 2202 |
if "page" not in st.session_state:
|
| 2203 |
st.session_state["page"] = "Home"
|