sable-sphynx 0.7.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. sable_sphynx-0.7.0.dev0.dist-info/METADATA +152 -0
  2. sable_sphynx-0.7.0.dev0.dist-info/RECORD +174 -0
  3. sable_sphynx-0.7.0.dev0.dist-info/WHEEL +5 -0
  4. sable_sphynx-0.7.0.dev0.dist-info/top_level.txt +1 -0
  5. sphynx/__init__.py +469 -0
  6. sphynx/__init__.pyi +376 -0
  7. sphynx/_analytical_client.py +77 -0
  8. sphynx/_analytical_values.py +252 -0
  9. sphynx/_audit_client.py +173 -0
  10. sphynx/_comparison_adapters.py +113 -0
  11. sphynx/_componentized_full_rag.py +34 -0
  12. sphynx/_composed_pipeline.py +109 -0
  13. sphynx/_full_rag_grading.py +218 -0
  14. sphynx/_full_rag_trace.py +221 -0
  15. sphynx/_local_eval.py +268 -0
  16. sphynx/_pipeline_core.py +243 -0
  17. sphynx/_pr_client.py +237 -0
  18. sphynx/_rag_diagnostics_calibration.py +285 -0
  19. sphynx/_rag_diagnostics_catalog.py +39 -0
  20. sphynx/_rag_diagnostics_common.py +26 -0
  21. sphynx/_rag_diagnostics_contracts.py +186 -0
  22. sphynx/_rag_diagnostics_engine.py +243 -0
  23. sphynx/_rag_diagnostics_importers.py +242 -0
  24. sphynx/_rag_diagnostics_ranking.py +81 -0
  25. sphynx/_rag_diagnostics_semantic.py +325 -0
  26. sphynx/_rag_ranking.py +149 -0
  27. sphynx/_speech_benchmark_scoring.py +140 -0
  28. sphynx/_speech_benchmark_sources.py +275 -0
  29. sphynx/_speech_complete_scoring.py +218 -0
  30. sphynx/_speech_complete_source.py +118 -0
  31. sphynx/_speech_kido.py +294 -0
  32. sphynx/_speech_scoring.py +77 -0
  33. sphynx/_track_capture.py +213 -0
  34. sphynx/analytical_views.py +515 -0
  35. sphynx/assessment_contracts.py +378 -0
  36. sphynx/assessment_selection.py +57 -0
  37. sphynx/audit_access.py +245 -0
  38. sphynx/audit_contracts.py +204 -0
  39. sphynx/audit_requests.py +180 -0
  40. sphynx/authoring.py +188 -0
  41. sphynx/benchmarks.py +568 -0
  42. sphynx/candidates.py +62 -0
  43. sphynx/catalog.py +272 -0
  44. sphynx/chunking.py +372 -0
  45. sphynx/clickhouse_reader.py +169 -0
  46. sphynx/client.py +486 -0
  47. sphynx/comparison_core.py +149 -0
  48. sphynx/comparison_views.py +223 -0
  49. sphynx/components.py +1661 -0
  50. sphynx/contracts.py +514 -0
  51. sphynx/customer_rerank.py +299 -0
  52. sphynx/customer_retrieval.py +146 -0
  53. sphynx/data/benchmark-releases.v1.json +164 -0
  54. sphynx/data/public-benchmark-release.v1.json +354 -0
  55. sphynx/data/rag-diagnostics-synthetic.v1.json +6982 -0
  56. sphynx/data/rag-paper-references.v1.json +176 -0
  57. sphynx/data/rag-paper-schemas.v1.json +1069 -0
  58. sphynx/data/rag-rerank-v1.json +507 -0
  59. sphynx/dataset_catalog_data.py +1214 -0
  60. sphynx/dataset_readiness.py +138 -0
  61. sphynx/dataset_v3.py +999 -0
  62. sphynx/demo.py +130 -0
  63. sphynx/demo_candidates/__init__.py +1 -0
  64. sphynx/demo_candidates/dense.py +11 -0
  65. sphynx/demo_candidates/lexical.py +21 -0
  66. sphynx/diagnostics.py +207 -0
  67. sphynx/documentation.py +737 -0
  68. sphynx/documentation_candidate.py +939 -0
  69. sphynx/evals.py +538 -0
  70. sphynx/evaluation_protocols.py +124 -0
  71. sphynx/evaluator_bindings.py +370 -0
  72. sphynx/evidence.py +352 -0
  73. sphynx/execution_capabilities.py +109 -0
  74. sphynx/fast_search.py +285 -0
  75. sphynx/full_rag.py +341 -0
  76. sphynx/hosted/__init__.py +18 -0
  77. sphynx/hosted/benchmark_intake.py +218 -0
  78. sphynx/hosted/contracts.py +361 -0
  79. sphynx/hosted/model_admission.py +130 -0
  80. sphynx/hosted/model_artifacts.py +298 -0
  81. sphynx/hosted/model_contracts.py +63 -0
  82. sphynx/hosted/model_gcs.py +181 -0
  83. sphynx/hosted/model_history.py +84 -0
  84. sphynx/hosted/model_session.py +233 -0
  85. sphynx/hosted/public_suite.py +74 -0
  86. sphynx/hosted/registry.py +97 -0
  87. sphynx/hosted/revisions.py +126 -0
  88. sphynx/hosted/sandbox.py +105 -0
  89. sphynx/hosted/submission.py +287 -0
  90. sphynx/hosted_workflow.py +451 -0
  91. sphynx/http_transport.py +86 -0
  92. sphynx/integrated_rag.py +352 -0
  93. sphynx/integrated_workflow.py +552 -0
  94. sphynx/integration_contracts.py +474 -0
  95. sphynx/invocation.py +791 -0
  96. sphynx/invocation_candidates.py +392 -0
  97. sphynx/invocation_catalog_data.py +40 -0
  98. sphynx/learned_catalog_data.py +30 -0
  99. sphynx/learned_rerank.py +267 -0
  100. sphynx/legacy_full_rag.py +67 -0
  101. sphynx/lifecycle.py +885 -0
  102. sphynx/lifecycle_workflow.py +490 -0
  103. sphynx/metric_views.py +474 -0
  104. sphynx/metrics.py +282 -0
  105. sphynx/model_adapters.py +438 -0
  106. sphynx/model_catalog_data.py +30 -0
  107. sphynx/official_benchmarks.py +385 -0
  108. sphynx/packs.py +258 -0
  109. sphynx/pipeline.py +313 -0
  110. sphynx/pipeline_workflow.py +551 -0
  111. sphynx/pr_evaluations.py +560 -0
  112. sphynx/progress.py +89 -0
  113. sphynx/public_bundle.py +173 -0
  114. sphynx/public_eval.py +62 -0
  115. sphynx/public_rerank.py +355 -0
  116. sphynx/py.typed +0 -0
  117. sphynx/pytest_plugin.py +62 -0
  118. sphynx/question_robustness.py +437 -0
  119. sphynx/rag.py +329 -0
  120. sphynx/rag_diagnostics.py +309 -0
  121. sphynx/rag_papers/__init__.py +29 -0
  122. sphynx/rag_papers/__main__.py +45 -0
  123. sphynx/rag_papers/candidates.py +19 -0
  124. sphynx/rag_papers/contracts.py +282 -0
  125. sphynx/rag_papers/datasets.py +291 -0
  126. sphynx/rag_papers/experiments.py +116 -0
  127. sphynx/rag_papers/judges.py +308 -0
  128. sphynx/rag_papers/registry.py +33 -0
  129. sphynx/rag_papers/runner.py +176 -0
  130. sphynx/rag_papers/sable.py +135 -0
  131. sphynx/rag_papers/schemas.py +12 -0
  132. sphynx/rag_papers/scoring.py +249 -0
  133. sphynx/rag_papers/semantic.py +288 -0
  134. sphynx/rag_papers/trec.py +80 -0
  135. sphynx/recorded_scores.py +352 -0
  136. sphynx/records.py +124 -0
  137. sphynx/reflex_traces.py +193 -0
  138. sphynx/report_rendering.py +71 -0
  139. sphynx/rerank_family_contracts.py +150 -0
  140. sphynx/rerank_workflow.py +424 -0
  141. sphynx/research_candidate.py +190 -0
  142. sphynx/results.py +852 -0
  143. sphynx/retrieval.py +750 -0
  144. sphynx/retrieval_candidates.py +295 -0
  145. sphynx/retrieval_catalog_data.py +19 -0
  146. sphynx/retrieval_recordings.py +125 -0
  147. sphynx/reviewer_audit.py +266 -0
  148. sphynx/routing_policies.py +375 -0
  149. sphynx/runner.py +544 -0
  150. sphynx/sable_rerank.py +489 -0
  151. sphynx/score_reliability.py +393 -0
  152. sphynx/speech_benchmarks.py +204 -0
  153. sphynx/speech_campaigns.py +378 -0
  154. sphynx/speech_complete.py +236 -0
  155. sphynx/speech_contracts.py +225 -0
  156. sphynx/speech_evidence.py +429 -0
  157. sphynx/speech_listening.py +115 -0
  158. sphynx/speech_multilingual.py +71 -0
  159. sphynx/speech_tts.py +479 -0
  160. sphynx/sql/001_sync_v1.sql +30 -0
  161. sphynx/stage_contracts.py +199 -0
  162. sphynx/statistical_catalog_data.py +53 -0
  163. sphynx/statistical_rerank.py +232 -0
  164. sphynx/sync.py +797 -0
  165. sphynx/workbench.py +378 -0
  166. sphynx/workbench_catalog_data.py +13 -0
  167. sphynx/workbench_tracks.py +78 -0
  168. sphynx/workflow_async.py +197 -0
  169. sphynx/workflow_contracts.py +594 -0
  170. sphynx/workflow_decisions.py +211 -0
  171. sphynx/workflow_defaults.py +16 -0
  172. sphynx/workflow_extensions.py +212 -0
  173. sphynx/workflow_registry.py +513 -0
  174. sphynx/workflows.py +792 -0
