4.2 KiB
Pet Insurance — Code Challenge
A Java coding challenge implementing a data driven calculation logic for a highly extensible pet health insurance product.
Build & test
Requires Java 25 and Maven 3.x.
# Compile
mvn compile
# Run all tests
mvn test
# Full build
mvn package
Project structure
src/
├── main/
│ ├── java/com/pfefferminzia/petinsurance/
│ │ ├── animals/ # Animal sealed interface + Dog, Cat, SmallAnimal records
│ │ ├── calculations/ # InsuranceContract, Calculations, CalculationResult
│ │ ├── exception/ # InvalidInsuranceContractException
│ │ └── tariffs/ # TariffOption, TariffConfiguration, TariffLoader
│ └── resources/
│ └── tariff.properties # Runtime tariff configuration
└── test/
├── java/.../calculations/
│ └── CalculationsTest.java
└── resources/
└── tariff.properties # Test tariff configuration (mirrors main)
Architecture
Animals
Animal is a sealed interface (permits Dog, Cat, SmallAnimal). Each type is an immutable record:
Dog(LocalDate birthday, String breed)— identifies with species key"DOG"Cat(LocalDate birthday)— species key"CAT"SmallAnimal(LocalDate birthday)— species key"SMALLANIMAL"
The interface exposes birthday() and speciesKey(). Base insurance sums are not hardcoded in the animal types; they live in configuration (see below).
Tariff configuration (tariff.properties)
All tariff data is loaded from tariff.properties at startup via TariffLoader.loadFromFile(), which produces a TariffConfiguration record. This makes adding new tariffs, animals, age brackets, or breed adjustments a config-only change.
# Animal base insurance sums
baseSum.DOG=2500.00
baseSum.CAT=2000.00
baseSum.SMALLANIMAL=1000.00
# Tariffs: factor,availableFromDate
tariffs.KOMPAKT=1.0,1970-01-01
tariffs.OPTIMAL=1.2,1970-01-01
tariffs.PREMIUM=1.4,2026-07-01
# Age-based premium factors (≤ age → factor; elderlyFactor covers > 7)
age.2=0.20
age.5=0.25
age.7=0.28
elderlyFactor=0.30
# Dog breed surcharge (positive) / discount (negative)
breed.DACKEL=-0.1
breed.BERNHARDINER=0.1
TariffConfiguration.getTariffFactor(tariffName, startDate) throws IllegalStateException if the chosen tariff is not yet available on the given start date (e.g. PREMIUM before 2026-07-01).
Calculation flow
- Build an
InsuranceContract(animal, insuranceStartDate, tariffName). The record constructor validates all fields eagerly and throwsInvalidInsuranceContractExceptioncollecting all violations. - Call
Calculations.calculateInsuranceSumAndYearlyPremium(contract), which returns aCalculationResult(insuranceSum, yearlyPremium).
Insurance sum = baseSum(animal) × tariffFactor
Yearly premium = insuranceSum × ageFactor × (1 + breedFactor)
ageFactoris looked up from the age bracket table based on exact years betweenbirthdayandinsuranceStartDate.breedFactoronly applies toDog; all other animals use 0 (no adjustment).
Worked examples
| Animal | Insurance sum | Yearly premium |
|---|---|---|
| Husky, 7y, PREMIUM | 3500 | 980 |
| Dackel, 7y, PREMIUM | 3500 | 882 |
| Bernhardiner, 7y, PREMIUM | 3500 | 1078 |
| Cat, 3y, OPTIMAL | 2400 | 600 |
| SmallAnimal, 1y, KOMPAKT | 1000 | 200 |
All five examples are covered by CalculationsTest.
Extensibility
- New animal type — add a record implementing
Animal, register its base sum intariff.properties. - New tariff — add one line to
tariff.propertieswith a factor and availability date. - New breed adjustment — add one line to the
breed.*section. - New age bracket — add one
age.Nentry;TariffConfiguration.getAgeFactoruses aNavigableMapceiling lookup so ordering is automatic. - New calculation — extend
Calculationsor add a new method;CalculationResultcan be widened to carry additional fields.