MSU576 commited on
Commit
212ce97
Β·
verified Β·
1 Parent(s): 6a983e7

Update app.po

Browse files
Files changed (1) hide show
  1. app.po +170 -77
app.po CHANGED
@@ -534,40 +534,124 @@ def soil_recognizer_ui():
534
  # Soil Classifier
535
  # -------------------------------------------------------
536
 
537
- # Helpers for USCS + AASHTO classification logic
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
  def classify_uscs(inputs: Dict[str, Any]) -> Tuple[str, str]:
539
- """
540
- Verbatim simplified USCS logic based on % fines, D10/30/60, LL, PL, and observations.
541
- Returns (code, description).
542
- """
543
- # Very simplified placeholder (expand with full decision tree)
544
- fines = inputs.get("P200", 0.0)
545
  if inputs.get("organic", False):
546
  return "Pt", "Peat / Organic soil β€” compressible, poor engineering properties."
547
- if fines < 5:
548
- return "GW", "Well-graded gravel with excellent load-bearing capacity."
549
- if fines > 50:
550
- LL = inputs.get("LL", 0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
  if LL < 50:
552
- return "CL", "Low plasticity clay."
 
553
  else:
554
- return "CH", "High plasticity clay."
555
- return "SM", "Silty sand with moderate engineering quality."
 
556
 
557
- def classify_aashto(inputs: Dict[str, Any]) -> Tuple[str, str]:
558
- """
559
- Simplified AASHTO classification logic.
560
- """
561
- fines = inputs.get("P200", 0.0)
562
- if inputs.get("organic", False):
563
- return "A-8", "Organic soils (special handling required)."
564
- if fines < 35:
565
- return "A-1-a", "Granular material with excellent performance."
566
- else:
567
- return "A-7-6", "Clayey soils with poor performance unless stabilized."
568
 
 
569
  def soil_classifier_ui():
570
- st.header("πŸ€– Soil Classifier (Chatbot Style)")
571
  site = get_active_site()
572
 
573
  if "classifier_state" not in site:
@@ -582,7 +666,7 @@ def soil_classifier_ui():
582
  def add_user(msg: str):
583
  chat.append(["user", msg])
584
 
585
- # Chat rendering
586
  for role, msg in chat:
587
  bubble_color = THEME["bubble_bg"] if role=="bot" else "#1f2a44"
588
  border = f"2px solid {THEME['accent']}" if role=="bot" else "1px solid #333"
@@ -593,85 +677,94 @@ def soil_classifier_ui():
593
  </div>
594
  """, unsafe_allow_html=True)
595
 
596
- # State machine
597
  state = site["classifier_state"]
598
  inputs = site["classifier_inputs"]
599
 
600
- def ask(question: str):
601
- add_bot(question)
602
- site["classifier_state"] += 1
603
- save_active_site(site)
604
- st.rerun()
 
605
 
606
- # Initial Q
607
  if state == 0 and not chat:
608
- ask("Hello β€” I am the GeoMate Soil Classifier. Is the soil organic (spongy, dark, odorous)? (y/n)")
 
 
609
 
610
- # User input
611
  user_in = st.text_input("Your answer:", key=f"classifier_input_{state}")
612
- if st.button("➑️ Submit", key=f"classifier_submit_{state}"):
613
  if user_in.strip():
614
  add_user(user_in.strip())
615
- # Logic branch
616
- if state == 1: # organic q
 
617
  if user_in.lower().startswith("y"):
618
  inputs["organic"] = True
619
- site["USCS"], desc1 = classify_uscs(inputs)
620
- site["AASHTO"], desc2 = classify_aashto(inputs)
621
- add_bot(f"Classification complete: USCS={site['USCS']} ({desc1}), AASHTO={site['AASHTO']} ({desc2})")
 
 
 
622
  site["classifier_state"] = -1
623
  else:
624
  inputs["organic"] = False
625
- ask("What is the % passing the #200 sieve (0.075 mm)?")
 
626
  elif state == 2:
627
- try:
628
- inputs["P200"] = float(user_in)
629
  except: inputs["P200"] = 0.0
630
- if inputs["P200"] < 5:
631
- ask("What is the % passing the sieve no. 4 (4.75 mm)?")
632
- else:
633
- ask("What is the Liquid Limit (LL)?")
634
  elif state == 3:
635
- try:
636
- inputs["P4"] = float(user_in)
637
- except: inputs["P4"] = 0.0
638
- ask("Do you know the D10, D30, D60 values? (y/n)")
639
  elif state == 4:
640
- if user_in.lower().startswith("y"):
641
- ask("Enter D60 (mm):")
642
- else:
643
- ask("Enter Liquid Limit (LL):")
644
- elif state == 5:
645
- try: inputs["D60"] = float(user_in)
646
- except: inputs["D60"] = 0.0
647
- ask("Enter D30 (mm):")
648
- elif state == 6:
649
- try: inputs["D30"] = float(user_in)
650
- except: inputs["D30"] = 0.0
651
- ask("Enter D10 (mm):")
652
- elif state == 7:
653
- try: inputs["D10"] = float(user_in)
654
- except: inputs["D10"] = 0.0
655
- ask("Enter Liquid Limit (LL):")
656
- elif state == 8:
657
  try: inputs["LL"] = float(user_in)
658
  except: inputs["LL"] = 0.0
659
- ask("Enter Plastic Limit (PL):")
660
- elif state == 9:
 
661
  try: inputs["PL"] = float(user_in)
662
  except: inputs["PL"] = 0.0
663
- # classify now
664
- site["USCS"], desc1 = classify_uscs(inputs)
665
- site["AASHTO"], desc2 = classify_aashto(inputs)
666
- add_bot(f"Classification complete: USCS={site['USCS']} ({desc1}), AASHTO={site['AASHTO']} ({desc2})")
 
667
  site["classifier_state"] = -1
 
668
  save_active_site(site)
669
  st.rerun()
670
 
671
  if site["classifier_state"] == -1:
672
  if st.button("πŸ“„ Export Classification Report"):
673
- st.success("Report export will be in Reports page.")
 
674
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
675
  # -------------------------------------------------------
676
  # GSD Curve Page
677
  # -------------------------------------------------------
 
534
  # Soil Classifier
535
  # -------------------------------------------------------
536
 
537
+ # -------------------------------------------------------
538
+ # Soil Classifier (Chatbot Style, with OCR + LLM)
539
+ # -------------------------------------------------------
540
+ import pytesseract
541
+ import tempfile
542
+ from PIL import Image
543
+ from typing import Dict, Any, Tuple
544
+
545
+ # ---------- Utilities ----------
546
+ def run_ocr_on_image(uploaded_file) -> Dict[str, float]:
547
+ """Run OCR on uploaded soil problem sheet to extract LL, PL, sieve %s."""
548
+ img = Image.open(uploaded_file).convert("L")
549
+ text = pytesseract.image_to_string(img)
550
+ extracted = {}
551
+
552
+ # Very naive parsing – refine later
553
+ for line in text.splitlines():
554
+ if "LL" in line.upper():
555
+ try: extracted["LL"] = float([s for s in line.split() if s.replace('.','',1).isdigit()][0])
556
+ except: pass
557
+ if "PL" in line.upper():
558
+ try: extracted["PL"] = float([s for s in line.split() if s.replace('.','',1).isdigit()][0])
559
+ except: pass
560
+ if "#200" in line or "200" in line:
561
+ try: extracted["P200"] = float([s for s in line.split() if s.replace('.','',1).isdigit()][0])
562
+ except: pass
563
+ if "#40" in line or "40" in line:
564
+ try: extracted["P40"] = float([s for s in line.split() if s.replace('.','',1).isdigit()][0])
565
+ except: pass
566
+ return extracted
567
+
568
+
569
+ # ---------- Classification Logic ----------
570
+ def classify_aashto(inputs: Dict[str, Any]) -> Tuple[str, str, str]:
571
+ """Full AASHTO logic + Group Index + explanation."""
572
+ from math import floor
573
+ P2 = inputs.get("P200", 0.0)
574
+ P4 = inputs.get("P40", 0.0)
575
+ LL = inputs.get("LL", 0.0)
576
+ PL = inputs.get("PL", 0.0)
577
+ PI = LL - PL
578
+ result = "A-0"
579
+ desc = ""
580
+ GI = 0
581
+
582
+ if P2 <= 35:
583
+ if P2 <= 15 and P4 <= 30 and PI <= 6:
584
+ P1 = inputs.get("P10", 0.0)
585
+ if P1 <= 50:
586
+ result = "A-1-a"; desc = "Granular soil, excellent subgrade."
587
+ else:
588
+ result = "A-1-b"; desc = "Granular soil with fines, still good subgrade."
589
+ elif P2 <= 25 and P4 <= 50 and PI <= 6:
590
+ result = "A-1-b"; desc = "Granular soil with more fines, fair performance."
591
+ elif P2 <= 35:
592
+ if LL <= 40 and PI <= 10: result = "A-2-4"; desc = "Granular soil with silt, fair subgrade."
593
+ elif LL >= 41 and PI <= 10: result = "A-2-5"; desc = "Granular soil, high LL silt content."
594
+ elif LL <= 40 and PI >= 11: result = "A-2-6"; desc = "Granular soil with clayey fines."
595
+ else: result = "A-2-7"; desc = "Granular soil, poor clayey fines."
596
+ else:
597
+ result = "A-3"; desc = "Clean sands, excellent highway subgrade."
598
+ else:
599
+ if LL <= 40 and PI <= 10: result = "A-4"; desc = "Silt, fair to poor subgrade."
600
+ elif LL >= 41 and PI <= 10: result = "A-5"; desc = "Elastic silt, very poor subgrade."
601
+ elif LL <= 40 and PI >= 11: result = "A-6"; desc = "Clay of low plasticity, poor subgrade."
602
+ else:
603
+ if PI <= (LL-30): result = "A-7-5"; desc = "Clay, high LL, fair plasticity."
604
+ else: result = "A-7-6"; desc = "Clay, high plasticity, very poor subgrade."
605
+
606
+ # Group Index
607
+ a = min(max(P2 - 35, 0), 40)
608
+ b = min(max(P2 - 15, 0), 40)
609
+ c = min(max(LL - 40, 0), 20)
610
+ d = min(max(PI - 10, 0), 20)
611
+ GI = floor(0.2*a + 0.005*a*c + 0.01*b*d)
612
+
613
+ return result, desc, str(GI)
614
+
615
+
616
  def classify_uscs(inputs: Dict[str, Any]) -> Tuple[str, str]:
617
+ """Full USCS logic with Cu, Cc, PI, DS/DIL/TG."""
618
+ P2 = inputs.get("P200", 0.0)
 
 
 
 
619
  if inputs.get("organic", False):
620
  return "Pt", "Peat / Organic soil β€” compressible, poor engineering properties."
621
+
622
+ if P2 <= 50: # Coarse
623
+ P4 = inputs.get("P4", 0.0)
624
+ D60, D30, D10 = inputs.get("D60", 0.0), inputs.get("D30", 0.0), inputs.get("D10", 0.0)
625
+ LL, PL = inputs.get("LL", 0.0), inputs.get("PL", 0.0)
626
+ PI = LL - PL
627
+ Cu, Cc = 0, 0
628
+ if all([D60, D30, D10]):
629
+ Cu = D60/D10 if D10 else 0
630
+ Cc = (D30**2)/(D10*D60) if D10*D60 else 0
631
+
632
+ if P4 <= 50: # Gravels
633
+ if Cu >= 4 and 1 <= Cc <= 3: return "GW", "Well-graded gravel, excellent foundation material."
634
+ elif PI <= 7: return "GM", "Silty gravel, moderate quality."
635
+ else: return "GC", "Clayey gravel, reduced drainage."
636
+ else: # Sands
637
+ if Cu >= 6 and 1 <= Cc <= 3: return "SW", "Well-graded sand, excellent engineering soil."
638
+ elif PI <= 7: return "SM", "Silty sand, fair to moderate."
639
+ else: return "SC", "Clayey sand, reduced strength."
640
+ else: # Fine
641
+ LL, PL = inputs.get("LL", 0.0), inputs.get("PL", 0.0)
642
+ PI = LL - PL
643
  if LL < 50:
644
+ if PI <= 7: return "ML", "Low plasticity silt."
645
+ elif PI > 7: return "CL", "Low plasticity clay."
646
  else:
647
+ if PI < 0.73*(LL-20): return "MH", "Elastic silt."
648
+ else: return "CH", "High plasticity clay, compressible, weak foundation soil."
649
+ return "ML", "Default: Low plasticity silt."
650
 
 
 
 
 
 
 
 
 
 
 
 
651
 
652
+ # ---------- Main Chatbot ----------
653
  def soil_classifier_ui():
654
+ st.header("πŸ€– Soil Classifier (Chatbot + OCR + LLM)")
655
  site = get_active_site()
656
 
657
  if "classifier_state" not in site:
 
666
  def add_user(msg: str):
667
  chat.append(["user", msg])
668
 
669
+ # Render chat
670
  for role, msg in chat:
671
  bubble_color = THEME["bubble_bg"] if role=="bot" else "#1f2a44"
672
  border = f"2px solid {THEME['accent']}" if role=="bot" else "1px solid #333"
 
677
  </div>
678
  """, unsafe_allow_html=True)
