Militaryint commited on
Commit
a30c9c8
·
verified ·
1 Parent(s): 696c0f5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +230 -846
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
- # 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
- # QUESTIONS (exact phrasing you requested)
24
  # -------------------------
25
- STD_QUESTIONS = [
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
- # Logic categories → PDF lists (you approved this mapping)
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
- # File reading & chunking (larger chunks for richer context)
123
  # -------------------------
124
- def read_all_files(paths):
125
- """
126
- Read PDFs and text files from the provided list of folders.
127
- Returns dict: {filename: text}
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 = ["knowledge_base"]
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
-
281
  # -------------------------
282
- # PARA SF Priority PDFs
283
  # -------------------------
284
- SF_PRIORITY_PDFS = [
285
- "Operational Course of Action Using the Kashmir Military Algorithm.pdf",
286
- "UPLOAD TO CHAT.pdf",
287
- "5D_Crime_Analysis.pdf",
288
- "INT_SOP_CONTROL_logic.pdf",
289
- "Staff_Officer_Playbook_5D.pdf"
290
- ]
291
 
292
- print("[KB] Total chunks indexed:", len(KB_INDEX.chunks))
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
- # Safety system prompt (non-actionable)
299
- # -------------------------
300
 
301
- SYSTEM_SAFE = (
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
- # Operational Command (plain-English instruction block)
310
- # Paste this to the top of prompts so the model always follows it.
311
- # -------------------------
312
- OPERATIONAL_COMMANDS = """
313
- OPERATIONAL COMMAND (ADMIN/D0CTRINAL ONLY) — MANDATORY:
314
- 1) PRIORITIZE these KB files when reasoning (priority order):
315
- - Operational Course of Action Using the Kashmir Military Algorithm.pdf
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
- # Prompt builders (redact Q/A in visible report)
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
- # Chat API wrapper
388
- # -------------------------
389
- def call_chat_api(messages, max_tokens=1400, model="gpt-4o-mini"):
390
- if client is None:
391
- raise RuntimeError("OpenAI client not initialized. Set OPENAI_API_KEY in the environment.")
392
- try:
393
- resp = client.chat.completions.create(model=model, messages=messages, max_tokens=max_tokens)
394
- try:
395
- return resp["choices"][0]["message"]["content"]
396
- except Exception:
397
- return resp.choices[0].message.content
398
- except Exception as e:
399
- raise RuntimeError(f"OpenAI API call failed: {e}")
 
 
 
400
 
401
- # -------------------------
402
- # Deterministic remediation helpers (administrative only)
403
- # -------------------------
404
- def remediation_for_std_question(q_text):
405
- q = q_text.lower()
406
- rem = []
407
- if "sighted" in q or "where" in q:
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
- # Report generation functions
471
- # -------------------------
472
- def generate_standard_report_with_lens(answers_map, kb_index, top_k_per_answer=3):
473
- # Map each STD question to a logic category (enemy_detect_information for detection questions)
474
- # For simplicity: use enemy_detect_information for STD questions
475
- retrieved = []
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
- # Threat readiness
539
- # -------------------------
540
- def evaluate_threat_brief_from_answers(all_vals):
541
- total = len(STD_QUESTIONS) + len(ENH_SECTION_A) + len(ENH_SECTION_B) + len(ENH_SECTION_C)
542
- vals = list(all_vals)[:total] + [""] * max(0, total - len(all_vals))
543
- yes_count = sum(1 for v in vals if v and str(v).strip().lower() in ("yes","y","true","1","affirmative","confirmed"))
544
- readiness = int((yes_count / total) * 100) if total else 0
545
- if readiness >= 85:
546
- color = "🟢 GREEN"
547
- meaning = "Peacetime / strong readiness"
548
- elif readiness >= 70:
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
- "When and at what exact location was the enemy last observed?",
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
- "Confirm all SITREP entries include mandatory geo/time/observer-confidence fields.",
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
- # Doctrinal summary constant
715
  # -------------------------
716
- DOCTRINAL_SUMMARY_DETAILED = """
717
- # 📘 Doctrinal Summary (Sanitized, Detailed)
718
-
719
- Purpose and Approach
720
- - 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.
721
- - Objective: Give the commander a persistent, adaptive situational picture by embedding Command, Control, Communications, Intelligence, Surveillance, Reconnaissance (C3ISR) across the AO.
722
- - Scope: Covers architecture, zoning, collection posture, force protection, staff planning, and doctrinal lenses. No tactical timings or unit counts are provided here.
 
 
 
 
 
 
 
 
 
 
 
 
723
 
724
- (Excerpt sanitized administrative doctrinal guidance only.)
725
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
 
727
  # -------------------------
728
- # Gradio UI (preserve your two tabs; add doctrinal & threat)
729
  # -------------------------
730
  with gr.Blocks() as demo:
731
  gr.HTML(f'<img src="{BANNER_URL}" width="100%">')
732
- gr.Markdown("# Kashmir AO Action Plan for Inf Army Analyst Tool & Battle Planner (Sanitized)")
733
 
734
- # ----------- Standard Analyst Tab -----------
735
- with gr.Tab("Standard Analyst"):
736
  std_inputs = [gr.Textbox(label=q, lines=1) for q in STD_QUESTIONS]
737
- std_btn = gr.Button("Generate Standard Advisory (KB-lens)")
738
- std_output = gr.Textbox(label="Standard Advisory (sanitized)", lines=30)
739
- def on_std_click(*answers):
740
  amap = dict(zip(STD_QUESTIONS, answers))
741
- try:
742
- return generate_standard_report_with_lens(amap, KB_INDEX, top_k_per_answer=3)
743
- except Exception as e:
744
- 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."
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
- gate_q = gr.Textbox(label="Gate Assessment (commander's final note) — used for analysis only", lines=1)
753
- enh_btn = gr.Button("Generate Enhanced Advisory (KB-lens)")
754
- enh_output = gr.Textbox(label="Enhanced Advisory (sanitized)", lines=36)
755
- def on_enh_click(*args):
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
- a_vals = answers = list(args for args in args_for_dummy()) # placeholder if needed
790
- # but we'll use the outputs from enh_btn click defined above to supply state
791
- # here we implement simple caching via the click triggered on the enh_btn (see below)
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
- # Launch
830
- # -------------------------
831
- with gr.Tab("PARA SF (Two Reports)"):
832
- gr.Markdown("## PARA SF — Two Separate Administrative Advisories (Non-Actionable)")
833
-
834
- # Question inputs (50)
835
- para_questions_inputs = [gr.Textbox(label=q, lines=1) for q in PARA_QUESTIONS_50]
836
-
837
- # Paste box for SR / human-intel notes
838
- para_fieldcraft = gr.Textbox(label="Paste Fieldcraft / SR notes (your text)", lines=6)
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
- # Wire buttons
915
- para_coa_btn.click(para_coa_runner, inputs=para_questions_inputs + [para_fieldcraft, para_file_selector], outputs=para_coa_out)
916
- para_prec_btn.click(para_prec_runner, inputs=para_questions_inputs + [para_fieldcraft, para_file_selector], outputs=para_prec_out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()