59 min readAmbient capture

Implementation Guide: Capture deposition proceedings and generate structured summaries with key admissions

Step-by-step implementation guide for deploying AI to capture deposition proceedings and generate structured summaries with key admissions for Legal Services clients.

Hardware Procurement

Shure MXA920 Ceiling Array Microphone

Shure MXA920 Ceiling Array Microphone

ShureMXA920W-S (Square, White) or MXA920W-R (Round, White)Qty: 2

$3,800 per unit MSP cost / $4,900 suggested resale per unit

Primary ambient audio capture device for permanent deposition suites. Automatic Coverage technology provides superior multi-speaker directional pickup without manual beam steering. Ceiling-mount installation ensures unobstructed table space and consistent capture regardless of speaker position. One unit per deposition room.

EPOS EXPAND Capture 5 Tabletop Speakerphone

EPOS (Sennheiser)1000895Qty: 2

$250 per unit MSP cost / $400 suggested resale per unit

Backup and portable tabletop microphone with 7-beamforming microphone array for speaker attribution. Used as a secondary capture device in case of ceiling mic issues, or for portable deployment when depositions occur outside the firm's main office (e.g., at opposing counsel's office, hospital bedside depositions). Also provides Teams Room audio integration.

Jabra PanaCast 50 Video Bar

Jabra (GN Audio)8200-2390 (Black) or 8200-2380 (Grey)Qty: 2

$1,100 per unit MSP cost / $1,500 suggested resale per unit

Combined video and supplemental audio capture for video-recorded depositions. Three 13MP cameras with 180° field of view capture all participants. 8-microphone array provides secondary audio stream for redundancy. Particularly important for remote/hybrid depositions conducted via Teams or Zoom where video capture of demeanor is required under FRCP Rule 30(b)(3).

Lenovo ThinkCentre M90q Gen 4 Tiny Desktop

Lenovo12CR000GUS (i7-13700T, 16GB, 512GB SSD, Win11 Pro)Qty: 2

$700 per unit MSP cost / $950 suggested resale per unit

Dedicated recording workstation for each deposition room. Runs the recording capture application (OBS Studio or custom capture agent), manages local buffering of audio/video files, and initiates upload to the transcription pipeline. Tiny form factor mounts behind monitor or under desk. Windows 11 Pro required for BitLocker encryption and domain join.

Synology DiskStation DS1621+

SynologyDS1621+Qty: 1

$900 MSP cost (diskless) / $1,300 suggested resale (diskless)

On-premises NAS for encrypted archival of all deposition recordings, raw transcripts, and AI-generated summaries. 6-bay design supports RAID 5/6 for redundancy. Serves as the primary local repository with 7+ year retention capability per legal hold requirements. Synology Hyper Backup handles automated cloud replication to Azure Blob Storage.

WD Red Plus NAS Hard Drive 4TB

Western DigitalWD40EFPXQty: 6

$100 per unit MSP cost / $140 suggested resale per unit

NAS-rated 3.5" SATA drives for the Synology DS1621+ in RAID 5 configuration. 6 × 4TB in RAID 5 provides approximately 20TB usable storage. At ~500MB/hour for high-quality stereo audio, this supports approximately 40,000 hours of deposition recordings—sufficient for 5+ years for most mid-size litigation firms.

APC Smart-UPS 1500VA LCD

APC by Schneider ElectricSMT1500CQty: 2

$450 per unit MSP cost / $600 suggested resale per unit

Uninterruptible power supply for each deposition room's recording workstation and network equipment. A power interruption during a deposition recording can have severe legal consequences. The SMT1500C provides approximately 20 minutes of runtime for graceful shutdown and has USB connectivity for automatic shutdown scripting via PowerChute.

Dell UltraSharp 24 Monitor

DellU2424HQty: 2

$200 per unit MSP cost / $280 suggested resale per unit

Display for the recording workstation in each deposition room. Used by the paralegal or tech staff to monitor recording status, verify audio levels, and manage the capture session. IPS panel with USB-C connectivity for clean single-cable connection to ThinkCentre.

Software Procurement

CaseMark Professional (White-Label)

CaseMarkUsage-based with monthly tiersQty: Per summary or volume tier

$25 per deposition summary (MSP cost); resell at $45–$60 per summary. Professional tier ~$500/month for volume pricing

Primary AI deposition summarization engine. Converts raw transcripts into structured summaries with key admissions, credibility analysis, event chronologies, and page-line citations. White-label capability allows MSP to brand the portal, PDFs, and client-facing interface under the MSP's own identity. SOC 2 Type II certified. Integrates with Smokeball directly; API available for Clio and other platforms.

Deepgram Nova-3 API

DeepgramNova-3Qty: Usage-based API

$0.0043/minute for pre-recorded audio; $0.0077/minute for streaming. Typical firm: $50–$200/month based on deposition volume

Primary speech-to-text engine for converting deposition audio recordings into text transcripts with speaker diarization (speaker identification and labeling). Nova-3 delivers a 54.3% reduction in word error rate versus competitors and includes native punctuation, custom vocabulary (legal terminology), and speaker diarization at the base price. $200 free credit provided on signup.

Anthropic Claude API (Sonnet)

AnthropicSonnetQty: Usage-based API

$3/MTok input, $15/MTok output. Typical deposition (50-page transcript ~25K tokens): ~$0.15–$0.50 per summarization run

Secondary/custom LLM summarization layer for firms that need bespoke summary formats beyond CaseMark's templates, or for MSPs building custom prompt-based workflows for specific practice areas (e.g., medical malpractice with ICD-10 code extraction). Used when CaseMark templates are insufficient or when the MSP wants to offer differentiated summarization products.

Microsoft 365 Business Premium

MicrosoftPer-seat SaaS (CSP)

$22/user/month (CSP cost); resell at $28–$35/user/month

Foundation platform providing Azure AD (Entra ID) for identity management, SharePoint Online for document management and transcript archival, Microsoft Teams for hybrid/remote deposition capture, OneDrive for per-attorney file sync, and Intune for workstation management. BitLocker enforcement via Intune protects recording workstations.

OBS Studio

OBS Project (Open Source)Open Source (GPLv2)

Free

Audio and video recording application on the deposition room workstation. Records multi-channel audio from the Shure MXA920 (via Dante/USB) and video from the Jabra PanaCast 50 simultaneously. Configured with automated scene switching and recording profiles for consistent capture. Scriptable via obs-websocket for automation.

