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,115 @@
1
+ """Blind packs bound to new TTS waveforms; explicit human reviews only."""
2
+ from collections import defaultdict
3
+ import csv
4
+ import io
5
+ from pathlib import Path
6
+ import random
7
+ import statistics
8
+ import uuid
9
+
10
+ from ._speech_kido import digest,encoded,json_value,csv_rows,require
11
+ from ._speech_benchmark_sources import DIMENSIONS
12
+ from .speech_evidence import _new_directory,_read,_write_files,_sha
13
+ from .speech_tts import load_tts_run,_load_plan
14
+
15
+ SUITE_ID='speech.listening.v1'
16
+
17
+
18
+ def prepare_speech_listening_pack(run_path,*,expected_manifest_sha256,output_dir,protocol='blind-quality',seed=20260921,repetition=1,case_ids=None):
19
+ require(protocol in ('blind-quality','pt-br-listening') and type(seed) is int and type(repetition) is int and repetition>=1,'Invalid listening design')
20
+ root=Path(run_path).absolute();load_tts_run(root,expected_manifest_sha256=expected_manifest_sha256)
21
+ manifest=json_value(_read(root,'manifest.json'));_,plan=_load_plan(root/'frozen-plan',manifest['plan_sha256'])
22
+ cases={r['id']:r for r in plan['cases']}
23
+ require(case_ids is None or type(case_ids) is list and len(set(case_ids))==len(case_ids) and set(case_ids)<=set(cases),'Invalid listening case selection')
24
+ selected=set(cases) if case_ids is None else set(case_ids);require(selected,'Empty listening pack')
25
+ if protocol=='pt-br-listening':require(all(cases[i]['language']=='pt-BR' for i in selected),'PT-BR pack contains another language')
26
+ captures=[(p,json_value(_read(root,p))) for p in manifest['files'] if p.startswith('captures/') and p.endswith('/capture.json')]
27
+ captures=[(p,r) for p,r in captures if r['repetition']==repetition and r['case_id'] in selected]
28
+ require(len(captures)==2*len(selected) and len({(r['case_id'],r['method_id']) for _,r in captures})==len(captures)
29
+ and all(r['success'] for _,r in captures),'Listening pack requires both successful arms for every selected text')
30
+ random.Random(seed).shuffle(captures);pack_id='listening-pack-'+uuid.uuid4().hex;mapping=[];payload={};rows=[]
31
+ dimensions=list(DIMENSIONS) if protocol=='pt-br-listening' else ['overall_quality_1_5']
32
+ for index,(path,capture) in enumerate(captures):
33
+ sample='sample-'+str(index+1).zfill(3);audio=_read(root,path.removesuffix('capture.json')+'audio.wav');sha=digest(audio)
34
+ payload['blind/audio/'+sample+'.wav']=audio
35
+ case=cases[capture['case_id']];method=plan['methods'][capture['method_id']]
36
+ mapping.append(dict(sample_id=sample,case_id=case['id'],method_id=capture['method_id'],provider=method['provider'],model=method['model'],
37
+ audio_sha256=sha,request_id=capture['request_id'],text=case['text'],language=case['language']))
38
+ rows.append(dict(pack_id=pack_id,sample_id=sample,audio_sha256=sha,text=case['text'],language=case['language'],**{d:'' for d in dimensions},notes=''))
39
+ out=io.StringIO(newline='');writer=csv.DictWriter(out,fieldnames=list(rows[0]));writer.writeheader();writer.writerows(rows)
40
+ payload['blind/scores.csv']=out.getvalue().encode('utf-8');payload['mapping.json']=encoded(mapping)
41
+ dest=_new_directory(output_dir);files=_write_files(dest,payload)
42
+ pack=dict(schema_version='sphynx.speech.listening.pack.v1',suite_id=SUITE_ID,pack_id=pack_id,protocol=protocol,dimensions=dimensions,
43
+ source_run_sha256=expected_manifest_sha256,source_mode=plan['mode'],seed=seed,repetition=repetition,files=files,
44
+ samples=len(rows),quality_ratings_available=False,historical_ratings_reused=False)
45
+ raw=encoded(pack);(dest/'manifest.json').write_bytes(raw)
46
+ return dict(path=str(dest),pack_sha256=digest(raw),suite_id=SUITE_ID,pack_id=pack_id,samples=len(rows),distribute='blind directory only; keep mapping private')
47
+
48
+
49
+ def _pack(path,pin):
50
+ root=Path(path).absolute();raw=_read(root,'manifest.json');require(digest(raw)==_sha(pin),'Listening pack changed');pack=json_value(raw)
51
+ require(pack.get('schema_version')=='sphynx.speech.listening.pack.v1' and pack.get('suite_id')==SUITE_ID and
52
+ pack.get('historical_ratings_reused') is False and pack.get('quality_ratings_available') is False,'Invalid new-waveform listening pack')
53
+ for p,h in pack['files'].items():
54
+ b=_read(root,p);require(h==dict(sha256=digest(b),size_bytes=len(b)),'Listening audio, mapping or template changed')
55
+ mapping=json_value(_read(root,'mapping.json'))
56
+ require(len(mapping)==pack['samples'] and len({r['sample_id'] for r in mapping})==len(mapping),'Listening mapping census differs')
57
+ for row in mapping:require(digest(_read(root,'blind/audio/'+row['sample_id']+'.wav'))==row['audio_sha256'],'Listening waveform binding differs')
58
+ return root,pack,mapping
59
+
60
+
61
+ def _grade(pack,mapping,reviews):
62
+ require(pack.get('schema_version')=='sphynx.speech.listening.pack.v1' and pack.get('suite_id')==SUITE_ID and
63
+ pack.get('protocol') in ('blind-quality','pt-br-listening') and pack.get('source_mode') in ('fixture','provider') and
64
+ pack.get('historical_ratings_reused') is False and pack.get('quality_ratings_available') is False and
65
+ pack.get('dimensions')==(list(DIMENSIONS) if pack['protocol']=='pt-br-listening' else ['overall_quality_1_5']),
66
+ 'Listening definition or evidence boundary differs')
67
+ require(type(reviews) is list and reviews and len({r['rater_id'] for r in reviews})==len(reviews),'Declare unique review identities')
68
+ by_id={r['sample_id']:r for r in mapping};groups=defaultdict(list)
69
+ for review in reviews:
70
+ require(isinstance(review['rater_id'],str) and review['rater_id'] and type(review['rows']) is list,'Invalid rater review')
71
+ rows=review['rows'];require(len({r['sample_id'] for r in rows})==len(rows) and {r['sample_id'] for r in rows}<=set(by_id),'Unknown or duplicated blind review sample')
72
+ supplied={r['sample_id']:r for r in rows}
73
+ for sample,identity in by_id.items():
74
+ row=supplied.get(sample)
75
+ if row:require(row.get('pack_id')==pack['pack_id'] and row.get('audio_sha256')==identity['audio_sha256'] and
76
+ row.get('text')==identity['text'] and row.get('language')==identity['language'],'Review belongs to another pack, waveform or text')
77
+ for dimension in pack['dimensions']:
78
+ value=row.get(dimension,'') if row else ''
79
+ require(value in ('','1','2','3','4','5'),'Human rating must be blank or an integer from one to five')
80
+ groups[(identity['method_id'],dimension)].append(int(value) if value else None)
81
+ results=[]
82
+ for (method,dimension),values in sorted(groups.items()):
83
+ present=[v for v in values if v is not None]
84
+ results.append(dict(method_id=method,dimension=dimension,expected_reviews=len(values),rated=len(present),pending=len(values)-len(present),
85
+ mean=statistics.mean(present) if len(present)==len(values) else None,observed_subset_mean=statistics.mean(present) if present else None,
86
+ declared_raters=len(reviews),independent_raters_verified=False,confidence_interval=None))
87
+ return results
88
+
89
+
90
+ def grade_speech_listening_pack(path,*,expected_pack_sha256,reviews,output_dir):
91
+ """reviews is [{rater_id, rows}], with rows read from a completed CSV."""
92
+ _,pack,mapping=_pack(path,expected_pack_sha256);results=_grade(pack,mapping,reviews)
93
+ report=dict(schema_version='sphynx.speech.listening.report.v1',suite_id=SUITE_ID,
94
+ invocation_id='listening-invocation-'+uuid.uuid4().hex,pack_sha256=expected_pack_sha256,results=results,
95
+ source_mode=pack['source_mode'],historical_ratings_reused=False,subjective_latency_inferred=False)
96
+ dest=_new_directory(output_dir);files=_write_files(dest,{'pack.json':encoded(pack),'mapping.json':encoded(mapping),'reviews.json':encoded(reviews),'report.json':encoded(report)})
97
+ receipt=dict(schema_version='sphynx.speech.listening.receipt.v1',suite_id=SUITE_ID,files=files,invocation_id=report['invocation_id'])
98
+ raw=encoded(receipt);(dest/'receipt.json').write_bytes(raw)
99
+ return dict(path=str(dest),receipt_sha256=digest(raw),suite_id=SUITE_ID,invocation_id=report['invocation_id'])
100
+
101
+
102
+ def load_speech_listening_report(path,*,expected_receipt_sha256):
103
+ root=Path(path).absolute();raw=_read(root,'receipt.json');require(digest(raw)==_sha(expected_receipt_sha256),'Listening receipt changed');receipt=json_value(raw)
104
+ require(receipt.get('schema_version')=='sphynx.speech.listening.receipt.v1' and receipt.get('suite_id')==SUITE_ID and
105
+ set(receipt.get('files',{}))=={'pack.json','mapping.json','reviews.json','report.json'},'Invalid listening report')
106
+ values={}
107
+ for p,h in receipt['files'].items():
108
+ b=_read(root,p);require(h==dict(sha256=digest(b),size_bytes=len(b)),'Listening report artifact changed');values[p]=json_value(b)
109
+ pack=values['pack.json'];report=values['report.json']
110
+ require(report.get('schema_version')=='sphynx.speech.listening.report.v1' and report.get('suite_id')==SUITE_ID and
111
+ report.get('invocation_id')==receipt.get('invocation_id') and report.get('pack_sha256')==digest(encoded(pack)) and
112
+ pack['files']['mapping.json']==dict(sha256=digest(encoded(values['mapping.json'])),size_bytes=len(encoded(values['mapping.json']))) and
113
+ report.get('historical_ratings_reused') is False and report.get('subjective_latency_inferred') is False and report.get('source_mode')==pack['source_mode'] and
114
+ report.get('results')==_grade(pack,values['mapping.json'],values['reviews.json']),'Listening arithmetic, identity or scope changed')
115
+ return report
@@ -0,0 +1,71 @@
1
+ """Separate frozen multilingual TTS campaigns using the existing TTS runner."""
2
+ from pathlib import Path
3
+ import uuid
4
+ from ._speech_kido import encoded,digest,json_value,require
5
+ from .speech_evidence import _new_directory,_read,_write_files,_sha
6
+ from .speech_complete import complete_tts_cases
7
+ from .speech_tts import freeze_tts_plan,run_tts_plan,load_tts_run,_load_plan
8
+
9
+ SUITE_ID='speech.multilingual.execution.v1'
10
+
11
+
12
+ def freeze_multilingual_tts_campaign(bundle,repository,*,source_commit,source_pr,baseline,candidate,
13
+ output_dir,case_ids=None,repetitions=3,max_requests=20,baseline_profile=None,candidate_profile=None):
14
+ """Default to the frozen source's three repetitions, within one total cap.
15
+
16
+ Both arms use only the shared, historically supported language subset. The
17
+ profile filter is an explicit operator declaration, not live capability proof.
18
+ """
19
+ views=[complete_tts_cases(bundle,benchmark='multilingual-isolated',case_ids=None,profile=p) for p in (baseline_profile,candidate_profile)]
20
+ shared={r['id'] for r in views[0]['cases']}&{r['id'] for r in views[1]['cases']}
21
+ require(case_ids is None or type(case_ids) is list and len(set(case_ids))==len(case_ids) and set(case_ids)<=shared,'Invalid or unsupported multilingual selection')
22
+ cases=[r for r in views[0]['cases'] if r['id'] in shared and (case_ids is None or r['id'] in case_ids)]
23
+ require(cases and type(repetitions) is int and 1<=repetitions<=3,'Invalid multilingual repetition design')
24
+ require(type(max_requests) is int and 1<=max_requests<=1000 and len(cases)*repetitions*2<=max_requests,'Multilingual campaign exceeds total request cap')
25
+ per_batch=20//(repetitions*2);dest=_new_directory(output_dir);plans=[]
26
+ for i in range(0,len(cases),per_batch):
27
+ chunk=cases[i:i+per_batch]
28
+ plans.append(freeze_tts_plan(repository,source_commit=source_commit,source_pr=source_pr,cases=chunk,
29
+ cohort_identity=dict(kind=SUITE_ID,sha256=digest(encoded(dict(bundle=bundle.manifest_sha256,cases=chunk)))),
30
+ baseline=baseline,candidate=candidate,output_dir=dest/('batch-'+str(len(plans)+1)),repetitions=repetitions))
31
+ manifest=dict(schema_version='sphynx.speech.multilingual.plan.v1',suite_id=SUITE_ID,
32
+ bundle_manifest_sha256=bundle.manifest_sha256,plans=plans,total_requests=len(cases)*repetitions*2,
33
+ max_requests=max_requests,baseline_profile=baseline_profile,candidate_profile=candidate_profile,
34
+ historical_support_filter_only=True,native_speaker_validation_available=False,provider_calls_made=False)
35
+ raw=encoded(manifest);(dest/'plan.json').write_bytes(raw)
36
+ return dict(path=str(dest),plan_sha256=digest(raw),total_requests=manifest['total_requests'],batches=len(plans),suite_id=SUITE_ID)
37
+
38
+
39
+ def run_multilingual_tts_campaign(path,*,expected_plan_sha256,output_dir,allow_provider_calls=False):
40
+ root=Path(path).absolute();raw=_read(root,'plan.json');require(digest(raw)==_sha(expected_plan_sha256),'Multilingual plan changed')
41
+ plan=json_value(raw);require(plan.get('schema_version')=='sphynx.speech.multilingual.plan.v1' and plan.get('suite_id')==SUITE_ID and
42
+ 0<plan['total_requests']<=plan['max_requests'] and plan.get('provider_calls_made') is False,'Invalid multilingual plan')
43
+ verified=[]
44
+ # Verify every subplan and the total budget before starting the first arm.
45
+ for i,sub in enumerate(plan['plans']):
46
+ batch=root/('batch-'+str(i+1));_,p=_load_plan(batch,sub['plan_sha256']);verified.append((batch,p,sub))
47
+ require(sum(p['budget']['planned_requests'] for _,p,_ in verified)==plan['total_requests'],'Multilingual request budget changed')
48
+ require(allow_provider_calls or all(p['mode']=='fixture' for _,p,_ in verified),'Explicit provider launch required for the entire campaign')
49
+ dest=_new_directory(output_dir);runs=[]
50
+ for i,(batch,p,sub) in enumerate(verified):
51
+ r=run_tts_plan(batch,expected_plan_sha256=sub['plan_sha256'],output_dir=dest/('batch-'+str(i+1)),allow_provider_calls=allow_provider_calls)
52
+ load_tts_run(r['path'],expected_manifest_sha256=r['manifest_sha256']);runs.append(r)
53
+ receipt=dict(schema_version='sphynx.speech.multilingual.run.v1',suite_id=SUITE_ID,
54
+ invocation_id='multilingual-invocation-'+uuid.uuid4().hex,plan_sha256=expected_plan_sha256,
55
+ status='completed' if all(r['status']=='completed' for r in runs) else 'completed_with_failures',runs=runs,
56
+ planned_requests=plan['total_requests'],quality=None,subjective_perceived_latency=None)
57
+ raw=encoded(receipt);(dest/'receipt.json').write_bytes(raw)
58
+ return dict(path=str(dest),receipt_sha256=digest(raw),status=receipt['status'],suite_id=SUITE_ID,invocation_id=receipt['invocation_id'])
59
+
60
+
61
+ def load_multilingual_tts_campaign(path,*,expected_receipt_sha256):
62
+ root=Path(path).absolute();raw=_read(root,'receipt.json');require(digest(raw)==_sha(expected_receipt_sha256),'Multilingual run receipt changed')
63
+ receipt=json_value(raw);require(receipt.get('schema_version')=='sphynx.speech.multilingual.run.v1' and receipt.get('suite_id')==SUITE_ID,'Invalid multilingual run')
64
+ reports=[];planned=0
65
+ for i,r in enumerate(receipt['runs']):
66
+ batch=root/('batch-'+str(i+1));reports.append(load_tts_run(batch,expected_manifest_sha256=r['manifest_sha256']))
67
+ manifest=json_value(_read(batch,'manifest.json'));planned+=manifest['planned_requests']
68
+ require(r.get('status')==manifest['status'] and r.get('invocation_id')==manifest['invocation_id'],'Multilingual batch status or identity changed')
69
+ require(receipt['status']==('completed' if all(r['status']=='completed' for r in receipt['runs']) else 'completed_with_failures') and
70
+ receipt.get('quality') is None and receipt.get('subjective_perceived_latency') is None and receipt.get('planned_requests')==planned,'Multilingual scope changed')
71
+ return dict(receipt=receipt,reports=reports)