
    Zj(A                      S r SSKJr  SSKrSSKrSSKrSSKrSSKrSSKrSSK	r	SSK
r
SSKrSSKrSSKrSSKrSSKrSSKrSSKJr  SSKJrJr  SSKJr  SSKJr  \" \5      R5                  5       R6                  r\R:                  R=                  S5      =(       d    S	R?                  5       r \!" \R:                  R=                  S
5      =(       d    S5      r"\" \R:                  R=                  S5      =(       d    \S-  \ -  S-  5      r#\" \R:                  R=                  S5      =(       d    \RH                  " 5       S-  S-  5      r%\" \R:                  R=                  S5      =(       d    \%S-  S-  S-  \ -  5      r&\&S-  r'\&S-  r(\&S-  r)\&RU                  SSS9  \'RU                  SSS9  \(RU                  SSS9  \S-  S-  r+\+S-  S-  r,\+S-  S -  r-\+S!-  S"-  S#-  S$-  r.\S%-  r/\R`                  " 5       r1\Rd                  " 5       r3\R                  " 5       r4S&r5S'r6S&r7S(r8\&S)-  r9SSS*S0 S+S_S,S!_S-\:" \.5      _S.\:" \/5      _S/S0_S1S2_S3S4_S5S6_S7S&_S8S9_S:S;_S<S=_S>S?_S@S_SASB_SCSD_S*SESBSSSSF/ SGQSH.SSISJSKSLS9SM.SSN/SOSPS9SQSR.SSSTSSSSUSVSW/SX.SNSYSNSSZS[SVSW/S\./S].	r;SS^ jr<SS_ jr=SS` jr>SSa jr?SSb jr@SSc jrASSd jrB\R                  " Se\R                  5      rESSf jrFSSg jrGSSh jrHSSi jrI " Sj Sk5      rJ " Sl Sm5      rK " Sn So5      rL\J" 5       rM\K" 5       rN\L" 5       rOSSp jrPSSq jrQSSSr jjrRSSs jrSSSt jrTSSu jrU1 SvkrVSw/ SxQ4Sy/ SzQ4S{/ S|Q4S}/ S~Q4S/ SQ4SSS/4S/ SQ4S/ SQ4S/ SQ4S/ SQ4S/ SQ4/rW/ SQrXS rYS rZS r[SSSSSSSSSSSSS.r\S r]S r^S r_S r`SS jraSS jrbSS jrcSS jrdSS jreSS jrfSS jrgS rhS riS rjSrk " S S\5      rlS rm\nS:X  a  \m" 5         gg)a  NW AL Scanner Radio Intelligence - Incident Intelligence Phase 1.

Monitor-only service. Watches configured recording folders, waits for completed
files, identifies source/channel metadata, archives audio, transcribes locally
with whisper.cpp when installed, and marks likely duplicate Lauderdale paging
repeater copies, provides operator audio playback and transcript correction,
and stores correction vocabulary. It does NOT publish, create incidents, or modify
Incident Command in Phase 3.
    )annotationsN)datetime)BaseHTTPRequestHandlerThreadingHTTPServer)Path)urlparseNWALS_TENANTznw-al-scannerNWALS_RADIO_PORT9021NWALS_RADIO_CONFIGtenantszradio_intelligence.jsonLOCALAPPDATAz.localshareNWALS_RADIO_DATA_DIRNWALScannerPlatformdataradio_intelligenceaudiotranscriptszradio_intelligence.sqlite3Tparentsexist_oktoolsradio_transcriptionwhisperzwhisper-cli.exemodelszggml-base.en.binfaster_whispervenvScriptsz