679
 
 
680
  state = site["classifier_state"]
681
  inputs = site["classifier_inputs"]
682
 
683
+ # OCR Upload
684
+ uploaded = st.file_uploader("πŸ“„ Upload soil test sheet (OCR)", type=["jpg","png","jpeg"])
685
+ if uploaded:
686
+ ocr_data = run_ocr_on_image(uploaded)
687
+ inputs.update(ocr_data)
688
+ add_bot(f"OCR detected values: {ocr_data}")
689
 
690
+ # Initial
691
  if state == 0 and not chat:
692
+ add_bot("Hello πŸ‘‹ I am GeoMate Soil Classifier. Let's begin. Is the soil organic (spongy, dark, odorous)? (y/n)")
693
+ site["classifier_state"] = 1
694
+ save_active_site(site)
695
 
696
+ # User Input
697
  user_in = st.text_input("Your answer:", key=f"classifier_input_{state}")
698
+ if st.button("➑️", key=f"classifier_submit_{state}"):
699
  if user_in.strip():
700
  add_user(user_in.strip())
701
+
702
+ # Logic branches
703
+ if state == 1:
704
  if user_in.lower().startswith("y"):
705
  inputs["organic"] = True
706
+ uscs, desc1 = classify_uscs(inputs)
707
+ aashto, desc2, gi = classify_aashto(inputs)
708
+ add_bot(f"Classification complete βœ… USCS={uscs} ({desc1}), AASHTO={aashto} (GI={gi}, {desc2})")
709
+ # Expand with LLM
710
+ full_report = query_llm_for_soil(uscs, aashto, desc1, desc2, gi)
711
+ add_bot(full_report)
712
  site["classifier_state"] = -1
