Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
56c191f
feat(assessment): Implement assessment evaluation orchestration service
vprashrex Mar 31, 2026
b837b12
feat(feature-flags): add feature flag APIs and assessment access gating
vprashrex Apr 27, 2026
bce0944
chore(migrations): renumber assessment migration to revision 55
vprashrex Apr 27, 2026
435d8bc
chore(migrations): remove superseded 055 feature-flag migration file
vprashrex Apr 27, 2026
f2e512e
Refactor assessment evaluation handling to use dedicated AssessmentRu…
vprashrex Apr 27, 2026
0935b98
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex Apr 27, 2026
0c04860
feat(assessment): Enhance image handling with MIME type detection and…
vprashrex Apr 27, 2026
0a66f3c
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex Apr 27, 2026
cd946ba
refactor: Remove unused feature flag imports from models
vprashrex Apr 27, 2026
5758167
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex Apr 27, 2026
360edee
refactor: Clean up logger statements and improve code formatting in a…
vprashrex Apr 27, 2026
e0af9d6
refactor: Remove unused feature flag dependency from assessment routes
vprashrex Apr 27, 2026
1330333
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex Apr 27, 2026
ee920b4
feat: Add feature flag dependency to assessment routes and import fea…
vprashrex Apr 27, 2026
593eea3
refactor(tests): Update cron job tests to use AsyncMock for asynchron…
vprashrex Apr 27, 2026
803b848
feat: Implement feature flag CRUD operations and update related docum…
vprashrex Apr 28, 2026
147ff6c
feat(assessment): Add new API documentation and enhance assessment fu…
vprashrex Apr 28, 2026
6e62234
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex Apr 28, 2026
3e44365
Merge branch 'main' into feature/assessment
vprashrex Apr 28, 2026
b0efcac
refactor: Organize imports across multiple files for improved readabi…
vprashrex Apr 28, 2026
dcb4141
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex Apr 28, 2026
928cad3
test: Add unit tests for assessment utilities, mappers, parsing, proc…
vprashrex Apr 29, 2026
2f6cdb8
feat: Enhance assessment processing and export functionality with imp…
vprashrex Apr 29, 2026
bc55665
feat: Refactor assessment processing and improve callback payload han…
vprashrex Apr 29, 2026
77235a4
feat: Enhance dataset handling by rejecting legacy Excel format (.xls…
vprashrex Apr 29, 2026
148b619
feat: Add tests for Excel dataset handling, including parsing and err…
vprashrex Apr 29, 2026
f2d7c24
Add comprehensive tests for assessment functionality
vprashrex Apr 29, 2026
bc07542
refactor: Simplify patch statements in assessment tests for improved …
vprashrex Apr 29, 2026
a9c9d32
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex Apr 29, 2026
912cd4c
feat: Add feature flag model and corresponding API tests
vprashrex Apr 29, 2026
956e39e
refactor: Remove assessment event broadcasting and related code from …
vprashrex Apr 30, 2026
535a65a
refactor: Remove assessment event broadcasting and related code from …
vprashrex Apr 30, 2026
91c011e
Refactor assessment tests and routes for improved structure and clarity
vprashrex May 3, 2026
a54e105
refactor: Clean up comments and remove unused imports in assessment p…
vprashrex May 3, 2026
fc2180c
refactor: Update map_kaapi_to_openai_params to accept session and kaa…
vprashrex May 3, 2026
abe55e9
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex May 3, 2026
bd01a7f
feat: Add feature flag support with default value and trigger for pro…
vprashrex May 4, 2026
ebc6ead
Refactor config and document CRUD operations to enforce tagging rules
vprashrex May 4, 2026
b0a3633
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex May 4, 2026
4a4b9c6
refactor: Remove tag handling from document CRUD operations and relat…
vprashrex May 4, 2026
0a19fec
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex May 4, 2026
b4fab33
feat: Add langfuse_dataset_id parameter to create_assessment_dataset …
vprashrex May 4, 2026
7c82585
refactor: Update type hints and reorganize imports in documents.py fo…
vprashrex May 4, 2026
54f856d
refactor: Reorganize imports and clean up exception handling in docum…
vprashrex May 4, 2026
e011850
refactor: Update tag parameter formatting in config routes for consis…
vprashrex May 4, 2026
1e19a28
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex May 4, 2026
6086a0b
Merge branch 'main' into feature/assessment
vprashrex May 4, 2026
74d4034
Merge branch 'feature/assessment' into feature/feature-flag
Ayush8923 May 4, 2026
f9c1a98
refactor: Remove DEFAULT feature flag and update related database ope…
vprashrex May 4, 2026
50e5c09
refactor: Remove commented-out sections and reorganize import stateme…
vprashrex May 5, 2026
e87bcc3
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex May 5, 2026
fbcc36d
refactor: Standardize tag parameter usage across config and version e…
vprashrex May 5, 2026
4757c05
refactor: Simplify description formatting for ConfigUpdate tag field
vprashrex May 5, 2026
25268f3
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex May 5, 2026
30d5909
feat: Add trigger and function to seed default feature flag on projec…
vprashrex May 5, 2026
81bac63
feat: Enhance assessment CRUD operations with error handling and type…
vprashrex May 5, 2026
6feb82a
refactor: Improve code readability by formatting long lines in assess…
vprashrex May 5, 2026
7fbf109
refactor: Change status field type from Literal to str for flexibilit…
vprashrex May 5, 2026
f9e40a4
fix: Import _load_dataset_rows in _load_dataset_rows_for_run for prop…
vprashrex May 5, 2026
5d3e2a1
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex May 5, 2026
ae4376d
feat: Implement feature flag CRUD operations with project organizatio…
vprashrex May 5, 2026
9a83031
refactor: Rename mapping functions for clarity and update related usages
vprashrex May 5, 2026
7e0b597
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex May 5, 2026
f388721
Merge branch 'main' into feature/feature-flag
vprashrex May 6, 2026
f68c784
refactor: Improve code readability by restructuring conditional state…
vprashrex May 6, 2026
7d94a2f
Merge branch 'main' into feature/assessment
vprashrex May 6, 2026
e37e087
Merge branch 'feature/assessment' into feature/feature-flag
vprashrex May 6, 2026
96404ea
Merge branch 'main' into feature/assessment
AkhileshNegi May 6, 2026
5c39c6d
Merge branch 'feature/assessment' into feature/feature-flag
Ayush8923 May 6, 2026
48fdba4
refactor: simplify error message in create_assessment_dataset function
vprashrex May 6, 2026
a77ee2b
Merge branch 'feature/assessment' into feature/feature-flag
Ayush8923 May 6, 2026
c940bb5
fix: restore default cron interval to 5 minutes from environment vari…
vprashrex May 6, 2026
6bae1fb
Merge branch 'main' into feature/feature-flag
vprashrex May 6, 2026
0ca4009
test: update feature flag tests to reflect auto-seeding behavior
vprashrex May 6, 2026
809a98c
fix: update feature flag tests to reflect auto-seeding behavior and i…
vprashrex May 6, 2026
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
156 changes: 156 additions & 0 deletions backend/app/alembic/versions/057_create_feature_flag_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"""create feature flag table

Revision ID: 057
Revises: 056
Create Date: 2026-04-22 12:00:00.000000

"""

import sqlalchemy as sa
from alembic import op

from app.core.feature_flags.constants import FeatureFlag as FeatureFlagKeyEnum
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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n '^from app\.|^import app\.' backend/app/alembic/versions
fd '057_create_feature_flag_table\.py$' backend/app/alembic/versions --exec sed -n '1,140p' {}

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 3960


🏁 Script executed:

#!/bin/bash
# Get complete file with line numbers
fd '057_create_feature_flag_table\.py$' backend/app/alembic/versions --exec wc -l {} \;

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 141


🏁 Script executed:

#!/bin/bash
# Read complete file
fd '057_create_feature_flag_table\.py$' backend/app/alembic/versions --exec cat -n {} \;

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 5604


Add return type hints and freeze enum values in the migration.

This migration imports a runtime application symbol (app.core.feature_flags.constants.FeatureFlag) and uses it in SQL (lines 110, 128). Future changes to the enum's location, values, or definition will break database bootstrapping from this historical revision. Alembic migrations must be self-contained with frozen values.

Additionally, both upgrade() (line 21) and downgrade() (line 148) functions lack return type annotations, violating the type hints requirement.

Suggested fix
-from app.core.feature_flags.constants import FeatureFlag as FeatureFlagKeyEnum

+DEFAULT_FEATURE_FLAG_KEY = "ASSESSMENT"

-def upgrade():
+def upgrade() -> None:

     op.execute(
         sa.text(
             f"""
             INSERT INTO feature_flag
                 (key, organization_id, project_id, enabled, inserted_at, updated_at)
-            SELECT '{FeatureFlagKeyEnum.ASSESSMENT.value}'::featureflagkey,
+            SELECT '{DEFAULT_FEATURE_FLAG_KEY}'::featureflagkey,
                    p.organization_id, p.id, FALSE, NOW(), NOW()
             FROM project p
             """
         )
     )

     op.execute(
         sa.text(
             f"""
             CREATE OR REPLACE FUNCTION seed_default_feature_flag()
             RETURNS trigger
             LANGUAGE plpgsql
             AS $$
             BEGIN
                 INSERT INTO feature_flag
                     (key, organization_id, project_id, enabled, inserted_at, updated_at)
                 VALUES
-                    ('{FeatureFlagKeyEnum.ASSESSMENT.value}'::featureflagkey,
+                    ('{DEFAULT_FEATURE_FLAG_KEY}'::featureflagkey,
                      NEW.organization_id, NEW.id, FALSE, NOW(), NOW());
                 RETURN NEW;
             END;
             $$;
             """
         )
     )

-def downgrade():
+def downgrade() -> None:
🤖 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/app/alembic/versions/057_create_feature_flag_table.py` at line 12,
The migration currently imports a runtime enum FeatureFlag and leaves upgrade()
and downgrade() without return type hints; replace the import of
app.core.feature_flags.constants.FeatureFlag with frozen, literal enum values
(e.g., a hard-coded tuple/list of the exact string names/values used in the SQL
DDL and insert statements) so the migration is self-contained and will not break
if the application enum changes, and add explicit return type annotations to
both upgrade() -> None and downgrade() -> None; update any references in the
migration (e.g., inside CREATE TYPE or INSERT INTO feature_flag, and the symbols
FeatureFlag used at lines around 110 and 128) to use the frozen literals instead
of the runtime enum.


# revision identifiers, used by Alembic.
revision = "057"
down_revision = "056"
branch_labels = None
depends_on = None


def upgrade():
Comment thread
vprashrex marked this conversation as resolved.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add explicit -> None to the migration entrypoints.

These are new Python functions, and the repo guideline requires explicit parameter and return typing.

Suggested fix
-def upgrade():
+def upgrade() -> None:
...
-def downgrade():
+def downgrade() -> None:

As per coding guidelines, "Always add type hints to all function parameters and return values in Python code."

Also applies to: 148-148

🤖 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/app/alembic/versions/057_create_feature_flag_table.py` at line 21,
Add explicit return type annotations for the migration entrypoints by changing
the function signatures for upgrade and downgrade to include "-> None"; update
the def upgrade() and def downgrade() functions in this migration module to be
def upgrade() -> None: and def downgrade() -> None: so they comply with the
repository's requirement for explicit function return typing.

op.create_table(
"feature_flag",
sa.Column(
"id",
sa.Integer(),
nullable=False,
comment="Unique identifier for feature flag",
),
sa.Column(
"key",
Comment thread
vprashrex marked this conversation as resolved.
sa.Enum("ASSESSMENT", name="featureflagkey"),
nullable=False,
comment="Feature flag key",
),
sa.Column(
"organization_id",
sa.Integer(),
nullable=False,
comment="Organization scope for this feature flag",
),
sa.Column(
"project_id",
sa.Integer(),
nullable=False,
comment="Project scope for this feature flag",
),
sa.Column(
"enabled",
sa.Boolean(),
nullable=False,
comment="Whether the feature flag is enabled",
),
sa.Column(
"inserted_at",
sa.DateTime(),
nullable=False,
comment="Timestamp when the flag row was created",
),
sa.Column(
"updated_at",
sa.DateTime(),
nullable=False,
comment="Timestamp when the flag row was last updated",
),
sa.ForeignKeyConstraint(
["organization_id"],
["organization.id"],
name="fk_feature_flag_organization_id",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["project_id"],
["project.id"],
name="fk_feature_flag_project_id",
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(
op.f("ix_feature_flag_key"),
"feature_flag",
["key"],
unique=False,
)
op.create_index(
op.f("ix_feature_flag_organization_id"),
"feature_flag",
["organization_id"],
unique=False,
Comment on lines +86 to +90
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

need similar index for project_id

)
op.create_index(
op.f("ix_feature_flag_project_id"),
"feature_flag",
["project_id"],
unique=False,
)
op.create_index(
"uq_feature_flag_key_org_project",
"feature_flag",
["key", "organization_id", "project_id"],
unique=True,
)

op.execute(
sa.text(
f"""
INSERT INTO feature_flag
(key, organization_id, project_id, enabled, inserted_at, updated_at)
SELECT '{FeatureFlagKeyEnum.ASSESSMENT.value}'::featureflagkey,
p.organization_id, p.id, FALSE, NOW(), NOW()
FROM project p
"""
)
)

op.execute(
sa.text(
f"""
CREATE OR REPLACE FUNCTION seed_default_feature_flag()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO feature_flag
(key, organization_id, project_id, enabled, inserted_at, updated_at)
VALUES
('{FeatureFlagKeyEnum.ASSESSMENT.value}'::featureflagkey,
NEW.organization_id, NEW.id, FALSE, NOW(), NOW());
RETURN NEW;
END;
$$;
"""
)
)
op.execute(
sa.text(
"""
CREATE TRIGGER trg_seed_default_feature_flag
AFTER INSERT ON project
FOR EACH ROW
EXECUTE FUNCTION seed_default_feature_flag();
"""
)
)


def downgrade():
op.execute("DROP TRIGGER IF EXISTS trg_seed_default_feature_flag ON project")
op.execute("DROP FUNCTION IF EXISTS seed_default_feature_flag()")
op.drop_index("uq_feature_flag_key_org_project", table_name="feature_flag")
op.drop_index(op.f("ix_feature_flag_project_id"), table_name="feature_flag")
op.drop_index(op.f("ix_feature_flag_organization_id"), table_name="feature_flag")
op.drop_index(op.f("ix_feature_flag_key"), table_name="feature_flag")
op.drop_table("feature_flag")
sa.Enum(name="featureflagkey").drop(op.get_bind())
16 changes: 16 additions & 0 deletions backend/app/api/docs/features/create_flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Create a project-scoped feature flag. Superuser only.

Required payload fields:
- `key`
- `organization_id`
- `project_id`
- `enabled`
- Currently supported key(s): `ASSESSMENT`.

Validation:
- `organization_id` must exist.
- `project_id` must exist and belong to `organization_id`.

Behavior:
- A flag is unique by (`key`, `organization_id`, `project_id`).
- Returns `409` if the same flag already exists.
14 changes: 14 additions & 0 deletions backend/app/api/docs/features/delete_flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Delete a project-scoped feature flag. Superuser only.

Required payload fields:
- `key`
- `organization_id`
- `project_id`

Validation:
- `organization_id` must exist.
- `project_id` must exist and belong to `organization_id`.

Returns:
- `{"deleted": true}` on success.
- `404` if the target flag does not exist.
12 changes: 12 additions & 0 deletions backend/app/api/docs/features/list_flags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
List feature flag records. Superuser only.

Query parameters are optional:
- `key`
- `organization_id`
- `project_id`

`key` matches stored feature flag values (currently `ASSESSMENT`).

Validation:
- When `project_id` is provided, `organization_id` is required.
- If both are provided, `project_id` must belong to `organization_id`.
15 changes: 15 additions & 0 deletions backend/app/api/docs/features/update_flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Update a project-scoped feature flag. Superuser only.

Required payload fields:
- `key`
- `organization_id`
- `project_id`
- `enabled`
- Currently supported key(s): `ASSESSMENT`.

Validation:
- `organization_id` must exist.
- `project_id` must exist and belong to `organization_id`.

Returns:
- `404` if the target flag does not exist.
5 changes: 5 additions & 0 deletions backend/app/api/docs/users/get_me.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Return the current authenticated user's profile.

All standard user profile fields are always returned.

`features` is returned only when both organization and project are available for the user; otherwise it is `[]`.
18 changes: 9 additions & 9 deletions backend/app/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from app.api.routes import (
api_keys,
assessment as assessment_routes,
assistants,
auth,
collection_job,
Expand All @@ -12,6 +13,7 @@
doc_transformation_job,
documents,
evaluations,
features,
fine_tuning,
languages,
llm,
Expand All @@ -30,27 +32,29 @@
users,
utils,
)
from app.api.routes import (
assessment as assessment_routes,
)
from app.core.config import settings

api_router = APIRouter()
api_router.include_router(api_keys.router)
api_router.include_router(assessment_routes.router)
api_router.include_router(assistants.router)
api_router.include_router(collections.router)
api_router.include_router(auth.router)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

api_router.include_router(auth.router) already exist in line:49

can you make all these alphabetical to ensure same is not repeated by other developers

api_router.include_router(collection_job.router)
api_router.include_router(collections.router)
api_router.include_router(config.router)
api_router.include_router(credentials.router)
api_router.include_router(cron.router)
api_router.include_router(doc_transformation_job.router)
api_router.include_router(documents.router)
api_router.include_router(auth.router)
api_router.include_router(evaluations.router)
api_router.include_router(features.router)
api_router.include_router(fine_tuning.router)
api_router.include_router(languages.router)
api_router.include_router(llm.router)
api_router.include_router(llm_chain.router)
api_router.include_router(login.router)
api_router.include_router(model_config.router)
api_router.include_router(model_evaluation.router)
api_router.include_router(onboarding.router)
api_router.include_router(openai_conversation.router)
api_router.include_router(organization.router)
Expand All @@ -60,10 +64,6 @@
api_router.include_router(user_project.router)
api_router.include_router(users.router)
api_router.include_router(utils.router)
api_router.include_router(fine_tuning.router)
api_router.include_router(model_evaluation.router)
api_router.include_router(model_config.router)
api_router.include_router(assessment_routes.router)

if settings.ENVIRONMENT in ["development", "testing"]:
api_router.include_router(private.router)
50 changes: 44 additions & 6 deletions backend/app/api/permissions.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from enum import Enum
from typing import Annotated
from fastapi import Depends, HTTPException
from enum import StrEnum

from fastapi import HTTPException
from sqlmodel import Session

from app.models import AuthContext
from app.api.deps import AuthContextDep, SessionDep
from app.core.feature_flags import FeatureFlag
from app.models import AuthContext


class Permission(str, Enum):
class Permission(StrEnum):
"""Permission types for authorization checks"""

SUPERUSER = "require_superuser"
Expand All @@ -18,7 +19,7 @@ class Permission(str, Enum):
def has_permission(
auth_context: AuthContext,
permission: Permission,
session: Session | None = None,
_session: Session | None = None,
) -> bool:
"""
Check if the auth_context has the specified permission.
Expand Down Expand Up @@ -68,3 +69,40 @@ def permission_checker(
)

return permission_checker


def require_feature(flag: FeatureFlag):
"""Dependency factory that gates a route behind a feature flag.

Returns 403 when the flag is disabled for the caller's org/project,
so callers can explicitly handle feature access denial.

Usage::

router = APIRouter(
Comment on lines +75 to +82
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

cleanup formatting a bit

dependencies=[Depends(require_feature(FeatureFlag.ASSESSMENT))]
)
"""

def _check_with_session(auth_context: AuthContextDep, session: SessionDep):
from app.core.feature_flags import is_enabled

org_id = auth_context.organization.id if auth_context.organization else None
project_id = auth_context.project.id if auth_context.project else None

if (
org_id is None
or project_id is None
or not is_enabled(
session=session,
flag=flag.value,
organization_id=org_id,
project_id=project_id,
)
):
raise HTTPException(
status_code=403,
detail=f"Feature '{flag.value}' is not enabled for this org or project.",
)
Comment on lines +87 to +106
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

Distinguish "missing context" from "feature disabled" in the 403 response.

When org_id or project_id is None, the handler returns the same "Feature 'X' is not enabled..." message used when the flag is genuinely disabled. This is misleading for callers debugging an auth/context issue (e.g., a token without project scope) — they will think the feature is off when in fact the request lacks the required scope. Consider routing missing org/project through require_permission(REQUIRE_ORGANIZATION/REQUIRE_PROJECT) (or returning a distinct message), and only emitting the "not enabled" response when is_enabled(...) returns falsy.

🔧 Suggested split
-        if (
-            org_id is None
-            or project_id is None
-            or not is_enabled(
-                session=session,
-                flag=flag.value,
-                organization_id=org_id,
-                project_id=project_id,
-            )
-        ):
+        if org_id is None or project_id is None:
+            raise HTTPException(
+                status_code=403,
+                detail="Organization and project context are required to evaluate feature flags.",
+            )
+
+        if not is_enabled(
+            session=session,
+            flag=flag.value,
+            organization_id=org_id,
+            project_id=project_id,
+        ):
             raise HTTPException(
                 status_code=403,
                 detail=f"Feature '{flag.value}' is not enabled for this org or project.",
             )
🤖 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/app/api/permissions.py` around lines 87 - 106, The
_check_with_session function conflates missing org/project context with a
disabled feature; change it so that if auth_context.organization or
auth_context.project is missing (org_id or project_id is None) you route to the
appropriate permission requirement (e.g., invoke
require_permission(REQUIRE_ORGANIZATION/REQUIRE_PROJECT) or raise a distinct
HTTPException message like "Missing organization/project scope") instead of the
generic disabled message, and only call is_enabled(...) and raise the existing
HTTPException("Feature 'X' is not enabled...") when both ids exist and
is_enabled(...) returns false; update the HTTPException branches around
is_enabled and the org/project checks accordingly.


return _check_with_session
Comment on lines +74 to +108
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add return type annotations per coding guidelines.

Both require_feature and the inner _check_with_session are missing return type hints. The inner check returns None and the factory returns the dependency callable.

✏️ Proposed annotations
-def require_feature(flag: FeatureFlag):
+def require_feature(flag: FeatureFlag) -> Callable[[AuthContextDep, SessionDep], None]:
     """Dependency factory that gates a route behind a feature flag.
     ...
     """

-    def _check_with_session(auth_context: AuthContextDep, session: SessionDep):
+    def _check_with_session(auth_context: AuthContextDep, session: SessionDep) -> None:
         from app.core.feature_flags import is_enabled

Add from collections.abc import Callable to imports.

As per coding guidelines: "Always add type hints to all function parameters and return values in Python code".

🤖 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/app/api/permissions.py` around lines 74 - 108, Add explicit return
type hints for both require_feature and its inner function _check_with_session
and import Callable from collections.abc; specifically annotate
_check_with_session to return None (it raises or returns None) and annotate
require_feature to return a Callable that accepts the dependency parameters
(AuthContextDep, SessionDep) and returns None (e.g., Callable[[AuthContextDep,
SessionDep], None]) so type checkers know the factory yields a dependency
callable.

12 changes: 8 additions & 4 deletions backend/app/api/routes/assessment/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
"""Main router for assessment API routes."""

from fastapi import APIRouter
from fastapi import APIRouter, Depends

from app.api.permissions import require_feature
from app.api.routes.assessment import assessments, datasets, runs
from app.core.feature_flags import FeatureFlag

router = APIRouter(prefix="/assessment", tags=["Assessment"])
router = APIRouter(
prefix="/assessment",
tags=["Assessment"],
dependencies=[Depends(require_feature(FeatureFlag.ASSESSMENT))],
)

router.include_router(datasets.router)
router.include_router(assessments.router)
Expand Down
2 changes: 1 addition & 1 deletion backend/app/api/routes/cron.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async def evaluation_cron_job(
f"[evaluation_cron_job] Assessment polling failed: {ae}",
exc_info=True,
)
result["assessment_error"] = str(ae)
result["assessment_error"] = "Assessment polling failed"

logger.info(
f"[evaluation_cron_job] Completed: "
Expand Down
Loading
Loading