Framework Requirements Guide
Defines and validates software framework requirements across functional, non-functional, operational, and strategic dimensions. When loaded, this skill makes the model act as a senior architect — eliciting structured requirements using formal catalogs (MoSCoW prioritization, NFR classification), building weighted evaluation scoring matrices with evidence-based criterion validation, designing proof-of-concept test harnesses for integration feasibility assessment, generating Architecture Decision Records documenting trade-offs, and enforcing production readiness gates before any framework is committed to code.
TL;DR Checklist
- Classify every requirement using MoSCoW (Must/Should/Could/Won't) — never mix priorities in scoring
- Extract non-functional requirements from the NFR catalog (performance, security, extensibility, observability, operability, portability) before evaluating any candidate
- Build a weighted evaluation matrix where scoring criteria weights sum to exactly 1.0 and knock-out criteria are binary pass/fail
- Design POC tests that exercise the three most critical integration points under realistic conditions — not toy examples
- Validate production readiness: startup time, memory footprint, dependency graph depth, license compatibility, CI/CD integration, monitoring hooks
- Generate an Architecture Decision Record (ADR) with context, drivers, scored evaluation, and documented consequences (both positive and negative)
- Involve at least two stakeholders in the decision and record all deciders
When to Use
Use this skill when:
- Starting a new service or major rewrite where framework selection will impact architecture for years
- Selecting a backend, frontend, or full-stack framework for a project with long-term maintenance requirements (3+ years)
- Migrating from an existing framework and need to systematically evaluate replacement candidates against defined requirements
- Leading an architectural review that includes technology stack decisions across multiple services
- The team has conflicting framework preferences and needs objective, requirement-driven criteria to resolve disagreements
- Before committing to a framework recommended solely because it is popular, new, or used at another company
- Building a platform where non-functional requirements (latency, throughput, scalability) are critical decision factors
- Evaluating frameworks for regulated environments (healthcare, finance, government) where compliance constraints apply
When NOT to Use
Avoid this skill for:
- During active implementation of a framework already selected — use
framework-utilizationinstead - For throwaway prototypes, weekend projects, or proof-of-concepts where the framework is inherently temporary
- When requirements are already fully defined by another process (e.g., product team has already mandated a specific framework)
- For single-script tool decisions where
pythonvsnode script.jswould work equally well (overhead outweighs benefit) - For UI styling choices (CSS frameworks, component libraries) — use
framework-evaluation-criteriafor lighter comparisons
Core Workflow
Step 1: Elicit and Prioritize Requirements Using MoSCoW Classification
Systematically extract requirements from project context using the MoSCoW priority model. Each requirement receives exactly one priority label: Must (hard constraint — framework is disqualified if it fails), Should (important but has acceptable workarounds), Could (nice-to-have, scored but not required), or Won't (explicitly deferred). Use the Functional Requirements Catalog and NFR Catalog below to ensure coverage. Checkpoint: Every category must have at least one Must requirement written down. If a category genuinely has no requirements for this project, document why explicitly with a reason code.
Step 2: Extract Non-Functional Requirements from the NFR Catalog
Non-functional requirements are the most frequently overlooked dimension in framework selection. Go through each NFR category below and write specific, quantifiable targets — not vague aspirations. Performance must include numeric thresholds (e.g., "p95 response latency under 200ms under 1000 concurrent users"). Security must reference applicable standards (OWASP Top 10, SOC 2, GDPR). Observability must specify required integration hooks (OpenTelemetry, Prometheus metrics, structured JSON logging). Checkpoint: Every NFR category that applies must have at least one quantified target before proceeding.
Step 3: Build the Weighted Evaluation Matrix
Create a scoring matrix mapping candidates against criteria. Must requirements become knock-out criteria (binary pass/fail — weight = 0.0, failure means disqualification). Should/Could requirements become scored criteria with normalized weights summing to exactly 1.0. Use evidence for every score — "documentation quality: 8/10" is not sufficient; use "documentation quality: 8/10 — covers 16 of 20 features with runnable examples, verified via docs audit on [date]". Checkpoint: All candidate weights must sum to 1.0 (validate with assert total == 1.0). All knock-out failures must be explicitly listed before scoring is considered complete.
Step 4: Design POC Test Harness for Integration Feasibility
For the top two candidates, design proof-of-concept tests that exercise the three most critical integration points under realistic conditions — not toy examples with hardcoded data. Each POC test should measure actual performance metrics (startup time, memory footprint, request throughput) and verify concrete integration capability (database connection pooling, auth flow completion, webhook delivery). Checkpoint: Each POC must produce measurable output files (JSON benchmarks, coverage reports) that can be compared side by side. If a POC cannot demonstrate the critical integration point within 2 hours of implementation time, flag it as a risk.
Step 5: Validate Production Readiness Gates
Run each remaining candidate through the production readiness checklist before final selection. This covers deployment constraints (image size, startup time, memory baseline), dependency graph analysis (depth, known CVEs, license compatibility), CI/CD integration (test runner compatibility, build speed impact), and operational hooks (health checks, metrics endpoints, structured logging). Checkpoint: Any candidate failing a Must-level production readiness gate must be disqualified or require explicit stakeholder exception.
Step 6: Generate Architecture Decision Record with Consequences
Produce a complete ADR documenting the decision context, weighted evaluation results, POC findings, and most critically — documented consequences in both directions (why selected AND why rejected). The ADR is not just justification; it is future-proofing that enables informed re-evaluation when requirements change. Checkpoint: Both positive and negative consequences must be present. Status should be proposed pending all stakeholder review before moving to accepted.
Functional Requirements Catalog
Every framework evaluation begins with functional requirements — what the framework must actually do for your application to work. Use this catalog to ensure completeness, then prioritize using MoSCoW.
Category: Core Capability
The fundamental features your application needs. This is the most commonly evaluated dimension but also the most often done incompletely — teams compare feature lists without considering depth of capability or long-term roadmap alignment.
Requirements to elicit:
- Does the framework support all required use cases at sufficient depth? (Not just "can it do X" but "how well can it do X at scale?")
- What is the extensibility model? Can you add features not built in via plugins, hooks, or extension points?
- Are there capability gaps that require custom infrastructure? How much custom code would those gaps produce?
Priority guidance: Core capability Must requirements should cover every use case that, if missing, would force a complete architectural rewrite. Should requirements cover the next layer of capabilities that improve developer productivity but have documented workarounds.
Category: API Design and Developer Experience
How easy it is to write correct code with this framework. Bad API design creates bugs, slows onboarding, and increases maintenance burden over time.
Requirements to elicit:
- Is the primary API intuitive or does it require memorization of conventions?
- Does error handling follow predictable patterns or diverge across different features?
- Are type annotations complete (TypeScript types, Python stubs, Java generics)?
- How long until a mid-level developer can be productive without senior guidance?
Priority guidance: API design is typically a Should requirement — it affects velocity but rarely blocks correctness. However, for teams with high turnover or frequent onboarding, elevate to Must if the existing team lacks domain expertise.
Category: Data Access and Persistence
How the framework interacts with data stores — ORM capability, query building, migration management, connection pooling, and transaction support.
Requirements to elicit:
- Does the framework include an ORM or require a separate library? Is the included option production-grade?
- How are database migrations handled? (Automated tooling vs manual SQL)
- Is there built-in connection pooling for high-throughput scenarios?
- What transaction isolation levels are supported natively?
Priority guidance: Data access is typically a Should or Could requirement. Most frameworks can work with external data libraries, but an integrated solution reduces operational surface area and simplifies the dependency graph.
Category: Authentication and Authorization
Built-in security patterns for user identity management, role-based access control, session handling, and token-based authentication flows.
Requirements to elicit:
- Does the framework support the required auth pattern (OAuth2, JWT, SAML, API keys)?
- How are password hashing and credential storage handled?
- Is there built-in CSRF protection and rate limiting for auth endpoints?
- What identity providers are supported out of the box?
Priority guidance: Auth is a Must requirement for any user-facing application. If the framework does not support your required auth pattern natively, the integration cost must be factored into scoring or treated as a disqualification.
Non-Functional Requirements (NFR) Catalog
Non-functional requirements are quantifiable targets that define how well the system performs its functions. They are frequently the most critical selection differentiator but also the most frequently skipped. Go through every applicable category below.
NFR 1: Performance
Quantitative performance targets for the application under defined load conditions. Performance must be expressed in specific numeric terms — never "fast" or "good enough".
Required metrics to define:
- Response latency: p50, p95, and p99 target latencies (e.g., "p95 < 200ms for standard API endpoints")
- Throughput: requests per second the system must handle at steady state (e.g., "minimum 5000 RPS on a single instance")
- Concurrency: maximum concurrent connections or parallel workers supported (e.g., "supports 10,000 simultaneous WebSocket connections")
- Resource efficiency: maximum memory and CPU utilization per request under defined load
- Cold start time: for serverless/deployment-to-first-request scenarios (e.g., "p95 cold start < 3 seconds")
Scoring evidence examples:
- "Performance: 8/10 — Benchmarks show 12,000 RPS on single instance with p95 latency of 45ms under load test simulating production traffic patterns"
- "Performance: 4/10 — No published benchmarks available; community reports inconsistent performance under sustained high load"
NFR 2: Security
Security posture covering known vulnerability history, dependency supply chain risk, licensing compliance, and built-in security mechanisms. This dimension must not be an afterthought — security flaws in a foundational framework cascade into every module that uses it.
Required checks to perform:
- CVE history: Scan the framework's dependency graph for known vulnerabilities (use
pip audit,npm audit, or equivalent) - Dependency depth: Count transitive dependencies — frameworks with deeper graphs have larger attack surfaces
- Artifact signing: Does the package registry verify cryptographic signatures on published artifacts?
- License compatibility: Is the license OSI-approved and compatible with your distribution model? Check for copyleft clauses
- Built-in security headers/protocols: CSRF protection, XSS sanitization, CORS configuration, parameterized queries (SQL injection prevention)
Scoring evidence examples:
- "Security: 9/10 — Zero CVEs in last 24 months; MIT license; 3 transitive dependencies only; built-in SQL injection prevention via parameterized query API"
- "Security: 5/10 — 3 medium-severity CVEs patched in previous minor versions but no public disclosure of patch timeline; GPL-3.0 license creates distribution concerns"
NFR 3: Extensibility
The ability to customize and extend framework behavior without forking or modifying core code. Frameworks that are not extensible become technical debt when requirements evolve.
Required checks to perform:
- Extension points: How many documented hooks, plugins, or middleware layers exist? Can you intercept requests at multiple stages of the pipeline?
- Plugin ecosystem: Is there an active third-party plugin/package ecosystem? What fraction of common patterns have community solutions?
- Fork risk: If a needed feature doesn't exist and no plugin exists, how much code would need to be written? Would it require modifying framework internals?
- Interface stability: Does the framework guarantee API stability across major versions for extension points?
Priority guidance: Extensibility is typically a Should requirement. For platforms or product-as-a-service applications where customers may request custom behavior, elevate to Must.
NFR 4: Observability
Built-in support for monitoring, logging, and tracing that enables production debugging, performance analysis, and incident response. Frameworks without observability hooks turn every production issue into a forensic investigation.
Required checks to perform:
- Structured logging: Does the framework produce structured (JSON) log output with contextual fields (request ID, user ID, latency)?
- Metrics export: Are Prometheus-compatible metrics or OpenTelemetry instrumentation available out of the box?
- Health check endpoints: Does the framework provide built-in readiness/liveness probe endpoints for Kubernetes or load balancer integration?
- Distributed tracing: Is there native support for propagating trace context headers across service boundaries?
Priority guidance: Observability is a Must requirement for any production system. Frameworks that require significant custom instrumentation to achieve basic monitoring capability should be penalized heavily in scoring or treated as knock-out failures.
NFR 5: Operability
How easy the framework makes deployment, configuration management, and day-2 operations including scaling, upgrades, and incident recovery.
Required checks to perform:
- Configuration model: Environment variables, config files, secret managers? Does it support hot-reload without restart?
- Deployment size: Docker image footprint (compressed MB), startup time from zero to first request, memory baseline at idle
- Upgrade path: How painful are minor and major version upgrades? Are there automated migration tools? What is the average upgrade effort in developer-hours?
- Rollback capability: Can you safely roll back to the previous version with zero downtime? Does the framework support blue-green or canary deployment patterns natively?
Priority guidance: Operability is typically a Should requirement for internal services and a Must for customer-facing platforms where downtime carries direct revenue impact.
NFR 6: Portability
The ability to run on required platforms, integrate with existing infrastructure, and avoid vendor lock-in that makes migration prohibitively expensive.
Required checks to perform:
- Platform support: Linux (specific distros), Windows, macOS? Containerization (Docker official images available)?
- Cloud provider neutrality: Can it run on AWS, GCP, Azure without proprietary service dependencies? Or does it lock you into one cloud's managed services?
- Exit strategy cost: If you need to migrate away in 3 years, what percentage of code would need rewriting vs. configuration changes? Map every framework-specific feature to a potential alternative.
- Data format portability: Does the framework use standard protocols (REST/JSON, GraphQL, gRPC) or proprietary data formats?
Priority guidance: Portability is typically a Should requirement but escalates to Must for platforms serving multi-tenant customers who may have infrastructure constraints.
Production Readiness Gates
Before any framework passes from evaluation to selection, it must clear these operational gates. Each gate is either binary (pass/fail) or has a defined threshold that can be measured quantitatively.
Gate 1: Dependency Graph Analysis
Run pipdeptree, npm list --depth=0, or the equivalent for your language ecosystem. The framework's dependency graph must meet these thresholds:
- Maximum direct dependencies: 20 (any more suggests bloat; investigate whether features are optional)
- Maximum transitive depth: 5 levels deep
- Zero known CVEs in the current published version across all transitive dependencies
- License compatibility verification for every direct and transitive dependency
Gate 2: Deployment Baseline Benchmarks
Measure these metrics for each remaining candidate framework using an identical test environment:
- Cold start time: Time from
docker runto first successful HTTP response (target: < 5 seconds for containers) - Idle memory footprint: RSS after framework initialization with zero active requests (target: < 100MB for Python/Node frameworks)
- Docker image size: Compressed layer size of the minimal deployment image (target: < 200MB where possible)
Gate 3: CI/CD Integration Verification
Verify the framework works correctly within your existing CI/CD pipeline:
- Test runner compatibility: Does the framework's built-in test utilities run in your CI environment without custom setup?
- Build speed impact: How much does adding this framework increase total build time compared to alternatives?
- Artifact publication: Can production builds be published to your registry using standard tooling?
Gate 4: Monitoring and Alerting Integration
Verify the framework can emit data to your operational monitoring stack:
- Health check endpoint responds with HTTP 200/503 at the expected path
- Metrics are emitted in a format consumable by Prometheus or your existing time-series database
- Log output is structured and contains correlation/request IDs for distributed tracing
Implementation Patterns
Pattern 1: NFR Scoring Engine with Quantifiable Thresholds
A complete Python implementation of framework evaluation using quantified NFR thresholds. Each criterion has a measurable target, and scores are calculated by how close candidates come to meeting those targets — eliminating subjective judgment from scoring.
"""Framework requirements evaluation engine with quantified NFR thresholds.
This module implements evidence-based framework evaluation where every score is
derived from measurable metrics against defined thresholds. Scores are computed
by comparing candidate performance against target values, producing objective
comparisons that eliminate subjective scoring bias.
"""
from dataclasses import dataclass, field
from enum import IntEnum
from typing import Any
class Priority(IntEnum):
"""MoSCoW priority classification for requirements."""
MUST = 5 # Hard constraint — framework disqualified if fails
SHOULD = 4 # Important but has acceptable workarounds
COULD = 3 # Nice-to-have, scored but not required
WONT = 0 # Explicitly deferred
def label(self) -> str:
return {self.MUST: "MUST", self.SHOULD: "SHOULD", self.COULD: "COULD", self.WONT: "WONT"}[self]
@dataclass
class Requirement:
"""A single prioritized framework requirement.
Each requirement specifies what the framework must or should do,
with a quantified threshold where applicable and evidence for scoring.
"""
name: str
category: str # e.g., "performance", "security", "core_capability"
priority: Priority # MoSCoW classification
is_knockout: bool = False # Must requirements default to knockout
target_value: float | None = None # Quantified target (e.g., p95 latency < 200ms → 200.0)
unit: str = "" # Metric unit ("ms", "MB", "RPS", "%")
description: str = "" # Human-readable explanation of the requirement
evidence: str = "" # How the score was determined
def __post_init__(self) -> None:
if self.priority == Priority.MUST and not self.is_knockout:
# Must requirements can be non-knockout only when they have a quantified threshold
# that allows partial credit (e.g., MUST meet but scores vary above threshold)
pass
@dataclass
class NFRCriterion:
"""A non-functional requirement criterion with measurable thresholds.
Unlike standard requirements, NFRs are always scored on a 0-10 scale
based on how the candidate performs against quantified targets.
"""
name: str
nfr_category: str # performance, security, extensibility, observability, operability, portability
priority: Priority
target_value: float # The desired value (lower is better for latency, higher for throughput)
direction: str = "lower_is_better" # "lower_is_better" or "higher_is_better"
tolerance_range: float = 1.5 # Acceptable deviation from target before score drops
unit: str = ""
@property
def max_score(self) -> int:
return 10
def compute_score(self, actual_value: float) -> float:
"""Compute a 0-10 score based on how close the candidate is to the target.
Uses a linear falloff within tolerance range, then steep penalty outside it.
At target: score = 10. Within tolerance: linearly interpolated. Beyond tolerance: drops toward 0.
"""
if self.direction == "lower_is_better":
deviation = max(0.0, actual_value - self.target_value)
else:
deviation = max(0.0, self.target_value - actual_value)
# Within tolerance: perfect score
if deviation <= self.target_value * (self.tolerance_range - 1.0):
return float(self.max_score)
# Calculate falloff — linear within tolerance, steep outside
penalty_zone = self.target_value * self.tolerance_range
ratio = min(deviation / penalty_zone, 5.0) # Cap at 5x target for max penalty
score = max(0.0, self.max_score * (1.0 - ratio))
return round(score, 2)
@dataclass
class FrameworkCandidate:
"""A framework candidate being evaluated against all requirements."""
name: str
version: str
license_type: str
nfr_scores: dict[str, float] = field(default_factory=dict) # criterion_name -> score (0-10)
knockout_failures: list[dict[str, str]] = field(default_factory=list) # {requirement: reason}
poc_results: dict[str, Any] = field(default_factory=dict) # test_name -> metrics
production_gates: dict[str, bool] = field(default_factory=dict) # gate_name -> pass/fail
@property
def is_viable(self) -> bool:
"""Framework passes all knock-out criteria and production readiness gates."""
return len(self.knockout_failures) == 0
@property
def weighted_nfr_score(self) -> float:
"""Calculate average NFR score across all evaluated categories."""
if not self.nfr_scores:
return 0.0
return round(sum(self.nfr_scores.values()) / len(self.nfr_scores), 2)
@property
def nfr_categories_scored(self) -> list[str]:
"""List of NFR categories that have been scored for this candidate."""
return list(self.nfr_scores.keys())
def add_nfr_score(self, criterion: NFRCriterion, actual_value: float) -> None:
"""Score a candidate on an NFR criterion using measured performance data.
Args:
criterion: The NFR criterion definition with target and direction.
actual_value: The measured value for this candidate (e.g., p95 latency in ms).
"""
score = criterion.compute_score(actual_value)
self.nfr_scores[criterion.name] = score
def record_knockout_failure(self, requirement_name: str, reason: str) -> None:
"""Record a knock-out failure that disqualifies this candidate."""
self.knockout_failures.append({
"requirement": requirement_name,
"reason": reason,
})
def record_poc_result(self, test_name: str, metrics: dict[str, Any]) -> None:
"""Record proof-of-concept test results for this candidate."""
self.poc_results[test_name] = metrics
def pass_production_gate(self, gate_name: str, passed: bool) -> None:
"""Record a production readiness gate result.
Args:
gate_name: Name of the gate (e.g., "dependency_graph", "deployment_baseline").
passed: True if the candidate meets this gate's threshold.
"""
self.production_gates[gate_name] = passed
class RequirementsEvaluator:
"""Framework requirements evaluation engine.
Implements weighted scoring matrices with evidence-based NFR thresholds,
knock-out criteria enforcement, POC result aggregation, and production
readiness gate validation.
"""
# Predefined NFR catalog for consistent elicitation
DEFAULT_NFR_CRITERIA: list[dict[str, Any]] = [
{"name": "response_latency_p95", "nfr_category": "performance", "target_value": 200.0, "unit": "ms", "direction": "lower_is_better"},
{"name": "idle_memory_mb", "nfr_category": "operability", "target_value": 100.0, "unit": "MB", "direction": "lower_is_better"},
{"name": "cold_start_seconds", "nfr_category": "operability", "target_value": 5.0, "unit": "s", "direction": "lower_is_better"},
{"name": "docker_image_mb", "nfr_category": "operability", "target_value": 200.0, "unit": "MB", "direction": "lower_is_better"},
{"name": "dependency_count", "nfr_category": "portability", "target_value": 20, "unit": "deps", "direction": "lower_is_better"},
]
all_nfr_criteria: dict[str, NFRCriterion] = {}
@classmethod
def initialize_criteria(cls) -> None:
"""Initialize the default NFR criteria catalog."""
cls.all_nfr_criteria = {
c["name"]: NFRCriterion(
name=c["name"],
nfr_category=c["nfr_category"],
priority=Priority.SHOULD,
target_value=c["target_value"],
direction=c["direction"],
tolerance_range=1.5,
unit=c.get("unit", ""),
)
for c in cls.DEFAULT_NFR_CRITERIA
}
@classmethod
def evaluate_nfrs(
cls,
candidates: list[FrameworkCandidate],
measured_metrics: dict[str, dict[str, float]],
) -> None:
"""Score all candidates on NFR criteria using measured performance data.
Args:
candidates: List of framework candidates to evaluate.
measured_metrics: Mapping of candidate_name -> {metric_name: actual_value}.
Example: {"FastAPI": {"response_latency_p95": 45.0, "idle_memory_mb": 62.0}}
"""
for candidate in candidates:
candidate_metrics = measured_metrics.get(candidate.name, {})
for criterion in cls.all_nfr_criteria.values():
if criterion.name in candidate_metrics:
actual = candidate_metrics[criterion.name]
candidate.add_nfr_score(criterion, actual)
@classmethod
def calculate_final_scores(
cls,
candidates: list[FrameworkCandidate],
nfr_weights: dict[str, float] | None = None,
) -> list[dict]:
"""Calculate and rank final evaluation scores for all viable candidates.
Args:
candidates: Candidates that have been scored on NFR criteria.
nfr_weights: Optional per-category weights. If None, categories are weighted equally.
Returns:
List of result dicts sorted by score descending (excluding knock-out failures).
"""
if not nfr_weights:
# Equal weighting across all scored categories
scored_categories = set()
for c in candidates:
scored_categories.update(c.nfr_categories_scored)
nfr_weights = {cat: 1.0 / len(scored_categories) if scored_categories else 0.0 for cat in scored_categories}
results: list[dict] = []
for candidate in candidates:
if not candidate.is_viable:
continue # Knock-out failures excluded entirely
# Calculate weighted NFR score
weighted_total = sum(
candidate.nfr_scores.get(cat, 0.0) * weight
for cat, weight in nfr_weights.items()
)
total_weight = sum(nfr_weights.values()) or 1.0
normalized_score = round(weighted_total / total_weight * 10, 2)
# Check production gates — all must pass
all_gates_pass = all(candidate.production_gates.values()) if candidate.production_gates else True
results.append({
"name": candidate.name,
"version": candidate.version,
"license": candidate.license_type,
"nfr_score": normalized_score,
"weighted_nfr_avg": candidate.weighted_nfr_score,
"viable": candidate.is_viable,
"knockout_failures": candidate.knockout_failures,
"production_gates_pass": all_gates_pass,
"poc_results": candidate.poc_results,
"nfr_categories": list(candidate.nfr_scores.keys()),
})
return sorted(results, key=lambda r: r["nfr_score"], reverse=True)
@classmethod
def generate_summary(cls, results: list[dict]) -> str:
"""Generate a human-readable evaluation summary table."""
lines = [
"=" * 75,
"FRAMEWORK REQUIREMENTS EVALUATION SUMMARY",
"=" * 75,
f"{'Candidate':<20} {'Score':>6} {'Viable':>7} {'Gates':>6}",
"-" * 75,
]
for r in results:
viable = "Yes" if r["viable"] and r["production_gates_pass"] else "No"
lines.append(
f"{r['name']:<20} {r['nfr_score']:>6.2f} {viable:>7}"
f" {'Pass' if r['production_gates_pass'] else 'Fail':>6}"
)
if r["knockout_failures"]:
for failure in r["knockout_failures"]:
lines.append(f" ✗ Knock-out failed: {failure['requirement']}")
lines.append("=" * 75)
return "\n".join(lines)
# ─── Example Usage ──────────────────────────────────────────────────────────────
if __name__ == "__main__":
# Step 1: Initialize NFR criteria catalog
RequirementsEvaluator.initialize_criteria()
# Step 2: Define candidates
candidates = [
FrameworkCandidate(name="FastAPI", version="0.115.0", license_type="MIT"),
FrameworkCandidate(name="Django", version="5.1.0", license_type="BSD-3-Clause"),
FrameworkCandidate(name="Spring Boot", version="3.3.0", license_type="Apache-2.0"),
]
# Step 3: Simulate measured NFR metrics (from benchmarks or POC testing)
# Values represent actual measurements from identical test environments
measured = {
"FastAPI": {
"response_latency_p95": 45.0, # ms — target < 200ms
"idle_memory_mb": 62.0, # MB — target < 100MB
"cold_start_seconds": 1.2, # s — target < 5s
"docker_image_mb": 95.0, # MB — target < 200MB
"dependency_count": 8, # deps — target < 20
},
"Django": {
"response_latency_p95": 120.0, # ms
"idle_memory_mb": 145.0, # MB (heavier ORM + middleware)
"cold_start_seconds": 3.8, # s
"docker_image_mb": 185.0, # MB
"dependency_count": 28, # deps (ORM + admin + auth built-in)
},
"Spring Boot": {
"response_latency_p95": 85.0, # ms
"idle_memory_mb": 220.0, # MB (JVM baseline)
"cold_start_seconds": 8.5, # s (JVM warmup — fails gate)
"docker_image_mb": 160.0, # MB (JRE included)
"dependency_count": 35, # deps (Spring ecosystem is deep)
},
}
# Step 4: Score NFRs using measured data
RequirementsEvaluator.evaluate_nfrs(candidates, measured)
# Step 5: Simulate knock-out failures
# Django passes all, but Spring Boot fails cold start gate (MUST requirement)
spring_boot = next(c for c in candidates if c.name == "Spring Boot")
spring_boot.record_knockout_failure(
"cold_start_seconds",
"p95 cold start 8.5s exceeds MUST threshold of < 5s for serverless deployment target",
)
# Step 6: Record production gate results
for candidate in candidates:
if not candidate.is_viable:
continue
candidate.pass_production_gate("dependency_graph", True)
candidate.pass_production_gate("ci_cd_integration", True)
candidate.pass_production_gate("monitoring_hooks", True)
# Spring Boot also fails deployment baseline gate (cold start)
spring_boot.pass_production_gate("deployment_baseline", False)
# Step 7: Calculate and display final scores
nfr_weights = {cat: 1.0 / len(RequirementsEvaluator.all_nfr_criteria)
for cat in RequirementsEvaluator.all_nfr_criteria}
results = RequirementsEvaluator.calculate_final_scores(candidates, nfr_weights)
print(RequirementsEvaluator.generate_summary(results))
# Verify deterministic output — scores are derived from measured data
assert results[0]["nfr_score"] > results[-1]["nfr_score"] or len(results) <= 1, \
"Results must be sorted by descending NFR score"
Pattern 2: Requirements Validation Schema with Pydantic
A pydantic-based schema validation system for framework requirements documents. This ensures every requirement is properly structured with quantified thresholds, evidence citations, and priority classifications before any evaluation begins. Prevents vague or incomplete requirement definitions from entering the scoring process.
"""Framework requirements validation using Pydantic schemas.
Validates that all framework requirements are properly specified with:
- Quantified targets (not qualitative descriptions)
- MoSCoW priority classification
- Evidence citations for scores
- NFR category membership and threshold definitions
- Cross-criteria weight consistency checks
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Optional
try:
from pydantic import BaseModel, Field, field_validator, model_validator
HAS_PYDANTIC = True
except ImportError:
HAS_PYDANTIC = False
# ─── Data Model (Pure Python Fallback) ──────────────────────────────────────────
class RequirementPriority(str, Enum):
MUST = "must"
SHOULD = "should"
COULD = "could"
WONT = "wont"
class NFRCategory(str, Enum):
PERFORMANCE = "performance"
SECURITY = "security"
EXTENSIBILITY = "extensibility"
OBSERVABILITY = "observability"
OPERABILITY = "operability"
PORTABILITY = "portability"
class ValidationResult:
"""Result of a requirements validation pass."""
def __init__(
self,
valid: bool = True,
errors: list[str] | None = None,
warnings: list[str] | None = None,
):
self.valid = valid
self.errors = errors or []
self.warnings = warnings or []
@property
def is_valid(self) -> bool:
return len(self.errors) == 0
def add_error(self, message: str) -> None:
self.errors.append(message)
self.valid = False
def add_warning(self, message: str) -> None:
self.warnings.append(message)
def __bool__(self) -> bool:
return self.is_valid
@dataclass
class FrameworkRequirementSchema:
"""A structured requirement with validation guarantees.
This dataclass ensures every requirement has the minimum fields needed
for evaluation, preventing vague or incomplete requirements from entering
the scoring pipeline.
"""
name: str
category: str
priority: RequirementPriority
target_value: float | None = None
unit: str = ""
description: str = ""
evidence: str = ""
@field_validator("name")
@classmethod
def name_not_empty(cls, v: str) -> str:
if not v.strip():
raise ValueError("Requirement name must not be empty")
return v
@dataclass
class NFRSchema:
"""A non-functional requirement with quantified threshold."""
name: str
category: NFRCategory
target_value: float
direction: str = "lower_is_better" # or "higher_is_better"
unit: str = ""
tolerance_multiplier: float = 1.5
def validate_target(self) -> ValidationResult:
"""Validate that the target value is reasonable and non-zero."""
result = ValidationResult()
if self.target_value <= 0:
result.add_error(
f"NFR '{self.name}' has non-positive target value {self.target_value}. "
"Target must be a positive number."
)
if not self.unit:
result.add_warning(f"NFR '{self.name}' is missing a unit specification.")
if self.direction not in ("lower_is_better", "higher_is_better"):
result.add_error(
f"NFR '{self.name}' has invalid direction '{self.direction}'. "
"Must be 'lower_is_better' or 'higher_is_better'."
)
return result
@dataclass
class FrameworkRequirementsDocument:
"""Complete framework requirements document with validation."""
project_name: str
functional_requirements: list[FrameworkRequirementSchema] = field(default_factory=list)
nfr_criteria: list[NFRSchema] = field(default_factory=list)
weights: dict[str, float] = field(default_factory=dict) # criterion_name -> normalized weight
knockout_requirements: list[str] = field(default_factory=list) # names of MUST requirements
def validate(self) -> ValidationResult:
"""Validate the complete requirements document.
Checks include:
- At least one MUST requirement exists
- All weights sum to exactly 1.0
- All NFR targets are valid and quantified
- Every requirement has an evidence field (non-empty for scored criteria)
- No duplicate requirement names
…(truncated)