Landed-cost model¶
Status: STUB. This doc names the surfaces and the inputs; the model itself isn't shipped yet. M2 candidate.
What "landed cost" means here¶
The total a Lebanese buyer pays to take a part home, not just the catalog price. Three components:
- Catalog price — what the retailer's website displays (USD only per ADR-0011).
- Delivery / shipping — varies by retailer, by district, by basket weight.
- VAT — 11% standard rate (12% pending Parliamentary enactment per #163). Some retailers display VAT-included; some VAT-excluded; we need to know which.
- Retailer surcharges — cash-on-delivery fees, fresh-USD vs lollar bifurcation, BANCO conversions for non-Beirut delivery.
Why 961tech needs to model it¶
Without landed cost, the cheapest catalog price is misleading. A buyer who picks Retailer A at $399 may actually pay more than Retailer B at $410 once VAT-included flag, $5 delivery, and a 1% cash-handling surcharge land. Persona research flags this as the #2 buyer frustration after stock uncertainty.
Per-retailer fields needed (M2 schema delta)¶
model Retailer {
// existing fields...
vatHandling String? // 'inclusive' | 'exclusive' | 'unknown'
deliveryPolicy Json? // { freeOver: number, baseFee: number, perKgFee: number, beyondBeirut: number }
paymentSurcharges Json? // { cashOnDelivery: 0.01, wishMoney: 0, omt: 0.005, cardFreshUsd: 0, cardLollar: 0.15 }
}
deliveryPolicy and paymentSurcharges modelled as JSON to start — formal columns once the shape stabilises.
Per-product fields¶
Mostly already there:
- weightKg — needed for per-kg delivery fees. Currently absent on Product; populate via #21 LLM extraction or scraper-side enrichment.
- dimensionsCmCm — bulky-item surcharges (some retailers).
Modelling approach¶
interface LandedCostInput {
retailer: Retailer;
basketTotalUsd: number;
basketKg: number;
deliveryDistrict: 'beirut' | 'mt-lebanon' | 'north' | 'south' | 'beqaa' | 'nabatieh' | 'unknown';
paymentMethod: 'card-fresh' | 'card-lollar' | 'cash-cod' | 'wish' | 'omt' | 'unknown';
}
interface LandedCostBreakdown {
catalogUsd: number;
vatUsd: number; // 0 if VAT inclusive
deliveryUsd: number;
paymentSurchargeUsd: number;
totalUsd: number;
notes: string[]; // surfaced as caveats next to the number
}
export function computeLandedCost(input: LandedCostInput): LandedCostBreakdown;
Each field has a fallback if the data isn't filled in: - VAT inclusive vs exclusive defaults to "inclusive" (Lebanese retailer convention) - Delivery defaults to retailer's published "delivery within Beirut" rate - Payment surcharge defaults to 0 (we don't penalise payment-method ambiguity)
The breakdown returns notes so the UI can surface "VAT handling unknown — assumed inclusive" rather than silently misleading.
UI surfaces (M2)¶
- Product detail card: cheapest landed cost vs cheapest catalog price, with the diff as a small chip.
- Build summary: total landed cost across retailers (combined with the M2 cart-aggregator #15).
- Retailer profile: documented payment + delivery policy for transparency.
- Per-card freshness signal (
page-content-spec.md) gains a landed-cost annotation when the buyer-side delta is > 5%.
What we're explicitly NOT doing¶
- Currency conversion display. ADR-0004 is English+USD only; no LBP / EUR / SAR projection.
- Customs / import duties. All catalog-listed parts are post-import; the retailer absorbs that cost.
- Real-time exchange rates. We're not a forex aggregator.
- Negotiated retailer discounts. If MASTER strikes a retailer-specific buyer discount via affiliate, that's per-retailer per-tier and lives in #16 admin tools.
See also¶
- ADR-0011 monetisation rates — USD-only currency precedent
- Reference → Personas — pain-point matrix naming this gap
- Reference → Retailers — per-retailer audit notes are the seed for
deliveryPolicy+paymentSurcharges - #163 — VAT 11% → 12% Parliamentary watch