Pinning Down Where 349 Won Split, Rule by Rule
Goal
You recalculate the unearned premium of mid-term canceled contracts, point to the differences made by the day count basis, rounding mode, and segment splitting at the contract level, and produce the correction list as well.
Why it matters
A refund looks like one line, "annual premium times unexpired days divided by total days," but hidden in that one line are three values that must be decided as policy. How you count days, where you cut, and whether you reflect mid-term changes. Two systems that chose the three values differently each calculate correctly and yet give different answers. What is needed then is not "there is a difference" but an explanation like "this contract's difference is due to the day count basis and that one's is due to segments." The grader does not compare the numbers you wrote against each other. It recomputes the reference values from the step 1 data and checks against them, so you must follow the rules in the instructions exactly.
Steps
- Create and run /root/prem/gen_prem.py to create /root/prem/policies.csv (60 rows) and /root/prem/changes.csv (15 rows).
- For each contract, write the total days, elapsed days, and unexpired days on the two bases, actual days and 30/360, to /root/prem/daycount.json.
- Calculate the unearned premium on the actual-days basis and write it to /root/prem/refund_actual.json.
- Calculate the same contracts on the 30/360 basis, write them to /root/prem/refund_30360.json, and leave the difference from step 3 too.
- Cut the contracts with coverage changes before and after the change date and write the per-segment refund to /root/prem/segments.json.
- Write the difference between the sum cut per segment and the value cut once at the end, and the monthly 12-way split, to /root/prem/rounding.json.
- Run the claims system and accounting system calculations side by side and write each contract's difference and cause to /root/prem/diff.json.
- Bundle the correction list and the conclusion into /root/prem/report.json and write the sha256 of policies.csv along with it.
Notes
- The step 1 contract rules (i runs from 0 to 59): policy_id is four digits from
P0001, the start date is 2024-01-01 plus(i * 59) % 730days, the expiry date is the day before the same date one year after the start date (if that date does not exist, pull it back to the last day of that month and then take the day before), pay_mode is monthly if i is odd and annual if even, the annual premium is120000 + (i * 7777) % 880001, and the cancellation date is the start date plus37 + (i * 29) % 300days. - The step 1 change rules: only contracts where the remainder of i divided by 4 is 2 have a change. The change date is the start date plus
30 + (i * 47) % 260days, and the new annual premium is연보험료 + 60000 + (i * 3331) % 200000(the placeholder is the annual premium). - Day definitions: total days counts both the start date and the expiry date, elapsed days is the number of days from the start date to the cancellation date (the cancellation day itself is not covered), and unexpired days is total days minus elapsed days.
- 30/360 (US) rule: if the day of the earlier date is 31, pull it to 30, and if the day of the later date is 31 and the earlier is 30 or more, pull it to 30, then count as
360 * 연차 + 30 * 월차 + 일차(the placeholders are the year, month, and day differences). For total days and unexpired days, add 1 to this in the same way as actual days so that both ends are included. - Refund: cut
연보험료 * 미경과일수 / 총일수to a 1-won unit (the placeholders are the annual premium, unexpired days, and total days). The policy for steps 3, 4, and 5 is ROUND_HALF_UP. - Segment unexpired days: count only the days on which the segment overlaps with the stretch from the cancellation date to the expiry date. If it does not overlap, 0.
- The two paths in step 7: the claims system is actual days · ROUND_HALF_UP · segments reflected, and the accounting system is 30/360 · truncation below one won (ROUND_DOWN) · segments ignored (the whole period with the original annual premium). For the cause, write only those for which the value changes when one rule in the accounting calculation is switched to the claims side, in the order
daycount,rounding,segment. - Common mistakes: putting the cancellation day itself into unexpired days, leaving out both-ends inclusion in 30/360, counting a segment as 1 day when it does not overlap the unexpired stretch, and dropping the remainder in the monthly split.
- Check:
python3 -c "import json;print(json.load(open('/root/prem/diff.json'))['by_cause'])"
Generate the contract and coverage change data
Create and run /root/prem/gen_prem.py to create /root/prem/policies.csv (60 rows) and /root/prem/changes.csv (15 rows).
The expiry date is tricky. When the same date one year after the start date does not exist (2024-02-29), you must pull it back to the last day of that month, and calendar.monthrange tells you the last day of that month. Write all dates as YYYY-MM-DD.
Separate the two day count bases
For each contract, write term_days, used_days, unused_days and the three 30/360 versions to /root/prem/daycount.json, and also leave the list of contracts whose insurance period contains February 29 and the number of contracts whose unexpired days differ between the two bases.
Actual days come directly from date subtraction. For 30/360, first apply the two rules that pull 31 down, and then add the year, month, and day differences. You must decide whether to include both ends the same way for the two bases for the comparison to hold.
Calculate the unearned premium on actual days
Write basis, rounding, each contract's unused_days and refund_krw, and total_krw to /root/prem/refund_actual.json. This step ignores coverage changes and calculates the whole period with the original annual premium.
Multiply and divide with Decimal and then cut at the 1-won place with quantize. If you calculate in floating point, 0.5 is not exactly 0.5, so choosing a rounding mode itself becomes meaningless.
Try changing the denominator to 30/360
Write the 30/360 refund, the diff_krw from step 3, and diff_rows and total_diff_krw to /root/prem/refund_30360.json.
You must change both the denominator and the numerator to 30/360. If you change only one side, the refund rate of a contract with a one-year period can exceed 1. diff_krw is the 30/360 value minus the actual-days value.
Split mid-term coverage changes into segments
Pick only the 15 contracts with coverage changes and write to /root/prem/segments.json the two segments (from, to, annual_krw, unused_days, refund_krw), the segment-summed refund_krw, the whole_period_refund_krw that ignores the change, and their difference gap_krw.
The earlier segment runs up to the day before the change date. Unexpired days count only the days on which the segment overlaps the stretch from the cancellation date to the expiry date, so if the change date is before the cancellation date, the earlier segment's unexpired days are 0. The denominator for both segments is the whole contract's total days.
Where to put the cutting place
Write segment_rounding (policies_with_gap, total_gap_krw) and monthly_split (policies, count, all_exact) to /root/prem/rounding.json. The 12-month split of a monthly-payment contract must sum exactly to the annual premium.
Compare the sum cut per segment with the value obtained by adding up all the exact per-segment values and then cutting just once. For the monthly split, give the quotient rounded down and then attach the remaining won as 1 won extra from the earlier months, and the sum matches.
Point to the difference between the two systems by rule
Write the rules of the two systems, each contract's claims_krw, acct_krw, diff_krw, causes, and diff_rows, total_diff_krw, and by_cause to /root/prem/diff.json.
Do not attach causes by guessing. Switch just one rule in the accounting calculation to the claims side and recompute, and only the rules for which the value changes are causes. You can just toggle each of the three rules once.
The correction list and the conclusion on one page
Write policies_sha256, policies, changed_policies, refund_actual_total_krw, refund_30360_total_krw, basis_gap_krw, segment_gap_policies, diff_rows, total_diff_krw, by_cause, corrections, policy_basis, policy_rounding, and verdict to /root/prem/report.json. verdict is claims-system-is-authoritative.
In corrections put only the contracts that differ, in ascending policy_id order, with only the four keys policy_id, from_krw, to_krw, and delta_krw. from is the value to be fixed (accounting), and to is the value taken as the reference (claims). Recompute the hash now from policies.csv.