Skip to content

Commit ab87b79

Browse files
committed
fix(spp_scoring): address QA findings on thresholds, smart button, strict mode
- #835: threshold gap/overlap check now rounds to 2 decimals before comparing. IEEE-754 made 20.00 - 19.99 occasionally compute to 0.010000000000000009, triggering a false "gap" error on valid contiguous boundaries. - #837: Scores smart button was inheriting base.view_partner_form, but spp_registry.view_individuals_form replaces the //sheet, so the button never showed on Individual or Group registrant pages. Inherit the registry form instead. Drop the now-redundant is_registrant guard — the registry form is only opened on registrants. - #838: strict mode detected missing/invalid required indicators but still created the score record with is_complete=False. Now it raises UserError, preventing an incomplete score from being persisted and downstream enrollment logic from acting on it.
1 parent 1a8bfdb commit ab87b79

3 files changed

Lines changed: 28 additions & 11 deletions

File tree

spp_scoring/models/scoring_engine.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,26 @@ def calculate_score(self, registrant, scoring_model, mode="manual"):
115115
if calculation_method == "cel_formula":
116116
total_score = self._calculate_cel_total(scoring_model, registrant, inputs_snapshot)
117117

118+
# In strict mode, required-indicator failures must prevent score creation
119+
# (not just mark the result incomplete) — otherwise an invalid score
120+
# can still enroll a registrant.
121+
if errors and is_strict_mode:
122+
raise UserError(
123+
_(
124+
"Cannot calculate score for '%(name)s' in strict mode. "
125+
"%(count)d required indicator(s) failed:\n- %(errors)s"
126+
)
127+
% {
128+
"name": registrant.display_name,
129+
"count": len(errors),
130+
"errors": "\n- ".join(errors),
131+
}
132+
)
133+
118134
# Determine classification (thresholds already prefetched)
119135
classification = self._get_classification(total_score, scoring_model)
120136

121-
# Check for errors in strict mode
122137
is_complete = True
123-
if errors and is_strict_mode:
124-
is_complete = False
125138

126139
# Create result record
127140
Result = self.env["spp.scoring.result"]

spp_scoring/models/scoring_model.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,12 @@ def _validate_thresholds(self):
270270

271271
sorted_thresholds = self.threshold_ids.sorted(key=lambda t: t.min_score)
272272

273-
# Check all consecutive threshold boundaries for gaps and overlaps
273+
# Check all consecutive threshold boundaries for gaps and overlaps.
274+
# Round to 2 decimal places to avoid IEEE-754 false positives
275+
# (e.g., 20.00 - 19.99 computing to 0.010000000000000009).
274276
for i, threshold in enumerate(sorted_thresholds[:-1]):
275277
next_threshold = sorted_thresholds[i + 1]
276-
gap = next_threshold.min_score - threshold.max_score
278+
gap = round(next_threshold.min_score - threshold.max_score, 2)
277279

278280
if gap > 0.01:
279281
errors.append(
@@ -285,7 +287,7 @@ def _validate_thresholds(self):
285287
"min": next_threshold.min_score,
286288
}
287289
)
288-
elif gap < -0.01:
290+
elif gap < 0:
289291
errors.append(
290292
_("Overlap detected between thresholds '%(current)s' (max %(max)s) and '%(next)s' (min %(min)s).")
291293
% {

spp_scoring/views/res_partner_views.xml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<odoo>
3-
<!-- Add Scores smart button to registrant form -->
4-
<record id="view_res_partner_form_scoring" model="ir.ui.view">
5-
<field name="name">res.partner.form.scoring</field>
3+
<!-- Add Scores smart button to the OpenSPP registrant form.
4+
spp_registry.view_individuals_form replaces //sheet on res.partner,
5+
so inheriting base.view_partner_form does not propagate to the
6+
registry form used by Individuals and Groups. -->
7+
<record id="view_registrant_form_scoring" model="ir.ui.view">
8+
<field name="name">spp_registry.view_registrant_form.scoring</field>
69
<field name="model">res.partner</field>
7-
<field name="inherit_id" ref="base.view_partner_form" />
10+
<field name="inherit_id" ref="spp_registry.view_individuals_form" />
811
<field name="arch" type="xml">
912
<xpath expr="//div[@name='button_box']" position="inside">
1013
<button
1114
name="action_view_scoring_results"
1215
type="object"
1316
class="oe_stat_button"
1417
icon="fa-bar-chart"
15-
invisible="not is_registrant"
1618
>
1719
<field
1820
name="scoring_result_count"

0 commit comments

Comments
 (0)