@@ -0,0 +1,378 @@
1
+ """Frozen speech protocol plans, explicit capture ingestion and offline fixtures.
2
+
3
+ No account access, microphone, provider or live session is launched here. Use
4
+ the separate TTS runner for TTS execution, or a reviewed collector for live
5
+ campaigns. Missing captures remain in every denominator.
6
+ """
7
+ from collections import Counter
8
+ from pathlib import Path
9
+ import argparse
10
+ import io
11
+ import json
12
+ import math
13
+ import uuid
14
+ import wave
15
+
16
+ from ._speech_kido import digest,encoded,json_value,require,number
17
+ from ._speech_scoring import score_transcript
18
+ from .speech_contracts import identifier
19
+ from .speech_evidence import _new_directory,_read,_write_files,_sha
20
+
21
+ SUITE_ID='speech.kido-campaigns.v1'
22
+ SCHEMA='sphynx.speech.kido-campaigns'
23
+ PROTOCOLS=('session-reliability','controlled-barge-in','rapid-turn-stress','machine-intelligibility')
24
+
25
+
26
+ def _candidate(value):
27
+ require(type(value) is dict and set(value)=={'id','provider','model','voice_id','source_commit','source_pr'},'Candidate identity fields differ')
28
+ for k in ('id','provider','model'):identifier(value[k])
29
+ require(isinstance(value['source_commit'],str) and len(value['source_commit'])==40
30
+ and all(c in '0123456789abcdef' for c in value['source_commit']),'Pin full candidate commit')
31
+ import re
32
+ require(isinstance(value['source_pr'],str) and re.fullmatch(r'https://github.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/pull/[1-9][0-9]*',value['source_pr']),'Pin candidate PR')
33
+ require(value['voice_id'] is None or isinstance(value['voice_id'],str) and 0<len(value['voice_id'])<=128,'Invalid voice identity')
34
+ return dict(value)
35
+
36
+
37
+ def _audio(path):
38
+ path=Path(path).absolute();raw=_read(path.parent,path.name)
39
+ try:
40
+ with wave.open(io.BytesIO(raw),'rb') as reader:
41
+ require(reader.getnchannels()==1 and reader.getsampwidth()==2 and reader.getcomptype()=='NONE'
42
+ and reader.getframerate() in (8000,16000,24000,44100,48000),'Use bounded mono PCM16 WAV audio')
43
+ count=reader.getnframes();rate=reader.getframerate();pcm=reader.readframes(count)
44
+ require(count>0 and count<=rate*120 and len(pcm)==count*2,'Empty, truncated or oversized PCM audio')
45
+ except (wave.Error,EOFError):raise ValueError('Invalid campaign WAV') from None
46
+ return raw,dict(sha256=digest(raw),size_bytes=len(raw),sample_rate=rate,frames=count,duration_ms=count/rate*1000)
47
+
48
+
49
+ def freeze_speech_campaign(bundle,*,protocol,candidates,output_dir,mode='capture',sessions_per_candidate=20,
50
+ rapid_turns=8,max_idle_ms=200,interrupt_after_ms=500,interrupt_tolerance_ms=250,
51
+ microphone_audio=None,audio_cases=None,asr=None,max_units=200,collector_source_sha256=None):
52
+ """Freeze an exact design. Audio references stay out of collector inputs."""
53
+ from .speech_complete import _verified
54
+ evidence=_verified(bundle)
55
+ require(protocol in PROTOCOLS and mode in ('capture','fixture'),'Unknown campaign protocol or mode')
56
+ if collector_source_sha256 is not None:_sha(collector_source_sha256)
57
+ require(type(candidates) is list and 1<=len(candidates)<=16,'Invalid candidate census')
58
+ candidates=[_candidate(c) for c in candidates]
59
+ require(len({c['id'] for c in candidates})==len(candidates),'Duplicate candidate')
60
+ require(type(sessions_per_candidate) is int and 1<=sessions_per_candidate<=100,'Invalid sessions per candidate')
61
+ require(type(max_units) is int and 1<=max_units<=200,'Invalid campaign unit cap')
62
+ for v in (max_idle_ms,interrupt_after_ms,interrupt_tolerance_ms):number(v,'campaign timing')
63
+ require(0<interrupt_tolerance_ms<=1000 and 0<interrupt_after_ms<=10000 and max_idle_ms<=2000,'Campaign timing exceeds limits')
64
+ require(type(rapid_turns) is int and 8<=rapid_turns<=10,'Rapid-turn design requires eight to ten turns')
65
+ payload={};references={};script=evidence['official_script']
66
+ if protocol=='session-reliability':steps=script[:4]
67
+ elif protocol=='controlled-barge-in':steps=script[5:7]
68
+ elif protocol=='rapid-turn-stress':steps=[script[6+i%4] for i in range(rapid_turns)]
69
+ else:steps=[]
70
+ steps=[dict(id='step:'+str(i+1),case_id=s['id'],text=s['text'],language=s['language']) for i,s in enumerate(steps)]
71
+ microphone=None
72
+ if protocol=='controlled-barge-in':
73
+ if mode=='capture':
74
+ require(microphone_audio is not None,'Freeze actual interruption audio before a microphone campaign')
75
+ raw,microphone=_audio(microphone_audio);payload['audio/microphone.wav']=raw
76
+ else:microphone=dict(sha256=digest(b'fixture microphone'),duration_ms=100,fixture=True)
77
+ if protocol=='machine-intelligibility':
78
+ require(type(asr) is dict and set(asr)=={'provider','model','revision','config'} and all(isinstance(asr[k],str) and asr[k] for k in ('provider','model','revision'))
79
+ and asr['revision'].lower() not in ('latest','default','auto') and type(asr['config']) is dict,'Freeze ASR revision and configuration')
80
+ require(type(audio_cases) is list and 0<len(audio_cases)<=100,'Supply approved audio cases')
81
+ inputs=[]
82
+ for row in audio_cases:
83
+ require(type(row) is dict and set(row)=={'id','path','reference','language','candidate_id'},'Audio case fields differ')
84
+ identifier(row['id']);require(isinstance(row['reference'],str) and len(row['reference'])<=10000,'Invalid reference')
85
+ import re
86
+ require(isinstance(row['language'],str) and re.fullmatch('[a-z]{2}(?:-[A-Za-z]{2,4})?',row['language']),'Invalid audio language')
87
+ require(row['candidate_id'] in {c['id'] for c in candidates},'Unknown audio candidate')
88
+ raw,meta=_audio(row['path']);name='audio/'+row['id'].replace(':','_')+'.wav'
89
+ require(name not in payload,'Duplicate audio identity');payload[name]=raw
90
+ inputs.append(dict(id=row['id'],candidate_id=row['candidate_id'],language=row['language'],audio=meta,audio_file=name))
91
+ references[row['id']]=row['reference']
92
+ require(len({r['id'] for r in inputs})==len(inputs),'Duplicate audio case ID')
93
+ units=[dict(id='unit:'+str(i+1),candidate_id=r['candidate_id'],audio_case=r,steps=[]) for i,r in enumerate(inputs)]
94
+ else:
95
+ require(asr is None and audio_cases is None,'ASR inputs belong only to machine intelligibility')
96
+ units=[dict(id='unit:'+str(i+1),candidate_id=c['id'],steps=steps) for i,(c,_) in enumerate(
97
+ (c,n) for c in candidates for n in range(sessions_per_candidate))]
98
+ require(len(units)<=max_units,'Campaign exceeds explicit unit cap')
99
+ plan=dict(schema_version=SCHEMA+'.plan.v1',suite_id=SUITE_ID,id='speech-campaign-'+uuid.uuid4().hex,
100
+ protocol=protocol,mode=mode,bundle_manifest_sha256=bundle.manifest_sha256,candidates=candidates,units=units,
101
+ source_commit='71ec7e89805360479ea19c5bb43fae7707229ce9',microphone=microphone,asr=asr,
102
+ design=dict(sessions_per_candidate=sessions_per_candidate,rapid_turns=rapid_turns,max_idle_ms=max_idle_ms,
103
+ interrupt_after_ms=interrupt_after_ms,interrupt_tolerance_ms=interrupt_tolerance_ms,max_units=max_units),
104
+ planned_units=len(units),planned_steps=sum(len(u['steps']) for u in units),provider_calls_made=False,
105
+ provider_cost_usd=None,monetary_cap_enforced=False,collector_source_verified=False,collector_source_sha256=collector_source_sha256,
106
+ implementation_sha256={p:digest(Path(__file__).with_name(p).read_bytes()) for p in ('speech_campaigns.py','_speech_scoring.py','speech_contracts.py')})
107
+ files=_write_files(_new_directory(output_dir),payload|{'references.json':encoded(references)})
108
+ dest=Path(output_dir).absolute();plan['files']=files;raw=encoded(plan);(dest/'plan.json').write_bytes(raw)
109
+ return dict(path=str(dest),plan_sha256=digest(raw),suite_id=SUITE_ID,protocol=protocol,planned_units=len(units),planned_steps=plan['planned_steps'])
110
+
111
+
112
+ def _load_plan(path,pin):
113
+ root=Path(path).absolute();raw=_read(root,'plan.json');require(digest(raw)==_sha(pin),'Campaign plan changed')
114
+ plan=json_value(raw)
115
+ require(plan.get('schema_version')==SCHEMA+'.plan.v1' and plan.get('suite_id')==SUITE_ID and
116
+ plan.get('protocol') in PROTOCOLS and plan.get('mode') in ('fixture','capture') and plan.get('provider_calls_made') is False,'Invalid campaign plan')
117
+ require(type(plan.get('units')) is list and len(plan['units'])==plan['planned_units'] and
118
+ len({u['id'] for u in plan['units']})==len(plan['units']),'Campaign unit census changed')
119
+ for c in plan['candidates']:_candidate(c)
120
+ for p,h in plan.get('files',{}).items():
121
+ b=_read(root,p);require(h==dict(sha256=digest(b),size_bytes=len(b)),'Campaign input artifact changed')
122
+ require('references.json' in plan['files'],'Campaign references missing')
123
+ return root,plan,json_value(_read(root,'references.json'))
124
+
125
+
126
+ def speech_campaign_requests(path,*,expected_plan_sha256):
127
+ """Collector view excludes reference text and unrelated repository data."""
128
+ root,plan,_=_load_plan(path,expected_plan_sha256)
129
+ return dict(plan_sha256=expected_plan_sha256,protocol=plan['protocol'],mode=plan['mode'],candidates=plan['candidates'],
130
+ design=plan['design'],asr=plan['asr'],microphone=plan['microphone'],units=plan['units'],
131
+ input_files={p:h for p,h in plan['files'].items() if p!='references.json'},
132
+ collector='reviewed external collector required for real sessions; this call launches nothing')
133
+
134
+
135
+ def _captures(plan,rows):
136
+ require(plan.get('schema_version')==SCHEMA+'.plan.v1' and plan.get('suite_id')==SUITE_ID and
137
+ plan.get('protocol') in PROTOCOLS and plan.get('mode') in ('fixture','capture') and
138
+ type(plan.get('units')) is list and 0<len(plan['units'])==plan.get('planned_units')<=200 and
139
+ len({u['id'] for u in plan['units']})==len(plan['units']),'Invalid frozen campaign census or scope')
140
+ require(type(plan.get('candidates')) is list and 0<len(plan['candidates'])<=16 and
141
+ len({c['id'] for c in plan['candidates']})==len(plan['candidates']),'Invalid frozen candidate census')
142
+ for c in plan['candidates']:_candidate(c)
143
+ require(type(rows) is list and len(rows)<=plan['planned_units'],'Invalid campaign capture census')
144
+ expected={u['id']:u for u in plan['units']};seen=set();sessions=set();result={}
145
+ candidates={c['id']:c for c in plan['candidates']}
146
+ for row in rows:
147
+ require(type(row) is dict and row.get('unit_id') in expected and row['unit_id'] not in seen,'Unknown or duplicate captured unit')
148
+ unit=expected[row['unit_id']];candidate=candidates[unit['candidate_id']];seen.add(row['unit_id'])
149
+ require(row.get('candidate_id')==candidate['id'] and row.get('candidate_sha256')==digest(encoded(candidate)),'Captured candidate identity differs')
150
+ require(row.get('source_kind')==('fixture' if plan['mode']=='fixture' else 'operator-capture'),'Capture provenance differs from plan')
151
+ require(row.get('status') in ('completed','connection_failed','disconnected','error','collector_error'),'Unknown capture status')
152
+ session=row.get('session_id')
153
+ if plan['protocol']!='machine-intelligibility' and row['status'] not in ('connection_failed','collector_error'):
154
+ identifier(session);require(session not in sessions,'Repeated session ID violates fresh-session design');sessions.add(session)
155
+ require(type(row.get('steps')) is list and len(row['steps'])<=len(unit['steps']),'Invalid captured step count')
156
+ require(len({s['step_id'] for s in row['steps']})==len(row['steps']),'Duplicate captured step')
157
+ wanted={s['id']:s for s in unit['steps']}
158
+ for s in row['steps']:
159
+ require(s['step_id'] in wanted and s.get('case_id')==wanted[s['step_id']]['case_id'],'Unknown captured step identity')
160
+ require(all(type(s.get(k)) is bool for k in ('complete','cancelled','error_present','cache_hit','interrupted')),'Invalid captured step flags')
161
+ require(type(s.get('audio_bytes')) is int and s['audio_bytes']>=0,'Invalid captured audio count')
162
+ for k in ('tts_start_ms','first_audio_ms','playout_start_ms','playout_end_ms'):number(s.get(k),k,nullable=True)
163
+ require(s.get('provider') is None or isinstance(s['provider'],str),'Invalid provider observation')
164
+ require(s.get('model') is None or isinstance(s['model'],str),'Invalid model observation')
165
+ result[row['unit_id']]=row
166
+ return result
167
+
168
+
169
+ def _successful(step,candidate):
170
+ return step['complete'] and not step['cancelled'] and not step['error_present'] and step['audio_bytes']>0 and step.get('provider')==candidate['provider'] and step.get('model') in (None,candidate['model'])
171
+
172
+
173
+ def evaluate_session_reliability(plan,captures):
174
+ bound=_captures(plan,captures);result=[]
175
+ for c in plan['candidates']:
176
+ units=[u for u in plan['units'] if u['candidate_id']==c['id']];rows=[bound[u['id']] for u in units if u['id'] in bound]
177
+ steps=[s for r in rows for s in r['steps']]
178
+ complete=sum(r['status']=='completed' and len(r['steps'])==len(next(u for u in units if u['id']==r['unit_id'])['steps']) and all(_successful(s,c) for s in r['steps']) for r in rows)
179
+ result.append(dict(candidate_id=c['id'],sessions_planned=len(units),sessions_attempted=len(rows),captures_missing=len(units)-len(rows),
180
+ connections_failed=sum(r['status']=='connection_failed' for r in rows),sessions_with_synthesis=sum(bool(r['steps']) for r in rows),
181
+ collector_errors=sum(r['status']=='collector_error' for r in rows),
182
+ all_scripted_steps_completed=complete,complete_session_rate=complete/len(units) if len(rows)==len(units) else None,
183
+ observed_completed_fraction=complete/len(units),synthesis_attempts=len(steps),synthesis_errors=sum(s['error_present'] for s in steps),
184
+ zero_audio_events=sum(s['complete'] and s['audio_bytes']==0 for s in steps),fallback_or_unconfirmed_attempts=sum(s.get('provider')!=c['provider'] for s in steps),
185
+ model_mismatch_attempts=sum(s.get('model') is not None and s['model']!=c['model'] for s in steps),model_identity_independently_verified=False,
186
+ missing_synthesis_telemetry=sum(r['status'] not in ('connection_failed','collector_error') and not r['steps'] for r in rows),
187
+ disconnected_sessions=sum(r['status']=='disconnected' for r in rows),disconnect_attributed_to_tts=None,
188
+ scope='fixture protocol check' if plan['mode']=='fixture' else 'controlled benchmark sessions; not production reliability',confidence_interval=None))
189
+ return result
190
+
191
+
192
+ def evaluate_controlled_barge_in(plan,captures):
193
+ bound=_captures(plan,captures);candidates={c['id']:c for c in plan['candidates']};result=[]
194
+ for unit in plan['units']:
195
+ row=bound.get(unit['id']);steps={s['step_id']:s for s in row['steps']} if row else {};c=candidates[unit['candidate_id']]
196
+ long=steps.get('step:1');recovery=steps.get('step:2');trigger=row.get('trigger_ms') if row else None
197
+ number(trigger,'actual microphone trigger',nullable=True)
198
+ start=long.get('playout_start_ms') if long else None;end=long.get('playout_end_ms') if long else None
199
+ timing=(all(x is not None for x in (start,end,trigger)) and start<trigger<=end and
200
+ abs(trigger-start-plan['design']['interrupt_after_ms'])<=plan['design']['interrupt_tolerance_ms'] and
201
+ 0<=end-trigger<=plan['design']['interrupt_tolerance_ms'])
202
+ mic=row.get('injected_audio_sha256')==plan['microphone']['sha256'] if row else False
203
+ recovered=bool(recovery and _successful(recovery,c) and trigger is not None and recovery['tts_start_ms'] is not None and recovery['tts_start_ms']>=trigger)
204
+ observed=bool(long and long['interrupted'] and long['audio_bytes']>0 and long.get('provider')==c['provider']
205
+ and long.get('model') in (None,c['model']) and timing and mic)
206
+ result.append(dict(unit_id=unit['id'],capture_available=row is not None,microphone_identity_matches=mic,
207
+ interruption_timing_valid=bool(timing),terminal_interruption_observed=bool(long and long['interrupted']),
208
+ recovery_completed=recovered,protocol_success=observed and recovered and row['status']=='completed',
209
+ interruption_to_playout_end_ms=end-trigger if timing else None,
210
+ subjective_latency=None,scope='fixture' if plan['mode']=='fixture' else 'frozen microphone interruption design'))
211
+ return result
212
+
213
+
214
+ def evaluate_rapid_turn_stress(plan,captures):
215
+ bound=_captures(plan,captures);candidates={c['id']:c for c in plan['candidates']};result=[]
216
+ for unit in plan['units']:
217
+ row=bound.get(unit['id']);steps={s['step_id']:s for s in row['steps']} if row else {};c=candidates[unit['candidate_id']]
218
+ sent=row.get('prompt_times_ms',{}) if row else {};values=[];idle=[];valid=bool(row and row['status']=='completed');previous=None
219
+ require(type(sent) is dict,'Invalid prompt timing observations')
220
+ for expected in unit['steps']:
221
+ s=steps.get(expected['id']);when=sent.get(expected['id']);number(when,'prompt injection time',nullable=True)
222
+ if s is None or not _successful(s,c) or when is None:valid=False
223
+ if previous is not None:
224
+ gap=when-previous if when is not None else None;idle.append(gap)
225
+ if gap is None or not 0<=gap<=plan['design']['max_idle_ms']:valid=False
226
+ if s:
227
+ a,b=s['tts_start_ms'],s['first_audio_ms']
228
+ clocks=(when,a,b,s['playout_start_ms'],s['playout_end_ms'])
229
+ if any(v is None for v in clocks) or list(clocks)!=sorted(clocks):valid=False
230
+ values.append(b-a if a is not None and b is not None and b>=a and not s['cache_hit'] and _successful(s,c) else None)
231
+ previous=s['playout_end_ms']
232
+ if previous is None:valid=False
233
+ else:values.append(None);previous=None
234
+ result.append(dict(unit_id=unit['id'],expected_turns=len(unit['steps']),captured_turns=len(steps),
235
+ timing_control_valid=valid and len(steps)==len(unit['steps']),idle_ms=idle,fresh_ttfa_ms_in_turn_order=values,
236
+ fitted_degradation_slope=None,confidence_interval=None,scope='fixture' if plan['mode']=='fixture' else 'timed rapid-turn campaign'))
237
+ return result
238
+
239
+
240
+ def evaluate_machine_intelligibility(plan,captures,references):
241
+ bound=_captures(plan,captures);result=[]
242
+ for c in plan['candidates']:
243
+ units=[u for u in plan['units'] if u['candidate_id']==c['id']];scores=[]
244
+ for unit in units:
245
+ row=bound.get(unit['id']);audio=unit['audio_case'];valid=bool(row and row['status']=='completed' and row.get('audio_sha256')==audio['audio']['sha256']
246
+ and row.get('asr_sha256')==digest(encoded(plan['asr'])) and isinstance(row.get('hypothesis'),str))
247
+ score=score_transcript(references[audio['id']],row['hypothesis']) if valid else None
248
+ if score is not None:scores.append((audio['language'],score))
249
+ languages=sorted({u['audio_case']['language'] for u in units});by_language={}
250
+ for language in languages:
251
+ group=[s for lg,s in scores if lg==language];expected=sum(u['audio_case']['language']==language for u in units)
252
+ words=sum(s['reference_words'] for s in group);chars=sum(s['reference_characters'] for s in group)
253
+ by_language[language]=dict(expected=expected,scored=len(group),missing_or_invalid=expected-len(group),
254
+ wer=sum(s['word_errors'] for s in group)/words if words and len(group)==expected and language!='ja' else None,
255
+ cer=sum(s['character_errors'] for s in group)/chars if chars and len(group)==expected else None,
256
+ silence_inserted_words=sum(s['silence_inserted_words'] for s in group),
257
+ structured_tokens={k:dict(matched=sum(s['structured_tokens'][k]['matched'] for s in group),reference_tokens=sum(s['structured_tokens'][k]['reference_tokens'] for s in group)) for k in (group[0]['structured_tokens'] if group else {})})
258
+ result.append(dict(candidate_id=c['id'],by_language=by_language,normalization='kido.nfkc-lower-punctuation.v1',
259
+ structured_token_scorer='kido.structured-substring-presence.v1',human_quality=None,confidence_interval=None,
260
+ asr_sha256=digest(encoded(plan['asr'])),scope='fixture' if plan['mode']=='fixture' else 'fixed-ASR machine intelligibility proxy'))
261
+ return result
262
+
263
+
264
+ def _grade(plan,captures,references):
265
+ funcs={'session-reliability':evaluate_session_reliability,'controlled-barge-in':evaluate_controlled_barge_in,'rapid-turn-stress':evaluate_rapid_turn_stress}
266
+ return evaluate_machine_intelligibility(plan,captures,references) if plan['protocol']=='machine-intelligibility' else funcs[plan['protocol']](plan,captures)
267
+
268
+
269
+ def grade_speech_campaign(path,*,expected_plan_sha256,captures,output_dir):
270
+ root,plan,refs=_load_plan(path,expected_plan_sha256);result=_grade(plan,captures,refs)
271
+ invocation='speech-campaign-invocation-'+uuid.uuid4().hex
272
+ report=dict(schema_version=SCHEMA+'.report.v1',suite_id=SUITE_ID,invocation_id=invocation,protocol=plan['protocol'],
273
+ mode=plan['mode'],plan_sha256=expected_plan_sha256,results=result,real_provider_calls_made_by_grader=0,
274
+ captures_supplied=len(captures),captures_planned=plan['planned_units'],subjective_perceived_latency=None)
275
+ dest=_new_directory(output_dir);files=_write_files(dest,{'plan.json':encoded(plan),'references.json':encoded(refs),'captures.json':encoded(captures),'report.json':encoded(report)})
276
+ receipt=dict(schema_version=SCHEMA+'.receipt.v1',suite_id=SUITE_ID,invocation_id=invocation,files=files)
277
+ raw=encoded(receipt);(dest/'receipt.json').write_bytes(raw)
278
+ return dict(path=str(dest),suite_id=SUITE_ID,invocation_id=invocation,receipt_sha256=digest(raw),mode=plan['mode'])
279
+
280
+
281
+ def load_speech_campaign_report(path,*,expected_receipt_sha256):
282
+ root=Path(path).absolute();raw=_read(root,'receipt.json');require(digest(raw)==_sha(expected_receipt_sha256),'Campaign receipt changed')
283
+ receipt=json_value(raw);require(receipt.get('schema_version')==SCHEMA+'.receipt.v1' and receipt.get('suite_id')==SUITE_ID and
284
+ set(receipt.get('files',{}))=={'plan.json','references.json','captures.json','report.json'},'Invalid campaign report census')
285
+ values={}
286
+ for p,h in receipt['files'].items():
287
+ b=_read(root,p);require(h==dict(sha256=digest(b),size_bytes=len(b)),'Campaign artifact changed');values[p]=json_value(b)
288
+ plan=values['plan.json'];report=values['report.json'];refs=values['references.json']
289
+ require(plan.get('schema_version')==SCHEMA+'.plan.v1' and plan.get('suite_id')==SUITE_ID and
290
+ report.get('schema_version')==SCHEMA+'.report.v1' and report.get('suite_id')==SUITE_ID and
291
+ report.get('invocation_id')==receipt.get('invocation_id') and report.get('plan_sha256')==digest(encoded(plan)) and
292
+ plan['files']['references.json']==dict(sha256=digest(encoded(refs)),size_bytes=len(encoded(refs))) and
293
+ report.get('mode')==plan['mode'] and report.get('protocol')==plan['protocol'] and
294
+ report.get('real_provider_calls_made_by_grader')==0 and report.get('subjective_perceived_latency') is None and
295
+ report.get('captures_supplied')==len(values['captures.json']) and report.get('captures_planned')==plan['planned_units'] and
296
+ report.get('results')==_grade(plan,values['captures.json'],refs),'Campaign report calculations or scope changed')
297
+ return report
298
+
299
+
300
+ def run_fixture_speech_campaign(path,*,expected_plan_sha256,output_dir):
301
+ _,plan,refs=_load_plan(path,expected_plan_sha256);require(plan['mode']=='fixture','Fixtures cannot populate a real capture campaign')
302
+ candidates={c['id']:c for c in plan['candidates']};rows=[]
303
+ for unit in plan['units']:
304
+ c=candidates[unit['candidate_id']];steps=[];sent={};clock=0
305
+ for i,expected in enumerate(unit['steps']):
306
+ sent[expected['id']]=clock;start=clock+10
307
+ end=start+100 if plan['protocol']!='controlled-barge-in' or i else start+plan['design']['interrupt_after_ms']+10
308
+ steps.append(dict(step_id=expected['id'],case_id=expected['case_id'],provider=c['provider'],model=c['model'],audio_bytes=32,
309
+ complete=True,cancelled=False,error_present=False,cache_hit=False,interrupted=plan['protocol']=='controlled-barge-in' and i==0,
310
+ tts_start_ms=clock,first_audio_ms=start,playout_start_ms=start,playout_end_ms=end))
311
+ clock=end+min(10,plan['design']['max_idle_ms'])
312
+ row=dict(unit_id=unit['id'],candidate_id=c['id'],candidate_sha256=digest(encoded(c)),source_kind='fixture',
313
+ status='completed',session_id='fixture:'+unit['id'],steps=steps,prompt_times_ms=sent)
314
+ if plan['protocol']=='controlled-barge-in':row.update(trigger_ms=steps[0]['playout_start_ms']+plan['design']['interrupt_after_ms'],injected_audio_sha256=plan['microphone']['sha256'])
315
+ if plan['protocol']=='machine-intelligibility':
316
+ a=unit['audio_case'];row.update(audio_sha256=a['audio']['sha256'],asr_sha256=digest(encoded(plan['asr'])),hypothesis='fixture transcript')
317
+ rows.append(row)
318
+ return grade_speech_campaign(path,expected_plan_sha256=expected_plan_sha256,captures=rows,output_dir=output_dir)
319
+
320
+
321
+ def run_speech_campaign(path,*,expected_plan_sha256,collector,output_dir,allow_external_calls=False):
322
+ """Run a deliberately supplied, source-hashed trusted-local collector.
323
+
324
+ The collector owns provider/session integration, dependency review and
325
+ request deadlines. This is not an OS sandbox or a provider implementation.
326
+ No collector is imported or discovered by the SDK. Input views omit gold.
327
+ """
328
+ import inspect
329
+ root,plan,_=_load_plan(path,expected_plan_sha256)
330
+ require(plan['mode']=='capture' and type(allow_external_calls) is bool and allow_external_calls,
331
+ 'Explicit external campaign launch is required')
332
+ require(callable(collector) and plan.get('collector_source_sha256') is not None,'Freeze the reviewed collector source hash')
333
+ file=inspect.getsourcefile(collector);require(file is not None,'Collector source must be inspectable')
334
+ file=Path(file).absolute();require(digest(_read(file.parent,file.name))==plan['collector_source_sha256'],'Collector source differs from reviewed plan')
335
+ requests=speech_campaign_requests(root,expected_plan_sha256=expected_plan_sha256)
336
+ dest=_new_directory(output_dir);candidates={c['id']:c for c in plan['candidates']};captures=[]
337
+ for i,unit in enumerate(plan['units']):
338
+ assets=dest/'collector-inputs'/('unit-'+str(i+1)+'-assets');assets.mkdir(parents=True)
339
+ names=[unit['audio_case']['audio_file']] if 'audio_case' in unit else ['audio/microphone.wav'] if plan['microphone'] else []
340
+ files={p:requests['input_files'][p] for p in names}
341
+ _write_files(assets,{p:_read(root,p) for p in files})
342
+ request=dict(protocol=plan['protocol'],plan_sha256=expected_plan_sha256,unit=unit,
343
+ candidate=candidates[unit['candidate_id']],design=plan['design'],asr=plan['asr'],
344
+ microphone=plan['microphone'],input_files=files,input_root=str(assets))
345
+ _write_files(dest,{'collector-inputs/unit-'+str(i+1)+'.json':encoded(request)})
346
+ try:row=collector(request)
347
+ except Exception:
348
+ row=dict(unit_id=unit['id'],candidate_id=unit['candidate_id'],candidate_sha256=digest(encoded(candidates[unit['candidate_id']])),
349
+ source_kind='operator-capture',status='collector_error',session_id=None,steps=[])
350
+ _captures(plan,[row]);captures.append(row)
351
+ _write_files(dest,{'collector-captures/unit-'+str(i+1)+'.json':encoded(row)})
352
+ return grade_speech_campaign(root,expected_plan_sha256=expected_plan_sha256,captures=captures,output_dir=dest/'evaluation')
353
+
354
+
355
+ def main(argv=None):
356
+ p=argparse.ArgumentParser(description=__doc__);commands=p.add_subparsers(dest='command',required=True)
357
+ commands.add_parser('describe')
358
+ c=commands.add_parser('freeze');c.add_argument('--bundle',required=True);c.add_argument('--expected-manifest-sha256',required=True);c.add_argument('--design-json',required=True);c.add_argument('--output',required=True)
359
+ for name in ('requests','fixture','grade'):
360
+ c=commands.add_parser(name);c.add_argument('--plan',required=True);c.add_argument('--expected-plan-sha256',required=True)
361
+ if name!='requests':c.add_argument('--output',required=True)
362
+ if name=='grade':c.add_argument('--captures-json',required=True)
363
+ args=p.parse_args(argv)
364
+ try:
365
+ if args.command=='describe':result=dict(suite_id=SUITE_ID,protocols=PROTOCOLS,provider_calls=False,live_collector='separately reviewed external capture')
366
+ elif args.command=='freeze':
367
+ from .speech_complete import load_kido_complete
368
+ b=load_kido_complete(args.bundle,expected_manifest_sha256=args.expected_manifest_sha256)
369
+ result=freeze_speech_campaign(b,output_dir=args.output,**json_value(_read(Path(args.design_json).absolute().parent,Path(args.design_json).name)))
370
+ elif args.command=='requests':result=speech_campaign_requests(args.plan,expected_plan_sha256=args.expected_plan_sha256)
371
+ elif args.command=='fixture':result=run_fixture_speech_campaign(args.plan,expected_plan_sha256=args.expected_plan_sha256,output_dir=args.output)
372
+ else:result=grade_speech_campaign(args.plan,expected_plan_sha256=args.expected_plan_sha256,output_dir=args.output,
373
+ captures=json_value(_read(Path(args.captures_json).absolute().parent,Path(args.captures_json).name)))
374
+ except (ValueError,OSError,KeyError,TypeError):p.exit(2,'Speech campaign failed; check frozen design, capture identities and output.\n')
375
+ print(json.dumps(result,indent=2,ensure_ascii=True))
376
+
377
+
378
+ if __name__=='__main__':main()
@@ -0,0 +1,236 @@
1
+ """Exhaustive Kido benchmark catalog and retained-evidence evaluation.
2
+
3
+ Post-0.5.0 source only. Separate invocation; no provider or imported-code runs.
4
+ """
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ import argparse
8
+ import uuid
9
+
10
+ from ._speech_kido import digest,encoded,json_value,require,csv_rows
11
+ from . import _speech_complete_source as source
12
+ from ._speech_complete_scoring import (evaluate_google_direct_api,evaluate_pronunciation_proxy,
13
+ evaluate_isolated_diagnostics,evaluate_listening_diagnostics,evaluate_pt_br_categories,evaluate_official_turns,evaluate_stt_diagnostics)
14
+ from ._speech_benchmark_scoring import grade_records
15
+ from .speech_evidence import _new_directory,_read,_write_files,_sha,_grade_rows
16
+
17
+ SUITE_ID='speech.kido-complete.v1'
18
+ SCHEMA='sphynx.speech.kido-complete'
19
+ MODULES=('_speech_complete_source.py','_speech_complete_scoring.py','speech_complete.py',
20
+ '_speech_kido.py','_speech_scoring.py','speech_evidence.py','_speech_benchmark_sources.py','_speech_benchmark_scoring.py','speech_contracts.py')
21
+ LIMITS=dict(historical_only=True,real_provider_calls=0,hosted_admitted=False,
22
+ population_inference=False,new_waveform_quality=False,subjective_perceived_latency=False)
23
+ SOURCE_FAMILIES={
24
+ 'results/human-eval/':'blind_quality',
25
+ 'results/isolated/official-v1/':'isolated_synthesis',
26
+ 'results/isolated/multilingual-extension-v1/':'multilingual_isolated',
27
+ 'results/live-agent/official-v1/':'live_agent_synthesis',
28
+ 'results/live-agent/reliability-v1/':'session_reliability',
29
+ 'results/live-agent/pronunciation-v1/':'pronunciation',
30
+ 'results/live-agent/language-switching-v1/':'language_switching',
31
+ 'results/exploratory/cartesia-ink-2-stt/':'stt_recognition',
32
+ 'results/exploratory/google-gemini-tts/':'google_direct_api',
33
+ 'results/exploratory/pronunciation-v1-cartesia-only/':'pronunciation_proxy',
34
+ }
35
+ # Every methodology document has an explicit home. A plan is not an executed study.
36
+ CATALOG=(
37
+ ('isolated_synthesis','saved-evidence','isolated-benchmark.md'),
38
+ ('blind_quality','saved-evidence','human-eval.md'),
39
+ ('pt_br_listening','saved-evidence','pt-br-listening-pack.md'),
40
+ ('live_agent_synthesis','saved-evidence','live-agent.md'),
41
+ ('stt_recognition','saved-evidence','cartesia-ink2-exploratory.md'),
42
+ ('stt_diagnostics','saved-evidence','cartesia-ink2-exploratory.md'),
43
+ ('stt_turn_lifecycle','saved-evidence','live-telemetry-schema.md'),
44
+ ('pronunciation','saved-evidence','pronunciation.md'),
45
+ ('language_switching','saved-evidence','language-switching.md'),
46
+ ('voice_continuity','saved-evidence','language-switching.md'),
47
+ ('code_switching','saved-evidence','multilingual.md'),
48
+ ('playout_interruptions','saved-evidence','barge-in-and-rapid-turns.md'),
49
+ ('google_direct_api','saved-evidence','google-direct-api-extension.md'),
50
+ ('pronunciation_proxy','saved-evidence','pronunciation.md'),
51
+ ('isolated_diagnostics','saved-evidence','percentile-conventions.md'),
52
+ ('listening_diagnostics','saved-evidence','corpus.md'),
53
+ ('pt_br_categories','saved-evidence','pt-br-listening-pack.md'),
54
+ ('official_turn_diagnostics','saved-evidence','barge-in-and-rapid-turns.md'),
55
+ ('multilingual_isolated','campaign','multilingual.md'),
56
+ ('session_reliability','campaign','reliability.md'),
57
+ ('machine_intelligibility','campaign','intelligibility.md'),
58
+ ('controlled_barge_in','campaign','barge-in-and-rapid-turns.md'),
59
+ ('rapid_turn_stress','campaign','barge-in-and-rapid-turns.md'),
60
+ ('third_party_stt_research','reference-only','cartesia-ink2-exploratory.md'),
61
+ ('pronunciation_customization','capability-only','pronunciation.md'),
62
+ ('protocol_overview','reference-only','protocol.md'),
63
+ )
64
+
65
+
66
+ def list_kido_evaluations():
67
+ return dict(suite_id=SUITE_ID,source_commit=source.SOURCE_COMMIT,source_lock_sha256=source.SOURCE_PIN,
68
+ invocation_schema=SCHEMA+'.invocation.v1',catalog=[dict(id=k,surface=s,methodology='methodology/'+p) for k,s,p in CATALOG],
69
+ source_families=SOURCE_FAMILIES,
70
+ execution='retained evidence only; campaign execution has a separate invocation',
71
+ implementation_sha256={name:digest(Path(__file__).with_name(name).read_bytes()) for name in MODULES},limits=LIMITS)
72
+
73
+
74
+ def export_kido_complete_source(repository,output_dir):
75
+ lock,data=source.git_bytes(repository);dest=_new_directory(output_dir);_write_files(dest,data)
76
+ return dict(path=str(dest),source_commit=source.SOURCE_COMMIT,source_lock_sha256=digest(encoded(lock)),files=len(data),source_executed=False)
77
+
78
+
79
+ def import_kido_complete(source_path,output_dir):
80
+ lock,data=source.source_bytes(source_path);evidence=source.normalize(data)
81
+ evidence['isolated_trials']=csv_rows(data['results/isolated/official-v1/combined/trials.csv'])
82
+ methodologies={p for p in data if p.startswith('methodology/') and p.endswith('.md')}
83
+ require(methodologies<={'methodology/'+p for _,_,p in CATALOG},'Uncatalogued methodology document')
84
+ require(all(any(p.startswith(prefix) for prefix in SOURCE_FAMILIES) or p in
85
+ ('results/live-agent/README.md','results/README.md') for p in data if p.startswith('results/')),
86
+ 'Uncatalogued result family')
87
+ dest=_new_directory(output_dir);files=_write_files(dest,{'source/'+p:b for p,b in data.items()}|{'evidence.json':encoded(evidence)})
88
+ manifest=dict(schema_version=SCHEMA+'.bundle.v1',suite_id=SUITE_ID,source_lock_sha256=digest(encoded(lock)),
89
+ source_commit=source.SOURCE_COMMIT,files=files,provider_called=False,candidate_executed=False,hosted_admitted=False)
90
+ raw=encoded(manifest);(dest/'manifest.json').write_bytes(raw)
91
+ return dict(path=str(dest),manifest_sha256=digest(raw),suite_id=SUITE_ID,source_files=len(data))
92
+
93
+
94
+ @dataclass(frozen=True)
95
+ class KidoComplete:
96
+ path:Path
97
+ manifest_sha256:str
98
+
99
+
100
+ def _verified(bundle):
101
+ raw=_read(bundle.path,'manifest.json');require(digest(raw)==bundle.manifest_sha256,'Complete manifest changed')
102
+ manifest=json_value(raw)
103
+ require(manifest.get('schema_version')==SCHEMA+'.bundle.v1' and manifest.get('suite_id')==SUITE_ID and
104
+ manifest.get('source_lock_sha256')==source.SOURCE_PIN and manifest.get('source_commit')==source.SOURCE_COMMIT,
105
+ 'Not the pinned complete benchmark source')
106
+ _,data=source.source_bytes(bundle.path/'source')
107
+ require(set(manifest.get('files',{}))=={'source/'+p for p in data}|{'evidence.json'},'Complete file census changed')
108
+ for p,h in manifest['files'].items():
109
+ b=_read(bundle.path,p);require(h==dict(sha256=digest(b),size_bytes=len(b)),'Complete artifact changed')
110
+ evidence=source.normalize(data);evidence['isolated_trials']=csv_rows(data['results/isolated/official-v1/combined/trials.csv'])
111
+ require(_read(bundle.path,'evidence.json')==encoded(evidence),'Complete evidence differs from source')
112
+ require(all(manifest.get(k) is False for k in ('provider_called','candidate_executed','hosted_admitted')),'Complete execution boundary changed')
113
+ return evidence
114
+
115
+
116
+ def load_kido_complete(path,*,expected_manifest_sha256):
117
+ bundle=KidoComplete(Path(path).absolute(),_sha(expected_manifest_sha256));_verified(bundle);return bundle
118
+
119
+
120
+ def _results(evidence):
121
+ saved=evidence['saved'];summary,cases=_grade_rows(saved['observations'],saved['references'])
122
+ pt=[r for r in evidence['benchmarks']['observations'] if r['track']=='pt_br_listening']
123
+ return dict(saved_evidence=dict(summaries=summary,per_case=cases,audit=saved['audit']),
124
+ benchmarks=grade_records(evidence['benchmarks']),google_direct_api=evaluate_google_direct_api(evidence['google']),
125
+ pronunciation_proxy=evaluate_pronunciation_proxy(evidence['proxy']),
126
+ isolated_diagnostics=evaluate_isolated_diagnostics(evidence['isolated_trials']),
127
+ listening_diagnostics=evaluate_listening_diagnostics(saved['observations'],saved['references'],evidence['isolated_trials']),
128
+ pt_br_categories=evaluate_pt_br_categories(pt),official_turn_diagnostics=evaluate_official_turns(evidence['official_sessions']),
129
+ stt_diagnostics=evaluate_stt_diagnostics(evidence['stt_analyses']),
130
+ unexecuted_campaigns=[k for k,s,_ in CATALOG if s=='campaign'],
131
+ source_documents=evidence['source_documents'],
132
+ nonexperimental_references=[k for k,s,_ in CATALOG if s in ('reference-only','capability-only')])
133
+
134
+
135
+ def grade_kido_complete(bundle,output_dir):
136
+ evidence=_verified(bundle);definition=list_kido_evaluations()
137
+ invocation=dict(schema_version=SCHEMA+'.invocation.v1',id='speech-complete-invocation-'+uuid.uuid4().hex,
138
+ suite_id=SUITE_ID,bundle_manifest_sha256=bundle.manifest_sha256,definition=definition,provider_called=False,candidate_executed=False)
139
+ report=dict(schema_version=SCHEMA+'.report.v1',suite_id=SUITE_ID,invocation_id=invocation['id'],
140
+ invocation_sha256=digest(encoded(invocation)),results=_results(evidence),limits=LIMITS)
141
+ dest=_new_directory(output_dir);files=_write_files(dest,{'evidence.json':encoded(evidence),'invocation.json':encoded(invocation),'report.json':encoded(report)})
142
+ receipt=dict(schema_version=SCHEMA+'.receipt.v1',suite_id=SUITE_ID,invocation_id=invocation['id'],files=files)
143
+ raw=encoded(receipt);(dest/'receipt.json').write_bytes(raw)
144
+ return dict(path=str(dest),receipt_sha256=digest(raw),invocation_id=invocation['id'],suite_id=SUITE_ID)
145
+
146
+
147
+ def load_kido_complete_report(path,*,expected_receipt_sha256):
148
+ root=Path(path).absolute();raw=_read(root,'receipt.json')
149
+ require(digest(raw)==_sha(expected_receipt_sha256),'Complete report receipt changed');receipt=json_value(raw)
150
+ require(receipt.get('schema_version')==SCHEMA+'.receipt.v1' and receipt.get('suite_id')==SUITE_ID and
151
+ set(receipt.get('files',{}))=={'evidence.json','invocation.json','report.json'},'Not a complete benchmark report')
152
+ payload={}
153
+ for p,h in receipt['files'].items():
154
+ b=_read(root,p);require(h==dict(sha256=digest(b),size_bytes=len(b)),'Complete report artifact changed');payload[p]=json_value(b)
155
+ invocation=payload['invocation.json'];report=payload['report.json']
156
+ require(invocation.get('schema_version')==SCHEMA+'.invocation.v1' and report.get('schema_version')==SCHEMA+'.report.v1' and
157
+ invocation.get('suite_id')==report.get('suite_id')==SUITE_ID and invocation.get('id')==report.get('invocation_id')==receipt.get('invocation_id') and
158
+ report.get('invocation_sha256')==digest(encoded(invocation)) and invocation.get('provider_called') is False and invocation.get('candidate_executed') is False,
159
+ 'Complete invocation identity or execution boundary changed')
160
+ definition=invocation.get('definition',{})
161
+ require(definition.get('source_lock_sha256')==source.SOURCE_PIN and definition.get('source_commit')==source.SOURCE_COMMIT
162
+ and definition.get('catalog')==list_kido_evaluations()['catalog'] and definition.get('source_families')==SOURCE_FAMILIES
163
+ and definition.get('limits')==LIMITS,'Complete source or catalog changed')
164
+ require(set(definition.get('implementation_sha256',{}))==set(MODULES),'Missing complete implementation identities')
165
+ for v in definition['implementation_sha256'].values():_sha(v)
166
+ require(report.get('limits')==LIMITS and report.get('results')==_results(payload['evidence.json']),'Complete calculations or limits changed')
167
+ return report
168
+
169
+
170
+ def complete_tts_cases(bundle,*,benchmark,case_ids=None,profile=None,language_hint=None):
171
+ """Input-only cases for existing reviewed candidate execution or campaign plans."""
172
+ evidence=_verified(bundle);cases=[]
173
+ if benchmark=='isolated-official':
174
+ unique={}
175
+ for r in evidence['saved']['observations']:
176
+ if r['track']!='tts_isolated':continue
177
+ value=dict(id='isolated:'+r['case_id'],text=r['input_text'],language=r['language'])
178
+ require(value['id'] not in unique or unique[value['id']]==value,'Conflicting original corpus identity')
179
+ unique[value['id']]=value
180
+ cases=sorted(unique.values(),key=lambda r:r['id'])
181
+ elif benchmark=='official-live-script':
182
+ cases=[{k:r[k] for k in ('id','text','language')} for r in evidence['official_script']]
183
+ elif benchmark=='multilingual-isolated':
184
+ rows=evidence['multilingual']['lines']
185
+ support={'elevenlabs':{'en','pt-BR','es','fr','ja'},'cartesia':{'en','pt-BR','es','fr','ja'},
186
+ 'minimax':{'en','pt-BR','es','fr','ja'},'rime':{'en','es','fr'},'deepgram':{'en'}}
187
+ require(profile is None or profile in support,'No frozen language support matrix for this profile')
188
+ cases=[dict(id='multilingual:'+r['id'],text=r['text'],language=r['language']) for r in rows if profile is None or r['language'] in support[profile]]
189
+ elif benchmark=='google-direct-api':
190
+ cases=[dict(id='google-probe:'+k,text=v,language='pt-BR' if k in ('short_pt','long_pt','numbers_pt','mixed_pt_en') else 'en') for k,v in evidence['google_texts'].items()]
191
+ elif benchmark=='pronunciation-proxy':
192
+ cases=[dict(id='pronunciation-proxy:'+r['id'],text=r['reference'],language='en') for r in evidence['proxy']]
193
+ else:
194
+ require(benchmark in ('pt-br-listening','pronunciation','code-switching'),'Unknown complete TTS corpus')
195
+ cases=[dict(r,id=r['id'].replace('/',':',1)) for r in evidence['benchmarks']['cases'] if r['id'].startswith(benchmark+'/')]
196
+ require(profile is None or benchmark=='multilingual-isolated','Profile filtering applies only to frozen multilingual support')
197
+ if benchmark=='code-switching':
198
+ import re
199
+ require(isinstance(language_hint,str) and re.fullmatch('[a-z]{2}',language_hint),'Select an explicit mixed-language hint')
200
+ for row in cases:row['language']=language_hint
201
+ else:require(language_hint is None,'Native corpus language cannot be overridden')
202
+ from .speech_contracts import identifier
203
+ for row in cases:identifier(row['id'])
204
+ if case_ids is not None:
205
+ require(type(case_ids) is list and len(set(case_ids))==len(case_ids) and set(case_ids)<={r['id'] for r in cases},'Invalid complete case selection')
206
+ cases=[r for r in cases if r['id'] in case_ids]
207
+ require(cases,'Empty complete case selection')
208
+ return dict(cases=cases,cohort_identity=dict(kind=SUITE_ID+':'+benchmark,sha256=digest(encoded(dict(bundle=bundle.manifest_sha256,cases=cases)))),
209
+ suite_id=SUITE_ID,new_waveform_ratings_available=False,adaptation='TTS contract PCM16 16k; not historical transport parity')
210
+
211
+
212
+ def main(argv=None):
213
+ import json
214
+ parser=argparse.ArgumentParser(description=__doc__);commands=parser.add_subparsers(dest='command',required=True)
215
+ commands.add_parser('describe')
216
+ c=commands.add_parser('export-source');c.add_argument('--repository',required=True);c.add_argument('--output',required=True)
217
+ c=commands.add_parser('import-kido');c.add_argument('--source',required=True);c.add_argument('--output',required=True)
218
+ for name in ('grade','tts-cases'):
219
+ c=commands.add_parser(name);c.add_argument('--bundle',required=True);c.add_argument('--expected-manifest-sha256',required=True)
220
+ if name=='grade':c.add_argument('--output',required=True)
221
+ else:
222
+ c.add_argument('--benchmark',required=True);c.add_argument('--case-id',action='append');c.add_argument('--profile');c.add_argument('--language-hint')
223
+ args=parser.parse_args(argv)
224
+ try:
225
+ if args.command=='describe':result=list_kido_evaluations()
226
+ elif args.command=='export-source':result=export_kido_complete_source(args.repository,args.output)
227
+ elif args.command=='import-kido':result=import_kido_complete(args.source,args.output)
228
+ else:
229
+ bundle=load_kido_complete(args.bundle,expected_manifest_sha256=args.expected_manifest_sha256)
230
+ result=grade_kido_complete(bundle,args.output) if args.command=='grade' else complete_tts_cases(bundle,
231
+ benchmark=args.benchmark,case_ids=args.case_id,profile=args.profile,language_hint=args.language_hint)
232
+ except (ValueError,OSError,KeyError,TypeError):parser.exit(2,'Complete speech operation failed; check source pin, selection and output.\n')
233
+ print(json.dumps(result,indent=2,ensure_ascii=True))
234
+
235
+
236
+ if __name__=='__main__':main()