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
11 changes: 8 additions & 3 deletions services/memory/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@
import numpy as np

from libs.observability.metrics import redis_write_latency
from libs.schemas.memory import ActionHint, TrackEvent, TrackSequence
from libs.schemas.memory import (
TrackEvent,
TrackSequence,
)
Comment on lines +39 to +42
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 | 🔴 Critical | ⚡ Quick win

Restore ActionHint in this import.

store_event() still compares event.action_hint to ActionHint.ZONE_ENTRY at Line 329, so dropping the import makes the module fail lint and raises on that path at runtime.

🐛 Minimal fix
 from libs.schemas.memory import (
+    ActionHint,
     TrackEvent,
     TrackSequence,
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from libs.schemas.memory import (
TrackEvent,
TrackSequence,
)
from libs.schemas.memory import (
ActionHint,
TrackEvent,
TrackSequence,
)
🤖 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/memory/memory.py` around lines 39 - 42, The import list in
services/memory/memory.py dropped ActionHint but store_event() still references
ActionHint.ZONE_ENTRY; restore ActionHint in the from libs.schemas.memory import
(...) line so ActionHint is available to the module and lint/runtime errors are
resolved (search for the import and the store_event function to update the
import tuple to include ActionHint).

from libs.schemas.tracking import TrackLifecycleEvent, TrackState
from services.tracking.cross_camera_reid import CrossCameraReID
from services.memory.baseline import ZoneBaseline
Expand Down Expand Up @@ -344,8 +347,10 @@ def get_sequence(self, track_id: int, last_n: Optional[int] = None, camera_id: O
key = self._seq_key(track_id)
raw_list = self._r.lrange(key, -last_n, -1) if last_n else self._r.lrange(key, 0, -1)

events: list[TrackEvent] = []
for raw in raw_list:
def get_active_track_ids(self, camera_id: str) -> set[int]:
members = self._r.smembers(self._active_key(camera_id))
result: set[int] = set()
for m in members:
Comment on lines +350 to +353
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 | 🔴 Critical | ⚡ Quick win

This insertion breaks get_sequence() and duplicates get_active_track_ids().

Placing a new method here ends get_sequence() right after raw_list, pulls the rest of its body into the wrong scope, and then gets shadowed again by the existing get_active_track_ids() at Lines 379-382. The new call to self._active_key(camera_id) is also invalid because _active_key() takes no argument.

🐛 Suggested correction
-    def get_active_track_ids(self, camera_id: str) -> set[int]:
-        members = self._r.smembers(self._active_key(camera_id))
-        result: set[int] = set()
-        for m in members:
+        events: list[TrackEvent] = []
+        for raw in raw_list:

Keep the single get_active_track_ids() implementation below get_zone_entry_count().

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_active_track_ids(self, camera_id: str) -> set[int]:
members = self._r.smembers(self._active_key(camera_id))
result: set[int] = set()
for m in members:
events: list[TrackEvent] = []
for raw in raw_list:
🧰 Tools
🪛 GitHub Actions: Phase 3 — Memory Tests / 0_test.txt

[error] 352-352: ruff check (F841): Local variable result is assigned to but never used.

🪛 GitHub Actions: Phase 3 — Memory Tests / test

[error] 352-352: Ruff (F841): Local variable result is assigned to but never used.

🤖 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/memory/memory.py` around lines 350 - 353, A newly inserted
get_active_track_ids definition was placed in the middle of get_sequence,
truncating get_sequence after raw_list, duplicating get_active_track_ids later,
and calling self._active_key(camera_id) even though _active_key() takes no
arguments; to fix, remove the misplaced duplicate method so get_sequence retains
its full body (including use of raw_list), keep a single get_active_track_ids
implementation positioned below get_zone_entry_count, and update any
_active_key(...) calls to call _active_key() with no arguments; reference
get_sequence, get_active_track_ids, get_zone_entry_count, raw_list, and
_active_key when making the changes.

try:
data = json.loads(raw if isinstance(raw, str) else raw.decode())
events.append(TrackEvent(**data))
Expand Down
53 changes: 43 additions & 10 deletions services/tracking/tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,22 +163,46 @@ def update(
y1 = float(ltwh[1])
x2 = x1 + float(ltwh[2])
y2 = y1 + float(ltwh[3])
cx, cy = (x1 + x2) / 2, (y1 + y2) / 2

zones = [z.name for z in get_zones_for_point(cx, cy)]
cx = (x1 + x2) / 2
cy = (y1 + y2) / 2

matched_zones = get_zones_for_point(cx, cy)

ZONE_PRIORITY = {
"keypad_area": 2,
"restricted_door": 1,
}

matched_zones.sort(
key=lambda z: ZONE_PRIORITY.get(z.name, 0),
reverse=True,
)

zones = [z.name for z in matched_zones]

# ── Lifecycle: BORN ───────────────────────────────────────────
if tid not in self._known_ids:
self._known_ids.add(tid)
self._emit_lifecycle(TrackState.BORN, tid, zones, 0.0)
logger.info(f"Track BORN: #{tid} in zones={zones}")

# ── Dwell time ────────────────────────────────────────────────
self._emit_lifecycle(
TrackState.BORN,
tid,
zones,
0.0,
)

logger.info(
f"Track BORN: #{tid} in zones={zones}"
)

prev = self._active_tracks.get(tid)
dwell_frames = (prev.dwell_time_frames + 1) if prev else 1

dwell_frames = (
prev.dwell_time_frames + 1
) if prev else 1

dwell_secs = dwell_frames / self.fps

# ── Trajectory ────────────────────────────────────────────────
prev_traj = prev.trajectory if prev else []
new_point = TrajectoryPoint(x=cx, y=cy, frame_id=self._frame_id)
trajectory = (prev_traj + [new_point])[-self.MAX_TRAJECTORY_LEN :]
Expand All @@ -196,21 +220,30 @@ def update(
zones_present=zones,
last_seen_frame=self._frame_id,
)

self._active_tracks[tid] = obj
current_ids.add(tid)
tracked_objects.append(obj)

current_ids.add(tid)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

active_tracks.set(len(tracked_objects))
for obj in tracked_objects:
track_dwell_seconds.observe(obj.dwell_time_seconds)

# ── Lifecycle: LOST for tracks that disappeared ────────────────────
for tid, prev_obj in list(self._active_tracks.items()):

if tid not in current_ids:
frames_since = self._frame_id - prev_obj.last_seen_frame
track = None
if frames_since == 1:
track = next((t for t in raw_tracks if int(t.track_id) == tid), None)
track = next(
(
t for t in raw_tracks
if int(t.track_id) == tid
),
None,
)

embedding = None
if frames_since == 1:
Expand Down
Loading