Mohibullah commited on
Commit
25824e9
·
1 Parent(s): ba74320

Update code to fresh version from parent project

Browse files
Files changed (1) hide show
  1. gradio_pharmacopilot_demo.py +178 -579
gradio_pharmacopilot_demo.py CHANGED
@@ -474,6 +474,7 @@ def load_kpi_metrics(searches: int = 0) -> str:
474
  elif fallback_path.exists():
475
  text = fallback_path.read_text(encoding="utf-8", errors="ignore")
476
  if "ocr_accuracy" in text:
 
477
  ocr_accuracy = 0.37888446215139443
478
  retrieval_accuracy = 0.6055776892430279
479
 
@@ -486,42 +487,10 @@ def load_kpi_metrics(searches: int = 0) -> str:
486
  return f"""
487
  <div class="app-shell">
488
  <div class="metric-row">
489
- <div class="metric">
490
- <div class="metric-head">
491
- <span>Session Searches</span>
492
- <div class="metric-icon" style="color: var(--glow-cyan); background: rgba(6, 182, 212, 0.12);">
493
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width: 16px; height: 16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
494
- </div>
495
- </div>
496
- <strong>{searches}</strong>
497
- </div>
498
- <div class="metric">
499
- <div class="metric-head">
500
- <span>Retrieval Recovery</span>
501
- <div class="metric-icon" style="color: var(--glow-green); background: rgba(16, 185, 129, 0.12);">
502
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width: 16px; height: 16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
503
- </div>
504
- </div>
505
- <strong>{retrieval_text}</strong>
506
- </div>
507
- <div class="metric">
508
- <div class="metric-head">
509
- <span>Medicines Indexed</span>
510
- <div class="metric-icon" style="color: var(--glow-indigo); background: rgba(99, 102, 241, 0.12);">
511
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width: 16px; height: 16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path></svg>
512
- </div>
513
- </div>
514
- <strong>{indexed}</strong>
515
- </div>
516
- <div class="metric">
517
- <div class="metric-head">
518
- <span>Rx Samples Tested</span>
519
- <div class="metric-icon" style="color: #f43f5e; background: rgba(244, 63, 94, 0.12);">
520
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width: 16px; height: 16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path></svg>
521
- </div>
522
- </div>
523
- <strong>2,510</strong>
524
- </div>
525
  </div>
526
  </div>
527
  """
@@ -534,27 +503,21 @@ def confidence_gauge(confidence: int = 97):
534
  go.Indicator(
535
  mode="gauge+number",
536
  value=confidence,
537
- number={"suffix": "%", "font": {"size": 34, "color": "#ffffff"}},
538
  gauge={
539
- "axis": {"range": [0, 100], "tickwidth": 1, "tickcolor": "#94a3b8", "tickfont": {"color": "#94a3b8"}},
540
- "bar": {"color": "#10b981"},
541
- "bgcolor": "rgba(255, 255, 255, 0.05)",
542
- "borderwidth": 1,
543
- "bordercolor": "rgba(255, 255, 255, 0.15)",
544
  "steps": [
545
- {"range": [0, 60], "color": "rgba(239, 68, 68, 0.15)"},
546
- {"range": [60, 85], "color": "rgba(245, 158, 11, 0.15)"},
547
- {"range": [85, 100], "color": "rgba(16, 185, 129, 0.15)"},
548
  ],
549
  },
550
  )
551
  )
552
- fig.update_layout(
553
- height=230,
554
- margin=dict(l=20, r=20, t=20, b=8),
555
- paper_bgcolor="rgba(0,0,0,0)",
556
- plot_bgcolor="rgba(0,0,0,0)"
557
- )
558
  return fig
559
 
560
 
@@ -575,34 +538,20 @@ def pipeline_html(stage: int = 0, validation_status: str = "waiting") -> str:
575
  cards = []
576
  logs = []
577
  for i, (title, status) in enumerate(steps, start=1):
578
- if i < stage:
579
- step_class = "done"
580
- elif i == stage:
581
- step_class = "current" if stage < 5 else "done"
582
- else:
583
- step_class = ""
584
-
585
  cards.append(
586
  f"""
587
- <div class="flow-step {step_class}">
588
  <span>{i}</span>
589
  <strong>{title}</strong>
590
  </div>
591
  """
592
  )
593
- if i <= stage:
594
- logs.append(f"<li>{title} {status}</li>")
595
-
596
- pulse_indicator = ""
597
- if stage > 0 and stage < 5:
598
- pulse_indicator = '<span style="display:inline-block; width:10px; height:10px; background:var(--glow-cyan); border-radius:50%; margin-left:8px; animation: pulse-step 1.5s infinite;"></span>'
599
-
600
  return f"""
601
  <div class="pipeline">
602
- <div class="pipeline-title" style="display:flex; align-items:center;">
603
- <span>SYSTEM DIAGNOSTICS TRACE</span>
604
- {pulse_indicator}
605
- </div>
606
  <div class="flow">{''.join(cards)}</div>
607
  <ul class="logs">{''.join(logs)}</ul>
608
  </div>
@@ -632,22 +581,22 @@ def medicine_details_html(
632
  )
633
  return f"""
634
  <div class="result-card">
635
- <h3>Prescription Analysis</h3>
636
  <dl class="details">
637
  <dt>Medicine</dt><dd>{medicine_label}</dd>
638
- <dt>Generic</dt><dd style="color: var(--glow-cyan) !important;">{generic_label}</dd>
639
  <dt>Strength</dt><dd>{strength_label}</dd>
640
  <dt>Manufacturer</dt><dd>{manufacturer_label}</dd>
641
- <dt>Confidence</dt><dd style="color: {'var(--text-success)' if accepted else 'var(--text-warning)'} !important;">{confidence}%</dd>
642
- <dt>Category</dt><dd><span style="padding: 2px 8px; background: rgba(99,102,241,0.15); border-radius: 4px; font-size:12px; color: #a5b4fc;">{category_label}</span></dd>
643
  <dt>Price</dt><dd>{price_label}</dd>
644
  </dl>
