Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,116 +1,53 @@
|
|
| 1 |
# app.py
|
| 2 |
-
# Kashmir AO Action Plan for Inf — Enhanced KB-driven advisory app (NON-ACTIONABLE)
|
| 3 |
-
# Rebuilt from your uploaded app (app (46).py) and today's requested enhancements.
|
| 4 |
|
| 5 |
-
import os
|
| 6 |
-
import math
|
| 7 |
-
import re
|
| 8 |
import gradio as gr
|
|
|
|
| 9 |
|
| 10 |
-
#
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
from openai import OpenAI
|
| 14 |
-
# If OPENAI_API_KEY is set in environment, the OpenAI client will pick it up (or pass explicitly).
|
| 15 |
-
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
| 16 |
-
except Exception:
|
| 17 |
-
client = None
|
| 18 |
-
|
| 19 |
-
# Banner (as provided)
|
| 20 |
BANNER_URL = "https://huggingface.co/spaces/Militaryint/ops/resolve/main/banner.png"
|
| 21 |
|
| 22 |
# -------------------------
|
| 23 |
-
#
|
| 24 |
# -------------------------
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
"What equipment and weapons are they carrying",
|
| 30 |
-
"What vehicles are they using or are they on foot?",
|
| 31 |
-
"How far are they from any roads frequented by soldiers vehicles",
|
| 32 |
-
"How far are they from any military unit camp",
|
| 33 |
-
"How far are they from any deployed soldiers",
|
| 34 |
-
"Are they getting support of locals? If so who are these locals?",
|
| 35 |
-
"What is their disposition? How are they spread out in formation? Are they moving closely tight or are they spread out?",
|
| 36 |
-
"Do you as the security force commander have Reconnaissance and Surveillance soldiers near the area where these enemy sighted?",
|
| 37 |
-
"Did you get the information of sighting of enemy from local source or army personnel?",
|
| 38 |
-
"If you got information from local source did you confirm.that information from second source or by sending your Reconnaissance and Surveillance team to observe and confirm truth of what source said? Please remember source can be planted by enemy to provide false information in order to lead your security forces to an ambush site of the enemy",
|
| 39 |
-
"How far is your commanded army unit from the place where enemy is sighted?",
|
| 40 |
-
"is the terrain urban or semi urban or jungle or hilly or rural?"
|
| 41 |
-
]
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
"Does the unit have Force Protection SOP? Threat Conditions Levels?FP specific route planner? Base FP review capability denoted by intelligence and counterintelligence vulnerability reports?",
|
| 48 |
-
"Does the unit have intelligence projection capability?In shape of forward intelligence surveillance and reconnaissance nodes,covert and regular? These nodes are one or two manned emplacements outside base in specific sectors of the AOR? May even comprise small teams.Intelligence projection is required so that these cells can be activated any time on receipt of info say enemy movement in nearby areas of the sector of AOR where they are emplaced.",
|
| 49 |
-
"Is there a vulnerablity analysis tool for unit? For example to determine vulnerable assets and critical assets.the former if compromised will not halt unit ops and mission but the latter will.The physical layout of camp assets for example should be out of reach of intrusive action like a breach of camp perimeter or attack on security gate.",
|
| 50 |
-
"Does the unit employ Randomness in guarding,troops movement and officers movement? Randomness is necessary to thwart enemy surveillance.",
|
| 51 |
-
"Is there a source vetting system in place? Is counterintelligence used to confirm source levels of efficiency and trust? Does the unit have a source registry?",
|
| 52 |
-
"Does the unit treat intelligence as only sourced information or as a repository of different tactics and techniques? Is there any intelligence doctrine or manual in unit library detailing these tactics?",
|
| 53 |
-
"Does the unit use counterintelligence in vulnerability analysis of ops and base safety? Is it understood that CI is not only spying but is also a repository of intelligence tactics amended to attack enemy intent and enemy situational Awareness?Does the unit int section have provisions to employ force protection in terms of intelligence and CI and not just use static physical protection methods and routine convoy protection?",
|
| 54 |
-
"Does the unit have embedded int personnel in routine ops like checkpoint,road blocks,cordon and search,patrols and R&S teams? This facilitates screening and advance Warn."
|
| 55 |
-
]
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
"Using two guide rails of 5D system,viz Deter and Deny did i make out a Vulnerability Assessment of my Bn security measures and information security both at base,during routine ops and deployed areas of troops outside base?Did I conduct a red teaming on these two guide rails as premise?",
|
| 63 |
-
"How do I account for Force Protection based on Q5?Am I giving FP top priority?",
|
| 64 |
-
"Do we attack the threats situational awareness,or his freedom of movement,or his tactics or his local support? Do i have the intelligence capability to assess on these lines?",
|
| 65 |
-
"Is the call for operation Deliberate or Quick?Do I have the appropriate int and R&S assets to report in both cases? In Quick mode do I have projected intelligence capability,that is do I have covert or overt int and R&S assets emplaced near or at the area of reported threat activity? If not how do I make an on the spot threat assessment before launching ops? So as to avert Surprise?",
|
| 66 |
-
"Does my Unit have a projected command and control capability in the AOR outside HQs?Does it have emplaced covert and overt tactical C2 nodes in focussed areas of AOR? Is there Net of troops and R&S plus int assets in touch with these tactical C2 nodes?",
|
| 67 |
-
"Do I clearly distinguish between Advance Warn, Surprise and Situational Awareness? Do i accept the fact that all intelligence collected by MI and sources may be accurate but SA wrong leading to failure? Do i understand that even SA can be rendered wrong by Enemy Surprise?"
|
| 68 |
-
]
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
# -------------------------
|
| 84 |
-
#
|
| 85 |
# -------------------------
|
| 86 |
-
LOGIC_PDFS = {
|
| 87 |
-
"enemy_detect_information": [
|
| 88 |
-
"Detect.pdf",
|
| 89 |
-
"ATTN LAW ENFORCEMENT HOW TO AVERT SURPRISE ATTACK BY CRIMINAL OR ENEMY.pdf"
|
| 90 |
-
],
|
| 91 |
-
"course_of_action_information": [
|
| 92 |
-
"Operational Course of Action Using the Kashmir Military Algorithm.pdf",
|
| 93 |
-
"Ops_Framework.pdf",
|
| 94 |
-
"Ops_Questionnaire.pdf"
|
| 95 |
-
],
|
| 96 |
-
"reconnaissance_surveillance": [
|
| 97 |
-
"Staff_Officer_Playbook_5D.pdf",
|
| 98 |
-
"INT_SOP_CONTROL_logic.pdf",
|
| 99 |
-
"SOP INT SECTION.pdf"
|
| 100 |
-
],
|
| 101 |
-
"cognitive_and_intelligence": [
|
| 102 |
-
"5D_Crime_Analysis.pdf",
|
| 103 |
-
"5D_Cognitive_Warfare_Flow.pdf",
|
| 104 |
-
"The intelligence foundation of operations.pdf",
|
| 105 |
-
"UPLOAD TO CHAT.pdf"
|
| 106 |
-
],
|
| 107 |
-
"other": [
|
| 108 |
-
"intel_investigation_rewrite (1).pdf",
|
| 109 |
-
"drugs_incoming_assessment_and_mitigations.pdf"
|
| 110 |
-
]
|
| 111 |
-
}
|
| 112 |
-
|
| 113 |
-
# Priority PDFs (highest priority when retrieving)
|
| 114 |
PRIORITY_PDFS = [
|
| 115 |
"Operational Course of Action Using the Kashmir Military Algorithm.pdf",
|
| 116 |
"UPLOAD TO CHAT.pdf",
|
|
@@ -119,801 +56,248 @@ PRIORITY_PDFS = [
|
|
| 119 |
]
|
| 120 |
|
| 121 |
# -------------------------
|
| 122 |
-
#
|
| 123 |
# -------------------------
|
| 124 |
-
|
| 125 |
-
""
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
""
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
if not os.path.isdir(d):
|
| 132 |
-
continue
|
| 133 |
-
for fname in sorted(os.listdir(d)):
|
| 134 |
-
path = os.path.join(d, fname)
|
| 135 |
-
if not os.path.isfile(path):
|
| 136 |
-
continue
|
| 137 |
-
lower = fname.lower()
|
| 138 |
-
try:
|
| 139 |
-
if lower.endswith(".pdf"):
|
| 140 |
-
try:
|
| 141 |
-
import PyPDF2
|
| 142 |
-
with open(path, "rb") as f:
|
| 143 |
-
reader = PyPDF2.PdfReader(f)
|
| 144 |
-
pages = []
|
| 145 |
-
for p in reader.pages:
|
| 146 |
-
try:
|
| 147 |
-
t = p.extract_text()
|
| 148 |
-
if t:
|
| 149 |
-
pages.append(t)
|
| 150 |
-
except Exception:
|
| 151 |
-
continue
|
| 152 |
-
files_text[fname] = "\n\n".join(pages) if pages else "[PDF read but no extractable text]"
|
| 153 |
-
except Exception as e:
|
| 154 |
-
files_text[fname] = f"[Could not read PDF: {e}]"
|
| 155 |
-
elif lower.endswith(".txt") or lower.endswith(".md"):
|
| 156 |
-
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
| 157 |
-
files_text[fname] = f.read()
|
| 158 |
-
except Exception as e:
|
| 159 |
-
files_text[fname] = f"[Error reading file: {e}]"
|
| 160 |
-
return files_text
|
| 161 |
-
|
| 162 |
-
def chunk_text(text, max_chars=4000, overlap=400):
|
| 163 |
-
if not text:
|
| 164 |
-
return []
|
| 165 |
-
chunks = []
|
| 166 |
-
start = 0
|
| 167 |
-
L = len(text)
|
| 168 |
-
while start < L:
|
| 169 |
-
end = min(start + max_chars, L)
|
| 170 |
-
chunks.append(text[start:end])
|
| 171 |
-
if end == L:
|
| 172 |
-
break
|
| 173 |
-
start = end - overlap
|
| 174 |
-
return chunks
|
| 175 |
|
| 176 |
# -------------------------
|
| 177 |
-
#
|
| 178 |
# -------------------------
|
| 179 |
-
def get_embedding(text):
|
| 180 |
-
if client is None:
|
| 181 |
-
return None
|
| 182 |
-
try:
|
| 183 |
-
resp = client.embeddings.create(model="text-embedding-3-small", input=text)
|
| 184 |
-
try:
|
| 185 |
-
return resp["data"][0]["embedding"]
|
| 186 |
-
except Exception:
|
| 187 |
-
return resp.data[0].embedding
|
| 188 |
-
except Exception:
|
| 189 |
-
return None
|
| 190 |
-
|
| 191 |
-
def cosine_sim(a, b):
|
| 192 |
-
if a is None or b is None:
|
| 193 |
-
return 0.0
|
| 194 |
-
dot = sum(x*y for x,y in zip(a,b))
|
| 195 |
-
na = math.sqrt(sum(x*x for x in a))
|
| 196 |
-
nb = math.sqrt(sum(x*x for x in b))
|
| 197 |
-
if na == 0 or nb == 0:
|
| 198 |
-
return 0.0
|
| 199 |
-
return dot / (na*nb)
|
| 200 |
-
|
| 201 |
class KBIndex:
|
| 202 |
def __init__(self):
|
| 203 |
-
self.
|
|
|
|
| 204 |
def build_from_files(self, files_text):
|
| 205 |
-
self.
|
| 206 |
-
for fname, text in files_text.items():
|
| 207 |
-
for i, chunk in enumerate(chunk_text(text)):
|
| 208 |
-
emb = get_embedding(chunk)
|
| 209 |
-
self.chunks.append({"file": fname, "id": f"{fname}::chunk{i+1}", "text": chunk, "emb": emb})
|
| 210 |
-
def retrieve(self, query, top_k=3, prefer_files=None):
|
| 211 |
-
"""
|
| 212 |
-
Retrieve top_k chunks for query.
|
| 213 |
-
If prefer_files list provided, prioritize chunks from those filenames first.
|
| 214 |
-
"""
|
| 215 |
-
q_emb = get_embedding(query) if client else None
|
| 216 |
-
scored = []
|
| 217 |
-
# scoring
|
| 218 |
-
if q_emb is not None:
|
| 219 |
-
for c in self.chunks:
|
| 220 |
-
score = cosine_sim(q_emb, c["emb"]) if c["emb"] is not None else 0.0
|
| 221 |
-
scored.append((score, c))
|
| 222 |
-
scored.sort(key=lambda x: x[0], reverse=True)
|
| 223 |
-
else:
|
| 224 |
-
q_tokens = set(re.findall(r"\w+", query.lower()))
|
| 225 |
-
for c in self.chunks:
|
| 226 |
-
tokens = set(re.findall(r"\w+", c["text"].lower()))
|
| 227 |
-
score = len(q_tokens & tokens)
|
| 228 |
-
scored.append((score, c))
|
| 229 |
-
scored.sort(key=lambda x: x[0], reverse=True)
|
| 230 |
-
|
| 231 |
-
# If prefer_files provided, move those up
|
| 232 |
-
if prefer_files:
|
| 233 |
-
prioritized = [c for s,c in scored if c["file"] in prefer_files]
|
| 234 |
-
others = [c for s,c in scored if c["file"] not in prefer_files]
|
| 235 |
-
ordered = prioritized + others
|
| 236 |
-
top = ordered[:top_k]
|
| 237 |
-
else:
|
| 238 |
-
top = [c for s,c in scored[:top_k]]
|
| 239 |
-
|
| 240 |
-
# fallback: if no results, return first top_k chunks available
|
| 241 |
-
if not top:
|
| 242 |
-
top = self.chunks[:min(top_k, max(1, len(self.chunks)))]
|
| 243 |
-
return top
|
| 244 |
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
# Diagnostics: report found / missing in categories
|
| 252 |
-
all_files_set = set(FILES_TEXT.keys())
|
| 253 |
-
category_diagnostics = {}
|
| 254 |
-
for cat, files in LOGIC_PDFS.items():
|
| 255 |
-
found = [f for f in files if f in all_files_set]
|
| 256 |
-
missing = [f for f in files if f not in all_files_set]
|
| 257 |
-
category_diagnostics[cat] = {"found": found, "missing": missing}
|
| 258 |
-
|
| 259 |
-
# Reorder so priority PDFs appear first (if present)
|
| 260 |
-
ordered_files = {}
|
| 261 |
-
for p in PRIORITY_PDFS:
|
| 262 |
-
# case-sensitive key match in FILES_TEXT keys (we assume exact naming)
|
| 263 |
-
for k in list(FILES_TEXT.keys()):
|
| 264 |
-
if k.lower() == p.lower():
|
| 265 |
-
ordered_files[k] = FILES_TEXT.pop(k)
|
| 266 |
-
break
|
| 267 |
-
# add the rest
|
| 268 |
-
for k, v in FILES_TEXT.items():
|
| 269 |
-
ordered_files[k] = v
|
| 270 |
-
|
| 271 |
-
FILES_TEXT = ordered_files
|
| 272 |
|
| 273 |
KB_INDEX = KBIndex()
|
| 274 |
KB_INDEX.build_from_files(FILES_TEXT)
|
| 275 |
|
| 276 |
-
# Startup print (diagnostics)
|
| 277 |
-
print("[KB] Indexed files (priority first):")
|
| 278 |
-
for f in FILES_TEXT.keys():
|
| 279 |
-
print(" -", f)
|
| 280 |
-
|
| 281 |
# -------------------------
|
| 282 |
-
#
|
| 283 |
# -------------------------
|
| 284 |
-
|
| 285 |
-
"
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
]
|
| 291 |
|
| 292 |
-
|
| 293 |
-
print("[KB] Category diagnostics (found / missing):")
|
| 294 |
-
for cat, info in category_diagnostics.items():
|
| 295 |
-
print(f" - {cat}: found={len(info['found'])}, missing={len(info['missing'])}")
|
| 296 |
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
# -------------------------
|
| 300 |
|
| 301 |
-
|
| 302 |
-
"You are an institutional analyst. You MUST follow these rules: "
|
| 303 |
-
"1) Provide ONLY non-actionable, administrative, doctrinal or training-oriented analysis. "
|
| 304 |
-
"2) If asked for operational content, refuse and instead produce administrative alternatives (SOPs, audits, training, doctrine). "
|
| 305 |
-
"3) Cite the KB chunk ids / filenames you used in reasoning. "
|
| 306 |
-
"4) DO NOT provide tactical step-by-step instructions."
|
| 307 |
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
- UPLOAD TO CHAT.pdf
|
| 317 |
-
- 5D_Crime_Analysis.pdf
|
| 318 |
-
- INT_SOP_CONTROL_logic.pdf
|
| 319 |
-
|
| 320 |
-
2) ALSO CONSULT all other PDFs found in the knowledge_base folder as secondary sources.
|
| 321 |
-
|
| 322 |
-
3) FOR STANDARD and ENHANCED REPORTS:
|
| 323 |
-
- DO NOT print the user's questions and answers verbatim in the visible report.
|
| 324 |
-
- Use the user's answers only as internal context for analysis.
|
| 325 |
-
- Base your advisory analysis on KB excerpts (cite file::chunk ids).
|
| 326 |
-
- Provide only administrative, doctrinal, training or audit-style recommendations.
|
| 327 |
-
- If asked for operational/tactical instructions, REFUSE and offer administrative alternatives.
|
| 328 |
-
|
| 329 |
-
4) For each incoming question, determine which logic category applies (enemy_detect_information, course_of_action_information, reconnaissance_surveillance, cognitive_and_intelligence, or other) and preferentially use KB excerpts from that category.
|
| 330 |
-
|
| 331 |
-
5) Always include a short "Sources cited" section that lists the file names and chunk ids that were used to produce the advisory.
|
| 332 |
-
"""
|
| 333 |
|
| 334 |
# -------------------------
|
| 335 |
-
#
|
| 336 |
# -------------------------
|
| 337 |
-
def _shorten(text, n=1200):
|
| 338 |
-
if not text:
|
| 339 |
-
return ""
|
| 340 |
-
return (text[:n] + "…") if len(text) > n else text
|
| 341 |
-
|
| 342 |
-
def build_standard_prompt_from_retrieved(retrieved_chunks):
|
| 343 |
-
"""
|
| 344 |
-
Build prompt where KB excerpts are passed to model and Q/A are not echoed.
|
| 345 |
-
"""
|
| 346 |
-
parts = []
|
| 347 |
-
parts.append(SYSTEM_SAFE)
|
| 348 |
-
parts.append(OPERATIONAL_COMMANDS)
|
| 349 |
-
parts.append("Produce a Standard Advisory Report for a commander.")
|
| 350 |
-
parts.append("Include: Executive Summary (2-4 lines); Key Administrative Issues (bulleted); Prioritized Administrative Recommendations (numbered); Sources cited (file::chunk ids).")
|
| 351 |
-
parts.append("Do NOT repeat questions or answers verbatim. Use only KB excerpts below as your primary lens.")
|
| 352 |
-
parts.append("\n--- KB Excerpts (lens) ---\n")
|
| 353 |
-
# attach KB excerpts (may have duplicates across questions)
|
| 354 |
-
added = set()
|
| 355 |
-
for chunk_list in retrieved_chunks:
|
| 356 |
-
for c in chunk_list:
|
| 357 |
-
marker = f"{c['id']}|{c['file']}"
|
| 358 |
-
if marker in added:
|
| 359 |
-
continue
|
| 360 |
-
added.add(marker)
|
| 361 |
-
parts.append(f"--- {c['id']} (from {c['file']}) ---\n{_shorten(c['text'])}\n")
|
| 362 |
-
user_text = "\n".join(parts)
|
| 363 |
-
return [{"role":"system","content":parts[0]}, {"role":"user","content":user_text}]
|
| 364 |
-
|
| 365 |
-
def build_enhanced_prompt_from_retrieved(retrA, retrB, retrC, gate_text):
|
| 366 |
-
parts = []
|
| 367 |
-
parts.append(SYSTEM_SAFE)
|
| 368 |
-
parts.append(OPERATIONAL_COMMANDS)
|
| 369 |
-
parts.append("Produce a Enhanced Staff Advisory for a commander.")
|
| 370 |
-
parts.append("Include: Executive Summary; Key doctrinal/organizational gaps; Prioritized Administrative Remediations; Measurement/audit suggestions; Sources cited.")
|
| 371 |
-
parts.append("Do NOT repeat questions or answers verbatim. Use KB excerpts below as your primary lens.")
|
| 372 |
-
parts.append("\n--- KB Excerpts (lens) ---\n")
|
| 373 |
-
added = set()
|
| 374 |
-
for clist in (retrA + retrB + retrC):
|
| 375 |
-
for c in clist:
|
| 376 |
-
marker = f"{c['id']}|{c['file']}"
|
| 377 |
-
if marker in added:
|
| 378 |
-
continue
|
| 379 |
-
added.add(marker)
|
| 380 |
-
parts.append(f"--- {c['id']} (from {c['file']}) ---\n{_shorten(c['text'])}\n")
|
| 381 |
-
if gate_text:
|
| 382 |
-
parts.append("\nGate assessment (used for analysis, not printed verbatim): [REDACTED]")
|
| 383 |
-
user_text = "\n".join(parts)
|
| 384 |
-
return [{"role":"system","content":parts[0]}, {"role":"user","content":user_text}]
|
| 385 |
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
|
|
|
|
|
|
|
|
|
| 400 |
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
rem.append("Ensure SITREP templates include precise time and geo fields and audit entries for accuracy.")
|
| 409 |
-
if "direction" in q:
|
| 410 |
-
rem.append("Add directional/axis fields to reporting templates and train observers.")
|
| 411 |
-
if "size of the enemy" in q or "how many" in q:
|
| 412 |
-
rem.append("Include size estimation procedures in SOP and require cross-checks.")
|
| 413 |
-
if "equipment" in q or "weapons" in q:
|
| 414 |
-
rem.append("Standardize equipment reporting taxonomy in the unit KB and train observers.")
|
| 415 |
-
if "vehicles" in q:
|
| 416 |
-
rem.append("Include vehicle identification fields in R&S templates and verify via secondary observation.")
|
| 417 |
-
if "roads" in q:
|
| 418 |
-
rem.append("Map and flag critical infrastructure in vulnerability audits.")
|
| 419 |
-
if "locals" in q:
|
| 420 |
-
rem.append("Apply CI vetting to local-source reports and require secondary confirmation.")
|
| 421 |
-
if "disposition" in q:
|
| 422 |
-
rem.append("Document disposition notation standards and rehearse via table-top exercises.")
|
| 423 |
-
if "reconnaissance" in q or "r&s" in q:
|
| 424 |
-
rem.append("Maintain an on-call R&S roster and ensure cover/concealment SOPs are current.")
|
| 425 |
-
if "confirmed" in q or "second source" in q:
|
| 426 |
-
rem.append("Mandate secondary confirmation and log confirmations in a secure registry.")
|
| 427 |
-
if "terrain" in q:
|
| 428 |
-
rem.append("Ensure terrain classification fields are mandatory and staff have access to terrain overlays.")
|
| 429 |
-
if not rem:
|
| 430 |
-
rem.append("Review reporting templates and conduct staff training to improve data capture.")
|
| 431 |
-
# dedupe
|
| 432 |
-
out = []
|
| 433 |
-
for r in rem:
|
| 434 |
-
if r not in out:
|
| 435 |
-
out.append(r)
|
| 436 |
-
return out
|
| 437 |
-
|
| 438 |
-
def remediation_for_enh_question(q_text):
|
| 439 |
-
q = q_text.lower()
|
| 440 |
-
rem = []
|
| 441 |
-
if "ops and int" in q or "integration" in q:
|
| 442 |
-
rem.append("Establish Ops-Int coordination SOP and a liaison role with documented responsibilities.")
|
| 443 |
-
if "int sop" in q or "course of action" in q:
|
| 444 |
-
rem.append("Create Intelligence SOP and COA templates that include int feeds and validation steps.")
|
| 445 |
-
if "reconnaissance and surveillance plan" in q:
|
| 446 |
-
rem.append("Publish R&S plan templates and run table-top exercises to validate them.")
|
| 447 |
-
if "force protection" in q or "route planner" in q:
|
| 448 |
-
rem.append("Institute FP review cycles and formal route planning checklists for missions and base movement.")
|
| 449 |
-
if "intelligence projection" in q or "forward intelligence" in q:
|
| 450 |
-
rem.append("Document intelligence projection concept, minimum manning and activation triggers in SOP.")
|
| 451 |
-
if "vulnerability analysis" in q:
|
| 452 |
-
rem.append("Adopt a vulnerability register and schedule regular audits and red-team reviews (administrative).")
|
| 453 |
-
if "randomness" in q:
|
| 454 |
-
rem.append("Implement randomized movement policies and document patterns for audit (ensure safety/legal compliance).")
|
| 455 |
-
if "source vetting" in q or "counterintelligence" in q:
|
| 456 |
-
rem.append("Build a source registry and CI vetting workflow with secure access and audit logs.")
|
| 457 |
-
if "doctrine" in q or "manual" in q:
|
| 458 |
-
rem.append("Compile and publish an internal intelligence doctrine/manual in the unit library.")
|
| 459 |
-
if "embedded int" in q:
|
| 460 |
-
rem.append("Define embedded-int roles, responsibilities and rotation cycles; maintain oversight.")
|
| 461 |
-
if not rem:
|
| 462 |
-
rem.append("Conduct a formal capability gap analysis and document required SOPs and resourcing.")
|
| 463 |
-
out = []
|
| 464 |
-
for r in rem:
|
| 465 |
-
if r not in out:
|
| 466 |
-
out.append(r)
|
| 467 |
-
return out
|
| 468 |
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
prefer_files = LOGIC_PDFS.get("enemy_detect_information", []) + PRIORITY_PDFS
|
| 477 |
-
for q in STD_QUESTIONS:
|
| 478 |
-
ans = answers_map.get(q, "")
|
| 479 |
-
query_text = (str(ans).strip() or q)
|
| 480 |
-
chunks = kb_index.retrieve(query_text, top_k=top_k_per_answer, prefer_files=prefer_files)
|
| 481 |
-
retrieved.append(chunks)
|
| 482 |
-
messages = build_standard_prompt_from_retrieved(retrieved)
|
| 483 |
-
# Force OpenAI call
|
| 484 |
-
try:
|
| 485 |
-
out = call_chat_api(messages, max_tokens=1400)
|
| 486 |
-
return out
|
| 487 |
-
except Exception as e:
|
| 488 |
-
# deterministic fallback
|
| 489 |
-
lines = ["[FALLBACK NON-ACTIONABLE STANDARD ADVISORY]\n"]
|
| 490 |
-
lines.append("Executive Summary: (derived administratively from inputs and KB)")
|
| 491 |
-
# For each question, only list admin remediation if answer negative
|
| 492 |
-
for i, q in enumerate(STD_QUESTIONS):
|
| 493 |
-
a = answers_map.get(q, "")
|
| 494 |
-
if not a or str(a).strip().lower() in ("", "no", "n", "none", "negative"):
|
| 495 |
-
lines.append(f"- Deficiency: {q}")
|
| 496 |
-
for r in remediation_for_std_question(q):
|
| 497 |
-
lines.append(f" *Admin remediation:* {r}")
|
| 498 |
-
lines.append("\nSources: (KB unavailable or OpenAI unreachable)")
|
| 499 |
-
return "\n".join(lines)
|
| 500 |
-
|
| 501 |
-
def generate_enhanced_report_with_lens(a_map, b_map, c_map, gate_text, kb_index, top_k_per_answer=3):
|
| 502 |
-
# Map Enhanced sections to categories
|
| 503 |
-
# Section A -> reconnaissance_surveillance
|
| 504 |
-
# Section B -> course_of_action_information
|
| 505 |
-
# Section C -> cognitive_and_intelligence
|
| 506 |
-
retrieved_A = [kb_index.retrieve(str(a_map.get(q,"") or q), top_k=top_k_per_answer, prefer_files=LOGIC_PDFS.get("reconnaissance_surveillance", []) + PRIORITY_PDFS) for q in ENH_SECTION_A]
|
| 507 |
-
retrieved_B = [kb_index.retrieve(str(b_map.get(q,"") or q), top_k=top_k_per_answer, prefer_files=LOGIC_PDFS.get("course_of_action_information", []) + PRIORITY_PDFS) for q in ENH_SECTION_B]
|
| 508 |
-
retrieved_C = [kb_index.retrieve(str(c_map.get(q,"") or q), top_k=top_k_per_answer, prefer_files=LOGIC_PDFS.get("cognitive_and_intelligence", []) + PRIORITY_PDFS) for q in ENH_SECTION_C]
|
| 509 |
-
messages = build_enhanced_prompt_from_retrieved(retrieved_A, retrieved_B, retrieved_C, gate_text)
|
| 510 |
-
try:
|
| 511 |
-
out = call_chat_api(messages, max_tokens=1600)
|
| 512 |
-
return out
|
| 513 |
-
except Exception as e:
|
| 514 |
-
lines = ["[FALLBACK ENHANCED ADVISORY]\n"]
|
| 515 |
-
lines.append("Administrative gaps and remediations:\n")
|
| 516 |
-
for q in ENH_SECTION_A:
|
| 517 |
-
a = a_map.get(q,"")
|
| 518 |
-
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 519 |
-
lines.append(f"- {q}")
|
| 520 |
-
for r in remediation_for_enh_question(q):
|
| 521 |
-
lines.append(f" *Admin remediation:* {r}")
|
| 522 |
-
for q in ENH_SECTION_B:
|
| 523 |
-
b = b_map.get(q,"")
|
| 524 |
-
if not b or str(b).strip().lower() in ("","no","n","none","negative"):
|
| 525 |
-
lines.append(f"- {q}")
|
| 526 |
-
for r in remediation_for_enh_question(q):
|
| 527 |
-
lines.append(f" *Admin remediation:* {r}")
|
| 528 |
-
for q in ENH_SECTION_C:
|
| 529 |
-
c = c_map.get(q,"")
|
| 530 |
-
if not c or str(c).strip().lower() in ("","no","n","none","negative"):
|
| 531 |
-
lines.append(f"- {q}")
|
| 532 |
-
for r in remediation_for_enh_question(q):
|
| 533 |
-
lines.append(f" *Admin remediation:* {r}")
|
| 534 |
-
lines.append("\nSources: (OpenAI unreachable or KB partial)")
|
| 535 |
-
return "\n".join(lines)
|
| 536 |
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
color = "🔵 BLUE"
|
| 550 |
-
meaning = "Anticipated short-term threat readiness"
|
| 551 |
-
elif readiness >= 50:
|
| 552 |
-
color = "🟠 ORANGE"
|
| 553 |
-
meaning = "Imminent threat readiness (admin gaps present)"
|
| 554 |
-
else:
|
| 555 |
-
color = "🔴 RED"
|
| 556 |
-
meaning = "Low readiness - significant administrative gaps"
|
| 557 |
-
|
| 558 |
-
deficiencies = []
|
| 559 |
-
rems = []
|
| 560 |
-
idx = 0
|
| 561 |
-
for q in STD_QUESTIONS:
|
| 562 |
-
a = vals[idx]; idx+=1
|
| 563 |
-
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 564 |
-
deficiencies.append(f"STD: {q}")
|
| 565 |
-
rems.extend(remediation_for_std_question(q))
|
| 566 |
-
for q in ENH_SECTION_A:
|
| 567 |
-
a = vals[idx]; idx+=1
|
| 568 |
-
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 569 |
-
deficiencies.append(f"ENH A: {q}")
|
| 570 |
-
rems.extend(remediation_for_enh_question(q))
|
| 571 |
-
for q in ENH_SECTION_B:
|
| 572 |
-
a = vals[idx]; idx+=1
|
| 573 |
-
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 574 |
-
deficiencies.append(f"ENH B: {q}")
|
| 575 |
-
rems.extend(remediation_for_enh_question(q))
|
| 576 |
-
for q in ENH_SECTION_C:
|
| 577 |
-
a = vals[idx]; idx+=1
|
| 578 |
-
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 579 |
-
deficiencies.append(f"ENH C: {q}")
|
| 580 |
-
rems.extend(remediation_for_enh_question(q))
|
| 581 |
-
|
| 582 |
-
unique_rems = []
|
| 583 |
-
for r in rems:
|
| 584 |
-
if r not in unique_rems:
|
| 585 |
-
unique_rems.append(r)
|
| 586 |
-
|
| 587 |
-
lines = []
|
| 588 |
-
lines.append(f"# Threat Readiness: {readiness}%")
|
| 589 |
-
lines.append(f"**Status:** {color} — {meaning}\n")
|
| 590 |
-
lines.append("## Commander’s Guide to WARN Levels:")
|
| 591 |
-
lines.append("- 🔴 RED (<50%): Significant administrative deficiencies. Prioritize SOP, CI, and audits.")
|
| 592 |
-
lines.append("- 🟠 ORANGE (50–69%): Moderate deficiencies; strengthen doctrine & R&S validation.")
|
| 593 |
-
lines.append("- 🔵 BLUE (70–84%): Minor gaps; schedule audits and training.")
|
| 594 |
-
lines.append("- 🟢 GREEN (85–100%): Good status; maintain vigilance and periodic reviews.\n")
|
| 595 |
-
if deficiencies:
|
| 596 |
-
lines.append("## Identified Administrative Deficiencies (summary)")
|
| 597 |
-
for d in deficiencies[:40]:
|
| 598 |
-
lines.append(f"- {d}")
|
| 599 |
-
if len(deficiencies)>40:
|
| 600 |
-
lines.append(f"... and {len(deficiencies)-40} more.")
|
| 601 |
-
else:
|
| 602 |
-
lines.append("## Identified Administrative Deficiencies: None found.")
|
| 603 |
-
lines.append("\n## Example Administrative Remediations (prioritized)")
|
| 604 |
-
if unique_rems:
|
| 605 |
-
for i, r in enumerate(unique_rems[:40],1):
|
| 606 |
-
lines.append(f"{i}. {r}")
|
| 607 |
-
else:
|
| 608 |
-
lines.append("- No admin remediations needed based on current answers.")
|
| 609 |
-
lines.append("\n**Note:** This brief is NON-ACTIONABLE. It focuses on doctrine, SOPs, audits and training — no operational instructions.")
|
| 610 |
-
return "\n".join(lines)
|
| 611 |
-
# -------------------------
|
| 612 |
-
# PARA SF Questionnaires (paste before UI)
|
| 613 |
-
# -------------------------
|
| 614 |
|
|
|
|
| 615 |
PARA_QUESTIONS_50 = [
|
| 616 |
-
"
|
| 617 |
-
"What was the precise timestamp (date & local time) of the sighting?",
|
| 618 |
-
"From which direction or axis did the observed elements approach?",
|
| 619 |
-
"What is the estimated size (number of personnel) of the enemy element?",
|
| 620 |
-
"What is the observed role-composition (fighters, leaders, logistics, lookouts) of the enemy?",
|
| 621 |
-
"What weapons and equipment were seen (small arms, heavy weapons, explosives)?",
|
| 622 |
-
"Were vehicles observed? If yes, type, count and mobility characteristics?",
|
| 623 |
-
"Were they mounted, motorized or on foot during the sighting?",
|
| 624 |
-
"What is the enemy’s formation, spacing and dispersion at point of observation?",
|
| 625 |
-
"What observable movement pattern or tactics were used (patrol, rush, bypass, hide)?",
|
| 626 |
-
"Did enemy show identifiable signs, clothing, insignia or leader marks?",
|
| 627 |
-
"Were candidate leaders or command figures visible or identifiable?",
|
| 628 |
-
"Was there evidence of enemy communications (radios, burst transmissions, messengers)?",
|
| 629 |
-
"Was any electronic emissions or comms pattern observed (bursts, silence, specific frequencies)?",
|
| 630 |
-
"How far is the sighting from nearest military route/road used by your forces (meters/km)?",
|
| 631 |
-
"How far is the sighting from your closest unit, patrol or base (meters/km)?",
|
| 632 |
-
"What civilian infrastructure is nearest (market, school, clinic) and its distance?",
|
| 633 |
-
"Was there any civilian activity at the site and did civilians appear to support/assist the enemy?",
|
| 634 |
-
"Any signs of cached supplies, recent logistics activity, or prepared hide sites?",
|
| 635 |
-
"Does the area show signs of recent digging, caches, or concealed stores?",
|
| 636 |
-
"Is the terrain at sighting urban / semi-urban / rural / hilly / jungle / mixed?",
|
| 637 |
-
"What were visibility and weather conditions at the time (day/night, rain, fog)?",
|
| 638 |
-
"Did enemy employ surveillance or target-casing behaviour before movement?",
|
| 639 |
-
"Do you observe enemy counter-surveillance (watching for being followed or observed)?",
|
| 640 |
-
"Were there scouts, pickets or early-warning elements deployed by the enemy?",
|
| 641 |
-
"Is the enemy using deceptive measures (dummy positions, false reports, signals)?",
|
| 642 |
-
"Are there known or suspected safe houses, support homes, or sympathiser nodes nearby?",
|
| 643 |
-
"What is known of enemy resupply methods and routes (timings, vehicles, mule lines)?",
|
| 644 |
-
"Is there evidence of medical support or casualty evacuation capability for the enemy?",
|
| 645 |
-
"Does the enemy have known heavy/indirect fire capability (mortars, rockets, heavy MGs)?",
|
| 646 |
-
"Does the enemy use snipers, suppressive fire or sniper traps in this AO?",
|
| 647 |
-
"Has the enemy adapted tactics recently after prior engagements in this AO?",
|
| 648 |
-
"Are there predictable daily/weekly/seasonal patterns in enemy activity or movement?",
|
| 649 |
-
"Does the enemy blend with civilians or use noncombatant cover deliberately?",
|
| 650 |
-
"Are there known local criminal networks or cartels supporting the enemy (who)?",
|
| 651 |
-
"Is there evidence of external support (cross-border aid, funding, materiel)?",
|
| 652 |
-
"Does the enemy attempt to monitor or catalogue your unit routines (patrol times, routes)?",
|
| 653 |
-
"Was the sighting corroborated by more than one independent source? If so, which?",
|
| 654 |
-
"What is your confidence level in this sighting (low/medium/high) and why?",
|
| 655 |
-
"What immediate intelligence priorities do you request (confirm size, leadership ID, caches)?",
|
| 656 |
-
"What follow-up sensors or surveillance (R&S teams, OPs, HI/geo) have been or should be tasked?",
|
| 657 |
-
"Does historical KB / previous incidents indicate likely COA (ambush, raid, VBIED, sabotage)?",
|
| 658 |
-
"Based on available information, what is the most likely short-term enemy intent?",
|
| 659 |
-
"What is the most dangerous unknown (highest intelligence gap) right now?",
|
| 660 |
-
"Does the local population sentiment appear supportive, neutral or hostile to security forces?",
|
| 661 |
-
"Are there legal or agency actors (police/DEA/other) operating in the AO that affect options?",
|
| 662 |
-
"Are there choke points, key nodes, or likely avenues of approach the enemy favours?",
|
| 663 |
-
"Has the enemy demonstrated capability to rehearse or coordinate complex multi-axis attacks?"
|
| 664 |
]
|
| 665 |
-
|
| 666 |
PARA_PRECAUTIONS_40 = [
|
| 667 |
-
"
|
| 668 |
-
"Require secondary confirmation for any local-source reporting before escalation.",
|
| 669 |
-
"Maintain a vetted source registry with CI provenance tags and access controls.",
|
| 670 |
-
"Institute scheduled CI audits for HUMINT sources and reporting teams.",
|
| 671 |
-
"Publish and maintain an SR (Special Reconnaissance) activation roster and SOP.",
|
| 672 |
-
"Define and enforce SR reporting formats: mandatory fields, cadence, secure channels.",
|
| 673 |
-
"Embed intelligence liaisons in checkpoints, patrol briefings and R&S handovers.",
|
| 674 |
-
"Maintain and regularly update a Base Vulnerability Register for critical assets.",
|
| 675 |
-
"Conduct quarterly administrative red-team tabletop reviews on vulnerability findings.",
|
| 676 |
-
"Implement and document randomness in routine movements and patrol timings.",
|
| 677 |
-
"Require route-planning checklists and formal Force Protection (FP) reviews prior to movement.",
|
| 678 |
-
"Document force-protection escalation triggers and make them accessible to command.",
|
| 679 |
-
"Define covert Tactical C2 naming/concealment conventions and secure comms policy.",
|
| 680 |
-
"Document activation triggers and minimum manning for forward intelligence projection nodes.",
|
| 681 |
-
"Maintain a cache registry with audit trails (who accessed, when, purpose) for clandestine stores.",
|
| 682 |
-
"Produce a daily local-threat mapping product for Area HQ use (doctrinal template).",
|
| 683 |
-
"Institute structured data-sharing protocols with police, DEA and partner agencies (MOUs).",
|
| 684 |
-
"Mandate CI cross-checks on sudden influx of local ‘helpful’ reports and mark for audit.",
|
| 685 |
-
"Schedule regular SR tradecraft refresher training and surveillance-detection drills.",
|
| 686 |
-
"Publish a Source Confidence Matrix and require staff to consult it in assessments.",
|
| 687 |
-
"Define Ops-Int liaison roles with formal RACI and documented responsibilities.",
|
| 688 |
-
"Create a 5D Decision Matrix template for staff planners and force protection planners.",
|
| 689 |
-
"Maintain and review a prioritized HVT administrative monitoring list with owners.",
|
| 690 |
-
"Require observation-post (OP) turnover logs, admin inspections and OP checklists.",
|
| 691 |
-
"Mandate redundant secure reporting channels with redundancy and audit logs.",
|
| 692 |
-
"Maintain a lessons-learned register populated after each R&S mission (admin only).",
|
| 693 |
-
"Publish legal/command guidance on engagement-of-locals policies and vetting before use.",
|
| 694 |
-
"Retain and update terrain overlays and likely enemy use-case scenarios for staff reference.",
|
| 695 |
-
"Establish MOUs to permit relevant cross-agency intelligence shares where lawful.",
|
| 696 |
-
"Use doctrine-approved checklists before activating covert nodes inside populated zones.",
|
| 697 |
-
"Require anonymized HUMINT entries for sensitive sources and log access for audits.",
|
| 698 |
-
"Schedule periodic audits of comms metadata logs for administrative oversight.",
|
| 699 |
-
"Create formal SOPs for suspected planted/false sources and double-agent indicators.",
|
| 700 |
-
"Maintain a documented forensic/evidence-collection admin procedure (non-operational).",
|
| 701 |
-
"Run regular table-top exercises on surprise events and deception scenarios (admin focus).",
|
| 702 |
-
"Institute 'intelligence sanity-checks' requiring at least two KB sources to align for high-confidence claims.",
|
| 703 |
-
"Require an audit record for every SR insertion or surveillance emplacement.",
|
| 704 |
-
"Mandate periodic cross-validation of terrain/use overlays by staff planners.",
|
| 705 |
-
"Publish a one-page WARN card for commanders (color-coded triggers and contacts)."
|
| 706 |
]
|
| 707 |
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
# -------------------------
|
| 714 |
-
#
|
| 715 |
# -------------------------
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 723 |
|
| 724 |
-
|
| 725 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 726 |
|
| 727 |
# -------------------------
|
| 728 |
-
#
|
| 729 |
# -------------------------
|
| 730 |
with gr.Blocks() as demo:
|
| 731 |
gr.HTML(f'<img src="{BANNER_URL}" width="100%">')
|
| 732 |
-
gr.Markdown("# Kashmir
|
| 733 |
|
| 734 |
-
|
| 735 |
-
with gr.Tab("Standard Analyst"):
|
| 736 |
std_inputs = [gr.Textbox(label=q, lines=1) for q in STD_QUESTIONS]
|
| 737 |
-
|
| 738 |
-
std_output = gr.Textbox(label="Standard Advisory
|
| 739 |
-
def
|
| 740 |
amap = dict(zip(STD_QUESTIONS, answers))
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
std_btn.click(on_std_click, inputs=std_inputs, outputs=std_output)
|
| 746 |
-
|
| 747 |
-
# ----------- Enhanced Analyst Tab -----------
|
| 748 |
-
with gr.Tab("Enhanced Analyst (Sectioned)"):
|
| 749 |
a_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_A]
|
| 750 |
b_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_B]
|
| 751 |
c_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_C]
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
enh_output = gr.Textbox(label="Enhanced Advisory
|
| 755 |
-
def
|
| 756 |
-
la = len(ENH_SECTION_A); lb = len(ENH_SECTION_B); lc = len(ENH_SECTION_C)
|
| 757 |
-
vals = list(args)
|
| 758 |
-
while len(vals) < (la+lb+lc+1):
|
| 759 |
-
vals.append("")
|
| 760 |
-
a_vals = vals[:la]; b_vals = vals[la:la+lb]; c_vals = vals[la+lb:la+lb+lc]; gate_val = vals[-1]
|
| 761 |
-
a_map = dict(zip(ENH_SECTION_A, a_vals))
|
| 762 |
-
b_map = dict(zip(ENH_SECTION_B, b_vals))
|
| 763 |
-
c_map = dict(zip(ENH_SECTION_C, c_vals))
|
| 764 |
-
try:
|
| 765 |
-
return generate_enhanced_report_with_lens(a_map, b_map, c_map, gate_val, KB_INDEX, top_k_per_answer=3)
|
| 766 |
-
except Exception as e:
|
| 767 |
-
return f"[Error generating Enhanced Advisory: {e}]\n\nEnsure OPENAI_API_KEY is set and KB files are accessible."
|
| 768 |
-
enh_btn.click(on_enh_click, inputs=a_inputs+b_inputs+c_inputs+[gate_q], outputs=enh_output)
|
| 769 |
-
|
| 770 |
-
# ----------- Doctrinal Summary Tab -----------
|
| 771 |
-
with gr.Tab("Doctrinal Summary (Sanitized, Detailed)"):
|
| 772 |
-
gr.Markdown(DOCTRINAL_SUMMARY_DETAILED)
|
| 773 |
-
|
| 774 |
-
# ----------- Threat Readiness Tab -----------
|
| 775 |
-
with gr.Tab("Threat Readiness"):
|
| 776 |
-
gr.Markdown("## Threat Readiness — color-coded commander brief (administrative only)")
|
| 777 |
-
std_state = gr.State()
|
| 778 |
-
a_state = gr.State()
|
| 779 |
-
b_state = gr.State()
|
| 780 |
-
c_state = gr.State()
|
| 781 |
-
|
| 782 |
-
# Capture answers when reports are generated
|
| 783 |
-
def cache_std(*answers):
|
| 784 |
-
return list(answers)
|
| 785 |
-
std_btn.click(cache_std, inputs=std_inputs, outputs=[std_state])
|
| 786 |
-
|
| 787 |
-
def cache_enh(*answers):
|
| 788 |
la, lb, lc = len(ENH_SECTION_A), len(ENH_SECTION_B), len(ENH_SECTION_C)
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
return None
|
| 793 |
-
|
| 794 |
-
# Because Gradio wiring for caching enhanced answers is done via the main click above,
|
| 795 |
-
# provide a direct Threat evaluator button that expects user to have generated both reports already.
|
| 796 |
-
threat_btn = gr.Button("Evaluate Threat Readiness (based on last filled answers)")
|
| 797 |
-
threat_output = gr.Textbox(label="Threat Readiness & Admin Diagnostics", lines=30)
|
| 798 |
-
|
| 799 |
-
# For simplicity, implement a function that reads the current inputs on demand
|
| 800 |
-
def threat_runner_live(*ignore):
|
| 801 |
-
# collect current values from the input widgets via the demo state (we accept they were filled)
|
| 802 |
-
# To avoid complex state wiring, require the user to press the "Generate Standard Advisory" and "Generate Enhanced Advisory"
|
| 803 |
-
# buttons first; the app warns otherwise.
|
| 804 |
-
return ("To evaluate threat readiness: please first generate the Standard Advisory (press the button in the Standard tab) "
|
| 805 |
-
"and the Enhanced Advisory (press the button in the Enhanced tab). This will cache the answers and permit a full assessment."
|
| 806 |
-
"\n\nNote: threat readiness evaluates administrative readiness and lists deficiencies and remediations only.")
|
| 807 |
-
threat_btn.click(threat_runner_live, inputs=None, outputs=threat_output)
|
| 808 |
-
|
| 809 |
-
# ----------- Diagnostics accordion -----------
|
| 810 |
-
with gr.Accordion("Operational notes & diagnostics (admin only)", open=False):
|
| 811 |
-
# Show which KB files were indexed and category diagnostics
|
| 812 |
-
files_list_md = "\n".join(f"- {k}" for k in FILES_TEXT.keys()) if FILES_TEXT else "- [NO KB FILES FOUND]"
|
| 813 |
-
diag_md = (
|
| 814 |
-
f"**Indexed KB files (priority first):**\n\n{files_list_md}\n\n"
|
| 815 |
-
f"**Total KB chunks indexed:** {len(KB_INDEX.chunks)}\n\n"
|
| 816 |
-
"**Category diagnostics:**\n"
|
| 817 |
-
)
|
| 818 |
-
for cat, info in category_diagnostics.items():
|
| 819 |
-
diag_md += f"- {cat}: found={len(info['found'])}, missing={len(info['missing'])}\n"
|
| 820 |
-
if info['missing']:
|
| 821 |
-
diag_md += f" - Missing: {info['missing']}\n"
|
| 822 |
-
gr.Markdown(diag_md)
|
| 823 |
-
if client is None:
|
| 824 |
-
gr.Markdown("**OpenAI client: NOT INITIALIZED** — set OPENAI_API_KEY to enable GPT-based advisory reports.")
|
| 825 |
-
else:
|
| 826 |
-
gr.Markdown("**OpenAI client: OK**")
|
| 827 |
|
| 828 |
-
|
| 829 |
-
#
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
# File selector for SF KB files (optional; leave blank to use defaults)
|
| 841 |
-
para_file_selector = gr.CheckboxGroup(
|
| 842 |
-
choices=SF_PRIORITY_PDFS,
|
| 843 |
-
label="Select SF KB files to use (optional — leave blank to use default SF-priority files)"
|
| 844 |
-
)
|
| 845 |
-
|
| 846 |
-
# Buttons for two separate advisories
|
| 847 |
-
para_coa_btn = gr.Button("Generate COA / Threat Assessment / Intelligence Summary")
|
| 848 |
-
para_prec_btn = gr.Button("Generate PARA SF Precautions & Protective Advisory")
|
| 849 |
-
|
| 850 |
-
# Outputs (two separate textboxes)
|
| 851 |
-
para_coa_out = gr.Textbox(label="COA / Threat Assessment / Intelligence Summary (sanitized)", lines=28)
|
| 852 |
-
para_prec_out = gr.Textbox(label="PARA SF Precautions & Protective Measures (sanitized)", lines=28)
|
| 853 |
-
|
| 854 |
-
# Runner helpers (these call the sanitized para_sf_inference_runner and then try to extract sections)
|
| 855 |
-
def _extract_section(full_text, markers):
|
| 856 |
-
"""Return substring beginning at first marker found, or None."""
|
| 857 |
-
if not full_text:
|
| 858 |
-
return None
|
| 859 |
-
for m in markers:
|
| 860 |
-
idx = full_text.find(m)
|
| 861 |
-
if idx != -1:
|
| 862 |
-
# return from marker to marker+max or end
|
| 863 |
-
return full_text[idx: idx + 4000] if len(full_text) > idx+4000 else full_text[idx:]
|
| 864 |
-
return None
|
| 865 |
-
|
| 866 |
-
def para_coa_runner(*all_inputs):
|
| 867 |
-
# last two inputs are fieldcraft and file_selector
|
| 868 |
-
answers = list(all_inputs[:-2])
|
| 869 |
-
pasted = all_inputs[-2] or ""
|
| 870 |
-
selected = all_inputs[-1] or []
|
| 871 |
-
# map answers
|
| 872 |
-
answers_map = dict(zip(PARA_QUESTIONS_50, answers))
|
| 873 |
-
# call sanitized engine
|
| 874 |
-
try:
|
| 875 |
-
full = para_sf_inference_runner(selected, pasted, answers_map)
|
| 876 |
-
except Exception as e:
|
| 877 |
-
full = f"[Error running PARA SF inference: {e}]"
|
| 878 |
-
# try extract COA / Threat Assessment / Intelligence Summary
|
| 879 |
-
markers = [
|
| 880 |
-
"Doctrinal Course of Action", "Course of Action", "COA",
|
| 881 |
-
"Threat Assessment", "Intelligence Summary", "Executive Summary"
|
| 882 |
-
]
|
| 883 |
-
sec = _extract_section(full, markers)
|
| 884 |
-
return sec or full
|
| 885 |
-
|
| 886 |
-
def para_prec_runner(*all_inputs):
|
| 887 |
-
answers = list(all_inputs[:-2])
|
| 888 |
-
pasted = all_inputs[-2] or ""
|
| 889 |
-
selected = all_inputs[-1] or []
|
| 890 |
-
answers_map = dict(zip(PARA_QUESTIONS_50, answers))
|
| 891 |
-
try:
|
| 892 |
-
full = para_sf_inference_runner(selected, pasted, answers_map)
|
| 893 |
-
except Exception as e:
|
| 894 |
-
full = f"[Error running PARA SF inference: {e}]"
|
| 895 |
-
# try extract precautionary measures section
|
| 896 |
-
markers = [
|
| 897 |
-
"Precautions", "Protective Measures", "Para SF Precautions",
|
| 898 |
-
"Precautionary Measures", "Administrative Remediations"
|
| 899 |
-
]
|
| 900 |
-
sec = _extract_section(full, markers)
|
| 901 |
-
# If we couldn't find a specific list, try to assemble from PARA_PRECAUTIONS_40 deterministic advisories
|
| 902 |
-
if sec:
|
| 903 |
-
return sec
|
| 904 |
-
else:
|
| 905 |
-
# fallback: try to synthesize a prioritized 10-item TACTICAL precautions list deterministically
|
| 906 |
-
lines = ["[Fallback: Admin Precautions — OpenAI output did not include clear Precautions section]"]
|
| 907 |
-
# pick top N items from PARA_PRECAUTIONS_40 and show remediation+audit stub
|
| 908 |
-
for i, itm in enumerate(PARA_PRECAUTIONS_40[:12], start=1):
|
| 909 |
-
lines.append(f"{i}. {itm} — Remediation: Implement & document. Audit: quarterly check.")
|
| 910 |
-
lines.append("\nFull report (if produced):\n")
|
| 911 |
-
lines.append(full[:4000])
|
| 912 |
return "\n".join(lines)
|
| 913 |
-
|
| 914 |
-
|
| 915 |
-
|
| 916 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 917 |
|
| 918 |
if __name__ == "__main__":
|
| 919 |
demo.launch()
|
|
|
|
| 1 |
# app.py
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
import os, re
|
|
|
|
|
|
|
| 4 |
import gradio as gr
|
| 5 |
+
from openai import OpenAI
|
| 6 |
|
| 7 |
+
# -------------------------
|
| 8 |
+
# Banner URL
|
| 9 |
+
# -------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
BANNER_URL = "https://huggingface.co/spaces/Militaryint/ops/resolve/main/banner.png"
|
| 11 |
|
| 12 |
# -------------------------
|
| 13 |
+
# Safety Block
|
| 14 |
# -------------------------
|
| 15 |
+
SYSTEM_SAFE = """
|
| 16 |
+
You are a military analyst assistant.
|
| 17 |
+
All outputs must remain ACTIONABLE, and advisory tactical-only.
|
| 18 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
+
# -------------------------
|
| 21 |
+
# OpenAI Client
|
| 22 |
+
# -------------------------
|
| 23 |
+
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
+
# -------------------------
|
| 26 |
+
# Knowledge Base
|
| 27 |
+
# -------------------------
|
| 28 |
+
from pathlib import Path
|
| 29 |
+
from PyPDF2 import PdfReader
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
def read_all_files(folder):
|
| 32 |
+
files_text = {}
|
| 33 |
+
p = Path(folder)
|
| 34 |
+
if not p.exists():
|
| 35 |
+
print(f"[KB] folder {folder} missing")
|
| 36 |
+
return files_text
|
| 37 |
+
for f in p.glob("*.pdf"):
|
| 38 |
+
try:
|
| 39 |
+
reader = PdfReader(str(f))
|
| 40 |
+
txt = "\n".join(page.extract_text() or "" for page in reader.pages)
|
| 41 |
+
files_text[f.name] = txt
|
| 42 |
+
except Exception as e:
|
| 43 |
+
print("[KB] error reading", f, e)
|
| 44 |
+
return files_text
|
| 45 |
+
|
| 46 |
+
FILES_TEXT = read_all_files("knowledge_base")
|
| 47 |
|
| 48 |
# -------------------------
|
| 49 |
+
# Priority PDFs
|
| 50 |
# -------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
PRIORITY_PDFS = [
|
| 52 |
"Operational Course of Action Using the Kashmir Military Algorithm.pdf",
|
| 53 |
"UPLOAD TO CHAT.pdf",
|
|
|
|
| 56 |
]
|
| 57 |
|
| 58 |
# -------------------------
|
| 59 |
+
# PARA SF Priority PDFs
|
| 60 |
# -------------------------
|
| 61 |
+
SF_PRIORITY_PDFS = [
|
| 62 |
+
"Operational Course of Action Using the Kashmir Military Algorithm.pdf",
|
| 63 |
+
"UPLOAD TO CHAT.pdf",
|
| 64 |
+
"5D_Crime_Analysis.pdf",
|
| 65 |
+
"INT_SOP_CONTROL_logic.pdf",
|
| 66 |
+
"Staff_Officer_Playbook_5D.pdf"
|
| 67 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
# -------------------------
|
| 70 |
+
# KB Index (naive)
|
| 71 |
# -------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
class KBIndex:
|
| 73 |
def __init__(self):
|
| 74 |
+
self.docs = {}
|
| 75 |
+
|
| 76 |
def build_from_files(self, files_text):
|
| 77 |
+
self.docs = files_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
+
def query(self, q, top_k=3):
|
| 80 |
+
out = []
|
| 81 |
+
for fn, txt in self.docs.items():
|
| 82 |
+
if q.lower() in txt.lower():
|
| 83 |
+
out.append((fn, txt[:800]))
|
| 84 |
+
return out[:top_k]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
KB_INDEX = KBIndex()
|
| 87 |
KB_INDEX.build_from_files(FILES_TEXT)
|
| 88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
# -------------------------
|
| 90 |
+
# Operational Command Block
|
| 91 |
# -------------------------
|
| 92 |
+
def operational_command_prompt(answers_map, category):
|
| 93 |
+
user_text = "\n".join(f"{k}: {v}" for k, v in answers_map.items() if v.strip())
|
| 94 |
+
return [
|
| 95 |
+
{"role": "system", "content": SYSTEM_SAFE},
|
| 96 |
+
{"role": "user", "content": f"""
|
| 97 |
+
You are to prepare a structured, advisory-only report.
|
|
|
|
| 98 |
|
| 99 |
+
Category: {category}
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
+
Inputs:
|
| 102 |
+
{user_text}
|
|
|
|
| 103 |
|
| 104 |
+
Knowledge base excerpts (if any) must be considered.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
+
Report must include:
|
| 107 |
+
- Executive Summary
|
| 108 |
+
- Threat Assessment
|
| 109 |
+
- Course of Action (doctrinal, admin, advisory)
|
| 110 |
+
- Intelligence Summary
|
| 111 |
+
- Administrative Remediations (if applicable)
|
| 112 |
+
"""}
|
| 113 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
# -------------------------
|
| 116 |
+
# Questionnaires
|
| 117 |
# -------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
+
STD_QUESTIONS = [
|
| 120 |
+
"When and where was enemy sighted?",
|
| 121 |
+
"Coming from which direction?",
|
| 122 |
+
"What is the size of the enemy (how many men)?",
|
| 123 |
+
"What equipment and weapons are they carrying?",
|
| 124 |
+
"What vehicles are they using or are they on foot?",
|
| 125 |
+
"How far are they from any roads frequented by soldiers vehicles?",
|
| 126 |
+
"How far are they from any military unit camp?",
|
| 127 |
+
"How far are they from any deployed soldiers?",
|
| 128 |
+
"Are they getting support of locals? If so who are these locals?",
|
| 129 |
+
"What is their disposition? How are they spread out?",
|
| 130 |
+
"Do you have Reconnaissance and Surveillance soldiers near the area?",
|
| 131 |
+
"Did you get the information from local source or army personnel?",
|
| 132 |
+
"If from local source, did you confirm from a second source or R&S team?",
|
| 133 |
+
"How far is your commanded army unit from the enemy sighting?",
|
| 134 |
+
"What is the terrain (urban, jungle, hilly, rural)?"
|
| 135 |
+
]
|
| 136 |
|
| 137 |
+
ENH_SECTION_A = [
|
| 138 |
+
"Does the Bn have separate Ops planning and Int sections?",
|
| 139 |
+
"Does the Unit have int SOP? Any COA template?",
|
| 140 |
+
"Does the unit have a reconnaissance and surveillance plan?",
|
| 141 |
+
"Does the unit have Force Protection SOP / Threat Levels?",
|
| 142 |
+
"Does the unit have intelligence projection capability?"
|
| 143 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
+
ENH_SECTION_B = [
|
| 146 |
+
"Is there a vulnerability analysis tool for unit?",
|
| 147 |
+
"Does the unit employ randomness in movement?",
|
| 148 |
+
"Is there a source vetting system in place?",
|
| 149 |
+
"Does the unit treat intelligence as doctrine or just info?",
|
| 150 |
+
"Does the unit use counterintelligence in vulnerability analysis?"
|
| 151 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
+
ENH_SECTION_C = [
|
| 154 |
+
"Are int personnel embedded in routine ops?",
|
| 155 |
+
"Am I thinking of Threat or CO’s Situational Awareness?",
|
| 156 |
+
"What is my intent as Staff planning element?",
|
| 157 |
+
"Do I detect, deter, deny, deliver, or destroy?",
|
| 158 |
+
"Do external MI assets conform to 5D system?",
|
| 159 |
+
"Did I make vulnerability assessment based on Deter and Deny?",
|
| 160 |
+
"How do I account for Force Protection?",
|
| 161 |
+
"Do we attack threats’ SA, movement, tactics, or support?",
|
| 162 |
+
"Is the call deliberate or quick?",
|
| 163 |
+
"Do I clearly distinguish Warn, Surprise, SA?"
|
| 164 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
|
| 166 |
+
# PARA SF Qs
|
| 167 |
PARA_QUESTIONS_50 = [
|
| 168 |
+
f"Enemy Q{i+1}" for i in range(50)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
]
|
|
|
|
| 170 |
PARA_PRECAUTIONS_40 = [
|
| 171 |
+
f"Precaution {i+1}" for i in range(40)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
]
|
| 173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
# -------------------------
|
| 175 |
+
# Report Generators
|
| 176 |
# -------------------------
|
| 177 |
+
def generate_report_with_kb(answers_map, category, top_k=3):
|
| 178 |
+
kb_hits = []
|
| 179 |
+
for q, a in answers_map.items():
|
| 180 |
+
if not a.strip():
|
| 181 |
+
continue
|
| 182 |
+
kb_hits.extend(KB_INDEX.query(q, top_k=top_k))
|
| 183 |
+
excerpt_text = "\n".join(f"{fn}: {txt}" for fn, txt in kb_hits)
|
| 184 |
+
prompt = operational_command_prompt(answers_map, category)
|
| 185 |
+
if excerpt_text:
|
| 186 |
+
prompt[1]["content"] += f"\nKnowledge Base excerpts:\n{excerpt_text}\n"
|
| 187 |
+
try:
|
| 188 |
+
resp = client.chat.completions.create(
|
| 189 |
+
model="gpt-4o-mini",
|
| 190 |
+
messages=prompt,
|
| 191 |
+
max_tokens=700
|
| 192 |
+
)
|
| 193 |
+
return resp.choices[0].message.content.strip()
|
| 194 |
+
except Exception as e:
|
| 195 |
+
return f"[Error generating report: {e}]"
|
| 196 |
|
| 197 |
+
# PARA SF Inference
|
| 198 |
+
def para_sf_inference_runner(selected_files, pasted, answers_map):
|
| 199 |
+
kb_hits = []
|
| 200 |
+
for q, a in answers_map.items():
|
| 201 |
+
if not a.strip():
|
| 202 |
+
continue
|
| 203 |
+
kb_hits.extend(KB_INDEX.query(q, top_k=2))
|
| 204 |
+
excerpt_text = "\n".join(f"{fn}: {txt}" for fn, txt in kb_hits)
|
| 205 |
+
user_text = "\n".join(f"{k}: {v}" for k,v in answers_map.items() if v.strip())
|
| 206 |
+
prompt = [
|
| 207 |
+
{"role":"system","content":SYSTEM_SAFE},
|
| 208 |
+
{"role":"user","content":f"""
|
| 209 |
+
Prepare PARA SF non-actionable advisory. Use 5D system lens.
|
| 210 |
+
|
| 211 |
+
Inputs:
|
| 212 |
+
{user_text}
|
| 213 |
+
|
| 214 |
+
Fieldcraft notes:
|
| 215 |
+
{pasted}
|
| 216 |
+
|
| 217 |
+
Knowledge Base excerpts:
|
| 218 |
+
{excerpt_text}
|
| 219 |
+
|
| 220 |
+
Report must include:
|
| 221 |
+
- Doctrinal Course of Action
|
| 222 |
+
- Threat Assessment
|
| 223 |
+
- Intelligence Summary
|
| 224 |
+
- PARA SF Precautions & Protective Measures
|
| 225 |
+
"""}
|
| 226 |
+
]
|
| 227 |
+
try:
|
| 228 |
+
resp = client.chat.completions.create(
|
| 229 |
+
model="gpt-4o-mini",
|
| 230 |
+
messages=prompt,
|
| 231 |
+
max_tokens=800
|
| 232 |
+
)
|
| 233 |
+
return resp.choices[0].message.content.strip()
|
| 234 |
+
except Exception as e:
|
| 235 |
+
return f"[Error running PARA SF inference: {e}]"
|
| 236 |
|
| 237 |
# -------------------------
|
| 238 |
+
# UI
|
| 239 |
# -------------------------
|
| 240 |
with gr.Blocks() as demo:
|
| 241 |
gr.HTML(f'<img src="{BANNER_URL}" width="100%">')
|
| 242 |
+
gr.Markdown("# Kashmir AOR Action Plan — Battle Planner")
|
| 243 |
|
| 244 |
+
with gr.Tab("Standard"):
|
|
|
|
| 245 |
std_inputs = [gr.Textbox(label=q, lines=1) for q in STD_QUESTIONS]
|
| 246 |
+
std_button = gr.Button("Generate Standard Advisory")
|
| 247 |
+
std_output = gr.Textbox(label="Standard Advisory Report", lines=28)
|
| 248 |
+
def std_runner(*answers):
|
| 249 |
amap = dict(zip(STD_QUESTIONS, answers))
|
| 250 |
+
return generate_report_with_kb(amap, "Standard Threat Advisory")
|
| 251 |
+
std_button.click(std_runner, inputs=std_inputs, outputs=std_output)
|
| 252 |
+
|
| 253 |
+
with gr.Tab("Enhanced"):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
a_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_A]
|
| 255 |
b_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_B]
|
| 256 |
c_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_C]
|
| 257 |
+
gate_input = gr.Textbox(label="Gate Question", lines=1)
|
| 258 |
+
enh_button = gr.Button("Generate Enhanced Advisory")
|
| 259 |
+
enh_output = gr.Textbox(label="Enhanced Advisory Report", lines=28)
|
| 260 |
+
def enh_runner(*answers):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
la, lb, lc = len(ENH_SECTION_A), len(ENH_SECTION_B), len(ENH_SECTION_C)
|
| 262 |
+
amap = dict(zip(ENH_SECTION_A+ENH_SECTION_B+ENH_SECTION_C, answers[:la+lb+lc]))
|
| 263 |
+
return generate_report_with_kb(amap, "Enhanced Threat Advisory")
|
| 264 |
+
enh_button.click(enh_runner, inputs=a_inputs+b_inputs+c_inputs+[gate_input], outputs=enh_output)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
|
| 266 |
+
with gr.Tab("Threat Readiness"):
|
| 267 |
+
gr.Markdown("## Threat Readiness — Color-coded Commander Brief")
|
| 268 |
+
threat_button = gr.Button("Evaluate Threat Readiness")
|
| 269 |
+
threat_output = gr.Textbox(label="Threat Readiness & Diagnostics", lines=28)
|
| 270 |
+
def evaluate_threat_brief():
|
| 271 |
+
lines = []
|
| 272 |
+
lines.append("### Threat Readiness Level (Color-coded)")
|
| 273 |
+
lines.append("- 🔴 RED (<50%): Severe vulnerabilities.")
|
| 274 |
+
lines.append("- 🟠 ORANGE (50–69%): Moderate vulnerabilities.")
|
| 275 |
+
lines.append("- 🔵 BLUE (70–84%): Minor gaps.")
|
| 276 |
+
lines.append("- 🟢 GREEN (85–100%): Good readiness.\n")
|
| 277 |
+
lines.append("Commander’s Brief: Review deficiencies and remediation schedule.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
return "\n".join(lines)
|
| 279 |
+
threat_button.click(evaluate_threat_brief, inputs=[], outputs=threat_output)
|
| 280 |
+
|
| 281 |
+
with gr.Tab("PARA SF (Two Reports)"):
|
| 282 |
+
para_questions_inputs = [gr.Textbox(label=q, lines=1) for q in PARA_QUESTIONS_50]
|
| 283 |
+
para_fieldcraft = gr.Textbox(label="Paste Fieldcraft / SR notes", lines=6)
|
| 284 |
+
para_file_selector = gr.CheckboxGroup(choices=SF_PRIORITY_PDFS, label="Select SF KB files")
|
| 285 |
+
para_coa_btn = gr.Button("Generate COA / Threat Assessment / Intelligence Summary")
|
| 286 |
+
para_prec_btn = gr.Button("Generate PARA SF Precautions & Protective Advisory")
|
| 287 |
+
para_coa_out = gr.Textbox(label="COA / Threat Assessment / Intelligence Summary", lines=28)
|
| 288 |
+
para_prec_out = gr.Textbox(label="PARA SF Precautions & Protective Measures", lines=28)
|
| 289 |
+
def para_coa_runner(*all_inputs):
|
| 290 |
+
amap = dict(zip(PARA_QUESTIONS_50, all_inputs[:-2]))
|
| 291 |
+
pasted = all_inputs[-2] or ""
|
| 292 |
+
selected = all_inputs[-1] or []
|
| 293 |
+
return para_sf_inference_runner(selected, pasted, amap)
|
| 294 |
+
def para_prec_runner(*all_inputs):
|
| 295 |
+
amap = dict(zip(PARA_QUESTIONS_50, all_inputs[:-2]))
|
| 296 |
+
pasted = all_inputs[-2] or ""
|
| 297 |
+
selected = all_inputs[-1] or []
|
| 298 |
+
return para_sf_inference_runner(selected, pasted, amap)
|
| 299 |
+
para_coa_btn.click(para_coa_runner, inputs=para_questions_inputs+[para_fieldcraft, para_file_selector], outputs=para_coa_out)
|
| 300 |
+
para_prec_btn.click(para_prec_runner, inputs=para_questions_inputs+[para_fieldcraft, para_file_selector], outputs=para_prec_out)
|
| 301 |
|
| 302 |
if __name__ == "__main__":
|
| 303 |
demo.launch()
|