① 녹음 수집 →
② 업로드 →
③ 자동 enqueue →
④ STT (Whisper) →
⑤ Gemini 분석 →
⑥ 연락처·거래처 매칭 →
⑦ DB 저장 →
⑧ 피드 조회
1 녹음 수집 구현
폴더 스캔 기기 통화녹음 폴더(/Recordings/Call 등) 재귀 스캔 → m4a·mp3·aac·3gp·amr·wav·ogg. 권한 MANAGE_EXTERNAL_STORAGE/A13+ READ_MEDIA_AUDIO.
recording_adapter.dart
파일명 파싱 "통화 <식별자>_<날짜>_<시각>" → 숫자면 번호 , 아니면 이름 .
call_filename.dart · parseCallFilename
기기 연락처 매칭 번호 → flutter_contacts 로 표시명 매칭(1회 캐시, +82↔0 정규화).
contact_directory.dart
예외 권한 거부 → 번호 fallback · 파일명 불일치 → 둘 다 null · 보안폴더 0바이트 → skip
2 업로드 구현 통화기록·SMS 자동화 신규
자동분석 토글 ON 토큰 확인 → 미업로드분 확인 모달 → 순차 큐 bulkUpload(재시도·중복가드·진행률 배너).
auto_analyze_toggle · recordings_notifier
엔드포인트 / 메타 POST /recordings-device/upload-base64 · meta {contactName, contactNumber, recordedAt, autoAnalyze}.
device-token 인증
통화기록·SMS 증분 동기화 신규 토글 ON / 포그라운드 복귀 시 watermark 이후 항목만 contact-imports 자동 업로드(배치·동의 모달).
contact_sync_notifier.dart
예외 5초 미만 통화 skip(STT 비용) · 0바이트 skip · 재시도 1회 · 권한 거부 silent skip · 토글 OFF 중단
3 자동 enqueue 구현
maybeAutoEnqueue meta.autoAnalyze=true → tryMarkRecordingTranscriptionPending(atomic, race 차단) → BullMQ enqueue.
recordings-device.routes
멱등 jobId ${deviceId}__${recordingId} → 같은 기기·녹음 중복 enqueue dedupe. attempts=1(Whisper 비용 보호).
queues.ts
4 STT — Whisper 구현
입력 게이트 2KB 미만(무음)·5초 미만·미지원 포맷(amr/3gp) → skipped (STT 호출 안 함).
Whisper presigned URL → gpt-4o-transcribe / whisper-1. text·language(ko)·duration·costUsd($0.006/분).
출력 게이트 무음/빈 전사·30초 미만 → skipped(LLM 호출 안 함, 분석 가치 부족).
예외 미지원 포맷 → skipped(재시도 X) · 기타 실패 → failed(UI 재시도 버튼)
5 Gemini 액션플랜 분석 구현
PII 마스킹 전사 텍스트 → maskPii 정규식으로 개인정보 제거 후 LLM 입력.
Gemini gemini-3-flash-preview + INSIGHT_SYSTEM_PROMPT + (transcript·contactName·recordedAt). Zod 스키마 강제 .
추출 결과 summary · actionItems[] · nextBestAction(urgency) · BANT · painPoints · sentiment · callOutcome · callType · topics · commitmentStrength.
6 연락처·거래처 매칭 신규 보강
번호 정규화 SSOT recordings·lead_contacts(phone) generated 컬럼 — 숫자 끝 9자리(국가코드·+82·0 변이 흡수). 부분 인덱스(email 78만행 제외).
migration 0514
통합 매칭 우선순위 call_log(시각근접) > contact > sms > lead_contacts(phone) . "알 수 없음"·번호동일·마스킹 필터.
enrichContactNames
거래처(lead) backfill 번호 → lead_contacts(phone) → leads.id → recordings.leadId·recording_insights.leadId 자동 채움(NULL만, 수동 보존, idempotent ).
예외 번호 없음 → 파일명/기기연락처 fallback · 동일번호 다중 lead → 최신·대표 1개 · lead 미존재 → NULL 유지 · 페이지당 ≤2쿼리(N+1 회피)
7 DB 저장
테이블 저장 내용 키/관계
recordingss3Key·transcriptionStatus·transcribedText(마스킹)·contactName/Number·recordedAt·isHandled·isFavorite·leadId uuidv7 PK · ws+handled/favorite 인덱스
recording_insightssummary·actionItems(jsonb,checked)·signals(BANT/sentiment/nextBestAction)·model·costUsd recording_id UNIQUE(1:1) · UPSERT
contact_importscall_log/sms/contact payload(jsonb)·sourceDevice (ws,type,SHA256) UNIQUE 멱등
lead_contactscontact_type(phone/email)·contact_value·정규화 컬럼 거래처 연락처 SSOT
8 피드 조회 구현
키셋 무한스크롤 GET /recordings-device/feed — recording_insights JOIN(본문 즉시), composite cursor, status·handled·favorite 서버 필터.
액션 완료처리(undo)·액션아이템 개별체크(전부→자동완료)·즐겨찾기·이름 수동편집·영업 카테고리 필터·전체검색.
상세 원문 재생·파일 다운로드·STT 텍스트 다운로드. 진행률 폴링(SSE 존재, 모바일 8초 폴링).
현황 진단 (제1원리)
파이프라인(②~⑦)은 완비됐으나, 분석 시점 alpha 기준 연락처/거래처 식별률은 0%였습니다.
원인 — (1) 최신 APK 미설치로 업로드에 번호가 안 실림(recordings 100% NULL, device_id 빈값), (2) 통화기록/SMS 인입 미가동(1건), (3) lead_contacts(phone) 인덱스 0·정규화 부재.
해결 — 번호 정규화 SSOT + 인덱스 (④가속) · 통합 매칭 + lead backfill (⑤자동화) · 통화기록/SMS 자동 증분 동기화 (②입력) · 최신 APK 배포 가 전제.
Rinda Calls 통화 인텔리전스 파이프라인 · 모바일(Flutter telinfo) + 백엔드(Elysia/Bun) + PostgreSQL 18 · BullMQ + Whisper + Gemini
내부 분석 문서 · 2026-06-30