Rajan Sharma commited on
Commit
f051f2e
·
verified ·
1 Parent(s): 96de12b

Upload 14 files

Browse files
README.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ClarityOps (Executive + Uploads Ready)
2
+
3
+ 1) Install deps:
4
+ pip install -r requirements.txt
5
+
6
+ 2) Add policies (.txt/.md) to /policies and build the index:
7
+ python build_policy_index.py
8
+
9
+ 3) Keep snapshots/current.json updated daily (manual or automated).
10
+
11
+ 4) Set up model auth if needed (HF_TOKEN or COHERE_API_KEY).
12
+
13
+ 5) Run the app:
14
+ python app.py
15
+
16
+ Uploads: You can attach PDFs, DOCX, CSVs, PNG/JPG. Images are OCR'd.
17
+ Scenario Context: Paste executive briefs/case studies here.
18
+
19
+ To reduce cost per screen client: increase daily throughput, amortize fixed costs,
20
+ or adjust logistics via route optimization and kit standardization.
app.py CHANGED
@@ -1,35 +1,23 @@
1
- import os
2
- import re
3
- import json
4
  from functools import lru_cache
5
 
6
  import gradio as gr
7
  import torch
8
 
9
- # -------------------
10
- # Writable caches for HF + Gradio (fixes PermissionError in Spaces)
11
- # -------------------
12
  os.environ.setdefault("HF_HOME", "/data/.cache/huggingface")
13
  os.environ.setdefault("HF_HUB_CACHE", "/data/.cache/huggingface/hub")
14
  os.environ.setdefault("GRADIO_TEMP_DIR", "/data/gradio")
15
  os.environ.setdefault("GRADIO_CACHE_DIR", "/data/gradio")
 
 
 
16
 
17
- for p in [
18
- "/data/.cache/huggingface/hub",
19
- "/data/gradio",
20
- ]:
21
- try:
22
- os.makedirs(p, exist_ok=True)
23
- except Exception:
24
- pass
25
-
26
- # Timezone (Python 3.9+)
27
  try:
28
  from zoneinfo import ZoneInfo
29
  except Exception:
30
  ZoneInfo = None
31
 
32
- # Cohere SDK (hosted path)
33
  try:
34
  import cohere
35
  _HAS_COHERE = True
@@ -39,57 +27,35 @@ except Exception:
39
  from transformers import AutoTokenizer, AutoModelForCausalLM
40
  from huggingface_hub import login
41
 
42
- # -------------------
43
- # NEW: Safety imports
44
- # -------------------
45
  from safety import safety_filter, refusal_reply
46
-
47
- # -------------------
48
- # NEW: Augmentation imports
49
- # -------------------
50
  from retriever import init_retriever, retrieve_context
51
  from decision_math import compute_operational_numbers
52
  from prompt_templates import build_system_preamble
 
 
 
53
 
54
- # -------------------
55
- # Config
56
- # -------------------
57
  MODEL_ID = os.getenv("MODEL_ID", "CohereLabs/c4ai-command-r7b-12-2024")
58
  HF_TOKEN = os.getenv("HUGGINGFACE_HUB_TOKEN") or os.getenv("HF_TOKEN")
59
  COHERE_API_KEY = os.getenv("COHERE_API_KEY")
60
  USE_HOSTED_COHERE = bool(COHERE_API_KEY and _HAS_COHERE)
61
 
62
- # -------------------
63
- # Helpers
64
- # -------------------
65
  def pick_dtype_and_map():
66
- if torch.cuda.is_available():
67
- return torch.float16, "auto"
68
- if torch.backends.mps.is_available():
69
- return torch.float16, {"": "mps"}
70
  return torch.float32, "cpu"
71
 
72
  def is_identity_query(message, history):
73
  patterns = [
74
- r"\bwho\s+are\s+you\b",
75
- r"\bwhat\s+are\s+you\b",
76
- r"\bwhat\s+is\s+your\s+name\b",
77
- r"\bwho\s+is\s+this\b",
78
- r"\bidentify\s+yourself\b",
79
- r"\btell\s+me\s+about\s+yourself\b",
80
- r"\bdescribe\s+yourself\b",
81
- r"\band\s+you\s*\?\b",
82
- r"\byour\s+name\b",
83
- r"\bwho\s+am\s+i\s+chatting\s+with\b"
84
  ]
85
- def match(t):
86
- return any(re.search(p, (t or "").strip().lower()) for p in patterns)
87
- if match(message):
88
- return True
89
  if history:
90
  last_user = history[-1][0] if isinstance(history[-1], (list, tuple)) else None
91
- if match(last_user):
92
- return True
93
  return False
94
 
95
  def _iter_user_assistant(history):
@@ -102,17 +68,12 @@ def _iter_user_assistant(history):
102
  def _history_to_prompt(message, history):
103
  parts = []
104
  for u, a in _iter_user_assistant(history):
105
- if u:
106
- parts.append(f"User: {u}")
107
- if a:
108
- parts.append(f"Assistant: {a}")
109
  parts.append(f"User: {message}")
110
  parts.append("Assistant:")
111
  return "\n".join(parts)
112
 
113
- # -------------------
114
- # Cohere Hosted
115
- # -------------------
116
  _co_client = None
117
  if USE_HOSTED_COHERE:
118
  _co_client = cohere.Client(api_key=COHERE_API_KEY)
@@ -124,42 +85,26 @@ def cohere_chat(message, history):
124
  model="command-r7b-12-2024",
125
  message=prompt,
126
  temperature=0.3,
127
- max_tokens=350,
128
  )
129
- if hasattr(resp, "text") and resp.text:
130
- return resp.text.strip()
131
- if hasattr(resp, "reply") and resp.reply:
132
- return resp.reply.strip()
133
- if hasattr(resp, "generations") and resp.generations:
134
- return resp.generations[0].text.strip()
135
  return "Sorry, I couldn't parse the response from Cohere."
136
  except Exception as e:
137
  return f"Error calling Cohere API: {e}"
138
 
139
- # -------------------
140
- # Local HF Model
141
- # -------------------
142
  @lru_cache(maxsize=1)