645
  <div class="explain">
646
- <h4>AI Diagnostic Breakdown</h4>
647
  <p><b>OCR detected:</b> "{ocr_text}"</p>
648
- <p><b>Retrieved Master Match:</b> {display_name} ({medicine.get('name', 'Unknown')})</p>
649
- <p><b>Nemotron Validation:</b> <span style="color: {'var(--text-success)' if accepted else 'var(--text-warning)'}; font-weight: 600;">{validation_label}</span></p>
650
- <p><b>Inventory Location:</b> {inventory_label}</p>
651
  </div>
652
  </div>
653
  """
@@ -665,19 +614,15 @@ def translated_prescription_html(plan: dict[str, Any]) -> str:
665
  ("Instructions", plan.get("instructions") or "Pharmacist review required"),
666
  ]
667
  row_html = "".join(f"<dt>{label}</dt><dd>{value}</dd>" for label, value in rows)
668
- status_val = plan.get("status", "needs_review")
669
- status_label = status_val.replace("_", " ").title()
670
- status_class = "status-pill"
671
- if status_val == "needs_review":
672
- status_class += " needs-review"
673
  return f"""
674
  <div class="translated-card">
675
  <div class="translated-head">
676
- <h3>Translated Instruction Plan</h3>
677
- <span class="{status_class}">{status_label}</span>
678
  </div>
679
  <dl class="details translated-details">{row_html}</dl>
680
- <p class="fine-print">Generated via local LLM + OCR validation pipeline. Verify dosage instructions before dispensing.</p>
681
  </div>
682
  """
683
 
@@ -686,19 +631,18 @@ def package_status_html(inventory: dict[str, Any], accepted: bool = True) -> str
686
  if not accepted:
687
  return """
688
  <div class="stock-card">
689
- <div><span>Available Stock</span><strong style="color: var(--text-muted);">-</strong></div>
690
  <div><span>Status</span><strong class="bad">Needs Review</strong></div>
691
- <div><span>Shelf Location</span><strong style="color: var(--text-muted);">Hidden</strong></div>
692
  </div>
693
  """
694
  status = "In Stock" if inventory.get("quantity", 0) > 0 else "Out of Stock"
695
  dot_class = "ok" if inventory.get("quantity", 0) > 0 else "bad"
696
- shelf_loc = f"Shelf {inventory.get('shelf', '-')} · Row {inventory.get('row', '-')}"
697
  return f"""
698
  <div class="stock-card">
699
  <div><span>Available Stock</span><strong>{inventory.get('quantity', 0)}</strong></div>
700
  <div><span>Status</span><strong class="{dot_class}">{status}</strong></div>
701
- <div><span>Shelf Location</span><strong>{shelf_loc}</strong></div>
702
  </div>
703
  """
704
 
@@ -707,13 +651,12 @@ def candidates_html(candidates: list[dict[str, Any]]) -> str:
707
  rows = []
708
  for rank, item in enumerate(candidates, start=1):
709
  score = round(item["score"] * 100)
710
- score_color = "var(--text-success)" if score > 80 else ("var(--text-warning)" if score > 50 else "var(--text-muted)")
711
  rows.append(
712
- f"<tr><td style='font-weight:600; color:var(--glow-cyan);'>{rank}</td><td style='font-weight:600;'>{item['label']}</td><td>{item['medicine']['name']}</td><td style='font-weight:700; color:{score_color};'>{score}%</td></tr>"
713
  )
714
  return f"""
715
  <table class="candidate-table">
716
- <thead><tr><th>Rank</th><th>Candidate Alias</th><th>Canonical Name</th><th>Match Score</th></tr></thead>
717
  <tbody>{''.join(rows)}</tbody>
718
  </table>
719
  """
@@ -726,25 +669,12 @@ def ocr_compare_html(
726
  confidence: int,
727
  plan: dict[str, Any],
728
  ) -> str:
729
- accepted = plan.get("status") == "validated" and confidence >= ACCEPTANCE_THRESHOLD
730
- corrected = display_name if accepted else f"Needs review: {display_name}"
731
- canonical = medicine['name'] if accepted else 'Not confirmed'
732
- corrected_color = "var(--text-success)" if accepted else "var(--text-warning)"
733
- canonical_color = "var(--glow-indigo)" if accepted else "var(--text-muted)"
734
  return f"""
735
  <div class="compare-grid">
736
- <div style="border-left: 3px solid var(--text-warning) !important;">
737
- <span>Raw OCR Output</span>
738
- <strong style="color: var(--text-warning) !important; font-family: monospace;">"{ocr_text}"</strong>
739
- </div>
740
- <div style="border-left: 3px solid {corrected_color} !important;">
741
- <span>AI Corrected Candidate</span>
742
- <strong style="color: {corrected_color} !important;">{corrected}</strong>
743
- </div>
744
- <div style="border-left: 3px solid {canonical_color} !important;">
745
- <span>Master Database Canonical</span>
746
- <strong style="color: {canonical_color} !important;">{canonical}</strong>
747
- </div>
748
  </div>
749
  """
750
 
@@ -898,564 +828,233 @@ def locate_on_shelf(shelf_image, state: dict[str, Any] | None):
898
 
899
 
900
  CSS = """
901
- @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;700&display=swap');
902
-
903
  :root {
904
- --glow-green: #10b981;
905
- --glow-green-hover: #059669;
906
- --glow-cyan: #06b6d4;
907
- --glow-indigo: #6366f1;
908
- --bg-dark: #070b13;
909
- --bg-glass: rgba(13, 20, 38, 0.65);
910
- --border-glass: rgba(255, 255, 255, 0.08);
911
- --border-active: rgba(16, 185, 129, 0.3);
912
- --text-main: #f1f5f9;
913
- --text-muted: #94a3b8;
914
- --text-success: #34d399;
915
- --text-warning: #fbbf24;
916
- --text-danger: #f87171;
917
  }
