A valuation service that adds up invoice lines is implementing the wrong shape. 19 CFR 152.103 builds dutiable value from a base plus a fixed set of additions, and the regulation says the additions cover those items and no others, counted only to the extent they are not already inside the base. That is an allowlist with a de-duplication rule, which is a very different data structure from a sum.
The model
type ChargeKind =
| 'goods'
| 'packingBuyer' // 152.103(b)(1)(i)
| 'sellingCommissionBuyer' // (ii)
| 'assist' // (iii)
| 'royaltyConditionOfSale' // (iv)
| 'resaleProceedsToSeller' // (v)
| 'foreignInlandFreight' // excluded only under 152.102(a)(5) conditions
| 'internationalFreight'
| 'merchantDiscount'
| 'unclassified';
type Charge = {
kind: ChargeKind;
amount: number;
currency: 'USD';
separatelyStated: boolean;
incurredBy: 'buyer' | 'seller';
evidence?: 'throughBillOfLading' | 'other';
};
The unclassified member is the honest one. Any charge the ingestion step cannot name lands there, and the pipeline must fail rather than silently include or exclude it. A model with no unknown bucket is a model that guesses.
The additions are a closed set
const ADDITIVE: ReadonlySet<ChargeKind> = new Set([
'packingBuyer', 'sellingCommissionBuyer', 'assist',
'royaltyConditionOfSale', 'resaleProceedsToSeller',
]);
function dutiableValue(charges: Charge[]): number {
const base = charges.filter((c) => c.kind === 'goods').reduce((n, c) => n + c.amount, 0);
let value = base;
for (const c of charges) {
if (c.kind === 'unclassified') throw new Error('UNCLASSIFIED_CHARGE: cannot value');
if (!ADDITIVE.has(c.kind)) continue;
if (c.incurredBy !== 'buyer' && c.kind !== 'resaleProceedsToSeller') continue;
value += c.amount;
}
return value;
}
Two details in that loop come straight from the text rather than from taste. The buyer-incurred test is there because the packing and selling commission items are worded as costs incurred by the buyer, while the royalty item is worded as a fee the buyer is required to pay as a condition of the sale for export. And the base already contains whatever the parties priced in, so an addition that is inside the invoice total should not be added twice. If the ingestion step cannot tell which case it has, the charge belongs in unclassified.
Freight is a documentation predicate
A foreign inland freight line does not become excludable just because someone typed it on its own row. Under 152.102(a)(5) the exclusion needs two facts at once. The charge has to appear as a separate item, and it has to arise after the goods were sold for export to the United States and handed to a carrier for through shipment. Proof of that second fact runs through a through bill of lading, with other paperwork available only where using one clearly would have been impossible.
function freightExcludable(c: Charge, seq: { soldForExportAt: Date; handedToCarrierAt: Date }): boolean {
if (!c.separatelyStated) return false;
if (seq.handedToCarrierAt < seq.soldForExportAt) return false;
if (c.evidence === 'throughBillOfLading') return true;
return false; // 'other' needs the impossibility record, which is a human call
}
The last branch returns false on purpose rather than trusting a string. The impossibility route exists, but it is a documented exception rather than a document type, and a service that treats other as equivalent to a through bill of lading has quietly deleted the condition.
Note the direction of the correction as well. The same section points the other way too. If checking reveals that a foreign inland freight figure, or an equivalent international shipment charge, was padded, the appraiser can still add the difference back into dutiable value. Overstating a deductible line is not conservative. It is a different error with the same endpoint.
The order of methods is a resolver chain
152.101(b) fixes the sequence: transaction value, then identical merchandise, then similar, then deductive, then computed, then a residual method consistent with the subpart's principles. A resolver that lets an operator pick a method has modeled a preference rather than a rule.
const METHOD_ORDER = ['transactionValue', 'identical', 'similar', 'deductive', 'computed', 'residual'] as const;
function resolve(charges: Charge[]): { method: typeof METHOD_ORDER[number]; value: number } {
try { return { method: 'transactionValue', value: dutiableValue(charges) }; }
catch { /* fall through to the next method only because this one could not be determined */ }
throw new Error('MANUAL_METHOD_SELECTION_REQUIRED');
}
The fallbacks each have their own inputs, and none of them is "the number we would like". 152.108 removes the convenient bases outright, including the US selling price of domestically produced goods, any system that appraises at the higher of two alternative values, and the price in the exporter's domestic market. A shop-floor shortcut like "use the Amazon price minus a margin" is the first of those with extra steps.
What the warehouse layer contributes
FulfillNexa by SBT (fulfillnexa.com) records weights, counts and packing configuration per SKU at receipt across three sites, the largest being Suzhou at 13,000 square meters, then Dongguan at 8,000 and Shenzhen at 3,000. That record is evidence rather than a valuation. It lets a packing cost be identified separately and dated, which is the condition the exclusion turns on. The declared number stays the seller's, and the log is what makes it defensible rather than merely stated.
Top comments (0)