GIRateEngine — Motor Comprehensive Rating Methodology

How the premium gets calculated

Every quote starts from a tariff rate, passes through thirteen independent loading factors, gets grossed up once, then has every applicable discount subtracted — with a floor check at the end that can claw discounts back if the result lands below the allowed minimum. This is the plain-language walkthrough of that pipeline, with a worked example at real magnitude.

Basic Premium — tariff + loadings Discounts — subtracted after gross-up Minimum-premium floor — can adjust both Reflects PremiumCalculation.GetPremium_Comp, current as of 2026-07-30
01 — Overview

One pipeline, four stages

Unlike the Medical rate engine, Motor Comprehensive doesn't blend two independent rates — it's a single linear pipeline. A tariff rate is selected, loaded up with rating factors, grossed up once, discounted, and finally checked against a minimum-premium floor that can push back on either the discounts or the premium itself.

%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#e6eef4','primaryBorderColor':'#1c5f8a','primaryTextColor':'#1c5f8a','lineColor':'#8a8272','fontFamily':'Georgia, serif','fontSize':'14px'}}}%%
flowchart TD
    A["Basic Tariff
rate selected by product/scheme/vehicle/region,
compared against a TPL floor and a tariff minimum"]:::book B["13 Loading Factors
Gender · Marital · Insured Age · Nationality · Body Type ·
City · Profession · Usage · Region · Deductible · Repair Type · NCD · Sum Insured
(+ conditional Accident Loading)"]:::book C["NRP → Gross
one Applicable-Loading gross-up"]:::book D["Discounts
NCD · Scheme/User · Loyalty · Driver ·
Promo · Other · Entity"]:::exp E["Minimum-Premium Floor
NetGross checked against the cached floor"]:::floor F["Final Premium
one row per deductible option"]:::final A --> B --> C --> D --> E --> F classDef book fill:#e6eef4,stroke:#1c5f8a,color:#1c5f8a,stroke-width:1.5px classDef exp fill:#f6ecdd,stroke:#a8621a,color:#a8621a,stroke-width:1.5px classDef floor fill:#f1e7f5,stroke:#7a3b8f,color:#7a3b8f,stroke-width:1.5px classDef final fill:#201c16,stroke:#201c16,color:#f7f4ec,stroke-width:1.5px
i

The whole pipeline runs once per deductible option on the product — the API returns a list, one row per deductible, so the caller can show the customer several excess levels side by side.

02 — Basic Premium

Selecting the tariff rate

The starting point isn't a single lookup — it's a tariff row selected by the vehicle and policy attributes, then compared against two independent floors before anything else happens.

Step 1 — Find the matching tariff row(s)

The cached comprehensive tariff table is filtered by an exact match on Policy Type, Product, Scheme, Vehicle Group, Vehicle Usage, and Repair Type, plus a range match on Vehicle Age and Sum Insured. If the request already carries an explicit region, only that region's rows are kept; otherwise the tariff rows are grouped by deductible and the highest-rate row in each group wins.

Region resolution (when no region was pre-selected)
Three candidate regions are compared: Insured's Address Region, Insured's ID Region, Vehicle Registration Region. If exactly one region code repeats across the three AND all three map to the same "actual region" → Final Region = that repeated code Otherwise → Final Region = 0 (no city-specific row applies; city-level tariff rows are skipped)

Step 2 — Compare against the TPL floor

A notional Third-Party-Liability-only premium is computed for the same vehicle, then grossed up by a configurable "Minimum Premium Configuration %". The basic premium can never be quoted below this TPL-based floor:

Final Basic Premium
TPL_Floor = TPL_BasicPremium + (TPL_BasicPremium × MinPremiumConfig% / 100) Final Basic Premium = MAX( Rate × SumInsured ÷ 100, TPL_Floor, Tariff MinPremium )

Whichever of the three wins is tagged on the result (BasePremium / TPLBasic / MinPremium) so it's traceable which floor actually determined the price.

03 — Loading factors

Thirteen independent loadings

Every loading is a percentage of the Basic Premium, looked up independently and added on top — none of them compound against each other, they all apply to the same base figure.

FactorKeyed byNotes
GenderInsured's gender code
Marital StatusInsured's marital status code
Insured AgeInsured's age (range match)Netted against Driver Age Loading later — see §3
Vehicle NationalityVehicle's nationality-of-make code
Body TypeVehicle type code
Registration CityVehicle registration city code
ProfessionInsured's profession/occupation code
Vehicle UsageUsage code (private, commercial, …)
RegionVehicle registration region code
DeductibleThe deductible option being pricedOne value per row in the result list
Repair TypeAgency vs. non-agency repair
NCD (loading)Claim-free yearsDistinct from the NCD discount applied later — see §5
Sum InsuredSum Insured band

Conditional — Accident Loading

Only evaluated when the request carries at least one prior accident (NoOfAccidents > 0). The match is resolved by priority, exact values beating wildcards in this order: Product, then Source (Aggregator / Online / POS vs. "All"), then Vehicle Usage — mirroring how the same loading is resolved server-side in the SP.

NRP — Net Rate Premium
NRP = Basic Premium + Σ (Basic Premium × Loading%i / 100) for all 13 factors above
04 — Gross-up

One gross-up, from NRP to Gross

A single "Applicable Loading %" — the product's own margin/expense load — grosses NRP up into the Gross premium. This is the figure every discount in the next section is subtracted from.

Gross
Gross = NRP ÷ (100 − Applicable Loading%) × 100
05 — Discounts

Every discount comes off Gross

Each discount is its own percentage of Gross, and they're all subtracted independently — order doesn't compound them, they're all computed against the same Gross figure.

DiscountSourceNotes
NCD (No-Claims Discount)Claim-free yearsSeparate from the NCD loading in §3
Scheme / UserScheme-level discount, falling back to a per-user discount if the scheme has none
LoyaltyOnly if the request flags IsLDApplicableCaller determines eligibility upstream — see §8
DriverOnly if the request flags a qualifying female driver
PromoPassed directly in the requestCaller supplies the %
OtherPassed directly in the requestCaller supplies the %
EntityPassed directly in the requestCaller supplies the %
NetGross
NetGross = Gross NCD Loyalty User/Scheme Driver Promo Other Entity

Driver Age Loading — netted, not stacked

A separate age-based loading tied to the driver's minimum age is looked up and applied against (Gross − NCD − Loyalty). To avoid charging twice for the same "young driver" risk that the Insured-Age loading in §3 may have already priced in, the Insured-Age loading amount is subtracted from it — and if that leaves nothing, no additional charge is made at all:

Driver Age Loading amount
Raw Driver-Age Charge = (Gross − NCD − Loyalty) × DriverAgeLoad% / 100 Driver Age Loading Amount = Raw Charge − Insured-Age Loading Amount if Raw Charge is bigger Driver Age Loading Amount = 0 otherwise
06 — Minimum-premium floor

The floor can claw back discounts — or gross up the premium

If NetGross ends up below the cached minimum premium for this Product/Vehicle-Group/Region/Claim-Free-Years combination, the engine doesn't just clamp the number — it re-derives it two different ways depending on which discounts are allowed to move.

%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#f1e7f5','primaryBorderColor':'#7a3b8f','primaryTextColor':'#7a3b8f','lineColor':'#7a3b8f','fontFamily':'Georgia, serif','fontSize':'14px'}}}%%
flowchart TD
    A{"NetGross < Minimum
Premium floor?"} B["No adjustment needed —
NetGross stands as the final premium"] C["Split discounts into
Adjustable vs. Non-Adjustable
(per this floor's own configuration)"] D{"Gross − Non-Adjustable
discounts still above the floor?"} E["Shrink the adjustable discounts
proportionally, just enough to land
exactly on the floor"] F["Gross up the premium instead
the floor can't be reached by
cutting discounts alone"] A -- Yes --> C --> D D -- Yes --> E D -- No --> F A -- No --> B
Case 1 — shrink adjustable discounts
MaxAdjustableDiscAmt = (Gross − NonAdjustableDiscAmt) − MinPremium AdjDiscVal = MaxAdjustableDiscAmt ÷ TotalAdjustableDiscPer Each adjustable discount is then re-scaled: New Discount% = (Old Discount% × AdjDiscVal) ÷ Gross × 100
Case 2 — gross up the premium
Only reached if shrinking every adjustable discount to zero still isn't enough: RevCalLoadAmt = ( MinPremium ÷ (100 − TotalDiscPer) × 100 ) − Gross New Gross = Gross + RevCalLoadAmt
!

Which discounts count as "non-adjustable" is itself configuration, looked up per minimum-premium rule — not every discount is eligible to be trimmed. A scheme discount marked non-adjustable is protected even when the floor is breached; only the discounts flagged adjustable get scaled back.

07 — Worked example

Putting it together

Figures below are illustrative, not real tariff data — chosen to show the mechanics at a realistic order of magnitude for a mid-range comprehensive policy.

Illustrative — Sum Insured 500,000, private-use sedan, agency repair
Tariff Rate = 2.1%  →  Base Premium = 2.1% × 500,000        = 10,500.00
TPL Floor comparison:   TPL Basic 950 + 10% config           =  1,045.00  (lower, ignored)
Tariff Minimum Premium                                        =    900.00  (lower, ignored)
Final Basic Premium (BasePremium wins)                        = 10,500.00

Loadings (of 10,500.00):
  Marital Status   3%  →    315.00
  Insured Age     15%  →  1,575.00
  City             1%  →    105.00
  Region           2%  →    210.00
  Repair Type      8%  →    840.00
  (Gender, Nationality, Body Type, Profession, Usage,
   Deductible, Sum Insured, NCD-loading, Accident — all 0% in this example)
                                Loadings total               =  3,045.00

NRP  = 10,500.00 + 3,045.00                                   = 13,545.00

Applicable Loading % = 10%
Gross = 13,545.00 ÷ (100 − 10) × 100                           = 15,050.00

Discounts (of 15,050.00):
  NCD (5 claim-free years)   20%  →  3,010.00
  Scheme/User discount        5%  →    752.50
  (Loyalty, Driver, Promo, Other, Entity — not applicable here)

NetGross = 15,050.00 − 3,010.00 − 752.50                       = 11,287.50

Driver Age Loading check:
  Raw charge = (15,050.00 − 3,010.00) × 5%                     =    602.00
  Insured-Age loading already charged                          =  1,575.00
  Raw charge < Insured-Age amount → Driver Age Loading applied =      0.00

Minimum-premium floor for this cell = 9,000.00
  NetGross (11,287.50) is above the floor → no reverse calculation triggered

FINAL PREMIUM  =  11,287.50
Same policy, but with a steeper 45% NCD discount pushing below the floor
Gross (unchanged)                                              = 15,050.00
NCD 45%  →  6,772.50   |   Scheme/User 5% (non-adjustable)  →     752.50
NetGross = 15,050.00 − 6,772.50 − 752.50                       =  7,525.00

Minimum-premium floor                                          =  9,000.00
7,525.00 < 9,000.00  →  reverse calculation triggers

Gross − NonAdjustable (Scheme/User 752.50) = 14,297.50, which is
still above the 9,000.00 floor → Case 1 applies (shrink NCD, not gross-up)

MaxAdjustableDiscAmt = 14,297.50 − 9,000.00                    =  5,297.50
AdjDiscVal = 5,297.50 ÷ 45%                                    = 11,772.22
New NCD% = (45% × 11,772.22) ÷ 15,050.00 × 100                 ≈    35.20%
New NCD amount = 15,050.00 × 35.20%                            ≈  5,297.60

FINAL PREMIUM ≈ 15,050.00 − 5,297.60 − 752.50                  ≈  9,000.00 ← lands on the floor
08 — Known gaps

What this pipeline doesn't cover yet

This engine was built to remove per-request database load from quoting. Additional Covers and VAT are now implemented (2026-07-30) — see §9 for what's priced and what's deliberately excluded. What's left, in full detail in Project Details.md:

Additional Covers — every eligible non-basic cover (Personal Accident, Replacement Vehicle, Zero Depreciation, etc.) is now priced alongside the base premium, gated by an eligibility whitelist per vehicle. Geographic-extension covers and Replacement Cover are intentionally excluded — production's own live quote flow doesn't exercise them either.

VAT — a flat per-product rate (currently 15% for Motor Comp) is now applied to both the base premium and every additional cover.

  • Comprehensive premium CAP (a maximum-premium ceiling for certain vehicle/product combinations) is not applied — verified dormant (zero active rows) as of this review, so not a launch blocker.
  • Cross-Sell and Multi-Vehicle discounts require a live lookup of the customer's existing policies — they're structurally hard to pre-cache and are currently not computed.
  • Motor Scheme Discount cap (a ceiling on the combined total of all discounts) is not enforced, so heavily-discounted quotes could combine discounts beyond what a scheme actually allows.
  • Loyalty, Promo, Other, and Entity discounts are taken as given by the caller rather than independently verified server-side, unlike in the production procedure.
09 — Additional Covers

Pricing the optional covers

Computed once per deductible option, right after that row's own Gross and minimum-premium floor are final — matching how production sources the Gross figure it prices add-ons from.

Pricing typeHow Gross is derived
FixedA flat rate, or Rate × Passenger Seats for per-seat covers (Personal Accident Benefit)
Applicable at Cover levelRate looked up by Region × Body Type × Nationality × Vehicle Group × Usage × Deductible-band × Insured-Age-band × Manufacture-Year-band, then Rate × Gross ÷ 100 (or a flat minimum, or per-seat, depending on the cover)
Per-cover total
GrossAmount = MAX( computed rate above, Cover's own Minimum Premium ) Loading Amount = Σ (GrossAmount × Loading%) only for the loading factors flagged for this specific cover Cover Total = GrossAmount + Loading Amount Cover Total + VAT = Cover Total × (1 + VAT%)
!

A non-mandatory cover is only offered if an active eligibility rule exists for this vehicle's Make/Model/Body Type/Nationality/Vehicle-Age/Deductible-band — otherwise it's silently left off the list, exactly as production does. Mandatory covers bypass this check entirely.

Fixed 2026-07-30: the Deductible-band check above must compare against a real deductible amount, not just a code. It was initially resolving that amount from the request's own DeductableCode field — which is 0 whenever a caller asks for every deductible option at once, causing deductible-banded covers (e.g. Personal Accident Benefit — Driver/Passenger) to silently disappear. Each returned premium row already carries its own resolved deductible code from its matched tariff row, so Additional Covers now price off that instead — correctly, for every deductible option in the response, not just a single caller-specified one. Every additional cover now also carries its own DeductableCode so it's traceable which option it belongs to.

10 — Reference

Formula reference

StageFormulaNotes
Final Basic PremiumMAX(Rate × SumInsured ÷ 100, TPL Floor, Tariff MinPremium)TPL Floor = TPL Basic × (1 + MinPremiumConfig%)
NRPBasic Premium + Σ(Basic Premium × Loading%)13 independent loading factors, see §3
GrossNRP ÷ (100 − Applicable Loading%) × 100Single product-level gross-up
NetGrossGross − NCD − Loyalty − User/Scheme − Driver − Promo − Other − EntityAll discounts computed independently off Gross
Driver Age LoadingMAX(0, (Gross−NCD−Loyalty)×Rate% − Insured-Age Loading Amount)Prevents double-charging young-driver risk
Min-Premium shrinkAdjDiscVal = (Gross − NonAdjDisc − MinPremium) ÷ Σ AdjustableDisc%Only if shrinking discounts alone can reach the floor
Min-Premium gross-upRevCalLoadAmt = MinPremium ÷ (100 − TotalDiscPer) × 100 − GrossUsed only when shrinking discounts to zero still isn't enough