713
  else:
714
  inputs["organic"] = False
715
+ add_bot("What is % passing #200 sieve?")
716
+ site["classifier_state"] = 2
717
  elif state == 2:
718
+ try: inputs["P200"] = float(user_in)
 
719
  except: inputs["P200"] = 0.0
720
+ add_bot("What is % passing #40 sieve?")
721
+ site["classifier_state"] = 3
 
 
722
  elif state == 3:
723
+ try: inputs["P40"] = float(user_in)
724
+ except: inputs["P40"] = 0.0
725
+ add_bot("Enter Liquid Limit (LL):")
726
+ site["classifier_state"] = 4
727
  elif state == 4:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
728
  try: inputs["LL"] = float(user_in)
729
  except: inputs["LL"] = 0.0
730
+ add_bot("Enter Plastic Limit (PL):")
731
+ site["classifier_state"] = 5
732
+ elif state == 5:
733
  try: inputs["PL"] = float(user_in)
734
  except: inputs["PL"] = 0.0
735
+ uscs, desc1 = classify_uscs(inputs)
736
+ aashto, desc2, gi = classify_aashto(inputs)
737
+ add_bot(f"Classification complete βœ… USCS={uscs} ({desc1}), AASHTO={aashto} (GI={gi}, {desc2})")
738
+ full_report = query_llm_for_soil(uscs, aashto, desc1, desc2, gi)
739
+ add_bot(full_report)
740
  site["classifier_state"] = -1
