|
10 | 10 | from __future__ import annotations |
11 | 11 |
|
12 | 12 | from dataclasses import dataclass, field |
13 | | -from pathlib import Path |
14 | 13 |
|
15 | 14 | from arbiter.analyzers.base import Finding |
16 | 15 | from arbiter.scoring import RepoScore |
@@ -68,35 +67,51 @@ def certify( |
68 | 67 | """Run full certification assessment. |
69 | 68 |
|
70 | 69 | Weights: code quality 50%, governance 30%, dependencies 20%. |
| 70 | + When code is unscorable (no Python LOC, analyzers not installed), |
| 71 | + reweight to governance 60% + dependencies 40% instead of penalizing. |
71 | 72 | """ |
72 | | - code = code_score.overall if code_score.is_scorable else 0 |
| 73 | + code = code_score.overall if code_score.is_scorable else None |
73 | 74 | gov = governance_report.score |
74 | 75 | deps = dep_report.score |
75 | 76 |
|
76 | | - overall = code * 0.50 + gov * 0.30 + deps * 0.20 |
| 77 | + if code is not None: |
| 78 | + overall = code * 0.50 + gov * 0.30 + deps * 0.20 |
| 79 | + else: |
| 80 | + # Reweight: skip code dimension entirely |
| 81 | + overall = gov * 0.60 + deps * 0.40 |
| 82 | + code = 0 # for display purposes |
| 83 | + |
77 | 84 | overall = round(overall, 1) |
78 | 85 |
|
79 | 86 | reasons = [] |
| 87 | + code_scorable = code_score.is_scorable |
80 | 88 |
|
81 | 89 | # Determine decision |
82 | 90 | cert = CERT_THRESHOLDS["certified"] |
83 | 91 | prov = CERT_THRESHOLDS["provisional"] |
84 | 92 |
|
85 | | - if (overall >= cert["overall"] and code >= cert["code"] |
| 93 | + # Skip code threshold checks when code is unscorable |
| 94 | + code_meets_cert = code >= cert["code"] if code_scorable else True |
| 95 | + code_meets_prov = code >= prov["code"] if code_scorable else True |
| 96 | + |
| 97 | + if not code_scorable: |
| 98 | + reasons.append("Code quality not scored (no supported analyzers for primary language)") |
| 99 | + |
| 100 | + if (overall >= cert["overall"] and code_meets_cert |
86 | 101 | and gov >= cert["governance"] and deps >= cert["deps"]): |
87 | 102 | decision = "CERTIFIED" |
88 | | - elif (overall >= prov["overall"] and code >= prov["code"] |
| 103 | + elif (overall >= prov["overall"] and code_meets_prov |
89 | 104 | and gov >= prov["governance"] and deps >= prov["deps"]): |
90 | 105 | decision = "PROVISIONAL" |
91 | | - if code < cert["code"]: |
| 106 | + if code_scorable and code < cert["code"]: |
92 | 107 | reasons.append(f"Code score {code:.1f} below certified threshold ({cert['code']})") |
93 | 108 | if gov < cert["governance"]: |
94 | 109 | reasons.append(f"Governance score {gov:.1f} below certified threshold ({cert['governance']})") |
95 | 110 | if deps < cert["deps"]: |
96 | 111 | reasons.append(f"Dependency score {deps:.1f} below certified threshold ({cert['deps']})") |
97 | 112 | else: |
98 | 113 | decision = "FAILED" |
99 | | - if code < prov["code"]: |
| 114 | + if code_scorable and code < prov["code"]: |
100 | 115 | reasons.append(f"Code score {code:.1f} below minimum threshold ({prov['code']})") |
101 | 116 | if gov < prov["governance"]: |
102 | 117 | reasons.append(f"Governance score {gov:.1f} below minimum threshold ({prov['governance']})") |
|
0 commit comments