Skip to content

Commit 62496eb

Browse files
authored
Merge pull request #49 from jethronap/48_stats_visualization
48 stats visualization
2 parents 251d1fd + fde62e4 commit 62496eb

File tree

15 files changed

+1393
-22
lines changed

15 files changed

+1393
-22
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ city or municipality. The agent downloads data in json format and stores them lo
1414
The Analyzer agent's goal is to get scraped data from the Scraper agent and:
1515

1616
- Enrich them by doing analysis using and LLM. The agent creates new file with structured information.
17-
- Produces a `.geojson` file in order to be used with mapping.
17+
- Produces a `.geojson` file to be used with mapping.
1818
- Creates a `heatmat` of the given area showing the spatial density of the surveillance infrastructure
1919
- Computes simple summary statistics from the available data.
20+
- Computes surveillance hotspots using DBSCAN and plots them overlaid on an OSM map
21+
- Plots sensitivity reasons distributions coming from a LLM
22+
- Plots camera counts for sensitive zones
23+
- Plots the distribution of private and public cameras using a donut-chart
2024

2125
# Installation
2226

local_test_pipeline.sh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,21 @@ pytest tests/tools/test_ollama_client.py
3535
echo "Done..."
3636
echo "==============================================="
3737

38-
3938
echo "Running io tools tests"
4039
pytest tests/tools/test_io_tools.py
4140
echo "Done..."
4241
echo "==============================================="
4342

43+
echo "Running mapping tools tests"
44+
pytest tests/tools/test_mapping_tools.py
45+
echo "Done..."
46+
echo "==============================================="
47+
48+
echo "Running chart tools tests"
49+
pytest tests/tools/test_chart_tools.py
50+
echo "Done..."
51+
echo "==============================================="
52+
4453
echo "Running memory store tests"
4554
pytest tests/memory/test_memory_store.py
4655
echo "Done..."

main.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,18 @@ def main():
2525
# content="This is only a test of the memory system.",
2626
# )
2727
# logger.success(
28-
# f" Stored memory: id={test_mem.id}, "
28+
# f" Stored memory: id={test_mem.id}, "
2929
# f"agent_id={test_mem.agent_id}, step={test_mem.step}"
3030
# )
3131
#
3232
# # 3. Load memories back
3333
# logger.info("Loading memories for TestAgent...")
3434
# records = memory.load(agent_id="TestAgent")
35-
# logger.success(f" Loaded {len(records)} record(s):")
35+
# logger.success(f": Loaded {len(records)} record(s):")
3636
# for rec in records:
3737
# print(
3838
# f" • [{rec.id}] {rec.timestamp.isoformat()} "
39-
# f"{rec.agent_id}/{rec.step} {rec.content}"
39+
# f"{rec.agent_id}/{rec.step}: {rec.content}"
4040
# )
4141
# agent = ScraperAgent(name="ScraperAgent", memory=memory)
4242
# result_context = agent.achieve_goal({"city": "Hamburg"})
@@ -68,14 +68,25 @@ def main():
6868
ctx = analyzer.achieve_goal(
6969
{
7070
"path": "overpass_data/malmö/malmö.json",
71-
"generate_geojson": True,
72-
"generate_heatmap": True,
71+
"generate_geojson": False,
72+
"generate_heatmap": False,
73+
"generate_hotspots": True,
74+
"generate_chart": False,
75+
"plot_zone_sensitivity": False,
76+
"plot_sensitivity_reasons": False,
77+
"plot_hotspots": True,
7378
}
7479
)
75-
print("Enriched file →", ctx["output_path"])
76-
print("GeoJSON →", ctx["geojson_path"])
77-
print("Heatmap →", ctx["heatmap_path"])
80+
# print("Context: ", ctx)
81+
print("Enriched file:", ctx["output_path"])
82+
# print("GeoJSON:", ctx["geojson_path"])
83+
# print("Heatmap:", ctx["heatmap_path"])
84+
print("hotspots file:", ctx["hotspots_path"])
7885
print("Summary stats:", ctx["stats"])
86+
# print("Chart:", ctx["chart_path"])
87+
# print("Zone‐sensitivity chart at", ctx["chart_zone_sens"])
88+
# print("Sensitivity reasons:", ctx["sensitivity_reasons_chart"])
89+
print("hotspots plot:", ctx["plot_hotspots"])
7990

