{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.13.0"
    },
    "blog_metadata": {
      "topic": "Code Modernization Is Finally Measurable — If You Treat It as a Data Program, Not a Copilot Demo",
      "slug": "code-modernization-is-finally-measurable-if-you-treat-it-as-",
      "generated_by": "LinkedIn Post Generator + Azure OpenAI",
      "generated_at": "2026-08-24T15:47:37.172Z"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Code Modernization Is Finally Measurable — If You Treat It as a Data Program, Not a Copilot Demo\n",
        "\n",
        "This notebook turns the blog post into a hands-on validation workflow using Python. The focus is not on demo activity, but on building a comparable modernization dataset, classifying backlog candidates, joining delivery evidence, and calculating scorecard metrics that support funding decisions."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "%pip install -q pandas numpy"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import json\n",
        "import math\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "from IPython.display import display"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Operating model overview\n",
        "\n",
        "The blog describes a governed loop where inventory becomes a normalized dataset, then a backlog, then modernization waves, and finally scorecard metrics used for executive review and funding. This cell renders that flow as structured Python data so it can be validated and reused."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "flow_steps = [\n",
        "    (\"Inventory Intake\", \"Normalized Dataset\"),\n",
        "    (\"Normalized Dataset\", \"Backlog Classification\"),\n",
        "    (\"Backlog Classification\", \"Modernization Wave\"),\n",
        "    (\"Modernization Wave\", \"Delivery Exports\"),\n",
        "    (\"Delivery Exports\", \"Scorecard Metrics\"),\n",
        "    (\"Scorecard Metrics\", \"Executive Review\"),\n",
        "    (\"Executive Review\", \"Next Wave Funding\"),\n",
        "]\n",
        "\n",
        "flow_df = pd.DataFrame(flow_steps, columns=[\"from_step\", \"to_step\"])\n",
        "display(flow_df)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Normalize repository intake\n",
        "\n",
        "The original post showed a PowerShell example for collecting repository inventory into a normalized intake dataset. Here, the same idea is implemented in Python so you can validate the target schema and confirm that identifiers and metadata are standardized early."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "repos = [\n",
        "    {\"Repo\": \"billing-api\", \"Language\": \"Python\", \"Team\": \"FinOps\", \"Criticality\": \"High\", \"LastCommit\": \"2026-08-01\", \"Pipeline\": \"AzureDevOps\"},\n",
        "    {\"Repo\": \"claims-ui\", \"Language\": \"TypeScript\", \"Team\": \"Claims\", \"Criticality\": \"Medium\", \"LastCommit\": \"2026-07-20\", \"Pipeline\": \"GitHubActions\"},\n",
        "]\n",
        "\n",
        "repos_df = pd.DataFrame(repos)\n",
        "normalized = pd.DataFrame({\n",
        "    \"asset_type\": \"repository\",\n",
        "    \"asset_id\": repos_df[\"Repo\"],\n",
        "    \"owner_team\": repos_df[\"Team\"],\n",
        "    \"primary_stack\": repos_df[\"Language\"],\n",
        "    \"business_tier\": repos_df[\"Criticality\"],\n",
        "    \"last_change_utc\": pd.to_datetime(repos_df[\"LastCommit\"], utc=True),\n",
        "    \"ci_platform\": repos_df[\"Pipeline\"],\n",
        "})\n",
        "\n",
        "display(normalized)\n",
        "print(normalized.to_json(orient=\"records\", date_format=\"iso\", indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Merge application metadata with repository intake\n",
        "\n",
        "Useful triage requires business and technical context in the same row. This example joins application metadata to repository intake so backlog prioritization can consider hosting model, data class, owner, stack, and SLA together."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "apps = pd.DataFrame([\n",
        "    {\"AppId\": \"APP-100\", \"Repo\": \"billing-api\", \"Hosting\": \"AKS\", \"DataClass\": \"PCI\", \"Sla\": \"99.9\"},\n",
        "    {\"AppId\": \"APP-200\", \"Repo\": \"claims-ui\", \"Hosting\": \"AppService\", \"DataClass\": \"Internal\", \"Sla\": \"99.5\"},\n",
        "])\n",
        "\n",
        "repos_join = pd.DataFrame([\n",
        "    {\"asset_id\": \"billing-api\", \"owner_team\": \"FinOps\", \"primary_stack\": \"Python\"},\n",
        "    {\"asset_id\": \"claims-ui\", \"owner_team\": \"Claims\", \"primary_stack\": \"TypeScript\"},\n",
        "])\n",
        "\n",
        "joined = apps.merge(repos_join, left_on=\"Repo\", right_on=\"asset_id\", how=\"left\")\n",
        "joined = joined.rename(columns={\n",
        "    \"AppId\": \"app_id\",\n",
        "    \"Repo\": \"repo_id\",\n",
        "    \"Hosting\": \"hosting_model\",\n",
        "    \"DataClass\": \"data_class\",\n",
        "    \"Sla\": \"sla_target\",\n",
        "})[[\"app_id\", \"repo_id\", \"owner_team\", \"primary_stack\", \"hosting_model\", \"data_class\", \"sla_target\"]]\n",
        "joined[\"sla_target\"] = joined[\"sla_target\"].astype(float)\n",
        "\n",
        "display(joined)\n",
        "joined.to_csv(\"modernization-intake.csv\", index=False)\n",
        "print(\"Wrote modernization-intake.csv\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Classify backlog candidates with simple rules\n",
        "\n",
        "The blog recommends consistent classification rules before wave planning. This example uses a small inventory and policy-style thresholds to assign each repository to a backlog class."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "inventory = pd.DataFrame([\n",
        "    {\"repo\": \"billing-api\", \"runtime_eol\": True, \"critical_findings\": 4, \"deploy_freq_month\": 18},\n",
        "    {\"repo\": \"claims-ui\", \"runtime_eol\": False, \"critical_findings\": 1, \"deploy_freq_month\": 3},\n",
        "    {\"repo\": \"ledger-batch\", \"runtime_eol\": True, \"critical_findings\": 0, \"deploy_freq_month\": 1},\n",
        "])\n",
        "\n",
        "def classify(row):\n",
        "    if row[\"runtime_eol\"] or row[\"critical_findings\"] >= 3:\n",
        "        return \"replatform-now\"\n",
        "    if row[\"deploy_freq_month\"] <= 2:\n",
        "        return \"stabilize-first\"\n",
        "    return \"optimize-later\"\n",
        "\n",
        "inventory[\"backlog_class\"] = inventory.apply(classify, axis=1)\n",
        "display(inventory[[\"repo\", \"backlog_class\"]])"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Define a minimum modernization event model\n",
        "\n",
        "A key point in the post is that telemetry should come before claims of progress. This cell creates a minimum event schema and sample events so you can validate traceability from recommendation to production outcome."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "event_model_fields = [\n",
        "    \"application_id\",\n",
        "    \"wave_id\",\n",
        "    \"recommendation_type\",\n",
        "    \"acceptance_decision\",\n",
        "    \"code_change_reference\",\n",
        "    \"test_evidence\",\n",
        "    \"deployment_outcome\",\n",
        "    \"defect_linkage\",\n",
        "    \"cost_allocation_key\",\n",
        "]\n",
        "\n",
        "modernization_events = pd.DataFrame([\n",
        "    {\n",
        "        \"application_id\": \"APP-100\",\n",
        "        \"wave_id\": \"W1\",\n",
        "        \"recommendation_type\": \"runtime-upgrade\",\n",
        "        \"acceptance_decision\": \"accepted\",\n",
        "        \"code_change_reference\": \"PR-1042\",\n",
        "        \"test_evidence\": \"ci-run-9001\",\n",
        "        \"deployment_outcome\": \"success\",\n",
        "        \"defect_linkage\": \"INC-301\",\n",
        "        \"cost_allocation_key\": \"CC-FINOPS-01\",\n",
        "    },\n",
        "    {\n",
        "        \"application_id\": \"APP-200\",\n",
        "        \"wave_id\": \"W1\",\n",
        "        \"recommendation_type\": \"dependency-remediation\",\n",
        "        \"acceptance_decision\": \"accepted\",\n",
        "        \"code_change_reference\": \"PR-1048\",\n",
        "        \"test_evidence\": \"ci-run-9002\",\n",
        "        \"deployment_outcome\": \"success\",\n",
        "        \"defect_linkage\": None,\n",
        "        \"cost_allocation_key\": \"CC-CLAIMS-02\",\n",
        "    },\n",
        "])\n",
        "\n",
        "print(\"Expected fields:\")\n",
        "print(event_model_fields)\n",
        "print(\"\\nObserved fields:\")\n",
        "print(list(modernization_events.columns))\n",
        "print(\"\\nMissing fields:\")\n",
        "print(sorted(set(event_model_fields) - set(modernization_events.columns)))\n",
        "display(modernization_events)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Load modernization exports and standardize keys for scorecard joins\n",
        "\n",
        "This example combines work, security, and test exports into a common score input. The important validation is that each wave lands in the same schema and can be joined on stable keys."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "work = pd.DataFrame([\n",
        "    {\"wave\": \"W1\", \"repo\": \"billing-api\", \"completed_points\": 34, \"prod_defects\": 2},\n",
        "    {\"wave\": \"W1\", \"repo\": \"claims-ui\", \"completed_points\": 21, \"prod_defects\": 1},\n",
        "])\n",
        "\n",
        "security = pd.DataFrame([\n",
        "    {\"wave\": \"W1\", \"repo\": \"billing-api\", \"open_critical_start\": 5, \"open_critical_end\": 1},\n",
        "    {\"wave\": \"W1\", \"repo\": \"claims-ui\", \"open_critical_start\": 2, \"open_critical_end\": 1},\n",
        "])\n",
        "\n",
        "tests = pd.DataFrame([\n",
        "    {\"wave\": \"W1\", \"repo\": \"billing-api\", \"tests_passed\": 420, \"tests_failed\": 12},\n",
        "    {\"wave\": \"W1\", \"repo\": \"claims-ui\", \"tests_passed\": 310, \"tests_failed\": 8},\n",
        "])\n",
        "\n",
        "score_input = work.merge(security, on=[\"wave\", \"repo\"]).merge(tests, on=[\"wave\", \"repo\"])\n",
        "display(score_input)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Calculate wave-level throughput, defect escape, risk burn-down, and unit cost\n",
        "\n",
        "This scorecard puts delivery, quality, risk, and cost in the same frame. That makes it possible to compare waves without over-rewarding speed alone."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "df = pd.DataFrame([\n",
        "    {\"wave\": \"W1\", \"completed_points\": 34, \"prod_defects\": 2, \"open_critical_start\": 5, \"open_critical_end\": 1, \"run_cost\": 12000},\n",
        "    {\"wave\": \"W1\", \"completed_points\": 21, \"prod_defects\": 1, \"open_critical_start\": 2, \"open_critical_end\": 1, \"run_cost\": 8000},\n",
        "])\n",
        "\n",
        "wave = df.groupby(\"wave\", as_index=False).sum(numeric_only=True)\n",
        "wave[\"throughput\"] = wave[\"completed_points\"]\n",
        "wave[\"defect_escape_rate\"] = wave[\"prod_defects\"] / wave[\"completed_points\"]\n",
        "wave[\"risk_burndown_pct\"] = (wave[\"open_critical_start\"] - wave[\"open_critical_end\"]) / wave[\"open_critical_start\"] * 100\n",
        "wave[\"unit_cost_per_point\"] = wave[\"run_cost\"] / wave[\"completed_points\"]\n",
        "\n",
        "display(wave[[\"wave\", \"throughput\", \"defect_escape_rate\", \"risk_burndown_pct\", \"unit_cost_per_point\"]])"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Add deployment reliability and test pass rate\n",
        "\n",
        "A wave is not truly successful if it destabilizes release management. This example adds deployment success and test pass rate to the scorecard."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "deploy = pd.DataFrame([\n",
        "    {\"wave\": \"W1\", \"repo\": \"billing-api\", \"deployments\": 14, \"failed_deployments\": 1},\n",
        "    {\"wave\": \"W1\", \"repo\": \"claims-ui\", \"deployments\": 10, \"failed_deployments\": 2},\n",
        "])\n",
        "\n",
        "tests = pd.DataFrame([\n",
        "    {\"wave\": \"W1\", \"repo\": \"billing-api\", \"tests_passed\": 420, \"tests_failed\": 12},\n",
        "    {\"wave\": \"W1\", \"repo\": \"claims-ui\", \"tests_passed\": 310, \"tests_failed\": 8},\n",
        "])\n",
        "\n",
        "m = deploy.merge(tests, on=[\"wave\", \"repo\"])\n",
        "m[\"deployment_success_rate\"] = (m[\"deployments\"] - m[\"failed_deployments\"]) / m[\"deployments\"]\n",
        "m[\"test_pass_rate\"] = m[\"tests_passed\"] / (m[\"tests_passed\"] + m[\"tests_failed\"])\n",
        "\n",
        "display(m[[\"repo\", \"deployment_success_rate\", \"test_pass_rate\"]])"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Validate a compact 90-day portfolio slice\n",
        "\n",
        "The post includes a sample result set over 90 days. This cell turns those narrative outcomes into a small dataset and derives a few checks so the claims can be inspected programmatically."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "portfolio_90d = {\n",
        "    \"applications_baselined\": 24,\n",
        "    \"active_modernization_waves\": 17,\n",
        "    \"completed_remediation_and_validation\": 11,\n",
        "    \"deployed_to_production\": 9,\n",
        "    \"critical_open_findings_start\": 29,\n",
        "    \"critical_open_findings_end\": 10,\n",
        "    \"wave1_defect_escape_rate\": 0.11,\n",
        "    \"wave2_defect_escape_rate\": 0.05,\n",
        "    \"wave1_deployment_success_rate\": 0.81,\n",
        "    \"wave2_deployment_success_rate\": 0.89,\n",
        "    \"unit_cost_per_accepted_remediation_reduction_pct\": 23,\n",
        "    \"applications_paused\": 2,\n",
        "}\n",
        "\n",
        "portfolio_df = pd.DataFrame([portfolio_90d])\n",
        "portfolio_df[\"risk_burndown_pct\"] = (\n",
        "    (portfolio_df[\"critical_open_findings_start\"] - portfolio_df[\"critical_open_findings_end\"]) /\n",
        "    portfolio_df[\"critical_open_findings_start\"] * 100\n",
        ")\n",
        "portfolio_df[\"defect_escape_improvement_pct\"] = (\n",
        "    (portfolio_df[\"wave1_defect_escape_rate\"] - portfolio_df[\"wave2_defect_escape_rate\"]) /\n",
        "    portfolio_df[\"wave1_defect_escape_rate\"] * 100\n",
        ")\n",
        "portfolio_df[\"deployment_success_improvement_pct\"] = (\n",
        "    (portfolio_df[\"wave2_deployment_success_rate\"] - portfolio_df[\"wave1_deployment_success_rate\"]) /\n",
        "    portfolio_df[\"wave1_deployment_success_rate\"] * 100\n",
        ")\n",
        "\n",
        "display(portfolio_df.T.rename(columns={0: \"value\"}))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Sequence view of the evidence loop\n",
        "\n",
        "The blog also included a sequence diagram showing how normalized metadata flows into classification, execution, exports, and scorecard feedback. This cell represents that sequence as an ordered event table."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "sequence_events = pd.DataFrame([\n",
        "    {\"step\": 1, \"from\": \"Inventory Intake\", \"to\": \"Backlog Classifier\", \"payload\": \"normalized repo/app metadata\"},\n",
        "    {\"step\": 2, \"from\": \"Backlog Classifier\", \"to\": \"Modernization Wave\", \"payload\": \"prioritized candidates\"},\n",
        "    {\"step\": 3, \"from\": \"Modernization Wave\", \"to\": \"Delivery Exports\", \"payload\": \"execute modernization work\"},\n",
        "    {\"step\": 4, \"from\": \"Delivery Exports\", \"to\": \"Scorecard\", \"payload\": \"work, security, test, deploy, cost exports\"},\n",
        "    {\"step\": 5, \"from\": \"Scorecard\", \"to\": \"Modernization Wave\", \"payload\": \"throughput, risk, quality, unit cost\"},\n",
        "])\n",
        "\n",
        "display(sequence_events)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Publish an executive scorecard as JSON\n",
        "\n",
        "The final example emits a compact JSON payload suitable for dashboards or funding reviews. The important field is the decision, because each wave should end with a clear outcome."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "scorecard = {\n",
        "    \"wave\": \"W1\",\n",
        "    \"throughput\": 55,\n",
        "    \"defect_escape_rate\": 0.055,\n",
        "    \"risk_burndown_pct\": 66.7,\n",
        "    \"deployment_success_rate\": 0.875,\n",
        "    \"unit_cost_per_point\": 363.64,\n",
        "    \"decision\": \"fund-next-wave\",\n",
        "}\n",
        "\n",
        "print(json.dumps(scorecard, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Summary\n",
        "\n",
        "This notebook validated the core argument of the post: modernization becomes measurable when every wave produces a governed, comparable dataset. Instead of relying on generated pull requests or assistant activity, the workflow ties inventory, telemetry, delivery evidence, risk reduction, quality, and unit economics into one scorecard.\n",
        "\n",
        "## Next Steps\n",
        "\n",
        "1. Replace the toy datasets with exports from your repos, CI/CD, security scanners, test systems, and incident tools.\n",
        "2. Formalize stable identifiers for applications, repositories, teams, and waves before scaling joins.\n",
        "3. Add cost attribution fields so unit economics can be reviewed alongside throughput and risk burn-down.\n",
        "4. Run at least two comparable waves with the same schema and metric definitions.\n",
        "5. Use the resulting scorecard to make explicit decisions: fund, redesign, or stop."
      ]
    }
  ]
}