gapguide-api / apps /analysis /views.py
arifRB's picture
Deploy GapGuide backend (Docker)
ffd36e0 verified
Raw
History Blame Contribute Delete
3.98 kB
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema
from rest_framework import permissions, status
from rest_framework.response import Response
from rest_framework.views import APIView
from apps.roles.models import Role, UserTargetRole
from .services import (
DEFAULT_LIMIT_PER_SKILL,
MAX_LIMIT_PER_SKILL,
compute_gap_report,
compute_recommendations,
suggest_roles,
)
_ROLE_ID_PARAM = OpenApiParameter(
name='role_id',
type=int,
location=OpenApiParameter.QUERY,
required=False,
description='Role to analyze. Defaults to the user\'s active target role.',
)
def _resolve_role(request):
"""Return (role, error_response). role_id query param overrides active target."""
role_id = (
request.query_params.get('role_id')
or request.query_params.get('role')
)
if role_id:
try:
role = Role.objects.get(id=role_id, is_active=True)
except (Role.DoesNotExist, ValueError):
return None, Response(
{'detail': 'Role not found or inactive.'},
status=status.HTTP_404_NOT_FOUND,
)
return role, None
try:
target = UserTargetRole.objects.select_related('role').get(
user=request.user, is_active=True
)
except UserTargetRole.DoesNotExist:
return None, Response(
{'detail': 'No active target role. Select a target role first.'},
status=status.HTTP_400_BAD_REQUEST,
)
return target.role, None
@extend_schema(
parameters=[_ROLE_ID_PARAM],
responses=OpenApiTypes.OBJECT,
description='Weighted competency-fulfillment gap report for the user against a role.',
)
class GapAnalysisView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
role, err = _resolve_role(request)
if err is not None:
return err
report = compute_gap_report(request.user, role)
return Response(report.to_dict(), status=status.HTTP_200_OK)
@extend_schema(
parameters=[
_ROLE_ID_PARAM,
OpenApiParameter(
name='limit',
type=int,
location=OpenApiParameter.QUERY,
required=False,
description=f'Resources returned per skill gap (default {DEFAULT_LIMIT_PER_SKILL}).',
),
],
responses=OpenApiTypes.OBJECT,
description='Ranked learning-resource recommendations per skill gap.',
)
class RecommendationsView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
role, err = _resolve_role(request)
if err is not None:
return err
limit_raw = request.query_params.get('limit')
limit = DEFAULT_LIMIT_PER_SKILL
if limit_raw is not None:
try:
limit = int(limit_raw)
except ValueError:
return Response(
{'detail': 'limit must be a positive integer.'},
status=status.HTTP_400_BAD_REQUEST,
)
if limit <= 0:
return Response(
{'detail': 'limit must be a positive integer.'},
status=status.HTTP_400_BAD_REQUEST,
)
limit = min(limit, MAX_LIMIT_PER_SKILL)
payload = compute_recommendations(request.user, role, limit_per_skill=limit)
return Response(payload, status=status.HTTP_200_OK)
@extend_schema(
responses=OpenApiTypes.OBJECT,
description=(
'Active roles ranked by how well the user\'s current skills fit them. '
'Powers the "Recommended for you" section on the role-selection page.'
),
)
class RoleSuggestionsView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
return Response(suggest_roles(request.user), status=status.HTTP_200_OK)