741
+
742
  save_active_site(site)
743
  st.rerun()
744
 
745
  if site["classifier_state"] == -1:
746
  if st.button("πŸ“„ Export Classification Report"):
747
+ site["classification_report"] = chat
748
+ st.success("Report saved. Generate full report in Reports Page.")
749
 
750
+
751
+ # ---------- LLM Expansion ----------
752
+ def query_llm_for_soil(uscs_code, aashto_code, desc1, desc2, gi):
753
+ """Ask Groq LLM to expand classification into detailed engineering report."""
754
+ prompt = f"""
755
+ Soil Classification Results:
756
+ - USCS: {uscs_code} ({desc1})
757
+ - AASHTO: {aashto_code} ({desc2}), Group Index={gi}
758
+
759
+ Provide:
760
+ 1. Engineering characteristics (compressibility, permeability, shear strength, settlement, frost susceptibility).
761
+ 2. Construction applications (foundations, embankments, pavements).
762
+ 3. Typical stabilization or improvement methods.
763
+ 4. Warnings or limitations.
764
+
765
+ Be detailed but concise, use professional engineering language.
766
+ """
767
+ return groq_chat(prompt) # assumes you have groq_chat() wrapper
768
  # -------------------------------------------------------
769
  # GSD Curve Page
770
  # -------------------------------------------------------