143
  def load_local_model():
144
- if not HF_TOKEN:
145
- raise RuntimeError("HUGGINGFACE_HUB_TOKEN is not set.")
146
  login(token=HF_TOKEN, add_to_git_credential=False)
147
  dtype, device_map = pick_dtype_and_map()
148
  tok = AutoTokenizer.from_pretrained(
149
- MODEL_ID,
150
- token=HF_TOKEN,
151
- use_fast=True,
152
- model_max_length=4096,
153
- padding_side="left",
154
- trust_remote_code=True,
155
  )
156
  mdl = AutoModelForCausalLM.from_pretrained(
157
- MODEL_ID,
158
- token=HF_TOKEN,
159
- device_map=device_map,
160
- low_cpu_mem_usage=True,
161
- torch_dtype=dtype,
162
- trust_remote_code=True,
163
  )
164
  if mdl.config.eos_token_id is None and tok.eos_token_id is not None:
165
  mdl.config.eos_token_id = tok.eos_token_id
@@ -168,199 +113,138 @@ def load_local_model():
168
  def build_inputs(tokenizer, message, history):
169
  msgs = []
170
  for u, a in _iter_user_assistant(history):
171
- if u:
172
- msgs.append({"role": "user", "content": u})
173
- if a:
174
- msgs.append({"role": "assistant", "content": a})
175
  msgs.append({"role": "user", "content": message})
176
- return tokenizer.apply_chat_template(
177
- msgs, tokenize=True, add_generation_prompt=True, return_tensors="pt"
178
- )
179
 
180
- def local_generate(model, tokenizer, input_ids, max_new_tokens=350):
181
  input_ids = input_ids.to(model.device)
182
  with torch.no_grad():
183
  out = model.generate(
184
- input_ids=input_ids,
185
- max_new_tokens=max_new_tokens,
186
- do_sample=True,
187
- temperature=0.3,
188
- top_p=0.9,
189
- repetition_penalty=1.15,
190
- pad_token_id=tokenizer.eos_token_id,
191
- eos_token_id=tokenizer.eos_token_id,
192
  )
193
  gen_only = out[0, input_ids.shape[-1]:]
194
  return tokenizer.decode(gen_only, skip_special_tokens=True).strip()
195
 
196
- # -------------------
197
- # Snapshot Loader
198
- # -------------------
199
  def _load_snapshot(path="snapshots/current.json"):
200
  try:
201
  with open(path, "r", encoding="utf-8") as f:
202
  return json.load(f)
203
  except Exception:
204
  return {
205
- "timestamp": None,
206
- "beds_total": 400,
207
- "staffed_ratio": 1.0,
208
- "occupied_pct": 0.97,
209
- "ed_census": 62,
210
- "ed_admits_waiting": 19,
211
- "avg_ed_wait_hours": 8,
212
- "discharge_ready_today": 11,
213
- "discharge_barriers": {"allied_health": 7, "placement": 4},
214
  "rn_shortfall": {"med_ward_A": 1, "med_ward_B": 1},
215
  "forecast_admits_next_24h": {"respiratory": 14, "other": 9},
216
- "isolation_needs_waiting": {"contact": 3, "airborne": 1},
217
- "telemetry_needed_waiting": 5
218
  }
219
 
220
- # Init retriever once
221
  init_retriever()
222
-
223
- # -------------------
224
- # Chat Function (with Augmentation + Safety)
225
- # -------------------
226
- def chat_fn(message, history, user_tz):
 
 
 
 
 
 
 
 
 
 
 
 
227
  try:
228
- # ---- INPUT SAFETY ----
229
  safe_in, blocked_in, reason_in = safety_filter(message, mode="input")
230
- if blocked_in:
231
- return refusal_reply(reason_in)
232
-
233
- # Identity short-circuit
234
  if is_identity_query(safe_in, history):
235
  return "I am ClarityOps, your strategic decision making AI partner."
236
 
237
- # --- Load snapshot + policies + numbers
 
 
 
 
 
 
 
 
 
 
238
  snapshot = _load_snapshot()
239
  policy_context = retrieve_context(
240
- "bed management huddle discharge acceleration bed leveling ambulance offload"
241
  )
242
  computed = compute_operational_numbers(snapshot)
243
- system_preamble = build_system_preamble(snapshot, policy_context, computed)
244
 
245
- # Augmented input
246
- augmented_user = (
247
- system_preamble
248
- + "\n\nUser question:\n"
249
- + safe_in
 
 
 
250
  )
251
 
252
- # ---- GENERATION ----
 
253
  if USE_HOSTED_COHERE:
254
  out = cohere_chat(augmented_user, history)
255
  else:
256
  model, tokenizer = load_local_model()
257
  inputs = build_inputs(tokenizer, augmented_user, history)
258
- out = local_generate(model, tokenizer, inputs, max_new_tokens=350)
259
 
260
- # Tidy echoes
261
  if isinstance(out, str):
262
  for tag in ("Assistant:", "System:", "User:"):
263
- if out.startswith(tag):
264
- out = out[len(tag):].strip()
265
 
266
- # ---- OUTPUT SAFETY ----
267
  safe_out, blocked_out, reason_out = safety_filter(out, mode="output")
268
- if blocked_out:
269
- return refusal_reply(reason_out)
270
  return safe_out
271
  except Exception as e:
272
  return f"Error: {e}"
273
 
