Fallback classification using structural weights only.
(
all_features: dict[str, float],
config: ScoringConfig,
)
| 274 | |
| 275 | |
| 276 | def _rule_based_classify( |
| 277 | all_features: dict[str, float], |
| 278 | config: ScoringConfig, |
| 279 | ) -> tuple[Tier, float]: |
| 280 | """Fallback classification using structural weights only.""" |
| 281 | sw = config.structural_weights |
| 282 | weight_map = { |
| 283 | "s_normalized_length": sw.normalized_length, |
| 284 | "s_enumeration_density": sw.enumeration_density, |
| 285 | "s_sentence_count": sw.sentence_count, |
| 286 | "s_code_markers": sw.code_markers, |
| 287 | "s_math_symbols": sw.math_symbols, |
| 288 | "s_nesting_depth": sw.nesting_depth, |
| 289 | "s_vocabulary_diversity": sw.vocabulary_diversity, |
| 290 | "s_avg_word_length": sw.avg_word_length, |
| 291 | "s_alphabetic_ratio": sw.alphabetic_ratio, |
| 292 | "s_functional_intent": sw.functional_intent, |
| 293 | "s_unique_concept_density": sw.unique_concept_density, |
| 294 | "s_requirement_phrases": sw.requirement_phrases, |
| 295 | } |
| 296 | |
| 297 | score = sum(all_features.get(k, 0.0) * w for k, w in weight_map.items()) |
| 298 | |
| 299 | bounds = config.tier_boundaries |
| 300 | if score < bounds.simple_medium: |
| 301 | tier, dist = Tier.SIMPLE, bounds.simple_medium - score |
| 302 | elif score < bounds.medium_complex: |
| 303 | tier = Tier.MEDIUM |
| 304 | dist = min(score - bounds.simple_medium, bounds.medium_complex - score) |
| 305 | else: |
| 306 | tier, dist = Tier.COMPLEX, score - bounds.medium_complex |
| 307 | |
| 308 | confidence = _sigmoid(dist, config.confidence_steepness) |
| 309 | return tier, confidence |
| 310 | |
| 311 | |
| 312 | # ─── Main Entry ─── |