2.1 정기보험#
이 챕터에서 배우는 것
정기보험 한 계약을 fastcashflow 로 평가하는 가장 짧은 코드 — 20 줄로 끝
손계산과 엔진 결과가 정확히 일치하는 것을 확인 (둘 다
39.11)BEL / RA / CSM 값이 의미하는 바와 부호 규약
한 계약 → 보유계약 portfolio (파일) 로 확장
흔한 실수와 그 자리에서 잡는 방법
이 챕터만 봐도 정기보험 평가는 끝까지 갈 수 있도록 만들었습니다. 다른 자료로 점프할 필요 없음.
상품 소개 — 정기보험#
정기보험 (term life insurance) 은 미리 정한 보험기간 동안 피보험자가 사망하면 사망보험금을 지급하는, 가장 단순한 형태의 생명보험입니다. 기간이 지나 만기 도달 시에는 어떤 환급도 없습니다.
본 챕터의 단순 정기보험 은 다음 구조를 가정합니다:
보험료는 매월 동일액 (level premium) 납입
보험금은 일정 금액 (level death benefit) — 감액 없음
사망 이외에 만기 시 어떤 지급도 없음 (만기환급금 0)
보험료 납입기간 = 보험기간 (전기납), 또는 짧게 (단기납)
이 챕터는 단순 정기보험 만 다룹니다. 보험료 납입면제 (waiver) / 다종 진단담보 / 만기환급금 결합 같은 변형은 후속 챕터에서 다룹니다.
한눈에 보기 — 입력 파일과 사용자 함수#
평가에 들어가기 전, 어떤 파일을 어떤 함수로 다루는지 전체 그림을 한 번 잡고 갑니다. 본 챕터의 코드는 이 트리를 그대로 따라갑니다. (같은 그림이 기초 part 의 한눈에 보기 에도 정리되어 있습니다.)
입력 파일과 입력 개체#
엔진은 두 클래스의 개체 만 받습니다 — Basis (산출기초 = 가정) 와
ModelPoints. measure(mp, basis) 호출의 두 인자가 바로 이 개체들.
사용자가 다루는 입력 파일들 은 reader 함수를 거쳐 이 두 개체로 모입니다.
Basis 는 개별 가정 (사망률·해지율·할인율 …) 을 묶은 산출기초이고,
ModelPoints 는 평가 대상 계약들입니다:
Basis클래스 (산출기초) —basis = fcf.read_basis(...)가 한 입력 파일 (basis.xlsx, multi-sheet 워크북) 을 읽어 산출기초 개체를 만듭니다.ModelPoints클래스 —mp = fcf.read_model_points(...)가 세 입력 파일 (policies.csv/coverages.csv/calculation_methods.csv) 을 읽어 한 개체로 합칩니다.
Basis 클래스 — 한 입력 파일에서 만드는 개체#
Basis (basis = fcf.read_basis(...))
└── basis.xlsx ── 산출기초 (multi-sheet workbook)
├── segments · (product, channel) → 어느 테이블 쓸지 (product_name / channel_name 라벨 컬럼을 둘 수는 있으나 엔진은 무시)
├── mortality_tables · table_id × sex × age → 사망률
├── lapse_tables · table_id × duration → 해지율
├── discount_tables · table_id × year → 할인율
├── expense_tables · table_id → 사업비 행 (acquisition / maintenance / ...)
└── coverages · 담보 코드 → 어느 위험률 테이블을 쓸지
ModelPoints 클래스 — 세 입력 파일에서 만드는 개체#
ModelPoints (mp = fcf.read_model_points(...))
├── policies.csv ── 보유 계약 (한 줄 = 한 계약, 가입 시점 영구 spec)
│ ├── mp_id · 계약 식별자 (다른 파일과 join 키)
│ ├── product · 어느 segment 가정을 쓸지 (basis 의 segments 와 맞물림)
│ ├── channel · 채널
│ ├── issue_age · 가입연령
│ ├── sex · 0 = 남, 1 = 여
│ ├── term_months · 보험기간 (개월)
│ ├── premium_term_months · 보험료 납입기간 (개월)
│ ├── issue_date · 가입일 (선택; 연도 cohort 그룹화에 사용)
│ └── count · 이 줄이 대표하는 계약 수 (없으면 1)
│
├── coverages.csv ── 담보 가입금액 (한 줄 = 한 (계약, 담보))
│ ├── mp_id · 담보가 붙는 계약의 식별자 (policies.csv 의 mp_id 와 join)
│ ├── coverage · 담보 코드 (calculation_methods 의 코드와 맞물림)
│ ├── amount · 가입금액 (보험금)
│ ├── premium · 월 보험료 (선택)
│ ├── waiting · 면책기간 개월수 (선택)
│ ├── reduction_end · 감액기간 종료 개월수 (선택)
│ └── reduction_factor · 감액기간 중 지급률 (선택, 0..1)
│
└── calculation_methods.csv ── 담보별 산출방식 (담보 코드 → 산출방식)
├── coverage · 담보 코드 (DEATH, CANCER, INPATIENT ...)
├── coverage_name · 사람 친화 라벨 (선택)
└── calculation_method · DEATH / MORBIDITY / DIAGNOSIS / ANNUITY / MATURITY
결산 모드 (보유계약 평가) 에서는 policies.csv 가 분기말 상태 컬럼
네 개를 더 갖는 inforce_2026Q1.csv 같은 한 파일로 들어옵니다 —
elapsed_months / count (잔존) / prior_csm (직전 분기 CSM) /
lock_in_rate (가입 시점의 할인율). reader 도
mp, state = fcf.read_inforce_policies(...) 로 바뀌어 두 개체를 돌려줍니다.
사용자 함수#
fastcashflow 사용자 API
├── 샘플 파일 폴더에 생성 (한 번만, 자기 파일이 있으면 생략)
│ ├── fcf.samples.export(dir, template="gmm") ── basis.xlsx + policies/coverages/calculation_methods (+inforce)
│ ├── fcf.samples.export(dir, template="vfa") ── 변액(VFA) 세트 (basis.xlsx + policies)
│ └── fcf.samples.templates() ── 사용 가능한 template 목록
│
├── 파일 읽어 들이기
│ ├── fcf.read_basis(path) ── basis dict 반환
│ ├── fcf.read_model_points(path, coverages=, ...) ── 신계약 평가용
│ └── fcf.read_inforce_policies(path, coverages=, ...) ── 결산 1-파일 reader
│
├── 평가
│ ├── fcf.gmm.measure(mp, basis) ── 신계약, 시간 trajectory 전체
│ ├── fcf.gmm.measure(mp, basis, full=False) ── 시점 0 headline 4 숫자 (빠름); basis 가 dict 면 세그먼트 라우팅
│ └── fcf.gmm.measure_inforce(mp, basis, state, full=) ── 결산(보유계약) 측정; full=False 면 headline 만
│
├── 결과 저장
│ ├── fcf.write_measurement(val, path) ── 결과 한 파일에 저장
│ └── fcf.gmm.measure_stream(policies, out_dir, basis, coverages=...) ── 메모리 초과 portfolio 스트리밍
│
├── 변동분해 (분기간 비교)
│ ├── fcf.roll_forward(m, period_months=...) ── 분기 사이 변동 분해
│ └── fcf.reconcile(movements) ── 분해 결과를 항별로 합산
│
└── 검증 / 시각화
├── fcf.gmm.trace(mp_index, mp, basis) ── 한 계약의 BEL 계산 ASCII 트리
├── fcf.gmm.trace_bel_step(mp_index, mp, basis, ...) ── 월별 BEL 식 전개
├── fcf.gmm.trace_csm_step(mp_index, mp, basis, ...) ── 월별 CSM 식 전개
└── fcf.plot_liability(m) / plot_cashflows(m) / plot_csm_runoff(m) ...
본 챕터는 위 그림 중 samples.export → read_* → measure → print 정도만 씁니다. 변동분해 / 시각화는 후속 챕터의 자리.
한 계약 — 손계산과 엔진 한 번에#
가장 단순한 케이스부터 시작합니다. 한 계약, 두 달짜리. 손계산이 그대로 잡히는 작은 예입니다.
예제 설정
가입연령 40세, 보험기간 2개월
월 사망률 1%, 해지 없음
사망보험금 12,000, 월 보험료 100
월 할인율 0.5%
사업비 0 (할인과 후방재귀에 집중)
손계산은 두 시점 (t=0, t=1) 의 cash flow 를 현재가치로 가져와 합칩니다:
t |
보유계약 |
사망보험금 (월중) |
보험료 (월초) |
|---|---|---|---|
0 |
1.0000 |
1.00 × 1% × 12,000 = 120.00 |
100.00 |
1 |
0.9900 |
0.99 × 1% × 12,000 = 118.80 |
99.00 |
월 할인율 0.5% 로 할인계수 — 월초 1 / (1.005)^t, 월중 1 / (1.005)^(t+0.5):
PV(보험료) = 100 × 1.000000 + 99 × 0.995025 = 198.51
PV(사망보험금) = 120 × 0.997512 + 118.80 × 0.992550 = 237.62
BEL = 237.62 − 198.51 = 39.11
이 39.11 이 엔진에서도 그대로 나오면 측정이 의도대로 동작하고 있다는 강한 신호입니다. 코드:
import numpy as np
import fastcashflow as fcf
# 사망률 함수 -- 월 사망률 1% 의 연 환산 (모든 sex/age/duration 에 동일)
death_fn = lambda s, a, d: np.full(a.shape, 1 - (1 - 0.01) ** 12)
# 해지율 함수 -- 해지 없음
lapse_fn = lambda s, a, d: np.full(d.shape, 0.0)
# 모델 포인트 (계약 하나)
mp = fcf.ModelPoints.single(
issue_age = 40, # 가입연령 40세
sex = 0, # 성별 (0=남, 1=여)
benefits = {0: 12_000}, # 0번 보장 (= DEATH) 의 보험금 12,000
premium = 100, # 월납 보험료 100
term_months = 2, # 보험기간 2개월
)
# 산출기초
basis = fcf.Basis(
mortality_annual = death_fn, # 보유계약 감쇠용 사망률 (위 death_fn)
lapse_annual = lapse_fn, # 해지율 (해지 없음)
discount_annual = 1.005 ** 12 - 1, # 연 할인율 (월 0.5% 의 연 환산)
ra_confidence = 0.75, # 위험조정 신뢰수준 75%
mortality_cv = 0.10, # 사망률 변동계수 10%
coverages = (
fcf.CoverageRate("DEATH", death_fn), # 사망 보장 1종 (청구 rate = death_fn)
),
)
m = fcf.gmm.measure(mp, basis)
print(f"BEL = {m.bel[0]:.2f}") # 최선추정부채
print(f"RA = {m.ra[0]:.2f}") # 위험조정
print(f"CSM = {m.csm[0]:.2f}") # 보험계약마진
print(f"Loss = {m.loss_component[0]:.2f}") # 손실요소
출력:
BEL = 39.11
RA = 16.03
CSM = 0.00
Loss = 55.14
손계산 39.11 과 엔진 39.11 이 정확히 일치합니다. 작은 toy 계약을 손으로 풀어 엔진을 신뢰할 수 있는지 확인하는 것이 cookbook 의 핵심 패턴입니다.
두 자리에 같은 사망률 함수를 넘기는 이유
mortality_annual 은 보유계약이 사망으로 감쇠 하는 율, coverages
의 CoverageRate("DEATH", ...) 는 그 사망 사건에 사망보험금이 지급
되는 율 — 엔진 안에서는 별개의 두 양입니다. 손계산은 둘이 같다는
가정에서 답을 도출했으므로, 코드에서도 같은 death_fn 을 두 자리에
공유합니다. 한 자리만 바꾸면 두 양이 silent 어긋나 BEL이 안 맞으니,
한 변수에 lift 후 두 자리에 통과시키는 게 안전한 패턴.
gmm.trace 로 한 줄씩 풀어 보기
fcf.gmm.trace(0, mp, basis) 한 줄이면 매월 cash flow, BEL의 후방
재귀, CSM의 전방 진행이 ASCII 트리로 풀려 나옵니다. 손계산과 엔진이
어긋날 때 어느 단계에서 갈렸는지 한눈에 확인. 자세한 사용은
검증 패턴.
결과 읽기 — BEL / RA / CSM#
부호 규약 (sign convention)#
fastcashflow 는 부채 관점에서 유출을 양수, 유입을 음수로 부호화하는 규약 (outflow-positive) 을 씁니다:
유입 (보험사가 받는 돈, 보험료) — 부채를 감소 시킴
유출 (보험사가 내는 돈, 사망보험금 + 사업비) — 부채를 증가 시킴
따라서
BEL = PV(claims) + PV(expenses) - PV(premiums)
BEL = +39.11 의 의미#
BEL이 양수 라는 것은 “예상 미래 사망보험금 + 사업비 유출” 이
“예상 미래 보험료 유입” 보다 크다는 뜻 — 즉 보험사 입장에서 손실이
나는 계약 (onerous). 가입 시점에 손실분이 즉시 인식되어
loss_component 로 잡힙니다 (IFRS 17 Sec. 47).
위 예제의 39.11 은 작은 toy 숫자지만, 실제 portfolio 의 BEL도 단위만 크게 같은 의미입니다. 음수 BEL은 이익이 예상되는 계약.
RA = 16.03 — 위험조정#
RA (Risk Adjustment = 위험조정) 는 미래 사망률 / 비용 / 해지의 불확실성 에 대해 보험사가 받는 보상. 75% 신뢰수준이면 “BEL + RA” 가 75% 백분위 부채 추정치에 해당.
확인 포인트
RA가 작으면 BEL의 불확실성이 작다는 뜻. mortality_cv (사망률
변동계수) 를 0.10 → 0.50 으로 바꾸면 RA가 5배 커집니다. 한 번
실험해보세요.
CSM = 0, Loss = 55.14 — 보험계약마진과 손실요소#
CSM (Contractual Service Margin = 보험계약마진) 은 IFRS 17 의 핵심 개념. 계약 가입 시점에 “이익이 날 거다” 라고 인식한 부분을 미래에 걸쳐 분산해서 손익으로 전환하기 위한 buffer.
가입 시점 (t=0):
CSM_0 = max(0, -FCF)whereFCF = BEL + RA매 기간 이자 부리 + 보장단위 비례 상각
이익이 나는 계약 (FCF < 0) → CSM > 0, 손실요소 = 0
손실이 나는 계약 (FCF > 0) → CSM = 0, 손실요소 = FCF
위 예제는 onerous 계약이므로 CSM = 0, Loss = 55.14
(= BEL + RA = 39.11 + 16.03).
full=True 와 full=False 의 차이#
측정은 함수 하나 — measure() — 이고, full 인자로 상세도만 고릅니다.
full=True (기본) 는 시간 trajectory 전체를, full=False 는 시점 0 의
headline 네 숫자만 돌려줍니다.
|
|
|
|---|---|---|
출력 |
시간 trajectory 전체 — BEL/RA/CSM/Loss 의 매월 값 + 현금흐름 6 갈래 |
시점 0 의 BEL/RA/CSM/Loss 만 |
용도 |
상세 검증 / 변동분해 / 시각화 / 보고용 |
대량 portfolio 평가, 민감도, 100만+ 계약 |
메모리 |
100만 MP × 120개월 ~ 9 GB |
100만 MP ~ 32 MB |
속도 (100만 MP) |
수 초 |
80–300 ms |
규칙: 시간 trajectory 가 필요하면 (검증 / 변동분해 / 시각화 / 보고)
full=True (기본), 시점 0 의 결과 4 개만 필요하면 (대량 portfolio 평가,
민감도) full=False. 두 경로는 서로 다른 커널 (full=True = rollforward,
full=False = fused) 을 쓰지만 시점 0 결과는 수치적으로 동일 (parity
test 가 자동 검증).
포트폴리오 평가 — 파일에서 읽기#
위는 한 계약을 코드로 직접 만든 평가였습니다. 실무에서는 보유계약이 수백 ~ 수천만 건이고, 보통 엑셀 / CSV 파일로 들어옵니다. fastcashflow 는 네 갈래의 파일을 입력으로 받습니다:
파일 |
내용 |
|---|---|
|
담보별 산출방식 — 담보 코드 → 산출방식 (DEATH / MORBIDITY / …) |
|
산출기초 — 사망률 · 해지율 · 할인율 · 사업비 · 위험조정 |
|
보유 계약 — 한 줄 = 한 계약 (가입연령 / 성별 / 보험기간 / 계약수) |
|
담보 가입금액 — 한 줄 = 한 (계약, 담보) |
각 파일의 자세한 구조는 튜토리얼 11장 에 정리되어 있습니다. 본 챕터에서는 패키지에 동봉된 샘플 파일을 그대로 씁니다 — 그대로 paste 하면 11건의 portfolio 가 평가됩니다:
import fastcashflow as fcf
# (1) 샘플 파일을 samples 폴더에 생성 (한 번만 -- 이미 자기 파일이 있으면 생략)
fcf.samples.export("samples", template="gmm", quiet=True) # basis.xlsx + policies / coverages / calculation_methods (+ inforce)
# (2) 읽어서 평가
basis = fcf.read_basis("samples/basis.xlsx") # {(product, channel): Basis}
mp = fcf.read_model_points(
"samples/policies.csv", # 계약 spec 파일
coverages="samples/coverages.csv", # 담보 가입금액 파일
calculation_methods="samples/calculation_methods.csv", # 담보별 산출방식 파일
)
# 한 segment 의 가정을 전체 portfolio 에 적용 — 상세 trajectory
detail = fcf.gmm.measure(mp, basis[("TERM_LIFE_A", "GA")])
# 같은 평가의 빠른 경로 — 시점 0 의 네 숫자만
fast = fcf.gmm.measure(mp, basis[("TERM_LIFE_A", "GA")], full=False)
print(f"<full=True — 시점 0 합계>")
print(f"BEL : {detail.bel.sum():>15,.0f}")
print(f"RA : {detail.ra.sum():>15,.0f}")
print(f"CSM : {detail.csm.sum():>15,.0f}")
print(f"Loss: {detail.loss_component.sum():>15,.0f}")
print()
print(f"<full=False — 시점 0>")
print(f"BEL : {fast.bel.sum():>15,.0f}")
print(f"RA : {fast.ra.sum():>15,.0f}")
print(f"CSM : {fast.csm.sum():>15,.0f}")
print(f"Loss: {fast.loss_component.sum():>15,.0f}")
출력 (샘플 그대로):
<full=True — 시점 0 합계>
BEL : -26,441,301
RA : 1,210,877
CSM : 27,593,495
Loss: 2,363,071
<full=False — 시점 0>
BEL : -26,441,301
RA : 1,210,877
CSM : 27,593,495
Loss: 2,363,071
full=True 와 full=False 의 시점 0 결과가 정확히 일치 — parity 가 항상
보장됩니다.
데모용 라우팅의 한계
위 코드는 한 segment 의 가정 (("TERM_LIFE_A", "GA")) 을 11건 전체에
적용합니다 — portfolio 안에 다른 (상품, 채널) 의 계약이 섞여 있어도
같은 가정을 씁니다. 실무에서는 각 계약을 자기 segment 의 가정에
라우팅하는 measure(mp, basis) 를 씁니다 — 자세한 건 11장.
자기 데이터로 돌리려면 (1) 단계를 건너뛰고 (2) 단계의 파일명을 자기
파일 경로로 바꾸면 됩니다. 입력은 늘 policies + coverages 두 프레임입니다
— 한 계약이 담보를 여럿 가질 수 있고, 면책 / 감액 같은 담보별 룰을 담아야
하므로 한 줄 = 한 (계약, 담보) 형태로 받습니다.
면책 / 감액 컬럼 (optional)#
coverages 프레임은 담보별 보장 룰을 세 개의 optional
컬럼으로 받습니다. 두 룰은 모두 가입 시점 (t=0) 에서 시작 하므로
별도의 *_start 컬럼은 없습니다.
컬럼 |
단위 |
적용 구간 |
|---|---|---|
|
정수 (개월) |
|
|
정수 (개월) |
|
|
실수 (0..1) |
감액기간 중 지급 비율 (보통 0.5) |
따라서 t < waiting → 0%, t < reduction_end → reduction_factor%,
그 이후 → 100%. 면책과 감액을 함께 두면 waiting <= reduction_end 가
일반적입니다 (예: waiting=3, reduction_end=24, reduction_factor=0.5
→ 첫 3개월 면책, 4 ~ 24개월 50%, 25개월부터 100%).
reduction_factor 만 있고 reduction_end 가 없으면 reader 가 거부합니다
(factor 가 영영 발동하지 않으므로). 자세한 동작은
검증 패턴 절을 참조하세요.
자주 쓰는 변형#
채널만 바꾸기 — 같은 상품, 다른 channel#
같은 상품 (TERM_LIFE_A) 의 GA / FC 두 채널은 해지율과 신사업비가
다릅니다. segment 키만 바꿔서 비교:
mp = fcf.samples.model_points()
basis = fcf.samples.basis()
for key in basis:
val = fcf.gmm.measure(mp, basis[key], full=False)
print(f"{str(key):22}: BEL={val.bel.sum():>14,.0f} RA={val.ra.sum():>9,.0f} CSM={val.csm.sum():>14,.0f}")
출력:
('TERM_LIFE_A', 'FC') : BEL= -30,983,596 RA=1,439,298 CSM= 31,667,935
('TERM_LIFE_A', 'GA') : BEL= -26,441,301 RA=1,210,877 CSM= 27,593,495
('HEALTH_A', 'FC') : BEL= -28,269,493 RA=1,550,078 CSM= 29,727,196
('HEALTH_A', 'GA') : BEL= -23,159,766 RA=1,317,173 CSM= 25,281,033
('HEALTH_A', 'TM') : BEL= -30,849,968 RA=1,317,173 CSM= 30,231,953
('WHOLE_LIFE_A', 'FC'): BEL= 3,108,262 RA=1,263,511 CSM= 10,452,822
('WHOLE_LIFE_A', 'GA'): BEL= 12,322,295 RA=1,045,613 CSM= 5,771,501
같은 보유계약, 같은 사망률 · 할인율이지만 채널의 해지율 · 신사업비 차이가 BEL과 CSM에 그대로 반영됩니다. 한국 상품 구조에서 (상품, 채널) 이 실질적인 가정 단위인 이유.
보유계약을 직접 만들기 — 빠른 실험용#
샘플 워크북 대신 코드로 가상의 portfolio 를 만들어 빠르게 실험:
import numpy as np
n_contracts = 1000 # 보유계약 1,000건
rng = np.random.default_rng(42) # 난수 생성기 (시드 42 — 매번 같은 값 재현)
portfolio = fcf.ModelPoints(
issue_age = rng.integers(25, 60, n_contracts), # 25 ~ 60세
sex = rng.integers(0, 2, n_contracts), # 0 또는 1
benefits = {0: rng.integers(10, 100, n_contracts) * 1_000_000}, # 1 ~ 10억
premium = rng.integers(3, 15, n_contracts) * 10_000, # 3 ~ 15만원
term_months = np.full(n_contracts, 120), # 모두 10년
calculation_methods = fcf.samples.calculation_methods(),
)
basis = fcf.samples.basis()[("TERM_LIFE_A", "GA")]
result = fcf.gmm.measure(portfolio, basis, full=False)
print(f"Total : {result.bel.sum():>15,.0f}") # 합계
print(f"Mean : {result.bel.mean():>15,.0f}") # 평균
print(f"Onerous: {(result.loss_component > 0).sum():>15,d}") # 손실 계약 수
보험료 납입기간 단기납 (보장기간 ≠ 납입기간)#
10년 만기, 5년만 보험료 납입:
mp = fcf.ModelPoints.single(
issue_age = 40, # 가입연령
benefits = {0: 100_000_000}, # 사망보험금 1억
premium = 140_000, # 5년만 내므로 더 큰 금액
term_months = 120, # 보장 10년
premium_term_months = 60, # 납입 5년
calculation_methods = fcf.samples.calculation_methods(),
)
보험료 frequency — 분기납 / 반기납 / 연납#
월납 외에:
mp = fcf.ModelPoints.single(
issue_age = 40, # 가입연령
benefits = {0: 100_000_000}, # 사망보험금 1억
premium = 70_000, # 매 분기 7만원
term_months = 120, # 보장 10년
premium_frequency_months = 3, # 분기납
calculation_methods = fcf.samples.calculation_methods(),
)
premium_frequency_months=12 이면 연납, =6 이면 반기납.
사망률 / 해지율 표를 바꾸려면
회사 경험률표로 평가하려면 워크북의 mortality_tables / lapse_tables
시트에 행을 추가하고 segments 시트의 mortality_table /
lapse_table 컬럼에서 그 table_id 를 가리키면 됩니다. 자세한 워크북
편집 가이드는 튜토리얼 11장.
함정 — 흔한 실수와 잡는 방법#
함정 1 — 존재하지 않는 segment 키#
basis = fcf.samples.basis()
# ✗ 없는 segment 키는 KeyError 입니다 (실행하지 마세요):
# basis[("TERM_LIFE_A", "TM")] # 샘플의 TERM_LIFE_A 는 GA / FC 만
basis.keys() 로 어떤 segment 가 있는지 먼저 확인:
print(sorted(basis.keys()))
# [('HEALTH_A', 'FC'), ('HEALTH_A', 'GA'), ('HEALTH_A', 'TM'),
# ('TERM_LIFE_A', 'FC'), ('TERM_LIFE_A', 'GA'),
# ('WHOLE_LIFE_A', 'FC'), ('WHOLE_LIFE_A', 'GA')]
자기 워크북에서는 segments 시트의 (product, channel)
조합이 그대로 키가 됩니다 (_DEFAULTS 행은 제외).
함정 2 — sex 코딩 (0 / 1)#
fastcashflow 의 성별 인코딩은 0 = 남, 1 = 여. 워크북의 policies
시트, mortality_tables 시트 모두 같은 규약. 일부 사내 표준 (M/F, 1/2)
과 다르므로 로드 전에 변환 필요.
함정 3 — 음수 BEL을 보고 놀람#
음수 BEL은 이익 계약 이라는 의미. 정상입니다. 손실 계약은 BEL 양수 + CSM 0 + Loss 양수의 조합. 신호 패턴:
BEL |
CSM |
loss_component |
의미 |
|---|---|---|---|
음수 |
양수 |
0 |
이익 계약 — 정상 |
0 |
0 |
0 |
손익분기 계약 |
양수 |
0 |
양수 |
손실 계약 (onerous) — 즉시 손실 인식 |
함정 4 — 자기 워크북의 table_id 매칭 누락#
segments 시트의 mortality_table 컬럼에 MORTALITY_STD 라고 적었는데
mortality_tables 시트엔 그런 table_id 가 없으면 로드 시 명확한
에러로 알려줍니다. 새 segment 를 추가할 때 자주 발생.
함정 5 — mortality_annual 과 DEATH coverage rate 의 미스매치#
위 “한 계약 평가” 예제에서 death_fn 을 두 자리 (mortality_annual 과
coverages 의 DEATH 행) 에 똑같이 넘긴 이유 — 한 자리만 override 하면
보유계약 감쇠와 사망보험금 청구가 silent 어긋나 손계산과 안 맞습니다.
워크북 로더는 이걸 자동으로 처리해주지만, 직접 Basis(...) 를
호출할 때는 항상 같은 callable 을 두 자리에 공유하는 게 안전한 패턴.
인접 레시피#
이 챕터를 읽고 나서 자연스럽게 갈 다음 자리들:
보장 청구 메커니즘 — DEATH 외에 다른 보장 (DIAGNOSIS / MORBIDITY) 이 엔진 안에서 어떻게 다른 알고리즘으로 처리되는지. 본 챕터가 한 가지 산출방식만 다루는 이유.
사망 + 단순 진단 일시금 (작성 예정) — 사망보험에 진단보험금 (CI = Critical Illness = 진단) 일시금 결합. 첫 번째 추가 담보 도입.
보험료 납입면제 (waiver) (작성 예정) —
STATE_MODELS["WAIVER"]입문. active → waiver 상태 추적.검증 패턴 —
gmm.trace/gmm.trace_bel_step/gmm.trace_csm_step으로 본 챕터의 숫자가 어디서 왔는지 풀어 보기.튜토리얼 11장 — 실무에서의 활용 — 네 갈래 입력 파일의 구조와 결산 워크플로.
전체 챕터 라인업은 쿡북 인덱스 참조.
기본 튜토리얼 (튜토리얼) 의 5장 (BEL 계산) / 6장 (RA 계산) /
7장 (CSM 계산) 이 본 챕터의 출력값을 도출하는 IFRS 17 의 자세한 수식과
손계산 예제를 다룹니다.
가정의 정확성과 결과의 의미
본 챕터의 BEL / RA / CSM 숫자는 샘플 가정 그대로 의 결과입니다. 실제 회사 평가에서는 mortality_cv / discount_annual / 사업비를 회사 설정에 맞춰야 의미 있는 숫자가 나옵니다. fastcashflow 의 결과는 “입력 가정에 충실한 산출” 이지, 회사 portfolio 의 진실값은 아닙니다.