Voice fingerprint embedding edge function.

The SQL pipeline is fully wired. All it needs is an edge function that takes an audio blob, generates a 192-dim speaker embedding, and calls register_voice_fingerprint or check_voice_against_banned. Here's your vendor options.

Vendor shortlist

VendorPer-request costEmbedding dimQualityVerdict
Azure Speaker Recognition~$0.004/reqInternal (use Verify API instead of raw embedding)ExcellentRecommended — cheapest, Microsoft-hosted, BAA available for HIPAA
Resemble AI~$0.01/req256 (trim/pad to 192)Very goodGood alternative, simpler API
pyannote-audio (self-hosted)Your compute192 nativeExcellentFree but operational load
ElevenLabs Voice ID~$0.015/reqInternalGreatPremium; overkill for this use
SpeechBrain ECAPA-TDNNYour compute192 nativeResearch SOTAFree, fits in Cloudflare Workers AI

Quick start: Azure Speaker Recognition

Azure's Speaker Recognition returns a verification score directly (no raw embedding exposure), so you'd adapt: call their API, take the returned score, and use it as similarity. For our pgvector-based approach, use a vendor that returns raw embeddings (Resemble or pyannote).

Recommended: Cloudflare Workers AI + SpeechBrain

Cloudflare has an AI runtime that can host ONNX models. ECAPA-TDNN exported to ONNX is ~30MB, runs in ~200ms per call, and produces native 192-dim embeddings. Free in Workers AI for modest volume. Your Cloudflare account already has Workers set up.

Edge function skeleton (Supabase)

// supabase/functions/voice-embed/index.ts
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

Deno.serve(async (req) => {
  const { audio_url, subject_kind, subject_id, display_name, phone,
          source, source_event_id, resident_state, mode } = await req.json();

  // 1. Fetch the audio blob
  const audioRes = await fetch(audio_url);
  const audioBlob = await audioRes.blob();

  // 2. Generate embedding (swap in whichever vendor you pick)
  const embeddingVendorRes = await fetch("https://YOUR_VENDOR/embed", {
    method: "POST",
    headers: { "Authorization": `Bearer ${Deno.env.get("VOICE_API_KEY")}` },
    body: audioBlob
  });
  const { embedding } = await embeddingVendorRes.json(); // array of 192 floats

  const sb = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  );

  // 3. Two modes: register new baseline, or check against banned
  if (mode === "check") {
    const { data } = await sb.rpc("check_voice_against_banned", {
      p_embedding: embedding,
      p_calling_phone: phone,
      p_caller_name: display_name,
      p_call_event_id: source_event_id,
      p_threshold: 0.85
    });
    return new Response(JSON.stringify(data));
  }

  // Register as new baseline
  const { data } = await sb.rpc("register_voice_fingerprint", {
    p_embedding: embedding,
    p_subject_kind: subject_kind,
    p_subject_id: subject_id,
    p_display_name: display_name,
    p_phone: phone,
    p_source: source,
    p_source_audio_url: audio_url,
    p_source_event_id: source_event_id,
    p_consent_method: "tos_acceptance",
    p_resident_state: resident_state || "TX",
    p_embedding_model: "azure-speaker-recognition-v2"
  });

  return new Response(JSON.stringify(data));
});

Where to call this in your existing code

1. Daisy phone calls (nana-voice edge function)

When a call completes, Twilio gives you a recording URL. At the end of nana-voice:

// After the call finishes, async fire-and-forget:
const firstCallForThisCaretaker = /* check daisy_calls table */;
const mode = firstCallForThisCaretaker ? "register" : "check";
fetch(`${SB}/functions/v1/voice-embed`, {
  method: "POST", headers: { Authorization: `Bearer ${SERVICE_KEY}` },
  body: JSON.stringify({
    audio_url: twilioRecordingUrl,
    subject_kind: "daisy_caretaker",
    subject_id: caretakerId,
    display_name: caretakerName,
    phone: fromNumber,
    source: "daisy_phone_call",
    source_event_id: callSid,
    resident_state: customerState,
    mode
  })
});

2. Driver SOS clips

The log_driver_sos RPC already uploads audio. Add a follow-up call:

// In /drive triggerSos() after the audio upload completes:
await fetch(`${SB}/functions/v1/voice-embed`, {
  method: "POST", headers: { apikey: KEY, Authorization: `Bearer ${KEY}` },
  body: JSON.stringify({
    audio_url: audioUrl,
    subject_kind: "unknown_caller",       // it's the hostile party, not the driver
    display_name: "SOS incident — aggressor",
    source: "sos_clip",
    source_event_id: runId,
    resident_state: "TX",
    mode: "check"                         // see if this voice is banned
  })
});

Safety rails

Current state