agentleFS
aiappsgbbaiappsgbb/awesome-gbb/skills/paygo-ptu-cost-analyzer/SKILL.md

paygo-ptu-cost-analyzer

Skill8 starsChanged 12 days agoMITView on GitHub

Headless Azure OpenAI PAYGO-vs-PTU cost analysis and sizing report. Wraps the analysis library of aiappsgbb/ptu-paygo-mix (NOT its Streamlit UI). Supports CSV token-usage input and Log Analytics queries via vendored KQL against `AzureMetrics`. Produces a markdown report, JSON data, and charts. Bundles a synthetic-data generator for offline demos. USE FOR: ptu sizing, paygo vs ptu, provisioned throughput unit recommendation, AOAI capacity planning, TPM percentile report, spillover cost estimate, log analytics token query, AzureMetrics PTU, CSV-based PTU sizing, headless cost report, paygo-ptu-cost-analyzer. DO NOT USE FOR: capacity reservation purchase workflow, deploying the upstream Streamlit UI (use the upstream repo), real-time TPM monitoring (use azure-monitor-query directly), pricing for non-Azure-OpenAI services.

  • Installs packages

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All three paths converge on the same run_analysis() and render_report() so the output contract is identical.

    line 55
  2. Distinctive:

    Every run prints the 4 absolute output paths to stdout and exits 0 on success, non-zero on validation errors.

    line 107
  3. Very specific:

    Every run produces exactly these four files under --out-dir:

    line 114
  4. Very specific:

    If your workspace uses AzureDiagnostics instead of AzureMetrics, write a custom KQL that emits the same 4-column schema and pass it with …

    line 241

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

PAYGO vs PTU Cost Analyzer

Headless cost-analysis skill for Azure OpenAI. Given a window of per-minute token usage, it computes the percentile TPM envelope, prices both PAYGO and PTU scenarios per percentile, estimates spillover costs, and emits a markdown + JSON + PNG report.

This is the analysis core of aiappsgbb/ptu-paygo-mix wrapped in a CLI; the upstream Streamlit UI is excluded by design (this skill runs in a terminal, in CI, or inside an agent — never as a web app).

Sister capability. If you actually want the interactive UI, use the upstream repo directly (uv run streamlit run app/streamlit_app.py). This skill is for the cases where the seller / analyst / agent needs a report file — over chat, in a PR, or as part of an automated capacity-planning rollup.

Continuous monitoring companion. foundry-cost-monitoring consumes live OTel gen_ai.usage.* spans for per-agent / per-project cost projection — use it when the FinOps question is "what's my spend right now?" rather than "how should I size PTU?"


Two data paths

PathWhen to useAuth needed
--csv <path>Customer already exported their token usage (e.g. via the upstream docs/sample_kql_queries.md, Excel, Cost Mgmt export).None.
--workspace <id>You have direct read access on the Log Analytics workspace ingesting Cognitive Services metrics.DefaultAzureCredential (az login / managed identity / env vars / VS Code) with Log Analytics Reader on the workspace.
--syntheticOffline demo, regression test, or you need to show the report shape before real data lands.None.

All three paths converge on the same run_analysis() and render_report() so the output contract is identical.

Required CSV schema

ColumnTypeNotes
minute_bindatetime (parseable)Per-minute bucket.
input_tokens_sumintTotal input tokens in the bucket (includes cached).
cached_tokens_sumintSubset of input_tokens_sum that hit the prompt cache.
output_tokens_sumintGenerated tokens.

normalize_usage_dataframe() validates the schema, parses datetimes, strips thousands separators, and sorts by minute_bin. Bad input fails loudly with a descriptive ValueError.


Quickstart

# 0. One-time deps (matches references/upstream-pin.md)
python3 -m venv .venv && source .venv/bin/activate
pip install "pandas~=3.0.3" "numpy~=2.4.6" "matplotlib~=3.11.0" \
            "azure-identity~=1.25.3" "azure-monitor-query~=2.0.0"

# 1. Offline demo — uses the bundled synthetic generator
python references/run_report.py \
  --synthetic --days 3 \
  --model gpt-5.4 --tier global --ptu-term monthly \
  --out-dir ./paygo-ptu-report

