diff --git a/app/api/controllers/onboarding.py b/app/api/controllers/onboarding.py index 32ae473..8ac734a 100644 --- a/app/api/controllers/onboarding.py +++ b/app/api/controllers/onboarding.py @@ -1,6 +1,6 @@ """Mandatory customer onboarding endpoints.""" -from fastapi import APIRouter, Depends, Header, status +from fastapi import APIRouter, Depends, Header, Query, status from app.api.dependencies.auth import build_request_context from app.api.schemas.risk_questionnaire import RiskQuestionnaireSubmission @@ -12,9 +12,10 @@ router = APIRouter(prefix="/api/v1/onboarding", tags=["customer-onboarding"]) @router.get("/risk-questionnaire") async def get_risk_questionnaire( + retake: bool = Query(default=False), context: RequestContext = Depends(build_request_context), # noqa: B008 ) -> dict[str, object]: - return await RiskQuestionnaireService().questionnaire(context) + return await RiskQuestionnaireService().questionnaire(context, retake=retake) @router.post("/risk-questionnaire/submissions", status_code=status.HTTP_201_CREATED) diff --git a/app/service/risk_questionnaire_service.py b/app/service/risk_questionnaire_service.py index e0c212c..8fc8650 100644 --- a/app/service/risk_questionnaire_service.py +++ b/app/service/risk_questionnaire_service.py @@ -61,17 +61,35 @@ class _ScoreResult: class RiskQuestionnaireService: - async def questionnaire(self, context: RequestContext) -> dict[str, object]: + async def questionnaire( + self, context: RequestContext, *, retake: bool = False + ) -> dict[str, object]: + """Return the current questionnaire definition for a customer. + + A completed assessment remains the default read state so onboarding guards + continue to behave exactly as before. The explicit ``retake`` mode lets a + customer request the same server-owned questions again without weakening + submission frequency limits or exposing scoring rules. + """ self._require_customer(context) required = await self.is_required(context) + pending_review = False + if not required: + async with SessionFactory() as session: + pending_review = ( + await RiskQuestionnaireRepository(session).pending_drift_review( + int(context.user_id) + ) + ) is not None return { "data": { "required": required, + "review_status": "pending_review" if pending_review else None, "questionnaire": { "version": QUESTIONNAIRE_VERSION, "questions": QUESTIONS, "declaration": DECLARATION, - } if required else None, + } if required or retake else None, }, "meta": {"trace_id": context.trace_id}, } @@ -122,6 +140,7 @@ class RiskQuestionnaireService: active_tags = await repository.active_tags(customer_id, lock=True) candidates = self._profile_tag_candidates(score, payload.answers, assessment_id) changes = self._drift_changes(active_tags, candidates) + review_pending = bool(active_tags and changes) audit_action: str audit_detail: dict[str, object] if active_tags and changes: @@ -199,6 +218,7 @@ class RiskQuestionnaireService: return { "data": { "completed": True, + "status": "pending_review" if review_pending else "active", "questionnaire_version": QUESTIONNAIRE_VERSION, "valid_until": valid_until.isoformat() + "Z", }, diff --git a/app/static/portal/common/api-client.js b/app/static/portal/common/api-client.js index 505a705..d6c8497 100644 --- a/app/static/portal/common/api-client.js +++ b/app/static/portal/common/api-client.js @@ -16,6 +16,8 @@ const ENDPOINTS = Object.freeze({ A039: { method: 'GET', path: '/api/v1/admin/customer-profile-candidates' }, A040: { method: 'POST', path: '/api/v1/admin/customer-profile-candidates/{candidateId}/reviews' }, A001: { method: 'POST', path: '/api/v1/admin/config-releases', idempotent: true }, + A041: { method: 'GET', path: '/api/v1/admin/advisor/profile-drift-reviews' }, + A042: { method: 'POST', path: '/api/v1/admin/advisor/profile-drift-reviews/{reviewId}/reviews', idempotent: true }, A002: { method: 'GET', path: '/api/v1/admin/config-releases' }, A008: { method: 'POST', path: '/api/v1/admin/config-releases/{releaseId}/platform-config-items', idempotent: true }, A009: { method: 'GET', path: '/api/v1/admin/config-releases/{releaseId}/platform-config-items' }, diff --git a/app/static/portal/customer/risk-questionnaire/index.html b/app/static/portal/customer/risk-questionnaire/index.html index 9f9b8b3..1c22939 100644 --- a/app/static/portal/customer/risk-questionnaire/index.html +++ b/app/static/portal/customer/risk-questionnaire/index.html @@ -5,7 +5,7 @@