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

1from typing import Annotated 

2 

3from sqlalchemy.orm import Session 

4 

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 

11 

12 

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. 

19 

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). 

24 

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 

35 

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) 

41 

42 if issue.winloss_weightset_id is None: 

43 weighting_set_id = 0 

44 else: 

45 weighting_set_id = issue.winloss_weightset_id 

46 

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 

50 

51 return fetch.score_gaps( 

52 issue, 

53 weighting_set_id=weighting_set_id, 

54 show_gap_value=show_gap_value, 

55 )