Capstone: traces and a cost report
One file that turns span records into a readable tree, a cost per feature and a list of expensive failures. Every idea in the course, once.
One file, two questions
The capstone answers two questions from the same records: where did the time go, and what did it cost. The first is a tree built from parent ids; the second is a sum of tokens times prices, grouped by the attributes that explain the spend. Neither needs a vendor SDK, which is the point: the data model is the skill. Plain dictionaries keep every field visible, including the ones a library would rename. The code is short enough to read in one sitting, and every number is visible.
The report is what a team reads on Monday. It should fit on a screen, name the traces that are worth opening, and show the count behind every rate. A report that requires a dashboard to interpret is a report that will be looked at once. A reader should not need a second window open to understand the first. The sections are ordered by the question they answer, not by the order the data arrived.
The shape of the report
Per trace, print the tree with duration and self time, then the total cost. Per feature and prompt version, print requests, p95 latency, cost, cost per success and error rate. Finish with the most expensive failures and the feedback attached to them, because a costly failure is the one worth fixing first. The tree is for the single request, the table is for the comparison, and the failure list is for the next hour of work. A reader should be able to stop after any section and still have a complete answer.
Keep the sections in that order. The order is a reading order: case, population, then the queue for the next hour. Mixing them produces a page that is interesting and not actionable. Put the count next to the rate and the window in the header; those two choices prevent most misreadings. A report that changes its price table between rows is not a report; it is a set of unrelated estimates.
Export the spans you just built
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
# In production, replace ConsoleSpanExporter with your trace backend's exporter.A tree with a bill attached
Print both trace trees and the cost per span, then add a third trace with a retry under generate. Confirm the retry appears in the tree and in the total.
Make the numbers comparable
A report is only useful if two rows mean the same thing. Use one price table, one time window and one definition of success across every feature. A 24-hour row compared with a seven-day baseline is not a comparison; it is two different questions sharing a column. A row without its window is a number that invites the wrong comparison.
Record the window and the price version in the report header. When someone asks why the number differs from last week, the header answers before the investigation starts. Reproducibility is a feature of the report, not a property of the dashboard. The header is also the first thing to check when a number looks impossible. A number that cannot be reproduced is a rumour with a decimal point.
Print the counts next to every rate. A 100 percent error rate on two requests is not a crisis; a 2 percent rate on 50,000 requests is, and the percentage alone cannot tell them apart.
What the capstone leaves out
A production system adds exporters, storage, retention, dashboards and alerts. Each is an adapter around the same records, not a new model. The exporter is the clearest example: the same spans can go to a console, a file or a hosted backend without changing the instrumentation. In production, it is the only line that changes. The hard part is deciding what to record and what each field means; the plumbing can be replaced without changing the questions. Retention is the one adapter with a legal dimension: a trace store is a copy of user text and inherits its obligations. The rest is a deployment choice.
The one thing not to leave out is the join key. Every cost row, feedback event and eval case should point at a trace id. Without it, the tree, the bill and the complaint are three unrelated datasets that happen to describe the same request. The id is small, stable and already in every record; there is no excuse for a report without it. The report is only as strong as that link.
The Monday report
Group the traces by feature and print requests, cost, cost per success and error rate. Then add a negative feedback event to the most expensive failure and show it in the list.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What two questions does the capstone answer from the same records?
