Skip to content

Risk engine & zone frame fix - #11

Merged
baasith6 merged 1 commit into
mainfrom
Test@3--Risk-engine
Jul 29, 2026
Merged

Risk engine & zone frame fix#11
baasith6 merged 1 commit into
mainfrom
Test@3--Risk-engine

Conversation

@Nilaxs0501

@Nilaxs0501 Nilaxs0501 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Improved detection of concealment and jacket-related activity.
    • Added stronger validation for bag and concealment cues, reducing alerts from brief or isolated detections.
    • Added a “Refresh frame” option and clearer snapshot-loading errors in camera zone setup.
    • Improved snapshot availability for single-camera deployments.
  • Improvements

    • Concealment activity now contributes more significantly to risk scoring.
    • Updated evaluation scenarios and results to cover concealment-focused detection.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds concealment detection and sustained evidence gating, updates risk scoring and local evaluations, improves single-source snapshot fallback, and renders setup snapshots directly on the zone editor canvas with refresh and error handling.

Changes

Concealment detection and evaluation

Layer / File(s) Summary
Concealment cues and sustained event extraction
cloud-ai/app/detector.py, cloud-ai/app/events.py
Adds concealment prompts, sustained cue thresholds, direct concealment tracking, and high-value-zone event handling.
Risk configuration alignment
backend/Services/RiskEngine.cs, cloud-ai/eval/run_eval.py
Increases the concealment weight from 20 to 40 and bumps the backend rule version.
Jacket prompt evaluation workflow and outputs
cloud-ai/eval/run_jacket_test.py, cloud-ai/eval/*.json
Adds jacket-aware clip evaluation and replaces evaluation inputs and recorded results for five local clips.

Snapshot handling and zone editor rendering

Layer / File(s) Summary
Snapshot fallback and canvas rendering
connector/app/admin.py, dashboard/src/app/pages/setup/setup.component.ts
Adds a single-source snapshot fallback and loads, refreshes, reports errors for, and renders snapshots beneath zone geometry.

Claude credential configuration

Layer / File(s) Summary
Claude API credential configuration
.claude/settings.json
Updates the configured API key and helper command output.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant YOLOEDetector
  participant extract_events
  participant RiskEngine
  participant EvaluationScript
  YOLOEDetector->>extract_events: Detect concealment and bag cues
  extract_events->>RiskEngine: Emit scored concealment events
  RiskEngine->>EvaluationScript: Return event score
  EvaluationScript->>EvaluationScript: Classify each clip against alert threshold
Loading

Possibly related PRs

Suggested reviewers: baasith6

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main changes: risk scoring updates and a camera/frame rendering fix in the zone editor.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Test@3--Risk-engine

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.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Strengthen concealment signal and fix setup snapshot/zone framing

🐞 Bug fix ✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Strengthen and clarify concealment detection, including sustained-cue gating to reduce false
 positives.
• Align risk scoring weights/version across backend and local eval tooling.
• Fix zone-drawing snapshot flow by drawing frames onto the canvas with refresh + error handling.
Diagram

graph TD
  ui["Dashboard Setup UI"] --> admin(["Connector Admin API"]) --> cache[("Last frame cache")]
  detector(["Detector cues"]) --> extractor(["Event extractor"]) --> risk(["Backend Risk Engine"])
  eval(["Eval runner/scripts"]) --> extractor --> risk
  subgraph Legend
    direction LR
    _ui["UI"] ~~~ _svc(["Service"]) ~~~ _db[("State/Cache")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use the new canonical 'concealment' cue end-to-end (including jacket test prompts)
  • ➕ Avoids overloading 'open_bag' semantics, reducing accidental BagOpen/Concealment coupling
  • ➕ Lets events.py’s direct-concealment path drive Concealment without implying bag usage
  • ➕ Makes eval artifacts easier to interpret (cue counts match meaning)
  • ➖ Requires updating any prompt sets / detectors that previously relied on open_bag as a proxy
  • ➖ May slightly change event distribution compared to historical runs
2. Make sustained-cue gating time-based (seconds) rather than fixed detection count
  • ➕ More stable behavior across cameras/clips with different FPS
  • ➕ Easier to reason about and tune (e.g., require 0.5s of evidence)
  • ➖ Requires careful handling of variable FPS and dropped frames
  • ➖ Might need retuning thresholds across datasets
3. Centralize risk weights in a shared config artifact (generated JSON or shared package)
  • ➕ Eliminates backend vs eval drift (currently duplicated in RiskEngine.cs and run_eval.py)
  • ➕ Makes versioning and auditability cleaner
  • ➖ Adds build/publish plumbing or a small config API surface
  • ➖ May be overkill if eval tooling is strictly local/ephemeral

Recommendation: Keep the PR’s overall direction (direct concealment cue + sustained gating + UI snapshot redraw). Consider adjusting the jacket prompt mapping to emit the new canonical cue 'concealment' (instead of proxying to 'open_bag') to better reflect semantics and avoid unintended BagOpen coupling. If FPS variance is expected in production, promote SUSTAINED_CUE_DETECTIONS to a time-based threshold. Also, ensure secrets are not committed: the .claude/settings.json API key change should be removed or replaced with a local-only mechanism.

Files changed (11) +374 / -53

Enhancement (2) +11 / -3
RiskEngine.csIncrease concealment weight and bump rule version +2/-2

Increase concealment weight and bump rule version

• Doubles the Concealment risk contribution (20→40) and updates RuleVersion to v4-starter-1.2, indicating a scoring rule revision.

backend/Services/RiskEngine.cs

detector.pyAdd concealment as a canonical cue and expand phrase mappings +9/-1

Add concealment as a canonical cue and expand phrase mappings

• Extends CANONICAL_CUES with 'concealment' and maps several jacket/clothing hiding phrases to that cue. Also adds extra product-in-hand phrase variants to reduce missed handling signals.

cloud-ai/app/detector.py

Bug fix (3) +98 / -10
events.pyGate bag/concealment cues on sustained detections and refine concealment logic +49/-3

Gate bag/concealment cues on sustained detections and refine concealment logic

• Introduces sustained detection gating for bag/open-bag cues to reduce one-off false positives. Adds direct concealment cue tracking and updates concealment emission logic to allow either handled-then-bag or sustained direct concealment; also treats sustained concealment in high-value zones as implying product involvement for HighValueActivity.

cloud-ai/app/events.py

admin.pyFallback snapshot selection for single-camera connectors +4/-0

Fallback snapshot selection for single-camera connectors

• If a snapshot is requested with a mismatched camera_id and only one source exists, returns the single cached frame instead of 404. This improves dashboard usability when GUIDs differ from local connector config.

connector/app/admin.py

setup.component.tsRender snapshots directly onto zone canvas with refresh + error UI +45/-7

Render snapshots directly onto zone canvas with refresh + error UI

• Replaces CSS background-image snapshot usage with explicit image loading and drawing into the canvas for better alignment. Adds a refresh button, loading/error state, and ensures snapshot reload occurs when selecting a camera.

dashboard/src/app/pages/setup/setup.component.ts

Tests (5) +262 / -37
ground_truth.jsonReplace eval clip set with local test videos +16/-11

Replace eval clip set with local test videos

• Updates ground-truth description and swaps the clip inventory to a new set of local videos/IDs. Keeps schema and alert threshold consistent while changing evaluation inputs.

cloud-ai/eval/ground_truth.json

results.jsonRefresh evaluation results for new clip set and concealment behavior +51/-25

Refresh evaluation results for new clip set and concealment behavior

• Updates aggregate metrics and per-clip outcomes to reflect the new ground-truth clips and revised event extraction/weighting (including concealment).

cloud-ai/eval/results.json

results_jacket_prompts.jsonAdd jacket-prompt evaluation output artifact +121/-0

Add jacket-prompt evaluation output artifact

• Introduces a new results file capturing per-clip scores, cue counts, and events when using jacket-oriented prompts for YOLOE.

cloud-ai/eval/results_jacket_prompts.json

run_eval.pySync local eval weights with backend concealment weight +1/-1

Sync local eval weights with backend concealment weight

• Updates the local WEIGHTS table so Concealment matches backend scoring (40). This keeps eval scoring aligned with RiskEngine.cs.

cloud-ai/eval/run_eval.py

run_jacket_test.pyAdd one-off jacket concealment prompt evaluation runner +73/-0

Add one-off jacket concealment prompt evaluation runner

• Adds a script to run the 5 local clips with an extended prompt set and write results_jacket_prompts.json. Uses the existing detector/event extraction and scoring path for quick iteration.

cloud-ai/eval/run_jacket_test.py

Other (1) +3 / -3
settings.jsonRotate Claude API key helper/env value +3/-3

Rotate Claude API key helper/env value

• Updates the configured ANTHROPIC_API_KEY and apiKeyHelper output. This appears to commit a live credential into the repo, which is risky and typically should be excluded from version control.

.claude/settings.json

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Concealment bypasses sustained gate 🐞 Bug ≡ Correctness
Description
In extract_events(), BagOpen/HighValueActivity are gated by sustained bag/open_bag counts, but the
“handled_then_bag” concealment path still uses ungated open_bag_last_idx/bag_last_idx. This can emit
a Concealment event from a single transient bag/open_bag detection after product_first_idx,
contradicting the new false-positive suppression intent.
Code

cloud-ai/app/events.py[R230-236]

    latest_bag_idx = max(open_bag_last_idx, bag_last_idx)
-    concealment_zone = open_bag_zone or bag_fallback_zone or handling_zone
-    if product_in_shelf and product_first_idx >= 0 and latest_bag_idx > product_first_idx:
+    conceal_sustained = conceal_dets >= SUSTAINED_CUE_DETECTIONS
+    handled_then_bag = product_in_shelf and product_first_idx >= 0 and latest_bag_idx > product_first_idx
+    if handled_then_bag or conceal_sustained:
+        concealment_zone = (conceal_zone if conceal_sustained else None) \
+            or open_bag_zone or bag_fallback_zone or handling_zone
        events.append(_ev("Concealment", concealment_zone, 1.0, 0.7, now))
Relevance

⭐⭐ Medium

No historical evidence found; file history lookup for cloud-ai/app/events.py failed (path not on
default branch).

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new sustained gating only clears zones (not the last-index timestamps), but concealment ordering
still uses the ungated last indexes, so a non-sustained bag cue can satisfy `latest_bag_idx >
product_first_idx` and emit Concealment.

cloud-ai/app/events.py[201-236]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`extract_events()` introduced sustained gating for bag/open-bag cues, but `handled_then_bag` still keys off `open_bag_last_idx` / `bag_last_idx` even when the cue was not sustained. This allows one-off bag detections to trigger `Concealment`.

### Issue Context
The code explicitly claims sustained detections should be required to drive BagOpen/Concealment. The current implementation only nulls `open_bag_zone` / `bag_fallback_zone`, not the indexes used by concealment ordering.

### Fix Focus Areas
- cloud-ai/app/events.py[201-236]

### Suggested fix
- Compute a **gated** latest bag index, e.g.:
 - `latest_bag_idx = max(open_bag_last_idx if open_bag_sustained else -1, bag_last_idx if bag_sustained else -1)`
 - and use that for `handled_then_bag`.
- Alternatively, if `not open_bag_sustained`, also reset `open_bag_last_idx = -1` (same for `bag_last_idx`).
- Keep the zone selection consistent with the gated path (i.e., only allow bag-based concealment if the corresponding sustained flag is true).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Jacket eval prompt inconsistency 🐞 Bug ⚙ Maintainability
Description
The evaluation script run_jacket_test.py overrides DEFAULT_YOLOE_PROMPTS by mapping the new jacket
concealment phrases to open_bag, even though production DEFAULT_YOLOE_PROMPTS now maps them to the
dedicated concealment cue. This makes results_jacket_prompts.json evaluate a different cue mapping
than the production pipeline, potentially invalidating comparisons.
Code

cloud-ai/eval/run_jacket_test.py[R21-27]

+JACKET_PROMPTS = {
+    **DEFAULT_YOLOE_PROMPTS,
+    # Clothing-concealment cues (proxy onto open_bag so Concealment/BagOpen logic fires).
+    "person hiding item inside jacket": "open_bag",
+    "person putting object under clothing": "open_bag",
+    "hand inside jacket": "open_bag",
+    # Extra holding phrasings for small items / clothing items.
Relevance

⭐⭐ Medium

No prior accepted/rejected suggestions found about keeping eval prompt mappings in sync with
production.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Production default prompts explicitly map jacket concealment phrases to the concealment cue, while
the eval script intentionally overrides those same phrases to open_bag, meaning the eval run is
not exercising the newly introduced cue.

cloud-ai/app/detector.py[29-46]
cloud-ai/eval/run_jacket_test.py[1-30]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The jacket eval script’s prompt map conflicts with the canonical production prompt map by remapping jacket concealment phrases away from `concealment`.

### Issue Context
This doesn’t change production behavior, but it can mislead anyone using the eval artifacts to validate the new concealment cue.

### Fix Focus Areas
- cloud-ai/app/detector.py[29-46]
- cloud-ai/eval/run_jacket_test.py[21-30]

### Suggested fix
- Either:
 - Map the jacket phrases to `concealment` in `JACKET_PROMPTS` (preferred for evaluating the new cue), and regenerate `results_jacket_prompts.json`, or
 - Keep the override but rename the script/artifact and document clearly that it is a **legacy proxy-to-open_bag** comparison, not representative of production mappings.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Anthropic API key committed 🐞 Bug ⛨ Security
Description
.claude/settings.json contains a plaintext ANTHROPIC_API_KEY and duplicates it in apiKeyHelper,
exposing a credential in the repo and git history. This should be treated as compromised and enables
unauthorized API usage/billing by anyone with repository access.
Code

.claude/settings.json[R3-11]

+    "ANTHROPIC_API_KEY": "aero***********************************************FU",
    "ANTHROPIC_BASE_URL": "https://capi.aerolink.lat/",
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
  },
  "permissions": {
    "allow": [],
    "deny": []
  },
-  "apiKeyHelper": "echo 'aero_live_XUjsoGcPnyw0JJ-DtO1ZfHGdoORLaO4LxR808wU2s7k'"
-}
+  "apiKeyHelper": "echo 'aero_live_qiFbk-Et6G9qRZaElOvjjHgOWIz_O_i6Fts6W9cioFU'"
Relevance

⭐ Low

Similar “remove committed Anthropic API key” suggestion was rejected in PR #2.

PR-#2

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The file directly embeds the API key value in the env block and repeats it in a shell-echo helper,
which is a credential exposure.

.claude/settings.json[1-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
A secret (Anthropic API key) is committed in a tracked file and echoed via `apiKeyHelper`, which exposes the credential to anyone with access to the repository and its history.

### Issue Context
Even if this file is intended for local tooling, committing the key makes it retrievable from git history and from any clones/artifacts.

### Fix Focus Areas
- .claude/settings.json[1-12]

### Suggested fix
- **Immediately revoke/rotate** the exposed key in the upstream key management system.
- Remove the key from `.claude/settings.json` (and from `apiKeyHelper`).
- Replace with an environment-variable reference (no plaintext secret in-repo), and ensure `.claude/settings.json` is not tracked (add to `.gitignore` if appropriate).
- If feasible, perform git history cleanup (or at minimum document that history contains a compromised key and confirm rotation is complete).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Snapshot load state race 🐞 Bug ☼ Reliability
Description
SetupComponent.loadSnapshot() updates shared state (snapshotImg/loadingSnapshot) from Image
onload/onerror without validating that the response matches the currently selected camera/request.
If a user switches cameras while a prior image is in-flight, a slower prior callback can overwrite
snapshotImg and redraw the wrong frame behind zones.
Code

dashboard/src/app/pages/setup/setup.component.ts[R515-534]

+  loadSnapshot(): void {
+    if (!this.cameraId) return;
+    this.loadingSnapshot = true;
+    this.snapshotError = '';
+    const img = new Image();
+    // No crossOrigin: the connector admin API sends no CORS headers, and we only
+    // draw the frame (never read pixels back), so a tainted canvas is fine.
+    // Cache-buster so "Refresh frame" always pulls the connector's latest frame.
+    img.src = `http://${this.connectorAdminHost}:8099/snapshot?camera_id=${this.cameraId}&t=${Date.now()}`;
+    img.onload = () => {
+      this.snapshotImg = img;
+      this.loadingSnapshot = false;
+      this.redraw();
+    };
+    img.onerror = () => {
+      this.snapshotImg = null;
+      this.loadingSnapshot = false;
+      this.snapshotError = `No frame from connector (${this.connectorAdminHost}:8099). Is the connector running with this camera?`;
+      this.redraw();
+    };
Relevance

⭐ Low

Similar “reset stale state when switching cameras” reliability suggestion was rejected in PR #2.

PR-#2

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The onload/onerror closures unconditionally set this.snapshotImg and call redraw() based on a
locally created Image, with no check that the callback corresponds to the latest request or current
camera selection.

dashboard/src/app/pages/setup/setup.component.ts[498-535]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`loadSnapshot()` has an async race: callbacks from older requests can mutate component state after the selected camera/host has changed.

### Issue Context
This is most visible during rapid camera switching or any situation that triggers overlapping snapshot loads.

### Fix Focus Areas
- dashboard/src/app/pages/setup/setup.component.ts[498-535]

### Suggested fix
- Add a monotonic `snapshotRequestId` counter on the component.
- In `loadSnapshot()`, increment the counter and capture `{requestId, cameraId, host}` locally.
- In `onload/onerror`, check that the captured `requestId` is still current (and optionally that `cameraId/host` still match) before writing `snapshotImg`, `loadingSnapshot`, and `snapshotError`.
- (Optional hardening) Consider avoiding mixed-content issues by deriving the scheme from the current page or proxying through the backend rather than hardcoding `http://`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
5. Snapshot fallback weakens scoping 🐞 Bug ⛨ Security
Description
The connector /snapshot endpoint now returns the sole cached frame even when the requested camera_id
does not match, which weakens camera-id scoping in single-source mode. If this admin API is
reachable beyond a strictly trusted localhost/network boundary, callers can retrieve a live frame
without knowing the configured camera ID.
Code

connector/app/admin.py[R422-425]

+        if not frame and len(state.last_frames) == 1:
+            # Single-source connector: tolerate a camera-id mismatch between the
+            # dashboard GUID and the locally configured source.
+            frame = next(iter(state.last_frames.values()))
Relevance

⭐ Low

Security hardening for connector admin exposure was previously rejected (bind/auth suggestions) in
PR #7.

PR-#7

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The handler first tries state.last_frames.get(camera_id) but then falls back to
next(iter(state.last_frames.values())) whenever there is exactly one cached frame, regardless of
the requested id; the FastAPI app is built without any per-request auth checks in the shown code.

connector/app/admin.py[44-60]
connector/app/admin.py[418-428]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`/snapshot` silently returns a frame even for mismatched `camera_id` when only one source is present. While intended to tolerate dashboard/config mismatch, it removes the only scoping check the endpoint had.

### Issue Context
The admin API routes shown do not perform authentication checks, so the effective security boundary is network exposure. This change increases the blast radius if the port is exposed on a LAN.

### Fix Focus Areas
- connector/app/admin.py[44-60]
- connector/app/admin.py[418-428]

### Suggested fix
- Make the mismatch fallback **explicit/opt-in**, e.g. only when `camera_id` is empty, or when a query flag like `allow_mismatch=1` is provided.
- Alternatively, only apply fallback when the requested `camera_id` equals the configured/known single source id (if available) and otherwise return 404.
- If this endpoint is intended to be local-only, consider enforcing that at runtime (bind to localhost, or add a simple auth token) so accidental exposure doesn’t leak frames.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread cloud-ai/app/events.py
Comment on lines 230 to 236
latest_bag_idx = max(open_bag_last_idx, bag_last_idx)
concealment_zone = open_bag_zone or bag_fallback_zone or handling_zone
if product_in_shelf and product_first_idx >= 0 and latest_bag_idx > product_first_idx:
conceal_sustained = conceal_dets >= SUSTAINED_CUE_DETECTIONS
handled_then_bag = product_in_shelf and product_first_idx >= 0 and latest_bag_idx > product_first_idx
if handled_then_bag or conceal_sustained:
concealment_zone = (conceal_zone if conceal_sustained else None) \
or open_bag_zone or bag_fallback_zone or handling_zone
events.append(_ev("Concealment", concealment_zone, 1.0, 0.7, now))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Concealment bypasses sustained gate 🐞 Bug ≡ Correctness

In extract_events(), BagOpen/HighValueActivity are gated by sustained bag/open_bag counts, but the
“handled_then_bag” concealment path still uses ungated open_bag_last_idx/bag_last_idx. This can emit
a Concealment event from a single transient bag/open_bag detection after product_first_idx,
contradicting the new false-positive suppression intent.
Agent Prompt
### Issue description
`extract_events()` introduced sustained gating for bag/open-bag cues, but `handled_then_bag` still keys off `open_bag_last_idx` / `bag_last_idx` even when the cue was not sustained. This allows one-off bag detections to trigger `Concealment`.

### Issue Context
The code explicitly claims sustained detections should be required to drive BagOpen/Concealment. The current implementation only nulls `open_bag_zone` / `bag_fallback_zone`, not the indexes used by concealment ordering.

### Fix Focus Areas
- cloud-ai/app/events.py[201-236]

### Suggested fix
- Compute a **gated** latest bag index, e.g.:
  - `latest_bag_idx = max(open_bag_last_idx if open_bag_sustained else -1, bag_last_idx if bag_sustained else -1)`
  - and use that for `handled_then_bag`.
- Alternatively, if `not open_bag_sustained`, also reset `open_bag_last_idx = -1` (same for `bag_last_idx`).
- Keep the zone selection consistent with the gated path (i.e., only allow bag-based concealment if the corresponding sustained flag is true).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +21 to +27
JACKET_PROMPTS = {
**DEFAULT_YOLOE_PROMPTS,
# Clothing-concealment cues (proxy onto open_bag so Concealment/BagOpen logic fires).
"person hiding item inside jacket": "open_bag",
"person putting object under clothing": "open_bag",
"hand inside jacket": "open_bag",
# Extra holding phrasings for small items / clothing items.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Informational

5. Jacket eval prompt inconsistency 🐞 Bug ⚙ Maintainability

The evaluation script run_jacket_test.py overrides DEFAULT_YOLOE_PROMPTS by mapping the new jacket
concealment phrases to open_bag, even though production DEFAULT_YOLOE_PROMPTS now maps them to the
dedicated concealment cue. This makes results_jacket_prompts.json evaluate a different cue mapping
than the production pipeline, potentially invalidating comparisons.
Agent Prompt
### Issue description
The jacket eval script’s prompt map conflicts with the canonical production prompt map by remapping jacket concealment phrases away from `concealment`.

### Issue Context
This doesn’t change production behavior, but it can mislead anyone using the eval artifacts to validate the new concealment cue.

### Fix Focus Areas
- cloud-ai/app/detector.py[29-46]
- cloud-ai/eval/run_jacket_test.py[21-30]

### Suggested fix
- Either:
  - Map the jacket phrases to `concealment` in `JACKET_PROMPTS` (preferred for evaluating the new cue), and regenerate `results_jacket_prompts.json`, or
  - Keep the override but rename the script/artifact and document clearly that it is a **legacy proxy-to-open_bag** comparison, not representative of production mappings.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 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 @.claude/settings.json:
- Around line 3-11: Remove the hard-coded credential from the settings
environment block and the apiKeyHelper command, then rotate or revoke the
exposed key and purge both values from repository history. Update credential
loading to use the existing secret manager or a local environment variable
without storing plaintext secrets in .claude/settings.json.

In `@backend/Services/RiskEngine.cs`:
- Line 17: Update the evidence description for the Concealment entry in the
RiskEngine mapping to describe both supported paths: direct clothing/jacket
concealment and bag or open-bag concealment following shelf handling. Keep the
description operator-facing and auditable while preserving the existing score
and mapping.

In `@cloud-ai/app/events.py`:
- Around line 261-264: Update the high-value-zone product inference in the
event-processing logic around conceal_sustained and conceal_in_hv so both
conditions are calculated from the same high-value-zone frame sequence. Track or
derive sustained concealment within the high-value zone rather than combining
clip-wide concealment counts with a single zone detection, then set
hv_has_product only when that zone-local threshold is met.
- Around line 97-108: Update the detection state around the open-bag and bag
handling branches, including the related handled_then_bag logic, to track each
cue’s latest eligible shelf-detection index separately from its
highest-confidence zone. Only record or use open_bag_last_idx and bag_last_idx
when the corresponding cue meets its sustained threshold; otherwise reset or
ignore the index so an unsustained post-handling detection cannot emit
Concealment.

In `@cloud-ai/eval/ground_truth.json`:
- Around line 29-51: Add the five video assets referenced by the ground-truth
entries shoplifting_1, shoplifting_2, normal_3, shoplifting_4, and
shoplifting_testvideo under the expected clips directory, or update the
evaluation setup to reliably provide or skip them while preserving those
referenced paths.

In `@cloud-ai/eval/results_jacket_prompts.json`:
- Around line 68-76: Regenerate the stale sustained-gating result for the
normal_3 case in the events data, ensuring its open_bag-derived BagOpen event
and resulting score reflect the checked-in extractor requirement of ten
detections; do not retain the impossible BagOpen=1.0 result with only four
detections.

In `@cloud-ai/eval/run_jacket_test.py`:
- Around line 21-30: Update JACKET_PROMPTS in
cloud-ai/eval/run_jacket_test.py:21-30 so the three jacket/clothing phrases use
the canonical concealment cue and revise the stale proxy comment. Regenerate
cloud-ai/eval/results_jacket_prompts.json:2-14 from the corrected prompt map;
both sites require updates.

In `@connector/app/admin.py`:
- Around line 422-425: Update the fallback in the request-handling logic around
the single-frame branch to determine single-source status from configured or
active source metadata, not len(state.last_frames). Prefer validating the
requested camera ID against a known camera-ID mapping before reusing a frame,
and otherwise avoid returning another source’s image when a multi-source
connector temporarily has one frame.

In `@dashboard/src/app/pages/setup/setup.component.ts`:
- Around line 507-510: Update the camera-loading callback around getCamera and
selectedCamera so connectorAdminHost is no longer assigned from cam.onvifHost.
Resolve and assign the connector admin address using the existing connector
registration or configuration source, ensuring loadSnapshot targets the
connector process rather than the camera IP.
- Around line 507-512: Update the camera-loading flow around getCamera,
listZones, and loadSnapshot so callbacks capture the current selection/request
generation and ignore results from older camera selections or snapshot requests.
Increment the generation when selecting or refreshing a camera, and validate it
before assigning selectedCamera, connectorAdminHost, zones, or applying image
callbacks, ensuring only the latest camera and snapshot state can update the
editor.
🪄 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 Plus

Run ID: bc07e0fa-2c6d-4995-a7fa-9ac86bb278f6

📥 Commits

Reviewing files that changed from the base of the PR and between bb7fd1e and 3a72f1c.

📒 Files selected for processing (11)
  • .claude/settings.json
  • backend/Services/RiskEngine.cs
  • cloud-ai/app/detector.py
  • cloud-ai/app/events.py
  • cloud-ai/eval/ground_truth.json
  • cloud-ai/eval/results.json
  • cloud-ai/eval/results_jacket_prompts.json
  • cloud-ai/eval/run_eval.py
  • cloud-ai/eval/run_jacket_test.py
  • connector/app/admin.py
  • dashboard/src/app/pages/setup/setup.component.ts

Comment thread .claude/settings.json
Comment on lines +3 to +11
"ANTHROPIC_API_KEY": "aero_live_qiFbk-Et6G9qRZaElOvjjHgOWIz_O_i6Fts6W9cioFU",
"ANTHROPIC_BASE_URL": "https://capi.aerolink.lat/",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
},
"permissions": {
"allow": [],
"deny": []
},
"apiKeyHelper": "echo 'aero_live_XUjsoGcPnyw0JJ-DtO1ZfHGdoORLaO4LxR808wU2s7k'"
}
"apiKeyHelper": "echo 'aero_live_qiFbk-Et6G9qRZaElOvjjHgOWIz_O_i6Fts6W9cioFU'"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Remove and rotate the hard-coded API credential.

This live-looking key is committed twice, including a helper that prints it directly. Revoke/rotate it immediately, remove both plaintext values from repository history, and load the credential from a secret manager or local environment variable instead.

🧰 Tools
🪛 Betterleaks (1.7.0)

[high] 3-3: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 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 @.claude/settings.json around lines 3 - 11, Remove the hard-coded credential
from the settings environment block and the apiKeyHelper command, then rotate or
revoke the exposed key and purge both values from repository history. Update
credential loading to use the existing secret manager or a local environment
variable without storing plaintext secrets in .claude/settings.json.

Source: Linters/SAST tools

[nameof(AiEventType.RepeatedHandling)] = 15,
[nameof(AiEventType.BagOpen)] = 20,
[nameof(AiEventType.Concealment)] = 20,
[nameof(AiEventType.Concealment)] = 40, // directly observed act of hiding an item — strongest single camera signal

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the concealment evidence description.

A Concealment event may now originate from sustained direct clothing/jacket concealment, but the operator-facing evidence still claims a bag/open-bag followed shelf handling. Describe both supported paths so alerts remain auditable.

🤖 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 `@backend/Services/RiskEngine.cs` at line 17, Update the evidence description
for the Concealment entry in the RiskEngine mapping to describe both supported
paths: direct clothing/jacket concealment and bag or open-bag concealment
following shelf handling. Keep the description operator-facing and auditable
while preserving the existing score and mapping.

Comment thread cloud-ai/app/events.py
Comment on lines +97 to 108
open_bag_dets += 1
for z in shelf_zones:
if d.conf >= open_bag_conf:
open_bag_zone, open_bag_conf, open_bag_last_idx = z.id, d.conf, idx
if hv_zone is not None:
hv_has_bag = True

elif d.cue == "bag":
bag_dets += 1
for z in shelf_zones:
if d.conf >= bag_fallback_conf:
bag_fallback_zone, bag_fallback_conf, bag_last_idx = z.id, d.conf, idx

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use sustained cues and actual last-seen indices for ordering.

open_bag_last_idx only updates when confidence increases, so it is not necessarily the latest bag observation. More critically, it remains valid when the cue is below the sustained threshold, allowing one post-handling bag detection to emit Concealment through handled_then_bag.

Track the last eligible shelf detection independently from the highest-confidence zone, and reset/ignore each index unless its cue is sustained.

Also applies to: 202-235

🤖 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 `@cloud-ai/app/events.py` around lines 97 - 108, Update the detection state
around the open-bag and bag handling branches, including the related
handled_then_bag logic, to track each cue’s latest eligible shelf-detection
index separately from its highest-confidence zone. Only record or use
open_bag_last_idx and bag_last_idx when the corresponding cue meets its
sustained threshold; otherwise reset or ignore the index so an unsustained
post-handling detection cannot emit Concealment.

Comment thread cloud-ai/app/events.py
Comment on lines +261 to +264
# A sustained concealment cue in a high-value zone implies an item is involved.
if conceal_sustained and conceal_in_hv:
hv_has_product = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require sustained concealment within the high-value zone.

conceal_sustained counts detections across the whole clip while conceal_in_hv only requires one high-value-zone detection. Ten detections elsewhere plus one incidental high-value detection will incorrectly credit high-value product activity. Count sustained concealment per high-value zone/frame sequence instead.

🤖 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 `@cloud-ai/app/events.py` around lines 261 - 264, Update the high-value-zone
product inference in the event-processing logic around conceal_sustained and
conceal_in_hv so both conditions are calculated from the same high-value-zone
frame sequence. Track or derive sustained concealment within the high-value zone
rather than combining clip-wide concealment counts with a single zone detection,
then set hv_has_product only when that zone-local threshold is met.

Comment on lines +29 to 51
"id": "shoplifting_1",
"file": "clips/1.mp4",
"label": "shoplifting"
},
{
"id": "shoplifting_2",
"file": "clips/2.mp4",
"label": "shoplifting"
},
{
"id": "normal_002",
"file": "clips/normal_002.mp4",
"id": "normal_3",
"file": "clips/3.mp4",
"label": "normal"
},
{
"id": "shoplifting_001",
"file": "clips/shoplifting_001.mp4",
"id": "shoplifting_4",
"file": "clips/4.mp4",
"label": "shoplifting"
},
{
"id": "shoplifting_002",
"file": "clips/shoplifting_002.mp4",
"id": "shoplifting_testvideo",
"file": "clips/testvideo.mp4",
"label": "shoplifting"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import json
from pathlib import Path

manifest = Path("cloud-ai/eval/ground_truth.json")
for clip in json.loads(manifest.read_text(encoding="utf-8"))["clips"]:
    path = manifest.parent / clip["file"]
    print(f"{clip['id']}: {path}")
    assert path.is_file(), f"Missing evaluation clip: {path}"
PY

Repository: baasith6/camera-phase-1

Length of output: 342


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate eval assets =="
if [ -d cloud-ai/eval ]; then
  git ls-files cloud-ai/eval | sed -n '1,200p'
else
  echo "cloud-ai/eval not tracked"
fi

echo
echo "== manifest contents =="
cat -n cloud-ai/eval/ground_truth.json | sed -n '1,120p'

echo
echo "== files named like expected clips in repo =="
git ls-files | rg '(^|/)clips/|1\.mp4$|2\.mp4$|3\.mp4$|4\.mp4$|testvideo\.mp4$' || true

Repository: baasith6/camera-phase-1

Length of output: 1896


🌐 Web query:

GitHub baasith6 camera-phase-1 cloud-ai/eval clips ground_truth.json

💡 Result:

The specific path "camera-phase-1/cloud-ai/eval/clips/ground_truth.json" does not appear to be a standard or publicly documented file path associated with the user "baasith6" on GitHub [1]. The GitHub user "baasith6" (Abdul Baasith) is a developer whose public repositories focus on full-stack development, including projects like "RepoGraph-Protocol," agricultural AI vision backends ("agri-ai-vision-back"), and payment system integrations [1][2][3]. A search of his public repositories does not reveal a project named "camera-phase-1" or the specific file structure requested. It is possible that this path refers to: 1. A private repository belonging to the user. 2. A locally organized project structure that has not been pushed to a public GitHub repository. 3. A misremembered file path or project name. While there are many repositories on GitHub related to "camera," "ground truth," and "evaluation" (often involving computer vision, SLAM, or AI training pipelines), they are not linked to the user "baasith6" [4][5][6][7][8]. If this is a file you expect to exist in a public repository, you may want to verify the repository name or check if the repository is private.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== eval scripts mentioning clips =="
rg -n "ground_truth|clips/|VideoCapture|cv2|ground_truth\\.json|results\\.json" cloud-ai/eval -

echo
echo "== gitignored files under cloud-ai/eval or matching clip names =="
if [ -f .gitignore ]; then
  cat -n .gitignore | rg 'cloud-ai|eval|\\.mp4$|\\.MP4$|clips' || true
fi

echo
echo "== tracked all files under cloud-ai =="
git ls-files cloud-ai | sed -n '1,200p'

Repository: baasith6/camera-phase-1

Length of output: 1157


Add the evaluation clip assets referenced by ground_truth.json.

cloud-ai/eval/ground_truth.json references cloud-ai/eval/clips/{1,2,3,4}.mp4 and cloud-ai/eval/clips/testvideo.mp4, but none of these files are present. The evaluation scripts depend on these paths, so add the clips to the repo or skip/bundle them through the evaluation setup.

🤖 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 `@cloud-ai/eval/ground_truth.json` around lines 29 - 51, Add the five video
assets referenced by the ground-truth entries shoplifting_1, shoplifting_2,
normal_3, shoplifting_4, and shoplifting_testvideo under the expected clips
directory, or update the evaluation setup to reliably provide or skip them while
preserving those referenced paths.

Comment on lines +68 to +76
"open_bag": 4
},
"events": [
"HighValueZoneEntry=1.0",
"Dwell=32.0",
"RepeatedHandling=1.0",
"GroupDistraction=4.0",
"HighValueActivity=2.0",
"BagOpen=1.0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Regenerate this stale sustained-gating result.

normal_3 has only four open_bag detections, but still reports BagOpen=1.0. The current extractor requires ten detections and clears the bag zone otherwise, so this event—and the resulting score of 70—cannot be produced by the checked-in logic.

🤖 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 `@cloud-ai/eval/results_jacket_prompts.json` around lines 68 - 76, Regenerate
the stale sustained-gating result for the normal_3 case in the events data,
ensuring its open_bag-derived BagOpen event and resulting score reflect the
checked-in extractor requirement of ten detections; do not retain the impossible
BagOpen=1.0 result with only four detections.

Comment on lines +21 to +30
JACKET_PROMPTS = {
**DEFAULT_YOLOE_PROMPTS,
# Clothing-concealment cues (proxy onto open_bag so Concealment/BagOpen logic fires).
"person hiding item inside jacket": "open_bag",
"person putting object under clothing": "open_bag",
"hand inside jacket": "open_bag",
# Extra holding phrasings for small items / clothing items.
"person holding a bottle": "product_in_hand",
"person holding clothes": "product_in_hand",
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep jacket prompts on the canonical concealment cue. The jacket-specific map overrides the new detector behavior back to open_bag, so direct concealment is evaluated through the obsolete bag proxy instead of the new direct-evidence path.

  • cloud-ai/eval/run_jacket_test.py#L21-L30: map the three jacket/clothing phrases to concealment and update the stale proxy wording.
  • cloud-ai/eval/results_jacket_prompts.json#L2-L14: regenerate the artifact from the corrected prompt map.
📍 Affects 2 files
  • cloud-ai/eval/run_jacket_test.py#L21-L30 (this comment)
  • cloud-ai/eval/results_jacket_prompts.json#L2-L14
🤖 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 `@cloud-ai/eval/run_jacket_test.py` around lines 21 - 30, Update JACKET_PROMPTS
in cloud-ai/eval/run_jacket_test.py:21-30 so the three jacket/clothing phrases
use the canonical concealment cue and revise the stale proxy comment. Regenerate
cloud-ai/eval/results_jacket_prompts.json:2-14 from the corrected prompt map;
both sites require updates.

Comment thread connector/app/admin.py
Comment on lines +422 to +425
if not frame and len(state.last_frames) == 1:
# Single-source connector: tolerate a camera-id mismatch between the
# dashboard GUID and the locally configured source.
frame = next(iter(state.last_frames.values()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not infer a single-source connector from last_frames.

A multi-source connector can temporarily contain only camera A’s first frame. A request for camera B then receives A’s image, allowing zones for B to be drawn and saved against the wrong scene. Restrict this fallback using configured/active source count (and preferably a known camera-ID mapping), not currently available frames.

🤖 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 `@connector/app/admin.py` around lines 422 - 425, Update the fallback in the
request-handling logic around the single-frame branch to determine single-source
status from configured or active source metadata, not len(state.last_frames).
Prefer validating the requested camera ID against a known camera-ID mapping
before reusing a frame, and otherwise avoid returning another source’s image
when a multi-source connector temporarily has one frame.

Comment on lines 507 to +510
this.api.getCamera(id).subscribe(cam => {
this.selectedCamera = cam;
if (cam.onvifHost) this.connectorAdminHost = cam.onvifHost;
this.loadSnapshot();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not use the ONVIF camera IP as the connector-admin host.

cam.onvifHost is the camera address, but /snapshot is served by the connector process on port 8099. This redirects snapshot requests to http://<camera-ip>:8099, where the connector endpoint is normally unavailable. Resolve the connector’s admin address from connector registration/configuration instead.

🤖 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 `@dashboard/src/app/pages/setup/setup.component.ts` around lines 507 - 510,
Update the camera-loading callback around getCamera and selectedCamera so
connectorAdminHost is no longer assigned from cam.onvifHost. Resolve and assign
the connector admin address using the existing connector registration or
configuration source, ensuring loadSnapshot targets the connector process rather
than the camera IP.

Comment on lines 507 to 512
this.api.getCamera(id).subscribe(cam => {
this.selectedCamera = cam;
if (cam.onvifHost) this.connectorAdminHost = cam.onvifHost;
this.loadSnapshot();
});
this.api.listZones(id).subscribe((z) => { this.zones = z; setTimeout(() => this.redraw()); });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Ignore stale camera and snapshot callbacks.

Selecting A then B, or refreshing twice, permits an older getCamera, listZones, or image callback to overwrite B’s state. The editor can consequently persist B’s zones using A’s frame. Track a selection/request generation and apply each callback only when it still matches the current camera and latest snapshot request.

Also applies to: 524-533

🤖 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 `@dashboard/src/app/pages/setup/setup.component.ts` around lines 507 - 512,
Update the camera-loading flow around getCamera, listZones, and loadSnapshot so
callbacks capture the current selection/request generation and ignore results
from older camera selections or snapshot requests. Increment the generation when
selecting or refreshing a camera, and validate it before assigning
selectedCamera, connectorAdminHost, zones, or applying image callbacks, ensuring
only the latest camera and snapshot state can update the editor.

@baasith6
baasith6 merged commit 09451e1 into main Jul 29, 2026
2 of 5 checks passed
This was referenced Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants