請求一件にいくら支払うのか
目標
約款の保障条件をコードに移して、請求1件の支払額とその根拠を出す判定器を作ります。自己負担金・共同負担率・1件あたりの限度・年間限度を決められた順序で適用し、免責期間と保障除外で拒否し、同じ事故の複数請求を合算したうえで、請求141件すべてを判定して集計と報告書を出します。
なぜ重要なのか
約款は言葉で書かれていて、順序が明示されていません。控除を先に行うのか比率を先に掛けるのか、自己負担金が請求ごとなのか事故ごとなのか、年間限度を事故日と受付日のどちらで数えるかによって、同じ請求の支払額が変わります。それなのに、どちらもエラーを出しません。 そのためこの判定は、値が合っているかどうか以前に、説明できるかどうかが先です。ルールの数値を表に出し、どのルールがいくら削ったかを請求ごとに残せば、その記録が顧客向けの案内文になり、回帰テストの期待値にもなります。 このラボの約款の数値(自己負担金1万ウォンから3万ウォン、共同負担10%から20%、1件あたりの限度30万ウォンから100万ウォン、年間限度100万ウォンから500万ウォン、免責期間30・90・180日)は、実際の商品ではなく、このラボのために作り出した合成の表です。 採点ツールは、判定の文言を信用しません。一時ディレクトリに採点ツールが作った約款・契約・請求を用意し、毎回違う数値で判定器を実際に実行して、支払額と判定根拠を、採点ツールが別に計算した値と突き合わせます。
ステップ
- /root/coverage/gen_claims.pyを作成して実行し、/root/coverage/claims.dbを作ってください。約款3種、契約12件、保障除外5行、請求141件です。
- 適用順序2通りの合計を比べ、/root/coverage/order_compare.jsonにclaims・order_a_total・order_b_total・differenceとして書いてください。
- /root/coverage/adjudicate.pyを作成し、自己負担金 → 共同負担 → 1件あたりの限度の順に削って、削った金額をstepsとして残してください。
- 保障期間外・免責期間内・保障除外項目の請求を拒否してください。decisionはdenied、payableは0で、reasonsには理由をすべて入れます。
- 自己負担金を事故1件につき1回だけ使うようにしてください。同じ事故の2件目の請求は、残りの自己負担金の分だけ削られます。
- 年間限度を入れてください。契約ごとに事故年度基準で積み上がり、受付日順に使い切られ、年が変わると再び復活します。
- 約款の数値を表からのみ読むようにし、共同負担計算の丸めを四捨五入(ROUND_HALF_UP)に固定してください。
- /root/coverage/make_payout.pyで請求141件をすべて判定し、/root/coverage/payout.jsonと/root/coverage/coverage_report.mdを作ってください。
参考
- 実行契約:
python3 /root/coverage/adjudicate.py --db <DB> --claim <청구번호>(プレースホルダーは請求番号です)は、判定JSONを1つの塊として標準出力に出し、0で終了します。存在しない請求番号なら2です。--allは、すべての請求の判定を1行に1つずつ出します。 - 判定JSON:
{"claim_id": …, "decision": "paid"|"denied", "billed": 정수, "payable": 정수, "reasons": [문자열], "steps": [{"rule": …, "amount": 깎은 금액, "after": 적용 뒤 금액}]}(プレースホルダーは順に、整数、整数、文字列、削った金額、適用後の金額です) - stepsのrule名は
deductible・coinsurance・per_claim_cap・annual_capで、適用した順に入れます。削った金額が0でも、ステップは残します。最後のステップのafterがpayableです。 - 拒否した請求はpayableが0・stepsが空リストで、reasonsに
out_of_period・waiting_period・excludedのうち該当するものをすべて入れます。 - 判定順序: 請求は受付日(filed_date)順、同じ日なら請求番号順に処理します。累積限度がこの順序に左右されます。
- 免責期間は、事故日と契約開始日の間の距離で見ます。受付日ではありません。
- よくある間違いは、自己負担金を請求ごとに引くこと、比率を先に掛けること、年間限度を請求1件だけを見て判断すること、丸めをPythonのデフォルトに任せることです。
- 約款の数値をコードに書いておかないでください。採点ツールが表の値を差し替えて、判定が追従するかを見ます。
約款表と請求のスナップショットを作る
/root/coverage/gen_claims.pyを作成して実行し、/root/coverage/claims.dbを作ってください。約款3種、契約12件、保障除外5行、請求141件(複数請求の事故31件)です。
まず/root/coverageを作り、その中でpython3でsqliteのDBを作ります。表はplan、policy、exclusion、claimの4つです。請求は事故100件に紐づいていて、1つの事故に請求が2件以上ある場合が31件です。
適用順序が合計をどれだけ広げるか
/root/coverage/order_compare.jsonにclaims・order_a_total・order_b_total・differenceを書いてください。Aは自己負担金を先に引いて共同負担率を適用した値の合計、Bは共同負担率を先に適用して自己負担金を引いた値の合計で、どちらも請求141件すべてが対象で、負の値は0とします。
このステップは判定ではなく比較です。限度・免責期間・保障除外は見ません。Aはmax(0, 請求額 - 自己負担金)から共同負担分を引いた値で、Bは請求額から共同負担分を引いたあとに自己負担金を引き、0で打ち切った値です。共同負担分は四捨五入でウォン単位の整数にします。
控除・共同負担・1件あたりの限度を順に適用する
/root/coverage/adjudicate.pyを作成し、--claimで受け取った請求の支払額を、自己負担金 → 共同負担 → 1件あたりの限度の順に計算して、各ルールが削った金額をstepsに残してください。
約款の数値はplan表から読みます。自己負担金は請求額より大きいことがあるのでminで打ち切り、1件あたりの限度は超える分だけを削ります。stepsの各要素はrule・amount・afterの3つのキーを持ち、最後のafterがpayableです。
保障期間・免責期間・除外項目で拒否する
保障期間外の事故、免責期間内の事故、保障除外項目の請求を拒否してください。decisionはdenied、payableは0、stepsは空リストで、reasonsにout_of_period・waiting_period・excludedのうち該当するものをすべて入れます。
免責期間は、事故日と契約開始日の間の距離です。受付日ではありません。日付はdate.fromisoformatとtimedeltaで扱ってください。保障期間外なら免責期間を問う必要もありませんが、除外項目は、それとは別に併記されることがあります。
自己負担金は事故1件につき1回
同じaccident_idの請求が、自己負担金を分け合うようにしてください。最初の請求が使い切れば、2件目の請求のdeductibleステップは0で、最初の請求が自己負担金より小さければ、残りの分を次の請求が使い切ります。
事故ごとに、すでに使った自己負担金を持ち回りながら、残りの分と請求額のうち小さいほうを削ります。請求を受付日順に処理してはじめて同じ答えが出ます。順序が変わると、どの請求が自己負担金を負担するかが変わります。
年間限度を使い切らせる
年間限度を4つ目のルールとして入れてください。契約ごとに事故年度基準で積み上がり、残りの余裕を超える分だけが削られ、年が変わると限度が再び復活します。stepsの最後はannual_capです。
年間限度は、その契約の先行する請求がいくら使ったかに左右されます。判定を受付日順に回しながら、(契約、事故年度)ごとに使った金額を累積してください。限度を使い切ったあとの請求は、支払額が0ですが拒否ではありません。decisionは依然としてpaidで、annual_capが全額を削ります。
数値は表に、丸めはポリシーに
約款の数値をplan表からのみ読むようにし(コードに値を書いておかないでください)、共同負担計算の丸めを四捨五入(ROUND_HALF_UP)に固定してください。
Pythonのround()とdecimalのデフォルトは、ちょうど半分の値を偶数側に寄せます。約款が四捨五入を述べているなら、Decimalのquantizeにroundingを明示する必要があります。採点ツールは、表の数値を差し替えて判定が追従するか、そして半分の位置が切り上がるかを見ます。
全体の判定と支払報告書
/root/coverage/make_payout.pyで請求141件をすべて判定し、/root/coverage/payout.json(claims・paid・denied・billed_total・payable_total・denied_by_reason・cut_by_rule)と、/root/coverage/coverage_report.md(## 무엇을 판정했나 ## 어떤 규칙이 얼마를 깎았나 ## 거절한 건과 이유 ## 남은 위험の4つの節。見出しは順に、韓国語で「何を判定したか」「どのルールがいくら削ったか」「拒否した案件と理由」「残るリスク」を意味する語です)を作ってください。
判定器を再実装せず、--allの出力を集めてください。cut_by_ruleはstepsのamountをルールごとに足した値で、denied_by_reasonはreasonsを理由ごとに数えた値です。理由が2つの請求は、2つの理由にそれぞれ1回ずつ数えます。報告書には支払合計を数字で書いてください。