Skip to content

feat: add scene graph representation layer for surveillance reasoning#89

Merged
Devnil434 merged 6 commits into
Devnil434:mainfrom
purvask2006-collab:feat/scene-graph-reasoning
May 19, 2026
Merged

feat: add scene graph representation layer for surveillance reasoning#89
Devnil434 merged 6 commits into
Devnil434:mainfrom
purvask2006-collab:feat/scene-graph-reasoning

Conversation

@purvask2006-collab
Copy link
Copy Markdown
Contributor

@purvask2006-collab purvask2006-collab commented May 18, 2026

  • Add GraphNode/GraphEdge Pydantic schemas (libs/schemas/graph.py)
  • Implement SceneGraph with NetworkX MultiDiGraph (services/reasoning/scene_graph.py)
  • Integrate scene graph into LLM prompt builder (services/reasoning/prompts.py)
  • Add comprehensive unit tests — 11/11 passing

Closes #69

Summary by CodeRabbit

  • New Features

    • Added scene graph schema for nodes and edges to represent persons, objects, zones and their relationships.
  • Refactor

    • Reworked scene graph construction and prompt serialization; prompts now include timestamps, edge listings, and distance formatting, with long prompts truncated.
    • Reasoning prompts updated to accept an event description alongside scene context.
  • Tests

    • Expanded tests covering manual graph creation, frame-based construction, and prompt serialization.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b362fea6-a571-4eea-83cb-3a7b92040de2

📥 Commits

Reviewing files that changed from the base of the PR and between 203e295 and e2ba502.

📒 Files selected for processing (3)
  • .gitignore
  • requirements.txt
  • services/reasoning/scene_graph.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/reasoning/scene_graph.py

📝 Walkthrough

Walkthrough

This PR adds Pydantic graph schemas, implements a NetworkX-backed SceneGraph that builds from tracked frames and serializes to an LLM-ready edge list, updates the prompt builder to accept SceneGraph + event_description, and replaces tests with a suite validating graph construction and prompt serialization.

Changes

Scene Graph Refactoring: Schema-Driven Representation with NetworkX

Layer / File(s) Summary
Graph Schema Definition
libs/schemas/graph.py
Introduces NodeType enum (PERSON, ZONE, OBJECT) and EdgeType enum (INSIDE, NEAR, INTERACTING_WITH, ENTERED_FROM). Defines GraphNode (id, node_type, optional label) and GraphEdge (source, target, edge_type, optional distance_px).
SceneGraph Class and Core Methods
services/reasoning/scene_graph.py
Adds SceneGraph using networkx.MultiDiGraph, with add_node()/add_edge(), from_tracked_frame() to populate INSIDE/NEAR/INTERACTING_WITH edges (including optional distance_px), to_prompt_str() to serialize timestamped edge list with NEAR(distance_px) formatting and word-count truncation, and node_count()/edge_count() helpers.
Prompt Builder Refactoring
services/reasoning/prompts.py
Updates build_reasoning_prompt() signature to (event_description: str, scene_graph: SceneGraph), uses scene_graph.to_prompt_str() as graph context, and narrows instructions to structured suspicious-activity analysis referencing spatial relationships and interactions.
Test Suite
tests/test_scene_graph.py
Replaces previous detection-frame tests with unit tests for manual node/edge construction, from_tracked_frame() behavior (empty/full frames, timestamp), NEAR distance persistence, and to_prompt_str() serialization (timestamp header, NEAR distance formatting, token-budget behavior, empty graph handling).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐰 I stitched a graph with hops and nodes,

