Spaces:
Sleeping
Sleeping
| """ | |
| 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, | |
| ) | |