Skip to content

Known Behaviors

Behaviors preserved in calculatecost4 for backward compatibility with calculatecost2. Each behavior is documented with its origin and rationale for preservation.


1. float('inf') on Missing Price

Location: getPrice(cprcode, branchId)

When a product's price is not found in DynamoDB, getPrice returns float('inf'). This value propagates through multiplication:

price = float('inf')
rowTotal = max(price * quantity, 0)  # inf
grandTotal = ... + inf               # inf

The response contains Infinity in JSON-serialized numeric fields. Standard JSON parsers reject this value. Python's json.dumps with default settings raises ValueError; the legacy code uses a custom serializer that emits the raw token.

Impact: Silent corruption of rowTotal, grandTotal, and all downstream totals. No error is raised. The caller receives a 200 response with unusable financial data.

Preserved because: Existing clients may rely on detecting Infinity to identify missing-price conditions. Changing this to an error or zero would alter the response contract.


2. getOriginalPrice Returns 0 on Missing Price

Location: getOriginalPrice(cprcode, branchId)

Unlike getPrice which returns float('inf') on a missing price, getOriginalPrice returns 0. This asymmetry means a product with no price data will have:

price = float('inf')
originalPrice = 0

Origin: Likely a bug. The two functions were written at different times and never harmonized. getPrice was written defensively (return a value that makes totals obviously wrong), while getOriginalPrice was written with a zero-default.

Preserved because: Changing either return value would alter the response for missing-price products.


3. grandTotal Does NOT Subtract two4Discount or percentageDiscount

Location: Grand total formula in Order.toDict()

The grand total formula is:

grandTotal = subTotal
           + discountedDeliveryFee
           + expressShippingCost
           - cartDiscount
           - bogoDiscount
           - voucherDiscount

two4Discount and percentageDiscount are computed and present in the response body but are not subtracted from grandTotal. They appear in totalDiscount (which is purely informational) but do not affect the final price.

Rationale: The coupon3-checker Lambda may fold these discount types into cartDiscount before returning them. The separation exists for reporting, not for calculation. Subtracting them from grandTotal would double-count the discount.

Preserved because: Altering the formula would change grandTotal for orders with percentage or 2-for-4 coupons.


4. Duplicate scheduleId in Product

Location: Legacy src/calculateCost2/schema/product.py

The old Product class declares scheduleId twice:

# Line 26
scheduleId: Optional[int] = None

# Line 40
scheduleId: Optional[int] = 0

In Python dataclasses, the second declaration overwrites the first. The effective default is 0, not None.

New code: Declares scheduleId once as int = 0. Behaviorally identical to the legacy code's effective default.

Preserved because: Any test or client that relies on scheduleId defaulting to 0 (rather than None) must continue to work.


5. Express Fee Hardcoded to 50 THB

Location: Shipping fee calculation in ShippingCalculator

The first schedule with mode=EXPRESS receives expressFee = 50. This value is hardcoded, not configurable, and not read from any database or configuration table.

if mode == EXPRESS and is_first_schedule:
    expressFee = 50

Only the first EXPRESS schedule gets the fee. Subsequent EXPRESS schedules in the same order get expressFee = 0.

Preserved because: The 50 THB value is baked into test assertions and client expectations. Changing it requires coordination with the business team.


6. Weight Default is 0.25 Per Unit

Location: Product enrichment step

Products without weight data in DynamoDB default to 0.25 kg per unit. The weight is then multiplied by quantity:

weight = product_data.get('weight') or 0.25
total_weight = weight * quantity

The or fallback triggers on both None (missing key) and 0 (explicit zero weight). A product with an explicit weight of 0 in DynamoDB will be treated as 0.25.

Preserved because: Nationwide shipping fees depend on weight. Defaulting to zero would make nationwide shipping free for products with missing weight data.


7. shippingType Default is PICKUP

Location: Shipping.from_dict() and ShippingType enum default

When no shippingType is provided in the request, it defaults to PICKUP. When shippingType is PICKUP, all schedule delivery fees are set to 0 regardless of schedule mode.

if shipping.shippingType == PICKUP:
    for schedule in scheduleList:
        schedule.deliveryFee = 0
        schedule.expressFee = 0

Impact: An order submitted without an explicit shippingType field will have zero delivery fees, even if schedules with REGULAR or EXPRESS modes are present.

Preserved because: Existing clients that omit shippingType expect zero fees. Changing the default to DELIVERY would introduce unexpected charges.