The first cut rendered entity_orders and nothing else, and it read like a table dump: a courier tracking notice and a subscription payment both presented as retail purchases, merchants shown as sender-domain slugs, and no indication that the same coffee roaster had been billing fortnightly for two years. Rows now carry what the ingestion layer already knew. A kind badge from content_class distinguishes a delivery notice, an invoice, a booking and a subscription from an actual purchase — NULL stays unbadged rather than being labelled a purchase, because 48% of rows predate the interpretation index and "unknown" is not "order". display_name shows the resolved merchant (98% of rows) instead of the slug. A recurrence badge marks merchants billing on a cadence, which is derived from the gaps between orders rather than stated anywhere in the mail. And the order's own title now appears when it differs from the merchant, so the manifest is not the only detail on the row. Adds the services lane for bookings, invoices and subscriptions — things with no goods and no delivery, where the retail columns are dead space.
This commit is contained in:
+34
-2
@@ -39,7 +39,9 @@ export function canViewOrders(userId: number): boolean {
|
||||
return ORDER_VIEWERS.includes(userId);
|
||||
}
|
||||
|
||||
export type OrderLane = "retail" | "food" | "grocery" | "digital" | "transport";
|
||||
/** Bookings, invoices and subscriptions get their own lane: they carry no
|
||||
* goods and no delivery, so the retail columns are dead space on them. */
|
||||
export type OrderLane = "retail" | "food" | "grocery" | "digital" | "transport" | "services";
|
||||
|
||||
export interface OrderRow {
|
||||
entity_id: number;
|
||||
@@ -60,6 +62,17 @@ export interface OrderRow {
|
||||
refunded_amount: string | null;
|
||||
line_item_count: number;
|
||||
item_preview: string[] | null;
|
||||
/** What the classifier thought the source document was. NULL on 48% of rows
|
||||
* (the Takeout backfill predates the interpretation index) — treat NULL as
|
||||
* "unknown", never as "purchase". */
|
||||
content_class: string | null;
|
||||
/** COALESCE(resolved merchant, interpretation counterparty, platform slug).
|
||||
* 95% resolve to a real name; the rest honestly show the slug rather than
|
||||
* guessing one from the sender domain — see ticket 176. */
|
||||
display_name: string;
|
||||
/** Merchant orders on a regular cadence: the subscription signal, derived
|
||||
* not extracted. NULL unless the gaps are tight relative to their mean. */
|
||||
cadence_days: number | null;
|
||||
txn_count: number;
|
||||
first_txn_id: number | null;
|
||||
}
|
||||
@@ -206,6 +219,7 @@ export async function getOrderFeed(filters: OrderFilters) {
|
||||
f.order_reference, f.reference_source, f.source_trust,
|
||||
f.ordered_at, f.eta_date, f.delivered_at,
|
||||
f.currency, f.order_total, f.line_item_count,
|
||||
f.content_class, f.display_name, f.cadence_days,
|
||||
m.canonical_name AS merchant_name,
|
||||
link.txn_count, link.first_txn_id,
|
||||
(SELECT sp.refunded_amount FROM order_spend sp
|
||||
@@ -316,6 +330,10 @@ export interface OrderDetail {
|
||||
tracking_carrier: string | null;
|
||||
line_items: { description?: string; quantity?: number; amount?: number }[];
|
||||
is_settled_duplicate: boolean;
|
||||
content_class: string | null;
|
||||
display_name: string;
|
||||
cadence_days: number | null;
|
||||
order_count: number | null;
|
||||
events: OrderLifecycleEvent[];
|
||||
siblings: OrderSibling[];
|
||||
transactions: OrderLinkedTxn[];
|
||||
@@ -342,12 +360,26 @@ export async function getOrderDetail(entityKey: string): Promise<OrderDetail | n
|
||||
COALESCE(o.details->'line_items', '[]'::jsonb) AS line_items,
|
||||
sp.order_total AS net_total,
|
||||
sp.gross_total,
|
||||
sp.refunded_amount
|
||||
sp.refunded_amount,
|
||||
ctx.content_class,
|
||||
COALESCE(me2.canonical_name, ctx.counterparty, o.platform) AS display_name,
|
||||
rec.cadence_days,
|
||||
rec.order_count
|
||||
FROM entities e
|
||||
JOIN entity_orders o ON o.entity_id = e.id
|
||||
LEFT JOIN entities m ON m.id = o.merchant_entity_id
|
||||
LEFT JOIN entities me2 ON me2.id = o.merchant_entity_id
|
||||
LEFT JOIN order_platforms p ON p.slug = o.platform
|
||||
LEFT JOIN order_spend sp ON sp.entity_id = o.entity_id
|
||||
LEFT JOIN order_merchant_cadence rec ON rec.merchant_entity_id = o.merchant_entity_id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT di.content_class, di.counterparty
|
||||
FROM extracted_facts ef
|
||||
JOIN document_interpretations di ON di.source_document_id = ef.source_document_id
|
||||
WHERE ef.fact_type = 'order_event'
|
||||
AND ef.payload->>'_order_entity_key' = e.entity_key
|
||||
ORDER BY (di.counterparty IS NULL), ef.effective_at LIMIT 1
|
||||
) ctx ON true
|
||||
WHERE e.entity_key = $1`,
|
||||
[entityKey]
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user