python.exezfaster_whisper_transcribe.py      ?g      ?g333333?zdeepgram_api_key.txtFenabledbackendpythonhelpermodeldistil-large-v3compute_typeint8_float16devicecuda	beam_size   patiencecpu_threads   fallback_modelsmall.enfallback_devicecpufallback_compute_typeint8
vad_filterlanguageeninitial_promptzPublic safety radio traffic in northwest Alabama. Fire, EMS, dispatch, street names, road names, unit numbers and local place names.nova-2-phonecall       @)wreckinjuries
LauderdaleFlorenceColbertKillen	Tuscumbia	SheffieldMuscleShoalsHeltonMarsHill)r!   r%   r7   smart_format	punctuatemip_opt_outkeyword_booststatic_keywords          )r!   lookback_hoursmax_gap_seconds cross_source_fast_window_secondssame_type_window_seconds	min_wordsvoxZ   (\?zLauderdale Paging Repeat)r!   watcher_idswindow_secondssimilarity_thresholdrV   group_labelsdrtrunkzSDRTrunk Callsz#%USERPROFILE%\\SDRTrunk\\recordings.mp3.wav)idnameadapterr!   path
extensionszLauderdale VOX RecordingszC:\\VOX Recorder\\RecordingsLauderdale County VOX)ra   rb   rc   r!   rd   source_namere   )	r!   monitor_onlyingest_existing_on_first_runarchive_audiotranscriptiondeepgramincident_groupingduplicate_detectionwatchersc                    UR                  5        H[  u  p#X ;  a  X0U'   M  [        U[        5      (       d  M'  [        U R                  U5      [        5      (       d  MM  [	        X   U5        M]     U $ N)items
isinstancedictgetdeep_defaults)dstsrckeyvals       0/mnt/data/v087_work/radio_intelligence_server.pyrv   rv      sT    IIK>HT""z#''#,'E'E#(C(	  
 J    c                    [         R                  5       (       dL  [         R                  R                  SSS9  [         R	                  [
        R                  " [        SS9S-   SS9   [
        R                  " [         R                  SS95      n [        U [        5        U $ ! [         a1    [
        R                  " [
        R                  " [        5      5      n  NLf = f)	NTr      indent
utf-8encoding	utf-8-sig)CONFIG_PATHexistsparentmkdir
write_textjsondumpsDEFAULT_CONFIGloads	read_text	Exceptionrv   )cfgs    r{   load_configr      s       =tzz.CdJU\]5jj...DE #~&J  5jjN345s   ''B   8CCc                    [         R                  R                  SSS9  [         R                  S5      nUR	                  [
        R                  " U SS9S-   SS9  UR                  [         5        g )	NTr   z	.json.tmpr~   r   r   r   r   )r   r   r   with_suffixr   r   r   replace)r   tmps     r{   save_configr      sW    TD9

!
!+
.CNN4::c!,t3gNFKKr|   c                    [        [        R                  R                  [        R                  R	                  U 5      5      5      R                  5       $ rq   )r   osrd   
expandvars
expanduserresolve)texts    r{   expand_pathr      s4    ""277#5#5d#;<=EEGGr|   c                    U R                  SU S35      R                  5        Vs1 s H  oDS   iM	     nnX%;  a  U R                  SU SU SU 35        g g s  snf )NzPRAGMA table_info()   zALTER TABLE z ADD COLUMN  )executefetchall)contablerb   ddlrcolss         r{   _ensure_columnr      sf    ++(:5'&CDMMOPOQaDODPl5'dV1SEBC  Qs   Ac                    [         R                  " [        SS9n [         R                  U l        U R                  S5        U R                  S5        U R                  S5        [        U SSS5        [        U SS	S
5        [        U SSS5        [        U SSS5        [        U SSS5        [        U SSS5        [        U SSS5        [        U SSS5        [        U SSS5        [        U SSS5        [        U SSS5        [        U SSS5        [        U SSS
5        [        U SSS5        [        U SSS5        [        U SSS5        [        U SSS5        U R                  S5        U R                  S5        U R                  S5        U R                  S5        U R                  S 5        U R                  S!5        U R                  5         U $ )"N
   timeoutzPRAGMA journal_mode=WALzCREATE TABLE IF NOT EXISTS files_seen (
        fingerprint TEXT PRIMARY KEY,
        original_path TEXT NOT NULL,
        size_bytes INTEGER NOT NULL,
        mtime REAL NOT NULL,
        first_seen REAL NOT NULL,
        disposition TEXT NOT NULL
    )a  CREATE TABLE IF NOT EXISTS transmissions (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        fingerprint TEXT UNIQUE NOT NULL,
        timestamp_epoch REAL NOT NULL,
        timestamp_text TEXT NOT NULL,
        adapter TEXT NOT NULL,
        watcher_id TEXT NOT NULL,
        watcher_name TEXT NOT NULL,
        source_name TEXT NOT NULL,
        system_name TEXT,
        site_name TEXT,
        alias_name TEXT,
        target_id TEXT,
        original_path TEXT NOT NULL,
        archive_path TEXT,
        filename TEXT NOT NULL,
        extension TEXT NOT NULL,
        size_bytes INTEGER NOT NULL,
        ingested_at REAL NOT NULL,
        status TEXT NOT NULL DEFAULT 'MONITOR_ONLY'
    )transmissions
transcriptTEXTtranscript_statuszTEXT DEFAULT 'PENDING'transcript_errortranscribed_atREALtranscript_backendduplicate_groupduplicate_of_idINTEGERduplicate_similaritycorrected_transcriptcorrected_atcorrected_bydeepgram_transcriptdeepgram_statusdeepgram_errordeepgram_transcribed_atdeepgram_modeldeepgram_confidencea4  CREATE TABLE IF NOT EXISTS transcript_corrections (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        transmission_id INTEGER NOT NULL,
        raw_transcript TEXT,
        corrected_transcript TEXT NOT NULL,
        corrected_at REAL NOT NULL,
        corrected_by TEXT NOT NULL DEFAULT 'operator'
    )a  CREATE TABLE IF NOT EXISTS vocabulary_phrases (
        phrase TEXT PRIMARY KEY,
        first_seen REAL NOT NULL,
        last_seen REAL NOT NULL,
        use_count INTEGER NOT NULL DEFAULT 1,
        source TEXT NOT NULL DEFAULT 'operator_correction'
    )zCREATE TABLE IF NOT EXISTS incident_handoffs (candidate_id TEXT PRIMARY KEY, sent_at REAL NOT NULL, incident_url TEXT NOT NULL, payload_json TEXT NOT NULL)zfCREATE INDEX IF NOT EXISTS idx_transmissions_transcript_status ON transmissions(transcript_status, id)zgCREATE INDEX IF NOT EXISTS idx_transmissions_watcher_time ON transmissions(watcher_id, timestamp_epoch)zbCREATE INDEX IF NOT EXISTS idx_transmissions_deepgram_status ON transmissions(deepgram_status, id))sqlite3connectDB_PATHRowrow_factoryr   r   commit)r   s    r{   dbr      s   
//'2
.CkkCOKK)*KK 	 
 KK 	 
* 3v>3)<>VW3);VD3)96B3)=vF3):FC3):IF3)?H3)?H3@3@3)>G3):<TU3)96B3)BFK3)96B3)>GKK 	 
 KK 	 
 KK  n  oKKxyKKyzKKtuJJLJr|   c                    U R                  5        SUR                   SUR                   3R                  SSS9n[        R
                  " U5      R                  5       $ )N|r   r   )errors)r   st_sizest_mtime_nsencodehashlibsha1	hexdigest)rd   statraws      r{   fingerprintr      sS    \\^Adll^1T-=-=,>
?
F
FwW`
F
aC<<&&((r|   zI^(?P<date>\d{8})_(?P<time>\d{6})(?P<body>.+?)(?:__TO_(?P<target>[^.]+))?$c                  ^ U R                   n[        R                  U5      nUR                  nS=n=n=pxSn	U(       Ga   [        R
                  " UR                  S5      UR                  S5      -   S5      n
U
R                  5       nUR                  S5      R                  S5      nUR                  S5      =(       d    SR                  S5      n	UR                  SS	5      m/ S
Qn[        U4S jU 5       S5      nU=(       d    TnU(       a4  TS [        U5      *  R                  5       nU=(       d    UnU=(       d    UnOUnUUUUUU	S.$ ! [         a     Nf = f)NSDRTrunk datetimez%Y%m%d%H%M%Sbody_targetr   )Florence FireColbert FireMuscle Shoals FireSheffield FirezLauderdale EMA NorthTuscumbia Firec              3     >#    U  H;  nTR                  5       R                  UR                  5       5      (       d  M7  Uv   M=     g 7frq   )lowerendswith).0khumans     r{   	<genexpr>!parse_sdrtrunk.<locals>.<genexpr>  s,     LA%++-*@*@*Kaas
   6A	Atimestamp_epochrg   system_name	site_name
alias_name	target_id)stemSDR_REmatchst_mtimer   strptimegroup	timestampr   stripr   nextlen)rd   str   mepochsystemsitealiassourcer   dtr   knownprefixr   s                 @r{   parse_sdrtrunkr     sE   99DTAKKE%//F/T/EF	""1776?QWWV_#DnUBLLNE wwv$$S)''(#)r005S#&
 LLbQ%<SZK(..0F_uF?UDE  #  		s   AE 
EEc                H   UR                   UR                  S5      =(       d    UR                  S5      =(       d    SUR                  S5      =(       d    SUR                  S5      =(       d    SUR                  S5      =(       d    UR                  S5      =(       d    SSS.$ )Nrg   rb   zRadio Audior   r   r   r   )r   ru   )rd   r   watchers      r{   parse_genericr	     sv    ;;{{=1YW[[5HYM{{=17R[[-3kk-0MGKK4GM2 r|   c                    [         R                  " SSU R                  5       5      nUR                  S5      S S =(       d    S$ )Nz[^A-Za-z0-9._-]+r   P   unknown)resubr   )r   ss     r{   	safe_slugr  +  s5    
"C6A773<)	)r|   c                   UR                  SS5      (       d  g[        R                  " US   5      n[        UR	                  S5      -  [        US   5      -  nUR                  SSS9  X@R                  -  nUR                  5       (       aA  UR                  5       R                  U R                  5       R                  :X  a  [        U5      $ UR                  5       (       a>  X@R                   S[        [        R                  " 5       S	-  5       U R                   3-  n[         R"                  " X5        [        U5      $ )
Nrj   Tr   r   z%Y-%m-%drg   r   r   i  )ru   r   fromtimestampARCHIVE_ROOTstrftimer  r   rb   r   r   r   strr   intr   suffixshutilcopy2)rd   metar   r  outdirdests         r{   archive_filer  0  s    77?D))			%6 7	8BBKK
33i]@S6TTF
LLL-IID{{}},,		0C0CC4y{{}}99+Qs499;t+;'<&=dkk]KK
LLt9r|   c                  D    \ rS rSrS rS
S jrSS jrSS jrS rS r	Sr
g	)IngestEnginei?  c                J    0 U l         SU l        SU l        SU l        SU l        g N        r   r   )pending	last_scanlast_ingest
last_errorcount_sessionselfs    r{   __init__IngestEngine.__init__@  s'    57r|   c                    [        5       n UR                  SU45      R                  5       S LUR                  5         $ ! UR                  5         f = f)Nz,SELECT 1 FROM files_seen WHERE fingerprint=?r   r   fetchoneclose)r)  fpr   s      r{   already_seenIngestEngine.already_seenG  sB    d	;;MPRuU^^`hllIIKCIIKs	   "? Ac           	        [        5       n UR                  SU[        U5      UR                  UR                  [
        R
                  " 5       U45        UR                  5         UR                  5         g ! UR                  5         f = f)Nz5INSERT OR IGNORE INTO files_seen VALUES (?,?,?,?,?,?))r   r   r  r   r   r   r   r/  )r)  r0  rd   r   dispositionr   s         r{   	mark_seenIngestEngine.mark_seenN  s\    d	KKOSY

BKKkZ\JJLIIKCIIKs   AA5 5Bc                   [         R                  " US   5      n[        5       n UR                  SXS   UR	                  S5      UR                  SS5      UR                  SS5      UR                  SS	5      US
   UR                  SS5      UR                  SS5      UR                  SS5      UR                  SS5      [        U5      UUR                  UR                  R                  5       UR                  [        R                  " 5       SS45        UR                  5         UR                  5         g ! UR                  5         f = f)Nr   ax  INSERT OR IGNORE INTO transmissions
                (fingerprint,timestamp_epoch,timestamp_text,adapter,watcher_id,watcher_name,source_name,
                 system_name,site_name,alias_name,target_id,original_path,archive_path,filename,extension,
                 size_bytes,ingested_at,status,transcript_status)
                VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)%Y-%m-%d %H:%M:%Src   genericra   r  rb   Watcherrg   r   r   r   r   r   MONITOR_ONLYPENDING)r   r  r   r   r  ru   r  rb   r  r   r   r   r   r/  )	r)  r0  rd   r   r  r  archive_pathtsr   s	            r{   insert_transmission IngestEngine.insert_transmissionW  s   ##D):$;<d	KK B
 *+R[[9L-Mw{{[denOoD+W[[	-JDQ^L_aeaiaijwxza{R($((<*CTXXkZ\E]_bcg_hjv		4;;,,.

DIIKYb	Dd	e JJLIIKCIIKs   DD7 7E	c           
        [        5       nUR                  SS5      (       d  g [        R                  " 5       nX l        UR                  S/ 5       GH  nUR                  SS5      (       d  M  [	        [        UR                  S5      =(       d    S5      5      nUR                  5       (       a  UR                  5       (       d  Mu  UR                  SSS/5       Vs1 s H  n[        U5      R                  5       iM     nn [        UR                  5       5      nU GH  n	 U	R                  5       (       a  U	R                  R                  5       U;  a  M:  U	R                  5       n
[!        X5      nU R#                  U5      (       a'  U R$                  R'                  [        U	5      S 5        M  UR                  SS5      (       d,  U
R(                  [*        S-
  :  a  U R-                  XU
S5        M  [        U	5      nU R$                  R                  U5      nU(       a  US   U
R.                  :w  a  U
R.                  U4U R$                  U'   GM3  X-S   -
  [0        :  a  GME  [        UR                  S5      =(       d    S5      R                  5       nUS:X  a  [3        X5      O[5        XU5      n[7        XU5      nU R9                  XXUU5        U R-                  XU
S5        U R$                  R'                  US 5        X l        U =R<                  S-  sl        GM      GM     g s  snf ! [         a(  nUR                  S	S
5       SU 3U l         S nAGM  S nAff = f! [         a"  nU	R>                   SU 3U l         S nAGMi  S nAff = f)Nr!   Tro   rd   r   re   r_   r`   rb   r  : ri   F   BASELINE_SKIPPEDr   r   rc   r9  r^   INGESTED) r   ru   r   r$  r   r  r   is_dirr   listiterdirr   r&  is_filer  r   r   r1  r#  popr   STARTEDr5  r   STABILITY_SECONDSr  r	  r  r?  r%  r'  rb   )r)  r   nowr  folderxextsrr   excrd   r   r0  ry   priorrc   r  archiveds                    r{   	scan_onceIngestEngine.scan_onceh  s   mwwy$''iikwwz2.G;;y$// W[[%8%>B!?@F==??&--//,3KKvvFV,WX,WqCFLLN,WDXV^^-. <<<>>T[[->->-@-L B$T.B((,,((TD9 77#A5IIbkk\cfg\gNgr5GH d)C LL,,S1E E!H

$:-/ZZ,=S) 1X~(99 !'++i"8"EIFLLNG7>*7L>$3R_`djqRrD+D<H,,RrD(SNN2R<LL$$S$/'*$&&!+&&5  / Y  %,[[	%B$C2cU"K< ! <)-2cU&;DOO<sX   #L$L)!3MAM.AM1AMM!B8M)
M3MM
N
(NN
c                    [         R                  [        5      (       d2   U R                  5         [         R                  [        5      (       d  M1  g g ! [         a  n[        U5      U l         S nANAS nAff = frq   )STOPwaitPOLL_SECONDSrT  r   r  r&  )r)  rQ  s     r{   loopIngestEngine.loop  sP    ))L))+  ))L))  +"%c(+s   A 
A6A11A6)r'  r&  r%  r$  r#  N)r0  r  returnbool)r0  r  rd   r   r   os.stat_resultr4  r  )r0  r  rd   r   r   r^  r  rt   r  rt   r=  r  )__name__
__module____qualname____firstlineno__r*  r1  r5  r?  rT  rZ  __static_attributes__ r|   r{   r  r  ?  s!    ".<`+r|   r  c                  p    \ rS rSrS rSS jrS rSSS jjrSS jrSS jr	\
SS j5       rSS	 jrS
 rSrg)TranscriptionEngine  c                J    SU l         SU l        SU l        SU l        S U l        g r!  last_attemptlast_successr&  r'  
current_idr(  s    r{   r*  TranscriptionEngine.__init__  (    r|   c           
     Z   [        5       R                  S0 5      n[        UR                  S5      =(       d    S5      nUS:X  Ga  [        UR                  S5      (       ae  [	        [        UR                  S5      5      5      R                  5       (       d/  [        [        UR                  S5      -  R                  5       5      O&[        UR                  S5      =(       d    [        5      5      n[        UR                  S5      (       ae  [	        [        UR                  S5      5      5      R                  5       (       d/  [        [        UR                  S5      -  R                  5       5      O&[        UR                  S5      =(       d    [        5      5      n[        UR                  SS5      5      =(       a'    UR                  5       =(       a    UR                  5       n[        UR                  SS5      5      U[        U5      [        U5      [        UR                  S5      =(       d    S	5      U[        [        S
-  5      S.$ [        UR                  S5      (       ae  [	        [        UR                  S5      5      5      R                  5       (       d/  [        [        UR                  S5      -  R                  5       5      O&[        UR                  S5      =(       d    [        5      5      n[        UR                  S5      (       ae  [	        [        UR                  S5      5      5      R                  5       (       d/  [        [        UR                  S5      -  R                  5       5      O&[        UR                  S5      =(       d    [        5      5      n[        UR                  SS5      5      =(       a'    UR                  5       =(       a    UR                  5       n[        UR                  SS5      5      U[        U5      [        U5      U[        [        S
-  5      S.$ )Nrk   r"   r   r#   r$   r!   Tr%   r&   zSETUP_RADIO_TRANSCRIPTION.bat)r!   r"   r#   r$   r%   readysetup_script
executable)r!   r"   rr  r%   rp  rq  )r   ru   r  r   r   is_absoluteBASE_DIRr   DEFAULT_FW_PYTHONDEFAULT_FW_HELPERr]  r   DEFAULT_WHISPER_EXEDEFAULT_WHISPER_MODEL)r)  r   r"   pyr$   rp  exer%   s           r{   backend_status"TranscriptionEngine.backend_status  sv   m4cggi(<,<=&&cggV^N_N_hlmpqtqxqx  zB  rC  nD  iE  iQ  iQ  iS  iSS(SWWX->">!G!G!IJ  Y\  ]`  ]d  ]d  em  ]n  ]C  rC  YD  EB RURYRYZbRcRclpqtuxu|u|  ~F  vG  rH  mI  mU  mU  mW  mWh1B&B%K%K%M!N  ]`  ad  ah  ah  iq  ar  aG  vG  ]H  IFD12Vryy{Vv}}E#CGGIt$<=']`ac]dpstzp{  GJ  KN  KR  KR  SZ  K[  Kp  _p  Gq  |A  SV  W_  bA  WA  SB  C  CswwWcOdOdmqruvyv}v}  K  wL  sM  nN  nZ  nZ  n\  n\#x#'',*??HHJK  be  fi  fm  fm  nz  f{  fR  R  bS  TCGGT[L\L\eijmnqnunuv}n~j  fA  fM  fM  fO  fOCCGGG,<!< E E GH  UX  Y\  Y`  Y`  ah  Yi  YB  mB  UC  DSWWY-.R3::<RELLN	4 89g]`ad]epstypz  FK  ]`  ai  lK  aK  ]L  M  	Mr|   c                    [        5       n UR                  S5      R                  5       nU(       a  [        U5      OS  UR	                  5         $ ! UR	                  5         f = f)NzSELECT * FROM transmissions
                WHERE COALESCE(transcript_status,'PENDING') IN ('PENDING','WAITING_SETUP','RETRY')
                ORDER BY timestamp_epoch ASC, id ASC LIMIT 1r   r   r.  rt   r/  r)  r   rows      r{   _next_pending!TranscriptionEngine._next_pending  sP    d	++ @ AAI  !$49-IIKCIIK   2A A"c                    [        5       n UR                  SX#U45        UR                  5         UR                  5         g ! UR                  5         f = f)NzKUPDATE transmissions SET transcript_status=?, transcript_error=? WHERE id=?r   r   r   r/  r)  txidstatuserrorr   s        r{   _set_statusTranscriptionEngine._set_status  s@    d	KKehnw{g|}JJLIIKCIIK   $A Ac                    UR                  S5      UR                  S5      4 HG  nU(       d  M  [        U5      nUR                  5       (       d  M.  UR                  5       (       d  ME  Us  $    g Nr=  original_pathru   r   r   rI  r)  r  	candidateps       r{   _audio_pathTranscriptionEngine._audio_path  Q    ''.1377?3KLIyO88::!))++H	 M
 r|   c                   U R                  5       nUS   (       d  U R                  US   S5        g US   (       d-  U R                  US   SS5        [        R                  " S5        g U R	                  U5      nU(       d  U R                  US   SS	5        g [        5       R                  S
0 5      n[        UR                  S5      =(       d    S5      R                  5       nUS   U l	        [        R                  " 5       U l
        U R                  US   S5         [        UR                  S5      =(       d    S5      nUS:X  Ga  US   US   S[        U5      S[        UR                  S5      =(       d    S5      S[        UR                  S5      =(       d    S5      S[        UR                  S5      =(       d    S5      S[        UR                  S5      =(       d    S5      S[        [        UR                  S 5      =(       d    S!5      5      S"[        [        UR                  S#5      =(       d    S$5      5      S%[        [        UR                  S&5      =(       d    S'5      5      S([        UR                  S)5      =(       d    S*5      S+[        UR                  S,5      =(       d    S-5      S.[        UR                  S/5      =(       d    S05      /nU(       a
  US1US S2 /-  n[        UR                  S3S45      5      (       a  US5/-  n[        R                  " US4S4S6S7S8[         R"                  S9:X  a  S:OS;S<9nUR$                  S;:w  ai  UR&                  =(       d"    UR(                  =(       d    S=UR$                   3R                  5       S>S  oR                  US   S?U	5        Xl        S U l	        g [,        R.                  " UR(                  =(       d    S@R                  5       R1                  5       SA   5      n
[2        R4                  " SBSC[        U
R                  SD5      =(       d    S5      5      R                  5       nSEU
R                  SF5      =(       d    UR                  S5      =(       d    S SGU
R                  SH5      =(       d    UR                  S5      =(       d    S 3U
R                  SI5      (       a  SJOS-   nGO[7        USK   5      n[7        US   5      n[8        SLUS    SM[;        UR                  SN5      =(       d    SO5       3-  n[7        [        U5      SP-   5      n UR=                  5       (       a  UR?                  5         [        U5      SQ[        U5      SR[        U5      SS[        UR                  S5      =(       d    S5      STSU[        U5      SVSW/nU(       a  US1U/-  n[        R                  " US4S4SXS7S8[         R"                  S9:X  a  S:OS;S<9nUR$                  S;:w  ai  UR&                  =(       d"    UR(                  =(       d    SYUR$                   3R                  5       SZS  oR                  US   S?U	5        Xl        S U l	        g UR=                  5       (       a  URC                  S[S8S\9R                  5       O"UR(                  =(       d    SR                  5       n[2        R4                  " SBSCU5      R                  5       nS]nURE                  5       S^;   a  Sn[2        R4                  " S_SU5      R                  5       nSnU(       Gax  [2        RF                  " S`URE                  5       5      n[I        [K        Sa[M        S'[O        U5      S-  5      5      SSA5       H  n[O        U5      US-  :  a  M  [I        S;[O        U5      U-
  Sb-   5       Vs/ s H  n[Q        UUUU-    5      PM     nn0 nU H  nUR                  US;5      Sb-   UU'   M     U(       d  M}  [M        URS                  5       5      S:  d  M  Scn  O   US:X  a  W
R                  Sd5      OS nUS:X  a  W
R                  Se5      OS nU(       d"  Ub  [        U5      Sf:  a  Sg[        U5      Sh Si3nU(       d"  Ub  [        U5      Sj::  a  Sk[        U5      Sh Si3nU(       a  SlO
U(       a  SmOSnn[U        5       n URW                  SoUUU[        R                  " 5       XS   45        URY                  5         UR[                  5         U(       a  U(       d  U R]                  US   5        [        R                  " 5       U l/        U =R`                  Sb-  sl0        SU l        S U l	        g ! [@         a     GNf = fs  snf ! UR[                  5         f = f! [        Rb                   a     SpoR                  US   S?U	5        Xl         Nf[@         a0  n[        U5      oR                  US   S?U	5        Xl         S nANS nAff = f! S U l	        f = f)qNr!   ra   DISABLEDrp  WAITING_SETUPzURun SETUP_RADIO_TRANSCRIPTION.bat to install local whisper.cpp and the base.en model.rC  AUDIO_MISSING6Neither archived nor original audio file is available.rk   r9   r   TRANSCRIBINGr"   r   r#   r$   z--audioz--modelr%   r&   z
--languager7   r8   z--compute-typer'   r(   z--devicer)   r*   z--beam-sizer+   r,   z
--patiencer-   r    z--cpu-threadsr.   r/   z--fallback-modelr0   r1   z--fallback-devicer2   r3   z--fallback-compute-typer4   r5   z--prompti  r6   Tz--vad-filter,  r   r   nti   r   )capture_outputr   r   r   r   creationflagszfaster-whisper exit iRETRYz{}\s+r   r   zfaster-whisper/actual_model@actual_devicefallback_usedz	/fallbackrr  tx_r   rg   radioz.txtz-mz-fz-lz-otxtz-ofz-ntz-np   zwhisper-cli exit Pr   )r   r   zwhisper.cpp/base.en>   you[music]
[applause]
thank you.thanks for watching.z'(?i)^(\[?static\]?|\[?noise\]?)[ .,-]*$
[a-z0-9']+   r   z*Rejected probable Whisper repetition loop.avg_logprobno_speech_probg?z5Rejected probable no-speech hallucination (no_speech=z.2fz).g      z8Rejected very low-confidence transcription (avg_logprob=HALLUCINATION_REJECTEDTRANSCRIBED	NO_SPEECHzUPDATE transmissions SET transcript=?, transcript_status=?, transcript_error=?, transcribed_at=?, transcript_backend=? WHERE id=?zTranscription timed out.)2r{  r  r   sleepr  r   ru   r  r   rl  rj  r  floatr]  
subprocessrunr   rb   
returncodestderrstdoutr&  r   r   
splitlinesr  r  r   TRANSCRIPT_ROOTr  r   unlinkr   r   r   findallrangeminmaxr   tuplevaluesr   r   r   r/  apply_duplicate_detectionrk  r'  TimeoutExpired)r)  r  r  r   r   promptr"   cmdprocmsgpayloadr   backend_labelrz  r%   outbasetxt_pathreject_reasonwordsnichunkscountscr  r  	tx_statusr   rQ  s                                r{   transcribe_one"TranscriptionEngine.transcribe_one  s   $$&i SY
3gSY  :Q  RJJqM  %SY9qrm4 SWW-.4"5;;=d)T%6dFVFVWZ[_W`bpFq=	!#''),@0@AG**h')99c%jR[]`adahahipaq  bG  vG  ^H  JV  X[  \_  \c  \c  dn  \o  \w  sw  Xx  zJ  LO  PS  PW  PW  Xf  Pg  Py  ky  Lz  |F  HK  LO  LS  LS  T\  L]  Lg  ag  Hh  jw  y|  }@  AD  AH  AH  IT  AU  AZ  YZ  }[  y\  ^j  lo  pu  vy  v}  v}  ~H  vI  vP  MP  pQ  lR  Tc  eh  il  mp  mt  mt  uB  mC  mH  GH  iI  eJ  L^  `c  dg  dk  dk  l|  d}  dK  AK  `L  Na  cf  gj  gn  gn  o@	  gA	  gJ	  E	J	  cK	  M	f	  h	k	  l	o	  l	s	  l	s	  t	K
  l	L
  l	V
  P
V
  h	W
  X
3:vet}"==3d344cn=M6Mc!~~c$TSVahqz  Z\  Za  Za  ei  Zi  LV  op  r??a'__9MdooM^7_ffhiniopC  sC  sC  DG  HL  DM  NU  VY  sZ  lo  \kj !DOi 

DKK$74#>#>#@#K#K#Mb#QRY[Y_Y_`fgjknovozoz  |B  pC  pI  GI  lJ  ZK  ZQ  ZQ  ZSTX  et  u|  u@  u@  AO  uP  uy  TW  T[  T[  \c  Td  uy  hy  tz  z{  |C  |G  |G  HW  |X  |w  \_  \c  \c  dl  \m  |w  qw  {x  cy  LS  LW  LW  Xg  Lh  Lh  }H  np  cq  Ub-.d6'?6KUdilmpqumvlwwx  zC  DG  DK  DK  LY  DZ  De  ^e  zf  yg  hh  VhWc'l612'(((//*;Xd3u:d3u:d3swwzGZGb^bCcdklqruv}r~  @E  FK  L3:f"553^^Ctc[bjs  RT  RY  RY  [_  R_  DN  ef  h??a'\\9J4??J[7\ccefkflmCoo  AD  EI  AJ  KR  SV  pW  il  YhR !DOQ [cZiZiZkZkX''I'NTTVrvr}r}  sD  BD  rK  rK  rM  TV  TZ  TZ  [a  be  fj  Tk  Tq  Tq  Ts  OS  CX  uBzz|aahjcgB2dKQQSD
 M

=$**,? s2s1c%j!m'<=q"EA5zAE) ;@CJqLQRN;ST;SaeE!AaCL1;SFTF#Aq1AA1EVAYVv#fmmo"6!";(T F =DGW<Wgkk-8]aBIM]B]-=!>cg$)CnH]aeHe&[\abp\qru[vvx$yM$)@U;EW[`E`&^_dep_qru^vvx$yM4A0Y]cnIC!  `  cg  hq  r  @D  @I  @I  @K  LY  ^b  Zc  bd  e  gj  gq  gq  gsYY[M4+I+I#d)+T"iikD4+=+=q+@+=RT$/ !DO] !&$&* U" YY[ (( 	i*C,<,<SYws,SehUd 	WCC**3t9WSASV??	W !DOs   Jf 4E:f /%e& Df E
f (e7*f 1f B(f 9=e< 6A(f &
e40f 3e44f <ff 1g=h  	g=&g83h  8g==h   	h	c                   U =(       d    SR                  5       n[        R                  " SSU5      n[        R                  " SSU5      n[        R                  " SSU5      R                  5       nU[	        UR                  5       5      4$ )Nr   [^a-z0-9\s]r   z"\b(uh|um|the|a|an|and|to|for|of)\br  )r   r  r  r   setsplitr   ts     r{   normalize_transcript(TranscriptionEngine.normalize_transcript"  si    ZR FF>3*FF8#qAFF63"((*#aggi.  r|   c           	        [        5       R                  S0 5      nUR                  SS5      (       d  g UR                  SS/5       Vs1 s H  n[        U5      iM     nn[        UR                  SS5      5      n[        UR                  SS	5      5      n[	        UR                  S
S5      5      n[        5       n UR                  SU45      R                  5       n	U	(       a  U	S   U;  d  U	S   (       d  U	S   (       d   UR                  5         g U	S   =(       d    U	S   n
U R                  U
5      u  p[        U5      U:  a   UR                  5         g UR                  SU	S   XS   U-
  U	S   U-   U	S   45      R                  5       nS nSnU H  nUS   =(       d    US   nU R                  U5      u  nn[        U5      U:  a  M9  [        R                  " S UU5      R                  5       nUU-  nU(       a  [        UU-  5      [        U5      -  OSn[        UUS-  US-  -   5      nUU:  d  M  UnUnM     Ub  X:  a   UR                  5         g US   =(       d    US   nUS   =(       d    SU 3nUS   =(       d    US   =(       d    Sn[        U
5      [        U5      S-  :  aG  US   (       d=  U	S   nSU 3nUR                  SUU	S   XS   45        UR                  SUU	S   45        O,UR                  SUUXS   45        UR                  SUU45        UR!                  5         UR                  5         g s  snf ! UR                  5         f = f)Nrn   r!   TrZ   rW   r[   rX   r\   rY   rV   r/   z&SELECT * FROM transmissions WHERE id=?
watcher_idr   r   zSELECT * FROM transmissions
                WHERE watcher_id=? AND id<>? AND (transcript IS NOT NULL OR corrected_transcript IS NOT NULL)
                  AND timestamp_epoch BETWEEN ? AND ?
                ORDER BY ABS(timestamp_epoch-?) ASC LIMIT 12r   r"  g?gffffff?r   ra   r   zvox-repeat-r   g?z`UPDATE transmissions SET duplicate_group=?, duplicate_of_id=?, duplicate_similarity=? WHERE id=?zfUPDATE transmissions SET duplicate_group=?, duplicate_of_id=NULL, duplicate_similarity=NULL WHERE id=?zOUPDATE transmissions SET duplicate_group=COALESCE(duplicate_group,?) WHERE id=?)r   ru   r  r  r  r   r   r.  r/  r  r   r   difflibSequenceMatcherratior  r   )r)  r  r   rO  rZ   window	thresholdrV   r   r  row_textnormr  
candidatesbest
best_scorecand	cand_textcnormcwordssequnionjacscorecanonical_idr   	best_texts                              r{   r  -TranscriptionEngine.apply_duplicate_detection*  s   m 5r:wwy$'''*ww}ug'FG'F!s1v'FGsww/45#''"8$?@	Q/0	d.	++FPYY[C#l+;>sKaGbfijvfwV IIKU 12Gc,6GH33H=KD5zI%N IIKM  &@ \"D.?*@*GM^I_`fIfhkl}h~	A BJ  BJ  BL	 
 DJ" !78ND<N	 $ 9 9) Dvv;*--dD%@FFH<As56>*SZ7sC#*sTz"9;:%!&JD # |z5$ IIK#   12@d4jL*+K\N/KE34P\8JPbI8}s9~44TBS=T"4y%l^4~"CIz:FH  E"CI.0 ~"L*$iHJmpu  xD  pE  FJJLIIKg Hf IIKs-   M;AM	 3M	 CM	 7M	 CM	 	Mc                t   [        5       n UR                  S5        UR                  5         UR                  5         [        R                  [        5      (       dI  U R                  5       nU(       a  U R                  U5        [        R                  [        5      (       d  MH  g g ! UR                  5         f = f)NzUPDATE transmissions SET transcript_status='PENDING' WHERE transcript_status IS NULL OR transcript_status='' OR transcript_status='MONITOR_ONLY')	r   r   r   r/  rW  rX  TRANSCRIBE_POLL_SECONDSr  r  r  s      r{   rZ  TranscriptionEngine.loopc  s    d	KK  k  lJJLIIK))344$$&C##C( ))344 IIK   !B% %B7r'  rl  rj  r&  rk  Nr\  rt   r   r  r  r  r  r  r  r  rt   r\  Path | Noner  rt   )r   r  r\  ztuple[str, set[str]])r  r  )r_  r`  ra  rb  r*  r{  r  r  r  r  staticmethodr  r  rZ  rc  rd  r|   r{   rf  rf    sB    M	P!d ! !7r)r|   rf  c                  j    \ rS rSrSrS rSS jrSS jrS rSSS jjr	SS jr
SS	 jrSS
 jrS rSrg)DeepgramEngineip  zAOptional cloud comparison backend for the transcription bake-off.c                J    SU l         SU l        SU l        SU l        S U l        g r!  ri  r(  s    r{   r*  DeepgramEngine.__init__r  rn  r|   c                   [         R                  R                  S5      =(       d    SR                  5       nU(       a  U$  [        R                  5       (       a!  [        R                  SS9R                  5       $  g! [         a     gf = f)NDEEPGRAM_API_KEYr   r   r   )r   environru   r   DEEPGRAM_KEY_PATHr   r   r   )r)  envs     r{   _api_keyDeepgramEngine._api_keyy  s}    zz~~017R>>@J	 ''))(22K2HNNPP *   		s   9A= =
B
	B
c                ~   [        5       R                  S0 5      nU R                  5       n[        UR                  SS5      5      [        UR                  SS5      5      =(       a    [        U5      [        U5      [	        UR                  S5      =(       d    S5      [	        [
        S-  5      [	        [        5      S.$ )Nrl   r!   Tr%   r:   zSETUP_DEEPGRAM.bat)r!   rp  
configuredr%   rq  key_path)r   ru   r  r]  r  rt  r  )r)  r   ry   s      r{   r{  DeepgramEngine.backend_status  s    m
B/mmoCGGIt45#'')T23AS	s))?-?@+? ?@-.
 	
r|   c                    [        5       n UR                  S5      R                  5       nU(       a  [        U5      OS  UR	                  5         $ ! UR	                  5         f = f)NzSELECT * FROM transmissions
                WHERE COALESCE(deepgram_status,'PENDING') IN ('PENDING','WAITING_SETUP','RETRY')
                ORDER BY timestamp_epoch ASC, id ASC LIMIT 1r~  r  s      r{   r  DeepgramEngine._next_pending  sP    d	++ @ AAI  !$49-IIKCIIKr  c                    [        5       n UR                  SX#U45        UR                  5         UR                  5         g ! UR                  5         f = f)NzGUPDATE transmissions SET deepgram_status=?, deepgram_error=? WHERE id=?r  r  s        r{   r  DeepgramEngine._set_status  s@    d	KKadjswcxyJJLIIKCIIKr  c                    UR                  S5      UR                  S5      4 HG  nU(       d  M  [        U5      nUR                  5       (       d  M.  UR                  5       (       d  ME  Us  $    g r  r  r  s       r{   r  DeepgramEngine._audio_path  r  r|   c                   [        5       R                  S0 5      n/ n[        5       nUR                  S/ 5      =(       d    /  Hs  n[        U5      R	                  5       n[        U5      S:  d  M-  UR                  5       U;  d  MC  UR                  UR                  5       5        UR                  U5        Mu      [        5       nUR                  S5      R                  5       nUR                  5         U H{  n[        R                  " SUS   =(       d    S5       HQ  n	U	R                  5       n
X;  d  M  UR                  U
5        UR                  U	5        [        U5      S:  d  MM  Us  s  $    M}     US S $ ! [         a     Nf = f)	Nrl   rM   rC  zUSELECT phrase FROM vocabulary_phrases ORDER BY use_count DESC,last_seen DESC LIMIT 80z[A-Za-z][A-Za-z'-]{2,}phraser   <   )r   ru   r  r  r   r   r   addappendr   r   r   r/  r  r  r   )r)  r   outseentermr  r   rowsr   tokenr   s              r{   	_keywordsDeepgramEngine._keywords  s4   m
B/SUGG-r28b8D$iooA1vqyQWWYd2#SZZ] 9	Ctu~~  ADIIKZZ(A1X;CTRTUEkkmA}SZZ%6s8R<	 V  3Bx  		s%   :A7E9 5/E9 (E9 .E9 9
FFc           
        U R                  5       nUS   (       d  U R                  US   S5        g US   (       d-  U R                  US   SS5        [        R                  " S5        g U R	                  U5      nU(       d  U R                  US   SS	5        g [        5       R                  S
0 5      nS[        UR                  S5      =(       d    S5      4S[        UR                  S5      =(       d    S5      4SUR                  SS5      (       a  SOS4SUR                  SS5      (       a  SOS4SUR                  SS5      (       a  SOS4/n[        UR                  S5      =(       d    S5      nU R                  5        H  nUR                  SU SUS 345        M     S[        R                  R                  USS9-   nUR                  R                  5       S:X  a  SOJUR                  R                  5       S:X  a  SO*[         R"                  " [        U5      5      S    =(       d    S!n	[        R$                  R'                  XR)                  5       S"S#U R+                  5       -   U	S$S%.S&9n
US   U l        [        R                  " 5       U l        U R                  US   S'5         [        R$                  R1                  U
S(S)9 n[2        R4                  " UR7                  5       R9                  S*S+5      5      nS S S 5        WR                  S,5      =(       d    0 R                  S-5      =(       d    0 /S    R                  S.5      =(       d    0 /S    n[:        R<                  " S/S0[        UR                  S15      =(       d    S25      5      R?                  5       nUR                  S35      nUb  [        U5      OS n[C        5       n URE                  S4X(       a  S5OS6[        R                  " 5       US   XS   45        URG                  5         URI                  5         [        R                  " 5       U l%        U =RL                  S7-  sl&        S2U l'        S U l        g ! , (       d  f       GNj= f! [@         a    S n Nf = f! URI                  5         f = f! [        RP                  RR                   a  nUR7                  5       R9                  S*S+5      S8S  nO! [@         a    S2n Of = fS9URT                   S:U=(       d    URV                   3nURT                  S;;   a  SOS<nU R                  US   UU5        UU l'        US<:X  a  [        R                  " S=5         S nAGNS nAf[@         aI  n[        U5      nU R                  US   S<U5        UU l'        [        R                  " S=5         S nAGN^S nAff = f! S U l        f = f)>Nr!   ra   r  rp  r  z9Run SETUP_DEEPGRAM.bat once and enter a Deepgram API key.rC  r  r  rl   r%   r:   r7   r8   rI   TtruefalserJ   rK   rL   r;   keywords:gz#https://api.deepgram.com/v1/listen?)doseqr_   
audio/mpegr`   	audio/wavr   zapplication/octet-streamPOSTzToken z"NWALScanner-RadioIntelligence/0.70)AuthorizationContent-Typez
User-Agent)r   methodheadersr  -   r   r   r   resultschannelsalternativesr  r   r   r   
confidencezUPDATE transmissions SET deepgram_transcript=?, deepgram_status=?, deepgram_error='',
                    deepgram_transcribed_at=?, deepgram_model=?, deepgram_confidence=? WHERE id=?r  r  r   r  zDeepgram HTTP rB  )i  i  r  r~   ),r{  r  r   r  r  r   ru   r  r  r&  r   urllibparse	urlencoder  r   	mimetypes
guess_typerequestRequest
read_bytesr  rl  rj  urlopenr   r   readdecoder  r  r   r   r   r   r   r/  rk  r'  r&  r  	HTTPErrorcodereason)r)  r  r  r   r   paramsboostkwurlctypereqrespr  altr   confr   rQ  r   r  states                        r{   r  DeepgramEngine.transcribe_one  s   ""$i SYz2FgSY7rsJJqM6s#SY7oprxMj"-c#'''*@.@ABSWWZ08D9:sww~d'C'CVQCGGK$=$=&7KcggmD&A&AFwO
 CGGO,34.."BMM:"QuQi'89: #1&,,2H2HW[2H2\\#ll002F:lPUP\P\PbPbPdflPls|  tH  tH  IL  MR  IS  tT  UV  tW  tu  [uNN""3-=-=-?$T]]_4 =Y
" 
 D	TYY[4#4$BRBRSVW[S\]kBl	!''R'8D

499;#5#5gi#HI 9KK	*0b55jAIbT1MQQR`aifheiklmCs3sww|'<'B#CDJJLD&D%)%5eDk4C! e4-[VT[_]afjbkln 

YY["iikD4+=+=q+@+=RT$/ !DO3 98 '4d' YY[||%% 	-hhj''	:56B%2d% 
"T-?SZZ,@AC%(XX%:/ESYuS134?g~tzz!} 	fCC$**3t9WSASV4?X\XbXbcdXeXe	f !DOs   *R 5Q =B2R /Q2 ?
R 
AR AR  
Q/*R 2R>R  RR RR V;7#SU%S*'U%)S**A5U%V> %V;2>V60V> 6V;;V> >	Wc                t   [        5       n UR                  S5        UR                  5         UR                  5         [        R                  [        5      (       dI  U R                  5       nU(       a  U R                  U5        [        R                  [        5      (       d  MH  g g ! UR                  5         f = f)NzUPDATE transmissions SET deepgram_status='PENDING' WHERE deepgram_status IS NULL OR deepgram_status='' OR deepgram_status='MONITOR_ONLY')	r   r   r   r/  rW  rX  DEEPGRAM_POLL_SECONDSr  r  r  s      r{   rZ  DeepgramEngine.loop  s    D	KK  c  dJJL))122""$CD'', ))122 r  r  N)r\  r  r   r  r  r  )r\  	list[str]r  )r_  r`  ra  rb  __doc__r*  r  r{  r  r  r  r&  r  rZ  rc  rd  r|   r{   r  r  p  s3    K	

*7!r-r|   r  c                   [        5       n UR                  S[        U 5      45      R                  5       nU(       d   UR	                  5         g US   US   4 HW  nU(       d  M  [        U5      nUR                  5       (       d  M.  UR                  5       (       d  ME  Us  UR	                  5         $     UR	                  5         g ! UR	                  5         f = f)Nz@SELECT archive_path, original_path FROM transmissions WHERE id=?r=  r  )r   r   r  r.  r/  r   r   rI  )r  r   r  r  r  s        r{   audio_path_for_idrZ    s    
$Ckk\_bcg_h^jkttv 			 n-s?/CDIyO88::!))++H 			 E
 				s)   1C C %C C C 2C Cc           	        [         R                  " SU =(       d    S5      n[         R                  " SU=(       d    S5      n/ n[        R                  " S U Vs/ s H  oUR	                  5       PM     snU Vs/ s H  oUR	                  5       PM     sn5      nUR                  5        H  u  pxpnUS:X  d  X:X  a  M  [        SU
S-
  5      n[        [        U5      US-   5      nSR                  X<U 5      R                  5       nS[        U5      s=::  a  S::  d  Mt  O  Mx  UR                  U5        M     [         R                  " S	SU=(       d    S5      R                  5       nS[        U5      s=::  a  S
::  a  O  OUR                  U5        / n[        5       nU H=  nUR	                  5       nUU;  d  M  UR                  U5        UR                  U5        M?     US S $ s  snf s  snf )Nz[A-Za-z0-9'/-]+r   equalr   r   r   r~   rX   r        )r  r  r  r  r   get_opcodesr  r  r   joinr   r   r  r  r  )r   	corrected	raw_words	cor_wordsphraseswsmtagi1i2j1j2abr  cleanr!  r"  phry   s                       r{   _extract_vocabulary_phrasesrp    s   

-syb9I

-yB?IG		 	 9'E9a	9'E[dGe[dVW	[dGe	fB!~~/'>RX26NC	NBF ;A)a.)//1F!r!!NN6"  0 FF63	R0668ECJ#u
CDHHJd?HHSM3::b>  r7N! (FGes   G!
7G&c           	     :   [         R                  " SSU=(       d    S5      R                  5       nU(       d  [        S5      e[	        U5      S:  a  [        S5      e[        5       n UR                  S[        U 5      45      R                  5       nU(       d  [        S5      eUS	   =(       d    Sn[        R                  " 5       nUR                  S
XU[        U 5      45        UR                  S[        U 5      XQXb45        [        XQ5       H  nUR                  SXvUSS45        M     UR                  5         UR                  5          [        R                  [        U 5      5        [        U 5      XUS.$ ! UR                  5         f = f! [          a     N0f = f)Nr  r   r   z%Corrected transcript cannot be blank.i  z!Corrected transcript is too long.z3SELECT id, transcript FROM transmissions WHERE id=?zTransmission not found.r   zZUPDATE transmissions SET corrected_transcript=?, corrected_at=?, corrected_by=? WHERE id=?zINSERT INTO transcript_corrections (transmission_id,raw_transcript,corrected_transcript,corrected_at,corrected_by) VALUES (?,?,?,?,?)zINSERT INTO vocabulary_phrases (phrase,first_seen,last_seen,use_count,source) VALUES (?,?,?,?,?)
                ON CONFLICT(phrase) DO UPDATE SET last_seen=excluded.last_seen, use_count=vocabulary_phrases.use_count+1r   operator_correction)ra   r   r   r   )r  r  r   
ValueErrorr   r   r   r  r.  LookupErrorr   rp  r   r/  TRANSCRIBERr  r   )r  ra  whor   r  r   rM  r  s           r{   save_correctionrw  /  sl   vsIO4::<I@AA
9~<==
$CkkORUVZR[Q]^ggi788,%2iikpS#d)4	6  \Y9	;1#AFKK |c1&;<> B 	

		--c$i8 d)Yehii 			  s   'CE8 F 8F

FFc                     [        5       n  U R                  S5      R                  5       S   nU R                  S5      R                  5       S   nXS.U R                  5         $ ! U R                  5         f = f)NznSELECT COUNT(*) n FROM transmissions WHERE corrected_transcript IS NOT NULL AND TRIM(corrected_transcript)<>''r  z)SELECT COUNT(*) n FROM vocabulary_phrases)ra  vocabulary_phrasesr-  )r   ra  vocabs      r{   correction_summaryr{  N  sy    
$CKK  !Q  R  [  [  ]  ^a  b	GHQQSTWX&D				s   AA$ $A6c                    U (       a  U(       d  g S nU" U 5      U" U5      pCU(       a  U(       d  g [        [        R                  " S X45      R                  5       S-  S5      $ )Nc           
         [         R                  " SS[         R                  " SSU R                  5       5      5      R                  5       $ )Nr  r   r  )r  r  r   r   rO  s    r{   r  _match_pct.<locals>.norm[  s1    vvfSs1779!EFLLNNr|   d   r   )roundr  r  r  )r  verifiedr  rl  rm  s        r{   
_match_pctr  X  sO    HOYXaA((a288:3>qAAr|   c                   [        5       n UR                  S[        U 5      45      R                  5       n/ nU H  n[	        U5      nUR                  S5      (       aU  [        UR                  S5      =(       d    SUS   5      US'   [        UR                  S5      =(       d    SUS   5      US'   O
S US'   S US'   UR                  U5        M     UUR                  5         $ ! UR                  5         f = f)NzJSELECT * FROM transmissions ORDER BY timestamp_epoch DESC, id DESC LIMIT ?r   r   r   whisper_match_pctr   deepgram_match_pct)	r   r   r  r   rt   ru   r  r   r/  )limitr   r$  r!  r   rO  s         r{   recent_transmissionsr  c  s    
$C{{gjmnsjtivw  A  A  CA1gAuu+,,'1!%%2E2KANdLe'f%&(21559N3O3USUVWXnVo(p&''+%&TQ/C-DJJqM  				s   CC$ $C6>0   rl  anatbeinisofonortoandareemsforthewascitycopyeastfirefromroadthatthisunitwerewestwithdrivenorthr  southtruckunitsadvisecountyenginepolicerescuestreetadvisedhighwayrespondstationtrafficdispatchreceivedresponseWreck with Injuries)z\bwreck\b.*\binjurz\baccident\b.*\binjurz\bvehicle\b.*\binjurz\bmva\b.*\binjurz\bmvc\b.*\binjurz\bmvc\bMotor Vehicle Accident)z	\bwreck\bz\bmotor vehicle accident\bz\bvehicle accident\bz	\bcrash\bStructure Fire)z\bstructure fire\bz\bhouse fire\bz\bresidential fire\bz\bcommercial fire\bz\bworking fire\bVehicle Fire)z\bvehicle fire\bz\bcar fire\bz\btruck fire\bBrush / Woods Fire)z\bbrush fire\bz\bwoods fire\bz\bgrass fire\b
Fire Alarmz\bfire alarm\bz\balarm activation\bMedical Emergency)z\bmedical\bz\bpatient\bz\bambulance\bz\bems\bTraumatic Injury)z
\btrauma\bz\btraumatic injury\bz
\binjury\bPerson Struck)z\bpedestrian\b.*\bstruck\bz\bperson struck\bz\bhit by (a )?(car|vehicle)\bTree / Lines Down)z\btree down\bz\bpower lines? down\bz\blines? down\bHazard / Gas Leak)z\bgas leak\bz\bcut gas line\bz
\bhazmat\b)z\b10[- ]?4\bz	\bclear\bz\bavailable\bz\breturning to service\bz\bradio check\bz\btest page\bz\bdelayed due to train\bz\ben route\b$z\bon scene\b$z^copy\bz^received\bc                R    U S   =(       d    U S   =(       d    SR                  5       $ )Nr   r   r   )r   )r  s    r{   _effective_textr    s&    &'B3|+<BIIKKr|   c                    [         R                  " SU =(       d    SR                  5       5      nU Vs1 s H9  n[        U5      S:  d  M  U[        ;  d  M   UR                  5       (       a  M7  UiM;     sn$ s  snf )Nr  r   rC  )r  r  r   r   _INCIDENT_STOPWORDSisdigit)r   toksr  s      r{   _clean_tokensr    sY    	MDJB#5#5#7	8D^t!s1vqyAQ6I-IARSR[R[R]At^^^s   A6
A6A6-A6c                   ^ U =(       d    SR                  5       m[         H#  u  p[        U4S jU 5       5      (       d  M!  Us  $    g)Nr   c              3  p   >#    U  H+  n[         R                  " UT[         R                  5      v   M-     g 7frq   r  searchIr   r  r  s     r{   r   %_classify_incident.<locals>.<genexpr>  s%     1Dqryy1RTT""D   36Radio Activity)r   _INCIDENT_PATTERNSany)r   labelpatsr  s      @r{   _classify_incidentr    s<    zrA)1D111L * r|   r         (   r6     #   )r  r  r  r  r  r  r  r  r  r  r  r  c                f    [         R                  US5      [         R                  U S5      :  a  U$ U $ )Nr   )_INCIDENT_SPECIFICITYru   )currentr  s     r{   _more_specific_incidentr    s2    -11)Q?BWB[B[\cefBgg9tmttr|   c                   ^ U =(       d    SR                  5       R                  5       m[        TR                  5       5      S:  a  g[	        U4S j[
         5       5      $ )Nr   r/   Tc              3  p   >#    U  H+  n[         R                  " UT[         R                  5      v   M-     g 7frq   r  r  s     r{   r   _is_routine.<locals>.<genexpr>  s'     >,=qryy1RTT"",=r  )r   r   r   r  r  _ROUTINE_PATTERNSr  s    @r{   _is_routiner    sD    zr  "A
1779~$>,=>>>r|   c                V   [         R                  " SSU =(       d    S5      R                  S5      n/ SQnU Ho  n[         R                  " X1[         R                  5      nU(       d  M1  UR                  S5      R                  S5      nS[        U5      s=::  a  S	::  d  Mi   Us  $   Mq     g)
Nr  r   r   z .)z[\b(?:at|near)\s+(.{4,80}?)(?=\s+(?:for|with|respond|time out|possible|and advise)\b|[.;]|$)zC\b(?:intersection of|at the intersection of)\s+(.{4,80}?)(?=[.;]|$)z|\b(?:on)\s+([A-Z0-9][^.;]{3,70}?(?:Road|Rd|Street|St|Drive|Dr|Boulevard|Blvd|Highway|Hwy|Lane|Ln|Avenue|Ave|Parkway|Pkwy))\br    ,.-rC  rX   )r  r  r   r  r  r   r   )r   r  r  patr   locs         r{   _extract_locationr    s    ffVC
#))$/A
D
 
))C"$$
1
  (CCH""
 #	  r|   c                   US   U S   -
  nUS:  d  U[        UR                  SS5      5      :  a  g[        US   U S   -  5      nUS   S:g  =(       a    US   U S   :H  nUS	   U S
   ;   nUS:  a  S[        US5      -   $ U(       a&  US:  a   U[        UR                  SS5      5      ::  a  gU(       a   U[        UR                  SS5      5      ::  a  gU(       a  US:  a  US::  a  gg)Nr   
last_epochr   rS   rO   tokensincident_typer  rg   sourcesr~   r/   r   rU   rQ   rT   rP   rC  r  )r  ru   r   r  )r   itemr   r  shared	same_typesame_sources          r{   _group_matchr    s    u\22B	AveCGG$5d;<<QtH~h/0F?#%55g$:OQVWfQg:gI]#uY'77K{1s6!},,Vq[R59STW1X+Y%YbcR5)KC!PQQZ[v{rSyr|   c                	   [        5       R                  S0 5      nUR                  SS5      (       d  / $ [        R                  " 5       [        UR                  SS5      5      S-  -
  n[	        5       n UR                  SU45      R                  5       nUR                  SU45      R                  5        Vs0 s H  oUS	   US
   _M     nnUR                  5         / nU H  n[        U5      n[        UR                  5       5      [        UR                  SS5      5      :  a  MF  UR                  US   US   US   US   U[        U5      [        U5      [        U5      [!        US   5      [        UR                  US   S5      5      S.
5        M     / n	U GH  n
[#        U
S   5      (       a  U
S   S:X  a  M"  S nSn[%        U	SS  5       H  n['        XU5      nX:  d  M  UonM     Ub  US:  a?  U	R                  SU
S    3U
S   U
S   U
S   U
S   U
S   1[)        U
S   5      U
/S.5        M  US   R                  U
5        U
S   US'   US   R+                  U
S   5        US==   U
S   -  ss'   [-        US   U
S   5      US'   US   (       a  M  U
S   (       d  GM
  U
S   US'   GM     / nU	 GH  nUS   S:X  a  [        US   5      S :  a  M!  [/        S! US    5       5      nS"nUS   S:w  a  US#-  n[        US   5      S :  a  US-  n[        US   5      S :  a  US$-  n[1        S% US    5       5      (       a  US&-  nUS   (       a  US'-  n[3        S(U5      n/ nUS    HA  nUR                  UR5                  5        VVs0 s H  u  nnUS:w  d  M  UU_M     snn5        MC     UR                  US)   US   US   US   [6        R8                  " US*   5      R;                  S+5      [6        R8                  " US   5      R;                  S+5      US*   US   U[=        US   5      [        US   5      UUS,.5        GM     [?        U Vs/ s H  nUS   PM
     sn5      nU HB  nUR                  US   5      n[!        U5      US-'   U(       a  UR                  S.5      OS US/'   MD     URA                  S0 SS19  US [        U 5       $ s  snf ! UR                  5         f = fs  snnf s  snf )2Nrm   r!   TrR   rN   i  a/  SELECT * FROM transmissions
            WHERE timestamp_epoch>=? AND duplicate_of_id IS NULL
              AND COALESCE(corrected_transcript, transcript, '')<>''
              AND (COALESCE(corrected_transcript,'')<>'' OR transcript_status='TRANSCRIBED')
            ORDER BY timestamp_epoch ASC, id ASCzSELECT duplicate_of_id,COUNT(*) n FROM transmissions
            WHERE duplicate_of_id IS NOT NULL AND timestamp_epoch>=? GROUP BY duplicate_of_idr   r  rV   r/   ra   r   timestamp_textrg   r   r   )
ra   r   r  rg   r   r  r  locationr  repeat_copiesr   r  r  irC  z	incident-r  r  )ry   first_epochr  r  r  r  r  membersr  r  r  r~   c              3  *   #    U  H	  oS    v   M     g7f)r  Nrd  r   r   s     r{   r   &incident_candidates.<locals>.<genexpr>  s     =1o&   r6  r  r   c              3  *   #    U  H	  oS    v   M     g7f)r  Nrd  r  s     r{   r   r    s     3l}lr  r^  r,   b   ry   r  r8  )ra   titler  r  
first_time	last_timer  r  r:  r  transmission_countrepeater_copiesr  sent_to_incident_commandsent_atsent_to_incident_command_atc                    U S   $ )Nr  rd  r~  s    r{   <lambda>%incident_candidates.<locals>.<lambda>  s    !L/r|   )ry   reverse)!r   ru   r   r  r   r   r   r/  r  r   r  r  r   r  r  r  r]  r  reversedr  r  r  r  sumr  r  rr   r   r  r  sorted_handoff_statesort)r  r   cutoffr   r$  r   
dup_countsrr   r   groupsr  r  	bestscorer-  r  r!  repeatsr:  public_membersr   r   vrO  statesr   s                            r{   incident_candidatesr    s   -b1C779T""2I99;uSWW%5b9:4??F
C	[[ 4 6<I	? @Hxz	 	
 := Fabhaj:lltltlv:w x :wA'(3/ :w
 x 			
EQtzz|s377;q#9::H1T7Q7H5I[\]m[n$%m$4D-X\J]  oA  BF  oG!24!8DKaIbDctw  yC  yG  yG  HI  JN  HO  PQ  yR  uST 	U  FtF|$$o)>@P)PRZQ9&,'Aqc*E%	 ( <9Q;MM9T$ZL!9M^H_mq  sD  nE+/+@DQ[L\hlmzh{g|$'X$74&J K O""4(TBS=T$|*<VZ[dVeViVijno|j}V~  AE  FN  AO  SW  X`  Sa  Aa  AO #:$:OQUVeQf"gD!
##Z(8(84PZK[$z:J   	C_//C)4Ea4G=)==
_//Rq|aRq|aR3a	l333Z]ZZ=*a-*r*%
 9A!!!'')"M)31QqH}31Q3)"MN 

5!O*<QM_klmwkx!)!7!7-8H!I!R!RSf!g ( 6 6q G P PQd e"#M"2,]g$Qy\2AiLHYls,. 	/ * C0Cq1T7C01F::agd2h"< =  Aikioiopyiz  GKJgHh HH)4H8{E
mx 			P #N 1s1   3AS 7S	S 	S'S'S-S S$c                    U =(       d    SR                  5       nSU;   d  SU;   d  SU;   d  SU;   a  gSU;   d  SU;   d  SU;   d  S	U;   d  S
U;   a  gSU;   d  SU;   a  gSU;   d  SU;   a  gSU;   a  gSU;   a  gg)Nr   r<   zvehicle accidentmotor vehicleperson struckzstructure firezvehicle firebrushz
woods firez
fire alarmr  medical	traumaticgas leakhazardcutgastree	tree_down
lines downpower_linesother)r   )r  r  s     r{   _incident_command_typer(    s    {A!|)Q./Q2F/]^J^gn1! 3w!||WXGX\hlm\mv|A~))Q(a-{;qr|   c                    [        U =(       d    / 5      nSU;   a  gSU;   a  gSU;   a  gSU;   a  gS	U;   a  g
SU;   a  gg)Nr   r?   r   zMuscle Shoalsr   rC   r   rB   r   zColbert Countyrf   zLauderdale Countyr   )r  )r  rx   s     r{   _incident_command_cityr*    sQ    GMrC#js"?3{3{%5#%.Ar|   c                  ^ U R                  S5      =(       d    SR                  5       mSR                  U R                  S5      =(       d    / 5      R                  5       n/ nST;   d  SU;   d  ST;   d  ST;   a  UR                  S5        [	        U4S jS	 5       5      (       d  S
U;   a  UR                  S
5        ST;   d  ST;   a  UR                  S5        [        [        R                  U5      5      $ )Nr  r   r   r  r  r   r!  c              3  ,   >#    U  H	  oT;   v   M     g 7frq   rd  r   r   r   s     r{   r   -_incident_command_agencies.<locals>.<genexpr>*  s     
KJ!:J   )r  injurr  r<   r  r<   r  r  )ru   r   r`  r   r  rG  rt   fromkeys)r  rx   agenciesr   s      @r{   _incident_command_agenciesr3  '  s    ==)/R
6
6
8Echhy}}U^G_Gece>f>l>l>n#y{px&C-:+>(eBSU]UdUdekUl

KJ
KKKuX[|]e]l]lmr]s%?e3X__X5Nh'((r|   c                4   U (       d  0 $ [        5       n SR                  S U  5       5      nUR                  SU S3U 5      R                  5       nU Vs0 s H  oDS   US   US   S._M     snUR	                  5         $ s  snf ! UR	                  5         f = f)	N,c              3  &   #    U  H  nS v   M	     g7f)?Nrd  )r   r   s     r{   r   !_handoff_state.<locals>.<genexpr>3  s     2MqsMs   zWSELECT candidate_id,sent_at,incident_url FROM incident_handoffs WHERE candidate_id IN (r   candidate_idr  incident_url)r  r:  )r   r`  r   r   r/  )candidate_idsr   marksr$  r   s        r{   r  r  /  s    )
Chh2M22[[rsxryyz{  }J  K  T  T  Veijei`a.!Qy\>IZ"[[eijYY[ kYY[s   A B B -B  B Bc                  ^ [         R                  " SSU R                  S5      =(       d    S5      R                  S5      nU(       d  gU R                  S5      =(       d    U R                  S5      =(       d    SR	                  5       m[        U4S jS	 5       5      nU(       a  U$ [        U4S
 jS 5       5      nU(       d  U$ [         R                  " SU[         R                  5      nU(       a  SUR                  S5       3$ [         R                  " SSU5      R                  S5      nSn[         R                  " SU S3U[         R                  5      nU(       a   UR                  S5      R                  S5      nU=(       d    U$ )zApply NWAL privacy/detail rules to the location sent to Incident Command.

Medical and fire/hazard calls get road-level location only. Wrecks and
tree/power-line calls may retain the exact address/intersection reported.
r  r   r  r   r  r  r   c              3  ,   >#    U  H	  oT;   v   M     g 7frq   rd  r-  s     r{   r   -_incident_command_location.<locals>.<genexpr>D  s       &e &r/  )r  r<   r  ztree / lines downz	tree downr%  zpower lines	powerlinec              3  ,   >#    U  H	  oT;   v   M     g 7frq   rd  r-  s     r{   r   r?  K  s       (!: (r/  )r  ztraumatic injuryr  r   r!  zB\b(?:county\s+road|co(?:unty)?\.?\s*rd|cr)\s*[-#]?\s*(\d+[a-z]?)\bzCR r   z^\s*\d+[A-Za-z]?[-/]?\d*\s+zRoad|Rd|Street|St|Drive|Dr|Boulevard|Blvd|Highway|Hwy|Lane|Ln|Avenue|Ave|Parkway|Pkwy|Circle|Cir|Court|Ct|Trail|Trl|Way|Place|Plz
^(.+?\b(?:z)\b))	r  r  ru   r   r   r  r  r  r   )r  r  exact_ok
road_levelr   cleanedr  r   s          @r{   _incident_command_locationrE  9  sF    	vc)--
39r:@@HCr==)IY]]7-CIr
P
P
RE   &  H    (  J cz 	iiUVYZ\Z^Z^_A3qwwqzl#
# FF12s;AA&IG 
MDii:dV4(6A!''!*""6*'>cr|   c                4  ^  [        U 4S j[        S5       5       S 5      nU(       d  [        S5      eT [        UR	                  S5      =(       d    UR	                  S5      =(       d    S5      UR	                  S5      =(       d    UR	                  S5      =(       d    S[        U5      [        UR	                  S5      =(       d    / 5      [        UR	                  S	5      =(       d    S
5      S:  a  SOS[        U5      UR	                  S	S
5      UR	                  SS5      S.	nS
SK	J
n  U" UR                  5        VVs0 s H>  u  pEUSS / 4;  d  M  U[        U[        5      (       a  [        R                  " U5      OU_M@     snn5      nSU-   n[         R                   " 5       n[#        5       n	 U	R%                  ST X[        R                  " USS945        U	R'                  5         U	R)                  5         T XUS.$ s  snnf ! U	R)                  5         f = f)Nc              3  T   >#    U  H  oR                  S 5      T:X  d  M  Uv   M     g7f)ra   N)ru   )r   r  r9  s     r{   r   )build_incident_handoff.<locals>.<genexpr>_  s"     U7!55;;TAA7s   (	(r  z<Incident candidate not found in the current lookback window.r  r   r   zRadio Incidentr  r:  r   rX   highnormalr  )	ri_candidate_idtyper   r  r  priorityr2  r:  r  )r=  z$/incident_command.html?ri_prefill=1&zINSERT INTO incident_handoffs(candidate_id,sent_at,incident_url,payload_json) VALUES(?,?,?,?) ON CONFLICT(candidate_id) DO UPDATE SET sent_at=excluded.sent_at, incident_url=excluded.incident_url, payload_json=excluded.payload_jsonF)ensure_ascii)r9  r  r:  r  )r   r  rt  r(  ru   rE  r*  r  r3  urllib.parser=  rr   rs   rG  r   r   r   r   r   r   r/  )
r9  r  r  r=  r   r  queryr:  rM  r   s
   `         r{   build_incident_handoffrQ  ^  s   U237UVZ[IK(fgg  ,3I)--XgJh  KIlulyly  {B  mC  KI  GI  4J  S\  S`  S`  ah  Si  S_  mv  mz  mz  {J  mK  S_  O_  kE  FO  kP  Xn  ox  o|  o|  }F  oG  oM  KM  XN  dg  hq  hu  hu  vB  hC  hH  GH  dI  KM  dM  Z`  S[  gA  BK  gL  Zc  Zg  Zg  ht  uv  Zw  EN  ER  ER  S_  `b  Ec  dG&
w}}xbclnostvkwbwBQAd););

1Bx
yE7=L499;s
C  }  K  LO  ]a  ]g  ]g  ho  }B  ]C  ~D  	E  GJ  GQ  GQ  GSYY['#\cdd y
 YY[s   ?G?
2G?
18H Hc                     [        5       n  U R                  S5      R                  5       nU Vs/ s H  n[        U5      PM     snU R	                  5         $ s  snf ! U R	                  5         f = f)Na|  SELECT source_name, COUNT(*) count, MAX(timestamp_epoch) last_epoch,
                              SUM(CASE WHEN transcript_status='TRANSCRIBED' THEN 1 ELSE 0 END) transcribed,
                              SUM(CASE WHEN deepgram_status='TRANSCRIBED' THEN 1 ELSE 0 END) deepgram_transcribed
                              FROM transmissions GROUP BY source_name ORDER BY count DESC)r   r   r   rt   r/  r   r$  r   s      r{   source_summaryrT  n  sd    
$C{{ ] ^ _g^f^h 	 "&&AQ&		 '		s   #A AA A A.c                     [        5       n  U R                  S5      R                  5       nU Vs0 s H  o"S   US   _M     snU R                  5         $ s  snf ! U R                  5         f = f)NzSELECT COALESCE(transcript_status,'PENDING') status, COUNT(*) count FROM transmissions GROUP BY COALESCE(transcript_status,'PENDING')r  count)r   r   r   r/  rS  s      r{   transcript_summaryrW  z  sk    
$C{{  g  h  q  q  s156A(QwZ'6		 7		s   #A AA A A+c                 4   [        5       n / nU R                  S/ 5       Hu  n[        [        UR                  S5      =(       d    S5      5      nUR	                  0 UE[        U5      UR                  5       =(       a    UR                  5       S.E5        Mw     U$ )Nro   rd   r   )resolved_pathr   )r   ru   r   r  r   r   rF  )r   r!  re  r  s       r{   watcher_statusrZ    st    
-C
CWWZ$AEE&M/R01

VaV#a&AHHJ<U188:VW % Jr|   uJ  <!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Radio Intelligence · Incident Intelligence Phase 1 · Resilient Dashboard</title><style>
:root{--bg:#06101a;--panel:#0d1b29;--line:#20384c;--text:#edf6ff;--muted:#8da7bb;--accent:#1da1f2;--good:#39d98a;--warn:#ffbd4a;--bad:#ff7272;--violet:#b69cff}
*{box-sizing:border-box}body{margin:0;background:linear-gradient(145deg,#050b12,#081521 55%,#04090e);color:var(--text);font-family:Inter,Segoe UI,Arial,sans-serif;min-height:100vh}.shell{max-width:1500px;margin:auto;padding:28px}.top{display:flex;justify-content:space-between;gap:18px;align-items:flex-start}.top h1{margin:0;font-size:32px}.top p{color:var(--muted);margin:6px 0}.badge{border:1px solid rgba(255,189,74,.4);background:rgba(255,189,74,.08);color:#ffd990;padding:9px 12px;border-radius:999px;font-weight:800}.section{margin-top:22px;background:rgba(13,27,41,.94);border:1px solid var(--line);border-radius:16px;padding:18px}.section h2{margin:0 0 14px}.engines,.watchers,.summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.card{background:#102438;border:1px solid var(--line);border-radius:12px;padding:14px}.muted{color:var(--muted);font-size:13px}.dot{display:inline-block;width:9px;height:9px;border-radius:50%;background:var(--warn);margin-right:7px}.dot.good{background:var(--good)}.dot.bad{background:var(--bad)}.setup{margin-top:12px;border:1px solid rgba(255,189,74,.35);background:rgba(255,189,74,.06);padding:12px 14px;border-radius:11px;color:#ffe1a2}.setup code{color:#fff}.transmissions{display:flex;flex-direction:column;gap:10px}.tx{border:1px solid var(--line);background:#091621;border-radius:12px;padding:13px 14px}.txhead{display:grid;grid-template-columns:170px 220px 1fr auto;gap:12px;align-items:center}.source{font-weight:800}.status{font-size:11px;font-weight:900;letter-spacing:.04em;color:#ffd990}.status.ok{color:#79e7ad}.status.wait{color:#ffd990}.status.err{color:#ff9292}.transcriptrow{display:flex;gap:10px;align-items:stretch;margin-top:10px}.transcript{flex:1;padding:11px 12px;border-left:3px solid #2a98d9;background:#0c1e2d;border-radius:7px;font-size:15px;line-height:1.45}.transcript.corrected{border-left-color:var(--good);background:#0d241f}.comparegrid{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:9px}.enginebox{background:#071724;border:1px solid #203e54;border-radius:8px;padding:9px 10px}.enginebox.dg{border-color:#53447a;background:#121427}.enginetitle{font-size:10px;font-weight:900;letter-spacing:.06em;color:#86cfff;margin-bottom:5px}.enginebox.dg .enginetitle{color:#c4b2ff}.confidence{font-size:11px;color:#b9c9d6;margin-left:7px}.match{font-size:11px;color:#79e7ad;margin-left:7px}.engineerr{font-size:11px;color:#ff9292;margin-top:5px}@media(max-width:900px){.comparegrid{grid-template-columns:1fr}}.label{display:inline-block;font-size:10px;font-weight:900;letter-spacing:.06em;color:#86cfff;margin-right:8px}.label.corrected{color:#79e7ad}.actions{display:flex;gap:8px;align-items:flex-start;flex-wrap:wrap}.btn{border:1px solid #31516a;background:#102a3f;color:#fff;padding:9px 12px;border-radius:8px;font-weight:800;cursor:pointer}.btn:hover{background:#17364f}.btn.playing{border-color:#ffbd4a;color:#ffd990}.btn.save{background:#145c43;border-color:#2b9c72}.btn.cancel{background:#202b35}.editor{margin-top:9px;display:none}.editor textarea{width:100%;min-height:82px;background:#07131e;color:#fff;border:1px solid #31516a;border-radius:8px;padding:10px;font:inherit;line-height:1.4}.editorbar{display:flex;gap:8px;margin-top:8px}.rawdetails{margin-top:8px}.rawdetails summary{color:var(--muted);cursor:pointer;font-size:12px}.rawdetails .raw{margin-top:7px;color:#bdd0df;font-size:13px;line-height:1.4;background:#07131e;border-radius:7px;padding:9px}.dup{display:inline-block;margin-left:8px;color:#d9ccff;border:1px solid rgba(182,156,255,.35);padding:2px 7px;border-radius:999px;font-size:10px;font-weight:900}.verified{display:inline-block;margin-left:8px;color:#79e7ad;border:1px solid rgba(57,217,138,.35);padding:2px 7px;border-radius:999px;font-size:10px;font-weight:900}.file{color:var(--muted);font-size:11px;word-break:break-all;margin-top:8px}.empty{color:var(--muted);padding:25px 0}.footer{margin-top:20px;color:var(--muted);font-size:12px}@media(max-width:900px){.engines,.watchers,.summary{grid-template-columns:1fr}.shell{padding:16px}.top{flex-direction:column}.txhead{grid-template-columns:1fr}.transcriptrow{flex-direction:column}}
.incidents{display:flex;flex-direction:column;gap:12px}.incident{background:#0a1926;border:1px solid #28506c;border-radius:13px;padding:14px}.incidenthead{display:grid;grid-template-columns:1fr auto;gap:12px;align-items:start}.incidenttitle{font-size:19px;font-weight:900}.incidentmeta{color:var(--muted);font-size:12px;margin-top:5px}.conf{display:inline-block;border:1px solid rgba(57,217,138,.35);color:#79e7ad;border-radius:999px;padding:4px 8px;font-size:11px;font-weight:900}.loc{margin-top:8px;color:#d8ecff;font-weight:700}.evidence{margin-top:10px;border-top:1px solid #1c374a;padding-top:9px}.evidence summary{cursor:pointer;color:#9fd8ff;font-weight:800}.evrow{display:grid;grid-template-columns:145px 170px 1fr auto;gap:8px;align-items:center;padding:8px 0;border-bottom:1px solid #142a39;font-size:13px}.evrow:last-child{border-bottom:0}.tinybtn{border:1px solid #31516a;background:#102a3f;color:#fff;padding:6px 9px;border-radius:7px;font-weight:800;cursor:pointer}.handoffbtn{border-color:#a86d14;background:#3a270b;color:#ffd990}.handoffbtn:hover{background:#53380d}.handoffbtn.sent{border-color:#2b9c72;background:#123b2e;color:#79e7ad}.tag{display:inline-block;margin-left:6px;color:#d9ccff;font-size:10px}@media(max-width:900px){.incidenthead,.evrow{grid-template-columns:1fr}}</style></head><body><div class="shell"><div class="top"><div><h1>Radio Intelligence</h1><p>Local audio ingest + scanner-tuned transcription + incident grouping + Incident Command handoff · Phase 7</p></div><div class="badge">MONITOR ONLY · NOTHING AUTO-POSTS</div></div>
<div class="section"><h2>Transcription Engines</h2><div class="engines"><div id="transcriber" class="card"></div></div><div id="setup" class="setup" style="display:none"></div></div>
<div class="section"><h2>Recording Sources</h2><div id="watchers" class="watchers"></div></div>
<div class="section"><h2>Source Activity</h2><div id="summary" class="summary"></div></div>
<div class="section"><h2>Incident Candidates</h2><div id="incidenterror" class="setup" style="display:none"></div><div id="incidents" class="incidents"></div><div id="noincidents" class="empty">No incident candidates in the current lookback window.</div></div>
<div class="section"><h2>Recent Transmissions</h2><div id="rows" class="transmissions"></div><div id="empty" class="empty">Waiting for new completed recordings…</div></div>
<div class="footer" id="footer"></div></div><script>
function e(s){return String(s??'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]))}
async function get(p){const r=await fetch(p,{cache:'no-store'});let j;try{j=await r.json()}catch(err){throw new Error(`${p}: invalid JSON response (${r.status})`)}if(!r.ok)throw new Error(j.error||`${p}: HTTP ${r.status}`);return j}
async function post(p,obj){const r=await fetch(p,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(obj)});const j=await r.json();if(!r.ok)throw new Error(j.error||'Request failed');return j}
function fmtBytes(n){if(n<1024)return n+' B';if(n<1048576)return (n/1024).toFixed(1)+' KB';return (n/1048576).toFixed(1)+' MB'}
function statusClass(s){if(s==='TRANSCRIBED'||s==='NO_SPEECH')return'ok';if(s==='WAITING_SETUP'||s==='PENDING'||s==='TRANSCRIBING')return'wait';return'err'}
let activeAudio=null,activeBtn=null;
function playTx(id,btn){if(activeAudio){activeAudio.pause();activeAudio=null;if(activeBtn){activeBtn.classList.remove('playing');activeBtn.textContent='▶ Play'}if(activeBtn===btn){activeBtn=null;return}}const a=new Audio('/api/audio/'+id);activeAudio=a;activeBtn=btn;btn.classList.add('playing');btn.textContent='■ Stop';a.onended=a.onerror=()=>{btn.classList.remove('playing');btn.textContent='▶ Play';activeAudio=null;activeBtn=null};a.play().catch(()=>{btn.classList.remove('playing');btn.textContent='▶ Play';activeAudio=null;activeBtn=null})}
const editingIds=new Set();
const incidentEditingIds=new Set();
function editTx(id){const ed=document.getElementById('ed-'+id);if(!ed)return;const opening=ed.style.display!=='block';if(opening){editingIds.add(id);ed.style.display='block';const ta=document.getElementById('ta-'+id);if(ta){requestAnimationFrame(()=>{ta.focus();ta.setSelectionRange(ta.value.length,ta.value.length)})}}else{editingIds.delete(id);ed.style.display='none'}}
async function saveTx(id){const ta=document.getElementById('ta-'+id);const b=document.getElementById('save-'+id);if(!ta||!b)return;b.disabled=true;b.textContent='Saving…';try{await post('/api/transmissions/'+id+'/correction',{corrected_transcript:ta.value});editingIds.delete(id);await refresh(true)}catch(err){alert(err.message)}finally{b.disabled=false;b.textContent='Save correction'}}
function editEvidenceTx(id,btn){const row=btn.closest('.evrow');if(!row)return;let ed=row.querySelector('.eveditor');if(!ed)return;const opening=ed.style.display!=='block';if(opening){incidentEditingIds.add(id);ed.style.display='block';const ta=ed.querySelector('textarea');if(ta){requestAnimationFrame(()=>{ta.focus();ta.setSelectionRange(ta.value.length,ta.value.length)})}}else{incidentEditingIds.delete(id);ed.style.display='none'}}
async function saveEvidenceTx(id,btn){const ed=btn.closest('.eveditor');if(!ed)return;const ta=ed.querySelector('textarea');if(!ta)return;btn.disabled=true;btn.textContent='Saving…';try{await post('/api/transmissions/'+id+'/correction',{corrected_transcript:ta.value});incidentEditingIds.delete(id);await refresh(true)}catch(err){alert(err.message)}finally{btn.disabled=false;btn.textContent='Save correction'}}
async function sendToIncidentCommand(id,btn){if(btn.dataset.sent==='1')return;const original=btn.textContent;btn.disabled=true;btn.textContent='Sending…';try{const r=await post('/api/incidents/'+encodeURIComponent(id)+'/handoff',{});btn.dataset.sent='1';btn.textContent='✓ Sent to Incident Command';btn.classList.add('sent');const rel=r.incident_url||'/incident_command.html?ri_prefill=1';const url=new URL(rel,window.location.href);url.port='9000';url.hostname=window.location.hostname;window.open(url.toString(),'_blank','noopener');await refresh(true)}catch(err){btn.disabled=false;btn.textContent=original;alert(err.message)}}
async function refresh(forceRows=false){
let s=null,t=null,ic=null;
let statusErr='',txErr='',incidentErr='';
try{s=await get('/api/status')}catch(err){statusErr=err.message||String(err)}
try{t=await get('/api/transmissions?limit=100')}catch(err){txErr=err.message||String(err)}
try{ic=await get('/api/incidents?limit=30')}catch(err){incidentErr=err.message||String(err)}

if(s){
 const tr=s.transcriber||{},dg=s.deepgram||{};
 transcriber.innerHTML=`<div><span class="dot ${tr.ready?'good':'bad'}"></span><b>${tr.ready?'GPU-Preferred Local Whisper Ready':'Local Transcription Setup Needed'}</b></div><div class="muted" style="margin-top:7px">Local/offline · ${e(tr.backend)} · primary ${e(tr.model)} / CUDA</div><div class="muted" style="margin-top:4px">Session: ${s.session_transcribed||0} ${s.current_transcription_id?'· Working on #'+s.current_transcription_id:''}</div>`;
 setup.style.display=tr.ready?'none':'block';setup.innerHTML=`Run <code>SETUP_RADIO_TRANSCRIPTION.bat</code> once if local Whisper is not installed.`;
 watchers.innerHTML=(s.watchers||[]).map(w=>`<div class="card"><div><span class="dot ${w.exists?'good':''}"></span><b>${e(w.name)}</b></div><div class="muted" style="margin-top:8px">${e(String(w.adapter||'').toUpperCase())}</div><div class="muted" style="margin-top:5px;word-break:break-all">${e(w.resolved_path)}</div><div style="margin-top:8px">${w.exists?'Ready':'Folder not found'}</div></div>`).join('');
 summary.innerHTML=(s.sources||[]).length?(s.sources||[]).map(x=>`<div class="card"><div class="source">${e(x.source_name)}</div><div style="font-size:26px;font-weight:800;margin-top:6px">${x.count}</div><div class="muted">indexed · Local transcripts ${x.transcribed||0}</div></div>`).join(''):'<div class="muted">No transmissions indexed yet.</div>';
 footer.textContent=`Updated ${s.timestamp} · Ingested ${s.session_ingested||0} · Whisper ${s.session_transcribed||0} · Corrections ${(s.corrections||{}).corrected||0} · Learned phrases ${(s.corrections||{}).vocabulary_phrases||0} · Archive ${s.archive_audio?'ON':'OFF'} · ${s.last_error?'Ingest: '+s.last_error:'No ingest errors'}${s.transcription_error?' · Whisper: '+s.transcription_error:''}${incidentErr?' · Incident API: '+incidentErr:''}`;
}else{
 transcriber.innerHTML=`<div><span class="dot bad"></span><b>Radio Intelligence status unavailable</b></div><div class="muted" style="margin-top:7px">${e(statusErr)}</div>`;
 watchers.innerHTML='<div class="muted">Status API unavailable. Recording-source monitoring may still be running server-side.</div>';
 summary.innerHTML='<div class="muted">Status API unavailable.</div>';
 footer.textContent='Dashboard status refresh error: '+statusErr;
}

const inc=document.getElementById('incidents'), noinc=document.getElementById('noincidents'), incerr=document.getElementById('incidenterror');
if(incidentErr){
 incerr.style.display='block';incerr.innerHTML=`<b>Incident grouping temporarily unavailable.</b><div class="muted" style="margin-top:5px">The rest of Radio Intelligence will continue updating. ${e(incidentErr)}</div>`;
 inc.innerHTML='';noinc.style.display='none';
}else if(ic){
 incerr.style.display='none';incerr.innerHTML='';
 // Preserve evidence-panel state across the dashboard's periodic refresh.
 // Incident IDs are anchored to the first canonical transmission, so they remain stable while a candidate grows.
 const openEvidenceIds=new Set(Array.from(inc.querySelectorAll('details.evidence[open]')).map(d=>d.dataset.incidentId).filter(Boolean));
 if(incidentEditingIds.size===0){inc.innerHTML=(ic.items||[]).map(g=>`<div class="incident" data-incident-id="${e(g.id)}"><div class="incidenthead"><div><div class="incidenttitle">${e(g.title)}</div><div class="incidentmeta">${e(g.first_time)} → ${e(g.last_time)} · ${g.transmission_count} canonical transmission${g.transmission_count===1?'':'s'}${g.repeater_copies?` · ${g.repeater_copies} repeater cop${g.repeater_copies===1?'y':'ies'} collapsed`:''} · ${e((g.sources||[]).join(' + '))}</div>${g.location?`<div class="loc">📍 ${e(g.location)}</div>`:''}</div><div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:flex-end"><div class="conf">${g.confidence}% confidence</div><button class="tinybtn handoffbtn${g.sent_to_incident_command?' sent':''}" data-sent="${g.sent_to_incident_command?'1':'0'}" onclick="sendToIncidentCommand('${e(g.id)}',this)" ${g.sent_to_incident_command?'disabled':''}>${g.sent_to_incident_command?'✓ Sent to Incident Command':'Send to Incident Command →'}</button></div></div><details class="evidence" data-incident-id="${e(g.id)}"><summary>Review evidence (${(g.members||[]).length} transmission${(g.members||[]).length===1?'':'s'})</summary>${(g.members||[]).map(m=>`<div class="evrow"><div>${e(m.timestamp_text)}</div><div>${e(m.source_name)}</div><div>${e(m.text)}${m.verified?'<span class="tag">VERIFIED</span>':''}${m.repeat_copies?`<span class="tag">+${m.repeat_copies} repeater copies</span>`:''}<div class="eveditor" style="display:none;margin-top:8px"><textarea style="width:100%;min-height:72px">${e(m.text||'')}</textarea><div class="editorbar"><button class="tinybtn" onclick="saveEvidenceTx(${m.id},this)">Save correction</button><button class="tinybtn" onclick="editEvidenceTx(${m.id},this.closest('.evrow').querySelector('.evidence-correct'))">Cancel</button></div></div></div><div style="display:flex;gap:7px;justify-content:flex-end"><button class="tinybtn" onclick="playTx(${m.id},this)">▶ Play</button><button class="tinybtn evidence-correct" onclick="editEvidenceTx(${m.id},this)">✎ Correct</button></div></div>`).join('')}</details></div>`).join('');}
 inc.querySelectorAll('details.evidence').forEach(d=>{if(openEvidenceIds.has(d.dataset.incidentId))d.open=true;});
 noinc.style.display=(ic.items||[]).length?'none':'block';
}

if(t){
 const tr=(s&&s.transcriber)||{}, dg=(s&&s.deepgram)||{};
 if(forceRows||editingIds.size===0){rows.innerHTML=(t.items||[]).map(x=>{
  const verified=!!x.corrected_transcript;
  const seed=x.corrected_transcript||x.deepgram_transcript||x.transcript||'';
  const conf=x.deepgram_confidence==null?'':`<span class="confidence">confidence ${(x.deepgram_confidence*100).toFixed(1)}%</span>`;
  const wmatch=x.whisper_match_pct==null?'':`<span class="match">vs verified ${x.whisper_match_pct}%</span>`;
  const dmatch=x.deepgram_match_pct==null?'':`<span class="match">vs verified ${x.deepgram_match_pct}%</span>`;
  const whisperText=x.transcript||((x.transcript_status==='NO_SPEECH')?'[no speech]':'');
  const whisperBox=`<div class="enginebox"><div class="enginetitle">LOCAL FASTER-WHISPER · ${e(x.transcript_backend||tr.model||'')} ${wmatch}</div><div>${e(whisperText)}</div>${x.transcript_error?`<div class="engineerr">${e(x.transcript_error)}</div>`:''}</div>`;
  return `<div class="tx"><div class="txhead"><div>${e(x.timestamp_text)}</div><div class="source">${e(x.source_name)}</div><div class="muted">${e(x.adapter)} · ${fmtBytes(x.size_bytes)}</div><div><span class="status ${statusClass(x.transcript_status)}">LOCAL:${e(x.transcript_status||'PENDING')}</span>${verified?'<span class="verified">CORRECTED</span>':''}${x.duplicate_of_id?`<span class="dup">REPEATER COPY #${e(x.duplicate_of_id)}</span>`:''}</div></div>
  ${verified?`<div class="transcript corrected" style="margin-top:10px"><span class="label corrected">VERIFIED</span>${e(x.corrected_transcript)}</div>`:''}
  <div class="comparegrid">${whisperBox}</div>
  <div class="actions" style="margin-top:9px"><button class="btn" onclick="playTx(${x.id},this)">▶ Play</button><button class="btn" onclick="editTx(${x.id})">✎ Correct</button></div>
  <div class="editor" id="ed-${x.id}"><textarea id="ta-${x.id}">${e(seed)}</textarea><div class="editorbar"><button class="btn save" id="save-${x.id}" onclick="saveTx(${x.id})">Save correction</button><button class="btn cancel" onclick="editTx(${x.id})">Cancel</button></div></div>
  <div class="file">${e(x.filename)}</div></div>`}).join('');empty.style.display=(t.items||[]).length?'none':'block'}
}else{
 if(editingIds.size===0) rows.innerHTML=`<div class="setup"><b>Transmission list temporarily unavailable.</b><div class="muted" style="margin-top:5px">${e(txErr)}</div></div>`;
 empty.style.display='none';
}
}
refresh();setInterval(()=>refresh(false),2500)
</script></body></html>
c                  ,    \ rS rSrS rS rS rS rSrg)Handleri  c                    g rq   rd  )r)  fmtargss      r{   log_messageHandler.log_message  s    r|   c                V   [         R                  " U5      R                  S5      nU R                  U5        U R	                  SS5        U R	                  SS5        U R	                  S[        [        U5      5      5        U R                  5         U R                  R                  U5        g )Nr   r3  zapplication/jsonCache-Controlno-storeContent-Length)
r   r   r   send_responsesend_headerr  r   end_headerswfilewrite)r)  rG  objr   s       r{   json_outHandler.json_out  s    zz#%%g.4 $"2"2>BT"UW[WgWghw  yC  XD  FJ  FV  FV  Wg  hk  lo  pt  lu  hv  Fw  y}  yI  yI  yK  MQ  MW  MW  M]  M]  ^b  Mcr|   c                4   [        U R                  5      nUR                  S;   a  [        R                  S5      o R	                  S5        U R                  SS5        U R                  S[        [        U5      5      5        U R                  5         U R                  R                  U5        g UR                  S:X  aC  U R                  SSS	S
S[        [        R                  5       [        R                  5       S.5        g UR                  S:X  Gaj  [!        5       o0R                  S0 SS_S["        R$                  " 5       R'                  S5      _S[(        _SS_S[+        5       _S[-        5       _S[/        5       _S[0        R2                  _S[        R2                  _S[        R4                  _S[        R2                  _S[        R4                  _S[        R6                  _S[        R                  5       _S[0        R8                  _S[0        R:                  _S[0        R6                  _[        R6                  [        R                  5       UR=                  SS5      [        [>        5      [A        5       S .E5        g UR                  S!:X  av   S"S#K!J"n   [G        S$[I        S%[K        U" URL                  5      R=                  S&S'/5      S"   5      5      5      n[Q        U5      nU R                  SSU[        U5      S).5        g UR                  S.:X  ah   S"S#K!J"n  [G        S$[I        S*[K        U" URL                  5      R=                  S&S//5      S"   5      5      5      nU R                  SS0[S        U5      05        g [T        RV                  " S1UR                  5      nU(       Ga  [Y        [K        UR[                  S$5      5      5      n	U	(       d  U R                  S2S3S405        g U	R]                  5       R^                  n
U	R`                  Rc                  5       S5:X  a  S6OS7nU Rd                  R=                  S8S95      nS"oS$-
  nSnURg                  S:5      (       a   US;S  Ri                  S<S$5      S"   nURi                  S=S$5      u  nnU(       a  [G        S"[K        U5      5      nU(       a  [I        U
S$-
  [K        U5      5      nU(       d%  U(       a  [K        U5      n[G        S"U
U-
  5      oS$-
  nX:  d  X:  a
  [k        5       eS>nX-
  S$-   nU R	                  U5        U R                  SU5        U R                  SBSC5        U R                  S[        U5      5        US>:X  a  U R                  S@SDU S=U SEU
 35        U R                  SFSG5        U R                  5         U	Rm                  SH5       nURo                  U5        UnUS":  aT  URq                  [I        SIU5      5      nU(       d  O1U R                  R                  U5        U[        U5      -  nUS":  a  MT  S S S 5        g UR                  SJ:X  a  U R                  S[!        5       5        g U R                  S2S3SK05        g ! [N         a    S(n GNf = f! [N         a,  nU R                  S*S+/ S,[        U5      -   S-.5         S nAg S nAff = f! [N         a    S%n GNdf = f! [N         a9    U R	                  S?5        U R                  S@SAU
 35        U R                  5          g f = f! , (       d  f       g = f)LN)/z/radio_intelligence.htmlr      r3  ztext/html; charset=utf-8re  z/healthTzradio-intelligenceg      @)okservicephaserh   startedtranscriberrl   z/api/statusrq  r   r8  tenantrh   ro   r  transcript_countssession_ingestedsession_transcribedcurrent_transcription_idsession_deepgramcurrent_deepgram_idr   rl   r$  r%  r&  rj   )transcription_errorru  rj   	data_rootcorrectionsz/api/incidentsr   )parse_qsr   r  r  30r  )rq  rr   rV    FzIncident grouping error: )rq  rr   r  z/api/transmissions100rr   z/api/audio/(\d+)  r  zaudio not foundr_   r/  r0  Ranger   zbytes=   r5  -   rg  zContent-Rangezbytes */zAccept-Rangesbyteszbytes ro  rc  rd  rbi   z/api/config	not found)9r   rd   HTMLr   rf  rg  r  r   rh  ri  rj  rl  rK  ru  r{  DEEPGRAMr   r   rM  r  	TENANT_IDrZ  rT  rW  ENGINEr'  rl  r&  r$  r%  ru   	DATA_ROOTr{  rO  r  r  r  r  rP  r   r  r  r  	fullmatchrZ  r   r   r   r  r   r5  
startswithr  rs  openseekrD  )r)  ur   r   r  r  rr   rQ  r   r  sizerM  rngstartendrG  specrl  rm  r  lengthf	remainingchunks                           r{   do_GETHandler.do_GET  s   TYY6655W%D'9'9#'>@P@PQ_`z@{  ~B  ~N  ~N  O_  `c  dg  hl  dm  `n  ~o  qu  qA  qA  qC  EI  EO  EO  EU  EU  VZ  E[  ]c66YMM#T4HQTdhsz  JU  Jd  Jd  Jf  rz  rI  rI  rK   L  M  OU66]"C}}S  3G4  3G[I`I`atIu  3Gv~  @I  3G  JX  Y]  3G  ^h  iw  iy  3G  zC  DR  DT  3G  Uh  i{  i}  3G  ~P  QW  Qe  Qe  3G  f{  |G  |U  |U  3G  Vp  q|  qG  qG  3G  HZ  [c  [q  [q  3G  rG  HP  H[  H[  3G  \l  mu  m@  m@  3G  AK  LT  Lc  Lc  Le  3G  fq  rx  rB	  rB	  3G  C	P	  Q	W	  Q	c	  Q	c	  3G  d	p	  q	w	  q	B
  q	B
  3G  Y
d
  Y
o
  Y
o
  ~
I  ~
X  ~
X  ~
Z  kn  kr  kr  sB  CG  kH  UX  Yb  Uc  rD  rF  3G   H  JP66%%	i1aCHQWW,=,A,A'4&,QRS,T(U VWE *%0ceCJ#OP 66))(1!CC(9(=(=gug(Nq(Q$RSTMM#(<U(CDEvLL,aff5!#aggaj/2AcG,=#>?668##D$%HHNN$4f$<L+E,,""72.CEaSc~~h''	}QRs1-a0DdjjQ6G#!AAc!f%c$q&Q0#a&Ad1f%6syEKz|1CD 9Q;Ft$d&6&6~e&LdN^N^_novNwy}  zJ  zJ  K[  \_  `f  \g  zhSy$**?VE7!C5PQRVQW;XY_Z8$:J:J:Luyk&&U9!56E %JJ$$U+YE
-BY  k  66]"MM#{}-vcGK01] ! E  icrB]^abe^fBf#ghhi 'Ce'( ! }&&s+T-=-=oPXY]X^N_-`bfbrbrbtv|}  sq   Y7 AY$ *Y7 A	Z0 B*[ -A,\	$Y40Y7 3Y44Y7 7
Z-"Z((Z-0[ ?[ A \\	
\c                x   [        U R                  5      n[        R                  " SUR                  5      n[        R                  " SUR                  5      nU(       d  U(       d  U R	                  SSS05        g  [        U R                  R                  S5      =(       d    S5      nUS:  d  US	:  a  [        S
5      eU(       a>  [        R                  " U R                  R                  U5      R                  S5      5      O0 nU(       a\  [        [        UR                  S5      5      [!        UR                  S5      =(       d    S5      5      nU R	                  SSS0UE5        g [#        UR                  S5      5      o`R	                  SSS0UE5        g ! [$         a'  nU R	                  SS[!        U5      05         S nAg S nAf[        [        R&                  4 a'  nU R	                  SS[!        U5      05         S nAg S nAf[(         a'  nU R	                  SS[!        U5      05         S nAg S nAff = f)Nz#/api/transmissions/(\d+)/correctionz%/api/incidents/(incident-\d+)/handoffr  r  r  re  0r   i N  zInvalid request size.r   r   r   r   rp  rq  Ti  r  )r   rd   r  r  rl  r  r5  ru   rs  r   r   rfilerD  rE  rw  r   r  rQ  rt  JSONDecodeErrorr   )r)  r  
correctionhandoffr  r  resultrQ  s           r{   do_POSTHandler.do_POST&  s   
499
<< FO
EqvvN'MM#45v
	H$,,""#34;<AsagZ0G%HHFGDJJtzzq188ABRG&s:+;+;A+>'?W[[QgEhEnlnAopcD#8#896)'--*:;F]]3QUVZPe^dPe=fI4==ws3x6H#I#ID001`$--gVYZ]V^M_:`:`GsWSX4F!G!GGs7   <C-F */F 
H9$GH9#HH9H44H9rd  N)	r_  r`  ra  rb  r`  rl  r  r  rc  rd  r|   r{   r\  r\    s    c;2zHr|   r\  c                 `   [        5       R                  5         [        R                  " [        R
                  SSS9R                  5         [        R                  " [        R
                  SSS9R                  5         [        R                  " [        R
                  SSS9R                  5         [        S[        4[        5      n [        S[         35        [        S[         35        [        S	[         35        [        S
[        R                  5       S    35        [        S[        R                  5       S    35         U R!                  5         [$        R'                  5         U R)                  5         g ! ["         a     N1f = f! [$        R'                  5         U R)                  5         f = f)NTzradio-ingest)r   daemonrb   zradio-transcribezradio-deepgramz0.0.0.0z7Radio Intelligence Phase 7 listening on http://0.0.0.0:zConfig: zData: zWhisper ready: rp  zDeepgram ready: )r   r/  	threadingThreadr  rZ  r  ru  r  r   PORTr\  printr   r  r{  serve_foreverKeyboardInterruptrW  r  server_close)srvs    r{   mainr  9  s<   DJJLFKK>JPPRK,,T@RSYY[HMM$=MNTTV
y$/
9C	CD6
JK	H[M
"#	F9+
	OK668AB
CD	X446w?@
AB' 	
C$$&   	
C$$&s$   E7 7
FF FF &F-__main__)rw   rt   rx   rt   r\  rt   r   )r   rt   r\  None)r   r  r\  r   )
r   sqlite3.Connectionr   r  rb   r  r   r  r\  r  )r\  r  )rd   r   r   r^  r\  r  )rd   r   r   r^  r\  rt   )rd   r   r   r^  r  rt   r\  rt   )r   r  r\  r  )rd   r   r  rt   r   rt   r\  r  )r  r  r\  r  )r   r  ra  r  r\  rW  )operator)r  r  ra  r  rv  r  r\  rt   )r  r  r  r  r\  zfloat | None)r  )r  )r  r  r\  r  )r  rW  r\  r  )r  rt   r\  rW  )r;  rW  r\  rt   )r  rt   r\  r  )r9  r  r\  rt   )orX  
__future__r   r  r   r   r   r  r  r   r  r  r   r>  urllib.errorr;  rO  urllib.requestr   http.serverr   r   pathlibr   r   __file__r   r   rt  r  ru   r   r  r  r  r   homer   r  r  r  r   r   
TOOLS_ROOTrw  rx  ru  rv  RLockLOCKEventrW  rK  rL  rY  r  rU  r  r  r   rv   r   r   r   r   r   r   compiler  r   r  r	  r  r  r  rf  r  r  ru  r  rZ  rp  rw  r{  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r(  r*  r3  r  rE  rQ  rT  rW  rZ  r  r\  r  r_  rd  r|   r{   <module>r     s   #    	 	           C  !>!!#**ZZ^^N+>EEG	
2::>>,-782::>>"67yHy<PS\<\_x<xzBJJNN>2Xtyy{X7MPW7WY 67  NLK`<`ci<i  mA  =A  DM  =M  O	7"m+
2
2 	t ,   4$  /   dT  2"77
 9,/@@ "X-0BB !11F:YFU == 
))+    66  $)4# 	#'( 	#'(	
 	" 	 	& 	Q 	C 	q 	* 	5 	  	d 	D  	  a!& # h	 ,/$' w $1 $!:!6*	
 /32!6*	
cDN	HDFR)
 
`bdbfbf	g"J*
^+ ^+BN) N)`P- P-f 
!# .j>B$    ]  ^st  H  INOTU%'>?@XYPQmnZ[OP  
L_   u?

AF)"Je 	SjUH$ UHp'$ zF r|   