deploycraft-ai / rules /scoring_rules.py
Upshivam
Initial release of AI MLOps Architecture Designer
4b9217e
Raw
History Blame Contribute Delete
5.12 kB
"""
rules/scoring_rules.py
--------------------------
Deterministic Architecture Score computation: five 0-100 category
subscores (Security, Scalability, Reliability, Cost Efficiency,
Observability) plus one blended overall score, derived from InfraConfig
decisions already made elsewhere plus the existing ArchitectureAdvice's
confidence — no new LLM calls.
Every heuristic below is intentionally simple and commented so the
scoring logic stays auditable; this is meant to give a directionally
useful signal, not a precise audit.
"""
from __future__ import annotations
from utils.config import InfraConfig
from utils.advice import ArchitectureAdvice
from utils.architecture_score import ArchitectureScore
_MANAGED_PLATFORMS = ("Kubernetes", "Cloud Run", "Azure Container Apps", "AWS ECS")
_TLS_CAPABLE_TARGETS = ("Kubernetes", "Cloud Run", "Azure Container Apps", "AWS ECS")
_HIGH_TRAFFIC_TIERS = ("10,000-100,000/day", "100,000+/day")
_LOW_TRAFFIC_TIERS = ("<100/day", "100-1,000/day")
_FULL_MONITORING_STACKS = ("Prometheus + Grafana", "Datadog")
def _clamp(value: float) -> int:
return max(0, min(100, round(value)))
def _score_security(config: InfraConfig) -> int:
score = 30
if config.auth != "None":
score += 25
if config.deployment_target in _TLS_CAPABLE_TARGETS:
score += 20 # managed TLS/ingress support available
if config.database != "None" or config.cache != "None" or config.vector_db != "None":
score += 15 # credentials get isolated into Secret/Secret Manager rather than hardcoded
if config.cicd != "None":
score += 10 # CI pipeline is a natural place to add image scanning later
return _clamp(score)
def _score_scalability(config: InfraConfig) -> int:
score = 25
if config.deployment_target in ("Kubernetes", "Cloud Run"):
score += 30 # native, fine-grained autoscaling support
elif config.deployment_target in ("Azure Container Apps", "AWS ECS"):
score += 20
if config.cache != "None":
score += 20 # cache absorbs read load, easing horizontal scale
if config.high_availability:
score += 15
if config.num_users in _HIGH_TRAFFIC_TIERS and config.deployment_target == "Docker Compose":
score -= 25 # Compose has no built-in autoscaling — flag the mismatch
return _clamp(score)
def _score_reliability(config: InfraConfig) -> int:
score = 25
if config.high_availability:
score += 30
if config.monitoring != "None":
score += 20
if config.deployment_target in _MANAGED_PLATFORMS:
score += 20 # managed restart/self-healing
if config.database != "None" and not config.high_availability:
score -= 10 # stateful dependency without HA is a single point of failure
return _clamp(score)
def _score_cost_efficiency(config: InfraConfig) -> int:
score = 45
if config.deployment_target in ("Cloud Run", "AWS ECS"):
score += 25 # scale-to-zero / pay-per-use friendly
if config.deployment_target == "Kubernetes" and config.num_users in _LOW_TRAFFIC_TIERS:
score -= 20 # cluster overhead is hard to justify at low traffic
if config.cache != "None":
score += 15 # reduces expensive database read load
if config.vector_db == "Pinecone":
score -= 10 # managed vector DB carries a premium vs. self-hosted
return _clamp(score)
def _score_observability(config: InfraConfig) -> int:
score = 15
if config.monitoring != "None":
score += 40
if config.monitoring in _FULL_MONITORING_STACKS:
score += 20 # full metrics+dashboards stack, not just cloud-native basics
if config.cicd != "None":
score += 15 # deploy history/traceability
if config.high_availability:
score += 10 # HA setups typically demand tighter observability
return _clamp(score)
def build_architecture_score(config: InfraConfig, advice: ArchitectureAdvice | None) -> ArchitectureScore:
"""
Compute the Architecture Score. The five category scores come purely
from deterministic rules over `config`; the overall score blends
their average with the ArchitectureAdvisor's own confidence (when the
advice came from a real/mock model call, not the error fallback) so a
run where the AI flagged low confidence in its reasoning is reflected
in the headline number too.
"""
security = _score_security(config)
scalability = _score_scalability(config)
reliability = _score_reliability(config)
cost_efficiency = _score_cost_efficiency(config)
observability = _score_observability(config)
category_mean = (security + scalability + reliability + cost_efficiency + observability) / 5
if advice is not None and advice.source != "fallback":
overall = 0.85 * category_mean + 0.15 * advice.confidence
else:
overall = category_mean
return ArchitectureScore(
overall=_clamp(overall),
security=security,
scalability=scalability,
reliability=reliability,
cost_efficiency=cost_efficiency,
observability=observability,
)