Spaces:
Runtime error
Runtime error
Update APPC.py
Browse files
APPC.py
CHANGED
|
@@ -1,226 +1,599 @@
|
|
| 1 |
-
# app.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import gradio as gr
|
| 4 |
|
| 5 |
-
#
|
|
|
|
| 6 |
try:
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or ""
|
| 10 |
-
if OPENAI_API_KEY:
|
| 11 |
-
openai.api_key = OPENAI_API_KEY
|
| 12 |
-
else:
|
| 13 |
-
OPENAI_AVAILABLE = False
|
| 14 |
except Exception:
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
def ai_advisory(report_text):
|
| 18 |
-
"""
|
| 19 |
-
Produce a short narrative advisory using OpenAI if available.
|
| 20 |
-
Advisory is intentionally non-actionable and high-level.
|
| 21 |
-
"""
|
| 22 |
-
if not OPENAI_AVAILABLE:
|
| 23 |
-
return "\n\n---\n*AI Advisory unavailable (OpenAI key not set).*"
|
| 24 |
-
try:
|
| 25 |
-
resp = openai.ChatCompletion.create(
|
| 26 |
-
model="gpt-4o-mini",
|
| 27 |
-
messages=[
|
| 28 |
-
{"role": "system", "content": "You are an analyst that provides high-level, non-actionable organizational advice."},
|
| 29 |
-
{"role": "user", "content": "Provide a concise, non-actionable advisory based on the following sanitized report:\n\n" + report_text}
|
| 30 |
-
],
|
| 31 |
-
max_tokens=400
|
| 32 |
-
)
|
| 33 |
-
return "\n\n---\n### AI Narrative Advisory\n" + resp["choices"][0]["message"]["content"]
|
| 34 |
-
except Exception as e:
|
| 35 |
-
return f"\n\n---\n*AI Advisory failed: {str(e)}*"
|
| 36 |
|
| 37 |
-
|
| 38 |
-
if text is None:
|
| 39 |
-
return False
|
| 40 |
-
return str(text).strip().lower() in ("yes", "y", "true", "1", "affirmative")
|
| 41 |
-
|
| 42 |
-
# --- Short one-line questions (sanitized placeholders) ---
|
| 43 |
|
|
|
|
| 44 |
STD_QUESTIONS = [
|
| 45 |
-
"When
|
| 46 |
-
"
|
| 47 |
-
"
|
| 48 |
-
"
|
| 49 |
-
"
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
-
"
|
| 55 |
-
"
|
| 56 |
-
"
|
| 57 |
-
"
|
| 58 |
-
"
|
| 59 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
]
|
| 61 |
|
| 62 |
-
|
| 63 |
-
"
|
| 64 |
-
"
|
| 65 |
-
"
|
| 66 |
-
"
|
| 67 |
-
"
|
| 68 |
-
"
|
| 69 |
-
"
|
| 70 |
-
"
|
| 71 |
-
"Unit
|
| 72 |
-
"
|
| 73 |
-
"Embedded Int in routine ops?",
|
| 74 |
-
"Staff aligned with CO intent?",
|
| 75 |
-
"Using 5D system?",
|
| 76 |
-
"MI assets align with 5D?",
|
| 77 |
-
"Red-teaming done?",
|
| 78 |
-
"FP measures prioritized?",
|
| 79 |
-
"Attack enemy SA/support?",
|
| 80 |
-
"Deliberate or Quick ops ready?",
|
| 81 |
-
"Projected C2 capability?",
|
| 82 |
-
"Clear distinction SA/Surprise?"
|
| 83 |
]
|
| 84 |
|
| 85 |
-
|
| 86 |
-
"Why
|
| 87 |
-
"Why no HUMINT report?",
|
| 88 |
-
"Why no
|
| 89 |
-
"Why no
|
| 90 |
-
"Why no
|
| 91 |
-
"Why no
|
| 92 |
-
"Why
|
| 93 |
-
"Why
|
| 94 |
-
"Why
|
| 95 |
-
"Why
|
| 96 |
]
|
| 97 |
|
| 98 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
| 101 |
"""
|
| 102 |
-
|
| 103 |
-
|
| 104 |
"""
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
"""
|
| 121 |
-
|
| 122 |
-
|
| 123 |
"""
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
-
|
| 136 |
-
|
| 137 |
-
-
|
| 138 |
-
""
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
with gr.Blocks() as demo:
|
| 143 |
-
|
| 144 |
-
gr.
|
| 145 |
-
gr.Markdown("# 🟡 Kashmir AO Action Plan (Sanitized) — Analyst Tool & Battle Planner")
|
| 146 |
|
| 147 |
-
|
| 148 |
-
with gr.Tab("Standard"):
|
| 149 |
std_inputs = [gr.Textbox(label=q, lines=1) for q in STD_QUESTIONS]
|
| 150 |
-
std_button = gr.Button("Generate Standard Report")
|
| 151 |
-
std_output = gr.
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
with gr.Tab("Threat Readiness"):
|
| 171 |
-
gr.Markdown("
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
# flatten inputs when wiring the click event to avoid nested lists error
|
| 206 |
-
threat_button.click(
|
| 207 |
-
fn=evaluate_readiness,
|
| 208 |
-
inputs=std_inputs + enh_inputs + post_inputs + [ai_toggle],
|
| 209 |
-
outputs=threat_output
|
| 210 |
-
)
|
| 211 |
-
|
| 212 |
-
# --- Doctrinal Summary Tab ---
|
| 213 |
-
with gr.Tab("Doctrinal Summary (Sanitized)"):
|
| 214 |
-
gr.Markdown(
|
| 215 |
-
"## Doctrinal Summary (Sanitized)\n\n"
|
| 216 |
-
"- Treat intelligence as an operational function integrated into staff planning.\n"
|
| 217 |
-
"- Aim for persistent situational awareness via C3ISR at an organizational level.\n"
|
| 218 |
-
"- Use zoning (rear / forward / deep forward) conceptually for planning and resource allocation.\n"
|
| 219 |
-
"- Maintain Force Protection SOPs, redundancy, and regular vulnerability reviews.\n"
|
| 220 |
-
"- Emphasize training, documentation, and staff alignment with commander intent.\n\n"
|
| 221 |
-
"_This doctrinal content is intentionally non-actionable and high-level._"
|
| 222 |
-
)
|
| 223 |
-
|
| 224 |
-
# --- Launch ---
|
| 225 |
if __name__ == "__main__":
|
| 226 |
demo.launch()
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
# Safe KB-driven retrieval + report generator (NON-ACTIONABLE)
|
| 3 |
+
# - Loads PDFs/TXT/MD from /mnt/data
|
| 4 |
+
# - Builds chunk index (embedding or lexical fallback)
|
| 5 |
+
# - For each answer: retrieves top chunks relevant to that answer (the "lens")
|
| 6 |
+
# - Produces sanitized, administrative/doctrinal reports only (no tactical/operational content)
|
| 7 |
+
# - Threat readiness color-coded brief (lists deficiencies + admin remediations only)
|
| 8 |
+
|
| 9 |
import os
|
| 10 |
+
import math
|
| 11 |
+
import textwrap
|
| 12 |
+
import re
|
| 13 |
+
from collections import defaultdict
|
| 14 |
import gradio as gr
|
| 15 |
|
| 16 |
+
# Try modern OpenAI client (OpenAI()). If absent, client will be None and we fallback.
|
| 17 |
+
client = None
|
| 18 |
try:
|
| 19 |
+
from openai import OpenAI
|
| 20 |
+
client = OpenAI()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
except Exception:
|
| 22 |
+
client = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
BANNER_URL = "https://huggingface.co/spaces/Militaryint/ops/resolve/main/banner.png"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
# ---------- QUESTION SETS (exact phrasing provided) ----------
|
| 27 |
STD_QUESTIONS = [
|
| 28 |
+
"When where was enemy sighted.",
|
| 29 |
+
"Coming from which direction",
|
| 30 |
+
"what is the size of the enemy? How many men?",
|
| 31 |
+
"What equipment and weapons are they carrying",
|
| 32 |
+
"What vehicles are they using or are they on foot?",
|
| 33 |
+
"How far are they from any roads frequented by soldiers vehicles",
|
| 34 |
+
"How far are they from any military unit camp",
|
| 35 |
+
"How far are they from any deployed soldiers",
|
| 36 |
+
"Are they getting support of locals? If so who are these locals?",
|
| 37 |
+
"What is their disposition? How are they spread out in formation? Are they moving closely tight or are they spread out?",
|
| 38 |
+
"Do you as the security force commander have Reconnaissance and Surveillance soldiers near the area where these enemy sighted?",
|
| 39 |
+
"Did you get the information of sighting of enemy from local source or army personnel?",
|
| 40 |
+
"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",
|
| 41 |
+
"How far is your commanded army unit from the place where enemy is sighted?",
|
| 42 |
+
"is the terrain urban or semi urban or jungle or hilly or rural?"
|
| 43 |
+
]
|
| 44 |
+
|
| 45 |
+
ENH_SECTION_A = [
|
| 46 |
+
"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).",
|
| 47 |
+
"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?",
|
| 48 |
+
"Does the unit have a proper reconnaissance and surveillance plan?Is it integrated with Ops and Int SOP?",
|
| 49 |
+
"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?",
|
| 50 |
+
"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.",
|
| 51 |
+
"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.",
|
| 52 |
+
"Does the unit employ Randomness in guarding,troops movement and officers movement? Randomness is necessary to thwart enemy surveillance.",
|
| 53 |
+
"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?",
|
| 54 |
+
"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?",
|
| 55 |
+
"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?",
|
| 56 |
+
"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."
|
| 57 |
]
|
| 58 |
|
| 59 |
+
ENH_SECTION_B = [
|
| 60 |
+
"Am I thinking of the Threat or am I thinking about my COs Situational Awareness.",
|
| 61 |
+
"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?",
|
| 62 |
+
"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.",
|
| 63 |
+
"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?",
|
| 64 |
+
"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?",
|
| 65 |
+
"How do I account for Force Protection based on Q5?Am I giving FP top priority?",
|
| 66 |
+
"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?",
|
| 67 |
+
"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?",
|
| 68 |
+
"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?",
|
| 69 |
+
"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?"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
]
|
| 71 |
|
| 72 |
+
ENH_SECTION_C = [
|
| 73 |
+
"Why was not the enemy's movement detected beforehand?",
|
| 74 |
+
"Why was there no HUMINT report on locals information about possibility of an attack?",
|
| 75 |
+
"Why was there no reconnaissance and surveillance report?",
|
| 76 |
+
"Why was there no advance Force Protection report?",
|
| 77 |
+
"Why was there no Tactical Command and Control nodes covertly emplaced in points equidistant from vulnerable targets like schools?",
|
| 78 |
+
"Why was there no Terrorist Targeting Standards Report that will predict likely targets of terrorist attack?",
|
| 79 |
+
"Why did the Army fail to observe indicators?",
|
| 80 |
+
"Why did the Army fail to exploit human terrain for advance information?",
|
| 81 |
+
"Why was there no exploitation of Physical terrain?",
|
| 82 |
+
"Why was forward operating zones absent?"
|
| 83 |
]
|
| 84 |
|
| 85 |
+
# -------------------------
|
| 86 |
+
# Utilities: read PDFs / files
|
| 87 |
+
# -------------------------
|
| 88 |
+
def read_all_files(data_dir="/mnt/data"):
|
| 89 |
+
files_text = {}
|
| 90 |
+
if not os.path.isdir(data_dir):
|
| 91 |
+
return files_text
|
| 92 |
+
for fname in sorted(os.listdir(data_dir)):
|
| 93 |
+
path = os.path.join(data_dir, fname)
|
| 94 |
+
if not os.path.isfile(path):
|
| 95 |
+
continue
|
| 96 |
+
lower = fname.lower()
|
| 97 |
+
try:
|
| 98 |
+
if lower.endswith(".pdf"):
|
| 99 |
+
try:
|
| 100 |
+
import PyPDF2
|
| 101 |
+
with open(path, "rb") as f:
|
| 102 |
+
reader = PyPDF2.PdfReader(f)
|
| 103 |
+
pages = []
|
| 104 |
+
for p in reader.pages:
|
| 105 |
+
try:
|
| 106 |
+
t = p.extract_text()
|
| 107 |
+
if t:
|
| 108 |
+
pages.append(t)
|
| 109 |
+
except Exception:
|
| 110 |
+
continue
|
| 111 |
+
files_text[fname] = "\n\n".join(pages) if pages else "[PDF read but no extractable text]"
|
| 112 |
+
except Exception as e:
|
| 113 |
+
files_text[fname] = f"[Could not read PDF: {e}]"
|
| 114 |
+
elif lower.endswith(".txt") or lower.endswith(".md"):
|
| 115 |
+
try:
|
| 116 |
+
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
| 117 |
+
files_text[fname] = f.read()
|
| 118 |
+
except Exception as e:
|
| 119 |
+
files_text[fname] = f"[Could not read text file: {e}]"
|
| 120 |
+
else:
|
| 121 |
+
continue
|
| 122 |
+
except Exception:
|
| 123 |
+
files_text[fname] = "[Error reading file]"
|
| 124 |
+
return files_text
|
| 125 |
+
|
| 126 |
+
# -------------------------
|
| 127 |
+
# Chunking helper
|
| 128 |
+
# -------------------------
|
| 129 |
+
def chunk_text(text, max_chars=2000, overlap=200):
|
| 130 |
+
if not text:
|
| 131 |
+
return []
|
| 132 |
+
chunks = []
|
| 133 |
+
start = 0
|
| 134 |
+
length = len(text)
|
| 135 |
+
while start < length:
|
| 136 |
+
end = min(start + max_chars, length)
|
| 137 |
+
chunk = text[start:end]
|
| 138 |
+
chunks.append(chunk)
|
| 139 |
+
if end == length:
|
| 140 |
+
break
|
| 141 |
+
start = end - overlap
|
| 142 |
+
return chunks
|
| 143 |
|
| 144 |
+
# -------------------------
|
| 145 |
+
# Embedding / similarity (with fallback)
|
| 146 |
+
# -------------------------
|
| 147 |
+
def get_embedding(text):
|
| 148 |
"""
|
| 149 |
+
Returns an embedding vector for text using OpenAI embeddings if available.
|
| 150 |
+
If no client is available, returns None.
|
| 151 |
"""
|
| 152 |
+
if client is None:
|
| 153 |
+
return None
|
| 154 |
+
try:
|
| 155 |
+
resp = client.embeddings.create(model="text-embedding-3-small", input=text)
|
| 156 |
+
# handle SDK variations
|
| 157 |
+
try:
|
| 158 |
+
return resp["data"][0]["embedding"]
|
| 159 |
+
except Exception:
|
| 160 |
+
return resp.data[0].embedding
|
| 161 |
+
except Exception:
|
| 162 |
+
return None
|
| 163 |
+
|
| 164 |
+
def cosine_sim(a, b):
|
| 165 |
+
# both lists of floats
|
| 166 |
+
if a is None or b is None:
|
| 167 |
+
return 0.0
|
| 168 |
+
dot = sum(x*y for x,y in zip(a,b))
|
| 169 |
+
na = math.sqrt(sum(x*x for x in a))
|
| 170 |
+
nb = math.sqrt(sum(x*x for x in b))
|
| 171 |
+
if na == 0 or nb == 0:
|
| 172 |
+
return 0.0
|
| 173 |
+
return dot / (na*nb)
|
| 174 |
+
|
| 175 |
+
# -------------------------
|
| 176 |
+
# Build in-memory index of chunks
|
| 177 |
+
# -------------------------
|
| 178 |
+
class KBIndex:
|
| 179 |
+
def __init__(self):
|
| 180 |
+
self.chunks = [] # list of dicts: {file, chunk_id, text, emb}
|
| 181 |
+
def build_from_files(self, files_text):
|
| 182 |
+
self.chunks = []
|
| 183 |
+
for fname, text in files_text.items():
|
| 184 |
+
for i, chunk in enumerate(chunk_text(text, max_chars=2000, overlap=300)):
|
| 185 |
+
emb = get_embedding(chunk) # may be None
|
| 186 |
+
self.chunks.append({"file": fname, "id": f"{fname}::chunk{i+1}", "text": chunk, "emb": emb})
|
| 187 |
+
def retrieve(self, query, top_k=3):
|
| 188 |
+
# prefer embedding similarity when available, else lexical score
|
| 189 |
+
q_emb = get_embedding(query) if client else None
|
| 190 |
+
scored = []
|
| 191 |
+
if q_emb is not None:
|
| 192 |
+
for c in self.chunks:
|
| 193 |
+
score = cosine_sim(q_emb, c["emb"]) if c["emb"] is not None else 0.0
|
| 194 |
+
scored.append((score, c))
|
| 195 |
+
scored.sort(key=lambda x: x[0], reverse=True)
|
| 196 |
+
return [c for s,c in scored[:top_k] if s>0]
|
| 197 |
+
else:
|
| 198 |
+
# lexical fallback: simple token overlap score
|
| 199 |
+
q_tokens = set(re.findall(r"\w+", query.lower()))
|
| 200 |
+
for c in self.chunks:
|
| 201 |
+
tokens = set(re.findall(r"\w+", c["text"].lower()))
|
| 202 |
+
score = len(q_tokens & tokens)
|
| 203 |
+
scored.append((score, c))
|
| 204 |
+
scored.sort(key=lambda x: x[0], reverse=True)
|
| 205 |
+
return [c for s,c in scored[:top_k] if s>0]
|
| 206 |
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def generate_admin_alternative(original_text):
|
| 210 |
+
# Produce a generic admin-oriented alternative (deterministic)
|
| 211 |
+
alt = (
|
| 212 |
+
"Administrative alternative (examples):\n"
|
| 213 |
+
"- Update SOP and reporting templates to include required fields.\n"
|
| 214 |
+
"- Conduct doctrinal review and table-top exercises; document lessons learned.\n"
|
| 215 |
+
"- Implement source vetting and CI audit trails; schedule training and compliance checks.\n"
|
| 216 |
+
"- Commission a vulnerability assessment and a red-team *administrative* review (no operational details).\n"
|
| 217 |
+
)
|
| 218 |
+
return alt
|
| 219 |
+
|
| 220 |
+
# -------------------------
|
| 221 |
+
# Chat API wrapper for completions
|
| 222 |
+
# -------------------------
|
| 223 |
+
def call_chat_api(messages, max_tokens=900, model="gpt-4o-mini"):
|
| 224 |
+
if client is None:
|
| 225 |
+
raise RuntimeError("OpenAI client not available in environment.")
|
| 226 |
+
resp = client.chat.completions.create(model=model, messages=messages, max_tokens=max_tokens)
|
| 227 |
+
try:
|
| 228 |
+
return resp["choices"][0]["message"]["content"]
|
| 229 |
+
except Exception:
|
| 230 |
+
return resp.choices[0].message.content
|
| 231 |
|
| 232 |
+
# -------------------------
|
| 233 |
+
# Prompt templates (strictly NON-ACTIONABLE)
|
| 234 |
+
# -------------------------
|
| 235 |
+
SYSTEM_SAFE = (
|
| 236 |
+
"You are an institutional analyst. You MUST follow these rules: "
|
| 237 |
+
"1) Provide ONLY non-actionable, administrative, doctrinal or training-oriented analysis. "
|
| 238 |
+
"2) If asked for operational content, refuse and instead produce administrative alternatives (SOPs, audits, training, doctrine). "
|
| 239 |
+
"3) Cite the KB chunk ids / filenames you used in reasoning. "
|
| 240 |
+
"Output must be explicit that it's NON-ACTIONABLE."
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
def build_standard_prompt(answer_map, retrieved_chunks_for_answers):
|
| 244 |
"""
|
| 245 |
+
Build messages: system + user with attached relevant KB excerpts per answer.
|
| 246 |
+
retrieved_chunks_for_answers: list of lists of chunk dicts in same order as answers
|
| 247 |
"""
|
| 248 |
+
user_parts = []
|
| 249 |
+
user_parts.append("Produce a NON-ACTIONABLE Standard Advisory Report for a commander.")
|
| 250 |
+
user_parts.append("Include: Executive Summary (2-4 lines); Key Administrative Issues (bulleted); Prioritized Administrative Recommendations (numbered); Sources cited (file::chunk ids).")
|
| 251 |
+
user_parts.append("DO NOT include any tactical or operational instructions.")
|
| 252 |
+
user_parts.append("\nAnswers and relevant KB excerpts (lens):\n")
|
| 253 |
+
for i, q in enumerate(STD_QUESTIONS):
|
| 254 |
+
ans = answer_map.get(q, "[no answer]")
|
| 255 |
+
user_parts.append(f"Q{i+1}. {q}\nA: {ans}\nRelevant KB excerpts:")
|
| 256 |
+
chunks = retrieved_chunks_for_answers[i]
|
| 257 |
+
if chunks:
|
| 258 |
+
for c in chunks:
|
| 259 |
+
user_parts.append(f"--- {c['id']} (from {c['file']}) ---\n{c['text'][:1200]}\n")
|
| 260 |
+
else:
|
| 261 |
+
user_parts.append("- [No relevant KB excerpt found]\n")
|
| 262 |
+
user_text = "\n".join(user_parts)
|
| 263 |
+
return [{"role":"system","content":SYSTEM_SAFE}, {"role":"user","content":user_text}]
|
| 264 |
+
|
| 265 |
+
def build_enhanced_prompt(a_map, b_map, c_map, gate_text, retrieved_A, retrieved_B, retrieved_C):
|
| 266 |
+
user_parts = []
|
| 267 |
+
user_parts.append("Produce a NON-ACTIONABLE Enhanced Staff Advisory for a commander.")
|
| 268 |
+
user_parts.append("Include: Executive Summary; Key doctrinal/organizational gaps; Prioritized Administrative Remediations; Measurement/audit suggestions; Sources cited.")
|
| 269 |
+
user_parts.append("\nSection A answers and relevant KB excerpts:\n")
|
| 270 |
+
for i, q in enumerate(ENH_SECTION_A):
|
| 271 |
+
ans = a_map.get(q, "[no answer]")
|
| 272 |
+
user_parts.append(f"A{i+1}. {q}\nA: {ans}\nRelevant KB excerpts:")
|
| 273 |
+
chunks = retrieved_A[i]
|
| 274 |
+
if chunks:
|
| 275 |
+
for c in chunks:
|
| 276 |
+
user_parts.append(f"--- {c['id']} (from {c['file']}) ---\n{c['text'][:1200]}\n")
|
| 277 |
+
else:
|
| 278 |
+
user_parts.append("- [No relevant KB excerpt found]\n")
|
| 279 |
+
user_parts.append("\nSection B answers and relevant KB excerpts:\n")
|
| 280 |
+
for i, q in enumerate(ENH_SECTION_B):
|
| 281 |
+
ans = b_map.get(q, "[no answer]")
|
| 282 |
+
user_parts.append(f"B{i+1}. {q}\nA: {ans}\nRelevant KB excerpts:")
|
| 283 |
+
chunks = retrieved_B[i]
|
| 284 |
+
if chunks:
|
| 285 |
+
for c in chunks:
|
| 286 |
+
user_parts.append(f"--- {c['id']} (from {c['file']}) ---\n{c['text'][:1200]}\n")
|
| 287 |
+
else:
|
| 288 |
+
user_parts.append("- [No relevant KB excerpt found]\n")
|
| 289 |
+
user_parts.append("\nSection C answers and relevant KB excerpts:\n")
|
| 290 |
+
for i, q in enumerate(ENH_SECTION_C):
|
| 291 |
+
ans = c_map.get(q, "[no answer]")
|
| 292 |
+
user_parts.append(f"C{i+1}. {q}\nA: {ans}\nRelevant KB excerpts:")
|
| 293 |
+
chunks = retrieved_C[i]
|
| 294 |
+
if chunks:
|
| 295 |
+
for c in chunks:
|
| 296 |
+
user_parts.append(f"--- {c['id']} (from {c['file']}) ---\n{c['text'][:1200]}\n")
|
| 297 |
+
else:
|
| 298 |
+
user_parts.append("- [No relevant KB excerpt found]\n")
|
| 299 |
+
user_parts.append(f"\nGate assessment: {gate_text}\n")
|
| 300 |
+
user_text = "\n".join(user_parts)
|
| 301 |
+
return [{"role":"system","content":SYSTEM_SAFE}, {"role":"user","content":user_text}]
|
| 302 |
+
|
| 303 |
+
# -------------------------
|
| 304 |
+
# Deterministic remediation helpers (as earlier) - administrative only
|
| 305 |
+
# -------------------------
|
| 306 |
+
def remediation_for_std_question(q_text):
|
| 307 |
+
q = q_text.lower()
|
| 308 |
+
rem = []
|
| 309 |
+
if "sighted" in q or "where" in q:
|
| 310 |
+
rem.append("Ensure SITREP templates include precise time and geo fields and audit entries for accuracy.")
|
| 311 |
+
if "direction" in q:
|
| 312 |
+
rem.append("Add directional/axis fields to reporting templates and train observers.")
|
| 313 |
+
if "size of the enemy" in q or "how many" in q:
|
| 314 |
+
rem.append("Include size estimation procedures in SOP and require cross-checks.")
|
| 315 |
+
if "equipment" in q or "weapons" in q:
|
| 316 |
+
rem.append("Standardize equipment reporting taxonomy in the unit KB and train observers.")
|
| 317 |
+
if "vehicles" in q:
|
| 318 |
+
rem.append("Include vehicle identification fields in R&S templates and verify via secondary observation.")
|
| 319 |
+
if "roads" in q:
|
| 320 |
+
rem.append("Map and flag critical infrastructure in vulnerability audits.")
|
| 321 |
+
if "locals" in q:
|
| 322 |
+
rem.append("Apply CI vetting to local-source reports and require secondary confirmation.")
|
| 323 |
+
if "disposition" in q:
|
| 324 |
+
rem.append("Document disposition notation standards and rehearse via table-top exercises.")
|
| 325 |
+
if "reconnaissance" in q or "r&s" in q:
|
| 326 |
+
rem.append("Maintain an on-call R&S roster and ensure cover/concealment SOPs are current.")
|
| 327 |
+
if "confirmed" in q or "second source" in q:
|
| 328 |
+
rem.append("Mandate secondary confirmation and log confirmations in a secure registry.")
|
| 329 |
+
if "terrain" in q:
|
| 330 |
+
rem.append("Ensure terrain classification fields are mandatory and staff have access to terrain overlays.")
|
| 331 |
+
if not rem:
|
| 332 |
+
rem.append("Review reporting templates and conduct staff training to improve data capture.")
|
| 333 |
+
seen = []
|
| 334 |
+
for r in rem:
|
| 335 |
+
if r not in seen:
|
| 336 |
+
seen.append(r)
|
| 337 |
+
return seen
|
| 338 |
+
|
| 339 |
+
def remediation_for_enh_question(q_text):
|
| 340 |
+
q = q_text.lower()
|
| 341 |
+
rem = []
|
| 342 |
+
if "ops and int" in q or "integration" in q:
|
| 343 |
+
rem.append("Establish Ops-Int coordination SOP and a liaison role with documented responsibilities.")
|
| 344 |
+
if "int sop" in q or "course of action" in q:
|
| 345 |
+
rem.append("Create Intelligence SOP and COA templates that include int feeds and validation steps.")
|
| 346 |
+
if "reconnaissance and surveillance plan" in q:
|
| 347 |
+
rem.append("Publish R&S plan templates and run table-top exercises to validate them.")
|
| 348 |
+
if "force protection" in q or "route planner" in q:
|
| 349 |
+
rem.append("Institute FP review cycles and formal route planning checklists for missions and base movement.")
|
| 350 |
+
if "intelligence projection" in q or "forward intelligence" in q:
|
| 351 |
+
rem.append("Document intelligence projection concept, minimum manning and activation triggers in SOP.")
|
| 352 |
+
if "vulnerability analysis" in q:
|
| 353 |
+
rem.append("Adopt a vulnerability register and schedule regular audits and red-team reviews (administrative).")
|
| 354 |
+
if "randomness" in q:
|
| 355 |
+
rem.append("Implement randomized movement policies and document patterns for audit (ensure safety/legal compliance).")
|
| 356 |
+
if "source vetting" in q or "counterintelligence" in q:
|
| 357 |
+
rem.append("Build a source registry and CI vetting workflow with secure access and audit logs.")
|
| 358 |
+
if "doctrine" in q or "manual" in q:
|
| 359 |
+
rem.append("Compile and publish an internal intelligence doctrine/manual in the unit library.")
|
| 360 |
+
if "embedded int" in q:
|
| 361 |
+
rem.append("Define embedded-int roles, responsibilities and rotation cycles; maintain oversight.")
|
| 362 |
+
if not rem:
|
| 363 |
+
rem.append("Conduct a formal capability gap analysis and document required SOPs and resourcing.")
|
| 364 |
+
seen = []
|
| 365 |
+
for r in rem:
|
| 366 |
+
if r not in seen:
|
| 367 |
+
seen.append(r)
|
| 368 |
+
return seen
|
| 369 |
+
|
| 370 |
+
# -------------------------
|
| 371 |
+
# Main report functions
|
| 372 |
+
# -------------------------
|
| 373 |
+
def generate_standard_report_with_lens(answers_map, kb_index, top_k_per_answer=2):
|
| 374 |
+
# for each STD question, retrieve top_k_per_answer chunks
|
| 375 |
+
retrieved = []
|
| 376 |
+
for q in STD_QUESTIONS:
|
| 377 |
+
ans = answers_map.get(q, "")
|
| 378 |
+
query_text = (str(ans).strip() or q)
|
| 379 |
+
chunks = kb_index.retrieve(query_text, top_k=top_k_per_answer)
|
| 380 |
+
retrieved.append(chunks)
|
| 381 |
+
# build prompt
|
| 382 |
+
messages = build_standard_prompt(answers_map, retrieved)
|
| 383 |
+
try:
|
| 384 |
+
out = call_chat_api(messages, max_tokens=1200)
|
| 385 |
+
out = safety_check_and_redact(out)
|
| 386 |
+
return out
|
| 387 |
+
except Exception as e:
|
| 388 |
+
# fallback deterministic: detailed administrative report referencing retrieved chunks
|
| 389 |
+
lines = ["[FALLBACK NON-ACTIONABLE STANDARD REPORT]\n"]
|
| 390 |
+
for i, q in enumerate(STD_QUESTIONS):
|
| 391 |
+
a = answers_map.get(q, "")
|
| 392 |
+
lines.append(f"Q{i+1}. {q}\nA: {a}\nRelevant KB excerpts (ids):")
|
| 393 |
+
chunks = retrieved[i]
|
| 394 |
+
if chunks:
|
| 395 |
+
for c in chunks:
|
| 396 |
+
lines.append(f"- {c['id']} (from {c['file']})")
|
| 397 |
+
else:
|
| 398 |
+
lines.append("- [None found]")
|
| 399 |
+
# remediation per negative answer
|
| 400 |
+
if not a or str(a).strip().lower() in ("", "no", "n", "none", "negative"):
|
| 401 |
+
for r in remediation_for_std_question(q):
|
| 402 |
+
lines.append(f" *Admin remediation:* {r}")
|
| 403 |
+
lines.append("")
|
| 404 |
+
lines.append(f"\nNote: AI unavailable: {e}")
|
| 405 |
+
return "\n".join(lines)
|
| 406 |
+
|
| 407 |
+
def generate_enhanced_report_with_lens(a_map, b_map, c_map, gate_text, kb_index, top_k_per_answer=2):
|
| 408 |
+
retrieved_A = [kb_index.retrieve(str(a_map.get(q,"") or q), top_k=top_k_per_answer) for q in ENH_SECTION_A]
|
| 409 |
+
retrieved_B = [kb_index.retrieve(str(b_map.get(q,"") or q), top_k=top_k_per_answer) for q in ENH_SECTION_B]
|
| 410 |
+
retrieved_C = [kb_index.retrieve(str(c_map.get(q,"") or q), top_k=top_k_per_answer) for q in ENH_SECTION_C]
|
| 411 |
+
messages = build_enhanced_prompt(a_map, b_map, c_map, gate_text, retrieved_A, retrieved_B, retrieved_C)
|
| 412 |
+
try:
|
| 413 |
+
out = call_chat_api(messages, max_tokens=1400)
|
| 414 |
+
out = safety_check_and_redact(out)
|
| 415 |
+
return out
|
| 416 |
+
except Exception as e:
|
| 417 |
+
# fallback deterministic enhanced report
|
| 418 |
+
lines = ["[FALLBACK NON-ACTIONABLE ENHANCED REPORT]\n"]
|
| 419 |
+
lines.append("Section A gaps and admin remediations:")
|
| 420 |
+
for q in ENH_SECTION_A:
|
| 421 |
+
a = a_map.get(q,"")
|
| 422 |
+
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 423 |
+
lines.append(f"- {q}")
|
| 424 |
+
for r in remediation_for_enh_question(q):
|
| 425 |
+
lines.append(f" *Admin remediation:* {r}")
|
| 426 |
+
lines.append("\nSection B gaps and admin remediations:")
|
| 427 |
+
for q in ENH_SECTION_B:
|
| 428 |
+
b = b_map.get(q,"")
|
| 429 |
+
if not b or str(b).strip().lower() in ("","no","n","none","negative"):
|
| 430 |
+
lines.append(f"- {q}")
|
| 431 |
+
for r in remediation_for_enh_question(q):
|
| 432 |
+
lines.append(f" *Admin remediation:* {r}")
|
| 433 |
+
lines.append("\nSection C gaps and admin remediations:")
|
| 434 |
+
for q in ENH_SECTION_C:
|
| 435 |
+
c = c_map.get(q,"")
|
| 436 |
+
if not c or str(c).strip().lower() in ("","no","n","none","negative"):
|
| 437 |
+
lines.append(f"- {q}")
|
| 438 |
+
for r in remediation_for_enh_question(q):
|
| 439 |
+
lines.append(f" *Admin remediation:* {r}")
|
| 440 |
+
lines.append(f"\nNote: AI unavailable: {e}")
|
| 441 |
+
return "\n".join(lines)
|
| 442 |
+
|
| 443 |
+
# -------------------------
|
| 444 |
+
# Threat readiness (color-coded) with deficiency listing
|
| 445 |
+
# -------------------------
|
| 446 |
+
def evaluate_threat_brief(*all_answers):
|
| 447 |
+
total = len(STD_QUESTIONS) + len(ENH_SECTION_A) + len(ENH_SECTION_B) + len(ENH_SECTION_C)
|
| 448 |
+
vals = list(all_answers)[:total] + [""] * max(0, total - len(all_answers))
|
| 449 |
+
yes_count = sum(1 for v in vals if v and str(v).strip().lower() in ("yes","y","true","1","affirmative","confirmed"))
|
| 450 |
+
readiness = int((yes_count / total) * 100) if total else 0
|
| 451 |
+
if readiness >= 85:
|
| 452 |
+
color = "🟢 GREEN"
|
| 453 |
+
meaning = "Peacetime / strong readiness"
|
| 454 |
+
elif readiness >= 70:
|
| 455 |
+
color = "🔵 BLUE"
|
| 456 |
+
meaning = "Anticipated short-term threat readiness"
|
| 457 |
+
elif readiness >= 50:
|
| 458 |
+
color = "🟠 ORANGE"
|
| 459 |
+
meaning = "Imminent threat readiness (admin gaps present)"
|
| 460 |
+
else:
|
| 461 |
+
color = "🔴 RED"
|
| 462 |
+
meaning = "Low readiness - significant administrative gaps"
|
| 463 |
+
|
| 464 |
+
# collect deficiencies and remediations
|
| 465 |
+
deficiencies = []
|
| 466 |
+
rems = []
|
| 467 |
+
idx = 0
|
| 468 |
+
for q in STD_QUESTIONS:
|
| 469 |
+
a = vals[idx]; idx+=1
|
| 470 |
+
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 471 |
+
deficiencies.append(f"STD: {q}")
|
| 472 |
+
rems.extend(remediation_for_std_question(q))
|
| 473 |
+
for q in ENH_SECTION_A:
|
| 474 |
+
a = vals[idx]; idx+=1
|
| 475 |
+
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 476 |
+
deficiencies.append(f"ENH A: {q}")
|
| 477 |
+
rems.extend(remediation_for_enh_question(q))
|
| 478 |
+
for q in ENH_SECTION_B:
|
| 479 |
+
a = vals[idx]; idx+=1
|
| 480 |
+
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 481 |
+
deficiencies.append(f"ENH B: {q}")
|
| 482 |
+
rems.extend(remediation_for_enh_question(q))
|
| 483 |
+
for q in ENH_SECTION_C:
|
| 484 |
+
a = vals[idx]; idx+=1
|
| 485 |
+
if not a or str(a).strip().lower() in ("","no","n","none","negative"):
|
| 486 |
+
deficiencies.append(f"ENH C: {q}")
|
| 487 |
+
rems.extend(remediation_for_enh_question(q))
|
| 488 |
+
|
| 489 |
+
# build concise commander brief (non-actionable)
|
| 490 |
+
lines = []
|
| 491 |
+
lines.append(f"# Threat Readiness: {readiness}%")
|
| 492 |
+
lines.append(f"**Status:** {color} — {meaning}\n")
|
| 493 |
+
lines.append("## Commander’s Guide to WARN Levels:")
|
| 494 |
+
lines.append("- 🔴 RED (<50%): Significant administrative deficiencies. Prioritize SOP, CI, and audits.")
|
| 495 |
+
lines.append("- 🟠 ORANGE (50–69%): Moderate deficiencies; strengthen doctrine & R&S validation.")
|
| 496 |
+
lines.append("- 🔵 BLUE (70–84%): Minor gaps; schedule audits and training.")
|
| 497 |
+
lines.append("- 🟢 GREEN (85–100%): Good status; maintain vigilance and periodic reviews.\n")
|
| 498 |
+
|
| 499 |
+
if deficiencies:
|
| 500 |
+
lines.append("## Identified Administrative Deficiencies (summary)")
|
| 501 |
+
for d in deficiencies[:40]:
|
| 502 |
+
lines.append(f"- {d}")
|
| 503 |
+
if len(deficiencies)>40:
|
| 504 |
+
lines.append(f"... and {len(deficiencies)-40} more.")
|
| 505 |
+
else:
|
| 506 |
+
lines.append("## Identified Administrative Deficiencies: None found.")
|
| 507 |
+
|
| 508 |
+
lines.append("\n## Example Administrative Remediations (prioritized)")
|
| 509 |
+
unique_rems = []
|
| 510 |
+
for r in rems:
|
| 511 |
+
if r not in unique_rems:
|
| 512 |
+
unique_rems.append(r)
|
| 513 |
+
if unique_rems:
|
| 514 |
+
for i, r in enumerate(unique_rems[:40],1):
|
| 515 |
+
lines.append(f"{i}. {r}")
|
| 516 |
+
else:
|
| 517 |
+
lines.append("- No admin remediations needed based on current answers.")
|
| 518 |
+
lines.append("\n**Note:** This brief is NON-ACTIONABLE. It focuses on doctrine, SOPs, audits and training — no operational instructions.")
|
| 519 |
+
return "\n".join(lines)
|
| 520 |
|
| 521 |
+
# -------------------------
|
| 522 |
+
# Build index at startup (best-effort)
|
| 523 |
+
# -------------------------
|
| 524 |
+
FILES_TEXT = read_all_files("/mnt/data")
|
| 525 |
+
KB_INDEX = KBIndex()
|
| 526 |
+
KB_INDEX.build_from_files(FILES_TEXT)
|
| 527 |
+
|
| 528 |
+
# -------------------------
|
| 529 |
+
# Gradio UI
|
| 530 |
+
# -------------------------
|
| 531 |
with gr.Blocks() as demo:
|
| 532 |
+
gr.HTML(f'<img src="{BANNER_URL}" width="100%">')
|
| 533 |
+
gr.Markdown("# Kashmir AO Action Plan — KB-driven, Sanitized Adviser")
|
|
|
|
| 534 |
|
| 535 |
+
with gr.Tab("Standard Report"):
|
|
|
|
| 536 |
std_inputs = [gr.Textbox(label=q, lines=1) for q in STD_QUESTIONS]
|
| 537 |
+
std_button = gr.Button("Generate Standard Report (sanitized + KB-lens)")
|
| 538 |
+
std_output = gr.Textbox(label="Standard Report (sanitized)", lines=24)
|
| 539 |
+
def std_runner(*answers):
|
| 540 |
+
amap = dict(zip(STD_QUESTIONS, answers))
|
| 541 |
+
return generate_standard_report_with_lens(amap, KB_INDEX, top_k_per_answer=3)
|
| 542 |
+
std_button.click(std_runner, inputs=std_inputs, outputs=std_output)
|
| 543 |
+
|
| 544 |
+
with gr.Tab("Enhanced Report"):
|
| 545 |
+
a_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_A]
|
| 546 |
+
b_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_B]
|
| 547 |
+
c_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_C]
|
| 548 |
+
gate_input = gr.Textbox(label="Gate Assessment (final note)", lines=1)
|
| 549 |
+
enh_button = gr.Button("Generate Enhanced Report (sanitized + KB-lens)")
|
| 550 |
+
enh_output = gr.Textbox(label="Enhanced Report (sanitized)", lines=28)
|
| 551 |
+
def enh_runner(*answers):
|
| 552 |
+
la = len(ENH_SECTION_A); lb = len(ENH_SECTION_B); lc = len(ENH_SECTION_C)
|
| 553 |
+
al = list(answers)
|
| 554 |
+
while len(al) < (la+lb+lc+1):
|
| 555 |
+
al.append("")
|
| 556 |
+
a_vals = al[:la]; b_vals = al[la:la+lb]; c_vals = al[la+lb:la+lb+lc]; gate_val = al[-1]
|
| 557 |
+
a_map = dict(zip(ENH_SECTION_A, a_vals))
|
| 558 |
+
b_map = dict(zip(ENH_SECTION_B, b_vals))
|
| 559 |
+
c_map = dict(zip(ENH_SECTION_C, c_vals))
|
| 560 |
+
return generate_enhanced_report_with_lens(a_map, b_map, c_map, gate_val, KB_INDEX, top_k_per_answer=3)
|
| 561 |
+
enh_button.click(enh_runner, inputs=a_inputs + b_inputs + c_inputs + [gate_input], outputs=enh_output)
|
| 562 |
+
|
| 563 |
with gr.Tab("Threat Readiness"):
|
| 564 |
+
gr.Markdown("## Threat Readiness — color-coded commander brief (administrative only)")
|
| 565 |
+
|
| 566 |
+
# Hidden states to capture answers from other tabs
|
| 567 |
+
std_state = gr.State()
|
| 568 |
+
a_state = gr.State()
|
| 569 |
+
b_state = gr.State()
|
| 570 |
+
c_state = gr.State()
|
| 571 |
+
|
| 572 |
+
threat_button = gr.Button("Evaluate Threat Readiness")
|
| 573 |
+
threat_output = gr.Textbox(label="Threat Readiness & Admin Diagnostics", lines=28)
|
| 574 |
+
|
| 575 |
+
# Capture answers whenever reports are generated
|
| 576 |
+
def cache_std(*answers):
|
| 577 |
+
return list(answers)
|
| 578 |
+
std_button.click(cache_std, inputs=std_inputs, outputs=[std_state])
|
| 579 |
+
|
| 580 |
+
def cache_enh(*answers):
|
| 581 |
+
la, lb, lc = len(ENH_SECTION_A), len(ENH_SECTION_B), len(ENH_SECTION_C)
|
| 582 |
+
a_vals = answers[:la]
|
| 583 |
+
b_vals = answers[la:la+lb]
|
| 584 |
+
c_vals = answers[la+lb:la+lb+lc]
|
| 585 |
+
return a_vals, b_vals, c_vals
|
| 586 |
+
enh_button.click(cache_enh, inputs=a_inputs+b_inputs+c_inputs+[gate_input],
|
| 587 |
+
outputs=[a_state, b_state, c_state])
|
| 588 |
+
|
| 589 |
+
def threat_runner(std_ans, a_ans, b_ans, c_ans):
|
| 590 |
+
# flatten all answers
|
| 591 |
+
all_vals = (std_ans or []) + (a_ans or []) + (b_ans or []) + (c_ans or [])
|
| 592 |
+
return evaluate_threat_brief(*all_vals)
|
| 593 |
+
|
| 594 |
+
threat_button.click(threat_runner, inputs=[std_state, a_state, b_state, c_state], outputs=threat_output)
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 598 |
if __name__ == "__main__":
|
| 599 |
demo.launch()
|