You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
72 lines
2.7 KiB
Python
72 lines
2.7 KiB
Python
# backend/app/connectors/finance/normalize.py — provider → finance_tx 정규화 + upsert
|
|
import uuid
|
|
|
|
from sqlmodel import Session, select
|
|
|
|
from ...models import ExternalLink, FinanceTx
|
|
from ..base import NormalizedRecord, RawRecord
|
|
|
|
# 가맹점/내용 키워드 → life-data.js 카테고리 6종
|
|
_CATS = [
|
|
("식비", ["식당", "김밥", "백반", "마트", "푸드", "배달", "음식", "고기"]),
|
|
("카페·간식", ["카페", "커피", "스타벅스", "베이커리", "디저트", "투썸", "이디야"]),
|
|
("교통", ["택시", "버스", "지하철", "주유", "기차", "ktx", "교통", "카카오t"]),
|
|
("쇼핑", ["쿠팡", "올리브영", "백화점", "쇼핑", "무신사", "29cm", "패션"]),
|
|
("구독", ["넷플릭스", "유튜브", "스포티파이", "구독", "멤버십", "프라임"]),
|
|
("문화·여가", ["영화", "cgv", "공연", "전시", "서점", "교보", "여가", "헬스"]),
|
|
]
|
|
|
|
|
|
def categorize(merchant: str) -> str:
|
|
low = (merchant or "").lower()
|
|
for cat, kws in _CATS:
|
|
if any(kw in low for kw in kws):
|
|
return cat
|
|
return "기타"
|
|
|
|
|
|
def normalize_tx(account, raw: RawRecord) -> NormalizedRecord:
|
|
p = raw.payload
|
|
merchant = str(p.get("merchant", p.get("내용", p.get("가맹점", "")))).strip()
|
|
raw_amt = p.get("amount", p.get("금액", 0))
|
|
try:
|
|
amount = int(str(raw_amt).replace(",", "").replace("원", "").strip() or 0)
|
|
except ValueError:
|
|
amount = 0
|
|
category = p.get("category") or p.get("분류") or categorize(merchant)
|
|
fields = {
|
|
"account_id": account.id,
|
|
"date": str(p.get("date", p.get("거래일", p.get("날짜", "")))).strip(),
|
|
"merchant": merchant,
|
|
"amount": amount,
|
|
"category": category,
|
|
"account_label": account.name,
|
|
}
|
|
return NormalizedRecord(
|
|
entity_type="finance_tx",
|
|
external_id=raw.external_id,
|
|
fields=fields,
|
|
etag=raw.etag,
|
|
external_updated_at=raw.external_updated_at,
|
|
)
|
|
|
|
|
|
def upsert_tx(session: Session, norm: NormalizedRecord) -> tuple[str, bool]:
|
|
link = session.exec(
|
|
select(ExternalLink).where(
|
|
ExternalLink.external_id == norm.external_id,
|
|
ExternalLink.entity_type == "finance_tx",
|
|
)
|
|
).first()
|
|
if link:
|
|
tx = session.get(FinanceTx, link.entity_id)
|
|
if tx:
|
|
for k, v in norm.fields.items():
|
|
if v is not None and hasattr(tx, k):
|
|
setattr(tx, k, v)
|
|
session.add(tx)
|
|
return tx.id, False
|
|
tid = "ftx-" + uuid.uuid4().hex[:8]
|
|
session.add(FinanceTx(id=tid, **{k: v for k, v in norm.fields.items() if v is not None}))
|
|
return tid, True
|