Merge branch 'dev'
This commit is contained in:
@@ -29,9 +29,15 @@ public class CardNotificationParser {
|
|||||||
private static final Pattern AMOUNT = Pattern.compile("([0-9]{1,3}(?:,[0-9]{3})+|[0-9]{4,})\\s*원");
|
private static final Pattern AMOUNT = Pattern.compile("([0-9]{1,3}(?:,[0-9]{3})+|[0-9]{4,})\\s*원");
|
||||||
private static final Pattern DATE_MD = Pattern.compile("(\\d{1,2})[./](\\d{1,2})");
|
private static final Pattern DATE_MD = Pattern.compile("(\\d{1,2})[./](\\d{1,2})");
|
||||||
|
|
||||||
// 광고/이벤트 푸시에 흔한 표현 — 강한 신호(승인/입금/출금/이체/취소/환불)가 없으면 무시.
|
// 명백한 광고/이벤트 표현 — 거래신호(승인/입출금/취소)가 있어도 무조건 광고로 보고 제외.
|
||||||
private static final Pattern AD_HINT = Pattern.compile(
|
// (실제 카드 승인/은행 입출금 알림에는 거의 등장하지 않는 표현들)
|
||||||
"이벤트|광고|혜택|쿠폰|할인|적립|포인트|캐시백|마일리지|리워드|당첨|응모|추첨|프로모션|배너|무료|증정|모집");
|
private static final Pattern AD_BLOCK = Pattern.compile(
|
||||||
|
"이벤트|광고|쿠폰|당첨|응모|추첨|프로모션|배너|모집|혜택|초대|수신거부|마케팅|사은품|경품|세일|"
|
||||||
|
+ "바로가기|자세히\\s*보기|클릭|구독|뉴스레터|안내문자|광고성");
|
||||||
|
// 약한 광고 표현 — 명확한 거래신호 없이 '결제'만 있을 때만 광고로 간주.
|
||||||
|
// (실거래 영수증/알림에도 '포인트 적립' 등이 붙는 경우가 있어 무조건 차단하지 않음)
|
||||||
|
private static final Pattern AD_WEAK = Pattern.compile(
|
||||||
|
"적립|포인트|캐시백|마일리지|리워드|무료|증정|할인");
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Builder
|
@Builder
|
||||||
@@ -54,15 +60,22 @@ public class CardNotificationParser {
|
|||||||
boolean deposit = raw.contains("입금");
|
boolean deposit = raw.contains("입금");
|
||||||
boolean withdraw = raw.contains("출금") || raw.contains("이체") || raw.contains("송금");
|
boolean withdraw = raw.contains("출금") || raw.contains("이체") || raw.contains("송금");
|
||||||
boolean refund = raw.contains("취소") || raw.contains("환불");
|
boolean refund = raw.contains("취소") || raw.contains("환불");
|
||||||
boolean approval = raw.contains("승인") || raw.contains("결제") || raw.contains("일시불") || raw.contains("할부");
|
boolean approval = raw.contains("승인") || raw.contains("일시불") || raw.contains("할부");
|
||||||
|
// '결제'는 광고 문구("결제하고 적립")에도 흔해 약신호로만 취급(단독으론 광고 의심)
|
||||||
|
boolean weakApproval = raw.contains("결제");
|
||||||
|
|
||||||
Matcher am = AMOUNT.matcher(raw);
|
Matcher am = AMOUNT.matcher(raw);
|
||||||
boolean strong = approval || deposit || withdraw || refund;
|
boolean realSignal = approval || deposit || withdraw || refund; // 명확한 거래신호
|
||||||
if (!am.find() || !strong) {
|
boolean anySignal = realSignal || weakApproval;
|
||||||
|
if (!am.find() || !anySignal) {
|
||||||
return notRecognized();
|
return notRecognized();
|
||||||
}
|
}
|
||||||
// 광고/이벤트 차단: (강신호는 위에서 보장되지만) 광고성 표현이 섞인 약한 케이스 방지용
|
// 1) 명백한 광고/이벤트 표현이면 거래신호가 있어도 무시(광고 푸시·문자 오등록 방지)
|
||||||
if (!strong && AD_HINT.matcher(raw).find()) {
|
if (AD_BLOCK.matcher(raw).find()) {
|
||||||
|
return notRecognized();
|
||||||
|
}
|
||||||
|
// 2) 약한 광고 표현(적립/포인트/할인 등)은 명확한 거래신호 없이 '결제'만 있으면 광고로 간주
|
||||||
|
if (!realSignal && AD_WEAK.matcher(raw).find()) {
|
||||||
return notRecognized();
|
return notRecognized();
|
||||||
}
|
}
|
||||||
long amount = Long.parseLong(am.group(1).replace(",", ""));
|
long amount = Long.parseLong(am.group(1).replace(",", ""));
|
||||||
|
|||||||
@@ -66,6 +66,39 @@ class CardNotificationParserTest {
|
|||||||
assertThat(p.isRecognized()).isFalse();
|
assertThat(p.isRecognized()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("승인 신호가 있어도 명백한 광고어(이벤트/쿠폰/혜택)면 무시")
|
||||||
|
void blocksAdEvenWithSignal() {
|
||||||
|
var p = parser.parse("[Web발신]", "삼성카드 승인 9,900원 이벤트 쿠폰 혜택 받으세요", today);
|
||||||
|
assertThat(p.isRecognized()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("실거래(승인)에 포인트/적립이 붙어도 인식 유지")
|
||||||
|
void keepsRealTxnWithPoints() {
|
||||||
|
var p = parser.parse("신한카드", "승인 50,000원 스타벅스 포인트 적립 120P", today);
|
||||||
|
assertThat(p.isRecognized()).isTrue();
|
||||||
|
assertThat(p.isIncome()).isFalse();
|
||||||
|
assertThat(p.getAmount()).isEqualTo(50000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("'결제'만 있고 약한 광고어(적립/포인트)면 광고로 무시")
|
||||||
|
void blocksPaymentOnlyAd() {
|
||||||
|
var p = parser.parse("[Web발신]", "5,000원 결제하고 포인트 적립 받기", today);
|
||||||
|
assertThat(p.isRecognized()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("'결제'만 있어도 광고어 없으면 지출로 인식")
|
||||||
|
void keepsPaymentOnlyWhenNoAd() {
|
||||||
|
var p = parser.parse("롯데카드", "결제 3,000원 편의점", today);
|
||||||
|
assertThat(p.isRecognized()).isTrue();
|
||||||
|
assertThat(p.isIncome()).isFalse();
|
||||||
|
assertThat(p.isBankTransfer()).isFalse();
|
||||||
|
assertThat(p.getAmount()).isEqualTo(3000L);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("금액 없으면 무시")
|
@DisplayName("금액 없으면 무시")
|
||||||
void requiresAmount() {
|
void requiresAmount() {
|
||||||
|
|||||||
Reference in New Issue
Block a user