918
-
919
- /* Base Styles & Background Override */
920
  .gradio-container {
921
- background: radial-gradient(circle at 50% 0%, #0d172e 0%, #060911 100%) !important;
922
- color: var(--text-main) !important;
923
- font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif !important;
924
- min-height: 100vh;
925
- }
926
-
927
- /* Title text colors override */
928
- .gradio-container h1, .gradio-container h2, .gradio-container h3, .gradio-container h4, .gradio-container h5, .gradio-container h6 {
929
- color: #ffffff !important;
930
- font-weight: 700 !important;
 
 
 
 
931
  }
932
-
933
  .app-shell {
934
- width: min(1120px, calc(100vw - 48px));
935
  max-width: 1120px;
936
  margin: 0 auto;
937
  box-sizing: border-box;
938
  }
939
-
940
- /* Beautiful Hero Header */
941
  .hero {
942
- padding: 24px 32px;
943
  display: flex;
944
  align-items: center;
945
  justify-content: space-between;
946
  gap: 24px;
947
- border: 1px solid var(--border-glass) !important;
948
- border-top: 3px solid var(--glow-green) !important;
949
- border-radius: 16px !important;
950
- background: linear-gradient(135deg, rgba(16, 26, 48, 0.8), rgba(9, 14, 27, 0.8)) !important;
951
- backdrop-filter: blur(16px);
952
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4), 0 0 15px rgba(16, 185, 129, 0.05);
953
  margin-top: 24px;
954
  }
955
- .hero h1 {
956
- font-size: 36px;
957
- line-height: 1.1;
958
- margin: 0;
959
- background: linear-gradient(90deg, #ffffff, #06b6d4, #10b981);
960
- -webkit-background-clip: text;
961
- -webkit-text-fill-color: transparent;
962
- letter-spacing: -0.5px;
963
- }
964
- .hero p {
965
- margin: 8px 0 0;
966
- color: var(--text-muted) !important;
967
- font-size: 16px;
968
- }
969
  .powered {
970
- color: #60a5fa !important;
971
- font-weight: 700;
972
  text-align: right;
973
- background: rgba(30, 64, 175, 0.15);
974
- border: 1px solid rgba(96, 165, 250, 0.25);
975
- border-radius: 10px;
976
- padding: 8px 14px;
977
- font-size: 13px;
978
- box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.1);
979
- letter-spacing: 0.5px;
980
  }
981
-
982
- /* Metric KPI Layout */
983
  .metric-row {
984
  display: grid;
985
  grid-template-columns: repeat(4, minmax(0, 1fr));
986
- gap: 16px;
987
- width: min(1120px, calc(100vw - 48px));
988
  max-width: 1120px;
989
- margin: 20px auto 24px;
990
  box-sizing: border-box;
991
  }