274
- # -------------------
275
- # Theme & CSS
276
- # -------------------
277
- theme = gr.themes.Soft(
278
- primary_hue="teal",
279
- neutral_hue="slate",
280
- radius_size=gr.themes.sizes.radius_lg,
281
- )
282
-
283
  custom_css = """
284
- :root {
285
- --brand-bg: #e6f7f8;
286
- --brand-accent: #0d9488;
287
- --brand-text: #0f172a;
288
- --brand-text-light: #ffffff;
289
- }
290
-
291
  .gradio-container { background: var(--brand-bg); }
292
-
293
- h1 {
294
- color: var(--brand-text);
295
- font-weight: 700;
296
- font-size: 28px !important;
297
- }
298
-
299
- .chatbot header, .chatbot .label, .chatbot .label-wrap, .chatbot .top, .chatbot .header, .chatbot > .wrap > header {
300
- display: none !important;
301
- }
302
-
303
- .message.user, .message.bot {
304
- background: var(--brand-accent) !important;
305
- color: var(--brand-text-light) !important;
306
- border-radius: 12px !important;
307
- padding: 8px 12px !important;
308
- }
309
-
310
  textarea, input, .gr-input { border-radius: 12px !important; }
311
-
312
- .examples, .examples .grid {
313
- display: flex !important;
314
- justify-content: center !important;
315
- text-align: center !important;
316
- }
317
  """
318
 
319
- # -------------------
320
- # UI
321
- # -------------------
322
  with gr.Blocks(theme=theme, css=custom_css) as demo:
323
  tz_box = gr.Textbox(visible=False)
324
- demo.load(
325
- lambda tz: tz,
326
- inputs=[tz_box],
327
- outputs=[tz_box],
328
- js="() => Intl.DateTimeFormat().resolvedOptions().timeZone",
329
- )
330
 
331
  hide_label_sink = gr.HTML(visible=False)
332
- demo.load(
333
- fn=lambda: "",
334
- inputs=None,
335
- outputs=hide_label_sink,
336
- js="""
337
- () => {
338
- const sel = [
339
- '.chatbot header',
340
- '.chatbot .label',
341
- '.chatbot .label-wrap',
342
- '.chatbot .top',
343
- '.chatbot .header',
344
- '.chatbot > .wrap > header'
345
- ];
346
- sel.forEach(s => document.querySelectorAll(s).forEach(el => el.style.display = 'none'));
347
- return "";
348
- }
349
- """,
350
- )
351
 
352
  gr.Markdown("# ClarityOps Augmented Decision AI")
353
 
 
 
 
 