8091

8192
if __name__ == "__main__":

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
[project]
22
name = "agentic-counter-surveillance"
3-
version = "0.1.0"
3+
version = "0.3.1"
44
description = "A software based on agentic architecture"
55
readme = "README.md"
66
requires-python = ">=3.11"
77
dependencies = [
8+
"contextily>=1.6.2",
89
"folium>=0.19.6",
10+
"geopandas>=1.0.1",
911
"loguru>=0.7.3",
12+
"matplotlib>=3.10.3",
1013
"pre-commit>=4.2.0",
1114
"pydantic-settings>=2.9.1",
1215
"pytest>=8.3.5",
1316
"requests>=2.32.3",
1417
"ruff>=0.11.7",
18+
"scikit-learn>=1.6.1",
1519
"sqlmodel>=0.0.24",
1620
]

src/agents/analyzer_agent.py

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
from __future__ import annotations
2-
31
import json
42
from functools import lru_cache
53
from pathlib import Path
6-
from typing import Dict, List, Any, Callable
4+
from typing import Dict, List, Any, Callable, Union
75

86

97
from src.agents.base_agent import Agent
@@ -18,8 +16,14 @@
1816
save_enriched_elements as save_json,
1917
to_geojson,
2018
)
21-
from src.tools.mapping_tools import to_heatmap
19+
from src.tools.mapping_tools import to_heatmap, to_hotspots
2220
from src.tools.stat_tools import compute_statistics
21+
from src.tools.chart_tools import (
22+
private_public_pie,
23+
plot_zone_sensitivity,
24+
plot_sensitivity_reasons,
25+
plot_hotspots,
26+
)
2327
from src.utils.decorators import log_action
2428