992
  .metric {
993
- border: 1px solid var(--border-glass) !important;
994
- background: var(--bg-glass) !important;
995
- border-radius: 16px !important;
996
- padding: 20px;
997
- min-height: 96px;
998
- backdrop-filter: blur(12px);
999
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
1000
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1001
- display: flex;
1002
- flex-direction: column;
1003
- justify-content: space-between;
1004
- }
1005
- .metric:hover {
1006
- transform: translateY(-4px);
1007
- border-color: var(--glow-cyan) !important;
1008
- box-shadow: 0 15px 30px rgba(6, 182, 212, 0.15), 0 0 10px rgba(6, 182, 212, 0.05);
1009
- }
1010
- .metric-head {
1011
- display: flex;
1012
- align-items: center;
1013
- justify-content: space-between;
1014
- margin-bottom: 4px;
1015
- }
1016
- .metric span {
1017
- color: var(--text-muted) !important;
1018
- font-size: 13px;
1019
- font-weight: 500;
1020
- text-transform: uppercase;
1021
- letter-spacing: 0.5px;
1022
- }
1023
- .metric strong {
1024
- color: #ffffff !important;
1025
- font-size: 32px;
1026
- line-height: 1;
1027
- font-weight: 800;
1028
- margin-top: 4px;
1029
- letter-spacing: -0.5px;
1030
- background: linear-gradient(180deg, #ffffff 0%, #e2e8f0 100%);
1031
- -webkit-background-clip: text;
1032
- -webkit-text-fill-color: transparent;
1033
- }
1034
- .metric-icon {
1035
- width: 24px;
1036
- height: 24px;
1037
- color: var(--glow-cyan);
1038
- display: flex;
1039
- align-items: center;
1040
- justify-content: center;
1041
- background: rgba(6, 182, 212, 0.1);
1042
  border-radius: 8px;
1043
- padding: 4px;
 
 
1044
  }
1045
-
1046
- /* Step Capture Card & Upload */
1047
  .capture-card {
1048
- border: 1px solid var(--border-glass) !important;
1049
- background: var(--bg-glass) !important;
1050
- border-radius: 16px !important;
1051
- padding: 24px 28px;
1052
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
1053
- margin-bottom: 12px;
1054
- }
1055
- .capture-card h2 {
1056
- color: #ffffff !important;
1057
- margin: 0 0 8px;
1058
- font-size: 22px;
1059
- font-weight: 600;
1060
- }
1061
- .capture-card p {
1062
- color: var(--text-muted) !important;
1063
- margin: 0;
1064
- font-size: 14px;
1065
  }
1066
-
1067
- /* Beautiful Stepper Diagnostics Trace */
1068
  .pipeline {
1069
- border: 1px solid var(--border-glass) !important;
1070
- background: var(--bg-glass) !important;
1071
- border-radius: 16px !important;
1072
- padding: 24px;
1073
  margin: 0;
1074
- backdrop-filter: blur(12px);
1075
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
1076
- }
1077
- .pipeline-title {
1078
- color: #ffffff !important;
1079
- font-weight: 700;
1080
- margin-bottom: 20px;
1081
- font-size: 16px;
1082
- text-transform: uppercase;
1083
- letter-spacing: 0.5px;
1084
- display: flex;
1085
- align-items: center;
1086
- gap: 8px;
1087
- }
1088
- .flow {
1089
- display: grid;
1090
- grid-template-columns: repeat(5, minmax(0, 1fr));
1091
- gap: 12px;
1092
- position: relative;
1093
  }
 
 
1094
  .flow-step {
1095
- min-height: 96px;
1096
- border: 1px solid var(--border-glass) !important;
1097
- border-radius: 12px !important;
1098
- background: rgba(255, 255, 255, 0.02) !important;
1099
- padding: 12px;
1100
- text-align: center;
1101
- display: flex;
1102
- flex-direction: column;
1103
- align-items: center;
1104
- justify-content: flex-start;
1105
- transition: all 0.3s ease;
1106
- position: relative;
1107
  }
1108
  .flow-step span {
1109
- width: 32px;
1110
- height: 32px;
1111
  border-radius: 50%;
1112
  display: inline-grid;
1113
  place-items: center;
1114
- background: rgba(255, 255, 255, 0.05);
1115
- color: var(--text-muted);
1116
- font-weight: 700;
1117
- font-size: 14px;
1118
- margin-bottom: 12px;
1119
- border: 1px solid var(--border-glass);
1120
- }
1121
- .flow-step.done {
1122
- border-color: var(--border-active) !important;
1123
- background: rgba(16, 185, 129, 0.04) !important;
1124
- box-shadow: 0 0 15px rgba(16, 185, 129, 0.05);
1125
- }
1126
- .flow-step.done span {
1127
- background: var(--glow-green);
1128
- color: #ffffff;
1129
- border: none;
1130
- box-shadow: 0 0 10px rgba(16, 185, 129, 0.4);
1131
- }
1132
- .flow-step.current {
1133
- border-color: rgba(6, 182, 212, 0.5) !important;
1134
- background: rgba(6, 182, 212, 0.06) !important;
1135
- box-shadow: 0 0 15px rgba(6, 182, 212, 0.15);
1136
- animation: pulse-step 2s infinite ease-in-out;
1137
- }
1138
- .flow-step.current span {
1139
- background: var(--glow-cyan);
1140
- color: #ffffff;
1141
- border: none;
1142
- box-shadow: 0 0 10px rgba(6, 182, 212, 0.4);
1143
- }
1144
- .flow-step strong {
1145
- color: #ffffff !important;
1146
- display: block;
1147
- font-size: 13px;
1148
- font-weight: 600;
1149
- }
1150
- @keyframes pulse-step {
1151
- 0% { box-shadow: 0 0 0 0 rgba(6, 182, 212, 0.4); }
1152
- 70% { box-shadow: 0 0 0 6px rgba(6, 182, 212, 0); }
1153
- 100% { box-shadow: 0 0 0 0 rgba(6, 182, 212, 0); }
1154
- }
1155
-
1156
- /* Monospace Diagnostics Console Logs */
1157
- .logs {
1158
- margin: 20px 0 0;
1159
- padding: 16px !important;
1160
- background: #05080e !important;
1161
- border: 1px solid rgba(255, 255, 255, 0.05) !important;
1162
- border-radius: 10px;
1163
- font-family: 'JetBrains Mono', 'Courier New', Courier, monospace;
1164
- color: #10b981 !important;
1165
- box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.8);
1166
- font-size: 13px;
1167
- list-style-type: none;
1168
- max-height: 120px;
1169
- overflow-y: auto;
1170
- }
1171
- .logs li {
1172
- margin-bottom: 6px;
1173
- line-height: 1.5;
1174
- display: flex;
1175
- align-items: center;
1176
- gap: 8px;
1177
- }
1178
- .logs li::before {
1179
- content: ">";
1180
- color: var(--glow-cyan);
1181
- font-weight: 700;
1182
- }
1183
-
1184
- /* Beautiful Result Card & Medical Details */
1185
- .result-card {
1186
- border: 1px solid var(--border-glass) !important;
1187
- background: var(--bg-glass) !important;
1188
- border-radius: 16px !important;
1189
- padding: 24px;
1190
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
1191
- }
1192
- .result-card h3 {
1193
- color: #ffffff !important;
1194
- margin: 0 0 16px;
1195
- font-size: 20px;
1196
- font-weight: 700;
1197
- border-left: 3px solid var(--glow-green);
1198
- padding-left: 10px;
1199
  }
 
1200
  .details {
1201
  display: grid;
1202
  grid-template-columns: 140px 1fr;
1203
- gap: 10px 16px;
1204
- margin: 0;
1205
- }
1206
- .details dt {
1207
- color: var(--text-muted) !important;
1208
- font-size: 14px;
1209
- font-weight: 500;
1210
- }
1211
- .details dd {
1212
- color: #ffffff !important;
1213
  margin: 0;
1214
- font-weight: 650;
1215
- font-size: 14px;
1216
  }
 
 
1217
  .explain {
1218
- margin-top: 20px;
1219
- border-top: 1px solid var(--border-glass);
1220
- padding-top: 18px;
1221
- }
1222
- .explain h4 {
1223
- color: #ffffff !important;
1224
- margin: 0 0 12px;
1225
- font-size: 16px;
1226
- font-weight: 600;
1227
- }
1228
- .explain p {
1229
- margin: 8px 0;
1230
- color: var(--text-main) !important;
1231
- font-size: 13.5px;
1232
- line-height: 1.6;
1233
- }
1234
- .explain p b {
1235
- color: var(--glow-cyan);
1236
  }
1237
-
1238
- /* Stock Indicators */
1239
  .stock-card {
1240
  display: grid;
1241
  grid-template-columns: repeat(3, minmax(0, 1fr));
1242
- gap: 16px;
1243
- margin-top: 12px;
1244
- border: 1px solid var(--border-glass) !important;
1245
- background: var(--bg-glass) !important;
1246
- border-radius: 16px !important;
1247
- padding: 18px;
1248
- }
1249
- .stock-card span {
1250
- color: var(--text-muted) !important;
1251
- font-size: 12px;
1252
- font-weight: 500;
1253
- text-transform: uppercase;
1254
- }
1255
- .stock-card strong {
1256
- color: #ffffff !important;
1257
- display: block;
1258
- font-size: 22px;
1259
- margin-top: 4px;
1260
- font-weight: 700;
1261
- }
1262
- .stock-card .ok {
1263
- color: var(--text-success) !important;
1264
- }
1265
- .stock-card .bad {
1266
- color: var(--text-danger) !important;
1267
- }
1268
-
1269
- /* Table Candidates */
1270
- .candidate-table {
1271
- width: 100%;
1272
- border-collapse: collapse;
1273
- background: transparent;
1274
  }
 
 
 
 
