Coverage for postrfp/model/questionnaire/score_views.py: 84%
67 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 decimal import Decimal
2from typing import Optional
4from sqlalchemy import Integer, String
5from sqlalchemy.types import DECIMAL
6from sqlalchemy.orm import Mapped, mapped_column
8from postrfp.model.meta import Base
11class QuestionScoreComponent(Base):
12 """
13 SQLAlchemy model for the question_score_components view.
14 Provides all scoring components for questions.
15 """
17 __tablename__ = "question_score_components"
18 __table_args__ = {"info": {"is_view": True}}
20 # Explicitly disable the default 'id' column
21 __mapper_args__ = {"primary_key": ["question_id", "issue_id"]}
23 id = None # type: ignore
25 # Composite primary key since this is a view
26 question_id: Mapped[int] = mapped_column(Integer, primary_key=True)
27 issue_id: Mapped[int] = mapped_column(Integer, primary_key=True)
29 section_id: Mapped[int] = mapped_column(Integer, nullable=False)
30 scoreset_id: Mapped[Optional[str]] = mapped_column(String(50))
31 raw_score: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(10, 4))
32 direct_weight: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(15, 4))
33 absolute_weight: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(15, 4))
34 weighting_set_id: Mapped[Optional[int]] = mapped_column(Integer)
36 def get_calculated_score(self, scoring_model: str) -> Decimal:
37 """Calculate score based on scoring model with consistent precision."""
38 if not self.raw_score:
39 return Decimal("0.0000")
41 # Ensure all values are Decimal with 4 decimal places
42 raw_score = Decimal(str(self.raw_score)).quantize(Decimal("0.0001"))
44 if scoring_model == "Unweighted":
45 return raw_score
46 elif scoring_model == "Direct":
47 if self.direct_weight is not None:
48 weight = Decimal(str(self.direct_weight)).quantize(Decimal("0.0001"))
49 return (raw_score * weight).quantize(Decimal("0.0001"))
50 else:
51 return raw_score
52 elif scoring_model == "Absolute":
53 if self.absolute_weight is not None:
54 weight = Decimal(str(self.absolute_weight)).quantize(Decimal("0.0001"))
55 return (raw_score * weight).quantize(Decimal("0.0001"))
56 else:
57 return raw_score
58 else:
59 return raw_score
62class SectionScoreComponent(Base):
63 """
64 SQLAlchemy model for the section_score_components view.
65 Provides aggregated scoring components for sections.
66 """
68 __tablename__ = "section_score_components"
69 __table_args__ = {"info": {"is_view": True}}
71 # Explicitly disable the default 'id' column
72 __mapper_args__ = {"primary_key": ["section_id", "issue_id"]}
74 id = None # type: ignore
76 # Composite primary key
77 section_id: Mapped[int] = mapped_column(Integer, primary_key=True)
78 issue_id: Mapped[int] = mapped_column(Integer, primary_key=True)
80 parent_id: Mapped[Optional[int]] = mapped_column(Integer)
81 scoreset_id: Mapped[Optional[str]] = mapped_column(String(50))
82 weighting_set_id: Mapped[Optional[int]] = mapped_column(Integer)
83 question_count: Mapped[int] = mapped_column(Integer, default=0)
84 questions_scored: Mapped[int] = mapped_column(Integer, default=0)
85 raw_total: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(15, 4))
86 direct_weighted_total: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(15, 4))
87 absolute_weighted_total: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(15, 4))
89 def get_calculated_score(self, scoring_model: str) -> Decimal:
90 """Calculate total score based on scoring model with consistent precision."""
91 if scoring_model == "Unweighted":
92 if self.raw_total is not None:
93 return Decimal(str(self.raw_total)).quantize(Decimal("0.0001"))
94 return Decimal("0.0000")
95 elif scoring_model == "Direct":
96 if self.direct_weighted_total is not None:
97 return Decimal(str(self.direct_weighted_total)).quantize(
98 Decimal("0.0001")
99 )
100 return Decimal("0.0000")
101 elif scoring_model == "Absolute":
102 if self.absolute_weighted_total is not None:
103 return Decimal(str(self.absolute_weighted_total)).quantize(
104 Decimal("0.0001")
105 )
106 return Decimal("0.0000")
107 else:
108 if self.raw_total is not None:
109 return Decimal(str(self.raw_total)).quantize(Decimal("0.0001"))
110 return Decimal("0.0000")