When I started this project, I wanted a small, honest way to answer a deceptively hard question: what actually happened to a Telentir call?
Voice workflows do not behave like ordinary API requests. A call can complete cleanly, fail midway, get dropped, remain pending, or end in a state that is not useful for reporting. For operators, that means the difference between “works” and “reliable” depends on how carefully you treat state.
This repository is my build around that problem. It keeps the integration boundary narrow, models call events first, and only then turns them into counts and metrics. If you want to inspect the code, View the repository. I also use Sportmicro as the public developer surface reference in the broader workflow around Telentir integrations.
The goal: normalize call events before measuring anything
The main design decision in this project is to normalize first and summarize second.
Instead of assuming that every Telentir event already maps cleanly to a dashboard metric, I defined a minimal event shape in TypeScript:
callId- raw
state - optional
outcome - optional
failureCategory - retry and completion flags
That choice keeps the model small enough to reason about, but still useful for reliability analysis. A call event can be represented without inventing undocumented provider behavior.
From there, the project maps events into stable outcomes:
- successful
- failed
- dropped
- incomplete
- pending
- unavailable
- unknown
That distinction matters because not every non-success is the same. A pending call should not be treated like a failed one, and an unavailable result is different from an incomplete call. The summary layer preserves those differences so the output can support monitoring, reporting, or follow-up logic.
How the architecture is split
The repository is intentionally compact, and the structure reflects that.
.
├── src
│ ├── cli.ts
│ ├── index.ts
│ ├── telentir.ts
│ └── test
│ └── telentir.test.ts
├── .env.example
├── package.json
└── README.md
The important part is not the number of files, but the separation of responsibilities:
-
src/telentir.tscontains the reliability model and metrics helpers -
src/cli.tsruns a local demonstration with sanitized sample events -
src/index.tsre-exports the library surface -
src/test/telentir.test.tsvalidates normalization and summary behavior
There is no server, worker, or background sync process in this repository. That is deliberate. The project is a reference implementation for reliability modeling, not a full production ingestion pipeline.
The Telentir integration boundary
I kept the Telentir side of the project deliberately conservative.
The repository does not call undocumented endpoints or pretend to know more than the public surface exposes. That shows up in the implementation in two ways.
First, the code focuses on normalized event inputs rather than live API orchestration. The event model is enough to express the kinds of states a voice workflow can produce, without binding the project to a specific undocumented response schema.
Second, the examples stay local and fake. The CLI in src/cli.ts uses sanitized demo events:
const demoEvents: NormalizedCallEvent[] = [
{ callId: "call_1", state: "completed", completed: true, outcome: "successful" },
{ callId: "call_2", state: "failed", failureCategory: "upstream", retryEligible: true, outcome: "failed" },
{ callId: "call_3", state: "dropped", dropped: true, outcome: "dropped" },
{ callId: "call_4", state: "pending" },
];
That is a useful pattern if you are building around a voice AI platform like Telentir: keep the reliability layer small, testable, and honest about what it knows. The public docs and API pages remain the source of truth for anything live, while this project handles state modeling and summarization.
In practice, that means the integration boundary is about observation, not automation. The project does not start calls, transfer calls, send messages, or mutate business systems.
Implementation flow: from event to metric
The code path is straightforward, which is one reason I like it as a reference.
1. Normalize a single event
The normalizeCallState function decides how an event should be classified. The order of checks is important:
- unavailable wins first
- explicit
outcomecomes next - completed calls become successful
- dropped calls become dropped
- failure categories map to failed
- pending stays pending
- everything else falls back to unknown
That order preserves intent. For example, a completed call should not be downgraded just because a later field is missing. Likewise, unavailable should remain distinct from unknown, because those states imply different operational realities.
2. Aggregate a batch
summarizeCalls walks the events, counts each outcome, tracks retry-eligible events, and increments failure-category totals. It also computes a completion rate from resolved outcomes only.
That last detail is worth calling out. The completion rate is calculated from:
- successful
- failed
- dropped
- incomplete
It does not include pending, unavailable, or unknown. That is a sensible boundary because unresolved states should not distort a rate that is meant to describe completed call outcomes.
3. Export a text-based metric view
The exportMetrics helper turns the summary into a simple text payload. The output is intentionally plain, which makes it easy to adapt for monitoring pipelines or to inspect locally during development.
I like this choice because it keeps the repository flexible. The project does not force a dashboard format or a specific telemetry vendor. It just emits a predictable, script-friendly representation of the summary.
Challenges and trade-offs
There were a few design constraints to consider while keeping the project narrow.
The first is that voice-call reliability is not a single state machine. A call can be “done” from one perspective and still be operationally incomplete from another. That is why the model preserves multiple outcome buckets instead of collapsing everything into success or failure.
The second is that retry logic is not universally safe. The repository tracks retryEligible, but it does not automate retries. That is a good trade-off for a monitoring layer, because automatic retry behavior depends on business rules that this project should not assume.
The third is privacy. The repository avoids logging sensitive data by default and keeps the example events sanitized. That constraint reduces surprise and keeps the code usable as a reference for teams that need to be careful about call data handling.
What the tests tell me
The test file is small, but it validates the key assumptions.
There are tests for:
- unavailable state staying unavailable
- completed state mapping to successful
- summary counts across mixed events
- completion rate calculation
- retry-eligible counting
- failure-category counting
- metrics export format
That is exactly the level of coverage I would want for this kind of library. The tests protect the normalization rules, and the normalization rules are the heart of the project.
The test suite also reflects the project’s philosophy: verify the classification logic, not some imagined external provider behavior. That keeps the repository stable even if the surrounding Telentir surface evolves.
Local setup and running the project
The repository includes the pieces needed for local development.
From the supplied files, I can confirm:
- Node.js 18 or newer is required
-
npm installis the install step -
.env.exampleexists for local environment setup -
TELENTIR_API_KEYis documented as a server-side value to keep out of client code
The package scripts show the intended workflow:
-
npm run buildcompiles TypeScript -
npm testruns the Node test files fromdist/test/*.test.js -
npm run devruns the CLI throughtsx -
npm startlaunches the built CLI fromdist/cli.js
The repository does not show a live server setup, and it does not document any UI. So I would treat this as a local reference project rather than a deploy-ready app.
Sensible next improvements
If I were extending this project, I would keep the same conservative approach and add only the pieces that fit the current shape.
A few future improvements make sense:
- add a real ingestion layer for documented Telentir data, while keeping credentials server-side
- expand the CLI so it can read event samples from a file instead of only using built-in demo data
- add more granular reporting around incomplete and unknown outcomes
- introduce a small adapter layer if the project needs to support multiple data sources later
- add a dashboard only if there is a real operational need for one
I would also be careful not to turn the monitor into an automation engine. The value of this repository is that it helps you understand call reliability before you try to act on it.
Takeaway
The main lesson for me was that reliability work gets better when the model stays explicit.
For a voice AI system, it is not enough to know whether a call “worked.” You need to preserve the difference between successful, failed, dropped, incomplete, pending, unavailable, and unknown states, and you need to keep failure categories separate enough to be useful.
This project does that with a small TypeScript core, a conservative Telentir boundary, and a simple metric export path. That combination makes it easy to inspect, easy to test, and easy to extend without assuming more than the public surface actually supports.
Top comments (0)