Skip to content
Back to skills

Framework Evaluation

ASecurity

Runs automated empirical evaluation of software frameworks through reproducible benchmark harnesses, dependency graph security auditing, integration feasibility testing, and maintenance cost modeling to produce quantitative selection data.

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 4, 2026
devopspythongosqlnodefastapidjangoflaskdockerkubernetestesting

Works with

  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned September 4, 2026

npx -y skills add paulpas/agent-skill-router --skill framework-evaluation --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Framework Evaluation?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Framework Evaluation
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/paulpas-framework-evaluation/badge)](https://www.skillsdirectory.com/skills/paulpas-framework-evaluation)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---




name: framework-evaluation
description: Runs automated empirical evaluation of software frameworks through reproducible benchmark harnesses, dependency graph security auditing, integration feasibility testing, and maintenance cost modeling to produce quantitative selection data.
license: MIT
compatibility: opencode
metadata:
  version: "1.0.0"
  domain: coding
  triggers: framework selection, technology evaluation, choose a framework, compare frameworks, tech stack decision, benchmark harness, dependency audit, framework scoring
  archetypes:
    - tactical
    - diagnostic
  anti_triggers:
    - brainstorming
    - vague ideation
    - long-form architecture
    - code golf
  response_profile:
    verbosity: medium
    directive_strength: high
    abstraction_level: operational
  role: implementation
  scope: implementation
  output-format: code
  content-types: [code, guidance, do-dont, examples]
  related-skills: framework-onboarding, framework-selection, framework-comparison-workflow, tool-evaluation-workflow




---





# Framework Evaluation Engine

Automates empirical evaluation of software frameworks through reproducible benchmark harnesses, dependency graph security auditing, integration feasibility testing, and maintenance cost modeling. When loaded, this skill makes the model act as a senior platform engineer — generating measurable, repeatable evaluation scripts that produce quantitative data (latency distributions, throughput percentiles, CVE severity scores, integration compatibility rates, TCO projections) to replace subjective framework debates with hard numbers.

## TL;DR Checklist

- [ ] Build automated benchmark harnesses that run each candidate against identical load profiles
- [ ] Run dependency graph audits: CVE lookup, license compliance, supply chain risk scoring
- [ ] Execute integration feasibility tests against your specific infrastructure stack
- [ ] Model 3-year maintenance costs using version history, breaking change rates, and community metrics
- [ ] Generate scaffold quality reports from each framework's CLI scaffolding tools
- [ ] Aggregate all quantitative results into a structured evaluation report JSON
- [ ] Feed the data into scoring matrices (use `framework-selection` for AHP-weighted decisions)

---

## When to Use

Use this skill when:

- Two or more frameworks need quantitative differentiation beyond theoretical feature comparisons
- Your team requires reproducible, auditable evidence for framework selection decisions
- Selecting a framework where performance characteristics directly impact SLA commitments
- Evaluating a framework that will be used across many services (scaling risk matters)
- A stakeholder needs hard numbers to justify or reject a framework recommendation
- Before committing to `framework-comparison-workflow` spike projects — this skill identifies which candidates are worth spiking
- Assessing supply chain risk for frameworks with deep dependency trees

---

## When NOT to Use

Avoid this skill for:

- First-time exploration of a completely unfamiliar domain — use `framework-utilization` or `modern-python-development` first to understand the landscape
- Micro-decisions where any reasonable tool would work equally well (overhead outweighs benefit)
- Frameworks that are already selected and in production — use `framework-performance-tuning` for optimization instead
- When requirements have not yet been elicited — go back to `framework-evaluation-criteria` first
- For evaluating infrastructure tools (CI runners, container runtimes) unless they are also application frameworks

---

## Core Workflow

### Step 1: Generate Automated Benchmark Harnesses

For each framework candidate under evaluation, create an automated benchmark harness that exercises the three most critical capabilities for your use case. Each harness must produce statistically meaningful results with proper warm-up, iteration counts, and percentile reporting — not a single run's average. The harness should be runnable as `python bench.py` from the project root and output JSON results to stdout.

**Benchmark design rules:**
- Include a warm-up phase (e.g., 100 iterations) before measurement begins
- Run at least 1,000 measured iterations or 60 seconds of sustained load, whichever is longer
- Report P50, P95, and P99 latencies — not just averages
- Record memory usage at start, end, and peak during the benchmark run
- Document the exact environment: OS, CPU, RAM, Python version, package versions

**Checkpoint:** Each benchmark harness must pass `python bench.py --dry-run` without errors, producing valid JSON. If the dry-run fails, the harness is not ready for comparison.

```python
"""Automated benchmark harness generator for framework evaluation.

Produces reproducible benchmark scripts that measure latency percentiles,
throughput, and memory consumption across multiple load iterations.
"""

from __future__ import annotations

import json
import os
import statistics
import subprocess
import sys
import time
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Optional


@dataclass
class BenchmarkResult:
    """Results from a single benchmark run with statistical summaries."""

    framework_name: str
    test_name: str
    iterations: int
    warmup_iterations: int
    total_wall_seconds: float
    throughput_rps: float
    latency_percentiles: dict[str, float]  # "p50", "p95", "p99" in milliseconds
    mean_latency_ms: float
    std_dev_latency_ms: float
    memory_start_mb: float
    memory_end_mb: float
    memory_peak_mb: float = 0.0
    errors: int = 0
    error_rate_pct: float = 0.0
    environment: dict[str, str] = field(default_factory=dict)

    def to_dict(self) -> dict:
        """Serialize for JSON output."""
        return asdict(self)

    def summary_table(self) -> str:
        """Human-readable one-line summary."""
        return (
            f"{self.framework_name}/{self.test_name}: "
            f"{self.throughput_rps:.0f} rps | P50={self.latency_percentiles['p50']:.1f}ms "
            f"P95={self.latency_percentiles['p95']:.1f}ms P99={self.latency_percentiles['p99']:.1f}ms "
            f"mem delta={self.memory_end_mb - self.memory_start_mb:+.1f}MB"
        )


def collect_environment_info() -> dict[str, str]:
    """Capture the runtime environment for benchmark reproducibility."""
    return {
        "os": os.uname().sysname + " " + os.uname().release,
        "python_version": sys.version.split()[0],
        "platform": sys.platform,
        "cpu_count": str(os.cpu_count() or 0),
        "hostname": os.environ.get("HOSTNAME", "unknown"),
    }


def run_benchmark(
    *,
    framework_name: str,
    test_function: callable,
    iterations: int = 1000,
    warmup_iterations: int = 100,
    test_name: str = "default",
    timeout_per_iteration_seconds: float = 30.0,
) -> BenchmarkResult:
    """Execute a benchmark suite and return statistically summarized results.

    Args:
        framework_name: Identifier for the framework being benchmarked.
        test_function: Callable that performs one unit of work. Must raise on failure.
        iterations: Number of measured iterations (after warmup).
        warmup_iterations: Initial iterations not included in measurements.
        test_name: Label for this specific test within the harness.
        timeout_per_iteration_seconds: Max seconds per single iteration.

    Returns:
        BenchmarkResult with computed percentiles, throughput, and memory metrics.
    """
    env = collect_environment_info()
    latencies: list[float] = []
    errors = 0

    # Warmup phase — discard all measurements
    for _ in range(warmup_iterations):
        try:
            start = time.perf_counter()
            test_function()
            elapsed_ms = (time.perf_counter() - start) * 1000
        except Exception:
            pass

    # Measured phase
    memory_samples: list[float] = []
    total_start = time.perf_counter()

    for i in range(iterations):
        try:
            import psutil  # type: ignore  # optional dependency for memory tracking

            process = psutil.Process(os.getpid())
            mem_before = process.memory_info().rss / (1024 * 1024)
            memory_samples.append(mem_before)

            start = time.perf_counter()
            test_function()
            elapsed_ms = (time.perf_counter() - start) * 1000
            latencies.append(elapsed_ms)
        except Exception:
            errors += 1
        finally:
            if memory_samples:
                try:
                    import psutil  # type: ignore

                    mem_after = psutil.Process(os.getpid()).memory_info().rss / (1024 * 1024)
                    memory_samples.append(mem_after)
                except Exception:
                    pass

    total_wall = time.perf_counter() - total_start

    if not latencies:
        raise RuntimeError(
            f"Framework '{framework_name}' failed all {iterations} measured iterations. "
            "Check that the test function executes successfully."
        )

    sorted_latencies = sorted(latencies)
    n = len(sorted_latencies)

    def percentile(data: list[float], pct: float) -> float:
        """Compute percentile using nearest-rank method."""
        k = max(0, int(pct / 100.0 * len(data)) - 1)
        return data[min(k, len(data) - 1)]

    memory_start = min(memory_samples) if memory_samples else 0.0
    memory_end = max(memory_samples) if memory_samples else 0.0
    memory_peak = max(memory_samples) if memory_samples else 0.0

    return BenchmarkResult(
        framework_name=framework_name,
        test_name=test_name,
        iterations=n,
        warmup_iterations=warmup_iterations,
        total_wall_seconds=total_wall,
        throughput_rps=n / total_wall if total_wall > 0 else 0.0,
        latency_percentiles={
            "p50": round(percentile(sorted_latencies, 50), 2),
            "p95": round(percentile(sorted_latencies, 95), 2),
            "p99": round(percentile(sorted_latencies, 99), 2),
        },
        mean_latency_ms=round(statistics.mean(latencies), 2),
        std_dev_latency_ms=round(statistics.stdev(latencies), 2) if len(latencies) > 1 else 0.0,
        memory_start_mb=round(memory_start, 1),
        memory_end_mb=round(memory_end, 1),
        memory_peak_mb=round(memory_peak, 1),
        errors=errors,
        error_rate_pct=round(errors / n * 100, 2) if n > 0 else 0.0,
        environment=env,
    )


def generate_benchmark_script(
    framework_name: str,
    test_cases: list[dict[str, str | callable]],
    output_path: Optional[str] = None,
) -> Path:
    """Generate a standalone benchmark script file for a framework.

    Args:
        framework_name: Identifier for the framework being tested.
        test_cases: List of dicts with 'name' (str) and 'func' (callable) keys.
        output_path: Where to write the script. Defaults to ./bench_{framework_name}.py

    Returns:
        Path to the generated benchmark script.
    """
    output_path = output_path or f"bench_{framework_name}.py"
    path = Path(output_path)

    # Write a self-contained benchmark runner as a standalone script
    script_lines = [
        '#!/usr/bin/env python3',
        f'"""Benchmark harness for {framework_name} — generated by framework-evaluation skill."""',
        'import json, sys, time',
        'from benchmark_lib import run_benchmark, BenchmarkResult',
        '',
    ]

    # Import section for the framework being tested
    script_lines.extend([
        f'# Framework imports',
    ])
    for tc in test_cases:
        # Each test case's import will be embedded inline
        pass

    script_lines.append(
        '''if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="Run benchmarks for """ + framework_name + '''")
    parser.add_argument("--dry-run", action="store_true", help="Validate without running full suite")
    args = parser.parse_args()

    results = []'''
    )

    for tc in test_cases:
        tc_name = tc["name"]
        script_lines.append(
            f"\n    # Test: {tc_name}"
        )
        script_lines.append(f"    try:")
        script_lines.append(f"        result = run_benchmark(")
        script_lines.append(f"            framework_name='{framework_name}',")
        script_lines.append(f"            test_function=lambda: your_{tc_name}_implementation(),")
        script_lines.append(f"            iterations=1000,")
        script_lines.append(f"            warmup_iterations=100,")
        script_lines.append(f"            test_name='{tc_name}',")
        script_lines.append(f"        )")
        script_lines.append(f"        results.append(result.to_dict())")
        script_lines.append(f"        print(result.summary_table(), file=sys.stderr)")

    script_lines.append(
        '''
    output = {
        "framework": "'"" + framework_name + '''",
        "tests": results,
        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
    }
    json.dump(output, sys.stdout, indent=2)
'''
    )

    path.write_text("\n".join(script_lines) + "\n")
    return path


# Example: Build a benchmark suite for comparing web frameworks
def example_web_framework_benchmarks():
    """Demonstrate building a benchmark harness for HTTP framework comparison."""

    import importlib

    # Test case 1: Simple string endpoint
    def bench_simple_string(framework_module):
        """Return the test function for a simple text response endpoint."""
        app = framework_module.create_app()  # Must be implemented per-framework

        def test_fn():
            with app.test_client() as client:
                resp = client.get("/hello")
                assert resp.status_code == 200
                assert resp.data == b"Hello, World!"

        return test_fn

    # Test case 2: JSON serialization endpoint
    def bench_json_response(framework_module):
        """Return the test function for a JSON response endpoint."""
        app = framework_module.create_app()

        def test_fn():
            with app.test_client() as client:
                resp = client.get("/api/data")
                assert resp.status_code == 200
                data = resp.get_json()
                assert "items" in data and len(data["items"]) == 100

        return test_fn

    # Test case 3: Database query endpoint
    def bench_db_query(framework_module):
        """Return the test function for a database-backed endpoint."""
        app = framework_module.create_app()

        def test_fn():
            with app.test_client() as client:
                resp = client.get("/api/users/1")
                assert resp.status_code == 200
                data = resp.get_json()
                assert "username" in data

        return test_fn

    # The harness would be called per-framework like this:
    # from fastapi_impl import create_app, test_client_context
    # result = run_benchmark(
    #     framework_name="FastAPI",
    #     test_function=bench_simple_string(create_app),
    #     iterations=1000,
    #     warmup_iterations=100,
    #     test_name="simple_string",
    # )
    pass
```

### Step 2: Run Dependency Graph Security Audit

For each candidate framework, analyze its full dependency tree to identify security vulnerabilities, license compliance issues, and supply chain risks. This step produces a structured risk score based on CVE history, maintainer concentration, update frequency, and known supply chain incidents.

**Audit procedure:**
- Install the framework in an isolated environment (virtualenv or container)
- Export the full dependency tree (e.g., `pip freeze` for Python, `npm ls --all` for Node.js)
- Query each package against known vulnerability databases (CVE, GHSA, PyPI advisory database)
- Compute a composite security score based on severity-weighted CVE count
- Verify license compatibility: no GPL/LGPL conflicts with your distribution model

**Checkpoint:** Every candidate must pass the license compliance check. A single GPL dependency that conflicts with your project's license is an automatic disqualifier — no exceptions.

```python
"""Dependency graph security auditor for framework evaluation.

Analyzes dependency trees, queries CVE databases, computes composite
security scores, and flags license compliance issues.
"""

from __future__ import annotations

import hashlib
import json
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional


class LicenseType(str, Enum):
    """Common OSS license categories for compliance checking."""
    PERMISSIVE = "permissive"       # MIT, BSD-2, BSD-3, Apache 2.0
    WEAK_COPYLEFT = "weak_copyleft"  # LGPL, MPL-2.0, EPL-2.0
    STRONG_COPYLEFT = "strong_copyleft"  # GPL-2.0, GPL-3.0, AGPL-3.0
    COMMERCIAL = "commercial"        # Requires paid license for use
    UNKNOWN = "unknown"


class CveSeverity(str, Enum):
    """CVE severity levels from CVSS scoring."""
    CRITICAL = "critical"   # CVSS 9.0–10.0
    HIGH = "high"          # CVSS 7.0–8.9
    MEDIUM = "medium"      # CVSS 4.0–6.9
    LOW = "low"            # CVSS 0.1–3.9


@dataclass
class Dependency:
    """A single package in the dependency tree."""

    name: str
    version: str
    license_type: LicenseType = LicenseType.UNKNOWN
    direct: bool = False  # Direct dependency of the framework, or transitive
    latest_version: Optional[str] = None
    last_updated_days_ago: Optional[int] = None
    vulnerabilities: list[dict] = field(default_factory=list)


@dataclass
class DependencySecurityReport:
    """Complete security audit report for a framework's dependency graph."""

    framework_name: str
    audit_date: str
    total_dependencies: int
    direct_dependencies: int
    transitive_dependencies: int
    dependencies: list[Dependency] = field(default_factory=list)
    cve_summary: dict[str, int] = field(default_factory=lambda: {
        "critical": 0, "high": 0, "medium": 0, "low": 0,
    })
    license_issues: list[str] = field(default_factory=list)
    supply_chain_risks: list[str] = field(default_factory=list)
    security_score: float = 0.0  # 0 (worst) to 100 (best)

    def has_critical_cves(self) -> bool:
        """Check if any critical or high CVEs exist."""
        return self.cve_summary["critical"] > 0 or self.cve_summary["high"] > 0

    def is_license_compliant(self, allowed_licenses: set[str]) -> bool:
        """Check if all dependencies use only allowed licenses."""
        for dep in self.dependencies:
            if dep.license_type == LicenseType.UNKNOWN:
                return False  # Unknown license is a compliance risk
            if dep.license_type == LicenseType.COMMERCIAL:
                return False
            if dep.license_type == LicenseType.STRONG_COPYLEFT:
                if dep.license_type.value.lower() not in allowed_licenses:
                    self.license_issues.append(
                        f"{dep.name}@{dep.version}: {dep.license_type.value} license "
                        f"conflicts with project licensing model"
                    )
                    return False
        return len(self.license_issues) == 0

    def to_dict(self) -> dict:
        """Serialize for JSON report output."""
        return {
            "framework": self.framework_name,
            "audit_date": self.audit_date,
            "summary": {
                "total_deps": self.total_dependencies,
                "cve_counts": self.cve_summary,
                "security_score": round(self.security_score, 1),
                "license_issues_count": len(self.license_issues),
                "supply_chain_risks_count": len(self.supply_chain_risks),
            },
            "license_issues": self.license_issues,
            "supply_chain_risks": self.supply_chain_risks,
            "vulnerable_dependencies": [
                dep.name for dep in self.dependencies if dep.vulnerabilities
            ],
        }


def compute_security_score(report: DependencySecurityReport) -> float:
    """Compute a composite security score from 0–100.

    Scoring formula:
      - Start at 100
      - Subtract: critical CVEs × 15, high × 8, medium × 3, low × 1
      - Subtract: license issues × 10 (compliance is non-negotiable)
      - Subtract: supply chain risks × 5
      - Floor at 0

    A score below 40 indicates significant dependency risk requiring mitigation.
    """
    score = 100.0
    score -= report.cve_summary["critical"] * 15
    score -= report.cve_summary["high"] * 8
    score -= report.cve_summary["medium"] * 3
    score -= report.cve_summary["low"] * 1
    score -= len(report.license_issues) * 10
    score -= len(report.supply_chain_risks) * 5
    return max(0.0, min(100.0, round(score, 1)))


def audit_dependency_graph(
    framework_name: str,
    dependencies: list[dict],  # Raw dependency data from pip freeze / npm ls
    cve_lookup_table: dict[str, list[dict]] | None = None,
) -> DependencySecurityReport:
    """Run a full security audit on a framework's dependency graph.

    Args:
        framework_name: Identifier for the framework being audited.
        dependencies: List of dicts with 'name', 'version', 'license' keys
                      as exported from the package manager.
        cve_lookup_table: Optional pre-fetched CVE data keyed by package name.
                          Format: {"package_name": [{"cve_id", "severity", "fixed_version"}]}

    Returns:
        DependencySecurityReport with computed scores and issue lists.
    """
    license_map = {
        "MIT": LicenseType.PERMISSIVE,
        "BSD-2-Clause": LicenseType.PERMISSIVE,
        "BSD-3-Clause": LicenseType.PERMISSIVE,
        "Apache-2.0": LicenseType.PERMISSIVE,
        "ISC": LicenseType.PERMISSIVE,
        "LGPL-2.1": LicenseType.WEAK_COPYLEFT,
        "LGPL-3.0": LicenseType.WEAK_COPYLEFT,
        "MPL-2.0": LicenseType.WEAK_COPYLEFT,
        "GPL-2.0": LicenseType.STRONG_COPYLEFT,
        "GPL-3.0": LicenseType.STRONG_COPYLEFT,
        "AGPL-3.0": LicenseType.STRONG_COPYLEFT,
    }

    cve_lookup = cve_lookup_table or {}
    deps: list[Dependency] = []
    seen = set()
    direct_count = 0

    for raw in dependencies:
        name = raw["name"]
        version = raw.get("version", "unknown")
        license_raw = raw.get("license", "").strip()

        # Deduplicate — a package may appear in both direct and transitive lists
        key = f"{name}=={version}"
        if key in seen:
            continue
        seen.add(key)

        dep = Dependency(
            name=name,
            version=version,
            license_type=license_map.get(license_raw, LicenseType.UNKNOWN),
            direct=name not in [d for d in deps],  # First appearance = direct
        )

        if dep.direct:
            direct_count += 1

        # Look up known CVEs
        vulns = cve_lookup.get(name, [])
        dep.vulnerabilities = vulns

        for vuln in vulns:
            severity = CveSeverity(vuln.get("severity", "low").lower())
            report.cve_summary[severity.value] = (
                report.cve_summary.get(severity.value, 0) + 1
            )

        deps.append(dep)

    # Compute final score
    report = DependencySecurityReport(
        framework_name=framework_name,
        audit_date=datetime.now().isoformat(),
        total_dependencies=len(deps),
        direct_dependencies=direct_count,
        transitive_dependencies=len(deps) - direct_count,
        dependencies=deps,
        security_score=compute_security_score(report),
    )

    # Add supply chain risk indicators
    for dep in deps:
        if dep.license_type == LicenseType.UNKNOWN:
            report.supply_chain_risks.append(
                f"{dep.name}: Unknown license — cannot verify compliance"
            )
        if dep.version.startswith("0.") and dep.last_updated_days_ago and dep.last_updated_days_ago > 365:
            report.supply_chain_risks.append(
                f"{dep.name}@{dep.version}: Pre-1.0 version with no updates in {dep.last_updated_days_ago} days"
            )

    return report


# Example: Audit dependency graph for two web frameworks
def example_dependency_audit():
    """Demonstrate dependency auditing for FastAPI vs Flask."""

    # Simulated dependency data from pip freeze
    fastapi_deps = [
        {"name": "fastapi", "version": "0.109.2", "license": "MIT"},
        {"name": "uvicorn", "version": "0.27.1", "license": "BSD-3-Clause"},
        {"name": "pydantic", "version": "2.5.3", "license": "MIT"},
        {"name": "starlette", "version": "0.36.3", "license": "BSD-3-Clause"},
        {"name": "sniffio", "version": "1.3.1", "license": "BSD-3-Clause"},
        {"name": "typing_extensions", "version": "4.9.0", "license": "PSF"},
    ]

    flask_deps = [
        {"name": "flask", "version": "3.0.2", "license": "BSD-3-Clause"},
        {"name": "werkzeug", "version": "3.0.1", "license": "BSD-3-Clause"},
        {"name": "jinja2", "version": "3.1.3", "license": "BSD-3-Clause"},
        {"name": "click", "version": "8.1.7", "license": "BSD-3-Clause"},
        {"name": "itsdangerous", "version": "2.2.0", "license": "BSD-3-Clause"},
        {"name": "markupsafe", "version": "2.1.4", "license": "BSD-3-Clause"},
    ]

    # Simulated CVE lookup data (in production, fetch from PyPI advisory database)
    mock_cves = {}  # No known CVEs in this example

    fastapi_report = audit_dependency_graph("FastAPI", fastapi_deps, mock_cves)
    flask_report = audit_dependency_graph("Flask", flask_deps, mock_cves)

    print(f"FastAPI security score: {fastapi_report.security_score}/100")
    print(f"Flask security score: {flask_report.security_score}/100")
    print(json.dumps(fastapi_report.to_dict()["summary"], indent=2))
```

### Step 3: Execute Integration Feasibility Tests

Validate each candidate framework's compatibility with your specific infrastructure stack. Create automated tests that exercise real integration points — database connections, authentication flows, serialization formats, and CI/CD pipeline hooks. Each test must pass or fail with a deterministic result, not "it works on my machine."

**Integration test categories:**
- **Database connectivity**: Can the framework connect to your target database(s) using your preferred driver/library? Measure connection setup time and query latency.
- **Authentication flow**: Can the framework integrate with your auth provider (OAuth2, JWT, SAML)? Test token validation and session management.
- **Data format compatibility**: Does the framework natively handle your required data formats (JSON Schema versions, Protocol Buffers, Avro)?
- **Observability hooks**: Does the framework support OpenTelemetry integration, structured logging in JSON, and Prometheus metrics export?

**Checkpoint:** A framework must pass at least 80% of integration tests to be considered viable. Any failure in database connectivity or authentication is an automatic knockout — those are non-negotiable for production systems.

```python
"""Integration feasibility test harness for framework evaluation.

Tests a framework's compatibility with a specific infrastructure stack
by running automated checks against real connection targets.
"""

from __future__ import annotations

import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional


class TestStatus(str, Enum):
    PASS = "pass"
    FAIL = "fail"
    SKIP = "skip"  # Inapplicable to this framework (e.g., ORM-only framework tested for no-ORM compatibility)


@dataclass
class IntegrationTestCase:
    """A single integration test case with results."""

    name: str
    category: str       # "database", "auth", "serialization", "observability"
    status: TestStatus = TestStatus.SKIP
    execution_time_ms: float = 0.0
    error_message: Optional[str] = None
    details: dict = field(default_factory=dict)

    def to_dict(self) -> dict:
        result = {
            "name": self.name,
            "category": self.category,
            "status": self.status.value,
            "execution_time_ms": round(self.execution_time_ms, 1),
        }
        if self.error_message:
            result["error"] = self.error_message
        if self.details:
            result["details"] = self.details
        return result


@dataclass
class IntegrationTestReport:
    """Complete integration test results for a framework against an infrastructure stack."""

    framework_name: str
    target_stack_description: str
    total_tests: int
    passed: int = 0
    failed: int = 0
    skipped: int = 0
    tests: list[IntegrationTestCase] = field(default_factory=list)
    overall_compatibility_pct: float = 0.0

    def run_test(self, test_case: IntegrationTestCase, test_fn: callable) -> None:
        """Execute a single integration test and record results."""
        start = time.perf_counter()
        try:
            result = test_fn()
            elapsed_ms = (time.perf_counter() - start) * 1000

            if result is True or result is None:
                test_case.status = TestStatus.PASS
                test_case.execution_time_ms = elapsed_ms
                self.passed += 1
            else:
                # test_fn returns error message on failure
                test_case.status = TestStatus.FAIL
                test_case.execution_time_ms = elapsed_ms
                test_case.error_message = str(result) if result else "Test failed without message"
                self.failed += 1
        except Exception as e:
            elapsed_ms = (time.perf_counter() - start) * 1000
            test_case.status = TestStatus.FAIL
            test_case.execution_time_ms = elapsed_ms
            test_case.error_message = str(e)
            self.failed += 1

    @property
    def compatibility_percentage(self) -> float:
        """Percentage of tests that passed (excluding skipped)."""
        tested = self.passed + self.failed
        if tested == 0:
            return 0.0
        return round(self.passed / tested * 100, 1)

    def is_viable(self, min_compatibility_pct: float = 80.0) -> bool:
        """Check if framework meets minimum compatibility threshold."""
        return self.compatibility_percentage >= min_compatibility_pct

    def has_nonnegotiable_failure(self, categories: list[str] | None = None) -> bool:
        """Check for failures in non-negotiable integration categories.

        Default non-negotiable categories: database connectivity and authentication.
        """
        if categories is None:
            categories = ["database", "auth"]
        return any(
            t.status == TestStatus.FAIL and t.category in categories
            for t in self.tests
        )

    def to_dict(self) -> dict:
        return {
            "framework": self.framework_name,
            "target_stack": self.target_stack_description,
            "summary": {
                "total": self.total_tests,
                "passed": self.passed,
                "failed": self.failed,
                "skipped": self.skipped,
                "compatibility_pct": self.compatibility_percentage,
                "viable": self.is_viable(),
            },
            "tests": [t.to_dict() for t in self.tests],
        }


def build_integration_suite(
    framework_name: str,
    target_stack: dict[str, str],  # {"database": "postgresql", "auth_provider": "okta", ...}
) -> tuple[IntegrationTestReport, list[tuple[IntegrationTestCase, callable]]]:
    """Build a complete integration test suite for evaluating a framework.

    Args:
        framework_name: Identifier for the framework being tested.
        target_stack: Dict describing the infrastructure stack to test against.

    Returns:
        Tuple of (report object, list of (test_case, test_function) pairs).
        Run each pair through report.run_test() to populate results.
    """
    report = IntegrationTestReport(
        framework_name=framework_name,
        target_stack_description=f"Stack: {', '.join(f'{k}={v}' for k, v in target_stack.items())}",
        total_tests=0,
    )

    tests_to_run: list[tuple[IntegrationTestCase, callable]] = []

    # Test 1: Database connectivity (if framework supports databases)
    if target_stack.get("database"):
        test_case = IntegrationTestCase(
            name=f"connect_to_{target_stack['database']}",
            category="database",
        )
        tests_to_run.append((test_case, lambda: build_db_test_function(target_stack["database"])))
        report.total_tests += 1

    # Test 2: Authentication flow integration
    auth_provider = target_stack.get("auth_provider")
    if auth_provider:
        test_case = IntegrationTestCase(
            name=f"integrate_{auth_provider}_auth",
            category="auth",
        )
        tests_to_run.append((test_case, lambda: build_auth_test_function(auth_provider)))
        report.total_tests += 1

    # Test 3: JSON serialization compatibility
    test_case = IntegrationTestCase(
        name="json_serialization_compatibility",
        category="serialization",
    )
    tests_to_run.append((test_case, build_serialization_test_function()))
    report.total_tests += 1

    # Test 4: OpenTelemetry integration (if observability requested)
    if target_stack.get("observability") == "opentelemetry":
        test_case = IntegrationTestCase(
            name="opentelemetry_integration",
            category="observability",
        )
        tests_to_run.append((test_case, build_otel_test_function()))
        report.total_tests += 1

    return report, tests_to_run


def build_db_test_function(db_type: str) -> callable:
    """Return a test function that verifies database connectivity."""
    def test_fn():
        # This would connect to the actual database in production evaluation
        # For the framework evaluation skill, this demonstrates the pattern
        if db_type == "postgresql":
            import psycopg2  # type: ignore
            # In real evaluation: conn = psycopg2.connect(dsn=your_dsn)
            pass
        return None  # Success

    return test_fn


def build_auth_test_function(provider: str) -> callable:
    """Return a test function for auth integration."""
    def test_fn():
        if provider == "okta":
            # Would verify OAuth2 token validation with Okta's JWKS endpoint
            pass
        elif provider == "auth0":
            # Would verify JWT verification against Auth0 tenant
            pass
        return None

    return test_fn


def build_serialization_test_function() -> callable:
    """Return a test function for data format compatibility."""
    def test_fn():
        import json
        sample = {"id": 1, "name": "test", "tags": ["a", "b"]}
        serialized = json.dumps(sample)
        deserialized = json.loads(serialized)
        assert deserialized == sample
        return None

    return test_fn


def build_otel_test_function() -> callable:
    """Return a test function for OpenTelemetry integration."""
    def test_fn():
        try:
            from opentelemetry import trace  # type: ignore
            tracer = trace.get_tracer("framework-eval")
            with tracer.start_as_current_span("test-span"):
                pass
            return None
        except ImportError:
            raise RuntimeError(
                "OpenTelemetry SDK not installed. Install with: pip install opentelemetry-api opentelemetry-sdk"
            )

    return test_fn


# Example: Run integration suite evaluation
def example_integration_evaluation():
    """Demonstrate integration testing across FastAPI and Flask against a target stack."""

    target_stack = {
        "database": "postgresql",
        "auth_provider": "okta",
        "observability": "opentelemetry",
        "cache": "redis",
    }

    report, tests_to_run = build_integration_suite("FastAPI", target_stack)

    for test_case, test_fn in tests_to_run:
        report.run_test(test_case, test_fn)

    print(f"FastAPI integration compatibility: {report.compatibility_percentage}%")
    print(f"Viable for target stack: {report.is_viable()}")
    if report.has_nonnegotiable_failure():
        print("NON-NEGOTIABLE FAILURE: Database or auth integration failed.")
```

### Step 4: Model Long-Term Maintenance Costs

Project the 3-year total cost of ownership for each framework using observable metrics from version history, release patterns, and community activity. TCO is not just licensing — it includes developer ramp-up time, upgrade costs when breaking changes occur, security patching effort, and the opportunity cost of framework-related work versus business logic development.

**TCO model components:**
- **Ramp-up cost**: Weeks × (average developer salary / 40 hours) for team to reach productivity
- **Annual upgrade cost**: Number of breaking minor versions per year × estimated fix hours × hourly rate
- **Security patching**: Monthly CVE count × average hours to assess and apply patches
- **Community support burden**: Hours per month the team spends troubleshooting framework issues vs. business logic

**Checkpoint:** If any framework's projected 3-year TCO exceeds the next-best alternative by more than 40%, flag it prominently — the performance or feature advantages must justify that cost delta.

```python
"""Maintenance cost modeler for long-term framework TCO estimation.

Projects 3-year total cost of ownership based on observable version history,
release patterns, breaking change rates, and community activity metrics.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import date


@dataclass
class VersionHistoryEntry:
    """A single release from a framework's version history."""

    version: str
    release_date: str  # ISO format YYYY-MM-DD
    is_breaking_change: bool = False
    has_security_patches: bool = False
    upgrade_hours_required: float = 0.0  # Estimated developer hours for migration


@dataclass
class TCOProjection:
    """3-year total cost of ownership projection for a framework."""

    framework_name: str
    evaluation_date: str
    hourly_rate: float  # Average fully-loaded developer hourly rate

    # Ramp-up
    ramp_up_weeks: float
    team_size: int

    # Annual recurring costs
    annual_upgrade_hours: float        # Hours per year for breaking change upgrades
    annual_security_patch_hours: float  # Hours per year for CVE patching and assessment
    annual_support_hours: float        # Hours per month troubleshooting framework issues (× 12)

    # Derived costs
    ramp_up_cost: float = 0.0
    annual_upgrade_cost: float = 0.0
    annual_security_cost: float = 0.0
    annual_support_cost: float = 0.0
    year_1_total: float = 0.0
    year_2_total: float = 0.0
    year_3_total: float = 0.0
    three_year_tco: float = 0.0

    # Version history data (for transparency)
    version_history_summary: dict = field(default_factory=dict)

    def compute(self) -> "TCOProjection":
        """Calculate all cost projections from input parameters."""
        hours_in_week = 40.0

        # Ramp-up: one-time cost in year 1
        self.ramp_up_cost = (
            self.ramp_up_weeks * hours_in_week * self.team_size * self.hourly_rate
        )

        # Annual costs
        self.annual_upgrade_cost = self.annual_upgrade_hours * self.hourly_rate
        self.annual_security_cost = self.annual_security_patch_hours * self.hourly_rate
        self.annual_support_cost = (
            self.annual_support_hours * 12 * self.hourly_rate
        )

        year_recurring = (
            self.annual_upgrade_cost + self.annual_security_cost + self.annual_support_cost
        )

        # Year 1 includes ramp-up; years 2–3 are recurring only
        self.year_1_total = self.ramp_up_cost + year_recurring
        self.year_2_total = year_recurring
        self.year_3_total = year_recurring
        self.three_year_tco = (
            self.year_1_total + self.year_2_total + self.year_3_total
        )

        return self

    @classmethod
    def from_version_history(
        cls,
        framework_name: str,
        hourly_rate: float,
        team_size: int,
        ramp_up_weeks: float,
        versions: list[VersionHistoryEntry],
        avg_security_patch_hours: float = 2.0,  # Hours per CVE to assess and patch
    ) -> TCOProjection:
        """Build a TCO projection from historical version data.

        Computes annualized upgrade costs and security patch burden from
        observed release patterns in the framework's Git history.

        Args:
            framework_name: Identifier for the framework.
            hourly_rate: Fully-loaded developer hourly rate (salary + overhead).
            team_size: Number of developers who will use this framework.
            ramp_up_weeks: Estimated weeks for a new team to reach full productivity.
            versions: Historical releases with breaking change and security metadata.
            avg_security_patch_hours: Average hours spent per CVE incident.

        Returns:
            TCOProjection with computed costs.
        """
        total_years = 3.0

        # Count events over the observed history window
        breaking_changes = [v for v in versions if v.is_breaking_change]
        security_patches = [v for v in versions if v.has_security_patches]

        if not versions:
            raise ValueError(
                "Version history is empty — cannot compute TCO without release data."
            )

        # Compute the time span covered by the version history
        dates = []
        for v in versions:
            try:
                dates.append(date.fromisoformat(v.release_date))
            except ValueError:
                continue

        if len(dates) >= 2:
            date_span_days = (max(dates) - min(dates)).days
            years_of_history = max(date_span_days / 365.0, 1.0)
        else:
            years_of_history = 1.0

        # Annualize rates from observed history
        annual_breaking_changes = len(breaking_changes) / years_of_history
        annual_security_incidents = len(security_patches) / years_of_history

        # Estimate upgrade hours per breaking change (average across versions)
        avg_upgrade_hours = sum(
            v.upgrade_hours_required for v in breaking_changes if v.upgrade_hours_required > 0
        ) / max(len(breaking_changes), 1)
        if avg_upgrade_hours == 0:
            # Heuristic: assume 8 hours per minor version, 24 hours per major
            avg_upgrade_hours = 16.0

        # Project annual costs for team of given size
        annual_upgrade_hours = annual_breaking_changes * avg_upgrade_hours * team_size
        annual_security_hours = annual_security_incidents * avg_security_patch_hours * team_size

        return cls(
            framework_name=framework_name,
            evaluation_date=date.today().isoformat(),
            hourly_rate=hourly_rate,
            ramp_up_weeks=ramp_up_weeks,
            team_size=team_size,
            annual_upgrade_hours=round(annual_upgrade_hours, 1),
            annual_security_patch_hours=round(annual_security_hours, 1),
            annual_support_hours=0.0,  # Estimated separately from community feedback
            version_history_summary={
                "versions_analyzed": len(versions),
                "years_of_history": round(years_of_history, 1),
                "total_breaking_changes": len(breaking_changes),
                "total_security_patches": len(security_patches),
                "annualized_breaking_changes": round(annual_breaking_changes, 2),
                "avg_upgrade_hours_per_change": round(avg_upgrade_hours, 1),
            },
        ).compute()

    def summary(self) -> str:
        """Generate a human-readable cost projection summary."""
        lines = [
            f"TCO Projection for {self.framework_name} (3 years)",
            "=" * 50,
            "",
            f"Ramp-up cost (year 1):      ${self.ramp_up_cost:>12,.0f}",
            f"Annual upgrade cost:         ${self.annual_upgrade_cost:>12,.0f}/yr",
            f"Annual security patching:    ${self.annual_security_cost:>12,.0f}/yr",
            f"Annual framework support:    ${self.annual_support_cost:>12,.0f}/yr",
            "",
            f"Year 1 total:                ${self.year_1_total:>12,.0f}",
            f"Year 2 total:                ${self.year_2_total:>12,.0f}",
            f"Year 3 total:                ${self.year_3_total:>12,.0f}",
            "",
            f"Three-year TCO:              ${self.three_year_tco:>12,.0f}",
            "",
            f"Effective monthly cost:      ${self.three_year_tco / 36:>12,.0f}/mo",
        ]
        return "\n".join(lines)


# Example: Compare TCO for FastAPI vs Flask vs Django
def example_tco_comparison():
    """Demonstrate 3-year TCO comparison across three frameworks."""

    hourly_rate = 85.0  # Fully-loaded rate
    team_size = 4       # Developers who will use the framework

    # Simulated version history for FastAPI (rapid release, fewer breaking changes in minor)
    fastapi_versions = [
        VersionHistoryEntry("0.65.0", "2021-03-01", is_breaking_change=False),
        VersionHistoryEntry("0.68.0", "2021-07-15", is_breaking_change=False),
        VersionHistoryEntry("0.95.0", "2022-08-01", is_breaking_change=True, upgrade_hours_required=4.0),
        VersionHistoryEntry("0.100.0", "2023-02-15", is_breaking_change=False, has_security_patches=True),
        VersionHistoryEntry("0.109.0", "2023-11-01", is_breaking_change=False, has_security_patches=True),
    ]

    # Simulated version history for Flask (stable releases, infrequent breaking changes)
    flask_versions = [
        VersionHistoryEntry("1.1.4", "2020-11-01", has_security_patches=True),
        VersionHistoryEntry("2.0.0", "2021-05-01", is_breaking_change=True, upgrade_hours_required=16.0),
        VersionHistoryEntry("2.3.0", "2023-05-01", is_breaking_change=False),
        VersionHistoryEntry("3.0.0", "2023-11-01", is_breaking_change=True, upgrade_hours_required=20.0, has_security_patches=True),
    ]

    # Simulated version history for Django (longer release cycles, significant breaking changes)
    django_versions = [
        VersionHistoryEntry("3.2.0", "2021-04-06", is_breaking_change=False, has_security_patches=True),
        VersionHistoryEntry("4.0.0", "2022-12-03", is_breaking_change=True, upgrade_hours_required=40.0),
        VersionHistoryEntry("4.2.0", "2023-04-03", is_breaking_change=False),
        VersionHistoryEntry("5.0.0", "2023-12-04", is_breaking_change=True, upgrade_hours_required=35.0),
    ]

    fastapi_tco = TCOProjection.from_version_history(
        framework_name="FastAPI",
        hourly_rate=hourly_rate,
        team_size=team_size,
        ramp_up_weeks=3.0,  # Smaller API surface to learn
        versions=fastapi_versions,
    )

    flask_tco = TCOProjection.from_version_history(
        framework_name="Flask",
        hourly_rate=hourly_rate,
        team_size=team_size,
        ramp_up_weeks=1.5,  # Minimal API, well-documented
        versions=flask_versions,
    )

    django_tco = TCOProjection.from_version_history(
        framework_name="Django",
        hourly_rate=hourly_rate,
        team_size=team_size,
        ramp_up_weeks=2.0,  # Convention-over-configuration reduces decisions
        versions=django_versions,
    )

    print(fastapi_tco.summary())
    print()
    print(flask_tco.summary())
    print()
    print(django_tco.summary())

    # Comparison summary
    tcvs = [fastapi_tco.three_year_tco, flask_tco.three_year_tco, django_tco.three_year_tco]
    best_idx = tcvs.index(min(tcvs))
    names = ["FastAPI", "Flask", "Django"]
    print(f"\nLowest 3-year TCO: {names[best_idx]} at ${tcvs[best_idx]:,.0f}")
```

### Step 5: Generate Scaffold Quality Report

Evaluate the baseline code quality produced by each framework's CLI scaffolding tools. A framework that generates clean, well-structured boilerplate lowers the team's initial development friction and reduces early-stage bugs. Analyze the generated code for test coverage, type annotation presence, proper separation of concerns, and adherence to common conventions.

**Scaffold analysis dimensions:**
- **Test inclusion**: Does the scaffold include basic test files? What framework do they use?
- **Type annotations**: Percentage of functions with type hints in generated code
- **Separation of concerns**: Are handlers/models/routes separated or monolithic?
- **Configuration management**: Environment variables vs. hardcoded values vs. config files
- **Error handling patterns**: Proper exception handling vs. bare returns

**Checkpoint:** If a framework's scaffold produces zero tests and zero type annotations, note this as a quality concern — it shifts more baseline work onto the team from day one.

### Step 6: Aggregate Results into Evaluation Report JSON

Combine all quantitative data into a single structured evaluation report. The report should be machine-readable (JSON) for integration with CI/CD gates and human-readable tables for stakeholder presentations. Feed this data directly into scoring matrices for final selection decisions.

**Report structure:**
- `frameworks[]` — Array of per-framework evaluation results
- Each framework contains: `benchmark_results`, `dependency_audit`, `integration_tests`, `tco_projection`, `scaffold_quality_score`
- `aggregate_comparison` — Summary table with key metrics side-by-side

**Checkpoint:** The report must be valid JSON parseable by downstream tools. If generating a presentation, produce the same data in a markdown table format as well.

---

## Implementation Patterns

### Pattern 1: Aggregated Evaluation Report Generator

Produces the final machine-readable evaluation report and human-readable comparison table from all analysis phases.

```python
"""Aggregated framework evaluation report generator.

Combines results from benchmarks, dependency audits, integration tests,
and TCO modeling into a structured report suitable for stakeholder review
and automated decision gates.
"""

from __future__ import annotations

import json
import sys
from dataclasses import asdict, dataclass, field
from typing import Optional


@dataclass
class FrameworkEvaluation:
    """Complete evaluation results for a single framework candidate."""

    name: str
    version_evaluated: str

    # Phase 1: Benchmarks
    benchmark_results: list[dict] = field(default_factory=list)
    best_throughput_rps: float = 0.0
    worst_p99_latency_ms: float = 0.0

    # Phase 2: Dependency security audit
    dependency_audit_summary: dict = field(default_factory=dict)
    security_score: float = 0.0
    license_compliant: bool = True

    # Phase 3: Integration tests
    integration_compatibility_pct: float = 0.0
    is_integration_viable: bool = True
    nonnegotiable_failures: list[str] = field(default_factory=list)

    # Phase 4: TCO modeling
    tco_3year: float = 0.0
    annual_recurring_cost: float = 0.0
    ramp_up_weeks: float = 0.0

    # Phase 5: Scaffold quality (1-10 scale)
    scaffold_quality_score: float = 0.0

    @property
    def is_viable(self) -> bool:
        """A framework is viable only if it passes integration AND license checks."""
        return (
            self.is_integration_viable
            and self.license_compliant
            and not self.nonnegotiable_failures
        )

    def to_dict(self) -> dict:
        result = {
            "name": self.name,
            "version": self.version_evaluated,
            "viable": self.is_viable,
            "benchmarks": {
                "best_throughput_rps": round(self.best_throughput_rps, 0),
                "worst_p99_latency_ms": round(self.worst_p99_latency_ms, 1),
            },
            "dependency_audit": {
                "security_score": round(self.security_score, 1),
                "license_compliant": self.license_compliant,
                **self.dependency_audit_summary,
            },
            "integration": {
                "compatibility_pct": round(self.integration_compatibility_pct, 1),
                "viable": self.is_integration_viable,
            },
            "tco_3year": round(self.tco_3year, 0),
            "scaffold_quality": round(self.scaffold_quality_score, 1),
        }
        if self.nonnegotiable_failures:
            result["nonnegotiable_failures"] = self.nonnegotiable_failures
        return result


@dataclass
class EvaluationReport:
    """Complete framework evaluation report with aggregate comparison."""

    title: str
    target_stack_description: str
    evaluation_date: str
    frameworks: list[FrameworkEvaluation] = field(default_factory=list)

    @property
    def viable_frameworks(self) -> list[FrameworkEvaluation]:
        return [f for f in self.frameworks if f.is_viable]

    @property
    def best_throughput_framework(self) -> Optional[FrameworkEvaluation]:
        candidates = [f for f in self.viable_frameworks if f.best_throughput_rps > 0]
        return max(candidates, key=lambda f: f.best_throughput_rps) if candidates else None

    @property
    def lowest_tco_framework(self) -> Optional[FrameworkEvaluation]:
        candidates = [f for f in self.viable_frameworks if f.tco_3year > 0]
        return min(candidates, key=lambda f: f.tco_3year) if candidates else None

    @property
    def best_security_framework(self) -> Optional[FrameworkEvaluation]:
        return max(
            self.viable_frameworks,
            key=lambda f: f.security_score,
        ) if self.viable_frameworks else None

    def generate_markdown_table(self) -> str:
        """Generate a human-readable comparison table."""
        lines = [
            "# Framework Evaluation Results",
            "",
            f"**Target Stack:** {self.target_stack_description}",
            f"**Date:** {self.evaluation_date}",
            "",
            "| Metric | " + " | ".join(f.name for f in self.frameworks) + " |",
            "|--------|" + "|".join("------" for _ in self.frameworks) + "|",
        ]

        metrics = [
            ("Viable", lambda f: "Yes" if f.is_viable else "No"),
            ("Throughput (rps)", lambda f: f"{f.best_throughput_rps:.0f}"),
            ("P99 Latency (ms)", lambda f: f"{f.worst_p99_latency_ms:.1f}"),
            ("Security Score", lambda f: f"{f.security_score:.1f}/100"),
            ("License Compliant", lambda f: "Yes" if f.license_compliant else "No"),
            ("Integration %", lambda f: f"{f.integration_compatibility_pct}%"),
            ("3-Year TCO", lambda f: f"${f.tco_3year:,.0f}"),
            ("Scaffold Quality", lambda f: f"{f.scaffold_quality_score:.1f}/10"),
        ]

        for label, extractor in metrics:
            row = f"| {label} | " + " | ".join(extractor(f) for f in self.frameworks) + " |"
            lines.append(row)

        lines.extend(["", "---"])

        if self.best_throughput_framework:
            lines.append(
                f"> **Highest throughput:** {self.best_throughput_framework.name} "
                f"({self.best_throughput_framework.best_throughput_rps:.0f} rps)"
            )
        if self.lowest_tco_framework:
            lines.append(
                f"> **Lowest 3-year TCO:** {self.lowest_tco_framework.name} "
                f"(${self.lowest_tco_framework.tco_3year:,.0f})"
            )

        return "\n".join(lines) + "\n"

    def to_json(self) -> str:
        """Serialize the full report to JSON."""
        data = {
            "title": self.title,
            "target_stack": self.target_stack_description,
            "evaluation_date": self.evaluation_date,
            "frameworks": [f.to_dict() for f in self.frameworks],
            "aggregate": {},
        }

        if self.best_throughput_framework:
            data["aggregate"]["best_throughput"] = self.best_throughput_framework.name
        if self.lowest_tco_framework:
            data["aggregate"]["lowest_tco"] = self.lowest_tco_framework.name
        if self.best_security_framework:
            data["aggregate"]["best_security"] = self.best_security_framework.name

        return json.dumps(data, indent=2)


# Example: Generate a complete evaluation report
def example_complete_report():
    """Demonstrate generating a full evaluation report from simulated data."""

    report = EvaluationReport(
        title="REST API Framework Evaluation — Q1 2026",
        target_stack_description="Python 3.12, PostgreSQL 16, Okta OAuth2, OpenTelemetry, Docker/Kubernetes",
        evaluation_date="2026-01-15",
    )

    # Simulated results for three frameworks
    report.frameworks = [
        FrameworkEvaluation(
            name="FastAPI",
            version_evaluated="0.109.2",
            best_throughput_rps=45200,
            worst_p99_latency_ms=12.3,
            security_score=92.0,
            license_compliant=True,
            integration_compatibility_pct=100.0,
            is_integration_viable=True,
            tco_3year=385000,
            annual_recurring_cost=62000,
            ramp_up_weeks=3.0,
            scaffold_quality_score=8.5,
        ),
        FrameworkEvaluation(
            name="Flask",
            version_evaluated="3.0.2",
            best_throughput_rps=12800,
            worst_p99_latency_ms=48.7,
            security_score=88.0,
            license_compliant=True,
            integration_compatibility_pct=100.0,
            is_integration_viable=True,
            tco_3year=295000,
            annual_recurring_cost=48000,
            ramp_up_weeks=1.5,
            scaffold_quality_score=6.0,
        ),
        FrameworkEvaluation(
            name="Django REST Framework",
            version_evaluated="4.2.3",
            best_throughput_rps=8900,
            worst_p99_latency_ms=125.0,
            security_score=78.0,
            license_compliant=True,
            integration_compatibility_pct=87.5,
            is_integration_viable=True,
            tco_3year=445000,
            annual_recurring_cost=82000,
            ramp_up_weeks=2.0,
            scaffold_quality_score=7.5,
        ),
    ]

    # Output JSON report
    print("=== JSON Report ===")
    print(report.to_json())
    print()

    # Output markdown table
    print("=== Comparison Table ===")
    print(report.generate_markdown_table())
```

---

## Constraints

### MUST DO
- Run benchmarks with proper statistical rigor — always report P50/P95/P99, never just averages; minimum 1,000 measured iterations or 60 seconds of load
- Audit every framework's full dependency tree, not just direct dependencies — transitive CVEs are production risks
- Verify license compliance for all dependencies including transitive ones — a single GPL conflict is a disqualifier regardless of performance advantages
- Run integration tests against your actual infrastructure, not generic examples — test against the real PostgreSQL instance, the real Okta tenant, the real Redis cluster
- Model 3-year TCO using historical version data when available; fall back to heuristic estimates only if no release history exists
- Document every assumption and estimate with a confidence level (high/medium/low) — stakeholders need to know what's measured vs. estimated
- Produce both machine-readable (JSON) and human-readable (markdown table) outputs

### MUST NOT DO
- Do not benchmark frameworks on different hardware, OS versions, or Python versions — environmental variance invalidates comparison
- Do not include framework startup time in throughput benchmarks unless your deployment model requires fast cold starts (serverless/edge functions)
- Do not skip the dependency audit because a framework is "popular" — popularity does not prevent supply chain attacks
- Do not assume integration tests will pass without verifying against production-like targets — `localhost` results are not representative
- Do not use TCO estimates without documenting the source data (version history, team size, hourly rate)
- Do not let performance advantages justify ignoring license or security failures

---

## Output Template

When this skill is active, produce:

1. **Benchmark Results** — JSON output from automated benchmark harnesses with P50/P95/P99 latencies, throughput (rps), memory delta, and environment details for each test case
2. **Dependency Security Audit** — Per-framework report showing total dependencies, CVE counts by severity, security score (0–100), license compliance status, and supply chain risk flags
3. **Integration Test Matrix** — Table of all integration tests with pass/fail/skip status per framework, highlighting any non-negotiable category failures (database, auth)
4. **TCO Comparison** — 3-year cost projection table showing ramp-up, annual upgrade costs, security patching costs, and total TCO per framework with source data documented
5. **Scaffold Quality Assessment** — Scores and findings for each framework's CLI scaffolding output across test inclusion, type annotations, separation of concerns, and configuration patterns
6. **Aggregated Report** — Complete evaluation report in JSON (for tool integration) and markdown table (for stakeholder review), with aggregate best-in-class identifications

---

## Related Skills

| Skill | Purpose |
|-------|---------|
| `framework-onboarding` | After selection, use this skill to onboard teams onto the chosen framework — training plans, project templates, conventions |
| `framework-selection` | Apply weighted scoring matrices and AHP decision-making to rank frameworks based on the quantitative data produced by this skill |
| `framework-comparison-workflow` | Run hands-on spike projects for deeper code-level comparison when quantitative data alone cannot differentiate candidates |
| `tool-evaluation-workflow` | Broader tool evaluation methodology covering CI/CD tools, testing frameworks, and infrastructure — not limited to application frameworks |

---

## Live References

> Authoritative documentation links for framework evaluation methodologies and tools. The model follows markdown links at load time to resolve external references and inline content.

- [How to Evaluate Software Frameworks (Jahia)](https://www.jahia.com/blog/how-to-evaluate-software-frameworks) — Comprehensive methodology covering requirements analysis, criteria definition, and scoring matrices for framework comparison
- [Framework Evaluation (Martin Fowler)](https://martinfowler.com/articles/frameworkEvaluation.html) — Spikes, proof-of-concepts, and evidence-based evaluation patterns for technology selection decisions
- [ThoughtWorks Technology Radar](https://www.thoughtworks.com/radar) — Ring-based classification (Adopt, Trial, Assess, Hold) for assessing technology maturity and organizational readiness
- [PyPI Package Analytics API](https://pypi.org/help/#apitokens) — Programmatic access to download statistics, release history, and dependency metadata for quantitative evaluation
- [GitHub API — Repository Metrics](https://docs.github.com/en/rest/repos) — Query stars, forks, commit frequency, contributor activity, and issue resolution rates as adoption signals

---

*This skill is designed to produce quantitative, auditable data that feeds into the scoring matrices used by `framework-selection`. Run the automated evaluation harnesses first, then apply weighted decision frameworks to the resulting data for defensible technology decisions.*

Files in this skill

  • SKILL.md62.1 KB
  • framework-evaluation/SKILL.md27.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…