1275
  .candidate-table th, .candidate-table td {
1276
- border-bottom: 1px solid var(--border-glass);
1277
- padding: 12px 14px;
1278
  text-align: left;
1279
- color: var(--text-main) !important;
1280
- font-size: 13.5px;
1281
  }
1282
- .candidate-table th {
1283
- color: var(--text-muted) !important;
1284
- font-weight: 600;
1285
- text-transform: uppercase;
1286
- font-size: 12px;
1287
- background: rgba(255, 255, 255, 0.02);
1288
- }
1289
- .candidate-table tbody tr {
1290
- transition: background 0.2s ease;
1291
- }
1292
- .candidate-table tbody tr:hover {
1293
- background: rgba(255, 255, 255, 0.02);
1294
- }
1295
-
1296
- /* Compare Grid */
1297
  .compare-grid {
1298
  display: grid;
1299
  grid-template-columns: repeat(3, minmax(0, 1fr));
1300
- gap: 16px;
1301
- margin-top: 12px;
1302
  }
1303
  .compare-grid div {
1304
- border: 1px solid var(--border-glass) !important;
1305
- border-radius: 12px !important;
1306
- padding: 16px;
1307
- background: var(--bg-glass) !important;
1308
- box-shadow: 0 4px 15px rgba(0,0,0,0.15);
1309
- transition: all 0.3s ease;
1310
- }
1311
- .compare-grid div:hover {
1312
- transform: translateY(-2px);
1313
- border-color: rgba(99, 102, 241, 0.3) !important;
1314
- }
1315
- .compare-grid strong {
1316
- color: #ffffff !important;
1317
- display: block;
1318
- margin-top: 6px;
1319
- font-size: 17px;
1320
- font-weight: 700;
1321
  }
1322
-
1323
- /* Translated Prescription Output */
1324
  .translated-card {
1325
- border: 1px solid var(--border-glass) !important;
1326
- background: var(--bg-glass) !important;
1327
- border-radius: 16px !important;
1328
- padding: 24px;
1329
- margin-top: 16px;
1330
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
1331
  }
1332
  .translated-head {
1333
  display: flex;
1334
  justify-content: space-between;
1335
  gap: 12px;
1336
  align-items: center;
1337
- margin-bottom: 20px;
1338
- }
1339
- .translated-head h3 {
1340
- color: #ffffff !important;
1341
- margin: 0;
1342
- font-size: 20px;
1343
- font-weight: 700;
1344
  }
 
1345
  .status-pill {
1346
- background: rgba(16, 185, 129, 0.12);
1347
- border: 1px solid var(--border-active);
1348
- color: var(--text-success) !important;
1349
  border-radius: 999px;
1350
- padding: 5px 12px;
1351
  font-size: 12px;
1352
- font-weight: 700;
1353
- text-transform: uppercase;
1354
- letter-spacing: 0.5px;
1355
- }
1356
- .status-pill.needs-review {
1357
- background: rgba(245, 158, 11, 0.12);
1358
- border: 1px solid rgba(245, 158, 11, 0.3);
1359
- color: var(--text-warning) !important;
1360
  }
1361
  .translated-details {
1362
- grid-template-columns: 140px 1fr;
1363
  }
1364
  .fine-print {
1365
- border-top: 1px solid var(--border-glass);
1366
- color: var(--text-muted) !important;
1367
- margin: 20px 0 0;
1368
- padding-top: 16px;
1369
- font-size: 12.5px;
1370
- line-height: 1.5;
1371
  }
1372
-
1373
- /* Action Buttons Custom Styling */
1374
  .gradio-container button.primary,
1375
  .gradio-container button[variant="primary"] {
1376
- background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
1377
- border: none !important;
1378
- color: #ffffff !important;
1379
- font-weight: 700 !important;
1380
- text-transform: uppercase !important;
1381
- letter-spacing: 0.5px !important;
1382
- border-radius: 10px !important;
1383
- padding: 12px 24px !important;
1384
- box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3) !important;
1385
- transition: all 0.2s ease !important;
1386
- }
1387
- .gradio-container button.primary:hover {
1388
- transform: translateY(-2px) !important;
1389
- box-shadow: 0 6px 20px rgba(16, 185, 129, 0.45), 0 0 12px rgba(16, 185, 129, 0.2) !important;
1390
- }
1391
- .gradio-container button.primary:active {
1392
- transform: translateY(0px) !important;
1393
- }
1394
-
1395
- /* Secondary Buttons */
1396
- .gradio-container button.secondary,
1397
- .gradio-container button:not(.primary) {
1398
- background: rgba(255, 255, 255, 0.04) !important;
1399
- border: 1px solid var(--border-glass) !important;
1400
  color: #ffffff !important;
1401
- border-radius: 10px !important;
1402
- font-weight: 600 !important;
1403
- transition: all 0.2s ease !important;
1404
  }
1405
- .gradio-container button:not(.primary):hover {
1406
- background: rgba(255, 255, 255, 0.08) !important;
1407
- border-color: rgba(255, 255, 255, 0.15) !important;
1408
  }
