RINDA · Import Exclusion

바이어 Import — 제외 필터 로직

업로드 시 어떤 행이 왜 제외되는가. 제외는 두 층으로 나뉩니다 — 화면에 보여주는 "미리보기 4기준"과, DB에 실제 반영되는 "파이프라인 dedup 3기준". 둘은 같지 않습니다.

작성: Claude (Opus 4.8) 기준: 2026-06-25 UI/UX: uiux ↗ 구조감사: audit ↗

0두 층 요약

화면의 "제외"는 고지(미리보기)이고, 실제 반영은 별도 파이프라인이 결정합니다.

① 미리보기 화면4 모달

exclusion-preview.service.ts
POST /api/v1/exclusion-preview

  • 기준 4종: 활성시퀀스·발송이력·이메일중복·회사명중복
  • 업로드 직전 "이 행들 빠져요" 고지/시뮬레이션
  • 3종 토글 가능, 활성시퀀스는 강제 ON
  • 실제 import를 바꾸지 않음 — 카운트만 보여줌

② 파이프라인 실제 반영

smart-import.service.ts
deduplicateRecords()

  • 기준 3종: 이메일 → URL host → 회사명
  • DB에 무엇이 들어갈지 실제 결정
  • 제외분은 import_jobs.unresolvedData로 보존(복구 가능)
  • 활성시퀀스·발송이력은 여기서 안 봄
핵심 주의 두 층의 기준·정규화가 달라 미리보기 수치 ≠ 실제 import 결과일 수 있음. 특히 회사명 정규화 차이가 가장 큰 어긋남(아래 §불일치).

1① 미리보기 4대 제외 기준

모두 워크스페이스 범위. 업로드 값(이메일/회사명)을 기존 DB와 대조.

사유 (코드)판정 로직토글
active_sequence
활성 시퀀스 등록됨
업로드 이메일 = 활성/일시정지 시퀀스활성/일시정지 enrollment로 등록된 리드의 이메일
sequences.status ∈ {active, paused} AND enrollment.status ∈ {active, paused} AND lead_contacts.email = 업로드이메일
강제 ON
못 끔
past_sent
과거 발송 이력
업로드 이메일 = emails 테이블에 발송 이력 있는 수신주소
emails.status ∈ {sent, delivered, opened, clicked, replied} AND emails.to_email = 업로드이메일
ON/OFF
duplicate_email
이메일 중복
업로드 이메일 = 워크스페이스 lead_contacts(type=email)에 이미 존재
lead_contacts.contact_type = 'email' AND lead_contacts.contact_value = 업로드이메일
ON/OFF
duplicate_company
회사명 중복
업로드 회사명(lower) = 워크스페이스 leads.company_name(lower)에 이미 존재
lower(leads.company_name) = lower(업로드회사명)
ON/OFF

2우선순위 — 한 행이 여러 개 걸릴 때

한 행이 여러 사유에 걸려도 가장 높은 사유 하나만 표시(가장 actionable한 이유).

active_sequence past_sent duplicate_email duplicate_company
예) 어떤 이메일이 "이미 존재(중복) + 활성 시퀀스 등록 + 발송 이력"에 모두 걸리면 → 표시 사유는 active_sequence 하나. active_sequence·past_sent는 본질적으로 "그 이메일이 이미 워크스페이스에 있다(=duplicate_email)"의 더 구체적인 부분집합이라, 우선순위로 더 쓸모있는 이유를 노출.

3② 파이프라인 실제 dedup 3기준

import 시 DB에 무엇이 들어갈지 실제로 결정. deduplicateRecords.

// 행별 순차, 매칭되면 duplicates 로 빼고 continue
 1 email   normalizeEmail (NFC+trim+lower+형식검증)
            → DB lead_contacts 매칭 OR 파일내 중복
 2 url host normalizeUrlHost (호스트만, www 제거)   ← 미리보기엔 없는 축
            → DB leads.website_url 매칭 OR 파일내 중복
 3 회사명  normalizeCompanyName (악센트·접미사·부호 제거) ← 미리보기보다 공격적
            → DB leads.company_name 매칭 OR 파일내 중복

 매칭분 → import_jobs.unresolvedData (수동 복구 가능)
기준미리보기에 있나비고
email○ (duplicate_email)거의 동일
url host✕ 없음파이프라인 전용
회사명△ (정규화 다름)접미사 제거로 더 많이 묶임
active_sequence / past_sent○ (미리보기 전용)파이프라인은 안 봄

4두 층의 불일치 (함정)

#불일치결과
1회사명 정규화 차이 — 미리보기=단순 lower / 파이프라인=접미사·악센트 제거미리보기엔 "안 빠짐"인데 import에서 빠질 수 있음 (Gap Inc. vs Gap)
2미리보기 전용 기준 — active_sequence·past_sent새 이메일이면 활성 시퀀스 멤버라도 import됨 (파이프라인은 email로만 간접 차단)
3파이프라인 전용 축 — url host미리보기엔 URL 중복 개념 없음
4토글은 시뮬레이션 — 모달 ON/OFF는 카운트만 바꿈"회사명 중복 끄기" 해도 실제 import는 항상 dedup (주석: false 는 시뮬레이션 전용)
실무 시사점 사용자가 보는 "제외 4종"은 고지이고, DB 반영은 이메일·URL·회사명으로 거른다. 미리보기 수치를 실제 결과로 신뢰하면 안 되며, 특히 같은 회사 다수 담당자(ABM) 업로드 시 회사명 dedup으로 의도보다 많이 빠질 수 있음.

5정규화 함수 비교

① 미리보기② 파이프라인
이메일NFC + trim + lowerNFC + trim + lower + 형식검증
회사명NFC + trim + lower (여기까지)+ 악센트 제거 + 접미사(Inc/Ltd/LLC…) 제거 + 부호 제거
URLhost만(www 제거, path/query 버림)
코드exclusion-preview.service.ts:26,32utils/dedup-normalize.ts
강제 못 끔 past_sent email company