Skip to content
Merged
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
1 change: 1 addition & 0 deletions migrations/000017_create_detection_feedback.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS detection_feedback;
31 changes: 31 additions & 0 deletions migrations/000017_create_detection_feedback.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
CREATE TABLE IF NOT EXISTS detection_feedback (
feedback_id SERIAL PRIMARY KEY,
detection_run_id INTEGER NOT NULL REFERENCES occupancy_observations(observation_id) ON DELETE CASCADE,
created_by_user_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL,
rating VARCHAR(32) NOT NULL CHECK (rating IN ('correct', 'partially_correct', 'incorrect')),
expected_occupied_count INTEGER CHECK (expected_occupied_count IS NULL OR expected_occupied_count >= 0),
expected_free_count INTEGER CHECK (expected_free_count IS NULL OR expected_free_count >= 0),
error_type VARCHAR(64) CHECK (
error_type IS NULL OR error_type IN (
'false_positive_car',
'false_negative_car',
'wrong_zone_assignment',
'bad_lighting',
'bad_camera_angle',
'calibration_problem',
'other'
)
),
comment TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE
);

CREATE INDEX IF NOT EXISTS idx_detection_feedback_detection_run_id
ON detection_feedback(detection_run_id);

CREATE INDEX IF NOT EXISTS idx_detection_feedback_created_by_user_id
ON detection_feedback(created_by_user_id);

CREATE INDEX IF NOT EXISTS idx_detection_feedback_created_at
ON detection_feedback(created_at DESC);
31 changes: 31 additions & 0 deletions migrations/up/000017_create_detection_feedback.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
CREATE TABLE IF NOT EXISTS detection_feedback (
feedback_id SERIAL PRIMARY KEY,
detection_run_id INTEGER NOT NULL REFERENCES occupancy_observations(observation_id) ON DELETE CASCADE,
created_by_user_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL,
rating VARCHAR(32) NOT NULL CHECK (rating IN ('correct', 'partially_correct', 'incorrect')),
expected_occupied_count INTEGER CHECK (expected_occupied_count IS NULL OR expected_occupied_count >= 0),
expected_free_count INTEGER CHECK (expected_free_count IS NULL OR expected_free_count >= 0),
error_type VARCHAR(64) CHECK (
error_type IS NULL OR error_type IN (
'false_positive_car',
'false_negative_car',
'wrong_zone_assignment',
'bad_lighting',
'bad_camera_angle',
'calibration_problem',
'other'
)
),
comment TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE
);

CREATE INDEX IF NOT EXISTS idx_detection_feedback_detection_run_id
ON detection_feedback(detection_run_id);

CREATE INDEX IF NOT EXISTS idx_detection_feedback_created_by_user_id
ON detection_feedback(created_by_user_id);

CREATE INDEX IF NOT EXISTS idx_detection_feedback_created_at
ON detection_feedback(created_at DESC);
58 changes: 58 additions & 0 deletions src/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,64 @@ def __repr__(self) -> str:
return f"<OccupancyObservation id={self.observation_id} zone_id={self.zone_id}>"


# ---------------------------------------------------------------------------
# Detection Feedback
# ---------------------------------------------------------------------------

class DetectionFeedback(Base):
__tablename__ = "detection_feedback"

feedback_id = Column(Integer, primary_key=True, autoincrement=True)
detection_run_id = Column(
Integer,
ForeignKey("occupancy_observations.observation_id", ondelete="CASCADE"),
nullable=False,
)
created_by_user_id = Column(Integer, ForeignKey("users.user_id", ondelete="SET NULL"), nullable=True)
rating = Column(String(32), nullable=False)
expected_occupied_count = Column(Integer, nullable=True)
expected_free_count = Column(Integer, nullable=True)
error_type = Column(String(64), nullable=True)
comment = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), default=_now)
updated_at = Column(DateTime(timezone=True), nullable=True)

detection_run = relationship("OccupancyObservation", foreign_keys=[detection_run_id])
created_by = relationship("User", foreign_keys=[created_by_user_id])

__table_args__ = (
CheckConstraint(
"rating IN ('correct', 'partially_correct', 'incorrect')",
name="ck_detection_feedback_rating",
),
CheckConstraint(
"expected_occupied_count IS NULL OR expected_occupied_count >= 0",
name="ck_detection_feedback_expected_occupied_non_negative",
),
CheckConstraint(
"expected_free_count IS NULL OR expected_free_count >= 0",
name="ck_detection_feedback_expected_free_non_negative",
),
CheckConstraint(
"""
error_type IS NULL OR error_type IN (
'false_positive_car',
'false_negative_car',
'wrong_zone_assignment',
'bad_lighting',
'bad_camera_angle',
'calibration_problem',
'other'
)
""",
name="ck_detection_feedback_error_type",
),
)

def __repr__(self) -> str:
return f"<DetectionFeedback id={self.feedback_id} run_id={self.detection_run_id}>"


# ---------------------------------------------------------------------------
# Forecasts
# ---------------------------------------------------------------------------
Expand Down
13 changes: 13 additions & 0 deletions src/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ def decode_access_token(token: str) -> int:
"admin.system.manage",
"admin.monitoring.view",
"admin.analytics.view",
"admin.forecasts.view",
"analytics.view",
"analytics.feedback.create",
"analytics.feedback.view",
"cameras.view",
"cameras.create",
"cameras.update",
Expand Down Expand Up @@ -141,6 +145,8 @@ def decode_access_token(token: str) -> int:
"partner_members.update",
"partner_members.disable",
"partner_access.manage",
"analytics.view",
"analytics.feedback.create",
}),
"partner_admin": frozenset({
"sources.view",
Expand All @@ -157,6 +163,8 @@ def decode_access_token(token: str) -> int:
"partner_members.update",
"partner_members.disable",
"partner_access.manage",
"analytics.view",
"analytics.feedback.create",
}),
"partner_manager": frozenset({
"sources.view",
Expand All @@ -167,16 +175,21 @@ def decode_access_token(token: str) -> int:
"zones.create",
"zones.update",
"partner_members.view",
"analytics.view",
"analytics.feedback.create",
}),
"partner_analyst": frozenset({
"sources.view",
"cameras.view",
"zones.view",
"analytics.view",
"analytics.feedback.create",
}),
"partner_viewer": frozenset({
"sources.view",
"cameras.view",
"zones.view",
"analytics.view",
}),
}

Expand Down
3 changes: 2 additions & 1 deletion src/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import FastAPI, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware

from .routers import auth, users, partners, cameras, zones, admin, sources, occupancy, forecasts, routing, weather
from .routers import auth, users, partners, cameras, zones, admin, analytics, sources, occupancy, forecasts, routing, weather
from .database import engine
from .db_models import Base

Expand Down Expand Up @@ -50,6 +50,7 @@
app.include_router(cameras.router, prefix=PREFIX)
app.include_router(zones.router, prefix=PREFIX)
app.include_router(admin.router, prefix=PREFIX)
app.include_router(analytics.router,prefix=PREFIX)
app.include_router(sources.router, prefix=PREFIX)
app.include_router(occupancy.router,prefix=PREFIX)
app.include_router(forecasts.router,prefix=PREFIX)
Expand Down
Loading
Loading