1409
-
1410
- /* Custom Accordion overrides */
1411
- .gradio-container .accordion {
1412
- border: 1px solid var(--border-glass) !important;
1413
- background: var(--bg-glass) !important;
1414
- border-radius: 16px !important;
1415
- overflow: hidden;
1416
- box-shadow: 0 4px 15px rgba(0,0,0,0.15);
1417
- }
1418
- .gradio-container .accordion .label-wrap {
1419
- background: rgba(255, 255, 255, 0.02) !important;
1420
- padding: 14px 18px !important;
1421
- border-bottom: 1px solid var(--border-glass) !important;
1422
- }
1423
- .gradio-container .accordion .label-wrap span {
1424
- color: #ffffff !important;
1425
- font-weight: 600 !important;
1426
- font-size: 15px;
1427
- }
1428
-
1429
- /* Gradio input boxes styling to fit theme */
1430
- .gradio-container input, .gradio-container textarea, .gradio-container select {
1431
- background: rgba(0, 0, 0, 0.3) !important;
1432
- border: 1px solid var(--border-glass) !important;
1433
- color: #ffffff !important;
1434
- border-radius: 8px !important;
1435
- }
1436
- .gradio-container input:focus, .gradio-container textarea:focus {
1437
- border-color: var(--glow-cyan) !important;
1438
- box-shadow: 0 0 8px rgba(6, 182, 212, 0.2) !important;
1439
- }
1440
-
1441
- /* Fix mobile responsiveness */
1442
- @media (max-width: 768px) {
1443
- .app-shell { width: min(100% - 24px, 1120px); }
1444
- .hero { display: block; padding: 20px; }
1445
- .powered { text-align: left; margin-top: 12px; display: inline-block; }
1446
  .metric-row, .flow, .stock-card, .compare-grid { grid-template-columns: 1fr; }
1447
  .details { grid-template-columns: 1fr; }
1448
- .translated-head { align-items: flex-start; flex-direction: column; gap: 8px; }
1449
- .flow-step { min-height: 80px; }
1450
  }
1451
  """
1452
 
1453
 
1454
- APP_THEME = gr.themes.Default(
1455
  primary_hue="emerald",
1456
- secondary_hue="indigo",
1457
  neutral_hue="slate",
1458
- font=[gr.themes.GoogleFont("Plus Jakarta Sans"), "ui-sans-serif", "system-ui", "sans-serif"],
1459
  )
1460
 
1461
 
 
474
  elif fallback_path.exists():
475
  text = fallback_path.read_text(encoding="utf-8", errors="ignore")
476
  if "ocr_accuracy" in text:
477
+ # Keep a conservative fallback tied to the checked-in report values.
478
  ocr_accuracy = 0.37888446215139443
479
  retrieval_accuracy = 0.6055776892430279
480
 
 
487
  return f"""
488
  <div class="app-shell">
489
  <div class="metric-row">
490
+ <div class="metric"><span>Session Searches</span><strong>{searches}</strong></div>
491
+ <div class="metric"><span>Retrieval Recovery</span><strong>{retrieval_text}</strong></div>
492
+ <div class="metric"><span>Medicines Indexed</span><strong>{indexed}</strong></div>
493
+ <div class="metric"><span>Rx Samples Tested</span><strong>2,510</strong></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  </div>
495
  </div>
496
  """
 
503
  go.Indicator(
504
  mode="gauge+number",
505
  value=confidence,
506
+ number={"suffix": "%", "font": {"size": 34}},
507
  gauge={
508
+ "axis": {"range": [0, 100], "tickwidth": 1},
509
+ "bar": {"color": "#0f9f6e"},
510
+ "bgcolor": "white",
511
+ "borderwidth": 0,
 
512
  "steps": [
513
+ {"range": [0, 60], "color": "#fee2e2"},
514
+ {"range": [60, 85], "color": "#fef3c7"},
515
+ {"range": [85, 100], "color": "#dcfce7"},
516
  ],
517
  },
518
  )
519
  )
520
+ fig.update_layout(height=230, margin=dict(l=20, r=20, t=20, b=8), paper_bgcolor="rgba(0,0,0,0)")
 
 
 
 
 
521
  return fig
522
 
523
 
 
538
  cards = []
539
  logs = []
540
  for i, (title, status) in enumerate(steps, start=1):
541
+ active = i <= stage
 
 
 
 
 
 
542
  cards.append(
543
  f"""
544
+ <div class="flow-step {'done' if active else ''}">
545
  <span>{i}</span>
546
  <strong>{title}</strong>
547
  </div>
548
  """
549
  )
550
+ if active:
551
+ logs.append(f"<li>{title} {status}</li>")
 
 
 
 
 
552
  return f"""
553
  <div class="pipeline">
554
+ <div class="pipeline-title">Actual Run Trace</div>
 
 
 
555
  <div class="flow">{''.join(cards)}</div>
556
  <ul class="logs">{''.join(logs)}</ul>
557
  </div>
 
581
  )
582
  return f"""
583
  <div class="result-card">
584
+ <h3>Prescription Details</h3>
585
  <dl class="details">
586
  <dt>Medicine</dt><dd>{medicine_label}</dd>
587
+ <dt>Generic</dt><dd>{generic_label}</dd>
588
  <dt>Strength</dt><dd>{strength_label}</dd>
589
  <dt>Manufacturer</dt><dd>{manufacturer_label}</dd>
590
+ <dt>Confidence</dt><dd>{confidence}%</dd>
591
+ <dt>Category</dt><dd>{category_label}</dd>
592
  <dt>Price</dt><dd>{price_label}</dd>
593
  </dl>
594
  <div class="explain">
595
+ <h4>AI Explanation</h4>
596
  <p><b>OCR detected:</b> "{ocr_text}"</p>
597
+ <p><b>Retrieved:</b> {display_name} ({medicine.get('name', 'Unknown')})</p>
598
+ <p><b>Validation:</b> {validation_label}</p>
599
+ <p><b>Inventory:</b> {inventory_label}</p>
600
  </div>
601
  </div>
602
  """
 
614
  ("Instructions", plan.get("instructions") or "Pharmacist review required"),
615
  ]
616
  row_html = "".join(f"<dt>{label}</dt><dd>{value}</dd>" for label, value in rows)
617
+ status = plan.get("status", "needs_review").replace("_", " ").title()
 
 
 
 
618
  return f"""
619
  <div class="translated-card">
620
  <div class="translated-head">
621
+ <h3>Translated Prescription</h3>
622
+ <span class="status-pill">{status}</span>
623
  </div>
624
  <dl class="details translated-details">{row_html}</dl>
625
+ <p class="fine-print">Generated from OCR text and retrieval candidates. Confirm before dispensing.</p>
626
  </div>
627
  """
