Main Classes
Core orchestration, data loading, requests, results, and history.
package parvocure;
import java.nio.file.Path;
import java.util.List;
public class CheckoutService {
private static final double ML_PER_FL_OZ = 29.5735295625;
private final OrderHistory history;
private final DogAgeCalculator age;
private final DosageCalculator dosage;
private final MedicineCostCalculator medicineCost;
private final DiscountCalculator discount;
private final ShippingCalculator shipping;
private final TaxCalculator tax;
private final TotalCostCalculator total;
private final DataTables data;
public List<String> usStates = List.of(
"Alabama",
"Alaska",
"Arizona",
"Arkansas",
"California",
"Colorado",
"Connecticut",
"Delaware",
"Florida",
"Georgia",
"Hawaii",
"Idaho",
"Illinois",
"Indiana",
"Iowa",
"Kansas",
"Kentucky",
"Louisiana",
"Maine",
"Maryland",
"Massachusetts",
"Michigan",
"Minnesota",
"Mississippi",
"Missouri",
"Montana",
"Nebraska",
"Nevada",
"New Hampshire",
"New Jersey",
"New Mexico",
"New York",
"North Carolina",
"North Dakota",
"Ohio",
"Oklahoma",
"Oregon",
"Pennsylvania",
"Rhode Island",
"South Carolina",
"South Dakota",
"Tennessee",
"Texas",
"Utah",
"Vermont",
"Virginia",
"Washington",
"West Virginia",
"Wisconsin",
"Wyoming"
);
public CheckoutService() {
this(DataLoader.load(Path.of("data")), new OrderHistory());
}
public CheckoutService(DataTables data) {
this(data, new OrderHistory());
}
public CheckoutService(DataTables data, OrderHistory history) {
this.data = data;
this.history = history;
this.age = new DogAgeCalculator();
this.dosage = new DosageCalculator(data.breedBase());
this.medicineCost = new MedicineCostCalculator();
this.discount = new DiscountCalculator();
this.shipping = new ShippingCalculator(data.shippingFee());
this.tax = new TaxCalculator(data.taxRatePercent());
this.total = new TotalCostCalculator();
}
public OrderQuote checkout(CheckoutRequest request) {
validateRequest(request);
if (history.hasOrderWithin30Days(request.ownerId(), request.purchaseDate())) {
throw new IllegalArgumentException(
"owner already ordered a dose within the previous 30 days");
}
double dogAgeDisplay = age.calculate(request.dogAgeYears());
double dosageMl = dosage.calculateMl(
request.dogBreed(), request.dogAgeYears(), request.dogWeightKg());
double medicine = medicineCost.calculate(dosageMl);
double net = discount.calculate(
medicine,
request.coupons(),
request.ownerId(),
request.purchaseDate(),
history);
double discountAmount = medicine - net;
double shippingCost = shipping.calculate(request.deliveryLocation());
double taxAmount = tax.calculate(net, request.deliveryLocation());
double totalCost = total.calculate(net, shippingCost, taxAmount);
boolean us = usStates.contains(request.ownerLocation());
double dosageDisplayValue = us ? dosageMl / ML_PER_FL_OZ : dosageMl;
String dosageDisplayUnit = us ? "fl oz" : "mL";
history.record(
request.ownerId(),
net,
taxAmount,
shippingCost,
request.purchaseDate(),
request.coupons());
return new OrderQuote(
dogAgeDisplay,
dosageMl,
dosageDisplayValue,
dosageDisplayUnit,
medicine,
discountAmount,
net,
shippingCost,
taxAmount,
totalCost);
}
private static void validateRequest(CheckoutRequest request) {
if (request == null) throw new IllegalArgumentException("request is required");
if (request.ownerId() == null || request.ownerId().isBlank()) {
throw new IllegalArgumentException("ownerId is required");
}
if (request.ownerLocation() == null || request.ownerLocation().isBlank()) {
throw new IllegalArgumentException("ownerLocation is required");
}
if (request.deliveryLocation() == null || request.deliveryLocation().isBlank()) {
throw new IllegalArgumentException("deliveryLocation is required");
}
if (request.dogBreed() == null || request.dogBreed().isBlank()) {
throw new IllegalArgumentException("dogBreed is required");
}
if (request.coupons() == null) {
throw new IllegalArgumentException("coupons are required");
}
if (request.purchaseDate() == null) {
throw new IllegalArgumentException("purchaseDate is required");
}
}
}
package parvocure;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class DataLoader {
private DataLoader() {}
public static DataTables load(Path dataDir) {
try {
Map<String, Double> breedBase = loadBreedBase(dataDir.resolve("breed_dosage.csv"));
LocationMaps locations = loadLocations(dataDir.resolve("location_rate.csv"));
return new DataTables(
breedBase,
locations.taxRates,
locations.shippingFees);
} catch (IOException e) {
throw new IllegalStateException("Unable to load reference data", e);
}
}
private static Map<String, Double> loadBreedBase(Path path) throws IOException {
Map<String, Double> result = new HashMap<>();
List<String> lines = Files.readAllLines(path);
for (int i = 1; i < lines.size(); i++) {
if (lines.get(i).isBlank()) continue;
String[] parts = lines.get(i).split(",", -1);
result.put(parts[0], Double.parseDouble(parts[1]));
}
return result;
}
private static LocationMaps loadLocations(Path path) throws IOException {
Map<String, Double> taxRates = new HashMap<>();
Map<String, Double> shippingFees = new HashMap<>();
List<String> lines = Files.readAllLines(path);
for (int i = 1; i < lines.size(); i++) {
if (lines.get(i).isBlank()) continue;
String[] parts = lines.get(i).split(",", -1);
String location = parts[0];
double tax = Double.parseDouble(parts[1]);
double shipping = Double.parseDouble(parts[2]);
Set<String> normalized = new HashSet<>();
if (location.equals("PennsylvaniaRhode Island")) {
normalized.add("Pennsylvania");
normalized.add("Rhode Island");
} else {
normalized.add(location);
}
for (String key : normalized) {
taxRates.put(key, tax);
shippingFees.put(key, shipping);
}
}
return new LocationMaps(taxRates, shippingFees);
}
private record LocationMaps(
Map<String, Double> taxRates,
Map<String, Double> shippingFees) {}
}
package parvocure;
import java.util.Map;
public record DataTables(
Map<String, Double> breedBase,
Map<String, Double> taxRatePercent,
Map<String, Double> shippingFee) {
public DataTables {
breedBase = Map.copyOf(breedBase);
taxRatePercent = Map.copyOf(taxRatePercent);
shippingFee = Map.copyOf(shippingFee);
}
}
package parvocure;
import java.time.LocalDate;
import java.util.List;
public record CheckoutRequest(
String ownerId,
String ownerLocation,
String deliveryLocation,
String dogBreed,
double dogAgeYears,
double dogWeightKg,
List<String> coupons,
LocalDate purchaseDate) {
public CheckoutRequest {
coupons = List.copyOf(coupons);
}
}
package parvocure;
public record OrderQuote(
double dogAgeDisplay,
double dosageMl,
double dosageDisplayValue,
String dosageDisplayUnit,
double medicineCost,
double discountAmount,
double netCost,
double shippingCost,
double taxAmount,
double totalCost) {}
package parvocure;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
public class OrderHistory {
private final List<HistoryRecord> records = new ArrayList<>();
public OrderHistory(){}
public boolean hasOrderWithin30Days(String ownerId, LocalDate purchaseDate) {
for (HistoryRecord record : records) {
if (!record.ownerId().equals(ownerId)) continue;
long days = ChronoUnit.DAYS.between(record.purchaseDate(), purchaseDate);
if (days >= 0 && days < 30) return true;
}
return false;
}
public boolean couponUsedInCalendarMonth(
String ownerId, String coupon, LocalDate purchaseDate) {
for (HistoryRecord record : records) {
if (record.ownerId().equals(ownerId)
&& record.coupons().contains(coupon)
&& record.purchaseDate().getYear() == purchaseDate.getYear()
&& record.purchaseDate().getMonthValue() == purchaseDate.getMonthValue()) {
return true;
}
}
return false;
}
public void record(
String ownerId,
double netCost,
double taxAmount,
double shippingCost,
LocalDate purchaseDate,
List<String> coupons) {
records.add(new HistoryRecord(
ownerId, netCost, taxAmount, shippingCost, purchaseDate, coupons));
}
public List<HistoryRecord> allRecords() {
return List.copyOf(records);
}
}
Smaller Classes
Focused calculator and supporting data classes.
package parvocure;
import java.time.LocalDate;
import java.util.List;
public record HistoryRecord(
String ownerId,
double netCost,
double taxAmount,
double shippingCost,
LocalDate purchaseDate,
List<String> coupons) {
public HistoryRecord {
coupons = List.copyOf(coupons);
}
}
package parvocure;
public class DogAgeCalculator {
public double calculate(double humanAgeYears) {
if (!Double.isFinite(humanAgeYears) || humanAgeYears < 0) {
throw new IllegalArgumentException("dog age must be finite and nonnegative");
}
return humanAgeYears * 7.0;
}
}
package parvocure;
import java.util.Map;
public class DosageCalculator {
private final Map<String, Double> breedBase;
public DosageCalculator(Map<String, Double> breedBase) {
this.breedBase = Map.copyOf(breedBase);
}
public double calculateMl(String breed, double humanAgeYears, double weightKg) {
Double base = breedBase.get(breed);
if (base == null) {
throw new IllegalArgumentException("unknown breed: " + breed);
}
if (!Double.isFinite(humanAgeYears) || humanAgeYears < 0) {
throw new IllegalArgumentException("dog age must be finite and nonnegative");
}
if (!Double.isFinite(weightKg) || weightKg <= 0) {
throw new IllegalArgumentException("dog weight must be finite and positive");
}
double dosageMl = base * ((weightKg + 5.0) - humanAgeYears / 3.0);
if (dosageMl < 0) {
throw new IllegalArgumentException("the dosage formula produced a negative dosage");
}
return dosageMl;
}
}
package parvocure;
public class MedicineCostCalculator {
public static final double PRICE_PER_ML = 0.033814057;
public double calculate(double dosageMl) {
if (!Double.isFinite(dosageMl) || dosageMl < 0) {
throw new IllegalArgumentException("dosage must be finite and nonnegative");
}
return dosageMl * PRICE_PER_ML;
}
}
package parvocure;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class DiscountCalculator {
private static final Set<String> VALID = Set.of("10% Off", "$10 Off", "BOGO");
public double calculate(
double medicineCost,
List<String> coupons,
String ownerId,
LocalDate purchaseDate,
OrderHistory history) {
if (!Double.isFinite(medicineCost) || medicineCost < 0) {
throw new IllegalArgumentException("medicine cost must be finite and nonnegative");
}
if (coupons.size() > 2) {
throw new IllegalArgumentException("maximum of two coupons per transaction");
}
if (new HashSet<>(coupons).size() != coupons.size()) {
throw new IllegalArgumentException(
"a coupon type may only be used once per calendar month");
}
double net = medicineCost;
for (String coupon : coupons) {
if (!VALID.contains(coupon)) {
throw new IllegalArgumentException("unknown coupon: " + coupon);
}
if (coupon.equals("BOGO")) {
throw new IllegalArgumentException("BOGO is unavailable in this release");
}
if (history.couponUsedInCalendarMonth(ownerId, coupon, purchaseDate)) {
throw new IllegalArgumentException("coupon already used this calendar month");
}
if (coupon.equals("10% Off")) {
net *= 0.90;
} else if (coupon.equals("$10 Off")) {
net = Math.max(0.0, net - 10.0);
}
}
return net;
}
}
package parvocure;
import java.util.Map;
public class ShippingCalculator {
private final Map<String, Double> shippingFees;
public ShippingCalculator(Map<String, Double> shippingFees) {
this.shippingFees = Map.copyOf(shippingFees);
}
public double calculate(String deliveryLocation) {
Double fee = shippingFees.get(deliveryLocation);
if (fee == null) {
throw new IllegalArgumentException(
"unsupported delivery location: " + deliveryLocation);
}
return fee;
}
}
package parvocure;
import java.util.Map;
public class TaxCalculator {
private final Map<String, Double> taxRatePercent;
public TaxCalculator(Map<String, Double> taxRatePercent) {
this.taxRatePercent = Map.copyOf(taxRatePercent);
}
public double calculate(double netCost, String deliveryLocation) {
if (!Double.isFinite(netCost) || netCost < 0) {
throw new IllegalArgumentException("net cost must be finite and nonnegative");
}
Double rate = taxRatePercent.get(deliveryLocation);
if (rate == null) {
throw new IllegalArgumentException(
"unsupported delivery location: " + deliveryLocation);
}
return netCost * rate / 100.0;
}
}
package parvocure;
public class TotalCostCalculator {
public double calculate(double netCost, double shippingCost, double taxAmount) {
double[] values = {netCost, shippingCost, taxAmount};
for (double value : values) {
if (!Double.isFinite(value) || value < 0) {
throw new IllegalArgumentException(
"total inputs must be finite and nonnegative");
}
}
return netCost + shippingCost + taxAmount;
}
}
Test Files
Integration tests, unit tests, and the small test-support helper.
package parvocure.tests;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.List;
import parvocure.*;
public class IntegrationTests {
public static void main(String[] args) {
TestSupport t = new TestSupport();
DataTables data = DataLoader.load(Path.of("/Users/jessica/yauney/assets/java/parvocure/tests"));
CheckoutService usService = new CheckoutService(data);
OrderQuote us = usService.checkout(new CheckoutRequest(
"owner-1", "California", "California", "Labrador Retriever",
2, 20, List.of("10% Off"), LocalDate.of(2026, 3, 10)));
t.near(us.dosageMl(), 37.96, 1e-12, "integration US dosage");
t.equal(us.dosageDisplayUnit(), "fl oz", "integration US display unit");
t.near(us.netCost(), 1.1552234433480002, 1e-12, "integration US net");
t.near(us.totalCost(), 6.811912969633067, 1e-12, "integration US total");
CheckoutService internationalService = new CheckoutService(data);
OrderQuote international = internationalService.checkout(new CheckoutRequest(
"owner-2", "Canada", "Canada", "Beagle",
4, 10, List.of(), LocalDate.of(2026, 4, 1)));
t.equal(international.dosageDisplayUnit(), "mL", "integration non-US unit");
t.near(international.dosageDisplayValue(), international.dosageMl(), 1e-12,
"integration non-US display value");
t.near(international.shippingCost(), 15.5901966, 1e-12,
"integration uses delivery location");
t.ok(international.taxAmount() > 0, "integration non-US tax");
OrderHistory history = new OrderHistory();
CheckoutService historyService = new CheckoutService(data, history);
historyService.checkout(new CheckoutRequest(
"owner-3", "California", "Utah", "Poodle",
3, 12, List.of("10% Off"), LocalDate.of(2026, 5, 1)));
t.throwsIA(() -> historyService.checkout(new CheckoutRequest(
"owner-3", "California", "Utah", "Poodle",
3, 12, List.of(), LocalDate.of(2026, 5, 20))),
"integration rolling order limit");
OrderQuote boundary = historyService.checkout(new CheckoutRequest(
"owner-3", "California", "Utah", "Poodle",
3, 12, List.of("$10 Off"), LocalDate.of(2026, 5, 31)));
t.near(boundary.taxAmount(), 0, 1e-12, "integration zero tax at boundary");
t.finish();
}
}
package parvocure.tests;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.List;
import parvocure.*;
public class UnitTests {
public static void main(String[] args) {
TestSupport t = new TestSupport();
DataTables data = DataLoader.load(Path.of("/Users/jessica/yauney/assets/java/parvocure/tests"));
DogAgeCalculator age = new DogAgeCalculator();
t.near(age.calculate(2.5), 17.5, 1e-12, "dog age fractional");
t.throwsIA(() -> age.calculate(-0.1), "dog age rejects negative");
DosageCalculator dosage = new DosageCalculator(data.breedBase());
t.near(dosage.calculateMl("Labrador Retriever", 2, 20), 37.96, 1e-12,
"dosage formula");
t.ok(dosage.calculateMl("Labrador Retriever", 2, 20)
!= dosage.calculateMl("Labrador Retriever", 5, 20),
"dosage uses age");
t.throwsIA(() -> dosage.calculateMl("No Such Breed", 2, 20),
"dosage rejects unknown breed");
MedicineCostCalculator medicine = new MedicineCostCalculator();
t.near(medicine.calculate(100), 3.3814057, 1e-12, "medicine price per ml");
t.throwsIA(() -> medicine.calculate(-1), "medicine rejects negative dosage");
OrderHistory history = new OrderHistory();
DiscountCalculator discount = new DiscountCalculator();
t.near(discount.calculate(100, List.of("$10 Off", "10% Off"), "A",
LocalDate.of(2026, 3, 2), history),
81, 1e-12, "discount sequential order 1");
t.near(discount.calculate(100, List.of("10% Off", "$10 Off"), "B",
LocalDate.of(2026, 3, 2), history),
80, 1e-12, "discount sequential order 2");
t.throwsIA(() -> discount.calculate(10, List.of("BOGO"), "A",
LocalDate.of(2026, 3, 2), history),
"discount rejects BOGO");
t.throwsIA(() -> discount.calculate(100, List.of("10% Off", "10% Off"), "A",
LocalDate.of(2026, 3, 2), history),
"discount rejects duplicate type");
history.record("A", 90, 0, 5, LocalDate.of(2026, 1, 2), List.of("10% Off"));
t.throwsIA(() -> discount.calculate(100, List.of("10% Off"), "A",
LocalDate.of(2026, 1, 31), history),
"coupon reuse same calendar month");
t.near(discount.calculate(100, List.of("10% Off"), "A",
LocalDate.of(2026, 2, 1), history),
90, 1e-12, "coupon reusable next month");
ShippingCalculator shipping = new ShippingCalculator(data.shippingFee());
t.near(shipping.calculate("Utah"), 5.44925658, 1e-12, "shipping fee only");
t.near(shipping.calculate("Pennsylvania"), shipping.calculate("Rhode Island"),
1e-12, "combined PA/RI row split");
TaxCalculator tax = new TaxCalculator(data.taxRatePercent());
t.near(tax.calculate(100, "California"), 5.59208841, 1e-12,
"tax rate interpreted as percent");
t.near(tax.calculate(12.34, "Utah"), 0, 1e-12, "zero tax valid");
TotalCostCalculator total = new TotalCostCalculator();
t.near(total.calculate(10, 5, 1), 16, 1e-12, "total includes shipping");
t.near(total.calculate(1.111, 2.222, 3.333), 6.666, 1e-12,
"total keeps precision");
OrderHistory rolling = new OrderHistory();
rolling.record("A", 10, 1, 5, LocalDate.of(2026, 1, 15), List.of());
t.ok(rolling.hasOrderWithin30Days("A", LocalDate.of(2026, 2, 10)),
"rolling 30 day limit inside window");
t.ok(!rolling.hasOrderWithin30Days("A", LocalDate.of(2026, 2, 14)),
"rolling 30 day exact boundary allowed");
HistoryRecord record = rolling.allRecords().get(0);
t.near(record.netCost(), 10, 1e-12, "history persists net cost");
t.near(record.taxAmount(), 1, 1e-12, "history persists tax");
t.near(record.shippingCost(), 5, 1e-12, "history persists shipping");
t.finish();
}
}
package parvocure.tests;
import java.util.Objects;
public final class TestSupport {
private int passed = 0;
private int failed = 0;
public void near(double actual, double expected, double epsilon, String name) {
if (Math.abs(actual - expected) > epsilon) {
failed++;
System.out.printf("FAIL %s expected %.15f got %.15f%n", name, expected, actual);
} else {
passed++;
System.out.println("PASS " + name);
}
}
public void equal(Object actual, Object expected, String name) {
if (!Objects.equals(actual, expected)) {
failed++;
System.out.printf("FAIL %s expected %s got %s%n", name, expected, actual);
} else {
passed++;
System.out.println("PASS " + name);
}
}
public void ok(boolean condition, String name) {
if (!condition) {
failed++;
System.out.println("FAIL " + name);
} else {
passed++;
System.out.println("PASS " + name);
}
}
public void throwsIA(Runnable action, String name) {
try {
action.run();
failed++;
System.out.println("FAIL " + name + " expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
passed++;
System.out.println("PASS " + name);
}
}
public void finish() {
System.out.printf("Passed: %d Failed: %d%n", passed, failed);
if (failed > 0) System.exit(1);
}
}