뱅크샐러드
경력근거: 뱅크샐러드의 네이티브 기능 개발을 진행합니다. (홈, 자산, 가계부
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고는 홈과 자산과 가계부 같은 네이티브 기능 개발을 첫 업무로 두고, SwiftUI 와 Swift Concurrency 기반으로 옮기는 이관을 주도하라고 적으며, AI 로 만든 산출물을 테스트로 검증한다고 밝힙니다. 이 포트폴리오는 그 조건에 맞는 화면 하나를 새 구조로 만든 것입니다. 계산은 순수 함수로 두고 화면은 그 결과만 그려 테스트가 붙는 자리를 분명히 합니다. 다만 이 포트폴리오는 앱 화면과 계산 규칙 구현을 보여줍니다. 마이데이터 연동이나 금융 규제 대응에 대한 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
홈과 자산과 가계부와 송금과 인증서 같은 뱅크샐러드의 네이티브 기능 개발을 주요업무 첫 줄에 적습니다. 화면 하나가 그대로 제품이 되는 자리입니다.
UIKit 과 RxSwift 기반 레거시를 SwiftUI 와 Swift Concurrency 기반 구조로 옮기는 대규모 이관을 주도한다고 적습니다. 새로 만드는 화면은 새 구조로 시작해야 하는 자리입니다.
코드 작성부터 리뷰와 품질 확인까지 AI 도구를 쓰되, 금융 서비스인 만큼 산출물을 테스트와 코드리뷰로 사람이 한 번 더 비판적으로 검증한다고 밝힙니다.
이용자가 앱을 열면, 등록한 고정 지출과 계좌 잔액을 날짜별로 겹쳐 다음 달 중 잔액이 모자라는 날을 보여줍니다. 모자란다는 판정은 날짜별 잔액 계산 규칙이 정하고, 모델은 그날 어떤 출금이 겹쳐 모자라는지 이용자 말로 짧게 풀어 씁니다.
저는 카드 대금과 구독료가 여러 날에 나뉘어 빠집니다. 잔액은 앱에서 보이지만 다음 주에 무엇이 빠지는지는 따로 기억해야 해서, 이체가 실패하고 나서야 알게 된 적이 있습니다.
출금 실패가 생기는 이유는 돈이 없어서가 아니라 언제 얼마가 빠지는지 한 화면에서 안 보이기 때문입니다. 예정 출금을 잔액에 날짜별로 얹어 모자라는 날만 짚어 주면, 미리 옮겨 둘 금액이 정해집니다.
이용자가 쓰는 앱 화면 하나가 완결됩니다. 고정 지출과 잔액을 날짜별로 겹쳐 모자라는 날을 표시하고, 그날 빠지는 항목과 계산에 쓴 값을 같이 보여줍니다. 실제 계좌 연동은 예시 데이터를 넣는 것으로 대신하고 3주차로 미룹니다.
지난달 출금 기록 180건과 예측을 맞춰 보고, 날짜나 금액이 어긋난 항목을 유형별로 냅니다. 반복 주기 규칙을 그 결과로 손봅니다.
예정 출금과 잔액이 바뀌면 다시 계산해 모자라는 날이 새로 생길 때 알립니다. 이용자가 앱을 열지 않아도 먼저 압니다.
이용자가 앱을 열면 날짜별 잔액이 이어 계산되고, 모자라는 날이 그날 빠지는 항목과 같이 보입니다.
금액이 비거나 출금일이 없는 달이 오거나 등록되지 않은 계좌 코드가 섞인 입력 30종을 넣어도 앱이 죽지 않고, 계산에서 뺀 항목이 표시로 남습니다.
지난달 출금 기록 180건과 예측을 맞춰 어긋난 항목을 셉니다. 단, 정답과 데이터를 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 더미데이터 자가채점이며 실제 예측 정확도는 뱅크샐러드의 실 거래 데이터가 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 금액 결측·31일이 없는 달·주말 이월을 더미에 일부러 섞어 쉬운 문제만 푸는 함정을 피합니다.
모자란다는 계산이 곧 이체 실패를 뜻하지는 않습니다. 당일 입금이 들어오는 경우가 많으므로 판정을 결론으로 읽지 않게 계산에 쓴 값을 항상 같이 보여줍니다.
실제 도입에서는 카드사마다 결제일 이월 규칙이 달라 날짜 전개가 흔들립니다. 규칙이 늘어나면 코드에서 빼 설정으로 옮겨야 하며, 그것은 도입 단계 과제로 분리합니다.
계좌 간 이체를 실제로 실행하는 일은 금융 권한이 있어야 하므로 이 포트폴리오의 범위 밖입니다. 여기서는 모자라는 날을 미리 보여주는 데까지만 다룹니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
이용자가 등록한 고정 지출입니다. 계산의 입력이며 금액 결측과 말일 처리, 등록되지 않은 계좌 코드를 의도적으로 포함합니다.
| payment_no | label | amount_krw | day_of_month | account_code_raw | category | weekend_rule | note |
|---|---|---|---|---|---|---|---|
| PM-101 | 카드 대금 | 840,000 | 14 | AC-01 | card | after | — |
| PM-102 | 월세 | 750,000 | 25 | AC-01 | rent | before | — |
| PM-103 | 실손보험 | 42,000 | 31 | AC-01 | insurance | — | 31일이 없는 달 처리 필요 |
| PM-104 | 음악 구독 | 10,900 | 3 | AC-02 | subscription | — | — |
| PM-105 | 통신비 | — | 17 | AC-01 | subscription | — | 금액이 매달 달라 비워 둠 |
아래 JSON Schema 에 맞는 고정 지출 행을 더 만들어 주세요. {"type":"object","properties":{"payment_no":{"type":"string"},"label":{"type":"string"},"amount_krw":{"type":["integer","null"]},"day_of_month":{"type":["integer","null"]},"account_code_raw":{"type":"string"},"category":{"type":"string","enum":["card","subscription","rent","insurance"]},"weekend_rule":{"type":["string","null"],"enum":["before","after",null]},"note":{"type":["string","null"]}},"required":["payment_no","label","account_code_raw","category"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) amount_krw 나 day_of_month 가 null 인 행, (2) day_of_month 가 31 이거나 amount_krw 가 0 인 경계 행, (3) 같은 날짜에 몰린 항목 두 건, (4) 잔액 목록에 없는 account_code_raw. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"payment_no":{"type":"string"},"label":{"type":"string"},"amount_krw":{"type":["integer","null"]},"day_of_month":{"type":["integer","null"]},"account_code_raw":{"type":"string"},"category":{"type":"string","enum":["card","subscription","rent","insurance"]},"weekend_rule":{"type":["string","null"],"enum":["before","after",null]},"note":{"type":["string","null"]}},"required":["payment_no","label","account_code_raw","category"]}계좌별 잔액과 예정 입금입니다. 계산의 다른 축이며 입금일 미상과 잔액 갱신 지연을 포함합니다.
| balance_no | account_code | as_of | balance_krw | expected_income_krw | income_day | stale_hours |
|---|---|---|---|---|---|---|
| BL-001 | AC-01 | 2026-08-31 | 1,320,000 | 2,800,000 | 25 | 2 |
| BL-002 | AC-02 | 2026-08-31 | 96,000 | — | — | 5 |
| BL-003 | AC-01 | 2026-08-30 | 1,410,000 | 2,800,000 | 25 | 26 |
| BL-004 | AC-02 | 2026-08-29 | 112,000 | — | — | 50 |
| BL-005 | AC-01 | 2026-08-28 | 980,000 | 2,800,000 | 25 | 74 |
아래 JSON Schema 에 맞는 잔액 행을 더 만들어 주세요. {"type":"object","properties":{"balance_no":{"type":"string"},"account_code":{"type":"string"},"as_of":{"type":"string"},"balance_krw":{"type":"integer"},"expected_income_krw":{"type":["integer","null"]},"income_day":{"type":["integer","null"]},"stale_hours":{"type":["integer","null"]}},"required":["balance_no","account_code","as_of","balance_krw"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) expected_income_krw 나 income_day 가 null 인 행, (2) balance_krw 가 0 인 행, (3) stale_hours 가 하루를 넘겨 갱신이 밀린 행, (4) 고정 지출에는 없는 account_code. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"balance_no":{"type":"string"},"account_code":{"type":"string"},"as_of":{"type":"string"},"balance_krw":{"type":"integer"},"expected_income_krw":{"type":["integer","null"]},"income_day":{"type":["integer","null"]},"stale_hours":{"type":["integer","null"]}},"required":["balance_no","account_code","as_of","balance_krw"]}| Swift | 앱 본체의 언어입니다. 날짜별 잔액 계산을 순수 함수로 두어 테스트가 붙습니다. |
| SwiftUI | 달력과 항목 목록을 그리는 화면입니다. 상태가 바뀌면 계산 결과만 다시 그립니다. |
| Swift Concurrency | 잔액과 예정 출금을 불러오는 비동기 흐름을 다룹니다. |
| SwiftTesting | 계산 규칙의 경계 사례를 고정해 두는 테스트를 씁니다. 월말과 윤년과 주말 이월이 대상입니다. |
| Claude Code | 화면 골격과 반복되는 목록 코드를 만드는 데 씁니다. 계산 규칙은 직접 쓰고 테스트로 고정합니다. |
| LLM | 그날 계산이 끝난 뒤 어떤 출금이 겹쳤는지 이용자 말로 풀어 씁니다. 금액과 부족 판정은 규칙이 내고 모델은 숫자를 바꾸지 못합니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 지난달 실제 출금과 예측을 맞춰 보는 단계로 넘어갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 모자라는 날이 새로 생길 때 알리는 단계로 확장합니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
day_code사유 라벨 · payment_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
enough잔액 충분short잔액 부족weekend_shifted출금일 이월amount_missing금액 미등록day_missing출금일 미등록account_unmapped계좌 미매핑다음 달 하루치 판정입니다. 판정 코드와 그날 잔액과 출금 합계를 함께 남깁니다.
{"type":"object","properties":{"forecast_id":{"type":"string"},"payment_no":{"type":["string","null"]},"balance_no":{"type":["string","null"]},"target_date":{"type":"string"},"day_code":{"type":"string","enum":["enough","short","weekend_shifted","amount_missing","day_missing","account_unmapped"]},"shortfall_krw":{"type":["integer","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":["string","null"]},"value":{"type":["string","number","null"]}},"required":["field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["forecast_id","target_date","day_code","evidence","summary"]}[
{
"forecast_id": "df_0001",
"payment_no": "PM-104",
"balance_no": "BL-002",
"target_date": "2026-09-03",
"day_code": "enough",
"shortfall_krw": null,
"evidence": [
{
"field": "amount_krw",
"agg": null,
"value": 10900
},
{
"field": "balance_krw",
"agg": null,
"value": 96000
}
],
"summary": "음악 구독 출금액보다 잔액이 많아 그대로 빠져나갑니다.",
"hedge": null
},
{
"forecast_id": "df_0002",
"payment_no": "PM-109",
"balance_no": "BL-002",
"target_date": "2026-09-08",
"day_code": "enough",
"shortfall_krw": null,
"evidence": [
{
"field": "amount_krw",
"agg": null,
"value": 13900
},
{
"field": "balance_krw",
"agg": null,
"value": 96000
}
],
"summary": "영상 구독도 잔액 안에서 처리됩니다.",
"hedge": null
},
{
"forecast_id": "df_0003",
"payment_no": "PM-101",
"balance_no": "BL-001",
"target_date": "2026-09-14",
"day_code": "short",
"shortfall_krw": 220000,
"evidence": [
{
"field": "amount_krw",
"agg": null,
"value": 840000
},
{
"field": "balance_krw",
"agg": "on_date",
"value": 620000
}
],
"summary": "카드 대금이 빠지는 날 잔액이 220,000원 모자랍니다.",
"hedge": "합성 기준 판정이라 당일 입금이 들어오면 달라질 수 있습니다."
},
{
"forecast_id": "df_0004",
"payment_no": "PM-106",
"balance_no": "BL-001",
"target_date": "2026-09-01",
"day_code": "weekend_shifted",
"shortfall_krw": null,
"evidence": [
{
"field": "weekend_rule",
"agg": null,
"value": "after"
},
{
"field": "day_of_month",
"agg": null,
"value": 1
}
],
"summary": "출금일이 휴일이라 다음 영업일로 밀려 계산했습니다.",
"hedge": null
},
{
"forecast_id": "df_0005",
"payment_no": "PM-105",
"balance_no": "BL-001",
"target_date": "2026-09-17",
"day_code": "amount_missing",
"shortfall_krw": null,
"evidence": [
{
"field": "amount_krw",
"agg": null,
"value": null
},
{
"field": "label",
"agg": null,
"value": "통신비"
}
],
"summary": "금액이 매달 달라 비어 있어 그날 합계에서 뺐습니다.",
"hedge": null
},
{
"forecast_id": "df_0006",
"payment_no": "PM-107",
"balance_no": "BL-002",
"target_date": "2026-09-30",
"day_code": "day_missing",
"shortfall_krw": null,
"evidence": [
{
"field": "day_of_month",
"agg": null,
"value": null
},
{
"field": "amount_krw",
"agg": null,
"value": 89000
}
],
"summary": "출금일이 등록되지 않아 달력에 놓지 못했습니다.",
"hedge": null
},
{
"forecast_id": "df_0007",
"payment_no": "PM-108",
"balance_no": null,
"target_date": "2026-09-20",
"day_code": "account_unmapped",
"shortfall_krw": null,
"evidence": [
{
"field": "account_code_raw",
"agg": null,
"value": "AC-03"
},
{
"field": "account_code",
"agg": "count",
"value": 0
}
],
"summary": "잔액 목록에 없는 계좌라 얼마가 남는지 계산하지 못합니다.",
"hedge": null
},
{
"forecast_id": "df_0008",
"payment_no": "PM-103",
"balance_no": "BL-001",
"target_date": "2026-09-30",
"day_code": "weekend_shifted",
"shortfall_krw": null,
"evidence": [
{
"field": "day_of_month",
"agg": null,
"value": 31
},
{
"field": "label",
"agg": null,
"value": "실손보험"
}
],
"summary": "9월에는 31일이 없어 말일로 옮겨 계산했습니다.",
"hedge": null
},
{
"forecast_id": "df_0009",
"payment_no": "PM-102",
"balance_no": "BL-008",
"target_date": "2026-09-25",
"day_code": "enough",
"shortfall_krw": null,
"evidence": [
{
"field": "amount_krw",
"agg": null,
"value": 750000
},
{
"field": "balance_krw",
"agg": null,
"value": 3620000
}
],
"summary": "급여가 들어온 날이라 월세와 학자금이 함께 빠져도 남습니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. day_code 가 enum 여섯 값 중 하나인지 확인합니다. 2. forecast_id·target_date·day_code·evidence·summary 가 모두 채워졌는지 확인합니다. 3. day_of_month 가 1 이상 31 이하이고 amount_krw 가 0 이상인지 확인합니다. 4. evidence 의 field 이름이 고정 지출과 잔액 데이터에 실재하는 컬럼인지 대조합니다. 5. agg 가 없는 evidence 의 value 가 그 payment_no 또는 balance_no 행의 값과 같은지 확인합니다. 6. shortfall_krw 가 evidence 의 출금액과 그날 잔액의 차이와 같은지 다시 계산합니다. 금액이나 날짜가 비면 shortfall_krw 도 null 인지 확인합니다. 7. summary 가 존댓말로 끝나는지 확인합니다. [평가 러너 · 2주차] 지난달 실제 출금 기록 180건과 예측을 비교해 날짜나 금액이 어긋난 항목을 유형별로 냅니다. [수동 검증 · 사람이 봅니다] 1. 휴일 이월 규칙이 카드사별로 맞는지 표본 20건을 눈으로 확인합니다. 2. 금액이 매달 달라지는 항목을 평균으로 채울지 비워 둘지는 이용자 설정으로 정합니다.