Clio Manage (or client's existing PMS)

ClioPer-seat SaaSQty: Per user/month

$39–$99/user/month (paid by client); integration labor included in MSP implementation fee

The firm's practice management system where deposition summaries, transcripts, and recordings are filed to the appropriate matter/case. The MSP builds an automated integration that pushes completed summaries from the AI pipeline directly into the correct matter's document folder via Clio's REST API.

Synology Hyper Backup + Azure Blob Storage

Synology / Microsoft AzureHyper Backup / Azure Blob Storage (Cool Tier)

Azure Blob Storage (Cool tier): ~$0.01/GB/month. Estimated $20–$80/month for typical firm's archive

Automated encrypted backup of all on-premises deposition recordings and transcripts to Azure Blob Storage. Provides geographic redundancy and disaster recovery capability. Cool tier pricing optimized for archival data that is rarely accessed but must be retained for 7+ years per legal hold requirements.

Rev Human Transcription (Backup)

RevHuman Transcription / Rev MaxQty: Per-minute usage-based

$1.99/audio minute for human transcription; Rev Max subscription at $29.99/month includes 20 hours of AI transcription

Human transcription backstop for high-stakes depositions where 99.5%+ accuracy is required or where audio quality is poor (e.g., hostile witness, heavy accents, multiple crosstalk). The AI pipeline processes all depositions by default, but attorneys can flag specific depositions for human review via Rev. Rev Max subscription at $29.99/month provides 20 hours of AI transcription as a secondary processing path.

Prerequisites

  • Minimum 25 Mbps upload bandwidth on the firm's internet connection (50 Mbps+ recommended for real-time streaming transcription); verify with speed test from each deposition room location
  • Failover/redundant internet connection (cellular failover at minimum) — a dropped recording during deposition has severe legal consequences
  • Dedicated VLAN capability on the firm's network switch infrastructure (managed switches required, not consumer-grade)
  • Microsoft 365 Business Premium licenses for all attorneys and paralegals who will access the system (or equivalent Azure AD/Entra ID for SSO)
  • Practice management software with API access enabled (Clio Manage Boutique tier or higher, Smokeball Boost tier, or equivalent)
  • Electrical outlets and ceiling mounting points in each deposition room (verify ceiling can support Shure MXA920 weight of 7.9 lbs; standard T-bar grid ceiling or hard ceiling with appropriate mounting hardware)
  • Room acoustic assessment completed — deposition rooms should have some acoustic treatment (carpet, acoustic panels) to minimize reverberation that degrades transcription accuracy
  • Written consent/disclosure policy reviewed and approved by the firm's managing partner and ethics counsel — MSP provides templates but the firm must approve the legal language
  • Client has designated a 'Deposition Technology Coordinator' (typically a senior paralegal) who will be the primary point of contact for training and ongoing operations
  • Existing antivirus/endpoint protection solution compatible with OBS Studio and Deepgram desktop agents (whitelist rules may be needed)
  • Physical security controls for deposition rooms — rooms must be lockable and recording workstations should not be accessible to unauthorized persons
  • Azure subscription (can be created during implementation) for Azure Blob Storage backup destination

Installation Steps

Step 1: Network Infrastructure Preparation

Create a dedicated VLAN for deposition room equipment to isolate recording traffic from general office network traffic. This ensures consistent bandwidth for audio/video upload and adds a security boundary around sensitive deposition data. Configure QoS rules to prioritize traffic from deposition room workstations to cloud transcription API endpoints.

Example for Cisco/Meraki managed switch
cisco-ios
# adjust for client's switch vendor

configure terminal
vlan 50
name DEPOSITION_CAPTURE
exit
interface range GigabitEthernet0/1-4
switchport mode access
switchport access vlan 50
no shutdown
exit
  • Configure DHCP scope for VLAN 50 on firewall/DHCP server
  • Subnet: 10.10.50.0/24, Gateway: 10.10.50.1
  • DNS: firm's DNS servers
  • QoS: Mark VLAN 50 traffic as AF31 (Assured Forwarding) for priority
Note

Adjust VLAN ID and interface range to match client's actual switch hardware. For Meraki, configure via Dashboard > Switch > Routing & DHCP. For Ubiquiti, configure via UniFi Network controller. Ensure the firewall allows outbound HTTPS (443) from VLAN 50 to Deepgram API endpoints (api.deepgram.com), CaseMark endpoints, and Azure Blob Storage endpoints.

Step 2: Deposition Room Physical Setup — Ceiling Microphone Installation

Install the Shure MXA920 ceiling array microphone in each deposition room. The MXA920 should be centered over the deposition table at the recommended height of 6–12 feet above the talker plane (seated head height, approximately 4 feet). The microphone connects via a single Ethernet cable carrying both Dante audio and PoE power.

1
Mount the MXA920 using the included ceiling mount hardware. For drop ceiling: Use Shure A900-PM plenum mount. For hard ceiling: Use Shure A900-HCM hard ceiling mount.
2
Run a Cat6 Ethernet cable from the MXA920 to the deposition room's network switch port (VLAN 50).
3
Ensure the switch port provides 802.3af PoE (15.4W minimum).
4
Verify the MXA920 boots and obtains a DHCP address.
5
Access Shure Designer software to configure. Download from https://www.shure.com/en-US/products/software/designer. The MXA920 default IP will appear in Shure Designer device discovery.
Note

The MXA920 uses Automatic Coverage technology and generally requires minimal configuration out of the box. However, use Shure Designer to verify coverage zones include all seating positions at the deposition table. If the room has significant reverberation, enable IntelliMix noise reduction on the MXA920. A Shure ANIUSB-MATRIX may be needed to bridge Dante audio to USB for the recording workstation if the workstation doesn't have a Dante Virtual Soundcard license.

Step 3: Deposition Room Physical Setup — Video Bar and Workstation

Mount the Jabra PanaCast 50 video bar at the end of the deposition table (or on a wall mount at table height) facing the deposition participants. Connect via USB-C to the recording workstation. Position the workstation, monitor, UPS, and EPOS Capture 5 backup microphone. The workstation monitor should be positioned so the paralegal can see recording status without being obtrusive to the deposition.

1
Mount Jabra PanaCast 50 on table stand or wall mount at seated eye level
2
Connect PanaCast 50 to ThinkCentre via USB-C cable (included)
3
Place EPOS EXPAND Capture 5 at center of table as backup mic
4
Connect EPOS to ThinkCentre via USB-C cable
5
Connect ThinkCentre to Dell monitor via USB-C
6
Connect ThinkCentre and network switch to APC UPS
7
Connect ThinkCentre to VLAN 50 network port via Ethernet
8
Power on and verify Windows 11 Pro boot
Note

Cable management is important — deposition rooms must look professional. Use cable raceways or in-wall conduit. The EPOS Capture 5 serves as both a backup microphone and a speaker for playing back recordings during the deposition if needed. Verify the UPS is providing battery backup (not just surge protection) by disconnecting AC power briefly and confirming the workstation stays running.

Step 4: Windows 11 Pro Workstation Configuration and Hardening

Configure the recording workstation with appropriate security hardening, domain join (or Entra ID join), BitLocker encryption, and local user accounts. The workstation must be encrypted at rest because it temporarily stores deposition recordings containing privileged attorney-client communications.

1
Join to Azure AD / Entra ID: Settings > Accounts > Access work or school > Connect > Join this device to Azure Active Directory
Enable BitLocker and back up recovery key to Azure AD
powershell
# Enable BitLocker via PowerShell (run as Administrator)
Enable-BitLocker -MountPoint "C:" -EncryptionMethod XtsAes256 -UsedSpaceOnly -RecoveryPasswordProtector

# Store BitLocker recovery key to Azure AD
BackupToAAD-BitLockerKeyProtector -MountPoint "C:" -KeyProtectorId (Get-BitLockerVolume -MountPoint "C:").KeyProtector[0].KeyProtectorId
Disable unnecessary Windows services
powershell
# Disable unnecessary services
Set-Service -Name "WSearch" -StartupType Disabled  # Windows Search indexer not needed
Set-Service -Name "MapsBroker" -StartupType Disabled
Configure Windows Firewall outbound rules
powershell
# Configure Windows Firewall - allow only required outbound
New-NetFirewallRule -DisplayName "Allow Deepgram API" -Direction Outbound -RemoteAddress Any -RemotePort 443 -Protocol TCP -Action Allow
New-NetFirewallRule -DisplayName "Allow SMB to NAS" -Direction Outbound -RemoteAddress 10.10.50.0/24 -RemotePort 445 -Protocol TCP -Action Allow
Set High Performance power plan to prevent sleep during recording
powershell
# Set power plan to High Performance (prevent sleep during recording)
powercfg /setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
1
Disable automatic Windows Updates during business hours: Configure via Intune policy: Update Ring > Active Hours 7AM–8PM
Create local deposition recording directory structure
powershell
# Create local recording directory structure
New-Item -Path "C:\DepositionRecordings" -ItemType Directory
New-Item -Path "C:\DepositionRecordings\Pending" -ItemType Directory
New-Item -Path "C:\DepositionRecordings\Processed" -ItemType Directory
New-Item -Path "C:\DepositionRecordings\Archive" -ItemType Directory
Note

BitLocker is MANDATORY — unencrypted deposition recordings on a stolen workstation would be a catastrophic data breach involving privileged material. Verify BitLocker recovery keys are backed up to Azure AD before proceeding. Configure Intune compliance policy to block access if BitLocker is disabled. Set active hours broadly to prevent Windows Update reboots during depositions.

Step 5: Install and Configure Shure ANIUSB-MATRIX Audio Bridge

Install the Shure ANIUSB-MATRIX to bridge Dante audio from the MXA920 ceiling microphone to USB audio input on the recording workstation. This device converts the networked Dante audio stream into a standard USB audio device that OBS Studio and other recording applications can use directly.

1
Connect ANIUSB-MATRIX to the same VLAN 50 network switch as the MXA920
2
Connect ANIUSB-MATRIX to ThinkCentre workstation via USB
3
Download and install Shure Designer on the workstation: https://www.shure.com/en-US/products/software/designer
4
Open Shure Designer > Device Discovery — Both MXA920 and ANIUSB-MATRIX should appear
5
Create an audio route: MXA920 Output → ANIUSB-MATRIX Input
6
In Windows Sound Settings, verify 'Shure ANIUSB-MATRIX' appears as a recording device
7
Set sample rate to 48kHz, 24-bit in device properties
Verify Shure ANIUSB-MATRIX appears as an audio endpoint in Windows
powershell
Get-PnpDevice -Class AudioEndpoint | Where-Object {$_.FriendlyName -like '*Shure*'}
Note

If the firm's budget does not accommodate the ANIUSB-MATRIX (~$800), an alternative is to install Dante Virtual Soundcard on the workstation ($49.99 license from Audinate). However, the ANIUSB-MATRIX is more reliable and does not require driver management. The ANIUSB-MATRIX also provides a hardware automixer, which is beneficial for multi-speaker deposition scenarios.

Step 6: Install and Configure OBS Studio for Deposition Recording

Install OBS Studio as the primary recording application. Configure it with two audio sources (Shure ceiling mic as primary, EPOS Capture 5 as backup) and one video source (Jabra PanaCast 50). Create recording profiles optimized for deposition capture with appropriate file naming conventions and automatic file splitting.

Download and install OBS Studio (latest stable)
shell
winget install OBSProject.OBSStudio

Install obs-websocket plugin (for automation) — included in OBS 28+. Verify by navigating to: Tools > WebSocket Server Settings > Enable WebSocket Server. Set port: 4455 and set a strong authentication password.

1
Settings > Output > Recording: set Type to Standard, Recording Path to C:\DepositionRecordings\Pending, Recording Format to mkv (remux to mp4 after recording), Audio Encoder to FLAC (lossless), Video Encoder to x264 (CRF 23), Audio Track 1 to Shure MXA920 (primary), Audio Track 2 to EPOS Capture 5 (backup)
2
Settings > Audio: set Sample Rate to 48kHz, Channels to Stereo, Mic/Auxiliary Audio to Shure ANIUSB-MATRIX, Mic/Auxiliary Audio 2 to EPOS EXPAND Capture 5
3
Settings > Video: set Base Resolution to 1920x1080, Output Resolution to 1920x1080, FPS to 30
4
Sources panel: Add Video Capture Device 'Jabra PanaCast 50', Add Audio Input Capture 'Shure ANIUSB-MATRIX', Add Audio Input Capture 'EPOS EXPAND Capture 5'
5
Settings > Advanced: set Filename Formatting to %CCYY-%MM-%DD_Deposition_%hh-%mm-%ss, enable Automatically Remux to mp4
6
Create a profile named 'Deposition' and a scene named 'Deposition Room'
Note

MKV container is used during recording because it is resilient to corruption if the recording is interrupted (e.g., power failure). OBS automatically remuxes to MP4 after recording stops. FLAC audio encoding is lossless, which is critical for legal recordings where audio quality may be scrutinized. The dual audio track setup ensures that if the ceiling mic has an issue, the tabletop mic recording is preserved independently.

Step 7: Configure Synology NAS for Deposition Archive Storage

Set up the Synology DS1621+ NAS with RAID 5, create shared folders for deposition recordings with appropriate access controls, enable encryption, and configure automated backup to Azure Blob Storage via Hyper Backup.

1
Install 6x WD Red Plus 4TB drives in bays 1-6
2
Access Synology DSM at http://find.synology.com or https://<NAS-IP>:5001
3
Initial Setup Wizard: Create admin account with strong password
4
Storage Manager > Create Storage Pool — RAID Type: RAID 5 (5+1 parity, ~20TB usable), Drives: All 6x WD40EFPX, Drive Check: Enable
5
Create Volume on Storage Pool (Btrfs filesystem for snapshots)
6
Create Shared Folders via Control Panel > Shared Folder > Create: Name: DepositionRecordings (Encryption: Enable AES-256, set encryption key, Enable data checksum for advanced data integrity), Name: Transcripts (Encryption: Enable AES-256), Name: AISummaries (Encryption: Enable AES-256)
7
Create user accounts: depo-workstation-01 (service account for workstation auto-upload), depo-workstation-02, depo-admin (MSP admin account), attorney-readonly (for attorney access to summaries)
8
Set permissions: depo-workstation-*: Read/Write on DepositionRecordings, depo-admin: Read/Write on all folders, attorney-readonly: Read-only on Transcripts, AISummaries
9
Enable SMB service and map network drive on workstations
Map DepositionRecordings shared folder as network drive Z: on workstations
batch
net use Z: \\<NAS-IP>\DepositionRecordings /user:depo-workstation-01 /persistent:yes
1
Install Hyper Backup from Package Center
2
Configure Hyper Backup task: Destination: Microsoft Azure Blob Storage, Container: deposition-archive-<firmname>, Folders: DepositionRecordings, Transcripts, AISummaries, Schedule: Daily at 2:00 AM, Encryption: Enable client-side encryption, Retention: Keep all versions for 7 years, Bandwidth limit: 50% of upload bandwidth during business hours
Note

Encryption keys for the shared folders must be documented and stored securely — if lost, data is unrecoverable. Store keys in the MSP's password manager (e.g., IT Glue, Hudu) AND provide the client's managing partner with a sealed envelope containing the keys for safe deposit. RAID 5 provides single-drive fault tolerance; monitor drive health via Synology's Storage Manager alerts and DSM email notifications. Configure S.M.A.R.T. extended tests monthly.

Step 8: Set Up Deepgram API Account and Test Transcription

Create a Deepgram account, obtain API keys, configure the Nova-3 model with legal-specific settings, and validate transcription accuracy with a test recording. Deepgram will be the primary speech-to-text engine that converts deposition audio into text with speaker diarization.

1
Create Deepgram account at https://console.deepgram.com/signup
2
Create a new Project: '<FirmName>-Depositions'
3
Generate API Key: Settings > API Keys > Create Key — Key Name: deposition-capture-prod, Permissions: Member (transcription only, no admin)
4
Store API key securely in environment variable on workstation:
5
Test transcription with a sample audio file:
6
Verify the JSON response contains speaker labels (speaker: 0, speaker: 1, etc.) and accurate text with punctuation
Store Deepgram API key as a machine-level environment variable
powershell
[System.Environment]::SetEnvironmentVariable('DEEPGRAM_API_KEY', 'your-api-key-here', 'Machine')
Test transcription API call using Nova-3 model with diarization and smart formatting
bash
curl -X POST "https://api.deepgram.com/v1/listen?model=nova-3&smart_format=true&diarize=true&punctuate=true&paragraphs=true&utterances=true&language=en-US" -H "Authorization: Token $env:DEEPGRAM_API_KEY" -H "Content-Type: audio/wav" --data-binary @C:\DepositionRecordings\test_sample.wav -o C:\DepositionRecordings\test_transcript.json
  • model=nova-3 — highest accuracy model
  • diarize=true — speaker identification
  • smart_format=true — formats numbers, dates, currency
  • punctuate=true — adds punctuation
  • paragraphs=true — groups into paragraphs
  • utterances=true — returns speaker-attributed utterances
  • keywords=deposition:2,objection:2,stipulate:2,exhibit:2 — boost legal terms
  • numerals=true — converts spoken numbers to digits
Note

Deepgram provides $200 in free credits on signup — sufficient for approximately 775 hours of pre-recorded transcription at $0.0043/min. Use this credit for all testing and initial deployments. The 'keywords' parameter boosts recognition accuracy for specified legal terms — add firm-specific terms (case names, party names, expert names) for each deposition. Store the API key in Azure Key Vault in production rather than environment variables for better security.

Step 9: Set Up CaseMark Account and Configure White-Label Portal

Create a CaseMark Professional account, configure the white-label branding (MSP's or firm's branding), set up the deposition summary templates, and test the summary generation pipeline with a sample transcript.

1
Sign up at https://www.casemark.ai
2
Contact CaseMark sales for White-Label Professional tier
3
Configure branding in CaseMark Admin Portal: Upload MSP logo (or firm logo if co-branded), Set brand colors (primary, secondary), Configure custom domain (e.g., summaries.yourmsp.com) via CNAME, Customize PDF header/footer templates
4
Configure Deposition Summary Template: Template Type: Deposition Summary. Output Sections: a. Case Caption and Deponent Information, b. Key Admissions (with page:line citations), c. Contradictions and Impeachment Material, d. Chronological Event Timeline, e. Exhibit References, f. Credibility Analysis Notes, g. Follow-up Questions / Areas for Further Discovery
5
Test with sample transcript: Upload a test deposition transcript (PDF or TXT), Select 'Deposition Summary' template, Review output for accuracy of page-line citations, Verify all sections are populated
6
Set up API access for automated pipeline: CaseMark API Key: Settings > Integrations > API, Store key securely alongside Deepgram key
Note

CaseMark's SOC 2 Type II certification and contractual commitment not to train on client data are critical selling points for law firm clients. During the white-label setup, ensure the custom domain SSL certificate is properly configured. CaseMark's first summary is free for evaluation — use this to demonstrate value to the firm's attorneys before going live. The per-summary pricing ($25 base) provides clear margin when resold at $45–$60.

Step 10: Build the Automated Deposition Processing Pipeline

Create the Python-based automation pipeline that watches for new recordings, extracts audio, sends to Deepgram for transcription, formats the transcript, sends to CaseMark (or Claude API) for summarization, and files results to the NAS and practice management system. This is the core integration logic that ties all components together.

1
Install Python 3.11+ on the recording workstation (or a dedicated processing server)
2
Create project directory and virtual environment
3
Install required packages
4
Create .env file with API keys (encrypted at rest via BitLocker)
5
Deploy the pipeline script (see custom_ai_components for full code)
6
Install as Windows Service for auto-start
7
Verify service is running
Step 1: Install Python 3.11+
powershell
winget install Python.Python.3.11
Step 2: Create project directory and virtual environment
powershell
mkdir C:\DepositionPipeline
cd C:\DepositionPipeline
python -m venv venv
.\venv\Scripts\Activate.ps1
Step 3: Install required packages
powershell
pip install deepgram-sdk anthropic requests watchdog python-docx schedule
pip install azure-storage-blob python-dotenv
Step 4: .env file with API keys (encrypted at rest via BitLocker)
dotenv
DEEPGRAM_API_KEY=your_key
CASEMARK_API_KEY=your_key
ANTHROPIC_API_KEY=your_key
CLIO_API_TOKEN=your_token
AZURE_STORAGE_CONNECTION_STRING=your_connection
NAS_SHARE_PATH=\\nas-ip\DepositionRecordings
  • Main entry point: deposition_pipeline.py
  • transcriber.py — Deepgram integration
  • summarizer.py — CaseMark/Claude integration
  • pms_integration.py — Clio API integration
  • file_watcher.py — monitors recording directory
Step 6: Install as Windows Service for auto-start
powershell
pip install pywin32
python deposition_service.py install
python deposition_service.py start
Step 7: Verify service is running
powershell
Get-Service DepositionPipeline
Note

The pipeline runs as a Windows Service so it starts automatically on boot and runs in the background. It monitors C:\DepositionRecordings\Pending for new files. When a recording is completed (file is no longer being written to), it initiates the transcription→summarization→filing workflow automatically. All processing is logged to C:\DepositionPipeline\logs for troubleshooting. See the custom_ai_components section for complete source code.

Step 11: Configure Practice Management System Integration (Clio)

Set up the Clio API integration to automatically file completed deposition transcripts and AI summaries to the correct matter in Clio Manage. This requires creating a Clio API application, configuring OAuth2 authentication, and mapping the pipeline output to Clio's document management structure.

1
Register a Clio API application: Go to https://app.clio.com/api/applications and create a new application with Name: Deposition AI Pipeline, Redirect URI: http://localhost:8080/callback, Scopes: documents:write, matters:read, contacts:read
2
Complete OAuth2 flow to obtain refresh token — in browser, navigate to the authorization URL below
3
Exchange authorization code for tokens using the curl command below
4
Store refresh token in .env file (see values below)
5
Test API connectivity using the curl command below
6
Create a 'Deposition Summaries' document category in Clio: Clio > Settings > Document Categories > Add: AI Deposition Summary, AI Deposition Transcript, Deposition Recording
OAuth2 authorization URL
text
# open in browser to begin authorization flow

https://app.clio.com/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:8080/callback&scope=documents:write%20matters:read%20contacts:read
Exchange authorization code for access and refresh tokens
bash
curl -X POST https://app.clio.com/oauth/token -d "grant_type=authorization_code&code=AUTH_CODE&client_id=YOUR_CLIENT_ID&client_secret=YOUR_SECRET&redirect_uri=http://localhost:8080/callback"
Refresh token values to store in .env file
bash
# .env entries
CLIO_CLIENT_ID=your_id
CLIO_CLIENT_SECRET=your_secret
CLIO_REFRESH_TOKEN=your_refresh_token
Test API connectivity — returns up to 5 matters from Clio
bash
curl -H "Authorization: Bearer ACCESS_TOKEN" https://app.clio.com/api/v4/matters.json?limit=5
Note

If the firm uses Smokeball instead of Clio, CaseMark has a direct embedded integration — no custom API work needed. For PracticePanther or MyCase, use Zapier as a middleware layer (Zapier webhook trigger → PMS document upload action). Clio's OAuth tokens expire after 24 hours, so the pipeline must handle token refresh automatically using the refresh token. See the pms_integration.py component in custom_ai_components.

Implement the compliance workflow for deposition recording consent. This includes on-screen consent capture at the start of each recording session, generation of a consent log, and integration with the firm's existing deposition notice procedures. This step is critical for two-party consent state compliance.

1
Create a consent disclosure form template (Word/PDF). Store at C:\DepositionPipeline\templates\consent_disclosure.docx. Template includes: Date and case caption; Statement that proceedings will be audio/video recorded; Statement that AI transcription and summarization will be used; Identification of recording technology vendor (MSP name); Signature lines for all parties and counsel; Reference to deposition notice method of recording specification.
2
Create pre-recording checklist PowerShell script (see custom_ai_components for DepositionSessionManager). Script displays consent checklist on workstation monitor.
3
The checklist must be completed before OBS recording can be started via the automation pipeline.
Critical

THIS IS THE MOST LEGALLY SENSITIVE STEP. The MSP provides the technology and templates, but the firm's attorneys must approve all consent language and take responsibility for compliance with applicable recording consent laws. In two-party consent states (CA, FL, IL, MA, PA, WA, and others), ALL parties must consent to the recording. The consent should be captured on the record (stated by the noticing attorney at the start of the deposition) AND documented in writing. The MSP should not provide legal advice — frame this as 'here is what other firms typically do' and defer to the firm's ethics counsel.

Step 13: Configure Monitoring, Alerting, and Logging

Set up comprehensive monitoring for the entire deposition capture pipeline including recording workstation health, NAS storage capacity, API availability, and pipeline processing status. Configure alerts to the MSP's RMM/PSA system and to the firm's designated coordinator.

1
Install your RMM agent on recording workstations (Datto RMM, ConnectWise Automate, NinjaOne, etc.)
2
Create custom monitoring checks:
Check 1: OBS Studio process running
powershell
$obs = Get-Process obs64 -ErrorAction SilentlyContinue
if (-not $obs) { Write-Output 'ALERT: OBS Studio not running on deposition workstation'; exit 1 }
Check 2: Deepgram API reachability
powershell
$response = Invoke-WebRequest -Uri 'https://api.deepgram.com/v1/listen' -Method OPTIONS -TimeoutSec 10 -ErrorAction SilentlyContinue
if ($response.StatusCode -ne 200 -and $response.StatusCode -ne 405) { Write-Output 'ALERT: Deepgram API unreachable'; exit 1 }
Check 3: NAS storage capacity (via Synology SNMP or DSM API)
powershell
$usage = (Get-PSDrive Z).Used / (Get-PSDrive Z).Free * 100
if ($usage -gt 80) { Write-Output "ALERT: NAS storage at $usage% capacity"; exit 1 }
Check 4: Pipeline service running
powershell
$svc = Get-Service DepositionPipeline -ErrorAction SilentlyContinue
if ($svc.Status -ne 'Running') { Write-Output 'ALERT: Deposition Pipeline service stopped'; exit 1 }
Check 5: Pending files older than 4 hours (stuck processing)
powershell
$stale = Get-ChildItem C:\DepositionRecordings\Pending -File | Where-Object { $_.LastWriteTime -lt (Get-Date).AddHours(-4) }
if ($stale) { Write-Output "ALERT: $($stale.Count) files stuck in Pending queue"; exit 1 }
1
Configure Synology DSM notifications: Control Panel > Notification > Email — SMTP: smtp.office365.com:587, Sender: nas-alerts@firmname.com, Recipients: msp-alerts@mspdomain.com and coordinator@firmname.com, Events: Drive failure, RAID degradation, Storage full warning
Note

Monitoring cadence: workstation health checks every 5 minutes, API reachability every 15 minutes, storage capacity daily, pipeline queue hourly. Create a dashboard in your RMM/PSA showing all deposition infrastructure at a glance. Set up escalation: Level 1 (pipeline service restart) → Level 2 (API/connectivity issues) → Level 3 (hardware failure, data loss risk). Configure PagerDuty or Opsgenie for after-hours alerts if the firm conducts depositions outside business hours.

Step 14: End-to-End System Testing with Mock Deposition

Conduct a full end-to-end test of the entire system using a mock deposition with firm staff. This validates every component from audio capture through final summary delivery to the practice management system. Record issues and tune the system before going live.

1
Schedule a 30-minute mock deposition with 3-4 participants. Include: attorney (questioning), witness role, defending attorney, paralegal.
2
Pre-deposition checklist: Verify consent workflow completes successfully. Confirm OBS is recording with proper audio levels. Verify both audio tracks (ceiling mic + tabletop) show signal. Confirm video capture is active.
3
During mock deposition: Test various speaking volumes and positions. Include legal terminology: 'objection', 'stipulate', 'exhibit'. Have speakers talk over each other briefly (crosstalk test). Reference exhibit numbers and specific dates. Include a clear 'admission' statement for summarization testing.
4
Post-recording validation: Stop OBS recording. Verify file appears in C:\DepositionRecordings\Pending. Monitor pipeline log. Verify Deepgram transcription completes (check Pending → Processed move). Verify speaker diarization correctly identifies different speakers. Verify CaseMark summary is generated with page-line citations. Verify summary is filed to test matter in Clio. Verify recording is archived to NAS. Verify Hyper Backup replicates to Azure (next backup cycle).
5
Quality scoring: Transcription WER (have a paralegal compare transcript to what was said). Speaker attribution accuracy (% of utterances correctly attributed). Summary completeness (did it catch the test 'admission'?). Citation accuracy (do page-line refs match actual transcript?). End-to-end time (recording stop to summary available in PMS).
Monitor pipeline log during post-recording validation
shell
type C:\DepositionPipeline\logs\pipeline.log
Note

Run at least 3 mock depositions before going live: one in ideal conditions, one with deliberate challenges (low voice, background noise, crosstalk), and one simulating a remote/hybrid deposition via Teams. Acceptable thresholds: WER <8%, speaker attribution >90%, summary captures 100% of flagged admissions. If thresholds aren't met, adjust Deepgram keywords, MXA920 coverage zones, or room acoustics before proceeding.

Custom AI Components

Deposition Processing Pipeline

Type: workflow

The core automation workflow that orchestrates the entire deposition capture-to-summary pipeline. It watches for completed recordings, extracts audio, sends to Deepgram for transcription with speaker diarization, formats the transcript into a structured document, sends to CaseMark or Claude for summarization, and files results to the NAS and practice management system. Runs as a Windows Service.

Implementation:

deposition_pipeline.py
python
# Core pipeline orchestrator for deposition processing

# deposition_pipeline.py
# Core pipeline orchestrator for deposition processing

import os
import sys
import json
import time
import logging
import shutil
from pathlib import Path
from datetime import datetime
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from dotenv import load_dotenv

load_dotenv('C:\\DepositionPipeline\\.env')

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.FileHandler('C:\\DepositionPipeline\\logs\\pipeline.log'),
        logging.StreamHandler(sys.stdout)
    ]
)
logger = logging.getLogger('DepositionPipeline')

