stub_runner_service.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. from dataclasses import dataclass
  2. from app.schemas.conclusion import ConclusionSummary, RankedFinding
  3. from app.schemas.evidence import EvidenceSummary
  4. from app.schemas.experiment import ExperimentSummary, ScoreCard
  5. from app.schemas.observation import ObservationSummary
  6. from app.schemas.orchestration import OrchestratorPlan
  7. @dataclass(slots=True)
  8. class StubRunnerResult:
  9. probe_evidence: list[EvidenceSummary]
  10. experiments: list[ExperimentSummary]
  11. conclusion: ConclusionSummary
  12. status: str
  13. class StubRunnerService:
  14. def run_initial_plan(
  15. self,
  16. *,
  17. session_id: str,
  18. observation: ObservationSummary,
  19. plan: OrchestratorPlan,
  20. ) -> StubRunnerResult:
  21. periodic_hint = 0.79 if "periodic" in observation.tags else 0.68
  22. tonal_hint = 0.74 if observation.duration_ms >= 4500 else 0.61
  23. classifier_top_score = 0.64 if "periodic" in observation.tags else 0.55
  24. probe_evidence = [
  25. EvidenceSummary(
  26. id=f"{session_id}-e1",
  27. producer_module_id="energy_probe",
  28. category="feature",
  29. values={
  30. "rms": 0.41,
  31. "peak_count": 7,
  32. "duration_ms": observation.duration_ms,
  33. },
  34. confidence=0.86,
  35. ),
  36. EvidenceSummary(
  37. id=f"{session_id}-e2",
  38. producer_module_id="periodicity_probe",
  39. category="pattern",
  40. values={
  41. "repeat_interval_ms": 620,
  42. "stability": periodic_hint,
  43. },
  44. confidence=0.82,
  45. ),
  46. EvidenceSummary(
  47. id=f"{session_id}-e3",
  48. producer_module_id="baseline_sound_classifier",
  49. category="classification",
  50. values={
  51. "top_k": [
  52. {
  53. "label": "periodic_alert_tone",
  54. "score": classifier_top_score,
  55. },
  56. {
  57. "label": "mechanical_click_sequence",
  58. "score": 0.27,
  59. },
  60. ]
  61. },
  62. confidence=classifier_top_score,
  63. ),
  64. ]
  65. experiments: list[ExperimentSummary] = []
  66. parent_id: str | None = None
  67. for index, planned_experiment in enumerate(plan.experiments, start=1):
  68. experiment_id = f"{session_id}-x{index}"
  69. title = planned_experiment.goal
  70. score_total = 0.72 + (index * 0.06)
  71. notes = [
  72. f"Executed planned chain '{planned_experiment.preferred_chain_id or 'ad-hoc'}'."
  73. ]
  74. if planned_experiment.preferred_chain_id == "periodic_validation_chain":
  75. components = {
  76. "repeat_consistency": 0.89,
  77. "pattern_clarity": periodic_hint,
  78. "resource_cost": 0.18,
  79. }
  80. penalties = {"mixed_source_risk": 0.05}
  81. notes.append("Autocorrelation and cepstrum align on repeat interval.")
  82. failure_reasons: list[str] = []
  83. elif planned_experiment.preferred_chain_id == "harmonic_validation_chain":
  84. components = {
  85. "tonal_stability": tonal_hint,
  86. "harmonic_ratio": 0.71,
  87. "resource_cost": 0.22,
  88. }
  89. penalties = {"short_clip_penalty": 0.04}
  90. notes.append("Harmonic structure is present but not fully dominant.")
  91. failure_reasons = []
  92. else:
  93. components = {
  94. "mixed_source_probability": 0.43,
  95. "post_separation_gain": 0.18,
  96. "resource_cost": 0.31,
  97. }
  98. penalties = {"separation_uncertainty": 0.08}
  99. notes.append(
  100. "Source separation did not improve confidence enough to overtake the periodic hypothesis."
  101. )
  102. failure_reasons = []
  103. experiments.append(
  104. ExperimentSummary(
  105. id=experiment_id,
  106. parent_id=parent_id,
  107. hypothesis_id=planned_experiment.hypothesis_id,
  108. title=title,
  109. status="done",
  110. pipeline_summary=[
  111. node.module_id for node in planned_experiment.pipeline
  112. ],
  113. score=ScoreCard(
  114. total=round(score_total, 2),
  115. components=components,
  116. penalties=penalties,
  117. notes=notes,
  118. ),
  119. failure_reasons=failure_reasons,
  120. )
  121. )
  122. parent_id = experiment_id
  123. conclusion = ConclusionSummary(
  124. summary=(
  125. "The sample most likely contains a periodic alert-like tone rather "
  126. "than an unstructured environmental event."
  127. ),
  128. findings=[
  129. RankedFinding(
  130. label="Periodic alert tone",
  131. confidence=0.84 if "periodic" in observation.tags else 0.76,
  132. supporting_evidence=[
  133. "Repeat interval remains stable near 620 ms.",
  134. "Probe chain shows clear onset grouping.",
  135. "Validation chains favor periodic structure over diffuse events.",
  136. ],
  137. contradicting_evidence=[
  138. "Mixed-source contamination is not fully ruled out.",
  139. ],
  140. )
  141. ],
  142. uncertainty=[
  143. "The current stub runner does not yet incorporate source separation output.",
  144. "A longer clip may improve confidence around tonal stability.",
  145. ],
  146. suggested_next_actions=[
  147. "Run harmonic validation against a longer sample window.",
  148. "Add a mixed-source validation branch if future captures include overlap tags.",
  149. ],
  150. )
  151. return StubRunnerResult(
  152. probe_evidence=probe_evidence,
  153. experiments=experiments,
  154. conclusion=conclusion,
  155. status="reviewing",
  156. )