mdmp / APPKESH
Militaryint's picture
Update APPKESH
26eed51 verified
Raw
History Blame Contribute Delete
47.8 kB
# app.py
# =============================================================================
# THE NEW MILITARY DECISION MAKING PROCESS (5D-MDMP) - FULL APP
# =============================================================================
# Author: Assembled for Keshav Mazumdar
# Purpose: Single-file Hugging Face Space (Gradio) implementing 5D MDMP,
# Integrated Warfare MDMP addon (SPTR, CARVER, BOS, Attack on Intent),
# Knowledge base links + inline Markdown full document,
# SITREP loader, Indian Army-style SA, COA(BULL) with dynamic triggers.
# =============================================================================
import os
import json
import datetime
import logging
import re
from collections import defaultdict
from difflib import SequenceMatcher
from pathlib import Path
import gradio as gr
import matplotlib.pyplot as plt
import numpy as np
# -------------------------
# Logging
# -------------------------
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("5D-MDMP")
# -------------------------
# Banner / Title / Intro
# -------------------------
BANNER_URL = "https://huggingface.co/spaces/Militaryint/mdmp/resolve/main/banner.png"
TITLE = "THE NEW MILITARY DECISION MAKING PROCESS"
INTRO = (
"A new military decision making process by Keshav Mazumdar \n"
"(To avert enemy surprise who may use deception in indicators to effect wrong situational awareness of friendly forces)\n\n"
"This MDMP uses the 5D System (Detect, Deny, Deter, Deliver, Destroy).\n\n"
"- At the beginning of MDMP: Observed enemy data (size, activity, location, unit type, equipment) is examined through the 5D lens to understand enemy intent.\n"
"- At COA planning: Reverse 5D is applied — for each enemy COA, friendly forces plan corresponding 5D actions."
)
# -------------------------
# Full Exhaustive Questionnaire (uncut sample)
# -------------------------
QUESTIONNAIRE = """
A) INITIAL SITUATIONAL AWARENESS — SEE THE ENEMY (5D LENS)
(note: for each D, capture indicators, sources, timestamps, geolocation, direction of movement, and supporting evidence)
1) DETECT — Is the enemy attempting to detect our forces, intentions, vulnerabilities, or movements?
- Evidence checklist:
- Are there observed reconnaissance assets? (UAVs, scouts, observation posts, long-range optics)
- Is there SIGINT indicative of forward listening/ELINT? (new or atypical radio chatter, beaconing patterns)
- Are local informants reporting suspicious surveillance or 'spotters'?
- Are there repeated sensor contacts at the same time of day (pattern)? Provide timestamps.
- Is there metadata anomaly (e.g., sudden new cell tower associations, unusual comm routing)?
- Are supply/logistics flows changing that would support detection (e.g., increased spare parts for optics)?
- Specific questions to answer:
- What sensors/platforms is the enemy using to observe (type, approximate range)?
- Where (grid/lat-lon) are those assets located relative to our positions?
- When did the detection activity start? Frequency? (daily, nightly)
- Source reliability & provenance for each observation (HUMINT id, SIGINT tag, imagery timestamp).
- Deception checks: Could these be decoys intended to lure our observation or choke ISR?
2) DENY — Is the enemy taking measures to deny us information or access to their activities?
- Evidence checklist:
- Electronic countermeasures (jamming, frequency hopping, signal masking).
- Camouflage/concealment of positions (smoke, netting, movement at night, use of tunnels).
- False logistics & decoys (dummy vehicles, false camps).
- OPSEC behavior: deliberate silence, message deletion, use of couriers.
- Disruption of our ISR (UAV interference, GPS spoofing).
- Specific questions:
- What methods are observed that would reduce our intelligence collection? (list exact times/locations)
- Which of our sensors are affected — imagery, SIGINT, HUMINT reliability?
- Are there indications the enemy knows our ISR collection windows (suggesting prior detection)?
- What gaps in data exist that may indicate successful denial (missing traffic, sudden disappearance)?
- Can we attribute denial to a particular enemy unit or capability?
3) DETER — Is the enemy attempting to deter our actions or create a deterrent climate?
- Evidence checklist:
- Demonstrations of force: visible troop movements, convoys, checkpoints, fortified positions.
- PSYOPS/propaganda, threats or warnings broadcast to locals or forces.
- Increased patrols, ambush posture, booby-traps visible on routes.
- Repetitive shows-of-force timed to our movements.
- Specific questions:
- Is there evidence of posture intended to intimidate (fortifications, large-calibre weapons on display)?
- Are there visible escort forces or overlapping fields of fire that deter movement?
- Are local civilians being told to avoid cooperation with security forces?
- How might the enemy's deterrence change our options or freedom of movement?
4) DELIVER — Is the enemy trying to deliver/secure local population support or remove locals from our influence?
- Evidence checklist:
- Civic actions by enemy or allied local groups (food distribution, payments, protection pledges).
- Coercion: threats, targeted killings of pro-government actors, intimidation of local leaders.
- Building of influence networks: recruiters, propaganda, local committees under enemy control.
- Offers of ‘protection’ in areas our forces patrol (suggests attempt to remove locals from our influence).
- Specific questions:
- Who among the local population is being targeted? (leaders, merchants, clinics)
- Are there new or increased local grievances being exploited by enemy messaging?
- Do we have HUMINT on recruitment or local collaboration? Provide names/locations if available.
- Could the enemy's influence operations impact force protection (informants, lookouts, IED assistance)?
5) DESTROY — Is there evidence of preparations to physically destroy our forces, bases, or infrastructure?
- Evidence checklist:
- Movement of indirect fire systems, heavy weapons, IED materials, ambush signatures.
- Targeting data: observers, range-finding activity, rehearsals, dry-runs on routes.
- Logistics indicating offensive intent (fuel/munitions build-up, movement of assault teams at night).
- Known enemy capabilities to strike (mortars, artillery, rockets, anti-armour).
- Specific questions:
- Are there confirmed munitions or weapon caches accessible to the observed unit?
- What are the likely targets (columns, bases, infrastructure)?
- Has there been rehearsal or practice attacks observed? Supply dumps? Night movement?
- Timelines: expected attack window, likely axes of approach, my force vulnerabilities exposed.
6) CROSS-D DIAGNOSTICS & DECEPTION HUNTS
- For every observation: check for contradictions between D indicators (e.g., high Detected + high Deny may indicate complex deception).
- Deception Checklist:
- Source redundancy? Are at least two independent sources confirming the same fact?
- Temporal plausibility: do timestamps match known movement timelines?
- Logistics match: does fuel/food/resupply support claimed enemy posture?
- Communication metadata anomalies: improbable routing, timestamp shifts, reused identifiers.
- Red-team hypotheses: what would the enemy want us to believe vs real intent?
- Specific questions:
- Which indicators, if false, would most change your hypothesis? (list critical unknowns)
- Which ISR tasks would best discriminate between alternate hypotheses?
7) OBSERVATION METADATA (always capture)
- Observation ID, timestamp (UTC), observer/source type, source reliability (0-1), lat-lon / grid, raw text, attachments (image/sigint clip).
- Note any potential biases in the source (local informant under threat, single-sensor detection).
---------------------------------------------------------------------
B) COA PLANNING — REVERSE 5D (For each enemy hypothesis, plan friendly action)
(For each hypothesis Hn generated in the SA step answer the following per D; fill in triggers, resources, sequencing, and BDA metrics)
... (The full questionnaire can be viewed in the Integrated MDMP document)
"""
# -------------------------
# Knowledge Base (links)
# -------------------------
KNOWLEDGE_BASE = {
"Flowchart (PDF)": "knowledge_base/flowchart.pdf",
"Checklist (PDF)": "knowledge_base/checklist.pdf",
"Sample Dataset (JSON)": "knowledge_base/sample_sitreps.json",
"Target Folder Template (JSON)": "knowledge_base/target_folder_template.json",
"Integrated MDMP (MD)": "knowledge_base/integrated_mdmp_detailed.md",
}
# Ensure knowledge_base folder exists (runtime workspace)
Path("knowledge_base").mkdir(parents=True, exist_ok=True)
# -------------------------
# Enhanced Keyword Bank
# -------------------------
KEYWORDS = {
"Detect": [
"scout", "recon", "reconnaissance", "observe", "observation", "surveil", "surveillance",
"spot", "uav", "uavs", "drone", "drones", "sighting", "watch", "eyes-on", "spotter",
"listening", "elint", "sigint", "listening-post"
],
"Deny": [
"jam", "jamming", "opsec", "block", "scramble", "spoof", "spoofer", "hide", "mask",
"camouflage", "decoy", "decoys", "dummy", "gps-spoof", "emcon"
],
"Deter": [
"patrol", "show", "show-of-force", "posture", "harden", "checkpoint", "presence",
"escort", "route security", "guard", "fortify", "fortified", "force protection"
],
"Deliver": [
"liberate", "support", "influence", "population", "protect", "recruit", "bribe", "payment",
"civic", "civic action", "hearts and minds", "aid", "protection", "community engagement",
"coercion", "intimidate"
],
"Destroy": [
"attack", "strike", "kill", "eliminate", "ambush", "ambushed", "ied", "bomb", "mortar",
"rocket", "artillery", "raid", "assault", "fire", "indirect-fire", "weapons cache", "cache"
],
}
# -------------------------
# Utility: fuzzy token similarity
# -------------------------
def similar(a, b):
return SequenceMatcher(None, a, b).ratio()
# -------------------------
# Fuzzy scoring + evidence extraction
# -------------------------
def fuzzy_score_with_evidence(text, keywords, token_threshold=0.85):
text_low = text.lower()
tokens = re.findall(r"\w+|\S", text_low)
sentences = re.split(r'(?<=[.!?])\s+', text)
scores = {k: 0 for k in keywords}
evidence = {k: [] for k in keywords}
for d, words in keywords.items():
for w in words:
w_low = w.lower()
for i, t in enumerate(tokens):
# direct substring match or fuzzy similarity on token
try:
if w_low in t or similar(w_low, t) >= token_threshold:
scores[d] += 1
# find sentence containing token for evidence
sent = next((s for s in sentences if t in s.lower()), text)
evidence[d].append({"word": w, "token": t, "index": i, "sentence": sent})
except Exception:
continue
return scores, evidence
# -------------------------
# OpenAI client detection (modern/classic)
# -------------------------
try:
from openai import OpenAI as ModernOpenAI
OPENAI_MODERN_AVAILABLE = True
except Exception:
ModernOpenAI = None
OPENAI_MODERN_AVAILABLE = False
try:
import openai as openai_classic
OPENAI_CLASSIC_AVAILABLE = True
except Exception:
openai_classic = None
OPENAI_CLASSIC_AVAILABLE = False
def get_openai_client():
key = os.environ.get("OPENAI_API_KEY")
if not key:
return None, "OPENAI_API_KEY missing"
# Try modern
if OPENAI_MODERN_AVAILABLE:
try:
client = ModernOpenAI(api_key=key)
return (client, "modern")
except Exception as e:
logger.warning(f"Modern OpenAI init failed: {e}")
if OPENAI_CLASSIC_AVAILABLE:
try:
openai_classic.api_key = key
return (openai_classic, "classic")
except Exception as e:
logger.warning(f"Classic OpenAI init failed: {e}")
return None, "No usable OpenAI client available"
# -------------------------
# LLM self-score (asks model to rate 0..5 per D)
# -------------------------
LLM_SELF_SCORE_INSTRUCTIONS = """
You are an analyst. Return EXACTLY a JSON object with keys detect, deny, deter, deliver, destroy
and integer values 0..5 based solely on the SITREP text that follows.
SITREP:
---
{text}
---
"""
def llm_self_score(sitrep_text):
client_info = get_openai_client()
client, method = client_info if isinstance(client_info, tuple) else (None, "none")
if client is None:
return None, "No OpenAI client"
prompt = LLM_SELF_SCORE_INSTRUCTIONS.format(text=sitrep_text)
try:
if method == "modern":
resp = client.responses.create(model=os.environ.get("OPENAI_API_MODEL", "gpt-4o-mini"),
input=prompt, max_output_tokens=200)
out_text = ""
for item in getattr(resp, "output", []) or []:
if isinstance(item, dict):
for c in item.get("content", []):
if c.get("type") == "output_text":
out_text += c.get("text", "")
else:
chat = client.ChatCompletion.create(model=os.environ.get("OPENAI_API_MODEL", "gpt-3.5-turbo"),
messages=[{"role":"user","content":prompt}], max_tokens=200)
out_text = chat.choices[0].message.content.strip()
# extract JSON object
start = out_text.find("{")
end = out_text.rfind("}")
if start != -1 and end != -1:
partial = out_text[start:end+1]
parsed = json.loads(partial)
normalized = {
"Detect": int(parsed.get("detect", 0)),
"Deny": int(parsed.get("deny", 0)),
"Deter": int(parsed.get("deter", 0)),
"Deliver": int(parsed.get("deliver", 0)),
"Destroy": int(parsed.get("destroy", 0)),
}
return normalized, None
return None, f"No JSON in LLM output: {out_text}"
except Exception as e:
logger.exception("llm_self_score failure")
return None, str(e)
# -------------------------
# Merge and normalize scores (0..5)
# -------------------------
def merge_and_normalize_scores(keyword_scores, llm_scores=None, kw_weight=0.6, llm_weight=0.4, scale_to=5):
max_kw = max(keyword_scores.values()) if keyword_scores and max(keyword_scores.values()) > 0 else 1
normalized_kw = {k: (v / max_kw) * scale_to for k, v in keyword_scores.items()}
if llm_scores:
normalized_llm = {k: float(llm_scores.get(k, 0)) for k in keyword_scores.keys()}
else:
normalized_llm = {k: 0.0 for k in keyword_scores.keys()}
merged = {}
for k in keyword_scores.keys():
merged_val = (normalized_kw.get(k, 0) * kw_weight) + (normalized_llm.get(k, 0) * llm_weight)
merged[k] = round(max(0.0, min(scale_to, merged_val)), 2)
return merged
# -------------------------
# Cross-diagnostics (deception hints)
# -------------------------
def cross_diagnostics(evidence, merged_scores):
flags = []
try:
if len(evidence.get("Detect", [])) >= 2 and len(evidence.get("Deny", [])) >= 2:
flags.append("High Detect & high Deny concurrently — potential deception; require source redundancy.")
# If Detect high but Destroy low -> possible recon/decoy
if merged_scores.get("Detect", 0) >= 3 and merged_scores.get("Destroy", 0) <= 1:
flags.append("Detect HIGH but Destroy LOW — possible recon/decoy activity or feint.")
# Deliver presence with low Destroy -> population influence move
if merged_scores.get("Deliver", 0) >= 3 and merged_scores.get("Destroy", 0) <= 1:
flags.append("Deliver HIGH & Destroy LOW — focused influence ops; watch for lookouts and informants.")
# Check evidence sentence redundancy
for d, evlist in evidence.items():
distinct = len(set([e.get("sentence", "").strip() for e in evlist if e.get("sentence")]))
if len(evlist) > 1 and distinct < 2:
flags.append(f"{d.upper()}: multiple token matches but low sentence redundancy -> low source diversity.")
except Exception:
pass
return flags
# -------------------------
# Radar chart helper (0..5)
# -------------------------
def make_radar_chart_from_merged(merged_scores):
labels = list(merged_scores.keys())
values = [float(merged_scores.get(k, 0)) for k in labels]
values += values[:1]
angles = np.linspace(0, 2 * np.pi, len(labels), endpoint=False).tolist()
angles += angles[:1]
fig, ax = plt.subplots(figsize=(5,5), subplot_kw=dict(polar=True))
ax.plot(angles, values, linewidth=2)
ax.fill(angles, values, alpha=0.25)
ax.set_thetagrids(np.degrees(angles[:-1]), labels)
ax.set_ylim(0, 5)
for i, angle in enumerate(angles[:-1]):
ax.text(angle, values[i] + 0.15, str(values[i]), horizontalalignment='center', verticalalignment='bottom', fontsize=9)
ax.set_title("5D Spectrum (0-5 intensity scale)")
return fig
# -------------------------
# Fallback two-lens report generator (guaranteed output when LLM absent)
# -------------------------
def fallback_two_lens_report(observation_json):
merged = observation_json.get("merged_5d_scores", {})
evidence = observation_json.get("evidence", {})
cross_flags = observation_json.get("cross_diagnostics", [])
raw = observation_json.get("raw_text", "")
lines = []
lines.append("⚠️ FALLBACK TWO-LENS REPORT (LLM unavailable)")
lines.append("\nA) INITIAL SITUATIONAL AWARENESS — ENEMY 5D VIEW")
for d in ["Detect", "Deny", "Deter", "Deliver", "Destroy"]:
val = merged.get(d, 0)
lines.append(f"\n{d.upper()} (intensity {val}/5):")
evs = evidence.get(d, [])
if evs:
for e in evs[:3]:
sent = e.get("sentence", "").strip()
lines.append(f"- Indicator: '{e.get('word')}' -> {sent[:200]}")
else:
lines.append("- No explicit textual indicators located for this D (INFERRED).")
if d == "Detect" and merged.get("Detect",0) >= 3 and merged.get("Destroy",0) <= 1:
lines.append("- NOTE: Strong detection activity but low Destroy — possible reconnaissance/feint.")
lines.append("\nDECEPTION & CROSS DIAGNOSTICS:")
if cross_flags:
for f in cross_flags:
lines.append(f"- {f}")
else:
lines.append("- No cross-diagnostic flags raised automatically; continue multi-source checks.")
lines.append("\nSITUATION HYPOTHESES TABLE (AUTO-GENERATED - INFERENTIAL)")
h1_desc = ""
h2_desc = ""
if merged.get("Destroy",0) >= 3 or merged.get("Detect",0) >= 3 and merged.get("Destroy",0) >= 2:
h1_desc = "H1: Credible attack/ambush planned in stated area (evidence: Destroy indicators present; IED/wpn movement)."
h2_desc = "H2: Secondary/feint or deception not excluded; confirm with ISR on logistics and supply lines."
else:
h1_desc = "H1: Recon/feint to draw attention (Detect high, Destroy low)."
h2_desc = "H2: Influence/local control operation (Deliver high) to set up future capability."
lines.append(f"H1 | {h1_desc} | Likelihood: 0.6")
lines.append(f"H2 | {h2_desc} | Likelihood: 0.4")
lines.append("\nCOA FAMILIES (REVERSE-5D) — Examples (Short):")
lines.append("\nCOA H1-A: DEFENSIVE FP (Contain & Observe)")
lines.append("- Mission: Maintain force protection; defeat ambush if occurs.")
lines.append("- Detect: Increase persistent ISR (UAS night ops, continuous SIGINT sweep).")
lines.append("- Deny: EMCON & comm discipline, randomize convoys, route clearance teams.")
lines.append("- Deter: Visible mounted patrols & QRF on standby.")
lines.append("- Deliver: Civil protection patrols to reduce local collab.")
lines.append("- Destroy: Prepare target development for interdiction (only when positive ID and BDA expected).")
lines.append("- Trigger: If Destroy indicators ≥ 3 OR ≥2 independent weapon cache confirmations -> escalate to interdiction COA.")
lines.append("\nCOA H1-B: ACTIVE INTERDICTION (Limited Strike)")
lines.append("- Mission: Prevent imminent ambush by targeted interdiction.")
lines.append("- Detect: Rapid target confirmation with combined HUMINT+IMINT.")
lines.append("- Deny: Isolate suspected area, block egress routes.")
lines.append("- Deter: Use feint presence at nearby axes to split ENY attention.")
lines.append("- Deliver: Protect local informants enabling arrest/capture.")
lines.append("- Destroy: Time-sensitive precision strike/QRF removal of IED teams.")
lines.append("- Trigger: If PIRs confirm weapons movement & lookouts -> execute.")
lines.append("\nCOA H2-A: ISR & Deception Hunting (Low Level Probe)")
lines.append("- Mission: Resolve ambiguity; do not escalate to kinetic action.")
lines.append("- Detect: Controlled probes (small patrols, sensor baiting) to force ENY reaction.")
lines.append("- Deny: Secure our ISR signatures to avoid revealing coverage.")
lines.append("- Deter: Limited shows-of-force to deprioritize ENY confidence.")
lines.append("- Deliver: Civil engagements to undercut ENY influence.")
lines.append("- Destroy: None immediate; escalate only with robust confirmation.")
lines.append("- Trigger: If ENY moves logistics or Destroy indicators rise -> escalate to H1 COAs.")
lines.append("\nEXECUTIVE SUMMARY (Short):")
lines.append("- Current auto-analysis suggests potential deception risk. Immediate action: prioritize ISR to remove ambiguity and cross-check HUMINT/SIGINT. Use conservative escalations with numeric triggers.")
return "\n".join(lines)
# -------------------------
# LLM prompt template for full two-lens report
# -------------------------
LLM_PROMPT_TEMPLATE = """
SYSTEM: You are a military analyst assistant. ALWAYS produce TWO LENSES in the output:
(A) INITIAL SITUATIONAL AWARENESS — Enemy 5D View
(B) COA PLANNING — Reverse-5D
INPUT OBSERVATION JSON:
{observation_json}
INSTRUCTIONS:
1) Write an "A) INITIAL SITUATIONAL AWARENESS — ENEMY 5D VIEW" section. Under each D (DETECT, DENY, DETER, DELIVER, DESTROY) list:
- Indicators (bullet points) including sentence-level supporting evidence and the evidence index
- Geo/time metadata from the observation (if missing, state MISSING)
- Source reliability (0-1)
- Deception checks and cross-D contradictions (explicit 'Yes/No' and why)
2) Produce a "Situation Hypotheses Table" with columns:
Hypothesis ID | Short description | Likelihood (0–1) | Key 5D indicators | Confirming evidence | Disconfirming evidence | Required ISR | Time to test
Provide at least 2 hypotheses (H1, H2).
3) For each hypothesis produce at least 2 COA families using REVERSE-5D with triggers, sequencing, logistics, and BDA metrics.
4) Output order: A) INITIAL SITUATIONAL AWARENESS, Situation Hypotheses Table, COA families, EXECUTIVE SUMMARY
Produce the output as human-readable text suitable for commanders, with clear bullets and headings. If observation fields are missing, mark them as 'MISSING: <field>'.
"""
def llm_generate_full_report(observation_json):
client_info = get_openai_client()
client, method = client_info if isinstance(client_info, tuple) else (None, "none")
if client is None:
# Return fallback full two-lens report
return fallback_two_lens_report(observation_json)
prompt = LLM_PROMPT_TEMPLATE.format(observation_json=json.dumps(observation_json, indent=2))
try:
if method == "modern":
resp = client.responses.create(model=os.environ.get("OPENAI_API_MODEL", "gpt-4o-mini"),
input=prompt, max_output_tokens=1500)
out_text = ""
for item in getattr(resp, "output", []) or []:
if isinstance(item, dict):
for c in item.get("content", []):
if c.get("type") == "output_text":
out_text += c.get("text", "")
return out_text.strip() if out_text.strip() else fallback_two_lens_report(observation_json)
else:
chat = client.ChatCompletion.create(model=os.environ.get("OPENAI_API_MODEL", "gpt-3.5-turbo"),
messages=[{"role":"user","content":prompt}], max_tokens=1500)
out_text = chat.choices[0].message.content.strip()
return out_text if out_text else fallback_two_lens_report(observation_json)
except Exception as e:
logger.exception("LLM generate full report failed")
return fallback_two_lens_report(observation_json)
# -------------------------
# Synthesize Indian Army style bulleted SA
# -------------------------
def synthesize_bulleted_SA(observation_text, merged_scores, evidence, cross_flags):
lines = []
# Header
lines.append("SITUATIONAL AWARENESS (5D — INDIAN ARMY FORMAT)")
# Short summary of observation
summary = observation_text.strip().replace("\n", " ")
lines.append(f"- ENY OBS (summary): {summary[:240]}{'...' if len(summary) > 240 else ''}")
# 5D bullets
for d in ["Detect", "Deny", "Deter", "Deliver", "Destroy"]:
val = merged_scores.get(d, 0)
evs = evidence.get(d, [])
if evs:
sents = []
for e in evs:
s = e.get("sentence", "").strip()
if s and s not in sents:
sents.append(s)
example = sents[0][:160] + "..." if sents else "See evidence entries."
lines.append(f"- ENY {d.upper()}: Intensity {val}/5; Example: {example}")
else:
lines.append(f"- ENY {d.upper()}: Intensity {val}/5; No direct sentence-level indicator found (INFERRED).")
# Deception flags
lines.append("- DECEPTION / CROSS-DIAGNOSTICS:")
if cross_flags:
for f in cross_flags:
lines.append(f" - {f}")
else:
lines.append(" - None automatically flagged. Recommend multi-source confirmation.")
# Immediate action recommendations
lines.append("- IMMEDIATE RECOMMENDED ACTIONS:")
if merged_scores.get("Detect", 0) >= 2:
lines.append(" - Prioritize persistent ISR (UAV night ops, SIGINT sweep), assign PIRs.")
if merged_scores.get("Destroy", 0) >= 2:
lines.append(" - Harden convoys and restrict movement on suspected routes; QRF on standby.")
if merged_scores.get("Deliver", 0) >= 2:
lines.append(" - Civilian protection measures and HUMINT expansion to remove collaborators.")
if merged_scores.get("Deny", 0) >= 2:
lines.append(" - Apply EMCON windows, comms hardening; check for GPS/signal spoofing.")
if merged_scores.get("Deter", 0) >= 2:
lines.append(" - Use visible patrols to shape ENY calculus and protect key nodes.")
if not any(v >= 2 for v in merged_scores.values()):
lines.append(" - No dominant 5D signals; maintain ISR and conduct deception hunt measurements.")
return "\n".join(lines)
# -------------------------
# Synthesize BULL COAs (Indian Army style) with dynamic triggers
# -------------------------
def synthesize_bull_coas(merged_scores):
total = sum(merged_scores.values()) if sum(merged_scores.values()) > 0 else 1
# dynamic thresholds scaled to current intensity
t1 = max(2, round(total / 4)) # for Detect escalation
t2 = max(2, round(total / 3)) # for Deliver+Destroy escalation
t3 = max(1, round(total / 5)) # for Destroy launch
lines = []
lines.append("COURSES OF ACTION (BULL — INDIAN ARMY STYLE, DYNAMIC TRIGGERS)")
lines.append("\nCOA 1 — DEFENSIVE FORCE PROTECTION (Hold & Harden)")
lines.append(" Mission: Maintain force protection of convoys and bases in AOR.")
lines.append(" Main Effort: ISR & route security.")
lines.append(" Tasks (5D):")
lines.append(" - Detect: Persistent UAS & patrols; assign PIRs to suspected axes.")
lines.append(" - Deny: EMCON windows, comm encryption, route randomization.")
lines.append(" - Deter: Visible mounted patrols & show-of-force.")
lines.append(" - Deliver: Civil protection patrols to reduce collab.")
lines.append(" - Destroy: Hold kinetic response in reserve; interdiction only on confirmed targets.")
lines.append(f" Trigger (to COA 2): Detect intensity ≥ {t1} within 24 hrs OR ≥ {t1} independent detections.")
lines.append("\nCOA 2 — ACTIVE INTERDICTION (Seize Initiative)")
lines.append(" Mission: Disrupt ENY buildup and remove immediate threat nodes.")
lines.append(" Main Effort: QRF, target development, precision interdiction.")
lines.append(" Tasks (5D):")
lines.append(" - Detect: Rapid target confirmation (HUMINT + IMINT).")
lines.append(" - Deny: Seal suspected areas, cut supply lines.")
lines.append(" - Deter: Short, sharp strikes & patrols to unsettle ENY.")
lines.append(" - Deliver: Isolate and protect local assets willing to cooperate.")
lines.append(" - Destroy: Limited precision strikes on IED/weapon caches (ROE compliant).")
lines.append(f" Trigger (to COA 3): Deliver+Destroy combined intensity ≥ {t2} OR confirmed munitions caches.")
lines.append("\nCOA 3 — OFFENSIVE ACTION (Elimination)")
lines.append(" Mission: Destroy ENY operational capability in AOR.")
lines.append(" Main Effort: Offensive operations with full support.")
lines.append(" Tasks (5D):")
lines.append(" - Detect: Full target confirmation (multi-source).")
lines.append(" - Deny: Isolate battle space, interdiction of reinforcement routes.")
lines.append(" - Deter: Suppress ENY freedom of movement via combined arms.")
lines.append(" - Deliver: Post-strike stabilization of population.")
lines.append(" - Destroy: Decisive strikes on leadership/munitions nodes.")
lines.append(f" Trigger: Destroy intensity ≥ {t3} AND at least 2 independent target confirmations.")
lines.append("\nRISKS & MITIGATION:")
lines.append("- Risk: Collateral damage and escalation. Mitigation: Strict ROE and phased escalation.")
lines.append("- Risk: ENY deception. Mitigation: Multi-source confirmation and deception-hunt probes before kinetic action.")
return "\n".join(lines)
# -------------------------
# Commander note explaining scores and radar
# -------------------------
def generate_commander_note(merged_scores):
note = []
note.append("COMMANDER'S NOTE — HOW TO INTERPRET SCORES & RADAR")
note.append("- Scores are intensity indicators (0-5) for ENY activity across 5D functions; higher = more evidence/weight.")
note.append("- Radar spikes show where ENY emphasis lies. A single spike should trigger targeted ISR to verify.")
note.append("- Do NOT treat scores as final truth; they are decision aids. Always insist on multi-source confirmation for kinetic moves.")
note.append("- Use the dynamic COA triggers in the BULL section to move between COAs; triggers scale with overall activity.")
note.append("- If DETECT is high but DESTROY is low => suspect recon/decoy. If DESTROY is high => prepare protection & interdiction.")
return "\n".join(note)
# -------------------------
# SITREP loader: search repo root and knowledge_base for .json SITREPs
# -------------------------
def load_sitreps_from_repo():
sitreps = {}
roots = [Path("."), Path("knowledge_base")]
for r in roots:
if r.exists() and r.is_dir():
for p in sorted(r.glob("*.json")):
try:
text = p.read_text(encoding="utf-8")
_ = json.loads(text)
sitreps[str(p)] = text
except Exception:
try:
text = p.read_text(encoding="utf-8", errors="ignore")
sitreps[str(p)] = text
except Exception:
continue
return sitreps
# Load at startup
SITREP_FILES = load_sitreps_from_repo()
# -------------------------
# MAIN analyze pipeline (single entry)
# -------------------------
def analyze_enemy_full(observation_text):
if not observation_text or not observation_text.strip():
observation_text = "NO OBSERVATION PROVIDED"
# 1) Keyword fuzzy scoring & evidence
keyword_scores, evidence = fuzzy_score_with_evidence(observation_text, KEYWORDS)
# 2) Try LLM self-score (optional); do not fail if not available
llm_scores, llm_err = None, None
client_info = get_openai_client()
if client_info and client_info[0] is not None:
try:
llm_scores, llm_err = llm_self_score(observation_text)
except Exception as e:
llm_err = str(e)
logger.warning("LLM self-score failed: %s", llm_err)
else:
llm_err = "No OpenAI client configured for self-score"
# 3) Merge
merged = merge_and_normalize_scores(keyword_scores, llm_scores, kw_weight=0.6, llm_weight=0.4, scale_to=5)
# 4) Cross diagnostics
cross_flags = cross_diagnostics(evidence, merged)
# 5) Build observation json for LLM full report
observation_json = {
"observation_id": f"OBS-{datetime.datetime.utcnow().strftime('%Y%m%d-%H%M%S')}",
"timestamp_utc": datetime.datetime.utcnow().isoformat(),
"raw_text": observation_text,
"keyword_scores_raw": keyword_scores,
"llm_scores_raw": llm_scores if llm_scores else {},
"merged_5d_scores": merged,
"evidence": evidence,
"cross_diagnostics": cross_flags,
"llm_self_score_error": llm_err
}
# 6) Radar chart
fig = make_radar_chart_from_merged(merged)
# 7) Quick COA bullets
quick_coa = []
if merged.get("Detect", 0) >= 1:
quick_coa.append("Detect: Increase ISR (UAS, SIGINT, HUMINT).")
if merged.get("Deny", 0) >= 1:
quick_coa.append("Deny: Harden comms, EMCON, randomize movement.")
if merged.get("Deter", 0) >= 1:
quick_coa.append("Deter: Visible patrols, QRF posture.")
if merged.get("Deliver", 0) >= 1:
quick_coa.append("Deliver: Civil protection, HUMINT expansion.")
if merged.get("Destroy", 0) >= 1:
quick_coa.append("Destroy: Prepare interdiction and target development.")
if not quick_coa:
quick_coa = ["No clear 5D indicators: prioritize deception-hunting ISR."]
# 8) LLM full two-lens (uses LLM if available; else fallback generator)
llm_full_text = llm_generate_full_report(observation_json)
# 9) Synthesize bulleted SA and BULL COAs and commander note
sa_bulleted = synthesize_bulleted_SA(observation_text, merged, evidence, cross_flags)
bull_coas = synthesize_bull_coas(merged)
cmd_note = generate_commander_note(merged)
# 10) Return outputs
return {
"fig": fig,
"merged_json": merged,
"quick_coa_text": "\n".join(quick_coa),
"llm_full_text": llm_full_text,
"sa_bulleted": sa_bulleted,
"bull_coas": bull_coas,
"commander_note": cmd_note
}
# -------------------------
# Integrated Warfare Addon (CARVER + BOS + SPTR + Attack on Intent)
# -------------------------
def carver_analysis_simple(observation_text):
results = []
lines = observation_text.splitlines()
for line in lines:
if "Target" in line and any(c in line for c in ["C=", "A=", "R=", "V=", "E="]):
try:
parts = line.split()
name = parts[0] + " " + parts[1] if len(parts) > 1 else parts[0]
vals = {}
for p in parts:
if "=" in p and p[0].upper() in ["C","A","R","V","E","G"]:
k = p.split("=")[0]
try:
vals[k] = int(p.split("=")[1])
except:
vals[k] = 0
ai = sum(vals.values()) if vals else 0
results.append(f"{name}: {vals} → AI={ai}")
except Exception:
continue
if not results:
return "No CARVER targets detected. Add lines like 'Target A C=8 A=6 R=5 V=9 E=7 RG=6'."
return "\n".join(results)
def integrated_analysis(observation_text):
scores, evidence = fuzzy_score_with_evidence(observation_text, KEYWORDS)
merged = merge_and_normalize_scores(scores)
fig = make_radar_chart_from_merged(merged)
# LLM narrative if available
client_info = get_openai_client()
narrative = ""
if client_info and client_info[0] is not None:
try:
prompt = f"""
You are an Indian Army staff officer preparing an Integrated MDMP report.
Input SITREP:
{observation_text}
5D Scores:
{json.dumps(merged, indent=2)}
TASK:
1. Generate Situational Awareness through 5D + deception lens.
2. Provide Enemy Intent assessment using CARVER if targets given.
3. Draft a full Integrated Warfare MDMP plan including:
- 5D Ops Channels
- Attack on Intent–Capability–Action
- SPTR methodology
- BOS-centric wargaming
4. Output in clear military operational language with headings and bullets.
"""
client, method = client_info
if method == "modern":
resp = client.responses.create(model=os.environ.get("OPENAI_API_MODEL", "gpt-4o-mini"),
input=prompt, max_output_tokens=1000)
for item in getattr(resp, "output", []) or []:
if isinstance(item, dict):
for c in item.get("content", []):
if c.get("type") == "output_text":
narrative += c.get("text", "")
else:
chat = client.ChatCompletion.create(model=os.environ.get("OPENAI_API_MODEL", "gpt-3.5-turbo"),
messages=[{"role":"user","content":prompt}], max_tokens=1000)
narrative = chat.choices[0].message.content
except Exception as e:
narrative = f"Error fetching LLM output: {e}"
else:
narrative = "LLM not configured (no OPENAI_API_KEY set)."
sa_report = f"""
SITUATIONAL AWARENESS (Integrated 5D)
- Enemy Observation (summary): {observation_text[:200]}...
- DETECT Indicators: {merged['Detect']}
- DENY Indicators: {merged['Deny']}
- DETER Indicators: {merged['Deter']}
- DELIVER Indicators: {merged['Deliver']}
- DESTROY Indicators: {merged['Destroy']}
- Assessment: Enemy may be masking intent with deception.
"""
warfare_plan = f"""
INTEGRATED WARFARE BATTLE PLAN
1. 5D OPS CHANNELS: Detect, Deny, Deter, Deliver, Destroy applied across HUMINT, CI, PsyOps, Strike.
2. ATTACK ON INTENT–CAPABILITY–ACTION: Degrade leadership, logistics, ISR; impose constraints; degrade/erode capability.
3. SPTR: Surveillance -> Processing (ACE) -> Targeting (CARVER, folders) -> Response with covert C2 and tactical HUMINT teams.
4. BOS-CENTRIC WARGAMING: Predict BOSEA, match friendly BOS responses, classify Most Likely / Most Dangerous COAs.
"""
coa_report = f"""
COURSES OF ACTION (Integrated Bull Format)
COA 1 – DEFENSIVE FP: ISR, OPSEC, patrols. Trigger: DENY ≥ 3.
COA 2 – ACTIVE INTERDICTION: Neutralize collaborators, strike caches. Trigger: DELIVER+DESTROY ≥ 4.
COA 3 – OFFENSIVE ACTION: Destroy ENY capability. Trigger: DESTROY ≥ 2 + confirmed staging.
"""
carver_report = carver_analysis_simple(observation_text)
commander_note = """
COMMANDER’S NOTE (Integrated System)
- Radar spikes show ENY emphasis; but deception may hide real intent.
- Always cross-check high DENY spikes with HUMINT.
- Apply CARVER to rank likely targets; defend most attractive ones.
- Attack Intent, Capability, Action simultaneously.
- Use SPTR for rhythm; BOS wargaming for adaptability.
"""
return narrative, sa_report, warfare_plan, coa_report, carver_report, commander_note, fig
# -------------------------
# GRADIO UI (single left flush column)
# -------------------------
custom_css = """
<style>
/* Remove left padding and make single left-aligned block */
#leftcol { padding-left: 0px; margin-left: 0px; max-width: 1100px; }
.gradio-container .panel { padding-left: 0px; }
</style>
"""
# Read inline integrated MD if present (also provide link at top)
def read_integrated_md():
mdp = "knowledge_base/integrated_mdmp_detailed.md"
try:
if os.path.exists(mdp):
with open(mdp, "r", encoding="utf-8") as f:
return f.read()
else:
return None
except Exception as e:
return f"Error reading integrated_mdmp_detailed.md: {e}"
INTEGRATED_MD_TEXT = read_integrated_md()
with gr.Blocks(css=custom_css) as demo:
# Banner (full width)
gr.HTML(f'<img src="{BANNER_URL}" style="width:100%; max-height:200px; object-fit:cover;">')
gr.Markdown(f"# {TITLE}")
gr.Markdown(INTRO)
# Put everything in a single left column block (#leftcol)
with gr.Column(elem_id="leftcol"):
# Top Knowledge Base Links (visible)
with gr.Row():
kb_links_md = "### Knowledge Base (Quick Links)\n"
for label, path in KNOWLEDGE_BASE.items():
kb_links_md += f"- **{label}** — `{path}`\n"
gr.Markdown(kb_links_md)
# Inline accordion with full integrated MD (if present) AND a link to file
# Both link and inline are provided as requested
with gr.Accordion("📘 Integrated MDMP — Full Detailed Document (link + inline)", open=False):
link_block = "#### Link to file (if committed to repo):\n"
link_block += f"- [Integrated MDMP document]({KNOWLEDGE_BASE.get('Integrated MDMP (MD)')})\n\n"
gr.Markdown(link_block)
if INTEGRATED_MD_TEXT:
# render inline the entire MD
gr.Markdown(INTEGRATED_MD_TEXT)
else:
gr.Markdown("**integrated_mdmp_detailed.md not found in `knowledge_base/` path.**\n\n"
"Upload `knowledge_base/integrated_mdmp_detailed.md` to see inline content here.")
# Full 5D Questionnaire (collapsed)
with gr.Accordion("Full 5D Questionnaire (open to view)", open=False):
gr.Textbox(value=QUESTIONNAIRE, lines=28, label="Exhaustive Questionnaire (read-only)", interactive=False)
# SITREP loader dropdown + input
sitrep_choices = list(SITREP_FILES.keys()) if SITREP_FILES else []
sitrep_dropdown = gr.Dropdown(choices=sitrep_choices, label="Load Sample SITREP (from repo / knowledge_base)", interactive=True)
sitrep_load_btn = gr.Button("Load selected SITREP into input")
obs_input = gr.Textbox(label="Observed Enemy Data / SITREP (paste or load)", lines=10, placeholder="Paste raw SITREP text or JSON here...")
# When user selects from dropdown, populate input (pretty-print JSON if JSON)
def load_selected_sitrep(name):
if not name:
return ""
text = SITREP_FILES.get(name, "")
try:
parsed = json.loads(text)
return json.dumps(parsed, indent=2)
except Exception:
return text
sitrep_dropdown.change(load_selected_sitrep, inputs=[sitrep_dropdown], outputs=[obs_input])
sitrep_load_btn.click(load_selected_sitrep, inputs=[sitrep_dropdown], outputs=[obs_input])
# Analyze button
analyze_btn = gr.Button("Analyze with 5D MDMP (Two-Lens)")
# Output blocks
llm_out = gr.Textbox(label="LLM Two-Lens Full Report (or Fallback)", lines=22)
sa_out = gr.Textbox(label="Bulleted Situational Awareness (Indian Army)", lines=12)
bull_out = gr.Textbox(label="COA (BULL Format) with Dynamic Triggers", lines=14)
cmdnote_out = gr.Textbox(label="Commander’s Note (How to read scores & radar)", lines=6)
quickcoa_out = gr.Textbox(label="Quick COA Bullets (Reverse-5D) - Immediate", lines=6)
radar_plot = gr.Plot(label="5D Radar (0-5)")
merged_json_out = gr.JSON(label="Merged 5D Scores (0-5)")
# On analyze, run pipeline and output
def on_analyze(obs_text):
result = analyze_enemy_full(obs_text)
fig = result["fig"]
merged = result["merged_json"]
llm_full = result["llm_full_text"]
sa = result["sa_bulleted"]
bull = result["bull_coas"]
cmdnote = result["commander_note"]
quick = result["quick_coa_text"]
return fig, merged, quick, llm_full, sa, bull, cmdnote
analyze_btn.click(on_analyze,
inputs=[obs_input],
outputs=[radar_plot, merged_json_out, quickcoa_out, llm_out, sa_out, bull_out, cmdnote_out])
# Divider and Integrated Warfare Addon Tab content (inline, flush left)
gr.Markdown("---")
gr.Markdown("## Integrated Warfare MDMP — (SPTR, CARVER, BOS, Attack on Intent)")
with gr.Accordion("Integrated Warfare Analysis (open)", open=False):
obs_input2 = gr.Textbox(label="Paste SITREP / Enemy Observation (Integrated Warfare)", lines=8, placeholder="Enter enemy situation details...")
analyze_btn2 = gr.Button("Run Integrated MDMP (Integrated Warfare Addon)")
narrative_out2 = gr.Textbox(label="LLM Narrative Report (Integrated)", lines=12)
sa_out2 = gr.Textbox(label="Situational Awareness (Integrated)", lines=8)
warfare_out = gr.Textbox(label="Integrated Warfare Plan", lines=12)
coa_out2 = gr.Textbox(label="Courses of Action (Bull Format)", lines=10)
carver_out = gr.Textbox(label="CARVER Target Analysis", lines=6)
commander_note_out2 = gr.Textbox(label="Commander’s Note (Integrated)", lines=8)
radar_out2 = gr.Plot(label="5D Radar Chart (Integrated)")
def on_integrated(obs_text):
narrative, sa_report, warfare_plan, coa_report, carver_report, commander_note, fig = integrated_analysis(obs_text)
return narrative, sa_report, warfare_plan, coa_report, carver_report, commander_note, fig
analyze_btn2.click(
on_integrated,
inputs=[obs_input2],
outputs=[narrative_out2, sa_out2, warfare_out, coa_out2, carver_out, commander_note_out2, radar_out2]
)
# Knowledge base accordion (read-only links)
with gr.Accordion("Knowledge Base & Sample Files (open)", open=False):
gr.Markdown("The app searches for `.json` in repo root and `knowledge_base/` and lists available sample SITREPs in the dropdown above.")
for label, path in KNOWLEDGE_BASE.items():
gr.Markdown(f"- **{label}** — `{path}` (place files in `knowledge_base/` or repo root to have them appear in the dropdown).")
# -------------------------
# Launch
# -------------------------
if __name__ == "__main__":
demo.launch()