Sunstead trust scoring project
1"""M7 voice briefing (PRD: an ElevenLabs voice briefing on the API).
2
3The explanation `summary` is already "suitable to read aloud" (6.6/6.9), so this
4just composes a short spoken script from an assessment and pipes it to ElevenLabs.
5No SDK — one POST with httpx.
6
7Env (only ELEVENLABS_API_KEY touches the network):
8 ELEVENLABS_API_KEY your key; absent -> the API returns the brief text instead of audio
9 ELEVENLABS_VOICE_ID voice (default: a public ElevenLabs voice)
10"""
11
12from __future__ import annotations
13
14import os
15
16URL = "https://api.elevenlabs.io/v1/text-to-speech/{voice}"
17DEFAULT_VOICE = os.environ.get("ELEVENLABS_VOICE_ID", "21m00Tcm4TlvDq8ikWAM") # "Rachel" (public)
18DECISION_PHRASE = {
19 "fast_lane": "safe to fast-lane",
20 "normal_queue": "for the normal review queue",
21 "needs_human": "routed to a human reviewer",
22}
23
24
25def brief_text(score: dict) -> str:
26 """2-3 sentence spoken script from an assessment. Skips raw DIDs (unspeakable)."""
27 who = score.get("handle") or score["did"]
28 pct = round((score.get("calibrated_prob") or 0) * 100)
29 decision = DECISION_PHRASE.get(score["decision"], score["decision"])
30 reason = score.get("explanation") or {}
31 out = [f"{who}: {decision}. Calibrated trust {pct} percent."]
32 if reason.get("compliance_block"):
33 out.append(reason["compliance_block"])
34 else:
35 factor = next((f for f in reason.get("top_factors", [])
36 if "did:" not in f and "trust reaches" not in f), None)
37 if factor:
38 out.append(factor.rstrip(".") + ".")
39 if reason.get("content_summary"):
40 out.append("Claude notes: " + reason["content_summary"])
41 return " ".join(out)
42
43
44def synthesize(text: str) -> bytes | None:
45 """ElevenLabs TTS -> mp3 bytes, or None when no API key is configured."""
46 key = os.environ.get("ELEVENLABS_API_KEY")
47 if not key:
48 return None
49 import httpx
50
51 r = httpx.post(URL.format(voice=DEFAULT_VOICE),
52 headers={"xi-api-key": key, "accept": "audio/mpeg"},
53 json={"text": text, "model_id": "eleven_turbo_v2_5"}, timeout=60)
54 r.raise_for_status()
55 return r.content
56
57
58def demo() -> None:
59 """Self-check: build a sensible script; synth is a no-op without a key."""
60 script = brief_text({
61 "did": "did:plc:alice", "handle": "alice.dev", "calibrated_prob": 1.0,
62 "decision": "needs_human",
63 "explanation": {"compliance_block": "sensitive-tier repo: a valid jurisdiction "
64 "attestation is required before fast-lane/merge (6.13)",
65 "top_factors": ["trust reaches did:plc:alice via maintainer", "8 merged PRs"]},
66 })
67 print(script)
68 assert "alice.dev" in script and "human reviewer" in script and "did:" not in script
69 assert synthesize(script) is None or isinstance(synthesize(script), bytes)
70 print("ok")
71
72
73if __name__ == "__main__":
74 demo()