628
 
 
631
  if not accepted:
632
  return """
633
  <div class="stock-card">
634
+ <div><span>Available Stock</span><strong>-</strong></div>
635
  <div><span>Status</span><strong class="bad">Needs Review</strong></div>
636
+ <div><span>Shelf</span><strong>Hidden</strong></div>
637
  </div>
638
  """
639
  status = "In Stock" if inventory.get("quantity", 0) > 0 else "Out of Stock"
640
  dot_class = "ok" if inventory.get("quantity", 0) > 0 else "bad"
 
641
  return f"""
642
  <div class="stock-card">
643
  <div><span>Available Stock</span><strong>{inventory.get('quantity', 0)}</strong></div>
644
  <div><span>Status</span><strong class="{dot_class}">{status}</strong></div>
645
+ <div><span>Shelf</span><strong>{inventory.get('shelf', '-')} · Row {inventory.get('row', '-')}</strong></div>
646
  </div>
647
  """
648
 
 
651
  rows = []
652
  for rank, item in enumerate(candidates, start=1):
653
  score = round(item["score"] * 100)
 
654
  rows.append(
655
+ f"<tr><td>{rank}</td><td>{item['label']}</td><td>{item['medicine']['name']}</td><td>{score}%</td></tr>"
656
  )
657
  return f"""
658
  <table class="candidate-table">
659
+ <thead><tr><th>#</th><th>Candidate</th><th>Canonical</th><th>Score</th></tr></thead>
660
  <tbody>{''.join(rows)}</tbody>
661
  </table>
662
  """
 
669
  confidence: int,
670
  plan: dict[str, Any],
671
  ) -> str:
672
+ corrected = display_name if plan.get("status") == "validated" else f"Needs review: {display_name}"
 
 
 
 
673
  return f"""
674
  <div class="compare-grid">
675
+ <div><span>OCR Output</span><strong>{ocr_text}</strong></div>
676
+ <div><span>AI Corrected</span><strong>{corrected}</strong></div>
677
+ <div><span>Canonical</span><strong>{medicine['name'] if plan.get('status') == 'validated' else 'Not confirmed'}</strong></div>
 
 
 
 
 
 
 
 
 
678
  </div>
679
  """
680
 
 
828
 
829
 
