feat(stage11): audio integration — STT for interviews, podcasts, press conferences with timestamped claims

This commit is contained in:
NSCT Agent
2026-08-25 15:31:03 +00:00
parent 3ab875d2bc
commit 10a083aa02
8 changed files with 2832 additions and 65 deletions

View File

@@ -538,7 +538,7 @@ class VisionEvidenceModel(Base):
extracted_text = Column(Text, nullable=False)
image_data_url = Column(Text, nullable=True)
entities = Column(JSON, nullable=False, default=dict)
extracted_entities = Column(JSON, nullable=False, default=dict)
confidence = Column(Float, nullable=False, default=0.5)
confidence_label = Column(String(16), nullable=False, default="medium")
@@ -601,4 +601,157 @@ VisionEvidenceModel.entities = relationship(
back_populates="evidence",
cascade="all, delete-orphan",
foreign_keys="VisionEntityModel.evidence_id",
)
)
# ---------------------------------------------------------------------------
# Stage 11 — Audio Evidence Extraction (Speech-to-Text)
# ---------------------------------------------------------------------------
class AudioSegmentType(str, enum.Enum):
"""Klassifikation der Audio-Quelle (Stage 11)."""
INTERVIEW = "interview"
PODCAST = "podcast"
PRESSEKONFERENZ = "pressekonferenz"
REDEN = "reden"
SONSTIGE = "sonstige"
class AudioTranscriptModel(Base):
"""Transkript eines Audio-Eintrags (Stage 11).
Felder:
uuid: Primärschlüssel (UUID)
research_run_id: Research-Run-Zuordnung
source_id: Quelle, von der das Audio stammt
segment_type: Art des Audio-Materials
transcript_text: Gesamtes Transkript als Text
audio_file_url: URL der Audio-Datei (optional)
duration_seconds: Gesamtdauer in Sekunden
language: Sprache des Audios
confidence: Confidence der STT-Erkennung
created_at / updated_at: Zeitstempel
"""
__tablename__ = "audio_transcripts"
id = Column(String(36), primary_key=True, default=lambda: str(uuid4()))
research_run_id = Column(String(36), nullable=False)
source_id = Column(String(36), ForeignKey("sources.id"), nullable=True)
segment_type = Column(Enum(AudioSegmentType), nullable=True)
transcript_text = Column(Text, nullable=False, default="")
audio_file_url = Column(Text, nullable=True)
duration_seconds = Column(Float, nullable=False, default=0.0)
language = Column(String(16), nullable=False, default="unknown")
confidence = Column(Float, nullable=False, default=0.5)
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
updated_at = Column(DateTime, nullable=False, default=datetime.utcnow)
# Relationships
segments = relationship(
"AudioTranscriptSegmentModel",
back_populates="transcript",
cascade="all, delete-orphan",
)
claims = relationship(
"AudioClaimModel",
back_populates="transcript",
cascade="all, delete-orphan",
)
__table_args__ = (
Index("ix_audio_transcripts_research_run_id", "research_run_id"),
Index("ix_audio_transcripts_segment_type", "segment_type"),
Index("ix_audio_transcripts_source_id", "source_id"),
)
class AudioTranscriptSegmentModel(Base):
"""Segment des Audio-Transkripts (Stage 11).
Felder:
uuid: Primärschlüssel (UUID)
transcript_id: FK zum AudioTranscript
start_time: Start-Zeitstempel in Sekunden
end_time: Ende-Zeitstempel in Sekunden
text: Transkribierter Text
speaker_id: ID des Sprechers
speaker_type: Kategorie des Sprechers
confidence: Confidence der STT-Erkennung
"""
__tablename__ = "audio_transcript_segments"
id = Column(String(36), primary_key=True, default=lambda: str(uuid4()))
transcript_id = Column(
String(36),
ForeignKey("audio_transcripts.id"),
nullable=False,
)
start_time = Column(Float, nullable=False, default=0.0)
end_time = Column(Float, nullable=False, default=0.0)
text = Column(Text, nullable=False, default="")
speaker_id = Column(String(64), nullable=False, default="")
speaker_type = Column(String(64), nullable=True)
confidence = Column(Float, nullable=False, default=0.5)
# Relationships
transcript = relationship("AudioTranscriptModel", back_populates="segments")
__table_args__ = (
Index("ix_audio_transcript_segments_transcript_id", "transcript_id"),
Index("ix_audio_transcript_segments_speaker_id", "speaker_id"),
)
class AudioClaimModel(Base):
"""Claim extrahiert aus Audio mit Zeitstempel (Stage 11).
Jeder Claim aus Audio hat einen Zeitstempel im Original-Audio
und muss quellenverknüpft sein (Provenance-Pflicht).
Felder:
uuid: Primärschlüssel (UUID)
transcript_id: FK zum AudioTranscript
claim_text: Die extrahierte Behauptung
timestamp_start: Start-Zeitstempel im Original-Audio
timestamp_end: Ende-Zeitstempel im Original-Audio
speaker_id: ID des Sprechers
source_url: URL der Quelle (Provenance)
evidence_span: Zitat oder Textpassage
claim_type: Art des Claims
confidence: Confidence der Claim-Extraktion
"""
__tablename__ = "audio_claims"
id = Column(String(36), primary_key=True, default=lambda: str(uuid4()))
transcript_id = Column(
String(36),
ForeignKey("audio_transcripts.id"),
nullable=False,
)
claim_text = Column(Text, nullable=False)
timestamp_start = Column(Float, nullable=False, default=0.0)
timestamp_end = Column(Float, nullable=False, default=0.0)
speaker_id = Column(String(64), nullable=False, default="")
source_url = Column(Text, nullable=True)
evidence_span = Column(Text, nullable=True)
claim_type = Column(String(64), nullable=True)
confidence = Column(Float, nullable=False, default=0.5)
# Relationships
transcript = relationship("AudioTranscriptModel", back_populates="claims")
__table_args__ = (
Index("ix_audio_claims_transcript_id", "transcript_id"),
Index("ix_audio_claims_claim_type", "claim_type"),
Index("ix_audio_claims_speaker_id", "speaker_id"),
)