kcode-k1-audio-classification-format 0.5.1

Stable facade for K1 audio-classification event and fragment formats
Documentation
use kcode_k1_audio_classification_format as facade;
use kcode_speaker_v3_analysis as current;
use kcode_speaker_v3_analysis_0_1 as previous;
use serde::Serialize;

#[derive(Serialize)]
struct PreviousTranscriptionCompleteV1 {
    fragment_id: [u8; 12],
    analysis: previous::ExecutedAnalysis,
}

fn txid(seed: u8) -> facade::TxId {
    let mut bytes = [0; 12];
    for (index, value) in bytes.iter_mut().enumerate() {
        *value = seed.wrapping_add(index as u8);
    }
    facade::TxId::from_bytes(bytes)
}

fn current_analysis() -> current::ExecutedAnalysis {
    let mut ogg = vec![0; 29];
    ogg[..4].copy_from_slice(b"OggS");
    ogg[26] = 1;
    ogg[27] = 1;
    current::ExecutedAnalysis {
        envelope: current::AnalysisEnvelope {
            audio: current::OggAudioMetadata::from_bytes(&ogg, 1_250, Some("sample.ogg".into()))
                .expect("current Ogg metadata"),
            analysis: current::StructuredAnalysis {
                transcript: "Speaker 1: hello".into(),
                speakers: vec![current::StructuredSpeaker {
                    speaker: current::LocalSpeakerLabel::new(1).expect("current speaker label"),
                    language: "English".into(),
                    features: current::FeatureVector24::default(),
                    features_usable_for_training: true,
                }],
            },
            gemini: current::GeminiCohort {
                model_id: "gemini".into(),
                transcript_prompt_revision: "transcript-r1".into(),
                feature_prompt_revisions: [
                    "feature-r1".into(),
                    "feature-r2".into(),
                    "feature-r3".into(),
                ],
                feature_schema_revision: "schema-r1".into(),
            },
            structurer: current::StructurerProvenance {
                model_id: "terra".into(),
                prompt_revision: "structuring-r1".into(),
            },
        },
        label_extractor: current::StructurerProvenance {
            model_id: "terra".into(),
            prompt_revision: "labels-r1".into(),
        },
    }
}

fn previous_analysis() -> previous::ExecutedAnalysis {
    let mut ogg = vec![0; 29];
    ogg[..4].copy_from_slice(b"OggS");
    ogg[26] = 1;
    ogg[27] = 1;
    previous::ExecutedAnalysis {
        envelope: previous::AnalysisEnvelope {
            audio: previous::OggAudioMetadata::from_bytes(&ogg, 1_250, Some("sample.ogg".into()))
                .expect("previous Ogg metadata"),
            analysis: previous::StructuredAnalysis {
                transcript: "Speaker 1: hello".into(),
                speakers: vec![previous::StructuredSpeaker {
                    speaker: previous::LocalSpeakerLabel::new(1).expect("previous speaker label"),
                    language: "English".into(),
                    features: previous::FeatureVector24::default(),
                    features_usable_for_training: true,
                }],
            },
            gemini: previous::GeminiCohort {
                model_id: "gemini".into(),
                transcript_prompt_revision: "transcript-r1".into(),
                feature_prompt_revisions: [
                    "feature-r1".into(),
                    "feature-r2".into(),
                    "feature-r3".into(),
                ],
                feature_schema_revision: "schema-r1".into(),
            },
            structurer: previous::StructurerProvenance {
                model_id: "terra".into(),
                prompt_revision: "structuring-r1".into(),
            },
        },
        label_extractor: previous::StructurerProvenance {
            model_id: "terra".into(),
            prompt_revision: "labels-r1".into(),
        },
    }
}

fn legacy_event<T: Serialize>(tag: u8, value: &T) -> Vec<u8> {
    let mut bytes = vec![4, tag];
    bytes.extend(postcard::to_allocvec(value).expect("serialize legacy event body"));
    bytes
}

fn assert_event<T: Serialize>(tag: u8, event: facade::AudioClassificationEventV3, value: &T) {
    let bytes = legacy_event(tag, value);
    assert_eq!(facade::encode_event(&event), Ok(bytes.clone()));
    assert_eq!(facade::decode_event(&bytes), Ok(event));
}