2529
Tool = Callable[..., Any]
@@ -35,16 +39,21 @@ def __init__(
3539
self,
3640
name: str,
3741
memory: MemoryStore,
38-
llm: LocalLLM | None = None,
39-
tools: Dict[str, Tool] | None = None,
42+
llm: Union[LocalLLM, None] = None,
43+
tools: Union[Dict[str, Tool], None] = None,
4044
):
4145
default_tools: Dict[str, Tool] = {
4246
"load_json": load_json,
4347
"enrich": self._enrich_element,
4448
"save_json": save_json,
4549
"to_geojson": to_geojson,
4650
"to_heatmap": to_heatmap,
51+
"to_hotspots": to_hotspots,
52+
"plot_hotspots": plot_hotspots,
4753
"report": compute_statistics,
54+
"plot_pie": private_public_pie,
55+
"plot_zone_sensitivity": plot_zone_sensitivity,
56+
"plot_sensitivity_reasons": plot_sensitivity_reasons,
4857
}
4958
super().__init__(name, tools or default_tools, memory)
5059
self.llm = llm or LocalLLM()
@@ -58,12 +67,22 @@ def perceive(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
5867
raise FileNotFoundError(path)
5968
generate_geojson = input_data.get("generate_geojson", True)
6069
generate_heatmap = input_data.get("generate_heatmap", False)
70+
generate_hotspots = input_data.get("generate_hotspots", False)
6171
compute_stats = input_data.get("compute_stats", True)
72+
generate_chart = input_data.get("generate_chart", False)
73+
plot_zone = input_data.get("plot_zone_sensitivity", False)
74+
plot_reasons = input_data.get("plot_sensitivity_reasons", False)
75+
plot_hotspots = input_data.get("plot_hotspots", False)
6276
return {
6377
"path": path,
6478
"generate_geojson": generate_geojson,
6579
"generate_heatmap": generate_heatmap,
80+
"generate_hotspots": generate_hotspots,
6681
"compute_stats": compute_stats,
82+
"generate_chart": generate_chart,
83+
"plot_zone_sensitivity": plot_zone,
84+
"plot_sensitivity_reasons": plot_reasons,
85+
"plot_hotspots": plot_hotspots,
6786
}
6887

6988
def plan(self, observation: Dict[str, Any]) -> List[str]:
@@ -72,8 +91,18 @@ def plan(self, observation: Dict[str, Any]) -> List[str]:
7291
steps.append("to_geojson")
7392
if observation["generate_heatmap"]:
7493
steps.append("to_heatmap")
94+
if observation["generate_hotspots"]:
95+
steps.append("to_hotspots")
7596
if observation["compute_stats"]:
7697
steps.append("report")
98+
if observation["generate_chart"]:
99+
steps.append("plot_pie")
100+
if observation["plot_zone_sensitivity"]:
101+
steps.append("plot_zone_sensitivity")
102+
if observation["plot_sensitivity_reasons"]:
103+
steps.append("plot_sensitivity_reasons")
104+
if observation["plot_hotspots"]:
105+
steps.append("plot_hotspots")
77106
return steps
78107

79108
@log_action
@@ -162,11 +191,54 @@ def act(self, action: str, context: Dict[str, Any]) -> Any:
162191
self.remember("heatmap_cache", f"{context['raw_hash']}|{html_path}")
163192
return str(html_path)
164193

194+
if action == "to_hotspots":
195+
geojson_path = Path(context["geojson_path"])
196+
hotspots_path = geojson_path.with_name(
197+
geojson_path.stem + "_hotspots.geojson"
198+
)
199+
self.tools["to_hotspots"](geojson_path, hotspots_path)
200+
context["hotspots_path"] = str(hotspots_path)
201+
cache_val = f"{context['raw_hash']}|{hotspots_path}"
202+
self.remember("hotspot_cache", cache_val)
203+
return str(hotspots_path)
204+
205+
if action == "plot_hotspots":
206+
hot = Path(context["hotspots_path"])
207+
pic = hot.with_suffix(".png")
208+
self.tools["plot_hotspots"](hot, pic)
209+
context["hotspots_plot"] = str(pic)
210+
return str(pic)
211+
165212
if action == "report":
166213
stats: Dict[str, Any] = self.tools["report"](context["enriched"])
167214
self.remember("report", json.dumps(stats))
168215
return stats
169216

217+
if action == "plot_pie":
218+
stats = context["stats"]
219+
src_path = Path(context["path"])
220+
chart_path = self.tools["plot_pie"](stats, src_path.parent)
221+
context["chart_path"] = chart_path
222+
self.remember("pie_chart", f"{context['raw_hash']}|{chart_path}")
223+
return str(chart_path)
224+
225+
if action == "plot_zone_sensitivity":
226+
stats = context["stats"]
227+
src_path = Path(context["path"])
228+
chart_path = self.tools["plot_zone_sensitivity"](stats, src_path.parent)
229+
context["chart_path"] = chart_path
230+
self.remember("chart_zone_sens", f"{context['raw_hash']}|{chart_path}")
231+
return str(chart_path)
232+
233+
if action == "plot_sensitivity_reasons":
234+
enriched_path = Path(context["output_path"])
235+
chart_path = enriched_path.with_name(
236+
f"{enriched_path.stem}_sensitivity.png"
237+
)
238+
self.tools["plot_sensitivity_reasons"](enriched_path, chart_path)
239+
context["sensitivity_reasons_chart"] = str(chart_path)
240+
return str(chart_path)
241+
170242
logger.error(f"Unhandled action: {action}")
171243
raise NotImplementedError(f"Unhandled action: {action}")
172244

@@ -188,8 +260,18 @@ def achieve_goal(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
188260
context["geojson_path"] = result
189261
elif step == "to_heatmap":
190262
context["heatmap_path"] = result
263+
elif step == "to_hotspots":
264+
context["hotspots_path"] = result
191265
elif step == "report":
192266
context["stats"] = result
267+
elif step == "plot_pie":
268+
context["chart_path"] = result
269+
elif step == "plot_zone_sensitivity":
270+
context["chart_zone_sens"] = result
271+
elif step == "plot_sensitivity_reasons":
272+
context["chart_sens_reasons"] = result
273+
elif step == "plot_hotspots":
274+
context["plot_hotspots"] = result
193275

194276
return context
195277

src/agents/scraper_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def perceive(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
5151

5252
def plan(self, observation: Dict[str, Any]) -> List[str]:
5353
"""
54-
Very simple two-step plan: (1) fetch (2) persist.
54+
Very simple two-step plan: (1) fetch: (2) persist.
5555
:param observation:
5656
:return: The available steps.
5757
"""

0 commit comments

Comments
 (0)