354
  gr.ChatInterface(
355
  fn=chat_fn,
356
  type="messages",
357
- additional_inputs=[tz_box],
358
- chatbot=gr.Chatbot(
359
- label="",
360
- show_label=False,
361
- type="messages",
362
- height=700,
363
- ),
364
  examples=[
365
  ["What are the symptoms of hypertension?"],
366
  ["What are common drug interactions with aspirin?"],
@@ -375,12 +259,4 @@ with gr.Blocks(theme=theme, css=custom_css) as demo:
375
 
376
  if __name__ == "__main__":
377
  port = int(os.environ.get("PORT", "7860"))
378
- demo.launch(
379
- server_name="0.0.0.0",
380
- server_port=port,
381
- show_api=False,
382
- max_threads=8,
383
- )
384
-
385
-
386
-
 
1
+ \
2
+ import os, re, json
 
3
  from functools import lru_cache
4
 
5
  import gradio as gr
6
  import torch
7
 
 
 
 
8
  os.environ.setdefault("HF_HOME", "/data/.cache/huggingface")
9
  os.environ.setdefault("HF_HUB_CACHE", "/data/.cache/huggingface/hub")
10
  os.environ.setdefault("GRADIO_TEMP_DIR", "/data/gradio")
11
  os.environ.setdefault("GRADIO_CACHE_DIR", "/data/gradio")
12
+ for p in ["/data/.cache/huggingface/hub", "/data/gradio"]:
13
+ try: os.makedirs(p, exist_ok=True)
14
+ except Exception: pass
15
 
 
 
 
 
 
 
 
 
 
 
16
  try:
17
  from zoneinfo import ZoneInfo
18
  except Exception:
19
  ZoneInfo = None
20
 
 
21
  try:
22
  import cohere
23
  _HAS_COHERE = True
 
27
  from transformers import AutoTokenizer, AutoModelForCausalLM
28
  from huggingface_hub import login
29
 
 
 
 
30
  from safety import safety_filter, refusal_reply
 
 
 
 
31
  from retriever import init_retriever, retrieve_context
32
  from decision_math import compute_operational_numbers
33
  from prompt_templates import build_system_preamble
34
+ from upload_ingest import extract_text_from_files
35
+ from session_rag import SessionRAG
36
+ from mdsi_analysis import capacity_projection, cost_estimate, outcomes_summary
37
 
 
 
 
38
  MODEL_ID = os.getenv("MODEL_ID", "CohereLabs/c4ai-command-r7b-12-2024")
39
  HF_TOKEN = os.getenv("HUGGINGFACE_HUB_TOKEN") or os.getenv("HF_TOKEN")
40
  COHERE_API_KEY = os.getenv("COHERE_API_KEY")
41
  USE_HOSTED_COHERE = bool(COHERE_API_KEY and _HAS_COHERE)
42
 
 
 
 
43
  def pick_dtype_and_map():
44
+ if torch.cuda.is_available(): return torch.float16, "auto"
45
+ if torch.backends.mps.is_available(): return torch.float16, {"": "mps"}
 
 
46
  return torch.float32, "cpu"
47
 
48
  def is_identity_query(message, history):
49
  patterns = [
50
+ r"\bwho\s+are\s+you\b", r"\bwhat\s+are\s+you\b", r"\bwhat\s+is\s+your\s+name\b",
51
+ r"\bwho\s+is\s+this\b", r"\bidentify\s+yourself\b", r"\btell\s+me\s+about\s+yourself\b",
52
+ r"\bdescribe\s+yourself\b", r"\band\s+you\s*\?\b", r"\byour\s+name\b", r"\bwho\s+am\s+i\s+chatting\s+with\b"
 
 
 
 
 
 
 
53
  ]
54
+ def match(t): return any(re.search(p, (t or "").strip().lower()) for p in patterns)
55
+ if match(message): return True
 
 
56
  if history:
57
  last_user = history[-1][0] if isinstance(history[-1], (list, tuple)) else None
58
+ if match(last_user): return True
 
59
  return False
60
 
61
  def _iter_user_assistant(history):
 
68
  def _history_to_prompt(message, history):
69
  parts = []
70
  for u, a in _iter_user_assistant(history):
71
+ if u: parts.append(f"User: {u}")
72
+ if a: parts.append(f"Assistant: {a}")
 
 
73
  parts.append(f"User: {message}")
74
  parts.append("Assistant:")
75
  return "\n".join(parts)
76
 
 
 
 
77
  _co_client = None
78
  if USE_HOSTED_COHERE:
79
  _co_client = cohere.Client(api_key=COHERE_API_KEY)
 
85
  model="command-r7b-12-2024",
86
  message=prompt,
87
  temperature=0.3,
88
+ max_tokens=700,
89
  )
90
+ if hasattr(resp, "text") and resp.text: return resp.text.strip()
91
+ if hasattr(resp, "reply") and resp.reply: return resp.reply.strip()
92
+ if hasattr(resp, "generations") and resp.generations: return resp.generations[0].text.strip()
 
 
 
93
  return "Sorry, I couldn't parse the response from Cohere."
94
  except Exception as e:
95
  return f"Error calling Cohere API: {e}"
96
 
 
 
 
97
  @lru_cache(maxsize=1)
98
  def load_local_model():
99
+ if not HF_TOKEN: raise RuntimeError("HUGGINGFACE_HUB_TOKEN is not set.")
 
100
  login(token=HF_TOKEN, add_to_git_credential=False)
101
  dtype, device_map = pick_dtype_and_map()
102
  tok = AutoTokenizer.from_pretrained(
103
+ MODEL_ID, token=HF_TOKEN, use_fast=True, model_max_length=8192, padding_side="left", trust_remote_code=True,
 
 
 
 
 
104
  )
105
  mdl = AutoModelForCausalLM.from_pretrained(
106
+ MODEL_ID, token=HF_TOKEN, device_map=device_map, low_cpu_mem_usage=True,
107
+ torch_dtype=dtype, trust_remote_code=True,
 
 
 
 
108
  )
109
  if mdl.config.eos_token_id is None and tok.eos_token_id is not None:
110
  mdl.config.eos_token_id = tok.eos_token_id
 
113
  def build_inputs(tokenizer, message, history):
114
  msgs = []
115
  for u, a in _iter_user_assistant(history):
116
+ if u: msgs.append({"role": "user", "content": u})
117
+ if a: msgs.append({"role": "assistant", "content": a})
 
 
118
  msgs.append({"role": "user", "content": message})
119
+ return tokenizer.apply_chat_template(msgs, tokenize=True, add_generation_prompt=True, return_tensors="pt")
 
 
120
 
121
+ def local_generate(model, tokenizer, input_ids, max_new_tokens=900):
122
  input_ids = input_ids.to(model.device)
123
  with torch.no_grad():
124
  out = model.generate(
125
+ input_ids=input_ids, max_new_tokens=max_new_tokens, do_sample=True, temperature=0.3, top_p=0.9,
126
+ repetition_penalty=1.15, pad_token_id=tokenizer.eos_token_id, eos_token_id=tokenizer.eos_token_id,
 
 
 
 
 
 
127
  )
128
  gen_only = out[0, input_ids.shape[-1]:]
129
  return tokenizer.decode(gen_only, skip_special_tokens=True).strip()
130
 
 
 
 
131
  def _load_snapshot(path="snapshots/current.json"):
132
  try:
133
  with open(path, "r", encoding="utf-8") as f:
134
  return json.load(f)
135
  except Exception:
136
  return {
137
+ "timestamp": None, "beds_total": 400, "staffed_ratio": 1.0, "occupied_pct": 0.97,
138
+ "ed_census": 62, "ed_admits_waiting": 19, "avg_ed_wait_hours": 8,
139
+ "discharge_ready_today": 11, "discharge_barriers": {"allied_health": 7, "placement": 4},
 
 
 
 
 
 
140
  "rn_shortfall": {"med_ward_A": 1, "med_ward_B": 1},
141
  "forecast_admits_next_24h": {"respiratory": 14, "other": 9},
142
+ "isolation_needs_waiting": {"contact": 3, "airborne": 1}, "telemetry_needed_waiting": 5
 
143
  }
144
 
145
+ # Init retriever & session RAG
146
  init_retriever()
147
+ _session_rag = SessionRAG()
148
+
149
+ def _mdsi_block() -> str:
150
+ base_capacity = capacity_projection(18, 48, 6)
151
+ cons_capacity = capacity_projection(12, 48, 6)
152
+ opt_capacity = capacity_projection(24, 48, 6)
153
+ cost_1200 = cost_estimate(1200, 74.0, 75000.0)
154
+ outcomes = outcomes_summary()
155
+ return json.dumps({
156
+ "capacity_projection": {
157
+ "conservative": cons_capacity, "base": base_capacity, "optimistic": opt_capacity
158
+ },
159
+ "cost_for_1200": cost_1200,
160
+ "outcomes_summary": outcomes
161
+ }, indent=2)
162
+
163
+ def chat_fn(message, history, user_tz, uploaded_files, scenario_text):
164
  try:
 
165
  safe_in, blocked_in, reason_in = safety_filter(message, mode="input")
166
+ if blocked_in: return refusal_reply(reason_in)
 
 
 
167
  if is_identity_query(safe_in, history):
168
  return "I am ClarityOps, your strategic decision making AI partner."
169
 
170
+ # Ingest uploads
171
+ filepaths = [f.name if hasattr(f, "name") else f for f in (uploaded_files or [])]
172
+ if filepaths:
173
+ items = extract_text_from_files(filepaths)
174
+ if items: _session_rag.add_docs(items)
175
+
176
+ # Retrieve snippets from session uploads
177
+ session_snips = "\\n---\\n".join(_session_rag.retrieve(
178
+ "diabetes screening Indigenous Métis mobile program cost throughput outcomes logistics", k=6
179
+ ))
180
+
181
  snapshot = _load_snapshot()
182
  policy_context = retrieve_context(
183
+ "mobile diabetes screening Indigenous community outreach logistics referral pathways privacy cultural safety data governance cost effectiveness outcomes"
184
  )
185
  computed = compute_operational_numbers(snapshot)
 
186
 
187
+ mdsi_extra = _mdsi_block() if ("diabetes" in (scenario_text or "").lower() or "mdsi" in (scenario_text or "").lower()) else ""
188
+
189
+ system_preamble = build_system_preamble(
190
+ snapshot=snapshot,
191
+ policy_context=policy_context,
192
+ computed_numbers=computed,
193
+ scenario_text=(scenario_text or "" ) + (f"\\n\\nExecutive Pre-Computed Blocks:\\n{mdsi_extra}" if mdsi_extra else ""),
194
+ session_snips=session_snips
195
  )
196
 
197
+ augmented_user = system_preamble + "\\n\\nUser question or request:\\n" + safe_in
198
+
199
  if USE_HOSTED_COHERE:
200
  out = cohere_chat(augmented_user, history)
201
  else:
202
  model, tokenizer = load_local_model()
203
  inputs = build_inputs(tokenizer, augmented_user, history)
204
+ out = local_generate(model, tokenizer, inputs, max_new_tokens=900)
205
 
 
206
  if isinstance(out, str):
207
  for tag in ("Assistant:", "System:", "User:"):
208
+ if out.startswith(tag): out = out[len(tag):].strip()
 
209
 
 
210
  safe_out, blocked_out, reason_out = safety_filter(out, mode="output")
211
+ if blocked_out: return refusal_reply(reason_out)
 
212
  return safe_out
213
  except Exception as e:
214
  return f"Error: {e}"
215
 
216
+ theme = gr.themes.Soft(primary_hue="teal", neutral_hue="slate", radius_size=gr.themes.sizes.radius_lg)
 
 
 
 
 
 
 
 
217
  custom_css = """
218
+ :root { --brand-bg: #e6f7f8; --brand-accent: #0d9488; --brand-text: #0f172a; --brand-text-light: #ffffff; }
 
 
 
 
 
 
219
  .gradio-container { background: var(--brand-bg); }
220
+ h1 { color: var(--brand-text); font-weight: 700; font-size: 28px !important; }
221
+ .chatbot header, .chatbot .label, .chatbot .label-wrap, .chatbot .top, .chatbot .header, .chatbot > .wrap > header { display: none !important; }
222
+ .message.user, .message.bot { background: var(--brand-accent) !important; color: var(--brand-text-light) !important; border-radius: 12px !important; padding: 8px 12px !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  textarea, input, .gr-input { border-radius: 12px !important; }
224
+ .examples, .examples .grid { display: flex !important; justify-content: center !important; text-align: center !important; }
 
 
 
 
 
225
  """
226
 
 
 
 
227
  with gr.Blocks(theme=theme, css=custom_css) as demo:
228
  tz_box = gr.Textbox(visible=False)
229
+ demo.load(lambda tz: tz, inputs=[tz_box], outputs=[tz_box],
230
+ js="() => Intl.DateTimeFormat().resolvedOptions().timeZone")
 
 
 
 
231
 
232
  hide_label_sink = gr.HTML(visible=False)
233
+ demo.load(fn=lambda: "", inputs=None, outputs=hide_label_sink, js="""
234
+ () => { const sel = ['.chatbot header','.chatbot .label','.chatbot .label-wrap','.chatbot .top','.chatbot .header','.chatbot > .wrap > header'];
235
+ sel.forEach(s => document.querySelectorAll(s).forEach(el => el.style.display = 'none')); return ""; } """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
  gr.Markdown("# ClarityOps Augmented Decision AI")
238
 
239
+ uploads = gr.Files(label="Upload docs/images (PDF, DOCX, CSV, PNG, JPG)", file_types=["file"], file_count="multiple")
240
+ scenario = gr.Textbox(label="Scenario Context (paste case studies or executive briefs here)",
241
+ lines=10, placeholder="Paste scenario text...")
242
+
243
  gr.ChatInterface(
244
  fn=chat_fn,
245
  type="messages",
246
+ additional_inputs=[tz_box, uploads, scenario],
247
+ chatbot=gr.Chatbot(label="", show_label=False, type="messages", height=700),
 
 
 
 
 
248
  examples=[
249
  ["What are the symptoms of hypertension?"],
250
  ["What are common drug interactions with aspirin?"],
 
259
 
260
  if __name__ == "__main__":
261
  port = int(os.environ.get("PORT", "7860"))
262
+ demo.launch(server_name="0.0.0.0", server_port=port, show_api=False, max_threads=8)
 
 
 
 
 
 
 
 
build_policy_index.py CHANGED
@@ -1,4 +1,4 @@
1
- # build_policy_index.py
2
  import os, glob, json
3
  from pathlib import Path
4
  from sentence_transformers import SentenceTransformer
@@ -12,7 +12,6 @@ INDEX_PATH = os.path.join(STORE_DIR, "index.faiss")
12
  MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
13
 
14
  def read_text_like(path: str) -> str:
15
- # Keep it simple: .txt / .md only to avoid extra deps
16
  if path.lower().endswith((".txt", ".md")):
17
  return Path(path).read_text(encoding="utf-8", errors="ignore")
18
  return ""
 
1
+ \
2
  import os, glob, json
3
  from pathlib import Path
4
  from sentence_transformers import SentenceTransformer
 
12
  MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
13
 
14
  def read_text_like(path: str) -> str:
 
15
  if path.lower().endswith((".txt", ".md")):
16
  return Path(path).read_text(encoding="utf-8", errors="ignore")
17
  return ""
clarityops_pack_fullcode.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2955564d22ad333c5cd8aba8022d2c348f9b6e6af8c3af042ac2b8c2934495f8
3
+ size 12005
decision_math.py CHANGED
@@ -1,4 +1,4 @@
1
- # decision_math.py
2
  from typing import Dict
3
 
4
  def free_staffed_beds(staffed_capacity: int, current_occupied: int) -> int:
@@ -11,19 +11,14 @@ def discharge_goal(today_ready: int, by_noon_ratio: float = 0.6) -> int:
11
  return max(0, int(round(today_ready * by_noon_ratio)))
12
 
13
  def compute_operational_numbers(snapshot: Dict) -> Dict:
14
- # snapshot should already include most fields; compute some derivations
15
  staffed_capacity = int(snapshot.get("beds_total", 0) * (snapshot.get("staffed_ratio", 1.0)))
16
  current_occupied = int(snapshot.get("beds_total", 0) * snapshot.get("occupied_pct", 0))
17
  free_now = free_staffed_beds(staffed_capacity or snapshot.get("beds_total", 0), current_occupied)
18
  ed_waiting = int(snapshot.get("ed_admits_waiting", 0))
19
-
20
- # simple surge buffer for next 12h if forecast exists
21
  forecast = snapshot.get("forecast_admits_next_24h", {})
22
  surge_buffer = int(round((forecast.get("respiratory", 0) + forecast.get("other", 0)) * 0.4))
23
-
24
  need_now = beds_needed_to_clear(ed_waiting, free_now, surge_buffer=surge_buffer)
25
  noon_goal = discharge_goal(int(snapshot.get("discharge_ready_today", 0)))
26
-
27
  return {
28
  "staffed_capacity": staffed_capacity or snapshot.get("beds_total", 0),
29
  "current_occupied": current_occupied,
 
1
+ \
2
  from typing import Dict
3
 
4
  def free_staffed_beds(staffed_capacity: int, current_occupied: int) -> int:
 
11
  return max(0, int(round(today_ready * by_noon_ratio)))
12
 
13
  def compute_operational_numbers(snapshot: Dict) -> Dict:
 
14
  staffed_capacity = int(snapshot.get("beds_total", 0) * (snapshot.get("staffed_ratio", 1.0)))
15
  current_occupied = int(snapshot.get("beds_total", 0) * snapshot.get("occupied_pct", 0))
16
  free_now = free_staffed_beds(staffed_capacity or snapshot.get("beds_total", 0), current_occupied)
17
  ed_waiting = int(snapshot.get("ed_admits_waiting", 0))
 
 
18
  forecast = snapshot.get("forecast_admits_next_24h", {})
19
  surge_buffer = int(round((forecast.get("respiratory", 0) + forecast.get("other", 0)) * 0.4))
 
20
  need_now = beds_needed_to_clear(ed_waiting, free_now, surge_buffer=surge_buffer)
21
  noon_goal = discharge_goal(int(snapshot.get("discharge_ready_today", 0)))
 
22
  return {
23
  "staffed_capacity": staffed_capacity or snapshot.get("beds_total", 0),
24
  "current_occupied": current_occupied,
mdsi_analysis.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ \
2
+ from typing import Dict, List
3
+
4
+ def capacity_projection(clients_per_day: int = 18, clinic_days_per_team: int = 48, teams: int = 6) -> int:
5
+ return clients_per_day * clinic_days_per_team * teams
6
+
7
+ def cost_estimate(n_clients: int, variable_per_client: float = 74.0, fixed_total: float = 75000.0) -> Dict:
8
+ total_variable = variable_per_client * n_clients
9
+ total = total_variable + fixed_total
10
+ return {
11
+ "n_clients": n_clients,
12
+ "variable_per_client": variable_per_client,
13
+ "fixed_total": fixed_total,
14
+ "total_variable": total_variable,
15
+ "total_cost": total,
16
+ "cost_per_client": total / max(1, n_clients)
17
+ }
18
+
19
+ def prioritize_settlements(records: List[Dict]) -> List[Dict]:
20
+ # records: [{name, population, risk_index, access_burden, repeat_opportunity}]
21
+ if not records:
22
+ return []
23
+ def scale(vals):
24
+ mn, mx = min(vals), max(vals)
25
+ return [0.0 if mx==mn else (v-mn)/(mx-mn) for v in vals]
26
+ pop_s = scale([r.get("population", 0) for r in records])
27
+ risk_s = scale([r.get("risk_index", 0) for r in records])
28
+ acc_s = scale([r.get("access_burden", 0) for r in records])
29
+ rep_s = scale([r.get("repeat_opportunity", 0) for r in records])
30
+ out = []
31
+ for i, r in enumerate(records):
32
+ score = 0.35*pop_s[i] + 0.35*risk_s[i] + 0.15*acc_s[i] + 0.15*rep_s[i]
33
+ rr = dict(r); rr["priority_score"] = round(score, 3)
34
+ out.append(rr)
35
+ out.sort(key=lambda x: x["priority_score"], reverse=True)
36
+ return out
37
+
38
+ def outcomes_summary(delta_a1c=-0.4, delta_sbp=-5, delta_bmi=-0.8, delta_ldl=-12):
39
+ return {
40
+ "median_delta_a1c_pct": delta_a1c,
41
+ "median_delta_systolic_bp_mmHg": delta_sbp,
42
+ "median_delta_bmi_kg_m2": delta_bmi,
43
+ "median_delta_ldl_mg_dl": delta_ldl
44
+ }
policies/README.md CHANGED
@@ -1,6 +1,4 @@
1
- Place hospital policies, bed-management playbooks, discharge acceleration checklists, and escalation trees here as .txt or .md.
2
- After adding files, run:
3
-
4
  python build_policy_index.py
5
-
6
- This creates `rag_store/index.faiss` and `rag_store/meta.json`.
 
1
+ Place hospital policies, playbooks, and SOPs here as .txt or .md.
2
+ Then run:
 
3
  python build_policy_index.py
4
+ This will create rag_store/index.faiss and rag_store/meta.json for retrieval.
 
prompt_templates.py CHANGED
@@ -1,4 +1,4 @@
1
- # prompt_templates.py
2
  import json
3
  from typing import Dict
4
 
@@ -12,11 +12,19 @@ DECISION_FRAME = """FRAME:
12
  - DECISION: ranked actions with owner, ETA, expected beds, and risks.
13
  """
14
 
15
- def build_system_preamble(snapshot: Dict, policy_context: str, computed_numbers: Dict) -> str:
 
 
 
 
 
 
 
 
16
  return f"""
17
- You are ClarityOps, a hospital flow decision co-pilot.
18
- Use the snapshot JSON, computed numbers, and policy excerpts to recommend next actions.
19
- Return EXACTLY: (1) Risks, (2) Beds needed & by when, (3) Actions ranked with owner/ETA/expected beds, (4) Expected impact, (5) Escalations/Comms.
20
 
21
  Policies & SOP Excerpts:
22
  {policy_context}
@@ -27,5 +35,13 @@ Snapshot (JSON):
27
  Computed Numbers:
28
  {json.dumps(computed_numbers, indent=2)}
29
 
 
 
 
 
 
 
30
  {DECISION_FRAME}
 
 
31
  """.strip()
 
1
+ \
2
  import json
3
  from typing import Dict
4
 
 
12
  - DECISION: ranked actions with owner, ETA, expected beds, and risks.
13
  """
14
 
15
+ EXEC_FRAME = """EXECUTIVE FRAME:
16
+ - OBJECTIVE: clarify success criteria, time horizon, and constraints.
17
+ - CONTEXT: scenario details, population, geography, cultural considerations.
18
+ - DATA INPUTS: population/community, health indicators, cost/ops, longitudinal outcomes.
19
+ - ANALYTICS: prioritization method, capacity simulation, cost model, outcome deltas.
20
+ - OUTPUTS: tables + bullets + assumptions + short narrative justifications.
21
+ """
22
+
23
+ def build_system_preamble(snapshot: Dict, policy_context: str, computed_numbers: Dict, scenario_text: str = "", session_snips: str = "") -> str:
24
  return f"""
25
+ You are ClarityOps, an operational & executive decision co-pilot for healthcare.
26
+ Use (a) the snapshot JSON, (b) policy excerpts, (c) computed numbers, and (d) any uploaded/scenario evidence
27
+ to recommend next actions and provide structured estimates.
28
 
29
  Policies & SOP Excerpts:
30
  {policy_context}
 
35
  Computed Numbers:
36
  {json.dumps(computed_numbers, indent=2)}
37
 
38
+ Scenario (if provided):
39
+ {scenario_text if scenario_text else "(none)"}
40
+
41
+ Uploaded Evidence (session):
42
+ {session_snips if session_snips else "(none)"}
43
+
44
  {DECISION_FRAME}
45
+
46
+ {EXEC_FRAME}
47
  """.strip()
requirements.txt CHANGED
@@ -1,13 +1,12 @@
1
- transformers>=4.45.0
2
- torch==2.3.1
3
- accelerate>=0.33.0
4
- gradio==4.44.1
5
- huggingface_hub==0.24.5
6
- cohere>=5.0.0
7
- tenacity>=8.4.1
8
- requests>=2.32.3
9
- safetensors>=0.4.3
10
  sentence-transformers
11
  faiss-cpu
12
  numpy
13
  pydantic
 
 
 
 
 
 
1
+ gradio
2
+ torch
3
+ transformers
 
 
 
 
 
 
4
  sentence-transformers
5
  faiss-cpu
6
  numpy
7
  pydantic
8
+ pdfplumber
9
+ python-docx
10
+ pytesseract
11
+ Pillow
12
+ pandas
retriever.py CHANGED
@@ -1,4 +1,4 @@
1
- # retriever.py
2
  import os, json
3
  from typing import List
4
  import faiss
@@ -35,7 +35,6 @@ class Retriever:
35
  chunks.append(self.docs[idx]["text"])
36
  return chunks
37
 
38
- # convenience
39
  _retriever = None
40
  def init_retriever(index_path="rag_store/index.faiss", meta_path="rag_store/meta.json"):
41
  global _retriever
@@ -46,6 +45,5 @@ def init_retriever(index_path="rag_store/index.faiss", meta_path="rag_store/meta
46
  def retrieve_context(query: str, k: int = 6) -> str:
47
  r = init_retriever()
48
  if not r.ready():
49
- # Safe fallback if index not built yet
50
- return ("(No policy index found. Run build_policy_index.py to enable RAG.)")
51
  return "\n---\n".join(r.retrieve(query, k=k))
 
1
+ \
2
  import os, json
3
  from typing import List
4
  import faiss
 
35
  chunks.append(self.docs[idx]["text"])
36
  return chunks
37
 
 
38
  _retriever = None
39
  def init_retriever(index_path="rag_store/index.faiss", meta_path="rag_store/meta.json"):
40
  global _retriever
 
45
  def retrieve_context(query: str, k: int = 6) -> str:
46
  r = init_retriever()
47
  if not r.ready():
48
+ return "(No policy index found. Run build_policy_index.py to enable RAG.)"
 
49
  return "\n---\n".join(r.retrieve(query, k=k))
safety.py CHANGED
@@ -1,38 +1,9 @@
1
- UNSAFE_PATTERNS = {
2
- "violent_harm": ["kill", "murder", "assassinate", "hurt someone", "violently harm"],
3
- "child_sexual": ["csam", "child sexual", "underage porn", "minor sexual"],
4
- "sexual_explicit": ["explicit porn", "incest fetish", "graphic sexual"],
5
- "self_harm": ["kill myself", "commit suicide", "how to self-harm", "end my life"],
6
- "weapons_making": ["make a bomb", "build an explosive", "molotov", "ghost gun"],
7
- "serious_illegal": ["credit card generator", "ssn list", "hack bank", "counterfeit money", "sell drugs"],
8
- "dox_malware": ["doxx", "make ransomware", "build malware", "backdoor", "keylogger"],
9
- "bio_chem": ["bioweapon", "chemical weapon", "create nerve agent"],
10
- }
11
-
12
- def _match_unsafe(text):
13
- t = (text or "").lower()
14
- for category, needles in UNSAFE_PATTERNS.items():
15
- for n in needles:
16
- if n in t:
17
- return category
18
- return None
19
-
20
  def safety_filter(text, mode="input"):
21
- cat = _match_unsafe(text)
22
- if cat:
23
- return text, True, cat
24
- return text, False, None
25
 
26
- def refusal_reply(category):
27
- reasons = {
28
- "violent_harm": "violent harm",
29
- "child_sexual": "sexual content involving minors",
30
- "sexual_explicit": "explicit sexual content",
31
- "self_harm": "self-harm",
32
- "weapons_making": "weapon construction",
33
- "serious_illegal": "illegal activity",
34
- "dox_malware": "privacy or malware abuse",
35
- "bio_chem": "biological or chemical harm",
36
- }
37
- reason = reasons.get(category, "unsafe content")
38
- return (f"⚠️ I can’t help with {reason}. ")
 
1
+ \
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  def safety_filter(text, mode="input"):
3
+ # Placeholder safety filter (replace with real policy if needed).
4
+ blocked = False
5
+ reason = ""
6
+ return text, blocked, reason
7
 
8
+ def refusal_reply(reason: str):
9
+ return "Sorry, I can't help with that request."
 
 
 
 
 
 
 
 
 
 
 
session_rag.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ \
2
+ from typing import List, Tuple
3
+ from sentence_transformers import SentenceTransformer
4
+ import numpy as np
5
+ import faiss
6
+
7
+ class SessionRAG:
8
+ def __init__(self, model_name="sentence-transformers/all-MiniLM-L6-v2"):
9
+ self.model = SentenceTransformer(model_name)
10
+ self.docs: List[Tuple[str, str]] = []
11
+ self.index = None
12
+ self.vecs = None
13
+
14
+ def add_docs(self, items: List[Tuple[str, str]]):
15
+ self.docs.extend(items)
16
+ texts = [t for _, t in self.docs]
17
+ if not texts:
18
+ self.index = None; self.vecs=None; return
19
+ embs = self.model.encode(texts, convert_to_numpy=True, normalize_embeddings=True).astype(np.float32)
20
+ self.vecs = embs
21
+ self.index = faiss.IndexFlatIP(embs.shape[1])
22
+ self.index.add(embs)
23
+
24
+ def retrieve(self, query: str, k: int = 6) -> List[str]:
25
+ if not self.index or self.vecs is None:
26
+ return []
27
+ q = self.model.encode([query], convert_to_numpy=True, normalize_embeddings=True).astype(np.float32)
28
+ D, I = self.index.search(q, k)
29
+ out = []
30
+ for idx in I[0]:
31
+ if 0 <= idx < len(self.docs):
32
+ out.append(self.docs[idx][1])
33
+ return out
snapshots/current.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "timestamp": "2025-09-05T10:00",
3
  "beds_total": 400,
4
  "staffed_ratio": 1.0,
5
  "occupied_pct": 0.97,
@@ -7,9 +7,21 @@
7
  "ed_admits_waiting": 19,
8
  "avg_ed_wait_hours": 8,
9
  "discharge_ready_today": 11,
10
- "discharge_barriers": {"allied_health": 7, "placement": 4},
11
- "rn_shortfall": {"med_ward_A": 1, "med_ward_B": 1},
12
- "forecast_admits_next_24h": {"respiratory": 14, "other": 9},
13
- "isolation_needs_waiting": {"contact": 3, "airborne": 1},
 
 
 
 
 
 
 
 
 
 
 
 
14
  "telemetry_needed_waiting": 5
15
- }
 
1
  {
2
+ "timestamp": "2025-09-07T10:00",
3
  "beds_total": 400,
4
  "staffed_ratio": 1.0,
5
  "occupied_pct": 0.97,
 
7
  "ed_admits_waiting": 19,
8
  "avg_ed_wait_hours": 8,
9
  "discharge_ready_today": 11,
10
+ "discharge_barriers": {
11
+ "allied_health": 7,
12
+ "placement": 4
13
+ },
14
+ "rn_shortfall": {
15
+ "med_ward_A": 1,
16
+ "med_ward_B": 1
17
+ },
18
+ "forecast_admits_next_24h": {
19
+ "respiratory": 14,
20
+ "other": 9
21
+ },
22
+ "isolation_needs_waiting": {
23
+ "contact": 3,
24
+ "airborne": 1
25
+ },
26
  "telemetry_needed_waiting": 5
27
+ }
upload_ingest.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ \
2
+ import os
3
+ from typing import List, Tuple
4
+ import pdfplumber
5
+ from docx import Document as DocxDocument
6
+ from PIL import Image
7
+ import pytesseract
8
+
9
+ TEXT_EXT = {".txt", ".md", ".csv"}
10
+ DOCX_EXT = {".docx"}
11
+ PDF_EXT = {".pdf"}
12
+ IMG_EXT = {".png", ".jpg", ".jpeg", ".webp"}
13
+
14
+ def _read_text_file(path: str) -> str:
15
+ return open(path, "r", encoding="utf-8", errors="ignore").read()
16
+
17
+ def _read_docx(path: str) -> str:
18
+ doc = DocxDocument(path)
19
+ return "\n".join([p.text for p in doc.paragraphs])
20
+
21
+ def _read_pdf(path: str) -> str:
22
+ out = []
23
+ with pdfplumber.open(path) as pdf:
24
+ for p in pdf.pages:
25
+ out.append(p.extract_text() or "")
26
+ return "\n".join(out)
27
+
28
+ def _read_image_ocr(path: str) -> str:
29
+ img = Image.open(path)
30
+ return pytesseract.image_to_string(img)
31
+
32
+ def extract_text_from_files(filepaths: List[str]) -> List[Tuple[str, str]]:
33
+ results = []
34
+ for fp in filepaths:
35
+ _, ext = os.path.splitext(fp.lower())
36
+ try:
37
+ if ext in TEXT_EXT:
38
+ txt = _read_text_file(fp)
39
+ elif ext in DOCX_EXT:
40
+ txt = _read_docx(fp)
41
+ elif ext in PDF_EXT:
42
+ txt = _read_pdf(fp)
43
+ elif ext in IMG_EXT:
44
+ txt = _read_image_ocr(fp)
45
+ else:
46
+ txt = ""
47
+ if txt and txt.strip():
48
+ results.append((os.path.basename(fp), txt))
49
+ except Exception:
50
+ continue
51
+ return results