# 2. Customer CSV
python references/run_report.py \
  --csv ./customer_tpm_april.csv \
  --model gpt-5.2 --tier data_zone --ptu-term yearly \
  --percentiles 75,90,95,99 \
  --out-dir ./customer-report

# 3. Live Log Analytics (requires az login + workspace Reader role)
python references/run_report.py \
  --workspace 11111111-2222-3333-4444-555555555555 \
  --time-range 14d \
  --deployment my-gpt-5-4-prod \
  --model gpt-5.4 \
  --out-dir ./live-report

# A bundled tiny CSV is also available for smoke tests:
python references/run_report.py \
  --csv references/sample_input.csv --model gpt-5.4 --out-dir /tmp/smoke

Every run prints the 4 absolute output paths to stdout and exits 0 on success, non-zero on validation errors.


Output contract

Every run produces exactly these four files under --out-dir:

FileContents
report.mdNarrative report: input window summary, percentile TPM table, PAYGO baseline breakdown, per-percentile PTU sizing scenarios (PTUs, capacity, covered-token %, base cost, spillover cost, total, ∆ vs PAYGO), recommendation paragraph, pricing snapshot.
report.jsonFull run_analysis() dict + run metadata (model, tier, ptu_term, source, percentiles, pricing_snapshot, generated_at). Reconsumable by downstream agents.
tpm_over_time.pngLine chart of total_tpm over minute_bin with horizontal dashed lines for each percentile's PTU capacity (so you can eyeball spillover).
ptu_sizing_scenarios.pngGrouped bar chart per percentile: PTU base cost (blue) stacked with spillover cost (orange), plus a green dashed line at the PAYGO baseline. Each bar labelled with the total.

The markdown report always contains the substrings "PTU Sizing", "PAYGO Baseline", and "p95" (the freshness validator asserts these).


CLI reference (run_report.py)

FlagDefaultNotes
--csv <path>—CSV matching the required schema. Mutually exclusive with --workspace / --synthetic.
--workspace <id>—Log Analytics workspace GUID. Mutually exclusive.
--synthetic—Use the bundled synthetic generator. Mutually exclusive.
--kql <path>references/queries/default.kqlKQL template; placeholders __TIME_RANGE__, __DEPLOYMENT_FILTER__.
--time-range7dNd or Nh. Applied to KQL + Log Analytics timespan.
--deploymentemptyFilter to a specific Cognitive Services deployment. Optional.
--days7Synthetic data duration.
--modelgpt-5.4Slug from references/analyzer/models.json.
--tierglobalglobal or data_zone (per the catalog).
--ptu-termmonthlymonthly or yearly. Switches the cost columns + recommendation.
--percentiles50,75,90,95,99Comma-separated, in (0,100).
--ptu-output-weightmodel's ptu.output_weightMultiplier on output-token TPM when sizing PTUs. Defaults to the selected model's catalog ptu.output_weight (e.g. 6 for gpt-5.4); pass a value to override (e.g. 1.0 for the old flat behaviour). Falls back to 1 if the model has no output_weight.
--out-dir./paygo-ptu-reportCreated if absent.

Pricing catalog (references/analyzer/models.json)

Vendored from the upstream repo. Each model entry carries:

  • paygo.global + optional paygo.data_zone → input_per_m, cached_input_per_m, output_per_m, plus optional priority_processing rates
  • ptu.capacity_tpm, ptu.min_deployment, ptu.increment, ptu.output_weight
  • ptu.global + optional ptu.data_zone → monthly_price, yearly_price

ptu.output_weight (typically 4–8) scales output-token TPM when sizing PTUs; --ptu-output-weight defaults to it (see the CLI table). As of the 2636464 re-vendor the catalog also ships gpt-5.5 and gpt-5.4-mini, and gpt-5.4 now carries data_zone pricing (so --tier data_zone resolves for it).