# Import sub-modules
from transcriber import DeepgramTranscriber
from summarizer import DepositionSummarizer
from pms_integration import ClioIntegration
from consent_manager import ConsentManager

# Configuration
WATCH_DIR = Path('C:/DepositionRecordings/Pending')
PROCESSED_DIR = Path('C:/DepositionRecordings/Processed')
ARCHIVE_DIR = Path('C:/DepositionRecordings/Archive')
NAS_PATH = Path(os.getenv('NAS_SHARE_PATH', 'Z:\\'))
STABILITY_WAIT = 30  # seconds to wait after file stops growing

class RecordingHandler(FileSystemEventHandler):
    """Watches for new recording files and triggers processing."""
    
    def __init__(self):
        self.transcriber = DeepgramTranscriber(api_key=os.getenv('DEEPGRAM_API_KEY'))
        self.summarizer = DepositionSummarizer(
            casemark_key=os.getenv('CASEMARK_API_KEY'),
            anthropic_key=os.getenv('ANTHROPIC_API_KEY')
        )
        self.clio = ClioIntegration(
            client_id=os.getenv('CLIO_CLIENT_ID'),
            client_secret=os.getenv('CLIO_CLIENT_SECRET'),
            refresh_token=os.getenv('CLIO_REFRESH_TOKEN')
        )
        self.processing = set()
    
    def on_created(self, event):
        if event.is_directory:
            return
        filepath = Path(event.src_path)
        if filepath.suffix.lower() in ['.mp4', '.mkv', '.wav', '.mp3', '.flac']:
            logger.info(f'New recording detected: {filepath.name}')
            self._wait_for_stable(filepath)
            if filepath.name not in self.processing:
                self.processing.add(filepath.name)
                try:
                    self._process_recording(filepath)
                except Exception as e:
                    logger.error(f'Pipeline error for {filepath.name}: {e}', exc_info=True)
                finally:
                    self.processing.discard(filepath.name)
    
    def _wait_for_stable(self, filepath, timeout=600):
        """Wait until file size stops changing (recording finished)."""
        logger.info(f'Waiting for {filepath.name} to stabilize...')
        prev_size = -1
        stable_count = 0
        elapsed = 0
        while elapsed < timeout:
            try:
                current_size = filepath.stat().st_size
            except FileNotFoundError:
                return
            if current_size == prev_size:
                stable_count += 1
                if stable_count >= 3:  # stable for 3 checks (30 sec)
                    logger.info(f'{filepath.name} is stable at {current_size / 1e6:.1f} MB')
                    return
            else:
                stable_count = 0
            prev_size = current_size
            time.sleep(10)
            elapsed += 10
        logger.warning(f'{filepath.name} did not stabilize within {timeout}s, processing anyway')
    
    def _process_recording(self, filepath):
        """Full pipeline: transcribe → summarize → file."""
        session_id = datetime.now().strftime('%Y%m%d_%H%M%S')
        logger.info(f'=== Processing {filepath.name} | Session: {session_id} ===')
        
        # Step 1: Transcribe with Deepgram
        logger.info('Step 1: Sending to Deepgram for transcription...')
        transcript_result = self.transcriber.transcribe(
            audio_path=str(filepath),
            options={
                'model': 'nova-3',
                'smart_format': True,
                'diarize': True,
                'punctuate': True,
                'paragraphs': True,
                'utterances': True,
                'language': 'en-US',
                'keywords': ['objection:2', 'stipulate:2', 'exhibit:2',
                             'deposition:2', 'counsel:2', 'testimony:2']
            }
        )
        
        # Step 2: Format transcript with speaker labels
        logger.info('Step 2: Formatting transcript with speaker labels...')
        formatted_transcript = self.transcriber.format_transcript(transcript_result)
        
        # Save raw transcript
        transcript_path = PROCESSED_DIR / f'{session_id}_transcript.json'
        with open(transcript_path, 'w') as f:
            json.dump(transcript_result, f, indent=2)
        
        formatted_path = PROCESSED_DIR / f'{session_id}_transcript.txt'
        with open(formatted_path, 'w') as f:
            f.write(formatted_transcript)
        
        logger.info(f'Transcript saved: {formatted_path}')
        
        # Step 3: Generate AI Summary
        logger.info('Step 3: Generating AI deposition summary...')
        summary = self.summarizer.generate_summary(
            transcript_text=formatted_transcript,
            session_id=session_id
        )
        
        summary_path = PROCESSED_DIR / f'{session_id}_summary.docx'
        self.summarizer.save_as_docx(summary, summary_path)
        logger.info(f'Summary saved: {summary_path}')
        
        # Step 4: Archive to NAS
        logger.info('Step 4: Archiving to NAS...')
        nas_session_dir = NAS_PATH / 'DepositionRecordings' / session_id
        nas_session_dir.mkdir(parents=True, exist_ok=True)
        shutil.copy2(filepath, nas_session_dir / filepath.name)
        shutil.copy2(formatted_path, NAS_PATH / 'Transcripts' / formatted_path.name)
        shutil.copy2(summary_path, NAS_PATH / 'AISummaries' / summary_path.name)
        logger.info('Files archived to NAS')
        
        # Step 5: Upload to Practice Management System
        logger.info('Step 5: Filing to Clio...')
        # Matter ID should be set in the session metadata file
        metadata_path = WATCH_DIR / f'{filepath.stem}_metadata.json'
        matter_id = None
        if metadata_path.exists():
            with open(metadata_path) as f:
                metadata = json.load(f)
                matter_id = metadata.get('clio_matter_id')
        
        if matter_id:
            self.clio.upload_document(
                matter_id=matter_id,
                file_path=str(summary_path),
                name=f'AI Deposition Summary - {session_id}',
                category='AI Deposition Summary'
            )
            self.clio.upload_document(
                matter_id=matter_id,
                file_path=str(formatted_path),
                name=f'AI Deposition Transcript - {session_id}',
                category='AI Deposition Transcript'
            )
            logger.info(f'Documents filed to Clio matter {matter_id}')
        else:
            logger.warning('No matter_id found in metadata — skipping Clio upload')
        
        # Step 6: Move original to archive
        archive_path = ARCHIVE_DIR / filepath.name
        shutil.move(str(filepath), str(archive_path))
        if metadata_path.exists():
            shutil.move(str(metadata_path), str(ARCHIVE_DIR / metadata_path.name))
        
        logger.info(f'=== Pipeline complete for {filepath.name} | Session: {session_id} ===')