NEAR and INSIDE trace winding roads,
Zones and objects, people in view,
Prompt lines ready for the LLM's cue,
A tiny rabbit cheers: graph built true.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes to services/init.py and removals in services/reasoning/init.py are out of scope; they represent module restructuring unrelated to the stated scene graph feature objectives. Clarify why init.py modifications were necessary or revert them; focus PR on the scene graph feature as specified in issue #69.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a scene graph representation layer for surveillance reasoning, which is the core objective.
Linked Issues check ✅ Passed All coding requirements from issue #69 are met: scene graph schemas created, NetworkX-based SceneGraph implemented, graph serialization working, prompt integration complete, and unit tests validate functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libs/schemas/graph.py (1)

28-33: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Enforce GraphEdge distance invariants at the schema boundary.

GraphEdge currently allows invalid states (e.g., negative distance_px, or distance_px on non-NEAR edges). Validating this in-model prevents bad graph data from propagating downstream.

Suggested fix
 from pydantic import BaseModel
+from pydantic import model_validator
 ...
 class GraphEdge(BaseModel):
     source: str                    # node id
     target: str                    # node id
     edge_type: EdgeType
     distance_px: Optional[float] = None   # for NEAR edges
+
+    `@model_validator`(mode="after")
+    def validate_distance_rules(self):
+        if self.distance_px is not None and self.distance_px < 0:
+            raise ValueError("distance_px must be non-negative")
+        if self.edge_type != EdgeType.NEAR and self.distance_px is not None:
+            raise ValueError("distance_px is only valid for NEAR edges")
+        return self
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/schemas/graph.py` around lines 28 - 33, GraphEdge allows invalid states;
add validation inside the GraphEdge Pydantic model to enforce that distance_px,
when provided, is non-negative, that distance_px is only set for edges with
edge_type == EdgeType.NEAR, and that NEAR edges must have a non-null
distance_px; implement this via a validator/root_validator on GraphEdge that
inspects edge_type and distance_px and raises ValueError with a clear message
when invariants are violated (referencing GraphEdge.distance_px and
EdgeType.NEAR).
🧹 Nitpick comments (1)
services/reasoning/scene_graph.py (1)

16-17: ⚡ Quick win

Use MAX_PROMPT_TOKENS in truncation logic instead of a hardcoded literal.

Line 99 hardcodes 200, so updating MAX_PROMPT_TOKENS has no effect. This is an easy drift bug to prevent.

Suggested fix
-        if len(words) > 200:
-            serialized = " ".join(words[:200]) + "\n[...truncated]"
+        word_budget = int(self.MAX_PROMPT_TOKENS / 1.3)
+        if len(words) > word_budget:
+            serialized = " ".join(words[:word_budget]) + "\n[...truncated]"

Also applies to: 99-100

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/reasoning/scene_graph.py` around lines 16 - 17, The truncation logic
in scene_graph.py currently uses a hardcoded literal 200 instead of the
module-level constant MAX_PROMPT_TOKENS, causing changes to the constant to have
no effect; update the truncation code (the function that trims prompt/token
length where the literal 200 is used) to reference MAX_PROMPT_TOKENS instead,
and replace any other occurrences at the nearby truncation logic (the two
occurrences noted) so all token-limit checks use the single MAX_PROMPT_TOKENS
symbol.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@services/reasoning/scene_graph.py`:
- Around line 39-43: The code in from_tracked_frame is assuming each
zone/object/other item has an "id" and will KeyError on malformed items; update
all places that access ["id"] (eg. the loops that call
sg.add_node(GraphNode(id=zone["id"], ...)) and similar lines at 52-67) to
defensively fetch the id (e.g., use .get("id") or "id" in item) and skip or log
the item when id is missing before calling GraphNode or sg.add_node/sg.add_edge;
ensure you apply the same check for zones, objects and any other tracked_frame
entries referenced in from_tracked_frame so malformed entries don’t crash the
graph build.

In `@tests/test_scene_graph.py`:
- Around line 132-137: The test_empty_graph_serialization currently asserts that
"?" is not in the prompt but the SceneGraph.to_prompt_str uses the arrow marker
"→" for edges; update the assertion in test_empty_graph_serialization to check
that the actual edge marker "→" is not present in the serialized prompt (e.g.,
assert "→" not in prompt) so the test fails if edge serialization appears in an
empty SceneGraph.

---

Outside diff comments:
In `@libs/schemas/graph.py`:
- Around line 28-33: GraphEdge allows invalid states; add validation inside the
GraphEdge Pydantic model to enforce that distance_px, when provided, is
non-negative, that distance_px is only set for edges with edge_type ==
EdgeType.NEAR, and that NEAR edges must have a non-null distance_px; implement
this via a validator/root_validator on GraphEdge that inspects edge_type and
distance_px and raises ValueError with a clear message when invariants are
violated (referencing GraphEdge.distance_px and EdgeType.NEAR).

---

Nitpick comments:
In `@services/reasoning/scene_graph.py`:
- Around line 16-17: The truncation logic in scene_graph.py currently uses a
hardcoded literal 200 instead of the module-level constant MAX_PROMPT_TOKENS,
causing changes to the constant to have no effect; update the truncation code
(the function that trims prompt/token length where the literal 200 is used) to
reference MAX_PROMPT_TOKENS instead, and replace any other occurrences at the
nearby truncation logic (the two occurrences noted) so all token-limit checks
use the single MAX_PROMPT_TOKENS symbol.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2b565d06-5992-4c48-bd40-deecb7df946d

📥 Commits

Reviewing files that changed from the base of the PR and between 07bb02a and 203e295.

📒 Files selected for processing (7)
  • libs/schemas/graph.py
  • services/__init__.py
  • services/reasoning/__init__.py
  • services/reasoning/prompts.py
  • services/reasoning/scene_graph.py
  • tests/__init__.py
  • tests/test_scene_graph.py
💤 Files with no reviewable changes (2)
  • services/init.py
  • services/reasoning/init.py

Comment thread services/reasoning/scene_graph.py Outdated
Comment thread tests/test_scene_graph.py
Comment on lines +132 to 137
def test_empty_graph_serialization(self):
sg = SceneGraph(timestamp=0.0)
prompt = sg.to_prompt_str()
assert "t=0.0s" in prompt
assert "?" not in prompt # no edges

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix empty-graph assertion to check the actual edge marker.

Line 136 checks for "?", but edge lines use . This test can pass even if edge serialization regresses.

Suggested fix
-        assert "?" not in prompt      # no edges
+        assert "→" not in prompt      # no edges
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_scene_graph.py` around lines 132 - 137, The
test_empty_graph_serialization currently asserts that "?" is not in the prompt
but the SceneGraph.to_prompt_str uses the arrow marker "→" for edges; update the
assertion in test_empty_graph_serialization to check that the actual edge marker
"→" is not present in the serialized prompt (e.g., assert "→" not in prompt) so
the test fails if edge serialization appears in an empty SceneGraph.

- Add GraphNode/GraphEdge Pydantic schemas (libs/schemas/graph.py)
- Implement SceneGraph with NetworkX MultiDiGraph (services/reasoning/scene_graph.py)
- Integrate scene graph into LLM prompt builder (services/reasoning/prompts.py)
- Add comprehensive unit tests — 11/11 passing

Closes Devnil434#69
- Defensively handle missing IDs in from_tracked_frame() to avoid KeyError
- Fix empty-graph serialization test to check for '→' instead of '?'
@purvask2006-collab
Copy link
Copy Markdown
Contributor Author

I will review the errors thoroughly and get back to you.

@purvask2006-collab
Copy link
Copy Markdown
Contributor Author

Apologies for the frequent updates, it seems like I might be spamming the PR a bit with these commits. Would it be okay if I focus on resolving the remaining issues locally first, and then push a single comprehensive update so that CodeRabbitAI can verify the fixes all at once? Let me know if that works!

@Devnil434 Devnil434 merged commit 535615c into Devnil434:main May 19, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Issue] — Build scene graph representation with NetworkX

2 participants