To use a custom catalog: edit models.json in place (it's a vendored file — not auto-refreshed). The upstream-pin tracks the SHA of the upstream repo so the catalog can be re-vendored when prices drift.

Pricing drift is real. Microsoft updates Azure OpenAI pricing several times a year. Treat the bundled models.json as a starting point for a customer conversation, not a quote — always confirm against the current pricing page for the customer's region and the region of their reservation.


Vendored layout

references/
├── analyzer/                      # Analysis core vendored from aiappsgbb/ptu-paygo-mix @ 2636464
│   ├── __init__.py                # Re-exports the public API
│   ├── analysis.py                # run_analysis() — pure pandas/numpy
│   ├── data.py                    # load/normalize/KQL helpers (frozen @ e1786f8, Streamlit stripped)
│   ├── formatting.py              # fmt_num / fmt_cost
│   └── models.json                # PTU + PAYGO pricing catalog
├── queries/
│   ├── default.kql                # AzureMetrics → required schema (InputTokens path)
│   └── active_tokens.kql          # Alternative: derives cached from ActiveTokens
├── render_report.py               # md + json + 2× png (matplotlib, Agg backend)
├── run_report.py                  # CLI entry point
├── sample_input.csv               # ~10 KB demo CSV (240 rows, reproducible seed)
└── upstream-pin.md                # Tier-B freshness contract

analysis.py, formatting.py, and models.json are byte-identical to upstream @ 2636464. Three files intentionally diverge:

  • data.py — frozen at the e1786f8 shape (Streamlit stripped: the import streamlit as st line and both @st.cache_data / @st.cache_data(ttl=300) decorators removed). Upstream deleted its live Log-Analytics / KQL path at commit 14a5bec; this skill keeps it to power --workspace mode, so data.py is not re-vendored from newer SHAs. See upstream-pin.md KI-002.
  • queries/*.kql — retained; upstream deleted the kql/ directory at 14a5bec. These drive the --workspace path.
  • __init__.py — exported names match the e1786f8 upstream plus REQUIRED_COLUMNS, MODELS_CONFIG_PATH, time_range_to_timedelta.

Re-vendoring the analysis core is a manual chore signalled by SHA drift in upstream-pin.md (the upstream repo is private — a token with read access is needed to clone it).


KQL path notes

The bundled query targets AzureMetrics for the Cognitive Services resource provider. Prereqs on the customer side:

  1. Diagnostic settings on the Azure OpenAI resource → route metrics to a Log Analytics workspace.
  2. Enable the metric categories InputTokens, ProcessedPromptTokens, GeneratedTokens (and optionally ActiveTokens).
  3. The caller needs Log Analytics Reader on the workspace (or Reader at the subscription scope).
  4. Allow at least 15 minutes for fresh metrics to land in AzureMetrics.

Two bundled queries — pick by metric availability

FileCached-token derivationUse when
references/queries/default.kqlInputTokens − ProcessedPromptTokensThe standard path. Works on any Cognitive Services deployment with InputTokens enabled.
references/queries/active_tokens.kqlProcessedPromptTokens − ActiveTokensFallback when InputTokens is missing or zero — uses the ActiveTokens metric (non-cached tokens) as the subtractor. Some older diagnostic-setting configurations only expose this path.

Pass either with --kql:

python references/run_report.py \
  --workspace <id> --kql references/queries/active_tokens.kql \
  --model gpt-5.4 --out-dir ./report

If your workspace uses AzureDiagnostics instead of AzureMetrics, write a custom KQL that emits the same 4-column schema and pass it with --kql. See docs/sample_kql_queries.md in the upstream repo for several alternative shapes (request-level breakdown, PTU utilisation, error analysis).


Known caveats

  • Pricing drift: as above — re-vendor models.json when upstream bumps. upstream-pin.md tracks the SHA.
  • AzureMetrics.Dimensions shape varies by API version — the bundled default KQL coalesces DimensionJson, Dimensions, and Tags, which covers everything we've seen. If you see empty ModelDeploymentName after a query, dump the raw row and adjust.
  • Synthetic generator's anchor date is 2026-01-19 (Monday). The synth produces realistic business-hours + weekend + burst patterns starting from that fixed timestamp — useful for reproducible demos, but obvious if you ship a customer report without disclosing it.
  • data.py + queries/*.kql are frozen at e1786f8 — upstream removed the live Log-Analytics / KQL path (commit 14a5bec); this skill keeps it for --workspace mode, so those files are not re-vendored from newer SHAs (only analysis.py / formatting.py / models.json are). The data.py Streamlit strip is part of that frozen baseline; the validation script catches any reintroduction because the import would fail outside a Streamlit context. See upstream-pin.md KI-002.

References

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.