{"slug": "build-a-local-call-review-panel-with-python", "title": "Build a local call-review panel with Python", "summary": "Oruk, an API provider, released a tutorial and open-source tool for building a local call-review panel in Python. The tool lets users replay speaker turns alongside transcripts and vocal-expression scores, using a saved response and a licensed public recording without an API key. The example was prepared with an AI coding agent and is maintained by Oruk.", "body_md": "An audio analysis response is easier to inspect when you can hear the passage beside its transcript. This tutorial builds a local review page that lets you replay a speaker turn, search the words or annotations, and read vocal-expression scores separately from what was said.\n\nWe will start with an actual saved response and a licensed public recording. That path needs Python and a browser, but no API key, model download, or inference request. Then we will connect the same page to the Oruk Python SDK for your own recordings.\n\nDisclosure: this example is maintained by Oruk, the API provider. The article and implementation were prepared with an AI coding agent and checked with automated tests and browser playback tests. The sample demonstrates an integration; it is not an accuracy evaluation or a customer case study.\n\nYou need Python 3.10 or later, curl, and a current browser. The complete renderer uses Python's standard library. It generates an HTML file with a native audio player, plain JavaScript controls, and no third-party scripts or fonts.\n\n```\nmkdir oruk-call-review\ncd oruk-call-review\ncurl --fail --location -o call-review.py https://oruk.ai/examples/call-review.py\ncurl --fail --location -o response.json https://oruk.ai/samples/conversations/02-grocery-prices.oruk.json\ncurl --fail --location -o recording.wav https://oruk.ai/samples/conversations/02-grocery-prices.wav\ncurl --fail --location -o attribution.txt https://oruk.ai/examples/call-review-attribution.txt\npython3 call-review.py --response response.json --audio recording.wav --output review.html --attribution-file attribution.txt\n```\n\nOpen `review.html` in your browser. Keep `recording.wav` next to it. On Windows, use your Python launcher if `python3` is not available.\n\nThe 14.45-second grocery conversation comes from [The Agentic Data Company's Open Yap 1K public sample](https://huggingface.co/datasets/TheAgenticDataCompany/open-yap-1k), licensed under CC BY 4.0. Oruk excerpted and downmixed it. The attribution file is included in the generated report; preserve it when sharing the sample. Other demo recordings can have different permissions.\n\nThe [complete program](https://oruk.ai/examples/call-review.py) is available under the [MIT license](https://oruk.ai/examples/call-review.LICENSE.txt). The [saved JSON](https://oruk.ai/samples/conversations/02-grocery-prices.oruk.json) contains the model's original output, not substitute scores written for this tutorial.\n\nThe page has four passages. The first runs from 0.185 to 4.325 seconds and has the local label `speaker_0`. Its transcript begins:\n\nI'm sorry. The price of food has gone up so much.\n\nThe returned emotion annotation is `disappointed`, with a score of 0.868 when rounded. The speaking-style annotation is `casual`, with a rounded score of 0.869. The report displays them in separate lists below the words.\n\nPress **Play passage 1** to replay that interval. Playback pauses near the returned end time. Choose **Play from here without stopping** when you want to hear the surrounding context. Searching `frustrated` should leave two passages visible; clearing the search restores all four. Filtering reads the existing response and does not call the model.\n\nThere is a useful limitation in this example: its `words` arrays are empty. The response provides speaker-turn boundaries, not individual word timing. The page says that explicitly. If another response includes word timestamps, a details control exposes those returned values and lets you replay a word. It never assigns timestamps by dividing a passage's duration by its word count.\n\nFor a fresh analysis, use an English recording you have permission to process, up to 30 MB and 60 minutes. Create a key through your Oruk account and set `ORUK_API_KEY` in your environment. The live API uses the [current subscription terms](https://oruk.ai/pricing); the saved-data example above does not require a subscription.\n\nThis command reuses the SDK's existing complete file example rather than introducing another upload implementation:\n\n```\npython3 -m venv .venv\n# macOS / Linux; on Windows use .venv\\Scripts\\activate\nsource .venv/bin/activate\npython -m pip install oruk==0.2.6\ncurl --fail --location -o analyze-file.py https://oruk.ai/examples/analyze-file.py\n\n# Set ORUK_API_KEY in the environment before this command.\npython analyze-file.py my-recording.wav --diarize > my-response.json && \\\n  python call-review.py --response my-response.json --audio my-recording.wav --output my-review.html\n```\n\nThe renderer runs only if analysis succeeds. It reads the saved file; it does not upload or analyze the recording again. Do not attach the grocery sample's attribution to your own audio. You can supply your own source note with `--attribution-file`.\n\nIf you already have a Python application, use this request-and-save step instead of the `analyze-file.py` command:\n\n``` python\nimport json\nimport os\nfrom pathlib import Path\nfrom oruk import Oruk\n\nwith Oruk(api_key=os.environ[\"ORUK_API_KEY\"]) as client:\n    result = client.analyze(\n        \"my-recording.wav\",\n        model=\"oruk-resonance\",\n        diarize=True,\n    )\n\nPath(\"my-response.json\").write_text(\n    json.dumps(result, ensure_ascii=False, indent=2), encoding=\"utf-8\"\n)\n```\n\nChoose the CLI or this snippet. Running both makes two logical requests. Unified analysis returns the transcript, emotion, and speaking style together; separate transcription and affect calls would process the audio separately.\n\n`diarize=True` makes the segments speaker turns. Leave `num_speakers` unset unless you know the number of speakers. The labels belong to that recording; `speaker_0` does not mean “customer,” “agent,” or a persistent identity. These SDK calls use the recorded-file API, not the separate realtime WebSocket preview.\n\nThe report is built around a small data model:\n\n```\nfor segment in result[\"segments\"]:\n    print(segment[\"start\"], segment[\"end\"], segment.get(\"speaker\"))\n    print(\"Words:\", segment.get(\"text\"))\n    print(\"Emotion scores:\", segment.get(\"emotions\"))\n    print(\"Style scores:\", segment.get(\"styles\"))\n    for word in segment.get(\"words\") or []:\n        print(word[\"word\"], word[\"start\"], word[\"end\"])\n```\n\n`text` is a model transcript and can contain transcription errors. `emotions` and `styles` contain selected acoustic model scores. They describe model support for how speech sounds; they do not reveal a person's private feelings or intent.\n\nSeveral labels can coexist. Their scores need not sum to one, and the numbers are not automatically calibrated probabilities. Emotion output includes the highest-scoring label when none meets its selection threshold. An empty style list means no style was returned, which does not establish absence. See the [score interpretation reference](https://oruk.ai/docs#labels) before designing application thresholds.\n\nThe report keeps the returned order and values, displays three decimal places, and preserves the full score in the meter value and numeric tooltip. It does not turn a high score into a customer-risk verdict or rank a speaker's performance.\n\nThe essential interaction is a seek followed by playback:\n\n```\naudio.currentTime = start;\nawait audio.play();\n```\n\nThe complete program also tracks the chosen end time, checks that boundary while playback is running, and pauses near it. It stops that animation-frame check when paused. Native seeking outside the selected interval releases the stop boundary. Highlighting uses `audio.currentTime`, so it follows the recording rather than a separate elapsed-time timer. Overlapping intervals can both be active.\n\nThese boundaries are useful for reviewing a passage. They are not sample-accurate editing cuts. Browser media clocks and seek precision impose limits.\n\nThe renderer escapes transcript text, labels, titles, and source notes before inserting them into HTML. It does not inject a tagged transcript as executable markup. Its Content Security Policy permits the generated script and style by hash, allows local media, and blocks network connections. The generated report has no analytics or API calls.\n\nKeep the report's relative audio path intact when moving files. The HTML contains the transcript, even though it never contains the API key. Treat the report and recording under the same permissions and access controls.\n\nThis is a local review tool, not an authenticated multi-user application. A team version needs recording permissions, reviewer access, storage and retention rules, and representative evaluation before introducing any automatic triage threshold. The small example provides a place to inspect those decisions against the original audio.\n\nFurther reference: [speaker diarization](https://oruk.ai/guides/speaker-diarization-python), [SDK setup and errors](https://oruk.ai/docs/sdks), and [analysis response fields](https://oruk.ai/docs#reference-analysis).", "url": "https://wpnews.pro/news/build-a-local-call-review-panel-with-python", "canonical_source": "https://dev.to/nathanroll/build-a-local-call-review-panel-with-python-2dgh", "published_at": "2026-09-08 01:31:19+00:00", "updated_at": "2026-09-08 02:00:46.508712+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "natural-language-processing"], "entities": ["Oruk", "The Agentic Data Company", "Open Yap 1K", "Python"], "alternates": {"html": "https://wpnews.pro/news/build-a-local-call-review-panel-with-python", "markdown": "https://wpnews.pro/news/build-a-local-call-review-panel-with-python.md", "text": "https://wpnews.pro/news/build-a-local-call-review-panel-with-python.txt", "jsonld": "https://wpnews.pro/news/build-a-local-call-review-panel-with-python.jsonld"}}