#[test]
fn all_public_paths_compile_and_event_bytes_remain_frozen() {
    let speaker = facade::LocalSpeakerLabel::new(1).expect("speaker label");
    let queue = facade::QueueV2 {
        audio_object_id: txid(1),
    };
    assert_event(
        1,
        facade::AudioClassificationEventV3::Queue(queue.clone()),
        &queue,
    );
    let updates = [
        facade::ProgressUpdateV1::LlmJobStarted {
            sequence: 1,
            stage: facade::FragmentStageV1::Transcript,
            name: "transcribe".into(),
        },
        facade::ProgressUpdateV1::LlmJobSucceeded { sequence: 2 },
        facade::ProgressUpdateV1::LlmJobFailed {
            sequence: 3,
            error: "failed".into(),
        },
        facade::ProgressUpdateV1::StageCompleted {
            stage: facade::FragmentStageV1::Structuring,
        },
    ];
    for update in updates {
        let progress = facade::ProgressV1 {
            fragment_id: txid(2),
            update,
        };
        assert_event(
            6,
            facade::AudioClassificationEventV3::Progress(progress.clone()),
            &progress,
        );
    }
    let transcription = facade::TranscriptionCompleteV1 {
        fragment_id: txid(3),
        analysis: current_analysis(),
    };
    assert_event(
        2,
        facade::AudioClassificationEventV3::TranscriptionComplete(transcription.clone()),
        &transcription,
    );
    let failed = facade::FailedV2 {
        fragment_id: txid(4),
        stage: facade::FragmentStageV1::SpeakerFeatures,
        llm_job_sequence: Some(7),
        error: "failure".into(),
    };
    assert_event(
        3,
        facade::AudioClassificationEventV3::Failed(failed.clone()),
        &failed,
    );
    let discarded = facade::DiscardedV2 {
        fragment_id: txid(5),
    };
    assert_event(
        4,
        facade::AudioClassificationEventV3::Discarded(discarded.clone()),
        &discarded,
    );
    let confirmation = facade::LabelConfirmationV1 {
        fragment_id: txid(6),
        interim_txid: txid(7),
        speakers: vec![facade::SpeakerLabelV1 {
            speaker,
            person_id: "person-1".into(),
        }],
    };
    assert_event(
        5,
        facade::AudioClassificationEventV3::LabelConfirmation(confirmation.clone()),
        &confirmation,
    );
    let _ = [
        facade::FragmentStageV1::Queue,
        facade::FragmentStageV1::Transcript,
        facade::FragmentStageV1::SpeakerLabels,
        facade::FragmentStageV1::SpeakerFeatures,
        facade::FragmentStageV1::Structuring,
        facade::FragmentStageV1::LabelConfirmation,
    ];
    let _ = facade::FormatError::InvalidTxIdSlotLength(facade::TxIdSlot::LEN);
}

#[test]
fn speaker_versions_preserve_analysis_and_transcription_complete_bytes() {
    let current = current_analysis();
    let previous = previous_analysis();
    assert_eq!(
        postcard::to_allocvec(&current).expect("serialize current analysis"),
        postcard::to_allocvec(&previous).expect("serialize previous analysis")
    );
    let fragment_bytes = [7; 12];
    let current_event =
        facade::encode_event(&facade::AudioClassificationEventV3::TranscriptionComplete(
            facade::TranscriptionCompleteV1 {
                fragment_id: facade::TxId::from_bytes(fragment_bytes),
                analysis: current,
            },
        ))
        .expect("encode current event");
    let previous_event = legacy_event(
        2,
        &PreviousTranscriptionCompleteV1 {
            fragment_id: fragment_bytes,
            analysis: previous,
        },
    );
    assert_eq!(current_event, previous_event);
}

#[test]
fn representative_fragment_and_path_bytes_remain_compatible() {
    let features = facade::FeatureVector24::default();
    let staged = facade::StagedFragmentV1 {
        analysis_txid: txid(20),
        transcript: "hello".into(),
        speakers: vec![facade::StagedSpeakerV1 {
            speaker: facade::LocalSpeakerLabel::new(1).expect("speaker label"),
            language: "English".into(),
            features: features.clone(),
            usable_for_training: true,
        }],
    };
    let staged_bytes = facade::encode_staged_fragment(&staged).expect("encode staged");
    assert_eq!(
        staged_bytes[..16],
        [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    );
    assert_eq!(facade::decode_staged_fragment(&staged_bytes), Ok(staged));
    let final_value = facade::FinalFragmentV1 {
        analysis_txid: txid(30),
        confirmation_txid: txid(40),
        transcript: "hello".into(),
        speakers: vec![facade::FinalSpeakerV1 {
            speaker: facade::LocalSpeakerLabel::new(1).expect("speaker label"),
            person_id: "person-1".into(),
            language: "English".into(),
            features,
            usable_for_training: false,
        }],
    };
    let final_bytes = facade::encode_final_fragment(&final_value).expect("encode final");
    assert_eq!(
        final_bytes[..16],
        [1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    );
    assert_eq!(facade::decode_final_fragment(&final_bytes), Ok(final_value));
    let slot = facade::TxIdSlot::new(txid(50));
    assert_eq!(facade::TxIdSlot::decode(&slot.encode()), Ok(slot));
    assert_eq!(
        facade::txid_from_path(facade::txid_path(txid(60))),
        Ok(txid(60))
    );
}