def main():
    # Ensure directories exist
    for d in [WATCH_DIR, PROCESSED_DIR, ARCHIVE_DIR]:
        d.mkdir(parents=True, exist_ok=True)
    Path('C:/DepositionPipeline/logs').mkdir(parents=True, exist_ok=True)
    
    logger.info('Deposition Processing Pipeline starting...')
    logger.info(f'Watching directory: {WATCH_DIR}')
    
    handler = RecordingHandler()
    observer = Observer()
    observer.schedule(handler, str(WATCH_DIR), recursive=False)
    observer.start()
    
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()
    logger.info('Pipeline stopped.')

if __name__ == '__main__':
    main()

Deepgram Transcription Module

Type: integration Handles communication with the Deepgram Nova-3 API for speech-to-text transcription with speaker diarization. Includes retry logic, error handling, and transcript formatting with speaker labels and page-line numbering suitable for legal citation.

Implementation:

transcriber.py
python
# Deepgram Nova-3 integration for deposition transcription

# transcriber.py
# Deepgram Nova-3 integration for deposition transcription

import json
import logging
import time
import math
from pathlib import Path
from deepgram import DeepgramClient, PrerecordedOptions, FileSource

logger = logging.getLogger('DepositionPipeline.Transcriber')

LINES_PER_PAGE = 25  # Standard legal transcript page format
CHARS_PER_LINE = 70  # Approximate characters per line

