MSU576 commited on
Commit
ff8b411
Β·
verified Β·
1 Parent(s): 3d1a699

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +320 -183
app.py CHANGED
@@ -485,82 +485,104 @@ def compute_gsd_metrics(diams: List[float], passing: List[float]) -> Dict[str, f
485
  if "sites" not in st.session_state:
486
  # initialize with a default site
487
  st.session_state["sites"] = [{
488
- "Site Name": None,
489
- "Project Name": "Project",
490
- "Site ID": None,
491
- "Soil Class": None,
492
- "Soil Recognizer Confidence": None,
493
- "Coordinates": "",
494
- "lat": None,
495
- "lon": None,
496
- "Project Description": "",
497
- # ---------------------------
498
- # Site Characterization
499
- # ---------------------------
500
- "Topography": None, # manual topo entry
501
- "Drainage": None, # manual drainage notes
502
- "Current Land Use": None, # can be linked to Environmental Data
503
- "Regional Geology": None, # manual geology notes
504
- # ---------------------------
505
- # Investigations & Lab
506
- # ---------------------------
507
- "Field Investigation": [],
508
- "Laboratory Results": [],
509
- "GSD": None,
510
- "USCS": None,
511
- "AASHTO": None,
512
- "GI": None,
513
- # ---------------------------
514
- # Geotechnical Parameters
515
- # ---------------------------
516
- "Load Bearing Capacity": None,
517
- "Skin Shear Strength": None,
518
- "Relative Compaction": None,
519
- "Rate of Consolidation": None,
520
- "Nature of Construction": None,
521
- # ---------------------------
522
- # Earth Engine Data
523
- # ---------------------------
524
- "Soil Profile": { # SoilGrids (multi-parameter)
525
- "Clay": None, # e.g. % clay at 200 cm
526
- "Sand": None, # % sand
527
- "Silt": None, # % silt
528
- "OrganicCarbon": None, # % organic carbon
529
- "pH": None # soil pH if available
530
- },
531
- "Topo Data": None, # Avg elevation (SRTM DEM)
532
- "Seismic Data": None, # PGA/g (GEM hazard)
533
- "Flood Data": None, # JRC Surface Water occurrence
534
- "Environmental Data": { # Landcover, vegetation, urban, etc.
535
- "Landcover Stats": None, # histogram by class
536
- "Forest Loss": None, # future add: Hansen dataset
537
- "Urban Fraction": None # optional calc from landcover
538
- },
539
- "Weather Data": { # daily/monthly climate summaries
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  "Rainfall": None,
541
  "Temperature": None,
542
  "Humidity": None
543
- },
544
- "Atmospheric Data": { # optional: pollution, aerosols
545
  "AerosolOpticalDepth": None,
546
  "NO2": None,
547
- "CO": None
548
- },
549
- # ---------------------------
550
- # Map & Visualization
551
- # ---------------------------
552
- "map_snapshot": None,
553
- # ---------------------------
554
- # AI / Reporting
555
- # ---------------------------
556
- "chat_history": [],
557
- "classifier_inputs": {},
558
- "classifier_decision": None,
559
- "report_convo_state": 0,
560
- "report_missing_fields": [],
561
- "report_answers": {}
562
- }]
563
-
 
 
 
 
564
 
565
  if "active_site" not in st.session_state:
566
  st.session_state["active_site"] = 0
@@ -568,23 +590,59 @@ if "active_site" not in st.session_state:
568
  if "llm_model" not in st.session_state:
569
  st.session_state["llm_model"] = "groq/compound"
570
 
571
- # Groq client (simple wrapper)
 
 
572
  GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
573
- def groq_generate(prompt: str, model: str = None, max_tokens: int = 512) -> str:
574
- """Call Groq. If call fails, return an explanatory text."""
 
 
 
 
 
 
 
 
 
575
  try:
576
- client = Groq(api_key=GROQ_API_KEY)
577
- model_name = model or st.session_state["llm_model"]
578
- completion = client.chat.completions.create(
579
- model=model_name,
580
- messages=[{"role":"user","content":prompt}],
581
- temperature=0.2,
582
- max_tokens=max_tokens
583
- )
584
- text = completion.choices[0].message.content
585
- return text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
586
  except Exception as e:
587
- return f"[LLM error or offline: {e}]"
588
 
589
  # 5) UI helper: nice CSS for chat bubbles & page styling
590
  st.markdown("""
@@ -592,11 +650,9 @@ st.markdown("""
592
  /* Background and card styling */
593
  body { background: #0b0b0b; color: #e9eef6; }
594
  .stApp > .main > .block-container { padding-top: 18px; }
595
-
596
  /* Landing and cards */
597
  .gm-card { background: linear-gradient(180deg, rgba(255,122,0,0.04), rgba(255,122,0,0.02)); border-radius:12px; padding:14px; border:1px solid rgba(255,122,0,0.06);}
598
  .gm-cta { background: linear-gradient(90deg,#ff7a00,#ff3a3a); color:white; padding:10px 14px; border-radius:10px; font-weight:700; }
599
-
600
  /* Chat bubbles */
601
  .chat-bot { background: #0f1720; border-left:4px solid #FF7A00; padding:10px 12px; border-radius:12px; margin:6px 0; color:#e9eef6; }
602
  .chat-user { background: #1a1f27; padding:10px 12px; border-radius:12px; margin:6px 0; color:#cfe6ff; text-align:right;}
@@ -615,7 +671,10 @@ with st.sidebar:
615
  "llama-3.1-8b-instant",
616
  "meta-llama/llama-guard-4-12b",
617
  "llama-3.3-70b-versatile",
618
- "groq/compound"
 
 
 
619
  ], index=0)
620
  st.markdown("---")
621
 
@@ -636,78 +695,97 @@ with st.sidebar:
636
  "Site Name": new_site_name.strip(),
637
  "Project Name": "Project - " + new_site_name.strip(),
638
  "Site ID": idx,
639
- "Soil Class": None,
640
- "Soil Recognizer Confidence": None,
641
  "Coordinates": "",
642
  "lat": None,
643
  "lon": None,
644
  "Project Description": "",
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  # ---------------------------
646
  # Site Characterization
647
  # ---------------------------
648
- "Topography": None, # manual topo entry
649
- "Drainage": None, # manual drainage notes
650
- "Current Land Use": None, # can be linked to Environmental Data
651
- "Regional Geology": None, # manual geology notes
 
652
  # ---------------------------
653
  # Investigations & Lab
654
  # ---------------------------
655
- "Field Investigation": [],
656
- "Laboratory Results": [],
657
- "GSD": None,
658
- "USCS": None,
659
- "AASHTO": None,
660
- "GI": None,
661
  # ---------------------------
662
- # Geotechnical Parameters
663
  # ---------------------------
664
- "Load Bearing Capacity": None,
665
- "Skin Shear Strength": None,
666
- "Relative Compaction": None,
667
- "Rate of Consolidation": None,
668
- "Nature of Construction": None,
 
 
 
 
 
 
669
  # ---------------------------
670
  # Earth Engine Data
671
  # ---------------------------
672
- "Soil Profile": { # SoilGrids (multi-parameter)
673
- "Clay": None, # e.g. % clay at 200 cm
674
- "Sand": None, # % sand
675
- "Silt": None, # % silt
676
- "OrganicCarbon": None, # % organic carbon
677
- "pH": None # soil pH if available
678
  },
679
- "Topo Data": None, # Avg elevation (SRTM DEM)
680
- "Seismic Data": None, # PGA/g (GEM hazard)
681
- "Flood Data": None, # JRC Surface Water occurrence
682
- "Environmental Data": { # Landcover, vegetation, urban, etc.
683
- "Landcover Stats": None, # histogram by class
684
- "Forest Loss": None, # future add: Hansen dataset
685
- "Urban Fraction": None # optional calc from landcover
686
  },
687
- "Weather Data": { # daily/monthly climate summaries
688
- "Rainfall": None,
689
- "Temperature": None,
690
- "Humidity": None
691
  },
692
- "Atmospheric Data": { # optional: pollution, aerosols
693
- "AerosolOpticalDepth": None,
694
- "NO2": None,
695
- "CO": None
 
696
  },
 
697
  # ---------------------------
698
  # Map & Visualization
699
  # ---------------------------
700
- "map_snapshot": None,
 
 
 
701
  # ---------------------------
702
  # AI / Reporting
703
  # ---------------------------
704
  "chat_history": [],
705
- "classifier_inputs": {},
706
- "classifier_decision": None,
707
  "report_convo_state": 0,
708
  "report_missing_fields": [],
709
  "report_answers": {}
710
- })
711
 
712
 
713
  st.success(f"Site '{new_site_name.strip()}' created.")
@@ -1169,6 +1247,12 @@ def locator_page():
1169
 
1170
  # --- Map setup
1171
  m = geemap.Map(center=[28.0, 72.0], zoom=5, plugin_Draw=True, draw_export=True, locate_control=True)
 
 
 
 
 
 
1172
 
1173
  # Restore ROI (if available)
1174
  if "roi_geojson" in st.session_state:
@@ -1340,31 +1424,65 @@ def locator_page():
1340
  st.session_state["sites"][active]["map_snapshot"] = map_bytes
1341
  st.image(map_bytes, caption="Map Snapshot", use_column_width=True)
1342
 
1343
- # Display
 
 
 
 
 
1344
  st.subheader("πŸ“Š Summary")
 
1345
  st.write(f"**Soil:** {soil_val}")
1346
- st.write(f"**Elevation:** {elev_val}")
1347
  st.write(f"**Seismic:** {seismic_val}")
1348
  st.write(f"**Flood:** {flood_val}")
1349
  st.write(f"**PM2.5 Index:** {pm25_val}")
1350
- st.json(lc_stats)
1351
-
 
 
 
 
 
 
 
 
 
 
 
1352
  if ndvi_ts:
 
1353
  d, v = zip(*ndvi_ts)
1354
  fig, ax = plt.subplots()
1355
- ax.plot(d, v, marker="o"); ax.set_title("NDVI"); ax.set_xlabel("Date")
 
 
 
 
1356
  st.pyplot(fig)
1357
-
 
1358
  if precip_ts:
 
1359
  d, v = zip(*precip_ts)
1360
  fig, ax = plt.subplots()
1361
- ax.plot(d, v, marker="o", color="blue"); ax.set_title("Precipitation (mm)"); ax.set_xlabel("Date")
 
 
 
 
1362
  st.pyplot(fig)
1363
-
 
1364
  if temp_ts:
 
1365
  d, v = zip(*temp_ts)
1366
  fig, ax = plt.subplots()
1367
- ax.plot(d, v, marker="o", color="red"); ax.set_title("LST Temperature (K)"); ax.set_xlabel("Date")
 
 
 
 
1368
  st.pyplot(fig)
1369
 
1370
 
@@ -1391,12 +1509,11 @@ def load_faiss():
1391
 
1392
  vectorstore = load_faiss()
1393
 
1394
-
1395
  # -------------------
1396
  # RAG Chat Page
1397
  # -------------------
1398
  def rag_page():
1399
- st.header("πŸ€– GeoMate Ask (RAG + Groq)")
1400
  site = st.session_state["sites"][st.session_state["active_site"]]
1401
 
1402
  # --- Ensure Site ID exists ---
@@ -1466,8 +1583,8 @@ def rag_page():
1466
  f"If user provides numeric engineering values, return them in the format: [[FIELD: value unit]]."
1467
  )
1468
 
1469
- # Call your Groq model (replace placeholder with real call)
1470
- resp = groq_generate(prompt, model=st.session_state["llm_model"], max_tokens=500)
1471
 
1472
  # Save bot reply
1473
  st.session_state["rag_history"][site_id].append({"who": "bot", "text": resp})
@@ -1544,45 +1661,69 @@ from reportlab.lib.units import mm
1544
 
1545
 
1546
  # ----------------------------
1547
- # LLM Helper (Groq API)
1548
  # ----------------------------
1549
- def groq_llm_analyze(prompt: str, section_title: str,
1550
- model_name: str = "deepseek-r1-distill-llama-70b",
1551
- max_tokens: int = 500) -> str:
1552
  """
1553
- Query Groq API for a humanized explanation/analysis of one report section.
1554
- Returns plain text.
 
1555
  """
1556
- import requests
1557
 
1558
- key = None
1559
- if "GROQ_API_KEY" in st.secrets:
1560
- key = st.secrets["GROQ_API_KEY"]
1561
- else:
1562
- key = st.session_state.get("GROQ_API_KEY")
1563
-
1564
- if not key:
1565
- return f"[LLM unavailable] {section_title}: {prompt[:200]}..."
1566
-
1567
- # βœ… FIX: use /openai/v1 instead of /v1
1568
- url = "https://api.groq.com/openai/v1/chat/completions"
1569
- headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
1570
- payload = {
1571
- "model": model_name,
1572
- "messages": [
1573
- {"role": "system", "content": "You are GeoMate, a geotechnical engineering assistant. Respond professionally with concise analysis and insights."},
1574
- {"role": "user", "content": f"Section: {section_title}\n\nInput: {prompt}\n\nWrite a professional engineering analysis for this section."}
1575
- ],
1576
- "temperature": 0.2,
1577
- "max_tokens": max_tokens,
1578
- }
1579
- try:
1580
- resp = requests.post(url, headers=headers, json=payload, timeout=60)
1581
- resp.raise_for_status()
1582
- data = resp.json()
1583
- return data["choices"][0]["message"]["content"].strip()
1584
- except Exception as e:
1585
- return f"[LLM error in {section_title}] {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1586
 
1587
 
1588
  # =============================
@@ -1726,10 +1867,6 @@ def build_classification_pdf(
1726
  doc.build(elems)
1727
  return filename
1728
 
1729
-
1730
- # -------------------------------
1731
- # Reports Page
1732
- # -------------------------------
1733
  # -------------------------------
1734
  # Reports Page
1735
  # -------------------------------
 
485
  if "sites" not in st.session_state:
486
  # initialize with a default site
487
  st.session_state["sites"] = [{
488
+ # ---------------------------
489
+ # Basic Project Info
490
+ # ---------------------------
491
+ "Site Name": None,
492
+ "Project Name": None,
493
+ "Site ID": None,
494
+ "Coordinates": "",
495
+ "lat": None,
496
+ "lon": None,
497
+ "Project Description": "",
498
+
499
+ # ---------------------------
500
+ # Soil Recognition / Classifier
501
+ # ---------------------------
502
+ "Soil Class": None,
503
+ "Soil Recognizer Confidence": None,
504
+ "USCS": None,
505
+ "AASHTO": None,
506
+ "GI": None,
507
+ "GSD": None, # Grain size distribution metrics
508
+ "classifier_inputs": {}, # User/lab/OCR extracted inputs
509
+ "classifier_decision": None, # Full text decision from classifier
510
+
511
+ # ---------------------------
512
+ # Site Characterization
513
+ # ---------------------------
514
+ "Topography": None, # manual topo entry
515
+ "Drainage": None, # manual drainage notes
516
+ "Current Land Use": None, # linked to environmental data
517
+ "Regional Geology": None, # manual geology notes
518
+
519
+ # ---------------------------
520
+ # Investigations & Lab
521
+ # ---------------------------
522
+ "Field Investigation": [], # e.g. borehole logs
523
+ "Laboratory Results": [], # lab test results
524
+
525
+ # ---------------------------
526
+ # Geotechnical Parameters (incl. REPORT_FIELDS)
527
+ # ---------------------------
528
+ "Load Bearing Capacity": None, # kPa or psf
529
+ "Skin Shear Strength": None, # kPa
530
+ "Relative Compaction": None, # %
531
+ "Rate of Consolidation": None, # mm/yr or days
532
+ "Nature of Construction": None, # text
533
+ "Borehole Count": None, # number
534
+ "Max Depth (m)": None, # m
535
+ "SPT N (avg)": None, # blows/ft
536
+ "CBR (%)": None, # %
537
+ "Allowable Bearing (kPa)": None, # kPa
538
+
539
+ # ---------------------------
540
+ # Earth Engine Data
541
+ # ---------------------------
542
+ "Soil Profile": { # SoilGrids / OpenLandMap
543
+ "Clay": None, # % clay
544
+ "Sand": None, # % sand
545
+ "Silt": None, # % silt
546
+ "OrganicCarbon": None, # % organic carbon
547
+ "pH": None # soil pH
548
+ },
549
+ "Topo Data": None, # Avg elevation
550
+ "Seismic Data": None, # PGA/g
551
+ "Flood Data": None, # % flood occurrence
552
+ "Environmental Data": {
553
+ "Landcover Stats": None, # histogram
554
+ "Forest Loss": None, # optional (Hansen)
555
+ "Urban Fraction": None # optional calc
556
+ },
557
+ "Weather Data": { # climate summaries
558
  "Rainfall": None,
559
  "Temperature": None,
560
  "Humidity": None
561
+ },
562
+ "Atmospheric Data": { # pollution, aerosols
563
  "AerosolOpticalDepth": None,
564
  "NO2": None,
565
+ "CO": None,
566
+ "PM2.5": None # from Sentinel-5P aerosol index
567
+ },
568
+
569
+ # ---------------------------
570
+ # Map & Visualization
571
+ # ---------------------------
572
+ "ROI": None, # GeoJSON of ROI
573
+ "roi_coords": None, # Flattened coords (lat,lon)
574
+ "map_snapshot": None, # PNG snapshot
575
+
576
+ # ---------------------------
577
+ # AI / Reporting
578
+ # ---------------------------
579
+ "chat_history": [],
580
+ "LLM_Report_Text": None,
581
+ "report_convo_state": 0,
582
+ "report_missing_fields": [],
583
+ "report_answers": {}
584
+ }]
585
+
586
 
587
  if "active_site" not in st.session_state:
588
  st.session_state["active_site"] = 0
 
590
  if "llm_model" not in st.session_state:
591
  st.session_state["llm_model"] = "groq/compound"
592
 
593
+ # -------------------
594
+ # API Keys
595
+ # -------------------
596
  GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
597
+ GEM_API_KEY = os.environ.get("GEM_API")
598
+ if "GEM_API" in st.secrets:
599
+ GEM_API_KEY = st.secrets["GEM_API"]
600
+
601
+ # -------------------
602
+ # Universal LLM Call
603
+ # -------------------
604
+ def llm_generate(prompt: str, model: str = None, max_tokens: int = 512) -> str:
605
+ """Universal LLM call for Groq, Gemini, DeepSeek."""
606
+ model_name = model or st.session_state["llm_model"]
607
+
608
  try:
609
+ # ------------------- GEMINI -------------------
610
+ if model_name.lower().startswith("gemini"):
611
+ import requests
612
+ if not GEM_API_KEY:
613
+ return "[LLM error: No GEM_API key found in secrets]"
614
+
615
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/{model_name}:generateContent?key={GEM_API_KEY}"
616
+ headers = {"Content-Type": "application/json"}
617
+ payload = {
618
+ "contents": [{"role": "user", "parts": [{"text": prompt}]}],
619
+ "generationConfig": {"temperature": 0.2, "maxOutputTokens": max_tokens}
620
+ }
621
+ resp = requests.post(url, headers=headers, json=payload, timeout=60)
622
+ resp.raise_for_status()
623
+ data = resp.json()
624
+ return data["candidates"][0]["content"]["parts"][0]["text"].strip()
625
+
626
+ # ------------------- GROQ / DEEPSEEK -------------------
627
+ elif "groq" in model_name or "llama" in model_name or "deepseek" in model_name:
628
+ from groq import Groq
629
+ if not GROQ_API_KEY:
630
+ return "[LLM error: No GROQ_API key found in secrets/env]"
631
+
632
+ client = Groq(api_key=GROQ_API_KEY)
633
+ completion = client.chat.completions.create(
634
+ model=model_name,
635
+ messages=[{"role": "user", "content": prompt}],
636
+ temperature=0.2,
637
+ max_tokens=max_tokens
638
+ )
639
+ return completion.choices[0].message.content
640
+
641
+ else:
642
+ return f"[LLM error: Unknown model {model_name}]"
643
+
644
  except Exception as e:
645
+ return f"[LLM error: {e}]"
646
 
647
  # 5) UI helper: nice CSS for chat bubbles & page styling
648
  st.markdown("""
 
650
  /* Background and card styling */
651
  body { background: #0b0b0b; color: #e9eef6; }
652
  .stApp > .main > .block-container { padding-top: 18px; }
 
653
  /* Landing and cards */
654
  .gm-card { background: linear-gradient(180deg, rgba(255,122,0,0.04), rgba(255,122,0,0.02)); border-radius:12px; padding:14px; border:1px solid rgba(255,122,0,0.06);}
655
  .gm-cta { background: linear-gradient(90deg,#ff7a00,#ff3a3a); color:white; padding:10px 14px; border-radius:10px; font-weight:700; }
 
656
  /* Chat bubbles */
657
  .chat-bot { background: #0f1720; border-left:4px solid #FF7A00; padding:10px 12px; border-radius:12px; margin:6px 0; color:#e9eef6; }
658
  .chat-user { background: #1a1f27; padding:10px 12px; border-radius:12px; margin:6px 0; color:#cfe6ff; text-align:right;}
 
671
  "llama-3.1-8b-instant",
672
  "meta-llama/llama-guard-4-12b",
673
  "llama-3.3-70b-versatile",
674
+ "groq/compound",
675
+ "deepseek-r1-distill-llama-70b", # βœ… Added DeepSeek
676
+ "gemini-1.5-pro", # βœ… Added Gemini
677
+ "gemini-1.5-flash"
678
  ], index=0)
679
  st.markdown("---")
680
 
 
695
  "Site Name": new_site_name.strip(),
696
  "Project Name": "Project - " + new_site_name.strip(),
697
  "Site ID": idx,
 
 
698
  "Coordinates": "",
699
  "lat": None,
700
  "lon": None,
701
  "Project Description": "",
702
+
703
+ # ---------------------------
704
+ # Soil Recognition / Classifier
705
+ # ---------------------------
706
+ "Soil Class": None,
707
+ "Soil Recognizer Confidence": None,
708
+ "USCS": None,
709
+ "AASHTO": None,
710
+ "GI": None,
711
+ "GSD": None, # Grain size distribution metrics
712
+ "classifier_inputs": {}, # User/lab/OCR extracted inputs
713
+ "classifier_decision": None, # Full text decision from classifier
714
+
715
  # ---------------------------
716
  # Site Characterization
717
  # ---------------------------
718
+ "Topography": None, # manual topo entry
719
+ "Drainage": None, # manual drainage notes
720
+ "Current Land Use": None, # linked to environmental data
721
+ "Regional Geology": None, # manual geology notes
722
+
723
  # ---------------------------
724
  # Investigations & Lab
725
  # ---------------------------
726
+ "Field Investigation": [], # e.g. borehole logs
727
+ "Laboratory Results": [], # lab test results
728
+
 
 
 
729
  # ---------------------------
730
+ # Geotechnical Parameters (incl. REPORT_FIELDS)
731
  # ---------------------------
732
+ "Load Bearing Capacity": None, # kPa or psf
733
+ "Skin Shear Strength": None, # kPa
734
+ "Relative Compaction": None, # %
735
+ "Rate of Consolidation": None, # mm/yr or days
736
+ "Nature of Construction": None, # text
737
+ "Borehole Count": None, # number
738
+ "Max Depth (m)": None, # m
739
+ "SPT N (avg)": None, # blows/ft
740
+ "CBR (%)": None, # %
741
+ "Allowable Bearing (kPa)": None, # kPa
742
+
743
  # ---------------------------
744
  # Earth Engine Data
745
  # ---------------------------
746
+ "Soil Profile": { # SoilGrids / OpenLandMap
747
+ "Clay": None, # % clay
748
+ "Sand": None, # % sand
749
+ "Silt": None, # % silt
750
+ "OrganicCarbon": None, # % organic carbon
751
+ "pH": None # soil pH
752
  },
753
+ "Topo Data": None, # Avg elevation
754
+ "Seismic Data": None, # PGA/g
755
+ "Flood Data": None, # % flood occurrence
756
+ "Environmental Data": {
757
+ "Landcover Stats": None, # histogram
758
+ "Forest Loss": None, # optional (Hansen)
759
+ "Urban Fraction": None # optional calc
760
  },
761
+ "Weather Data": { # climate summaries
762
+ "Rainfall": None,
763
+ "Temperature": None,
764
+ "Humidity": None
765
  },
766
+ "Atmospheric Data": { # pollution, aerosols
767
+ "AerosolOpticalDepth": None,
768
+ "NO2": None,
769
+ "CO": None,
770
+ "PM2.5": None # from Sentinel-5P aerosol index
771
  },
772
+
773
  # ---------------------------
774
  # Map & Visualization
775
  # ---------------------------
776
+ "ROI": None, # GeoJSON of ROI
777
+ "roi_coords": None, # Flattened coords (lat,lon)
778
+ "map_snapshot": None, # PNG snapshot
779
+
780
  # ---------------------------
781
  # AI / Reporting
782
  # ---------------------------
783
  "chat_history": [],
784
+ "LLM_Report_Text": None,
 
785
  "report_convo_state": 0,
786
  "report_missing_fields": [],
787
  "report_answers": {}
788
+ }]
789
 
790
 
791
  st.success(f"Site '{new_site_name.strip()}' created.")
 
1247
 
1248
  # --- Map setup
1249
  m = geemap.Map(center=[28.0, 72.0], zoom=5, plugin_Draw=True, draw_export=True, locate_control=True)
1250
+
1251
+ # βœ… Add a basemap explicitly
1252
+ m.add_basemap("HYBRID") # Google Satellite Hybrid
1253
+ m.add_basemap("ROADMAP") # Google Roads
1254
+ m.add_basemap("Esri.WorldImagery")
1255
+ m.add_basemap("OpenStreetMap")
1256
 
1257
  # Restore ROI (if available)
1258
  if "roi_geojson" in st.session_state:
 
1424
  st.session_state["sites"][active]["map_snapshot"] = map_bytes
1425
  st.image(map_bytes, caption="Map Snapshot", use_column_width=True)
1426
 
1427
+ import matplotlib.pyplot as plt
1428
+
1429
+ # Custom theme colors (orange/black/gray)
1430
+ COLOR_SCHEME = ["#FF6600", "#333333", "#FF9933", "#666666", "#FFCC99"]
1431
+
1432
+ # --- Display results
1433
  st.subheader("πŸ“Š Summary")
1434
+
1435
  st.write(f"**Soil:** {soil_val}")
1436
+ st.write(f"**Elevation:** {elev_val} m")
1437
  st.write(f"**Seismic:** {seismic_val}")
1438
  st.write(f"**Flood:** {flood_val}")
1439
  st.write(f"**PM2.5 Index:** {pm25_val}")
1440
+
1441
+ # --- Landcover pie chart
1442
+ if lc_stats:
1443
+ st.markdown("#### 🌱 Landcover Distribution")
1444
+ labels = list(lc_stats.keys())
1445
+ values = list(lc_stats.values())
1446
+ fig, ax = plt.subplots()
1447
+ ax.pie(values, labels=labels, autopct="%1.1f%%", startangle=90,
1448
+ colors=COLOR_SCHEME, wedgeprops={'edgecolor': 'white'})
1449
+ ax.set_aspect("equal")
1450
+ st.pyplot(fig)
1451
+
1452
+ # --- NDVI time series
1453
  if ndvi_ts:
1454
+ st.markdown("#### 🌿 NDVI Trend (2 years)")
1455
  d, v = zip(*ndvi_ts)
1456
  fig, ax = plt.subplots()
1457
+ ax.plot(d, v, marker="o", color=COLOR_SCHEME[0])
1458
+ ax.set_title("NDVI", fontsize=12, color=COLOR_SCHEME[1])
1459
+ ax.set_xlabel("Date")
1460
+ ax.set_ylabel("NDVI")
1461
+ ax.grid(True, linestyle="--", alpha=0.6)
1462
  st.pyplot(fig)
1463
+
1464
+ # --- Precipitation
1465
  if precip_ts:
1466
+ st.markdown("#### 🌧️ Precipitation Trend (1 year)")
1467
  d, v = zip(*precip_ts)
1468
  fig, ax = plt.subplots()
1469
+ ax.plot(d, v, marker="o", color=COLOR_SCHEME[2])
1470
+ ax.set_title("Precipitation (mm)", fontsize=12, color=COLOR_SCHEME[1])
1471
+ ax.set_xlabel("Date")
1472
+ ax.set_ylabel("Rainfall (mm)")
1473
+ ax.grid(True, linestyle="--", alpha=0.6)
1474
  st.pyplot(fig)
1475
+
1476
+ # --- Temperature
1477
  if temp_ts:
1478
+ st.markdown("#### 🌑️ Land Surface Temp (1 year)")
1479
  d, v = zip(*temp_ts)
1480
  fig, ax = plt.subplots()
1481
+ ax.plot(d, v, marker="o", color=COLOR_SCHEME[3])
1482
+ ax.set_title("LST Temperature (K)", fontsize=12, color=COLOR_SCHEME[1])
1483
+ ax.set_xlabel("Date")
1484
+ ax.set_ylabel("Kelvin (K)")
1485
+ ax.grid(True, linestyle="--", alpha=0.6)
1486
  st.pyplot(fig)
1487
 
1488
 
 
1509
 
1510
  vectorstore = load_faiss()
1511
 
 
1512
  # -------------------
1513
  # RAG Chat Page
1514
  # -------------------
1515
  def rag_page():
1516
+ st.header("πŸ€– GeoMate Ask (RAG + LLM)")
1517
  site = st.session_state["sites"][st.session_state["active_site"]]
1518
 
1519
  # --- Ensure Site ID exists ---
 
1583
  f"If user provides numeric engineering values, return them in the format: [[FIELD: value unit]]."
1584
  )
1585
 
1586
+ # Call the unified LLM function
1587
+ resp = llm_generate(prompt, model=st.session_state["llm_model"], max_tokens=500)
1588
 
1589
  # Save bot reply
1590
  st.session_state["rag_history"][site_id].append({"who": "bot", "text": resp})
 
1661
 
1662
 
1663
  # ----------------------------
1664
+ # LLM Helper (Reports Analysis with Orchestration)
1665
  # ----------------------------
1666
+ def groq_llm_analyze(prompt: str, section_title: str, max_tokens: int = 500) -> str:
 
 
1667
  """
1668
+ Query the selected model (Groq / Gemini / DeepSeek).
1669
+ If token limit or error occurs, automatically switch to backup models
1670
+ and continue the analysis seamlessly.
1671
  """
 
1672
 
1673
+ # Primary model (user choice from sidebar)
1674
+ model_chain = [st.session_state.get("llm_model", "groq/compound")]
1675
+
1676
+ # Add fallback chain (priority order: DeepSeek β†’ Gemini β†’ Groq)
1677
+ if "deepseek" not in model_chain:
1678
+ model_chain.append("deepseek-r1-distill-llama-70b")
1679
+ if "gemini" not in model_chain:
1680
+ model_chain.append("gemini-1.5-pro")
1681
+ if "groq/compound" not in model_chain:
1682
+ model_chain.append("groq/compound")
1683
+
1684
+ system_message = (
1685
+ "You are GeoMate, a geotechnical engineering assistant. "
1686
+ "Respond professionally with concise analysis and insights."
1687
+ )
1688
+
1689
+ full_prompt = (
1690
+ f"{system_message}\n\n"
1691
+ f"Section: {section_title}\n\n"
1692
+ f"Input: {prompt}\n\n"
1693
+ f"Write a professional engineering analysis for this section."
1694
+ )
1695
+
1696
+ final_response = ""
1697
+ remaining_prompt = full_prompt
1698
+
1699
+ # Try each model in the chain until completion
1700
+ for model_name in model_chain:
1701
+ try:
1702
+ response = llm_generate(remaining_prompt, model=model_name, max_tokens=max_tokens)
1703
+
1704
+ if not response or "[LLM error" in response:
1705
+ # If failed, continue to next model
1706
+ continue
1707
+
1708
+ final_response += response.strip()
1709
+
1710
+ # If response length is close to max_tokens, assume it cut off β†’ continue with next model
1711
+ if len(response.split()) >= (max_tokens - 20):
1712
+ # Add continuation instruction for the next model
1713
+ remaining_prompt = (
1714
+ f"Continue the analysis from where the last model stopped. "
1715
+ f"So far the draft is:\n\n{final_response}\n\n"
1716
+ f"Continue writing professionally without repeating."
1717
+ )
1718
+ continue
1719
+ else:
1720
+ break # Finished properly, exit loop
1721
+
1722
+ except Exception as e:
1723
+ final_response += f"\n[LLM orchestration error @ {model_name}: {e}]\n"
1724
+ continue
1725
+
1726
+ return final_response if final_response else "[LLM error: All models failed]"
1727
 
1728
 
1729
  # =============================
 
1867
  doc.build(elems)
1868
  return filename
1869
 
 
 
 
 
1870
  # -------------------------------
1871
  # Reports Page
1872
  # -------------------------------