830
  CSS = """
 
 
831
  :root {
832
+ --green: #0f9f6e;
833
+ --ink: #14213d;
834
+ --muted: #5f6b7a;
835
+ --line: #d9e2ec;
836
+ --soft: #f4f8f7;
837
+ --blue: #1769aa;
 
 
 
 
 
 
 
838
  }
 
 
839
  .gradio-container {
840
+ background: linear-gradient(180deg, #f8fbfb 0%, #edf6f4 100%);
841
+ color: var(--ink) !important;
842
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
843
+ }
844
+ .gradio-container h1,
845
+ .gradio-container h2,
846
+ .gradio-container h3,
847
+ .gradio-container h4,
848
+ .gradio-container p,
849
+ .gradio-container label,
850
+ .gradio-container span,
851
+ .gradio-container strong,
852
+ .gradio-container div {
853
+ letter-spacing: 0;
854
  }
 
855
  .app-shell {
856
+ width: min(1120px, calc(100vw - 64px));
857
  max-width: 1120px;
858
  margin: 0 auto;
859
  box-sizing: border-box;
860
  }
 
 
861
  .hero {
862
+ padding: 24px 28px;
863
  display: flex;
864
  align-items: center;
865
  justify-content: space-between;
866
  gap: 24px;
867
+ border: 1px solid var(--line);
868
+ border-radius: 8px;
869
+ background: #ffffff;
870
+ box-shadow: 0 10px 32px rgba(15, 23, 42, 0.06);
 
 
871
  margin-top: 24px;
872
  }
873
+ .hero h1 { color: var(--ink) !important; font-size: 34px; line-height: 1; margin: 0; letter-spacing: 0; }
874
+ .hero p { margin: 8px 0 0; color: var(--muted) !important; font-size: 16px; }
 
 
 
 
 
 
 
 
 
 
 
 
875
  .powered {
876
+ color: var(--blue) !important;
877
+ font-weight: 800;
878
  text-align: right;
879
+ background: #edf7ff;
880
+ border: 1px solid #cfe7fb;
881
+ border-radius: 8px;
882
+ padding: 10px 12px;
 
 
 
883
  }
 
 
884
  .metric-row {
885
  display: grid;
886
  grid-template-columns: repeat(4, minmax(0, 1fr));
887
+ gap: 12px;
888
+ width: min(1120px, calc(100vw - 64px));
889
  max-width: 1120px;
890
+ margin: 16px auto 24px;
891
  box-sizing: border-box;
892
  }
893
  .metric {
894
+ border: 1px solid var(--line);
895
+ background: #ffffff;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
896
  border-radius: 8px;
897
+ padding: 16px 18px;
898
+ min-height: 86px;
899
+ box-shadow: 0 10px 26px rgba(15, 23, 42, 0.045);
900
  }
901
+ .metric span, .stock-card span, .compare-grid span { color: var(--muted) !important; display: block; font-size: 13px; }
902
+ .metric strong { color: var(--ink) !important; display: block; font-size: 27px; margin-top: 6px; }
903
  .capture-card {
904
+ border: 1px solid var(--line);
905
+ background: #ffffff;
906
+ border-radius: 8px;
907
+ padding: 28px;
908
+ min-height: 132px;
909
+ box-shadow: 0 10px 26px rgba(15, 23, 42, 0.045);
 
 
 
 
 
 
 
 
 
 
 
910
  }
911
+ .capture-card h2 { color: var(--ink) !important; margin: 0 0 10px; font-size: 24px; }
912
+ .capture-card p { color: var(--muted) !important; margin: 0; }
913
  .pipeline {
914
+ border: 1px solid var(--line);
915
+ background: #ffffff;
916
+ border-radius: 8px;
917
+ padding: 20px;
918
  margin: 0;
919
+ min-height: 132px;
920
+ box-shadow: 0 10px 26px rgba(15, 23, 42, 0.045);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
921
  }
922
+ .pipeline-title { color: var(--ink) !important; font-weight: 800; margin-bottom: 12px; }
923
+ .flow { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
924
  .flow-step {
925
+ min-height: 86px;
926
+ border: 1px solid var(--line);
927
+ border-radius: 8px;
928
+ background: var(--soft);
929
+ padding: 10px;
 
 
 
 
 
 
 
930
  }
931
  .flow-step span {
932
+ width: 26px;
933
+ height: 26px;
934
  border-radius: 50%;
935
  display: inline-grid;
936
  place-items: center;
937
+ background: #d8e7e3;
938
+ color: var(--ink);
939
+ font-weight: 800;
940
+ margin-bottom: 8px;
941
+ }
942
+ .flow-step.done { border-color: #85d7bd; background: #ebfbf5; }
943
+ .flow-step.done span { background: var(--green); color: #ffffff; }
944
+ .flow-step strong { color: var(--ink) !important; display: block; font-size: 14px; }
945
+ .logs { margin: 14px 0 0; padding-left: 20px; color: #174c3c !important; }
946
+ .result-card, .stock-card {
947
+ border: 1px solid var(--line);
948
+ background: #ffffff;
949
+ border-radius: 8px;
950
+ padding: 18px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
  }
952
+ .result-card h3 { color: var(--ink) !important; margin: 0 0 12px; font-size: 20px; }
953
  .details {
954
  display: grid;
955
  grid-template-columns: 140px 1fr;
956
+ gap: 8px 14px;
 
 
 
 
 
 
 
 
 
957
  margin: 0;
 
 
958
  }
959
+ .details dt { color: var(--muted) !important; }
960
+ .details dd { color: var(--ink) !important; margin: 0; font-weight: 750; }
961
  .explain {
962
+ margin-top: 16px;
963
+ border-top: 1px solid var(--line);
964
+ padding-top: 14px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
965
  }
966
+ .explain h4 { color: var(--ink) !important; margin: 0 0 8px; }
967
+ .explain p { margin: 6px 0; color: #263445 !important; }
968
  .stock-card {
969
  display: grid;
970
  grid-template-columns: repeat(3, minmax(0, 1fr));
971
+ gap: 12px;
972
+ margin-top: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
  }
974
+ .stock-card strong { color: var(--ink) !important; display: block; font-size: 20px; margin-top: 3px; }
975
+ .stock-card .ok { color: var(--green); }
976
+ .stock-card .bad { color: #b91c1c; }
977
+ .candidate-table { width: 100%; border-collapse: collapse; background: #ffffff; }
978
  .candidate-table th, .candidate-table td {
979
+ border-bottom: 1px solid var(--line);
980
+ padding: 10px 8px;
981
  text-align: left;
982
+ color: var(--ink) !important;
 
983
  }
984
+ .candidate-table th { color: var(--muted) !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
985
  .compare-grid {
986
  display: grid;
987
  grid-template-columns: repeat(3, minmax(0, 1fr));
988
+ gap: 12px;
 
989
  }
990
  .compare-grid div {
991
+ border: 1px solid var(--line);
992
+ border-radius: 8px;
993
+ padding: 14px;
994
+ background: #ffffff;
 
 
 
 
 
 
 
 
 
 
 
 
 
995
  }
996
+ .compare-grid strong { color: var(--ink) !important; display: block; margin-top: 6px; font-size: 18px; }
 
997
  .translated-card {
998
+ border: 1px solid var(--line);
999
+ background: #ffffff;
1000
+ border-radius: 8px;
1001
+ padding: 18px;
1002
+ margin-top: 12px;
 
1003
  }
1004
  .translated-head {
1005
  display: flex;
1006
  justify-content: space-between;
1007
  gap: 12px;
1008
  align-items: center;
1009
+ margin-bottom: 12px;
 
 
 
 
 
 
1010
  }
1011
+ .translated-head h3 { color: var(--ink) !important; margin: 0; font-size: 20px; }
1012
  .status-pill {
1013
+ background: #ebfbf5;
1014
+ border: 1px solid #85d7bd;
1015
+ color: #075f45;
1016
  border-radius: 999px;
1017
+ padding: 5px 10px;
1018
  font-size: 12px;
1019
+ font-weight: 800;
 
 
 
 
 
 
 
1020
  }
1021
  .translated-details {
1022
+ grid-template-columns: 130px 1fr;
1023
  }
1024
  .fine-print {
1025
+ border-top: 1px solid var(--line);
1026
+ color: var(--muted) !important;
1027
+ margin: 14px 0 0;
1028
+ padding-top: 12px;
1029
+ font-size: 13px;
 
1030
  }
1031
+ .compact { margin-top: 0; }
 
1032
  .gradio-container button.primary,
1033
  .gradio-container button[variant="primary"] {
1034
+ background: var(--green) !important;
1035
+ border-color: var(--green) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1036
  color: #ffffff !important;
 
 
 
1037
  }
1038
+ .gradio-container button.primary:hover,
1039
+ .gradio-container button[variant="primary"]:hover {
1040
+ background: #0b7f59 !important;
1041
  }
1042
+ @media (max-width: 760px) {
1043
+ .app-shell { width: min(100% - 28px, 1120px); }
1044
+ .hero { display: block; }
1045
+ .powered { text-align: left; margin-top: 10px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1046
  .metric-row, .flow, .stock-card, .compare-grid { grid-template-columns: 1fr; }
1047
  .details { grid-template-columns: 1fr; }
1048
+ .translated-head { align-items: flex-start; flex-direction: column; }
 
1049
  }
1050
  """
1051
 
1052
 
1053
+ APP_THEME = gr.themes.Soft(
1054
  primary_hue="emerald",
1055
+ secondary_hue="blue",
1056
  neutral_hue="slate",
1057
+ font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
1058
  )
1059
 
1060