Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env
__pycache__/
*.pyc
logs/
45 changes: 45 additions & 0 deletions logs/events_log.jsonl
Original file line number Diff line number Diff line change
@@ -1 +1,46 @@
{"timestamp": "2025-11-05T04:49:15.063275+00:00", "source": "camera_node_1", "type": "intrusion", "details": {"zone": "loading_bay", "confidence": 0.87}}
{"timestamp": "2025-11-05T15:27:23.114703+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}}
{"timestamp": "2025-11-05T15:27:35.974145+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}}
{"timestamp": "2025-11-05T15:27:44.733306+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}}
{"timestamp": "2025-11-05T15:28:22.105045+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}}
{"timestamp": "2025-11-05T15:28:40.150320+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}}
{"timestamp": "2025-11-05T15:28:49.425947+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}}
{"timestamp": "2025-11-05T15:29:36.701314+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}}
{"timestamp": "2025-11-05T15:48:20.214370+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "MainGate", "confidence": 0.83}}
{"timestamp": "2025-11-05T15:48:24.273159+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "MainGate", "confidence": 0.99}}
{"timestamp": "2025-11-05T15:48:29.280312+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.78}}
{"timestamp": "2025-11-05T15:48:32.298760+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "ServerRoom", "confidence": 0.96}}
{"timestamp": "2025-11-05T15:48:34.305271+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "MainGate", "confidence": 0.81}}
{"timestamp": "2025-11-05T15:48:36.318437+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "Warehouse", "confidence": 0.79}}
{"timestamp": "2025-11-05T15:48:39.328778+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Warehouse", "confidence": 0.88}}
{"timestamp": "2025-11-05T15:48:42.336792+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "ServerRoom", "confidence": 0.96}}
{"timestamp": "2025-11-05T15:48:47.343460+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "MainGate", "confidence": 0.82}}
{"timestamp": "2025-11-05T15:48:49.350584+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "Warehouse", "confidence": 0.74}}
{"timestamp": "2025-11-05T15:48:53.355437+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "MainGate", "confidence": 0.9}}
{"timestamp": "2025-11-05T15:48:57.364895+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "ServerRoom", "confidence": 0.97}}
{"timestamp": "2025-11-05T15:49:00.368249+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "Lobby", "confidence": 0.97}}
{"timestamp": "2025-11-05T15:49:02.373533+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "ServerRoom", "confidence": 0.85}}
{"timestamp": "2025-11-05T15:49:04.381406+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "Lobby", "confidence": 0.99}}
{"timestamp": "2025-11-05T15:49:09.387040+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "MainGate", "confidence": 0.98}}
{"timestamp": "2025-11-05T15:49:13.392251+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "ServerRoom", "confidence": 0.84}}
{"timestamp": "2025-11-05T15:49:18.398145+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "Warehouse", "confidence": 0.82}}
{"timestamp": "2025-11-05T15:53:29.591057+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "ServerRoom", "confidence": 0.8}}
{"timestamp": "2025-11-05T15:53:32.597284+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.86}}
{"timestamp": "2025-11-05T15:53:35.602629+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "MainGate", "confidence": 0.93}}
{"timestamp": "2025-11-05T15:53:40.612955+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.86}}
{"timestamp": "2025-11-05T15:53:42.617961+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Lobby", "confidence": 0.84}}
{"timestamp": "2025-11-05T15:53:47.627867+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.89}}
{"timestamp": "2025-11-05T15:53:49.654096+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "ServerRoom", "confidence": 0.8}}
{"timestamp": "2025-11-05T15:53:54.663972+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "ServerRoom", "confidence": 0.92}}
{"timestamp": "2025-11-05T15:53:59.672196+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Lobby", "confidence": 0.92}}
{"timestamp": "2025-11-05T15:54:04.685684+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Warehouse", "confidence": 0.76}}
{"timestamp": "2025-11-05T15:54:07.695522+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "ServerRoom", "confidence": 0.89}}
{"timestamp": "2025-11-05T15:54:11.713202+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "Warehouse", "confidence": 0.9}}
{"timestamp": "2025-11-05T15:54:14.716615+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "ServerRoom", "confidence": 0.77}}
{"timestamp": "2025-11-05T15:54:16.725430+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.87}}
{"timestamp": "2025-11-05T15:54:21.732386+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "Warehouse", "confidence": 0.81}}
{"timestamp": "2025-11-05T15:54:25.734890+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Warehouse", "confidence": 0.71}}
{"timestamp": "2025-11-05T15:54:27.741517+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "MainGate", "confidence": 0.88}}
{"timestamp": "2025-11-05T15:54:31.746401+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.93}}
{"timestamp": "2025-11-05T15:54:34.749109+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "ServerRoom", "confidence": 0.86}}
{"timestamp": "2025-11-05T15:54:39.753955+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Warehouse", "confidence": 0.85}}
43 changes: 43 additions & 0 deletions sim/sensor_simulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import requests
import json
import random
import time
import os
from dotenv import load_dotenv

# -------- Load env vars --------
load_dotenv()
API_KEY = os.getenv("API_KEY")
BRIDGE_URL = "http://127.0.0.1:8000/api/event"

EVENT_TYPES = ["motion", "intrusion", "access_denied", "door_open"]
ZONES = ["Lobby", "Warehouse", "ServerRoom", "MainGate"]

def send_event(event_type):
payload = {
"source": "simSensor-01",
"type": event_type,
"details": {
"zone": random.choice(ZONES),
"confidence": round(random.uniform(0.7, 0.99), 2)
}
}
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}

try:
response = requests.post(BRIDGE_URL, headers=headers, data=json.dumps(payload))
print(f"[SENT] {event_type} ({response.status_code})")
except Exception as e:
print(f"[ERROR] Could not reach Bridge: {e}")

print("[INFO] SynAccel Sensor Simulator started. Press Ctrl+C to stop.")
try:
while True:
event = random.choice(EVENT_TYPES)
send_event(event)
time.sleep(random.randint(2, 5))
except KeyboardInterrupt:
print("\n[INFO] Sensor simulation stopped.")
Binary file modified src/bridge_core/__pycache__/main.cpython-311.pyc
Binary file not shown.
31 changes: 24 additions & 7 deletions src/bridge_core/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from datetime import datetime, timezone
import json
from pathlib import Path
from src.intelligence.correlation_engine import correlate_events


# ---------- Load environment variables ----------
load_dotenv()
Expand Down Expand Up @@ -46,6 +48,9 @@ class Event(BaseModel):
def index():
return {"message": "SynAccel-Bridge API is running"}

recent_events = []


@app.post("/api/event")
async def receive_event(event: Event, authorized: bool = Depends(verify_api_key)):
"""Receive and process a security or sensor event."""
Expand All @@ -59,13 +64,25 @@ async def receive_event(event: Event, authorized: bool = Depends(verify_api_key)

logs_dir = Path("logs")
logs_dir.mkdir(exist_ok=True)

log_file = logs_dir / "events_log.jsonl"
with open(log_file, "a") as f:
f.write(json.dumps(log_entry) + "\n")

return {
"received": True,
"message": "Event logged successfully",
"data": event.dict()
}

recent_events.append(log_entry)
if len(recent_events) > 20:
recent_events.pop(0)

# --- 3. Run correlation analysis
alerts = correlate_events(recent_events)

if alerts:
alerts_file = logs_dir / "alerts_log.jsonl"
with open(alerts_file, "a") as f:
for alert in alerts:
f.write(json.dumps(alert) + "\n")
for a in alerts:
print(f"[⚠] {a['severity'].upper()} | {a['message']}")

# Normal response
print(f"[✔] Logged {event.type} from {event.source}")
return {"received": True, "alerts_triggered": len(alerts), "data": event.dict()}
62 changes: 62 additions & 0 deletions src/intelligence/correlation_engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from datetime import datetime, timezone, timedelta

# A dictionary to remember when each source last triggered an alert
last_alert_time = {}

def correlate_events(events):
"""
Analyze recent events for abnormal patterns.
Returns a list of generated alerts.
"""
alerts = []
now = datetime.now(timezone.utc)

# --- Parameters ---
INTRUSION_THRESHOLD = 3 # how many intrusions before alert
COOLDOWN_PERIOD = timedelta(seconds=60) # wait 60s before re-alerting same source
WINDOW = timedelta(seconds=30) # only look at events within last 30 s

# --- Filter only recent events ---
recent_window = [
e for e in events
if now - datetime.fromisoformat(e["timestamp"]) <= WINDOW
]

# --- Example 1: repeated intrusions from same source ---
intrusion_counts = {}
for e in recent_window:
if e["type"] == "intrusion":
intrusion_counts[e["source"]] = intrusion_counts.get(e["source"], 0) + 1

for src, count in intrusion_counts.items():
if count >= INTRUSION_THRESHOLD:
# cooldown check
if src not in last_alert_time or now - last_alert_time[src] > COOLDOWN_PERIOD:
alerts.append({
"timestamp": now.isoformat(),
"source": src,
"type": "intrusion_spike",
"severity": "high",
"message": f"{count} intrusion events from {src} within short timeframe"
})
last_alert_time[src] = now # remember time of alert

# --- Example 2: motion followed by access_denied from same source ---
for i in range(len(recent_window) - 1):
if (
recent_window[i]["type"] == "motion"
and recent_window[i + 1]["type"] == "access_denied"
and recent_window[i]["source"] == recent_window[i + 1]["source"]
):
src = recent_window[i]["source"]
if src not in last_alert_time or now - last_alert_time[src] > COOLDOWN_PERIOD:
alerts.append({
"timestamp": now.isoformat(),
"source": src,
"type": "sequence_alert",
"severity": "medium",
"message": f"Motion followed by access_denied on {src}"
})
last_alert_time[src] = now

return alerts