Coverage for postrfp/vendor/api/reports.py: 100%
17 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-22 21:34 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-22 21:34 +0000
1from typing import Annotated
3from sqlalchemy.orm import Session
5from postrfp.shared import fetch
6from postrfp.model import User
7from postrfp.authorisation import perms
8from postrfp.shared import serial
9from postrfp.shared.decorators import http
10from postrfp.vendor.validation import validate
13@http
14def get_issue_scoregaps(
15 session: Session, effective_user: User, issue_id: int
16) -> Annotated[list[dict], list[serial.ScoreGaps]]:
17 """
18 Per‑question win/loss gap report for a respondent Issue.
20 For each question the service compares this Issue’s agreed (consensus) score
21 to the highest agreed score achieved by any other scoreable respondent
22 (Submitted / Updateable) in the same Project, using the weighting set defined
23 for win/loss analysis (issue.winloss_weightset_id or 0 if unset).
25 Returned fields:
26 - question_id / number / title / node_type
27 - weight (only meaningful if the Project exposes weightings)
28 - score_gap:
29 * Numeric difference (this_issue_score - best_other_score) when at least
30 3 respondents have scores (prevents inferring a competitor’s exact score).
31 * One of "+", "-", "==" when only 2 (or fewer) scored respondents:
32 "+" -> this Issue leads
33 "-" -> another Issue leads
34 "==" -> tie or no comparative data
36 Weightings are included only if project.expose_weightings is True.
37 Positive gap indicates an advantage; negative indicates a deficit.
38 """
39 issue = fetch.issue(session, issue_id)
40 validate(effective_user, issue, action=perms.ISSUE_VIEW_WINLOSS)
42 if issue.winloss_weightset_id is None:
43 weighting_set_id = 0
44 else:
45 weighting_set_id = issue.winloss_weightset_id
47 # Only show specific gap values if there are more than 2 scored respondents
48 # - possible to derive other vendor's scores if only two
49 show_gap_value = len(issue.project.scoreable_issues) > 2
51 return fetch.score_gaps(
52 issue,
53 weighting_set_id=weighting_set_id,
54 show_gap_value=show_gap_value,
55 )