refactor(scoring): persist weights in DB table, replace mutable Settings with scoring_config_service

This commit is contained in:
2026-02-19 17:46:02 +01:00
parent 93fde55389
commit 4e3787d091
6 changed files with 210 additions and 83 deletions

View File

@@ -14,7 +14,6 @@ from app.dependencies.auth import get_current_user, require_role
from app.models.user import User
from app.models.technique import Technique
from app.models.threat_actor import ThreatActor
from app.config import settings
from app.services.scoring_service import (
calculate_technique_score,
calculate_tactic_score,
@@ -22,6 +21,10 @@ from app.services.scoring_service import (
calculate_organization_score,
get_score_history,
)
from app.services.scoring_config_service import (
get_weights_dict,
update_scoring_weights,
)
router = APIRouter(prefix="/scores", tags=["scores"])
@@ -117,79 +120,45 @@ def score_history(
@router.get("/config")
def get_scoring_config(
db: Session = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""Get current scoring weights (admin only)."""
return {
"weights": {
"tests": settings.SCORING_WEIGHT_TESTS,
"detection_rules": settings.SCORING_WEIGHT_DETECTION_RULES,
"d3fend": settings.SCORING_WEIGHT_D3FEND,
"freshness": settings.SCORING_WEIGHT_FRESHNESS,
"platform_diversity": settings.SCORING_WEIGHT_PLATFORM_DIVERSITY,
},
"total": (
settings.SCORING_WEIGHT_TESTS
+ settings.SCORING_WEIGHT_DETECTION_RULES
+ settings.SCORING_WEIGHT_D3FEND
+ settings.SCORING_WEIGHT_FRESHNESS
+ settings.SCORING_WEIGHT_PLATFORM_DIVERSITY
),
}
return get_weights_dict(db)
# ── PATCH /scores/config ─────────────────────────────────────────────
class ScoringConfigUpdate(BaseModel):
tests: Optional[int] = None
detection_rules: Optional[int] = None
d3fend: Optional[int] = None
freshness: Optional[int] = None
platform_diversity: Optional[int] = None
tests: Optional[float] = None
detection_rules: Optional[float] = None
d3fend: Optional[float] = None
freshness: Optional[float] = None
platform_diversity: Optional[float] = None
@router.patch("/config")
def update_scoring_config(
payload: ScoringConfigUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""Update scoring weights (admin only).
Note: Since we're using Opcion A (env vars / Settings), changes
are applied at runtime but won't persist across restarts unless
the .env file is also updated. For production, consider migrating
to Option B (database table).
Weights are persisted in the database and survive restarts.
Validation enforces that all weights are non-negative and sum to 100.
"""
if payload.tests is not None:
settings.SCORING_WEIGHT_TESTS = payload.tests
if payload.detection_rules is not None:
settings.SCORING_WEIGHT_DETECTION_RULES = payload.detection_rules
if payload.d3fend is not None:
settings.SCORING_WEIGHT_D3FEND = payload.d3fend
if payload.freshness is not None:
settings.SCORING_WEIGHT_FRESHNESS = payload.freshness
if payload.platform_diversity is not None:
settings.SCORING_WEIGHT_PLATFORM_DIVERSITY = payload.platform_diversity
result = update_scoring_weights(
db,
tests=payload.tests,
detection_rules=payload.detection_rules,
d3fend=payload.d3fend,
freshness=payload.freshness,
platform_diversity=payload.platform_diversity,
)
# Weights changed — bust the score cache
from app.services.score_cache import invalidate
invalidate()
return {
"message": "Scoring config updated",
"weights": {
"tests": settings.SCORING_WEIGHT_TESTS,
"detection_rules": settings.SCORING_WEIGHT_DETECTION_RULES,
"d3fend": settings.SCORING_WEIGHT_D3FEND,
"freshness": settings.SCORING_WEIGHT_FRESHNESS,
"platform_diversity": settings.SCORING_WEIGHT_PLATFORM_DIVERSITY,
},
"total": (
settings.SCORING_WEIGHT_TESTS
+ settings.SCORING_WEIGHT_DETECTION_RULES
+ settings.SCORING_WEIGHT_D3FEND
+ settings.SCORING_WEIGHT_FRESHNESS
+ settings.SCORING_WEIGHT_PLATFORM_DIVERSITY
),
}
return {"message": "Scoring config updated", **result}