Spaces:
Runtime error
Runtime error
Update APPC.py
Browse files
APPC.py
CHANGED
|
@@ -1,719 +1,209 @@
|
|
| 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 |
-
# Try to import modern OpenAI client (recommended).
|
| 11 |
-
client = None
|
| 12 |
-
try:
|
| 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 |
-
"When where was enemy sighted.",
|
| 27 |
-
"Coming from which direction",
|
| 28 |
-
"what is the size of the enemy? How many men?",
|
| 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 |
-
ENH_SECTION_A = [
|
| 44 |
-
"Does the Bn have separate Ops planning and Int sections? Or do both have a common nodal point,for example an officer or JCO conversant with both Ops and Int.( Integration).",
|
| 45 |
-
"Does the Unit have int SOP? Is there any Course of Action template with provision for int feeds? Is there any Ops template?Ops SOP?",
|
| 46 |
-
"Does the unit have a proper reconnaissance and surveillance plan?Is it integrated with Ops and Int SOP?",
|
| 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 |
-
ENH_SECTION_B = [
|
| 58 |
-
"Am I thinking of the Threat or am I thinking about my COs Situational Awareness.",
|
| 59 |
-
"What is my intent as Staff planning element?Do i align myself with Cdrs Intent or do I point out all the alternatives not apparent to him?",
|
| 60 |
-
"Do I detect the enemy or do I deter him from hostile action,or do i deny him avenues of hostile action and intelligence about our activities and plans,or do I deliver the locals from his influence or do I plan to outright destroy him physically.The 5Ds.",
|
| 61 |
-
"My unit has no organic intelligence assets.I have to depend on sources and inputs from MI units.Do their way of working conform to 5D system?Can these assets get me information in line with 5D system requirements?Can I use my Bn soldiers somehow to work as per 5D system?",
|
| 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 |
-
ENH_SECTION_C = [
|
| 71 |
-
"Why was not the enemy's movement detected beforehand?",
|
| 72 |
-
"Why was there no HUMINT report on locals information about possibility of an attack?",
|
| 73 |
-
"Why was there no reconnaissance and surveillance report?",
|
| 74 |
-
"Why was there no advance Force Protection report?",
|
| 75 |
-
"Why was there no Tactical Command and Control nodes covertly emplaced in points equidistant from vulnerable targets like schools?",
|
| 76 |
-
"Why was there no Terrorist Targeting Standards Report that will predict likely targets of terrorist attack?",
|
| 77 |
-
"Why did the Army fail to observe indicators?",
|
| 78 |
-
"Why did the Army fail to exploit human terrain for advance information?",
|
| 79 |
-
"Why was there no exploitation of Physical terrain?",
|
| 80 |
-
"Why was forward operating zones absent?"
|
| 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",
|
| 117 |
"5D_Crime_Analysis.pdf",
|
| 118 |
-
"INT_SOP_CONTROL_logic.pdf"
|
| 119 |
]
|
| 120 |
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
""
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
"""
|
| 129 |
-
files_text = {}
|
| 130 |
-
for d in paths:
|
| 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 |
-
# Embeddings & retrieval helpers
|
| 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.chunks = [] # dicts: {file, id, text, emb}
|
| 204 |
-
def build_from_files(self, files_text):
|
| 205 |
-
self.chunks = []
|
| 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 |
-
# Load KB files (priority path knowledge_base)
|
| 247 |
-
# -------------------------
|
| 248 |
-
KB_PATHS = ["/mnt/data/knowledge_base", "/mnt/data"]
|
| 249 |
-
FILES_TEXT = read_all_files(KB_PATHS)
|
| 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 |
-
print("[KB] Total chunks indexed:", len(KB_INDEX.chunks))
|
| 281 |
-
print("[KB] Category diagnostics (found / missing):")
|
| 282 |
-
for cat, info in category_diagnostics.items():
|
| 283 |
-
print(f" - {cat}: found={len(info['found'])}, missing={len(info['missing'])}")
|
| 284 |
-
|
| 285 |
-
# -------------------------
|
| 286 |
-
# Safety system prompt (non-actionable)
|
| 287 |
-
# -------------------------
|
| 288 |
-
SYSTEM_SAFE = (
|
| 289 |
-
"You are an institutional analyst. You MUST follow these rules: "
|
| 290 |
-
"1) Provide ONLY non-actionable, administrative, doctrinal or training-oriented analysis. "
|
| 291 |
-
"2) If asked for operational content, refuse and instead produce administrative alternatives (SOPs, audits, training, doctrine). "
|
| 292 |
-
"3) Cite the KB chunk ids / filenames you used in reasoning. "
|
| 293 |
-
"4) DO NOT provide tactical step-by-step instructions."
|
| 294 |
-
)
|
| 295 |
-
|
| 296 |
-
# -------------------------
|
| 297 |
-
# Operational Command (plain-English instruction block)
|
| 298 |
-
# Paste this to the top of prompts so the model always follows it.
|
| 299 |
-
# -------------------------
|
| 300 |
-
OPERATIONAL_COMMANDS = """
|
| 301 |
-
OPERATIONAL COMMAND (ADMIN/D0CTRINAL ONLY) — MANDATORY:
|
| 302 |
-
1) PRIORITIZE these KB files when reasoning (priority order):
|
| 303 |
-
- Operational Course of Action Using the Kashmir Military Algorithm.pdf
|
| 304 |
-
- UPLOAD TO CHAT.pdf
|
| 305 |
-
- 5D_Crime_Analysis.pdf
|
| 306 |
-
- INT_SOP_CONTROL_logic.pdf
|
| 307 |
-
|
| 308 |
-
2) ALSO CONSULT all other PDFs found in the knowledge_base folder as secondary sources.
|
| 309 |
-
|
| 310 |
-
3) FOR STANDARD and ENHANCED REPORTS:
|
| 311 |
-
- DO NOT print the user's questions and answers verbatim in the visible report.
|
| 312 |
-
- Use the user's answers only as internal context for analysis.
|
| 313 |
-
- Base your advisory analysis on KB excerpts (cite file::chunk ids).
|
| 314 |
-
- Provide only administrative, doctrinal, training or audit-style recommendations.
|
| 315 |
-
- If asked for operational/tactical instructions, REFUSE and offer administrative alternatives.
|
| 316 |
-
|
| 317 |
-
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.
|
| 318 |
-
|
| 319 |
-
5) Always include a short "Sources cited" section that lists the file names and chunk ids that were used to produce the advisory.
|
| 320 |
-
"""
|
| 321 |
-
|
| 322 |
-
# -------------------------
|
| 323 |
-
# Prompt builders (redact Q/A in visible report)
|
| 324 |
-
# -------------------------
|
| 325 |
-
def _shorten(text, n=1200):
|
| 326 |
-
if not text:
|
| 327 |
-
return ""
|
| 328 |
-
return (text[:n] + "…") if len(text) > n else text
|
| 329 |
-
|
| 330 |
-
def build_standard_prompt_from_retrieved(retrieved_chunks):
|
| 331 |
-
"""
|
| 332 |
-
Build prompt where KB excerpts are passed to model and Q/A are not echoed.
|
| 333 |
-
"""
|
| 334 |
-
parts = []
|
| 335 |
-
parts.append(SYSTEM_SAFE)
|
| 336 |
-
parts.append(OPERATIONAL_COMMANDS)
|
| 337 |
-
parts.append("Produce a NON-ACTIONABLE Standard Advisory Report for a commander.")
|
| 338 |
-
parts.append("Include: Executive Summary (2-4 lines); Key Administrative Issues (bulleted); Prioritized Administrative Recommendations (numbered); Sources cited (file::chunk ids).")
|
| 339 |
-
parts.append("Do NOT repeat questions or answers verbatim. Use only KB excerpts below as your primary lens.")
|
| 340 |
-
parts.append("\n--- KB Excerpts (lens) ---\n")
|
| 341 |
-
# attach KB excerpts (may have duplicates across questions)
|
| 342 |
-
added = set()
|
| 343 |
-
for chunk_list in retrieved_chunks:
|
| 344 |
-
for c in chunk_list:
|
| 345 |
-
marker = f"{c['id']}|{c['file']}"
|
| 346 |
-
if marker in added:
|
| 347 |
-
continue
|
| 348 |
-
added.add(marker)
|
| 349 |
-
parts.append(f"--- {c['id']} (from {c['file']}) ---\n{_shorten(c['text'])}\n")
|
| 350 |
-
user_text = "\n".join(parts)
|
| 351 |
-
return [{"role":"system","content":parts[0]}, {"role":"user","content":user_text}]
|
| 352 |
-
|
| 353 |
-
def build_enhanced_prompt_from_retrieved(retrA, retrB, retrC, gate_text):
|
| 354 |
-
parts = []
|
| 355 |
-
parts.append(SYSTEM_SAFE)
|
| 356 |
-
parts.append(OPERATIONAL_COMMANDS)
|
| 357 |
-
parts.append("Produce a NON-ACTIONABLE Enhanced Staff Advisory for a commander.")
|
| 358 |
-
parts.append("Include: Executive Summary; Key doctrinal/organizational gaps; Prioritized Administrative Remediations; Measurement/audit suggestions; Sources cited.")
|
| 359 |
-
parts.append("Do NOT repeat questions or answers verbatim. Use KB excerpts below as your primary lens.")
|
| 360 |
-
parts.append("\n--- KB Excerpts (lens) ---\n")
|
| 361 |
-
added = set()
|
| 362 |
-
for clist in (retrA + retrB + retrC):
|
| 363 |
-
for c in clist:
|
| 364 |
-
marker = f"{c['id']}|{c['file']}"
|
| 365 |
-
if marker in added:
|
| 366 |
-
continue
|
| 367 |
-
added.add(marker)
|
| 368 |
-
parts.append(f"--- {c['id']} (from {c['file']}) ---\n{_shorten(c['text'])}\n")
|
| 369 |
-
if gate_text:
|
| 370 |
-
parts.append("\nGate assessment (used for analysis, not printed verbatim): [REDACTED]")
|
| 371 |
-
user_text = "\n".join(parts)
|
| 372 |
-
return [{"role":"system","content":parts[0]}, {"role":"user","content":user_text}]
|
| 373 |
|
| 374 |
# -------------------------
|
| 375 |
-
#
|
| 376 |
-
# -------------------------
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
|
| 389 |
# -------------------------
|
| 390 |
-
#
|
| 391 |
-
# -------------------------
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
rem.append("Publish R&S plan templates and run table-top exercises to validate them.")
|
| 435 |
-
if "force protection" in q or "route planner" in q:
|
| 436 |
-
rem.append("Institute FP review cycles and formal route planning checklists for missions and base movement.")
|
| 437 |
-
if "intelligence projection" in q or "forward intelligence" in q:
|
| 438 |
-
rem.append("Document intelligence projection concept, minimum manning and activation triggers in SOP.")
|
| 439 |
-
if "vulnerability analysis" in q:
|
| 440 |
-
rem.append("Adopt a vulnerability register and schedule regular audits and red-team reviews (administrative).")
|
| 441 |
-
if "randomness" in q:
|
| 442 |
-
rem.append("Implement randomized movement policies and document patterns for audit (ensure safety/legal compliance).")
|
| 443 |
-
if "source vetting" in q or "counterintelligence" in q:
|
| 444 |
-
rem.append("Build a source registry and CI vetting workflow with secure access and audit logs.")
|
| 445 |
-
if "doctrine" in q or "manual" in q:
|
| 446 |
-
rem.append("Compile and publish an internal intelligence doctrine/manual in the unit library.")
|
| 447 |
-
if "embedded int" in q:
|
| 448 |
-
rem.append("Define embedded-int roles, responsibilities and rotation cycles; maintain oversight.")
|
| 449 |
-
if not rem:
|
| 450 |
-
rem.append("Conduct a formal capability gap analysis and document required SOPs and resourcing.")
|
| 451 |
-
out = []
|
| 452 |
-
for r in rem:
|
| 453 |
-
if r not in out:
|
| 454 |
-
out.append(r)
|
| 455 |
-
return out
|
| 456 |
|
| 457 |
# -------------------------
|
| 458 |
-
#
|
| 459 |
# -------------------------
|
| 460 |
-
def
|
| 461 |
-
# Map each STD question to a logic category (enemy_detect_information for detection questions)
|
| 462 |
-
# For simplicity: use enemy_detect_information for STD questions
|
| 463 |
-
retrieved = []
|
| 464 |
-
prefer_files = LOGIC_PDFS.get("enemy_detect_information", []) + PRIORITY_PDFS
|
| 465 |
-
for q in STD_QUESTIONS:
|
| 466 |
-
ans = answers_map.get(q, "")
|
| 467 |
-
query_text = (str(ans).strip() or q)
|
| 468 |
-
chunks = kb_index.retrieve(query_text, top_k=top_k_per_answer, prefer_files=prefer_files)
|
| 469 |
-
retrieved.append(chunks)
|
| 470 |
-
messages = build_standard_prompt_from_retrieved(retrieved)
|
| 471 |
-
# Force OpenAI call
|
| 472 |
try:
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
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]
|
| 497 |
-
messages = build_enhanced_prompt_from_retrieved(retrieved_A, retrieved_B, retrieved_C, gate_text)
|
| 498 |
-
try:
|
| 499 |
-
out = call_chat_api(messages, max_tokens=1600)
|
| 500 |
-
return out
|
| 501 |
except Exception as e:
|
| 502 |
-
|
| 503 |
-
lines.append("Administrative gaps and remediations:\n")
|
| 504 |
-
for q in ENH_SECTION_A:
|
| 505 |
-
a = a_map.get(q,"")
|
| 506 |
-
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 507 |
-
lines.append(f"- {q}")
|
| 508 |
-
for r in remediation_for_enh_question(q):
|
| 509 |
-
lines.append(f" *Admin remediation:* {r}")
|
| 510 |
-
for q in ENH_SECTION_B:
|
| 511 |
-
b = b_map.get(q,"")
|
| 512 |
-
if not b or str(b).strip().lower() in ("","no","n","none","negative"):
|
| 513 |
-
lines.append(f"- {q}")
|
| 514 |
-
for r in remediation_for_enh_question(q):
|
| 515 |
-
lines.append(f" *Admin remediation:* {r}")
|
| 516 |
-
for q in ENH_SECTION_C:
|
| 517 |
-
c = c_map.get(q,"")
|
| 518 |
-
if not c or str(c).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 |
-
lines.append("\nSources: (OpenAI unreachable or KB partial)")
|
| 523 |
-
return "\n".join(lines)
|
| 524 |
|
| 525 |
# -------------------------
|
| 526 |
-
#
|
| 527 |
-
# -------------------------
|
| 528 |
-
def evaluate_threat_brief_from_answers(all_vals):
|
| 529 |
-
total = len(STD_QUESTIONS) + len(ENH_SECTION_A) + len(ENH_SECTION_B) + len(ENH_SECTION_C)
|
| 530 |
-
vals = list(all_vals)[:total] + [""] * max(0, total - len(all_vals))
|
| 531 |
-
yes_count = sum(1 for v in vals if v and str(v).strip().lower() in ("yes","y","true","1","affirmative","confirmed"))
|
| 532 |
-
readiness = int((yes_count / total) * 100) if total else 0
|
| 533 |
-
if readiness >= 85:
|
| 534 |
-
color = "🟢 GREEN"
|
| 535 |
-
meaning = "Peacetime / strong readiness"
|
| 536 |
-
elif readiness >= 70:
|
| 537 |
-
color = "🔵 BLUE"
|
| 538 |
-
meaning = "Anticipated short-term threat readiness"
|
| 539 |
-
elif readiness >= 50:
|
| 540 |
-
color = "🟠 ORANGE"
|
| 541 |
-
meaning = "Imminent threat readiness (admin gaps present)"
|
| 542 |
-
else:
|
| 543 |
-
color = "🔴 RED"
|
| 544 |
-
meaning = "Low readiness - significant administrative gaps"
|
| 545 |
-
|
| 546 |
-
deficiencies = []
|
| 547 |
-
rems = []
|
| 548 |
-
idx = 0
|
| 549 |
-
for q in STD_QUESTIONS:
|
| 550 |
-
a = vals[idx]; idx+=1
|
| 551 |
-
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 552 |
-
deficiencies.append(f"STD: {q}")
|
| 553 |
-
rems.extend(remediation_for_std_question(q))
|
| 554 |
-
for q in ENH_SECTION_A:
|
| 555 |
-
a = vals[idx]; idx+=1
|
| 556 |
-
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 557 |
-
deficiencies.append(f"ENH A: {q}")
|
| 558 |
-
rems.extend(remediation_for_enh_question(q))
|
| 559 |
-
for q in ENH_SECTION_B:
|
| 560 |
-
a = vals[idx]; idx+=1
|
| 561 |
-
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 562 |
-
deficiencies.append(f"ENH B: {q}")
|
| 563 |
-
rems.extend(remediation_for_enh_question(q))
|
| 564 |
-
for q in ENH_SECTION_C:
|
| 565 |
-
a = vals[idx]; idx+=1
|
| 566 |
-
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 567 |
-
deficiencies.append(f"ENH C: {q}")
|
| 568 |
-
rems.extend(remediation_for_enh_question(q))
|
| 569 |
-
|
| 570 |
-
unique_rems = []
|
| 571 |
-
for r in rems:
|
| 572 |
-
if r not in unique_rems:
|
| 573 |
-
unique_rems.append(r)
|
| 574 |
-
|
| 575 |
-
lines = []
|
| 576 |
-
lines.append(f"# Threat Readiness: {readiness}%")
|
| 577 |
-
lines.append(f"**Status:** {color} — {meaning}\n")
|
| 578 |
-
lines.append("## Commander’s Guide to WARN Levels:")
|
| 579 |
-
lines.append("- 🔴 RED (<50%): Significant administrative deficiencies. Prioritize SOP, CI, and audits.")
|
| 580 |
-
lines.append("- 🟠 ORANGE (50–69%): Moderate deficiencies; strengthen doctrine & R&S validation.")
|
| 581 |
-
lines.append("- 🔵 BLUE (70–84%): Minor gaps; schedule audits and training.")
|
| 582 |
-
lines.append("- 🟢 GREEN (85–100%): Good status; maintain vigilance and periodic reviews.\n")
|
| 583 |
-
if deficiencies:
|
| 584 |
-
lines.append("## Identified Administrative Deficiencies (summary)")
|
| 585 |
-
for d in deficiencies[:40]:
|
| 586 |
-
lines.append(f"- {d}")
|
| 587 |
-
if len(deficiencies)>40:
|
| 588 |
-
lines.append(f"... and {len(deficiencies)-40} more.")
|
| 589 |
-
else:
|
| 590 |
-
lines.append("## Identified Administrative Deficiencies: None found.")
|
| 591 |
-
lines.append("\n## Example Administrative Remediations (prioritized)")
|
| 592 |
-
if unique_rems:
|
| 593 |
-
for i, r in enumerate(unique_rems[:40],1):
|
| 594 |
-
lines.append(f"{i}. {r}")
|
| 595 |
-
else:
|
| 596 |
-
lines.append("- No admin remediations needed based on current answers.")
|
| 597 |
-
lines.append("\n**Note:** This brief is NON-ACTIONABLE. It focuses on doctrine, SOPs, audits and training — no operational instructions.")
|
| 598 |
-
return "\n".join(lines)
|
| 599 |
-
|
| 600 |
-
# -------------------------
|
| 601 |
-
# Doctrinal summary constant
|
| 602 |
-
# -------------------------
|
| 603 |
-
DOCTRINAL_SUMMARY_DETAILED = """
|
| 604 |
-
# 📘 Doctrinal Summary (Sanitized, Detailed)
|
| 605 |
-
|
| 606 |
-
Purpose and Approach
|
| 607 |
-
- Intelligence is treated as a combat function, not just support. Adversaries (cartels, terrorists, insurgents) have quasi-military structures that must be mapped and targeted using intelligence architectures.
|
| 608 |
-
- Objective: Give the commander a persistent, adaptive situational picture by embedding Command, Control, Communications, Intelligence, Surveillance, Reconnaissance (C3ISR) across the AO.
|
| 609 |
-
- Scope: Covers architecture, zoning, collection posture, force protection, staff planning, and doctrinal lenses. No tactical timings or unit counts are provided here.
|
| 610 |
-
|
| 611 |
-
(Excerpt sanitized — administrative doctrinal guidance only.)
|
| 612 |
-
"""
|
| 613 |
-
|
| 614 |
-
# -------------------------
|
| 615 |
-
# Gradio UI (preserve your two tabs; add doctrinal & threat)
|
| 616 |
# -------------------------
|
| 617 |
with gr.Blocks() as demo:
|
| 618 |
-
gr.
|
| 619 |
-
gr.Markdown("# Kashmir AO Action Plan for Inf — Army Analyst Tool & Battle Planner (Sanitized)")
|
| 620 |
-
|
| 621 |
-
# ----------- Standard Analyst Tab -----------
|
| 622 |
-
with gr.Tab("Standard Analyst"):
|
| 623 |
-
std_inputs = [gr.Textbox(label=q, lines=1) for q in STD_QUESTIONS]
|
| 624 |
-
std_btn = gr.Button("Generate Standard Advisory (KB-lens)")
|
| 625 |
-
std_output = gr.Textbox(label="Standard Advisory (sanitized)", lines=30)
|
| 626 |
-
def on_std_click(*answers):
|
| 627 |
-
amap = dict(zip(STD_QUESTIONS, answers))
|
| 628 |
-
try:
|
| 629 |
-
return generate_standard_report_with_lens(amap, KB_INDEX, top_k_per_answer=3)
|
| 630 |
-
except Exception as e:
|
| 631 |
-
return f"[Error generating Standard Advisory: {e}]\n\nEnsure OPENAI_API_KEY is set and KB files are accessible in /mnt/data/knowledge_base or /mnt/data."
|
| 632 |
-
std_btn.click(on_std_click, inputs=std_inputs, outputs=std_output)
|
| 633 |
-
|
| 634 |
-
# ----------- Enhanced Analyst Tab -----------
|
| 635 |
-
with gr.Tab("Enhanced Analyst (Sectioned)"):
|
| 636 |
-
a_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_A]
|
| 637 |
-
b_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_B]
|
| 638 |
-
c_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_C]
|
| 639 |
-
gate_q = gr.Textbox(label="Gate Assessment (commander's final note) — used for analysis only", lines=1)
|
| 640 |
-
enh_btn = gr.Button("Generate Enhanced Advisory (KB-lens)")
|
| 641 |
-
enh_output = gr.Textbox(label="Enhanced Advisory (sanitized)", lines=36)
|
| 642 |
-
def on_enh_click(*args):
|
| 643 |
-
la = len(ENH_SECTION_A); lb = len(ENH_SECTION_B); lc = len(ENH_SECTION_C)
|
| 644 |
-
vals = list(args)
|
| 645 |
-
while len(vals) < (la+lb+lc+1):
|
| 646 |
-
vals.append("")
|
| 647 |
-
a_vals = vals[:la]; b_vals = vals[la:la+lb]; c_vals = vals[la+lb:la+lb+lc]; gate_val = vals[-1]
|
| 648 |
-
a_map = dict(zip(ENH_SECTION_A, a_vals))
|
| 649 |
-
b_map = dict(zip(ENH_SECTION_B, b_vals))
|
| 650 |
-
c_map = dict(zip(ENH_SECTION_C, c_vals))
|
| 651 |
-
try:
|
| 652 |
-
return generate_enhanced_report_with_lens(a_map, b_map, c_map, gate_val, KB_INDEX, top_k_per_answer=3)
|
| 653 |
-
except Exception as e:
|
| 654 |
-
return f"[Error generating Enhanced Advisory: {e}]\n\nEnsure OPENAI_API_KEY is set and KB files are accessible."
|
| 655 |
-
enh_btn.click(on_enh_click, inputs=a_inputs+b_inputs+c_inputs+[gate_q], outputs=enh_output)
|
| 656 |
-
|
| 657 |
-
# ----------- Doctrinal Summary Tab -----------
|
| 658 |
-
with gr.Tab("Doctrinal Summary (Sanitized, Detailed)"):
|
| 659 |
-
gr.Markdown(DOCTRINAL_SUMMARY_DETAILED)
|
| 660 |
-
|
| 661 |
-
# ----------- Threat Readiness Tab -----------
|
| 662 |
-
with gr.Tab("Threat Readiness"):
|
| 663 |
-
gr.Markdown("## Threat Readiness — color-coded commander brief (administrative only)")
|
| 664 |
-
std_state = gr.State()
|
| 665 |
-
a_state = gr.State()
|
| 666 |
-
b_state = gr.State()
|
| 667 |
-
c_state = gr.State()
|
| 668 |
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
return list(answers)
|
| 672 |
-
std_btn.click(cache_std, inputs=std_inputs, outputs=[std_state])
|
| 673 |
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
a_vals = answers = list(args for args in args_for_dummy()) # placeholder if needed
|
| 677 |
-
# but we'll use the outputs from enh_btn click defined above to supply state
|
| 678 |
-
# here we implement simple caching via the click triggered on the enh_btn (see below)
|
| 679 |
-
return None
|
| 680 |
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
threat_output = gr.Textbox(label="Threat Readiness & Admin Diagnostics", lines=30)
|
| 685 |
-
|
| 686 |
-
# For simplicity, implement a function that reads the current inputs on demand
|
| 687 |
-
def threat_runner_live(*ignore):
|
| 688 |
-
# collect current values from the input widgets via the demo state (we accept they were filled)
|
| 689 |
-
# To avoid complex state wiring, require the user to press the "Generate Standard Advisory" and "Generate Enhanced Advisory"
|
| 690 |
-
# buttons first; the app warns otherwise.
|
| 691 |
-
return ("To evaluate threat readiness: please first generate the Standard Advisory (press the button in the Standard tab) "
|
| 692 |
-
"and the Enhanced Advisory (press the button in the Enhanced tab). This will cache the answers and permit a full assessment."
|
| 693 |
-
"\n\nNote: threat readiness evaluates administrative readiness and lists deficiencies and remediations only.")
|
| 694 |
-
threat_btn.click(threat_runner_live, inputs=None, outputs=threat_output)
|
| 695 |
-
|
| 696 |
-
# ----------- Diagnostics accordion -----------
|
| 697 |
-
with gr.Accordion("Operational notes & diagnostics (admin only)", open=False):
|
| 698 |
-
# Show which KB files were indexed and category diagnostics
|
| 699 |
-
files_list_md = "\n".join(f"- {k}" for k in FILES_TEXT.keys()) if FILES_TEXT else "- [NO KB FILES FOUND]"
|
| 700 |
-
diag_md = (
|
| 701 |
-
f"**Indexed KB files (priority first):**\n\n{files_list_md}\n\n"
|
| 702 |
-
f"**Total KB chunks indexed:** {len(KB_INDEX.chunks)}\n\n"
|
| 703 |
-
"**Category diagnostics:**\n"
|
| 704 |
)
|
| 705 |
-
for cat, info in category_diagnostics.items():
|
| 706 |
-
diag_md += f"- {cat}: found={len(info['found'])}, missing={len(info['missing'])}\n"
|
| 707 |
-
if info['missing']:
|
| 708 |
-
diag_md += f" - Missing: {info['missing']}\n"
|
| 709 |
-
gr.Markdown(diag_md)
|
| 710 |
-
if client is None:
|
| 711 |
-
gr.Markdown("**OpenAI client: NOT INITIALIZED** — set OPENAI_API_KEY to enable GPT-based advisory reports.")
|
| 712 |
-
else:
|
| 713 |
-
gr.Markdown("**OpenAI client: OK**")
|
| 714 |
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 718 |
if __name__ == "__main__":
|
| 719 |
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
|
|
|
|
|
|
| 2 |
import gradio as gr
|
| 3 |
+
from openai import OpenAI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
# -------------------------
|
| 6 |
+
# OpenAI client
|
| 7 |
# -------------------------
|
| 8 |
+
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
# -------------------------
|
| 11 |
+
# Priority PDFs
|
| 12 |
# -------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
PRIORITY_PDFS = [
|
| 14 |
"Operational Course of Action Using the Kashmir Military Algorithm.pdf",
|
| 15 |
"UPLOAD TO CHAT.pdf",
|
| 16 |
"5D_Crime_Analysis.pdf",
|
| 17 |
+
"INT_SOP_CONTROL_logic.pdf",
|
| 18 |
]
|
| 19 |
|
| 20 |
+
SF_PRIORITY_PDFS = [
|
| 21 |
+
"Operational Course of Action Using the Kashmir Military Algorithm.pdf",
|
| 22 |
+
"UPLOAD TO CHAT.pdf",
|
| 23 |
+
"5D_Crime_Analysis.pdf",
|
| 24 |
+
"INT_SOP_CONTROL_logic.pdf",
|
| 25 |
+
"Staff_Officer_Playbook_5D.pdf"
|
| 26 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
# -------------------------
|
| 29 |
+
# PARA SF Questions (50)
|
| 30 |
+
# -------------------------
|
| 31 |
+
PARA_QUESTIONS_50 = [
|
| 32 |
+
"Where was the enemy sighted?",
|
| 33 |
+
"At what time was the enemy detected?",
|
| 34 |
+
"What is the size of the enemy force?",
|
| 35 |
+
"What is the disposition/formation of the enemy?",
|
| 36 |
+
"What weapons and equipment are they carrying?",
|
| 37 |
+
"Are they supported by vehicles or on foot?",
|
| 38 |
+
"What direction are they approaching from?",
|
| 39 |
+
"How far are they from main supply routes?",
|
| 40 |
+
"How far are they from friendly bases?",
|
| 41 |
+
"Are they near civilian population centers?",
|
| 42 |
+
"Do they have air or drone support?",
|
| 43 |
+
"What is their reconnaissance capability?",
|
| 44 |
+
"What is their fire support capability?",
|
| 45 |
+
"Have they employed snipers?",
|
| 46 |
+
"What is their past history of ambushes?",
|
| 47 |
+
"What is their maneuver style?",
|
| 48 |
+
"Are they centralized or dispersed?",
|
| 49 |
+
"Are they supported by locals?",
|
| 50 |
+
"Which groups of locals support them?",
|
| 51 |
+
"Are they conducting deception operations?",
|
| 52 |
+
"Are they blending with civilian population?",
|
| 53 |
+
"Have they planted false information sources?",
|
| 54 |
+
"What is the terrain (urban, jungle, hills, rural)?",
|
| 55 |
+
"Do they exploit night movement?",
|
| 56 |
+
"What is their communication capability?",
|
| 57 |
+
"Are they radio silent?",
|
| 58 |
+
"Are they using couriers?",
|
| 59 |
+
"Are they using tunnels or hidden routes?",
|
| 60 |
+
"Are they mining or IED laying?",
|
| 61 |
+
"Are they targeting leadership?",
|
| 62 |
+
"Are they targeting logistics routes?",
|
| 63 |
+
"Have they conducted psychological ops?",
|
| 64 |
+
"What is their morale level?",
|
| 65 |
+
"Do they avoid direct contact?",
|
| 66 |
+
"Have they attacked critical infrastructure?",
|
| 67 |
+
"What infiltration methods do they prefer?",
|
| 68 |
+
"Do they coordinate with other hostile groups?",
|
| 69 |
+
"Are they foreign trained?",
|
| 70 |
+
"What camouflage or disguise do they use?",
|
| 71 |
+
"Are they probing defenses?",
|
| 72 |
+
"Have they rehearsed observed tactics?",
|
| 73 |
+
"Do they use women/children as informants?",
|
| 74 |
+
"Are they equipped for prolonged firefight?",
|
| 75 |
+
"Do they retreat quickly after attacks?",
|
| 76 |
+
"Have they used suicide attackers?",
|
| 77 |
+
"Do they use cyber or electronic warfare?",
|
| 78 |
+
"What intelligence capabilities do they possess?",
|
| 79 |
+
"Do they adapt tactics quickly?",
|
| 80 |
+
"Have they mapped friendly positions?",
|
| 81 |
+
"Do they exploit weather or festivals?",
|
| 82 |
+
]
|
| 83 |
|
| 84 |
# -------------------------
|
| 85 |
+
# PARA SF Precautions (40)
|
| 86 |
+
# -------------------------
|
| 87 |
+
PARA_PRECAUTIONS_40 = [
|
| 88 |
+
"Rotate patrol timings to prevent enemy surveillance patterning.",
|
| 89 |
+
"Use counter-surveillance teams on all movements.",
|
| 90 |
+
"Deploy deception convoys to mislead enemy observers.",
|
| 91 |
+
"Maintain strict radio discipline with burst transmissions.",
|
| 92 |
+
"Conceal movement using terrain masking.",
|
| 93 |
+
"Randomize entry and exit points of patrols.",
|
| 94 |
+
"Vary resupply routes and timings.",
|
| 95 |
+
"Conduct route clearance with mine detection.",
|
| 96 |
+
"Maintain local informant vetting system.",
|
| 97 |
+
"Employ reconnaissance drones for forward scanning.",
|
| 98 |
+
"Deploy early warning observation posts.",
|
| 99 |
+
"Integrate snipers for overwatch on patrols.",
|
| 100 |
+
"Use noise/light discipline at night.",
|
| 101 |
+
"Rehearse immediate ambush reaction drills.",
|
| 102 |
+
"Conduct periodic camp perimeter red-teaming.",
|
| 103 |
+
"Disperse high-value assets across base.",
|
| 104 |
+
"Rotate guard posts unpredictably.",
|
| 105 |
+
"Conduct deception radio chatter.",
|
| 106 |
+
"Run regular psychological operations to counter enemy influence.",
|
| 107 |
+
"Screen all civilian entrants to bases.",
|
| 108 |
+
"Harden logistics convoys with escort vehicles.",
|
| 109 |
+
"Rehearse casualty evacuation drills.",
|
| 110 |
+
"Regularly sanitize friendly comms from leaks.",
|
| 111 |
+
"Embed intelligence staff with operations.",
|
| 112 |
+
"Maintain tactical quick reaction force alert.",
|
| 113 |
+
"Rotate unit sectors periodically.",
|
| 114 |
+
"Employ camouflage nets on vehicles.",
|
| 115 |
+
"Reinforce gate security procedures.",
|
| 116 |
+
"Test night vision capability under field stress.",
|
| 117 |
+
"Create fallback positions in base defense.",
|
| 118 |
+
"Enforce strict OPSEC for families.",
|
| 119 |
+
"Maintain reserve ammunition caches.",
|
| 120 |
+
"Regularly train troops in civilian blending.",
|
| 121 |
+
"Deploy dummy positions to confuse enemy recon.",
|
| 122 |
+
"Audit source registry quarterly.",
|
| 123 |
+
"Cross-check intelligence feeds with CI.",
|
| 124 |
+
"Run deception exercises periodically.",
|
| 125 |
+
"Embed force protection analysts with staff.",
|
| 126 |
+
"Maintain contingency withdrawal routes.",
|
| 127 |
+
"Audit cyber hygiene and device discipline."
|
| 128 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
# -------------------------
|
| 131 |
+
# PARA SF Inference Runner
|
| 132 |
# -------------------------
|
| 133 |
+
def para_sf_inference_runner(selected_files, pasted_notes, answers_map):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
try:
|
| 135 |
+
# Context building — do NOT echo raw Q&A
|
| 136 |
+
context = []
|
| 137 |
+
if pasted_notes:
|
| 138 |
+
context.append("Fieldcraft / SR notes provided:\n" + pasted_notes)
|
| 139 |
+
if selected_files:
|
| 140 |
+
context.append("Priority SF doctrinal PDFs used:\n" + ", ".join(selected_files))
|
| 141 |
+
|
| 142 |
+
# Instead of feeding raw Q&A text, only tell model answers exist
|
| 143 |
+
if any(v.strip() for v in answers_map.values()):
|
| 144 |
+
context.append("Commander has provided a full set of answers. Use them through the 5D lens to infer threat, COAs, intelligence summary, and precautions. Do NOT restate questions or answers. Only provide advisory conclusions.")
|
| 145 |
+
|
| 146 |
+
full_prompt = "\n\n".join(context) if context else "No inputs."
|
| 147 |
+
|
| 148 |
+
response = client.chat.completions.create(
|
| 149 |
+
model="gpt-4o-mini",
|
| 150 |
+
messages=[
|
| 151 |
+
{"role": "system", "content": "You are an SF doctrinal analyst. Use PARA SF doctrine, 5D Crime Analysis, Kashmir Algorithm, and uploaded PDFs to produce threat assessments, COAs, intelligence summaries, and protective measures. Never output raw Q&A. Never output attack tactics. Only defensive readiness."},
|
| 152 |
+
{"role": "user", "content": full_prompt}
|
| 153 |
+
],
|
| 154 |
+
temperature=0.4,
|
| 155 |
+
max_tokens=1200,
|
| 156 |
+
)
|
| 157 |
+
return response.choices[0].message.content
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
except Exception as e:
|
| 159 |
+
return f"[Error in PARA SF runner: {e}]"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
# -------------------------
|
| 162 |
+
# Gradio App
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
# -------------------------
|
| 164 |
with gr.Blocks() as demo:
|
| 165 |
+
gr.Markdown("# Kashmir AOR Battle Planner")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
+
with gr.Tab("PARA SF (Two Reports)"):
|
| 168 |
+
gr.Markdown("## PARA SF — Administrative Non-Actionable Advisories")
|
|
|
|
|
|
|
| 169 |
|
| 170 |
+
para_questions_inputs = [gr.Textbox(label=q, lines=1) for q in PARA_QUESTIONS_50]
|
| 171 |
+
para_fieldcraft = gr.Textbox(label="Paste SR / fieldcraft notes", lines=6)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
+
para_file_selector = gr.CheckboxGroup(
|
| 174 |
+
choices=SF_PRIORITY_PDFS,
|
| 175 |
+
label="Select SF KB files (optional)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
|
| 178 |
+
para_coa_btn = gr.Button("Generate COA / Threat Assessment / Intel Summary")
|
| 179 |
+
para_prec_btn = gr.Button("Generate PARA SF Precautions & Protective Advisory")
|
| 180 |
+
|
| 181 |
+
para_coa_out = gr.Textbox(label="COA / Threat Assessment / Intelligence Summary", lines=28)
|
| 182 |
+
para_prec_out = gr.Textbox(label="Precautions & Protective Measures", lines=28)
|
| 183 |
+
|
| 184 |
+
def para_coa_runner(*all_inputs):
|
| 185 |
+
answers = list(all_inputs[:-2])
|
| 186 |
+
pasted = all_inputs[-2] or ""
|
| 187 |
+
selected = all_inputs[-1] or []
|
| 188 |
+
answers_map = dict(zip(PARA_QUESTIONS_50, answers))
|
| 189 |
+
return para_sf_inference_runner(selected, pasted, answers_map)
|
| 190 |
+
|
| 191 |
+
def para_prec_runner(*all_inputs):
|
| 192 |
+
answers = list(all_inputs[:-2])
|
| 193 |
+
pasted = all_inputs[-2] or ""
|
| 194 |
+
selected = all_inputs[-1] or []
|
| 195 |
+
answers_map = dict(zip(PARA_QUESTIONS_50, answers))
|
| 196 |
+
full = para_sf_inference_runner(selected, pasted, answers_map)
|
| 197 |
+
if any(m in full for m in ["Precautions", "Protective", "Measures"]):
|
| 198 |
+
return full
|
| 199 |
+
else:
|
| 200 |
+
lines = ["[Fallback Precautions]"]
|
| 201 |
+
for i, itm in enumerate(PARA_PRECAUTIONS_40[:12], start=1):
|
| 202 |
+
lines.append(f"{i}. {itm} — Remediation: Implement & audit quarterly.")
|
| 203 |
+
return "\n".join(lines)
|
| 204 |
+
|
| 205 |
+
para_coa_btn.click(para_coa_runner, inputs=para_questions_inputs + [para_fieldcraft, para_file_selector], outputs=para_coa_out)
|
| 206 |
+
para_prec_btn.click(para_prec_runner, inputs=para_questions_inputs + [para_fieldcraft, para_file_selector], outputs=para_prec_out)
|
| 207 |
+
|
| 208 |
if __name__ == "__main__":
|
| 209 |
demo.launch()
|