class DeepgramTranscriber:
    def __init__(self, api_key: str):
        self.client = DeepgramClient(api_key)
    
    def transcribe(self, audio_path: str, options: dict, max_retries: int = 3) -> dict:
        """Transcribe audio file using Deepgram Nova-3 with retry logic."""
        logger.info(f'Transcribing: {audio_path}')
        file_size = Path(audio_path).stat().st_size / 1e6
        logger.info(f'File size: {file_size:.1f} MB')
        
        with open(audio_path, 'rb') as audio_file:
            buffer_data = audio_file.read()
        
        payload: FileSource = {'buffer': buffer_data}
        
        dg_options = PrerecordedOptions(
            model=options.get('model', 'nova-3'),
            smart_format=options.get('smart_format', True),
            diarize=options.get('diarize', True),
            punctuate=options.get('punctuate', True),
            paragraphs=options.get('paragraphs', True),
            utterances=options.get('utterances', True),
            language=options.get('language', 'en-US'),
            keywords=options.get('keywords', [])
        )
        
        for attempt in range(max_retries):
            try:
                response = self.client.listen.rest.v('1').transcribe_file(
                    payload, dg_options
                )
                result = response.to_dict() if hasattr(response, 'to_dict') else json.loads(str(response))
                logger.info(f'Transcription complete. Words: {self._count_words(result)}')
                return result
            except Exception as e:
                logger.warning(f'Transcription attempt {attempt+1} failed: {e}')
                if attempt < max_retries - 1:
                    wait_time = 2 ** attempt * 5
                    logger.info(f'Retrying in {wait_time}s...')
                    time.sleep(wait_time)
                else:
                    logger.error('All transcription attempts failed')
                    raise
    
    def _count_words(self, result: dict) -> int:
        try:
            return len(result['results']['channels'][0]['alternatives'][0]['words'])
        except (KeyError, IndexError):
            return 0
    
    def format_transcript(self, result: dict) -> str:
        """Format Deepgram output into legal transcript format with page:line numbering."""
        utterances = result.get('results', {}).get('utterances', [])
        if not utterances:
            # Fallback to paragraphs if utterances not available
            return self._format_from_paragraphs(result)
        
        # Build speaker map
        speaker_map = {}
        speaker_counter = 1
        for utt in utterances:
            speaker_id = utt.get('speaker', 0)
            if speaker_id not in speaker_map:
                speaker_map[speaker_id] = f'SPEAKER {speaker_counter}'
                speaker_counter += 1
        
        # Format with page:line numbers
        lines = []
        current_line = 1
        current_page = 1
        
        # Header
        lines.append(f'PAGE {current_page}')
        lines.append('=' * 70)
        lines.append('')
        current_line = 1
        
        for utt in utterances:
            speaker = speaker_map.get(utt.get('speaker', 0), 'UNKNOWN')
            text = utt.get('transcript', '').strip()
            timestamp = utt.get('start', 0)
            
            # Format timestamp
            mins = int(timestamp // 60)
            secs = int(timestamp % 60)
            ts_str = f'[{mins:02d}:{secs:02d}]'
            
            # Speaker line
            speaker_line = f'{current_page}:{current_line:02d}  {ts_str}  {speaker}:'
            lines.append(speaker_line)
            current_line += 1
            
            # Wrap text to ~70 chars per line
            words = text.split()
            current_text_line = ''
            for word in words:
                if len(current_text_line) + len(word) + 1 > CHARS_PER_LINE:
                    lines.append(f'{current_page}:{current_line:02d}          {current_text_line}')
                    current_line += 1
                    current_text_line = word
                else:
                    current_text_line = f'{current_text_line} {word}'.strip()
                
                # Page break
                if current_line > LINES_PER_PAGE:
                    current_page += 1
                    current_line = 1
                    lines.append('')
                    lines.append(f'PAGE {current_page}')
                    lines.append('=' * 70)
                    lines.append('')
            
            if current_text_line:
                lines.append(f'{current_page}:{current_line:02d}          {current_text_line}')
                current_line += 1
            
            lines.append('')  # Blank line between utterances
            current_line += 1
            
            if current_line > LINES_PER_PAGE:
                current_page += 1
                current_line = 1
                lines.append(f'PAGE {current_page}')
                lines.append('=' * 70)
                lines.append('')
        
        # Footer
        lines.append('')
        lines.append(f'--- END OF TRANSCRIPT ---')
        lines.append(f'Total Pages: {current_page}')
        lines.append(f'Speaker Map: {json.dumps(speaker_map)}')
        lines.append(f'Note: Speaker identities should be verified by reviewing attorney.')
        lines.append(f'      This is an AI-generated transcript, not a certified court reporter transcript.')
        
        return '\n'.join(lines)
    
    def _format_from_paragraphs(self, result: dict) -> str:
        """Fallback formatting from paragraph-level results."""
        try:
            paragraphs = result['results']['channels'][0]['alternatives'][0]['paragraphs']['paragraphs']
            lines = []
            page = 1
            line = 1
            for para in paragraphs:
                speaker = para.get('speaker', 0)
                for sent in para.get('sentences', []):
                    text = sent.get('text', '')
                    lines.append(f'{page}:{line:02d}  SPEAKER {speaker + 1}: {text}')
                    line += 1
                    if line > LINES_PER_PAGE:
                        page += 1
                        line = 1
                lines.append('')
                line += 1
            return '\n'.join(lines)
        except (KeyError, IndexError) as e:
            logger.error(f'Failed to format transcript: {e}')
            # Last resort: return raw text
            return result.get('results', {}).get('channels', [{}])[0].get('alternatives', [{}])[0].get('transcript', 'TRANSCRIPTION ERROR')

Deposition Summarization Module

Type: skill Generates structured deposition summaries using CaseMark API as the primary engine and Anthropic Claude as a fallback/custom engine. Produces summaries with key admissions, contradictions, chronological timelines, exhibit references, and page-line citations in DOCX format.

Implementation

summarizer.py
python
# Deposition summary generation using CaseMark and Claude

# summarizer.py
# Deposition summary generation using CaseMark and Claude

import json
import logging
import requests
from pathlib import Path
from datetime import datetime
from docx import Document
from docx.shared import Pt, Inches
from docx.enum.text import WD_ALIGN_PARAGRAPH

logger = logging.getLogger('DepositionPipeline.Summarizer')

# Claude prompt template for deposition summarization
DEPOSITION_SUMMARY_PROMPT = """You are a senior litigation paralegal with 20 years of experience summarizing depositions. You are analyzing a deposition transcript that was generated by AI speech-to-text (with possible minor transcription errors). Your task is to produce a comprehensive, structured deposition summary.

IMPORTANT RULES:
1. Every factual assertion must include a page:line citation in the format (Page:Line)
2. Use exact quotes for key admissions — surround with quotation marks
3. Flag any statements that contradict other testimony, prior pleadings, or documentary evidence
4. Note any objections made and whether they were sustained
5. Identify all exhibits referenced and what they were used for
6. Flag credibility issues: evasive answers, memory failures, contradictions
7. Do NOT fabricate citations — if you cannot identify the exact page:line, use [approximate location]

Produce the summary in the following sections:

## 1. DEPOSITION OVERVIEW
- Deponent name (as identified in transcript)
- Date of deposition
- Duration (approximate from timestamps)
- Attorneys present (as identified)
- Key topics covered

## 2. KEY ADMISSIONS
List each significant admission by the deponent, with:
- The exact quote
- Page:line citation
- Why this admission is significant for the case
- Suggested follow-up or impeachment use

## 3. CONTRADICTIONS & IMPEACHMENT MATERIAL
List any statements that:
- Contradict other statements within this deposition
- Appear to contradict common knowledge or documentary evidence
- Show evasiveness or memory issues
Include page:line citations for each.

## 4. CHRONOLOGICAL TIMELINE
Extract all dates, times, and events mentioned, in chronological order:
| Date/Time | Event | Speaker | Citation |

## 5. EXHIBIT REFERENCES
List all exhibits mentioned:
| Exhibit # | Description | How Used | Citation |

## 6. OBJECTIONS LOG
List all objections:
| Objection | Basis | Ruling | Citation |

## 7. CREDIBILITY ASSESSMENT NOTES
- Overall cooperation level
- Areas of evasiveness
- Memory/recall issues
- Demeanor notes (if discernible from transcript)

## 8. RECOMMENDED FOLLOW-UP
- Areas requiring further discovery
- Suggested interrogatories or document requests
- Potential motion topics (e.g., motion to compel if witness refused to answer)

---

Here is the deposition transcript:

{transcript}

---

Produce the structured summary now. Be thorough and cite every assertion."""


class DepositionSummarizer:
    def __init__(self, casemark_key: str = None, anthropic_key: str = None):
        self.casemark_key = casemark_key
        self.anthropic_key = anthropic_key
    
    def generate_summary(self, transcript_text: str, session_id: str) -> str:
        """Generate summary using CaseMark (primary) or Claude (fallback)."""
        # Try CaseMark first
        if self.casemark_key:
            try:
                logger.info('Attempting summary via CaseMark API...')
                return self._summarize_casemark(transcript_text, session_id)
            except Exception as e:
                logger.warning(f'CaseMark failed, falling back to Claude: {e}')
        
        # Fallback to Claude
        if self.anthropic_key:
            logger.info('Generating summary via Claude API...')
            return self._summarize_claude(transcript_text)
        
        raise RuntimeError('No summarization API available (both CaseMark and Claude failed/unconfigured)')
    
    def _summarize_casemark(self, transcript_text: str, session_id: str) -> str:
        """Submit transcript to CaseMark API for summarization."""
        # CaseMark API endpoint (verify current endpoint with CaseMark docs)
        url = 'https://api.casemark.ai/v1/summaries'
        headers = {
            'Authorization': f'Bearer {self.casemark_key}',
            'Content-Type': 'application/json'
        }
        payload = {
            'document_type': 'deposition_transcript',
            'content': transcript_text,
            'template': 'deposition_summary',
            'options': {
                'include_key_admissions': True,
                'include_contradictions': True,
                'include_chronology': True,
                'include_exhibit_references': True,
                'include_credibility_notes': True,
                'citation_format': 'page_line'
            },
            'reference_id': session_id
        }
        
        response = requests.post(url, json=payload, headers=headers, timeout=300)
        response.raise_for_status()
        
        result = response.json()
        summary_text = result.get('summary', '')
        
        if not summary_text:
            # CaseMark may use async processing — poll for result
            job_id = result.get('job_id')
            if job_id:
                summary_text = self._poll_casemark(job_id)
        
        logger.info(f'CaseMark summary generated: {len(summary_text)} chars')
        return summary_text
    
    def _poll_casemark(self, job_id: str, timeout: int = 600, interval: int = 10) -> str:
        """Poll CaseMark for async job completion."""
        import time
        url = f'https://api.casemark.ai/v1/summaries/{job_id}'
        headers = {'Authorization': f'Bearer {self.casemark_key}'}
        
        elapsed = 0
        while elapsed < timeout:
            response = requests.get(url, headers=headers, timeout=30)
            response.raise_for_status()
            result = response.json()
            
            if result.get('status') == 'completed':
                return result.get('summary', '')
            elif result.get('status') == 'failed':
                raise RuntimeError(f'CaseMark job failed: {result.get("error")}')
            
            time.sleep(interval)
            elapsed += interval
        
        raise TimeoutError(f'CaseMark job {job_id} timed out after {timeout}s')
    
    def _summarize_claude(self, transcript_text: str) -> str:
        """Generate summary using Anthropic Claude API."""
        import anthropic
        
        client = anthropic.Anthropic(api_key=self.anthropic_key)
        
        # Truncate very long transcripts to fit context window
        # Claude claude-sonnet-4-20250514 supports 200K tokens (~150K words)
        max_chars = 500000  # ~125K words, safe for Sonnet
        if len(transcript_text) > max_chars:
            logger.warning(f'Transcript truncated from {len(transcript_text)} to {max_chars} chars')
            transcript_text = transcript_text[:max_chars] + '\n\n[TRANSCRIPT TRUNCATED DUE TO LENGTH]'
        
        prompt = DEPOSITION_SUMMARY_PROMPT.format(transcript=transcript_text)
        
        message = client.messages.create(
            model='claude-sonnet-4-20250514',
            max_tokens=8192,
            messages=[
                {'role': 'user', 'content': prompt}
            ]
        )
        
        summary = message.content[0].text
        logger.info(f'Claude summary generated: {len(summary)} chars, {message.usage.input_tokens} input tokens, {message.usage.output_tokens} output tokens')
        return summary
    
    def save_as_docx(self, summary_text: str, output_path: Path):
        """Save summary as a formatted Word document."""
        doc = Document()
        
        # Title
        title = doc.add_heading('AI DEPOSITION SUMMARY', level=0)
        title.alignment = WD_ALIGN_PARAGRAPH.CENTER
        
        # Metadata
        doc.add_paragraph(f'Generated: {datetime.now().strftime("%B %d, %Y at %I:%M %p")}')
        doc.add_paragraph('CONFIDENTIAL — ATTORNEY WORK PRODUCT')
        doc.add_paragraph('')
        
        # Disclaimer
        disclaimer = doc.add_paragraph()
        disclaimer.style = doc.styles['Intense Quote']
        disclaimer.add_run(
            'NOTICE: This summary was generated by artificial intelligence and has not been '
            'verified by a human reviewer. Speaker identifications are based on AI voice '
            'diarization and may contain errors. Page:line citations reference the AI-generated '
            'transcript, not the certified court reporter transcript. This document is attorney '
            'work product and should be reviewed for accuracy before reliance.'
        )
        
        # Parse and format summary sections
        lines = summary_text.split('\n')
        for line in lines:
            line = line.rstrip()
            if line.startswith('## '):
                doc.add_heading(line[3:], level=2)
            elif line.startswith('### '):
                doc.add_heading(line[4:], level=3)
            elif line.startswith('| ') and '|' in line[1:]:
                # Simple table row — add as formatted paragraph
                p = doc.add_paragraph(line, style='List Bullet')
                p.paragraph_format.space_after = Pt(2)
            elif line.startswith('- '):
                doc.add_paragraph(line[2:], style='List Bullet')
            elif line.startswith('  - '):
                doc.add_paragraph(line[4:], style='List Bullet 2')
            elif line.strip():
                doc.add_paragraph(line)
        
        # Footer
        doc.add_paragraph('')
        footer = doc.add_paragraph('--- END OF AI SUMMARY ---')
        footer.alignment = WD_ALIGN_PARAGRAPH.CENTER
        
        doc.save(str(output_path))
        logger.info(f'DOCX saved: {output_path}')

Clio Practice Management Integration

Type: integration

Handles OAuth2 authentication with Clio's REST API and uploads deposition transcripts and summaries as documents to the correct matter. Includes automatic token refresh and matter lookup capabilities.

Implementation:

pms_integration.py
python
# Clio Manage API integration for document filing

# pms_integration.py
# Clio Manage API integration for document filing

import json
import logging
import requests
from pathlib import Path
from datetime import datetime, timedelta

logger = logging.getLogger('DepositionPipeline.ClioIntegration')

CLIO_BASE_URL = 'https://app.clio.com/api/v4'
CLIO_TOKEN_URL = 'https://app.clio.com/oauth/token'


class ClioIntegration:
    def __init__(self, client_id: str, client_secret: str, refresh_token: str):
        self.client_id = client_id
        self.client_secret = client_secret
        self.refresh_token = refresh_token
        self.access_token = None
        self.token_expiry = datetime.min
    
    def _ensure_token(self):
        """Refresh access token if expired or missing."""
        if self.access_token and datetime.now() < self.token_expiry:
            return
        
        logger.info('Refreshing Clio access token...')
        response = requests.post(CLIO_TOKEN_URL, data={
            'grant_type': 'refresh_token',
            'refresh_token': self.refresh_token,
            'client_id': self.client_id,
            'client_secret': self.client_secret
        })
        response.raise_for_status()
        
        token_data = response.json()
        self.access_token = token_data['access_token']
        self.refresh_token = token_data.get('refresh_token', self.refresh_token)
        # Clio tokens expire in ~24 hours; refresh 1 hour early
        self.token_expiry = datetime.now() + timedelta(hours=23)
        logger.info('Clio token refreshed successfully')
        
        # Persist updated refresh token
        self._save_refresh_token()
    
    def _save_refresh_token(self):
        """Persist refresh token to .env file for service restarts."""
        env_path = Path('C:/DepositionPipeline/.env')
        if env_path.exists():
            lines = env_path.read_text().splitlines()
            new_lines = []
            found = False
            for line in lines:
                if line.startswith('CLIO_REFRESH_TOKEN='):
                    new_lines.append(f'CLIO_REFRESH_TOKEN={self.refresh_token}')
                    found = True
                else:
                    new_lines.append(line)
            if not found:
                new_lines.append(f'CLIO_REFRESH_TOKEN={self.refresh_token}')
            env_path.write_text('\n'.join(new_lines))
    
    def _headers(self):
        self._ensure_token()
        return {
            'Authorization': f'Bearer {self.access_token}',
            'Content-Type': 'application/json'
        }
    
    def get_matter(self, matter_id: int) -> dict:
        """Retrieve matter details from Clio."""
        response = requests.get(
            f'{CLIO_BASE_URL}/matters/{matter_id}.json',
            headers=self._headers(),
            params={'fields': 'id,display_number,description,client'}
        )
        response.raise_for_status()
        return response.json()['data']
    
    def search_matters(self, query: str) -> list:
        """Search for matters by name or number."""
        response = requests.get(
            f'{CLIO_BASE_URL}/matters.json',
            headers=self._headers(),
            params={'query': query, 'fields': 'id,display_number,description', 'limit': 10}
        )
        response.raise_for_status()
        return response.json()['data']
    
    def upload_document(self, matter_id: int, file_path: str, name: str, category: str = None):
        """Upload a document to a Clio matter."""
        self._ensure_token()
        file_path = Path(file_path)
        
        if not file_path.exists():
            raise FileNotFoundError(f'Document not found: {file_path}')
        
        # Step 1: Create the document record
        doc_data = {
            'data': {
                'name': name,
                'parent': {'id': matter_id, 'type': 'Matter'},
            }
        }
        
        response = requests.post(
            f'{CLIO_BASE_URL}/documents.json',
            headers=self._headers(),
            json=doc_data
        )
        response.raise_for_status()
        doc_id = response.json()['data']['id']
        logger.info(f'Created Clio document record: {doc_id}')
        
        # Step 2: Upload the file content
        # Get the upload URL
        upload_headers = {
            'Authorization': f'Bearer {self.access_token}'
        }
        
        # Determine content type
        content_types = {
            '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            '.txt': 'text/plain',
            '.pdf': 'application/pdf',
            '.json': 'application/json',
            '.mp4': 'video/mp4',
            '.wav': 'audio/wav'
        }
        content_type = content_types.get(file_path.suffix.lower(), 'application/octet-stream')
        
        with open(file_path, 'rb') as f:
            files = {'file': (file_path.name, f, content_type)}
            put_data = {'data': json.dumps({'uuid': None})}
            response = requests.put(
                f'{CLIO_BASE_URL}/documents/{doc_id}.json',
                headers=upload_headers,
                files=files,
                data=put_data
            )
        response.raise_for_status()
        
        logger.info(f'Uploaded {file_path.name} to Clio document {doc_id} in matter {matter_id}')
        return doc_id

Deposition Session Manager

Type: agent A GUI application that runs on the deposition room workstation, providing the paralegal with a simple interface to start/stop recordings, enter case metadata (matter ID, deponent name, case caption), complete the consent checklist, and monitor recording status. Controls OBS Studio via WebSocket.

Implementation

session_manager.py
python
# Tkinter-based GUI for deposition session management. Runs on the recording
# workstation; controls OBS via WebSocket.

# session_manager.py
# Tkinter-based GUI for deposition session management
# Runs on the recording workstation; controls OBS via WebSocket

import tkinter as tk
from tkinter import ttk, messagebox
import json
import os
import logging
import threading
from pathlib import Path
from datetime import datetime
import websocket
import hashlib
import base64

logger = logging.getLogger('DepositionSessionManager')

OBS_WS_HOST = 'localhost'
OBS_WS_PORT = 4455
OBS_WS_PASSWORD = os.getenv('OBS_WS_PASSWORD', 'change-me-in-production')
METADATA_DIR = Path('C:/DepositionRecordings/Pending')


class OBSController:
    """Controls OBS Studio via obs-websocket protocol."""
    
    def __init__(self, host, port, password):
        self.url = f'ws://{host}:{port}'
        self.password = password
        self.ws = None
        self.msg_id = 0
    
    def connect(self):
        self.ws = websocket.create_connection(self.url)
        # Handle authentication (obs-websocket v5 protocol)
        hello = json.loads(self.ws.recv())
        if hello.get('d', {}).get('authentication'):
            auth = hello['d']['authentication']
            challenge = auth['challenge']
            salt = auth['salt']
            # Generate auth string
            secret = base64.b64encode(
                hashlib.sha256((self.password + salt).encode()).digest()
            ).decode()
            auth_str = base64.b64encode(
                hashlib.sha256((secret + challenge).encode()).digest()
            ).decode()
            
            identify = {
                'op': 1,
                'd': {
                    'rpcVersion': 1,
                    'authentication': auth_str
                }
            }
            self.ws.send(json.dumps(identify))
            resp = json.loads(self.ws.recv())
    
    def send_request(self, request_type, request_data=None):
        self.msg_id += 1
        msg = {
            'op': 6,
            'd': {
                'requestType': request_type,
                'requestId': str(self.msg_id),
                'requestData': request_data or {}
            }
        }
        self.ws.send(json.dumps(msg))
        return json.loads(self.ws.recv())
    
    def start_recording(self):
        return self.send_request('StartRecord')
    
    def stop_recording(self):
        return self.send_request('StopRecord')
    
    def get_record_status(self):
        return self.send_request('GetRecordStatus')


class DepositionSessionGUI:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title('Deposition Capture Manager')
        self.root.geometry('600x800')
        self.root.configure(bg='#1a1a2e')
        
        self.obs = OBSController(OBS_WS_HOST, OBS_WS_PORT, OBS_WS_PASSWORD)
        self.is_recording = False
        self.session_start = None
        
        self._build_ui()
        self._connect_obs()
    
    def _build_ui(self):
        style = ttk.Style()
        style.configure('Title.TLabel', font=('Segoe UI', 16, 'bold'))
        style.configure('Status.TLabel', font=('Segoe UI', 12))
        
        # Header
        header = ttk.Frame(self.root, padding=10)
        header.pack(fill='x')
        ttk.Label(header, text='DEPOSITION CAPTURE SYSTEM', style='Title.TLabel').pack()
        
        # Case Info Frame
        info_frame = ttk.LabelFrame(self.root, text='Case Information', padding=10)
        info_frame.pack(fill='x', padx=10, pady=5)
        
        ttk.Label(info_frame, text='Clio Matter ID:').grid(row=0, column=0, sticky='e', padx=5)
        self.matter_id_var = tk.StringVar()
        ttk.Entry(info_frame, textvariable=self.matter_id_var, width=30).grid(row=0, column=1)
        
        ttk.Label(info_frame, text='Case Caption:').grid(row=1, column=0, sticky='e', padx=5)
        self.case_caption_var = tk.StringVar()
        ttk.Entry(info_frame, textvariable=self.case_caption_var, width=30).grid(row=1, column=1)
        
        ttk.Label(info_frame, text='Deponent Name:').grid(row=2, column=0, sticky='e', padx=5)
        self.deponent_var = tk.StringVar()
        ttk.Entry(info_frame, textvariable=self.deponent_var, width=30).grid(row=2, column=1)
        
        ttk.Label(info_frame, text='Noticing Attorney:').grid(row=3, column=0, sticky='e', padx=5)
        self.attorney_var = tk.StringVar()
        ttk.Entry(info_frame, textvariable=self.attorney_var, width=30).grid(row=3, column=1)
        
        # Consent Checklist
        consent_frame = ttk.LabelFrame(self.root, text='Pre-Recording Consent Checklist', padding=10)
        consent_frame.pack(fill='x', padx=10, pady=5)
        
        self.consent_vars = []
        checklist_items = [
            'Deposition notice specifies recording method per FRCP 30(b)(3)',
            'All parties informed of AI recording and transcription on the record',
            'Written consent form signed (or objections noted on record)',
            'Court reporter present (if required by jurisdiction)',
            'Audio levels verified — all microphones showing signal',
            'Backup microphone (EPOS Capture 5) verified',
            'UPS battery status confirmed (>50%)',
            'Sufficient storage space available on workstation and NAS'
        ]
        
        for i, item in enumerate(checklist_items):
            var = tk.BooleanVar()
            self.consent_vars.append(var)
            cb = ttk.Checkbutton(consent_frame, text=item, variable=var,
                                command=self._check_ready)
            cb.grid(row=i, column=0, sticky='w', pady=2)
        
        # Recording Controls
        control_frame = ttk.Frame(self.root, padding=10)
        control_frame.pack(fill='x', padx=10)
        
        self.start_btn = ttk.Button(control_frame, text='▶ START RECORDING',
                                     command=self._start_recording, state='disabled')
        self.start_btn.pack(side='left', padx=5)
        
        self.stop_btn = ttk.Button(control_frame, text='⏹ STOP RECORDING',
                                    command=self._stop_recording, state='disabled')
        self.stop_btn.pack(side='left', padx=5)
        
        # Status Display
        status_frame = ttk.LabelFrame(self.root, text='Recording Status', padding=10)
        status_frame.pack(fill='x', padx=10, pady=5)
        
        self.status_label = ttk.Label(status_frame, text='NOT RECORDING',
                                       style='Status.TLabel', foreground='gray')
        self.status_label.pack()
        
        self.timer_label = ttk.Label(status_frame, text='00:00:00',
                                      font=('Consolas', 24))
        self.timer_label.pack()
        
        self.obs_status = ttk.Label(status_frame, text='OBS: Connecting...',
                                     foreground='orange')
        self.obs_status.pack()
    
    def _connect_obs(self):
        try:
            self.obs.connect()
            self.obs_status.configure(text='OBS: Connected ✓', foreground='green')
        except Exception as e:
            self.obs_status.configure(text=f'OBS: Connection Failed ✗', foreground='red')
            logger.error(f'OBS connection failed: {e}')
    
    def _check_ready(self):
        all_checked = all(var.get() for var in self.consent_vars)
        has_info = bool(self.matter_id_var.get() and self.deponent_var.get())
        if all_checked and has_info and not self.is_recording:
            self.start_btn.configure(state='normal')
        else:
            self.start_btn.configure(state='disabled')
    
    def _start_recording(self):
        try:
            # Save session metadata
            session_id = datetime.now().strftime('%Y-%m-%d_Deposition_%H-%M-%S')
            metadata = {
                'session_id': session_id,
                'clio_matter_id': self.matter_id_var.get(),
                'case_caption': self.case_caption_var.get(),
                'deponent': self.deponent_var.get(),
                'noticing_attorney': self.attorney_var.get(),
                'start_time': datetime.now().isoformat(),
                'consent_checklist_completed': True
            }
            
            metadata_path = METADATA_DIR / f'{session_id}_metadata.json'
            with open(metadata_path, 'w') as f:
                json.dump(metadata, f, indent=2)
            
            self.obs.start_recording()
            self.is_recording = True
            self.session_start = datetime.now()
            self.status_label.configure(text='● RECORDING', foreground='red')
            self.start_btn.configure(state='disabled')
            self.stop_btn.configure(state='normal')
            self._update_timer()
            logger.info(f'Recording started: {session_id}')
        except Exception as e:
            messagebox.showerror('Recording Error', f'Failed to start recording: {e}')
    
    def _stop_recording(self):
        if messagebox.askyesno('Stop Recording', 'Are you sure you want to stop recording?'):
            try:
                self.obs.stop_recording()
                self.is_recording = False
                self.status_label.configure(text='RECORDING STOPPED — Processing...', foreground='blue')
                self.stop_btn.configure(state='disabled')
                logger.info('Recording stopped')
            except Exception as e:
                messagebox.showerror('Error', f'Failed to stop recording: {e}')
    
    def _update_timer(self):
        if self.is_recording and self.session_start:
            elapsed = datetime.now() - self.session_start
            hours, remainder = divmod(int(elapsed.total_seconds()), 3600)
            minutes, seconds = divmod(remainder, 60)
            self.timer_label.configure(text=f'{hours:02d}:{minutes:02d}:{seconds:02d}')
            self.root.after(1000, self._update_timer)
    
    def run(self):
        self.root.mainloop()


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    app = DepositionSessionGUI()
    app.run()

Speaker Identification Post-Processor

Type: prompt A prompt template and post-processing workflow that takes the Deepgram diarization output (generic SPEAKER 1, SPEAKER 2 labels) and maps them to actual participant names based on the session metadata and deposition conventions (e.g., the first extended speaker is typically the questioning attorney, the most responding speaker is the deponent).

Implementation

Purpose

Deepgram's diarization outputs generic speaker labels (Speaker 0, Speaker 1, etc.). This component uses an LLM to map those labels to actual participant names/roles based on deposition context.

Claude Prompt Template

You are analyzing a deposition transcript with AI-generated speaker labels. Your task is to identify which speaker label corresponds to which participant based on contextual clues.

  • Case Caption: {case_caption}
  • Deponent: {deponent_name}
  • Noticing Attorney: {noticing_attorney}
  • Expected participants: Questioning attorney, defending attorney, deponent, court reporter

Deposition Convention Clues:

1
The QUESTIONING ATTORNEY typically speaks first (after the court reporter), asking "Please state your name for the record"
2
The DEPONENT gives short answers early (name, address, oath confirmation)
3
The DEFENDING ATTORNEY interjects with objections ("Objection, form", "Objection, asked and answered")
4
The COURT REPORTER may speak briefly at start ("Please raise your right hand")

Here are the first 50 utterances of the transcript with speaker labels: {first_50_utterances} Based on these clues, provide your best mapping. Respond in this exact JSON format:

Expected JSON response format for speaker mapping
json
{
  "speaker_map": {
    "0": {"name": "identified name or role", "role": "questioning_attorney|defending_attorney|deponent|court_reporter|unknown", "confidence": 0.0-1.0},
    "1": {"name": "...", "role": "...", "confidence": 0.0-1.0}
  },
  "reasoning": "Brief explanation of how you determined each mapping"
}

Python Implementation

speaker_identifier.py
python
# maps Deepgram speaker IDs to named participants

# speaker_identifier.py
import json
import anthropic

def identify_speakers(transcript_result: dict, metadata: dict, api_key: str) -> dict:
    """Map generic speaker IDs to actual names/roles."""
    
    utterances = transcript_result.get('results', {}).get('utterances', [])
    first_50 = utterances[:50]
    
    utterance_text = '\n'.join([
        f"Speaker {u['speaker']}: {u['transcript']}"
        for u in first_50
    ])
    
    prompt = f"""You are analyzing a deposition transcript with AI-generated speaker labels.

Deposition Metadata:
- Case Caption: {metadata.get('case_caption', 'Unknown')}
- Deponent: {metadata.get('deponent', 'Unknown')}
- Noticing Attorney: {metadata.get('noticing_attorney', 'Unknown')}

Deposition conventions: The questioning attorney typically speaks first after the court reporter,
asking the deponent to state their name. The deponent gives short answers early. The defending
attorney interjects with objections.

First 50 utterances:
{utterance_text}

Map each speaker number to a name and role. Respond in JSON:
{{
  "speaker_map": {{
    "0": {{"name": "...", "role": "questioning_attorney|defending_attorney|deponent|court_reporter|unknown", "confidence": 0.0-1.0}}
  }},
  "reasoning": "..."
}}"""
    
    client = anthropic.Anthropic(api_key=api_key)
    response = client.messages.create(
        model='claude-sonnet-4-20250514',
        max_tokens=1024,
        messages=[{'role': 'user', 'content': prompt}]
    )
    
    result = json.loads(response.content[0].text)
    return result


def apply_speaker_names(transcript_text: str, speaker_map: dict) -> str:
    """Replace generic SPEAKER N labels with identified names."""
    for speaker_id, info in speaker_map.get('speaker_map', {}).items():
        old_label = f'SPEAKER {int(speaker_id) + 1}'
        name = info.get('name', old_label)
        role = info.get('role', 'unknown')
        confidence = info.get('confidence', 0)
        
        if confidence >= 0.7:
            new_label = f'{name} ({role.replace("_", " ").title()})'
        else:
            new_label = f'{old_label} [possibly {name}]'
        
        transcript_text = transcript_text.replace(old_label, new_label)
    
    return transcript_text

Integration Point

Call identify_speakers() after Deepgram transcription but before summarization. Insert into the pipeline between Step 2 (format transcript) and Step 3 (generate summary) in deposition_pipeline.py.

Testing & Validation

  • AUDIO CAPTURE TEST: In each deposition room, have 4 people sit in different positions around the table. Each person reads a 2-minute scripted passage with legal terminology (objections, exhibit references, stipulations). Record via OBS. Verify: all 4 voices are clearly captured, no dropouts, audio levels between -12dB and -6dB peak on OBS meters for both the Shure ceiling mic and EPOS tabletop mic tracks.
  • SPEAKER DIARIZATION ACCURACY TEST: Using the 4-person test recording, run through Deepgram and verify that speaker diarization correctly identifies at least 4 distinct speakers. Acceptable threshold: >90% of utterances attributed to the correct speaker. Have a paralegal manually score 50 random utterances.
  • TRANSCRIPTION ACCURACY TEST (WORD ERROR RATE): Using the scripted test passage (where exact text is known), compare Deepgram's output word-by-word. Calculate WER = (substitutions + insertions + deletions) / total words. Acceptable threshold: WER < 8% for clear audio. Test with both normal speaking volume and soft speaking to verify.
  • LEGAL TERMINOLOGY RECOGNITION TEST: Include these terms in the test script: 'plaintiff', 'defendant', 'stipulate', 'objection', 'hearsay', 'deposition', 'exhibit', 'sustained', 'overruled', 'interrogatories', 'subpoena duces tecum'. Verify Deepgram transcribes all correctly. Use Deepgram keyword boosting if any fail.
  • CROSSTALK HANDLING TEST: Have two speakers deliberately talk over each other for 30 seconds. Verify that Deepgram captures content from both speakers (even if attribution is imperfect) and does not drop significant content during the overlap.
  • END-TO-END PIPELINE TIMING TEST: Record a 30-minute mock deposition. Stop recording and start a timer. Measure: (a) time for file to appear in Pending directory, (b) time for Deepgram transcription to complete, (c) time for CaseMark/Claude summary generation, (d) time for documents to appear in Clio. Total acceptable: <15 minutes for a 30-minute recording. Document each phase timing.
  • SUMMARY QUALITY VALIDATION: Plant 5 specific 'key admission' statements in the mock deposition script. After AI summary generation, verify the summary correctly identifies and cites all 5 admissions with accurate page:line references. Have a senior paralegal or attorney review the full summary for legal accuracy and usefulness.
  • CONSENT WORKFLOW TEST: Launch the Deposition Session Manager GUI. Attempt to start recording without completing all checklist items — verify the Start button remains disabled. Complete all items and verify recording starts successfully. Verify the metadata JSON file is created with correct case information.
  • NAS ARCHIVAL TEST: After a mock deposition completes processing, verify: (a) recording file exists in the NAS DepositionRecordings share, (b) transcript exists in the Transcripts share, (c) summary DOCX exists in the AISummaries share. Verify files are encrypted at rest by checking Synology shared folder encryption status.
  • AZURE BACKUP VERIFICATION TEST: After the Synology Hyper Backup schedule runs, verify files are replicated to Azure Blob Storage. Log into Azure Portal > Storage Account > Container and confirm deposition files are present. Test a restore by downloading a file from Azure and verifying it matches the NAS original.
  • CLIO INTEGRATION TEST: Verify the summary DOCX and transcript TXT are automatically filed to the correct matter in Clio. Open the matter in Clio, navigate to Documents, confirm both files are present with correct names and document categories. Verify file contents are intact by downloading from Clio and opening.
  • POWER FAILURE RESILIENCE TEST: During a mock recording, unplug the workstation's AC power (UPS should take over). Verify: (a) UPS alarm sounds, (b) recording continues uninterrupted, (c) PowerChute initiates graceful shutdown after 5 minutes on battery. Reconnect power and verify OBS recording file is intact (MKV container should be uncorrupted).
  • FAILOVER INTERNET TEST: During a pipeline processing run (Deepgram transcription in progress), simulate primary internet failure by disconnecting the WAN cable. Verify: (a) pipeline retries after connection is restored, (b) no data loss, (c) the retry logic in transcriber.py handles the failure gracefully.
  • MULTI-DEPOSITION CONCURRENT TEST: Start recordings in both deposition rooms simultaneously. Run both through the pipeline and verify they process correctly without conflicts — separate session IDs, separate metadata files, separate Clio matter filings.
  • ACCESS CONTROL TEST: Attempt to access the NAS DepositionRecordings share with the attorney-readonly account — verify read-only access (can view/download but not delete or modify). Attempt access with an unauthorized account — verify access is denied. Verify BitLocker is active on both recording workstations.

Client Handoff

The client handoff should be conducted over two sessions.

SESSION 1 (2 hours, attorneys + paralegals)

1
Cover the overall system architecture and data flow (recording → transcription → summary → PMS filing).
2
Demonstrate the Deposition Session Manager GUI: entering case information, completing the consent checklist, starting and stopping recordings.
3
Review the consent and disclosure requirements in detail — provide the firm with the consent disclosure template, emphasize that the firm's ethics counsel must approve the final language, and discuss two-party consent state implications for depositions involving out-of-state parties.
4
Walk through a live demonstration using a mock deposition, showing the full pipeline from recording through summary delivery in Clio.
5
Review a sample AI-generated summary — explain the page:line citation format, the disclaimer about AI-generated content, and the requirement for attorney review before reliance.
6
Demonstrate how to access archived recordings and transcripts on the NAS.

SESSION 2 (1 hour, designated Deposition Technology Coordinator + managing partner)

1
Cover troubleshooting procedures — what to do if OBS stops recording, if the pipeline gets stuck, if audio quality is poor.
2
Review the escalation procedure to the MSP helpdesk.
3
Provide the documentation package (see below).

Documentation Package

1
Quick Start Guide — laminated single-page card for each deposition room with step-by-step recording procedure.
2
Consent Checklist Template — printable checklist for each deposition.
3
Troubleshooting Guide — common issues and resolutions.
4
System Architecture Diagram — showing all components and data flows.
5
Vendor Contact Information — Deepgram, CaseMark, Shure support contacts.
6
Compliance Reference Sheet — recording consent requirements by state.
7
MSP Support Contact Card — phone, email, portal URL, SLA response times.

Success Criteria

Maintenance

Ongoing Maintenance Schedule

Weekly

  • Review pipeline processing logs for errors or stuck jobs (C:\DepositionPipeline\logs\pipeline.log).
  • Check NAS storage utilization and project when additional drives or archival pruning will be needed.
  • Verify Synology Hyper Backup completed successfully in the past 7 days.

Monthly

  • Apply Windows updates to recording workstations during a scheduled maintenance window (never during business hours when depositions may be scheduled).
  • Check for OBS Studio updates and test in a non-production environment before deploying.
  • Review Deepgram API usage and costs in the Deepgram console — alert the client if usage is trending above budget.
  • Run S.M.A.R.T. extended tests on NAS drives.
  • Verify UPS battery health via PowerChute reports.

Quarterly

  • Conduct a test restore from Azure Blob Storage backup to verify recoverability.
  • Review and update the Deepgram keyword boost list with any new case-specific terminology the firm has encountered.
  • Review CaseMark summary quality with the firm's senior paralegal — adjust templates if needed.
  • Audit access controls on the NAS and Clio API integration.
  • Review consent/disclosure templates with firm ethics counsel for any regulatory changes.

Semi-Annually

  • Full system health check including microphone calibration (verify Shure MXA920 coverage zones haven't drifted), network performance testing, and end-to-end pipeline timing test.
  • Review API vendor compliance certifications (SOC 2 reports from Deepgram, CaseMark).

SLA Considerations

  • Severity 1 (1-hour response, 4-hour resolution): Recording workstation issues affecting an active or imminent deposition.
  • Severity 2 (4-hour response, next business day resolution): Pipeline processing failures (stuck transcriptions, failed summaries).
  • Severity 2 (4-hour response, next business day resolution): NAS or backup failures.
  • Severity 3 (next business day): Routine configuration changes.

Escalation Path

  • Tier 1 (MSP helpdesk): Handles workstation restarts, pipeline service restarts, basic OBS troubleshooting.
  • Tier 2 (MSP engineer): Handles API integration issues, Deepgram/CaseMark configuration, NAS administration.
  • Tier 3 (MSP architect + vendor support): Handles Shure microphone hardware issues, Deepgram API outages, CaseMark platform issues.

Model/API Update Triggers

  • If Deepgram releases a new model version (e.g., Nova-4), test with 5 sample depositions in a staging environment before switching production.
  • If CaseMark updates their summary templates, review output with the firm before accepting changes.
  • If Anthropic releases new Claude models, evaluate for cost/quality improvements on the custom summarization path.

Alternatives

Turnkey CaseMark-Only Approach (No Custom Pipeline)

Instead of building the custom Python pipeline with Deepgram + Claude + Clio integration, use CaseMark as the sole platform. The paralegal manually uploads completed recording files to the CaseMark white-label portal, CaseMark handles both transcription and summarization, and the paralegal manually downloads the summary and files it in the PMS. No custom code or API integration required.

Full Custom Pipeline with Self-Hosted Whisper

Replace the Deepgram cloud API with a self-hosted OpenAI Whisper model running on an on-premises GPU workstation. Transcription happens entirely on-premises with no audio data ever leaving the firm's network. Use Claude API only for summarization (much smaller data payload than full audio).

Otter.ai Business + Manual Summarization

Use Otter.ai Business for real-time transcription during depositions, with attorneys and paralegals manually reviewing and summarizing transcripts using traditional methods or basic AI assistants.

Steno Full-Service Approach

Engage Steno as a full-service provider that combines human court reporting, AI-assisted transcription via Transcript Genius, and deposition logistics. Steno handles everything end-to-end — the MSP only provides hardware and network infrastructure.

Want early access to the full toolkit?