{
  "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": "Organizational Prompts in Microsoft 365 Copilot: The New Governance Layer for Prompt Reuse",
      "slug": "organizational-prompts-in-microsoft-365-copilot-the-new-gove",
      "generated_by": "LinkedIn Post Generator + Azure OpenAI",
      "generated_at": "2026-07-30T18:36:22.099Z"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Organizational Prompts in Microsoft 365 Copilot: The New Governance Layer for Prompt Reuse\n",
        "\n",
        "This notebook turns the blog post into a hands-on validation workflow using Python. It focuses on the core claim that organizational prompts are not just reusable text, but governed assets with ownership, metadata, review criteria, lifecycle controls, and measurement."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "%pip install pandas"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "from dataclasses import dataclass, asdict\n",
        "from datetime import datetime, timedelta\n",
        "from collections import Counter\n",
        "import json\n",
        "import pandas as pd"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Lifecycle flow as a governed process\n",
        "\n",
        "The blog describes prompt reuse as an operating-model concern, not a convenience feature. This cell represents the authoring-to-retirement lifecycle as structured Python data so you can inspect and validate the governance stages."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "lifecycle_steps = [\n",
        "    \"Author creates organizational prompt\",\n",
        "    \"Store in governed prompt library\",\n",
        "    \"Apply metadata: owner, department, sensitivity\",\n",
        "    \"Approval and review workflow\",\n",
        "    \"Publish to Microsoft 365 Copilot\",\n",
        "    \"Employees discover and reuse prompt\",\n",
        "    \"Copilot executes with tenant permissions\",\n",
        "    \"Audit logs and usage analytics\",\n",
        "    \"Refine, version, or retire prompt\"\n",
        "]\n",
        "\n",
        "for i, step in enumerate(lifecycle_steps, start=1):\n",
        "    print(f\"{i}. {step}\")\n",
        "\n",
        "lifecycle_df = pd.DataFrame({\n",
        "    \"step_number\": range(1, len(lifecycle_steps) + 1),\n",
        "    \"step\": lifecycle_steps\n",
        "})\n",
        "\n",
        "lifecycle_df"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Define a governed organizational prompt record\n",
        "\n",
        "A central argument in the post is that a published prompt is more than prompt text. This example packages the prompt with metadata such as owner, department, sensitivity, version, and approval state."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "@dataclass\n",
        "class OrganizationalPrompt:\n",
        "    title: str\n",
        "    description: str\n",
        "    owner: str\n",
        "    department: str\n",
        "    sensitivity: str\n",
        "    version: str\n",
        "    prompt_text: str\n",
        "    approved: bool\n",
        "    created_utc: str\n",
        "\n",
        "prompt = OrganizationalPrompt(\n",
        "    title=\"Quarterly Business Review Summary\",\n",
        "    description=\"Summarize QBR notes into executive bullets\",\n",
        "    owner=\"copilot-governance@contoso.com\",\n",
        "    department=\"Strategy\",\n",
        "    sensitivity=\"Internal\",\n",
        "    version=\"1.0.0\",\n",
        "    prompt_text=\"Summarize the uploaded QBR notes into 5 executive bullets and 3 risks.\",\n",
        "    approved=False,\n",
        "    created_utc=datetime.utcnow().isoformat()\n",
        ")\n",
        "\n",
        "print(json.dumps(asdict(prompt), indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Validate required governance fields before publication\n",
        "\n",
        "The original post included a PowerShell example to fail publication when required governance fields are missing. Here, the same governance check is implemented in Python so you can test prompt records directly in the notebook."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "def validate_governance_fields(prompt_record: dict, required_fields: list[str]) -> str:\n",
        "    for field in required_fields:\n",
        "        if field not in prompt_record:\n",
        "            raise ValueError(f\"Missing required governance field: {field}\")\n",
        "        value = prompt_record[field]\n",
        "        if value is None:\n",
        "            raise ValueError(f\"Missing required governance field: {field}\")\n",
        "        if isinstance(value, str) and not value.strip():\n",
        "            raise ValueError(f\"Missing required governance field: {field}\")\n",
        "    return f\"Prompt '{prompt_record['Title']}' passed governance validation.\"\n",
        "\n",
        "prompt_record = {\n",
        "    \"Title\": \"Sales Account Brief\",\n",
        "    \"Owner\": \"copilot-governance@contoso.com\",\n",
        "    \"Department\": \"Sales\",\n",
        "    \"Sensitivity\": \"Confidential\",\n",
        "    \"Version\": \"1.2.0\",\n",
        "    \"Approved\": True\n",
        "}\n",
        "\n",
        "required_fields = [\"Title\", \"Owner\", \"Department\", \"Sensitivity\", \"Version\", \"Approved\"]\n",
        "print(validate_governance_fields(prompt_record, required_fields))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Review workflow as an interaction sequence\n",
        "\n",
        "The blog also described a sequence from author submission through governance review, publication, usage, and audit feedback. This cell models that sequence as a simple event log to make the control points visible."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "sequence_events = [\n",
        "    (\"Author\", \"Prompt Library\", \"Submit prompt draft\"),\n",
        "    (\"Prompt Library\", \"Governance Team\", \"Request review\"),\n",
        "    (\"Governance Team\", \"Prompt Library\", \"Approve with metadata\"),\n",
        "    (\"Prompt Library\", \"M365 Copilot\", \"Publish reusable prompt\"),\n",
        "    (\"M365 Copilot\", \"Audit Log\", \"Record usage and outcomes\"),\n",
        "    (\"Audit Log\", \"Governance Team\", \"Surface adoption insights\")\n",
        "]\n",
        "\n",
        "sequence_df = pd.DataFrame(sequence_events, columns=[\"from\", \"to\", \"action\"])\n",
        "sequence_df"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Enforce simple policy checks before publication\n",
        "\n",
        "This example implements a lightweight publication gate. It checks approval state, validates sensitivity labels, and blocks a simple keyword pattern to demonstrate how policy can be applied before a prompt is published."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "def can_publish(prompt: dict) -> tuple[bool, str]:\n",
        "    allowed_sensitivity = {\"Public\", \"Internal\", \"Confidential\"}\n",
        "    if prompt.get(\"approved\") is not True:\n",
        "        return False, \"Prompt must be approved before publication.\"\n",
        "    if prompt.get(\"sensitivity\") not in allowed_sensitivity:\n",
        "        return False, \"Sensitivity label is invalid.\"\n",
        "    if \"password\" in prompt.get(\"prompt_text\", \"\").lower():\n",
        "        return False, \"Prompt text contains a blocked keyword.\"\n",
        "    return True, \"Prompt is eligible for publication.\"\n",
        "\n",
        "candidate = {\n",
        "    \"title\": \"Incident Postmortem Draft\",\n",
        "    \"approved\": True,\n",
        "    \"sensitivity\": \"Internal\",\n",
        "    \"prompt_text\": \"Create a postmortem summary from the attached incident timeline.\"\n",
        "}\n",
        "\n",
        "allowed, message = can_publish(candidate)\n",
        "print({\"publish\": allowed, \"message\": message})\n",
        "\n",
        "blocked_candidate = {\n",
        "    \"title\": \"Unsafe Prompt\",\n",
        "    \"approved\": True,\n",
        "    \"sensitivity\": \"Internal\",\n",
        "    \"prompt_text\": \"Summarize the document and include any password references.\"\n",
        "}\n",
        "\n",
        "print({\"candidate\": blocked_candidate[\"title\"], \"result\": can_publish(blocked_candidate)})"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Export a prompt catalog for review and lifecycle tracking\n",
        "\n",
        "The post argues that if you cannot export your prompt inventory, you do not have a managed asset. This Python version creates a small catalog, exports it to CSV, and reads it back for review."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "catalog = pd.DataFrame([\n",
        "    {\"Title\": \"QBR Summary\", \"Owner\": \"strategy@contoso.com\", \"Version\": \"1.0.0\", \"Status\": \"Published\"},\n",
        "    {\"Title\": \"Account Brief\", \"Owner\": \"salesops@contoso.com\", \"Version\": \"1.2.0\", \"Status\": \"Published\"},\n",
        "    {\"Title\": \"Postmortem Draft\", \"Owner\": \"itops@contoso.com\", \"Version\": \"0.9.0\", \"Status\": \"Review\"}\n",
        "])\n",
        "\n",
        "path = \"prompt-catalog.csv\"\n",
        "catalog.to_csv(path, index=False)\n",
        "print(f\"Catalog exported to {path}\")\n",
        "\n",
        "reloaded_catalog = pd.read_csv(path)\n",
        "reloaded_catalog"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Track prompt usage metrics for governance decisions\n",
        "\n",
        "The blog emphasizes that usage alone is not enough; outcome quality matters too. This example counts prompt usage and outcome mix so you can identify signals like edits and abandonment."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "usage_events = [\n",
        "    {\"prompt\": \"QBR Summary\", \"user\": \"ana\", \"outcome\": \"success\"},\n",
        "    {\"prompt\": \"QBR Summary\", \"user\": \"li\", \"outcome\": \"success\"},\n",
        "    {\"prompt\": \"Account Brief\", \"user\": \"sam\", \"outcome\": \"edited\"},\n",
        "    {\"prompt\": \"QBR Summary\", \"user\": \"maya\", \"outcome\": \"success\"},\n",
        "    {\"prompt\": \"Postmortem Draft\", \"user\": \"raj\", \"outcome\": \"abandoned\"},\n",
        "]\n",
        "\n",
        "prompt_counts = Counter(event[\"prompt\"] for event in usage_events)\n",
        "outcome_counts = Counter(event[\"outcome\"] for event in usage_events)\n",
        "\n",
        "print(\"Prompt usage:\", dict(prompt_counts))\n",
        "print(\"Outcome mix:\", dict(outcome_counts))\n",
        "\n",
        "usage_df = pd.DataFrame(usage_events)\n",
        "usage_df"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Version and retire prompts like governed assets\n",
        "\n",
        "Retirement is presented in the post as proof that governance exists. This cell creates a simple lifecycle function and shows both a published update and an intentional retirement state."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "def set_prompt_lifecycle(title: str, version: str, state: str) -> dict:\n",
        "    allowed_states = {\"Draft\", \"Review\", \"Published\", \"Retired\"}\n",
        "    if state not in allowed_states:\n",
        "        raise ValueError(f\"State must be one of {sorted(allowed_states)}\")\n",
        "    return {\n",
        "        \"Title\": title,\n",
        "        \"Version\": version,\n",
        "        \"State\": state,\n",
        "        \"UpdatedUtc\": datetime.utcnow().isoformat()\n",
        "    }\n",
        "\n",
        "published = set_prompt_lifecycle(\"QBR Summary\", \"1.1.0\", \"Published\")\n",
        "retired = set_prompt_lifecycle(\"Legacy Sales Brief\", \"0.8.4\", \"Retired\")\n",
        "\n",
        "print(published)\n",
        "print(retired)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Validate review dates and retirement triggers\n",
        "\n",
        "A major theme in the article is lifecycle discipline. This example adds review dates and simple retirement logic based on stale review windows, missing owners, low usage, and high correction signals."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "today = datetime.utcnow().date()\n",
        "\n",
        "prompt_inventory = [\n",
        "    {\n",
        "        \"title\": \"QBR Summary\",\n",
        "        \"owner\": \"strategy@contoso.com\",\n",
        "        \"state\": \"Published\",\n",
        "        \"review_date\": today - timedelta(days=10),\n",
        "        \"usage\": 42,\n",
        "        \"edited_rate\": 0.10,\n",
        "        \"abandonment_rate\": 0.02\n",
        "    },\n",
        "    {\n",
        "        \"title\": \"Legacy Sales Brief\",\n",
        "        \"owner\": \"\",\n",
        "        \"state\": \"Published\",\n",
        "        \"review_date\": today - timedelta(days=120),\n",
        "        \"usage\": 3,\n",
        "        \"edited_rate\": 0.55,\n",
        "        \"abandonment_rate\": 0.30\n",
        "    },\n",
        "    {\n",
        "        \"title\": \"Policy Comparison Summary\",\n",
        "        \"owner\": \"legal@contoso.com\",\n",
        "        \"state\": \"Published\",\n",
        "        \"review_date\": today - timedelta(days=95),\n",
        "        \"usage\": 18,\n",
        "        \"edited_rate\": 0.15,\n",
        "        \"abandonment_rate\": 0.05\n",
        "    }\n",
        "]\n",
        "\n",
        "def evaluate_lifecycle(record: dict) -> list[str]:\n",
        "    reasons = []\n",
        "    if not record.get(\"owner\"):\n",
        "        reasons.append(\"missing owner\")\n",
        "    if record.get(\"review_date\") < today - timedelta(days=90):\n",
        "        reasons.append(\"review overdue\")\n",
        "    if record.get(\"usage\", 0) < 5:\n",
        "        reasons.append(\"low usage\")\n",
        "    if record.get(\"edited_rate\", 0) > 0.5:\n",
        "        reasons.append(\"high correction rate\")\n",
        "    if record.get(\"abandonment_rate\", 0) > 0.2:\n",
        "        reasons.append(\"high abandonment\")\n",
        "    return reasons\n",
        "\n",
        "results = []\n",
        "for record in prompt_inventory:\n",
        "    reasons = evaluate_lifecycle(record)\n",
        "    recommendation = \"Retire or revise\" if reasons else \"Keep published\"\n",
        "    results.append({**record, \"reasons\": \", \".join(reasons) if reasons else \"none\", \"recommendation\": recommendation})\n",
        "\n",
        "pd.DataFrame(results)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Score prompt governance maturity from 1 to 5\n",
        "\n",
        "The post ends with a practical self-rating question. This cell turns that into a simple assessment function based on ownership, review path, publication controls, telemetry, and retirement state."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "def governance_score(has_named_owners: bool,\n",
        "                     has_review_workflow: bool,\n",
        "                     has_metadata_policy: bool,\n",
        "                     has_usage_telemetry: bool,\n",
        "                     has_retirement_state: bool) -> tuple[int, str]:\n",
        "    checks = [\n",
        "        has_named_owners,\n",
        "        has_review_workflow,\n",
        "        has_metadata_policy,\n",
        "        has_usage_telemetry,\n",
        "        has_retirement_state\n",
        "    ]\n",
        "    score = sum(checks)\n",
        "    labels = {\n",
        "        1: \"shared prompts are wild west\",\n",
        "        2: \"basic awareness, weak controls\",\n",
        "        3: \"some governance exists\",\n",
        "        4: \"managed catalog with measurable controls\",\n",
        "        5: \"every published prompt has an owner, review path, and retirement state\"\n",
        "    }\n",
        "    return score, labels.get(score, \"no governance foundations\")\n",
        "\n",
        "sample_score = governance_score(\n",
        "    has_named_owners=True,\n",
        "    has_review_workflow=True,\n",
        "    has_metadata_policy=True,\n",
        "    has_usage_telemetry=False,\n",
        "    has_retirement_state=True\n",
        ")\n",
        "\n",
        "print({\"score\": sample_score[0], \"interpretation\": sample_score[1]})"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Summary\n",
        "\n",
        "This notebook validated the blog's main idea: organizational prompts should be treated as governed assets, not informal snippets. The examples showed how to represent prompt metadata, enforce publication checks, export catalogs, measure usage quality, and manage lifecycle states including retirement.\n",
        "\n",
        "## Next Steps\n",
        "\n",
        "1. Define a minimum prompt record for your organization.\n",
        "2. Add a publication gate with required metadata and approval checks.\n",
        "3. Export your prompt catalog regularly and review stale items.\n",
        "4. Track edits, abandonment, and repeat use instead of raw usage alone.\n",
        "5. Align prompt ownership with broader agent, data, and security governance."
      ]
    }
  ]
}