{ "cells": [ { "cell_type": "markdown", "id": "41520e77", "metadata": {}, "source": [ "# 00 · Provision — Genesys CX Cloud TEI in Athena\n", "\n", "Source study: Forrester, *The Total Economic Impact™ Of CX Cloud* (Genesys +\n", "Salesforce, December 2025). Published headline: **NPV \\$10.78M · ROI 266%**.\n", "\n", "This notebook creates everything the study needs in the Athena sandbox:\n", "\n", "1. **Report template** *CX Cloud (Genesys + Salesforce) 2025* + **field definitions** — 4 benefits, 3 published costs, **plus the `genesys_ai_tokens` consumption line the published study omits**\n", "2. **Client selection** from the CRM (profile pulled, no re-entry)\n", "3. **Attachment** to a Proposal or Engagement\n", "4. **Seed values** + server-side **calculation**\n", "5. **Two-tier verification**: exact match vs Athena-methodology expectations, then reconciliation to the published totals (explained Year-0 discounting delta)\n", "6. Persists study-scoped IDs (`PALLADIUM_GENESYSCX_*`) to `.env`\n", "\n", "Safe to re-run — every step finds existing objects before creating new ones." ] }, { "cell_type": "code", "execution_count": 1, "id": "1b6f1117", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "✅ Athena connected — https://athena.ouranos.helu.ca (1 report templates visible)\n", "📁 Study: 202512_GenesysCX\n" ] } ], "source": [ "import sys, pathlib # path shim: works on a fresh kernel\n", "for _p in [pathlib.Path.cwd(), *pathlib.Path.cwd().parents]:\n", " if (_p / \"pyproject.toml\").exists():\n", " sys.path.insert(0, str(_p)); break\n", "\n", "import pandas as pd\n", "from core.bootstrap import init, update_env\n", "\n", "pal = init(study=\"202512_GenesysCX\")\n", "client, seed, config = pal.client, pal.seed_data, pal.config\n", "assert pal.connection.get(\"status\") == \"ok\", \"Fix the connection first → 00_setup.ipynb\"" ] }, { "cell_type": "markdown", "id": "c1f8b6bd", "metadata": {}, "source": [ "## 1 · Report template (find or create)" ] }, { "cell_type": "code", "execution_count": 2, "id": "cc81e408", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created report template UCb2hSJprSBx\n" ] } ], "source": [ "REPORT_NAME, VENDOR = \"CX Cloud (Genesys + Salesforce) 2025\", \"Genesys\"\n", "\n", "report = next(\n", " (r for r in client.list_reports()\n", " if r.get(\"name\") == REPORT_NAME and r.get(\"vendor\") == VENDOR),\n", " None,\n", ")\n", "if report is None:\n", " report = client.create_report(\n", " name=REPORT_NAME,\n", " vendor=VENDOR,\n", " version=\"1.0\",\n", " description=(\n", " \"Forrester TEI of CX Cloud (Genesys + Salesforce), Dec 2025. \"\n", " \"Includes Palladium's genesys_ai_tokens consumption line, \"\n", " \"which the published study omits.\"\n", " ),\n", " analysis_period_years=seed.ASSUMPTIONS[\"analysis_years\"],\n", " discount_rate=seed.ASSUMPTIONS[\"discount_rate\"],\n", " status=\"draft\",\n", " )\n", " print(f\"Created report template {report['id']}\")\n", "else:\n", " print(f\"Found existing report template {report['id']} (status: {report.get('status')})\")\n", "\n", "REPORT_ID = report[\"id\"]" ] }, { "cell_type": "markdown", "id": "e31bbd8b", "metadata": {}, "source": [ "## 2 · Field definitions\n", "\n", "Same Palladium conventions as the Amazon Connect study: benefit risk\n", "adjustments live on the field; cost values get pushed pre-multiplied by\n", "`(1 + risk_adj)`; Year-0 amounts use companion `*_initial` fields.\n", "The `genesys_ai_tokens` line is seeded \\$0 (reproduces the published study) —\n", "the annual cost gets entered per deal, from the Genesys quote, in\n", "`01_business_case.ipynb`." ] }, { "cell_type": "code", "execution_count": 3, "id": "55e69828", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "12 fields created, 0 already existed.\n", "Report template activated.\n" ] } ], "source": [ "def field_defs():\n", " defs, sort = [], 0\n", " for b in seed.BENEFITS:\n", " sort += 1\n", " defs.append({\n", " \"table\": \"benefits\",\n", " \"field_key\": b[\"field_key\"],\n", " \"label\": b[\"label\"],\n", " \"description\": b[\"notes\"][:200],\n", " \"field_type\": \"currency\",\n", " \"category\": b[\"category\"],\n", " \"is_annual\": True,\n", " \"risk_adjustment\": str(b[\"risk_adjustment\"]),\n", " \"sort_order\": sort,\n", " \"is_required\": True,\n", " \"source_notes\": b[\"notes\"],\n", " })\n", " for c in seed.COSTS:\n", " sort += 1\n", " defs.append({\n", " \"table\": \"costs\",\n", " \"field_key\": c[\"field_key\"],\n", " \"label\": c[\"label\"],\n", " \"description\": c[\"notes\"][:200],\n", " \"field_type\": \"currency\",\n", " \"category\": c[\"category\"],\n", " \"is_annual\": True,\n", " \"risk_adjustment\": \"0\", # cost risk adj applied client-side\n", " \"sort_order\": sort,\n", " \"is_required\": False,\n", " \"source_notes\": c[\"notes\"],\n", " })\n", " sort += 1\n", " defs.append({\n", " \"table\": \"costs\",\n", " \"field_key\": f\"{c['field_key']}_initial\",\n", " \"label\": f\"{c['label']} — initial (Year 0)\",\n", " \"description\": \"One-time Year-0 amount (companion field).\",\n", " \"field_type\": \"currency\",\n", " \"category\": c[\"category\"],\n", " \"is_annual\": False,\n", " \"risk_adjustment\": \"0\",\n", " \"sort_order\": sort,\n", " \"is_required\": False,\n", " \"source_notes\": \"Year-0 lump sum; Athena treats non-annual values as Year 1.\",\n", " })\n", " return defs\n", "\n", "existing = {f[\"field_key\"] for f in client.list_fields(REPORT_ID)}\n", "created = 0\n", "for d in field_defs():\n", " if d[\"field_key\"] not in existing:\n", " client.create_field(REPORT_ID, d)\n", " created += 1\n", "print(f\"{created} fields created, {len(existing)} already existed.\")\n", "\n", "if report.get(\"status\") == \"draft\":\n", " client.update_report(REPORT_ID, status=\"active\")\n", " print(\"Report template activated.\")" ] }, { "cell_type": "markdown", "id": "96b360d3", "metadata": {}, "source": [ "## 3 · Select the client" ] }, { "cell_type": "code", "execution_count": 4, "id": "5a0a701f", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idnameverticalclient_typeemployee_countcontact_center_agent_countsupervisor_count
02Global Guardian InsuranceNoneFor-Profit120002500None
13EudaimonixNoneFor-Profit1500300None
24Aetherium ForgeNoneFor-Profit50042None
\n", "
" ], "text/plain": [ " id name vertical client_type employee_count \\\n", "0 2 Global Guardian Insurance None For-Profit 12000 \n", "1 3 Eudaimonix None For-Profit 1500 \n", "2 4 Aetherium Forge None For-Profit 500 \n", "\n", " contact_center_agent_count supervisor_count \n", "0 2500 None \n", "1 300 None \n", "2 42 None " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "CLIENT_SEARCH = \"\" # e.g. \"Acme\" — empty lists everyone\n", "\n", "clients = client.list_clients(search=CLIENT_SEARCH or None)\n", "if clients:\n", " display(pd.DataFrame(clients)[\n", " [c for c in (\"id\", \"name\", \"vertical\", \"client_type\", \"employee_count\",\n", " \"contact_center_agent_count\", \"supervisor_count\")\n", " if c in clients[0]]\n", " ])\n", "else:\n", " print(\"No clients found — create one in the Athena UI (Orbit → Clients) and re-run.\")" ] }, { "cell_type": "code", "execution_count": 6, "id": "1e375b54", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Global Guardian Insurance
id2
nameGlobal Guardian Insurance
abbreviated_nameGGI
verticalNone
client_typeFor-Profit
employee_count12000
revenue4500000000.0
contact_center_agent_count2500
service_desk_agent_count300
supervisor_countNone
location_count120
\n", "
" ], "text/plain": [ " Global Guardian Insurance\n", "id 2\n", "name Global Guardian Insurance\n", "abbreviated_name GGI\n", "vertical None\n", "client_type For-Profit\n", "employee_count 12000\n", "revenue 4500000000.0\n", "contact_center_agent_count 2500\n", "service_desk_agent_count 300\n", "supervisor_count None\n", "location_count 120" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "CRM agent count: 2500 (composite: 600) — indicative scale 4.17×\n", "CRM revenue: $4,500,000,000 (composite: $2,500,000,000)\n" ] } ], "source": [ "CLIENT_ID = 2 # ← set from the `id` column above, or leave for auto-pick\n", "\n", "if CLIENT_ID is None and len(clients) == 1:\n", " CLIENT_ID = clients[0][\"id\"]\n", " print(f\"Auto-selected the only client: {clients[0]['name']} (id={CLIENT_ID})\")\n", "assert CLIENT_ID is not None, \"Set CLIENT_ID from the table above and re-run this cell.\"\n", "\n", "profile = client.client_profile(CLIENT_ID)\n", "CLIENT_NAME = profile[\"name\"]\n", "display(pd.DataFrame([profile]).T.rename(columns={0: CLIENT_NAME}))\n", "\n", "# Client data → study scaling levers (no re-entry)\n", "CLIENT_ASSUMPTIONS = dict(seed.ASSUMPTIONS)\n", "if profile.get(\"contact_center_agent_count\"):\n", " CLIENT_ASSUMPTIONS[\"agents_fte\"] = profile[\"contact_center_agent_count\"]\n", " scale = CLIENT_ASSUMPTIONS[\"agents_fte\"] / seed.ASSUMPTIONS[\"agents_fte\"]\n", " print(f\"CRM agent count: {CLIENT_ASSUMPTIONS['agents_fte']} \"\n", " f\"(composite: {seed.ASSUMPTIONS['agents_fte']}) — \"\n", " f\"indicative scale {scale:.2f}×\")\n", "if profile.get(\"revenue\"):\n", " CLIENT_ASSUMPTIONS[\"annual_revenue\"] = float(profile[\"revenue\"])\n", " print(f\"CRM revenue: ${CLIENT_ASSUMPTIONS['annual_revenue']:,.0f} \"\n", " f\"(composite: ${seed.ASSUMPTIONS['annual_revenue']:,.0f})\")" ] }, { "cell_type": "markdown", "id": "2ff83486", "metadata": {}, "source": [ "## 4 · Pick the attachment — Proposal or Engagement" ] }, { "cell_type": "code", "execution_count": 7, "id": "584e01dd", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Proposals for Global Guardian Insurance:\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idnamestatusopportunity
01Secure Cloud Infrastructure ModernizationDraftSecure Cloud Infrastructure Modernization
\n", "
" ], "text/plain": [ " id name status \\\n", "0 1 Secure Cloud Infrastructure Modernization Draft \n", "\n", " opportunity \n", "0 Secure Cloud Infrastructure Modernization " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "proposals = client.proposals_for_client(CLIENT_ID)\n", "engagements = client.engagements_for_client(CLIENT_NAME)\n", "\n", "if proposals:\n", " print(f\"Proposals for {CLIENT_NAME}:\")\n", " display(pd.DataFrame([\n", " {\"id\": p[\"id\"], \"name\": p.get(\"name\"), \"status\": p.get(\"status\"),\n", " \"opportunity\": (p.get(\"opportunity\") or {}).get(\"name\")}\n", " for p in proposals\n", " ]))\n", "if engagements:\n", " print(f\"Engagements for {CLIENT_NAME}:\")\n", " display(pd.DataFrame([\n", " {\"id\": e[\"id\"], \"name\": e.get(\"name\"), \"status\": e.get(\"status\")}\n", " for e in engagements\n", " ]))\n", "if not proposals and not engagements:\n", " print(f\"{CLIENT_NAME} has no proposals or engagements yet — \"\n", " \"the next cell can create a sandbox opportunity + proposal.\")" ] }, { "cell_type": "code", "execution_count": 8, "id": "e04b1676", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Auto-selected proposal 1: Secure Cloud Infrastructure Modernization\n", "Attaching via: {'proposal': 1}\n" ] } ], "source": [ "# Set exactly ONE (ids from above). Leave both None to auto-pick — a single\n", "# existing option wins; otherwise a sandbox opportunity + proposal is created.\n", "PROPOSAL_ID = config.PROPOSAL_ID # or e.g. 42\n", "ENGAGEMENT_ID = config.ENGAGEMENT_ID # or e.g. 7\n", "\n", "if PROPOSAL_ID is None and ENGAGEMENT_ID is None:\n", " if len(proposals) == 1 and not engagements:\n", " PROPOSAL_ID = proposals[0][\"id\"]\n", " print(f\"Auto-selected proposal {PROPOSAL_ID}: {proposals[0].get('name')}\")\n", " elif len(engagements) == 1 and not proposals:\n", " ENGAGEMENT_ID = engagements[0][\"id\"]\n", " print(f\"Auto-selected engagement {ENGAGEMENT_ID}: {engagements[0].get('name')}\")\n", " elif not proposals and not engagements:\n", " opp = client.create_opportunity(\n", " name=f\"{CLIENT_NAME} — CX Cloud Modernization (sandbox)\",\n", " client_id=CLIENT_ID,\n", " description=\"Created by Palladium 00_provision for the Genesys CX Cloud TEI.\",\n", " )\n", " prop = client.create_proposal(\n", " name=f\"{CLIENT_NAME} — Genesys CX Cloud TEI (sandbox)\",\n", " opportunity_id=opp[\"id\"],\n", " status=\"Draft\",\n", " )\n", " PROPOSAL_ID = prop[\"id\"]\n", " print(f\"Created opportunity {opp['id']} and proposal {PROPOSAL_ID} for {CLIENT_NAME}.\")\n", " else:\n", " raise SystemExit(\"Multiple options — set PROPOSAL_ID or ENGAGEMENT_ID above and re-run.\")\n", "\n", "assert (PROPOSAL_ID is None) != (ENGAGEMENT_ID is None), \\\n", " \"Set exactly one of PROPOSAL_ID / ENGAGEMENT_ID.\"\n", "attach = {\"proposal\": PROPOSAL_ID} if PROPOSAL_ID else {\"engagement\": ENGAGEMENT_ID}\n", "print(f\"Attaching via: {attach}\")" ] }, { "cell_type": "markdown", "id": "2b4fcb45", "metadata": {}, "source": [ "## 5 · Tool instance & seed the published values" ] }, { "cell_type": "code", "execution_count": 9, "id": "0655d1fc", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created tool 3rzDgVdsjhVv attached to {'proposal': 1}\n" ] } ], "source": [ "from core.tei_client import AthenaAPIError\n", "\n", "def _report_id_of(t):\n", " r = t.get(\"report\")\n", " return r.get(\"id\") if isinstance(r, dict) else r\n", "\n", "def _matches_attachment(t):\n", " if PROPOSAL_ID is not None:\n", " opp = t.get(\"opportunity\") or {}\n", " return t.get(\"proposal\") == PROPOSAL_ID or opp.get(\"proposal_id\") == PROPOSAL_ID\n", " eng = t.get(\"engagement\")\n", " eng_id = eng.get(\"id\") if isinstance(eng, dict) else eng\n", " return eng_id == ENGAGEMENT_ID\n", "\n", "candidates = [t for t in client.list_tools() if _report_id_of(t) == REPORT_ID]\n", "tool = next((t for t in candidates if _matches_attachment(t)),\n", " candidates[0] if len(candidates) == 1 else None)\n", "\n", "if tool is None:\n", " try:\n", " tool = client.create_tool(\n", " report_public_id=REPORT_ID,\n", " name=f\"{CLIENT_NAME} — Genesys CX Cloud TEI\",\n", " **attach,\n", " )\n", " print(f\"Created tool {tool['id']} attached to {attach}\")\n", " except AthenaAPIError as e:\n", " if e.status_code == 409: # DUPLICATE_INSTANCE\n", " raise SystemExit(\n", " \"An active tool already exists for this report + attachment. \"\n", " \"Find it with client.list_tools() or pick a different proposal/engagement.\"\n", " ) from e\n", " raise\n", "else:\n", " print(f\"Found existing tool {tool['id']} (status: {tool.get('status')})\")\n", "\n", "TOOL_ID = tool[\"id\"]" ] }, { "cell_type": "code", "execution_count": 10, "id": "86443d76", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Pushed values for 8 fields (genesys_ai_tokens seeded at $0 — published-study baseline).\n" ] } ], "source": [ "payload = []\n", "for b in seed.BENEFITS: # nominal; Athena risk-adjusts via the field definition\n", " payload.append({\n", " \"field_key\": b[\"field_key\"],\n", " \"year_values\": b[\"year_values\"],\n", " \"notes\": b[\"notes\"],\n", " })\n", "for c in seed.COSTS: # risk-adjusted UP client-side (Forrester methodology)\n", " factor = 1 + c[\"risk_adjustment\"]\n", " payload.append({\n", " \"field_key\": c[\"field_key\"],\n", " \"year_values\": {y: round(v * factor, 2) for y, v in c[\"year_values\"].items()},\n", " \"initial\": round(c[\"initial\"] * factor, 2),\n", " \"notes\": c[\"notes\"],\n", " })\n", "\n", "client.update_values(TOOL_ID, payload)\n", "print(f\"Pushed values for {len(payload)} fields \"\n", " f\"(genesys_ai_tokens seeded at $0 — published-study baseline).\")" ] }, { "cell_type": "markdown", "id": "509b52be", "metadata": {}, "source": [ "## 6 · Calculate & verify\n", "\n", "**Tier 1 — pipeline correctness:** Athena must match `seed.ATHENA_EXPECTED`\n", "(the published model re-discounted under Athena's Year-0-as-Year-1 rule)\n", "within 0.5%.\n", "\n", "**Tier 2 — reconciliation:** show Athena vs the published totals. The\n", "implementation initial (\\$1.309M, ~32% of cost PV) is discounted by Athena\n", "but not by Forrester, so costs PV reads ~\\$119k lower and ROI ~11pp higher\n", "than published. That delta is methodology, not data error." ] }, { "cell_type": "code", "execution_count": 11, "id": "0728b42e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "════════════════════════════════════════════════════════\n", " TEI Financial Summary\n", "════════════════════════════════════════════════════════\n", " Total Benefits (PV): $ 14,840,637\n", " Total Costs (PV): $ 3,938,170\n", "────────────────────────────────────────────────────────\n", " Net Present Value: $ 10,902,466\n", " ROI: 277%\n", " Payback: 4.0 months\n", "════════════════════════════════════════════════════════\n" ] } ], "source": [ "summary = client.calculate(TOOL_ID)\n", "client.print_summary(TOOL_ID)" ] }, { "cell_type": "code", "execution_count": 12, "id": "aba8fc21", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
metricpublished (Forrester)expected (Athena methodology)athena actualvs expected
0total_benefits_pv14,840,63814,840,64014,840,637-0.00%
1total_costs_pv4,057,1703,938,1703,938,170+0.00%
2net_present_value10,783,46810,902,47010,902,466-0.00%
3roi_percentage266277277+0.01%
\n", "
" ], "text/plain": [ " metric published (Forrester) expected (Athena methodology) \\\n", "0 total_benefits_pv 14,840,638 14,840,640 \n", "1 total_costs_pv 4,057,170 3,938,170 \n", "2 net_present_value 10,783,468 10,902,470 \n", "3 roi_percentage 266 277 \n", "\n", " athena actual vs expected \n", "0 14,840,637 -0.00% \n", "1 3,938,170 +0.00% \n", "2 10,902,466 -0.00% \n", "3 277 +0.01% " ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Payback: 4 months (expected ≈ 4)\n", "✅ Tier 1 passed — pipeline reproduces the study under Athena's discounting.\n", "ℹ️ Tier 2: published ROI 266% vs Athena ~277% — explained Year-0 delta (see above).\n" ] } ], "source": [ "rows, ok = [], True\n", "for key in (\"total_benefits_pv\", \"total_costs_pv\", \"net_present_value\", \"roi_percentage\"):\n", " actual = float(summary.get(key) or 0)\n", " expected = seed.ATHENA_EXPECTED[key]\n", " published = seed.PUBLISHED[key]\n", " diff = (actual - expected) / expected\n", " rows.append({\n", " \"metric\": key,\n", " \"published (Forrester)\": f\"{published:,.0f}\",\n", " \"expected (Athena methodology)\": f\"{expected:,.0f}\",\n", " \"athena actual\": f\"{actual:,.0f}\",\n", " \"vs expected\": f\"{diff:+.2%}\",\n", " })\n", " ok &= abs(diff) <= 0.005\n", "\n", "display(pd.DataFrame(rows))\n", "print(f\"Payback: {summary.get('payback_period_months')} months (expected ≈ 4)\")\n", "assert ok, \"Athena diverged >0.5% from its own expected methodology — investigate.\"\n", "print(\"✅ Tier 1 passed — pipeline reproduces the study under Athena's discounting.\")\n", "print(\"ℹ️ Tier 2: published ROI 266% vs Athena ~277% — explained Year-0 delta (see above).\")" ] }, { "cell_type": "markdown", "id": "181c7b55", "metadata": {}, "source": [ "## 7 · Save a baseline version & persist IDs" ] }, { "cell_type": "code", "execution_count": 13, "id": "d8102590", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Saved version 1 (baseline).\n", "Saved to /Users/robert/git/palladium/.env:\n", " PALLADIUM_GENESYSCX_REPORT_PUBLIC_ID=UCb2hSJprSBx\n", " PALLADIUM_GENESYSCX_TOOL_PUBLIC_ID=3rzDgVdsjhVv\n", " PALLADIUM_GENESYSCX_PROPOSAL_ID=1\n", "\n", "Next → 01_business_case.ipynb (AI token cost entry + sensitivity).\n" ] } ], "source": [ "if not client.list_versions(TOOL_ID):\n", " client.save_version(TOOL_ID, note=(\n", " \"Baseline — published Forrester CX Cloud TEI figures (Dec 2025). \"\n", " \"genesys_ai_tokens at $0 per the published study; set the annual \"\n", " \"cost from the Genesys quote in 01_business_case before client use.\"\n", " ))\n", " print(\"Saved version 1 (baseline).\")\n", "\n", "ids = {\n", " \"PALLADIUM_GENESYSCX_REPORT_PUBLIC_ID\": REPORT_ID,\n", " \"PALLADIUM_GENESYSCX_TOOL_PUBLIC_ID\": TOOL_ID,\n", "}\n", "if PROPOSAL_ID is not None:\n", " ids[\"PALLADIUM_GENESYSCX_PROPOSAL_ID\"] = str(PROPOSAL_ID)\n", "if ENGAGEMENT_ID is not None:\n", " ids[\"PALLADIUM_GENESYSCX_ENGAGEMENT_ID\"] = str(ENGAGEMENT_ID)\n", "\n", "env_path = update_env(**ids)\n", "print(f\"Saved to {env_path}:\")\n", "for k, v in ids.items():\n", " print(f\" {k}={v}\")\n", "print(\"\\nNext → 01_business_case.ipynb (AI token cost entry + sensitivity).\")" ] }, { "cell_type": "code", "execution_count": null, "id": "4fc81c99-f073-486a-9f65-f207e96e59cd", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.7" } }, "nbformat": 4, "nbformat_minor": 5 }