Products — Price Lookup Module
The Products app exports a Filtrera module at /apps/products/price-lookup.module.hrc that computes the effective price of one or more products across a set of price lists, plus a price-change history within a configurable time window.
It is the canonical building block for:
- Custom price-lookup ingresses — exposing pricing data to a storefront, marketing system, or BI tool with a request and response shape that fits your project.
- Bulk price exports — computing prices in batches for export to ERP, PIM, or marketing platforms.
- EU Omnibus 30-day-low displays —
lowestPriceover a 30-daywindowis the canonical input for "lowest price in the last 30 days" labels.
Module path
/apps/products/price-lookup.module.hrcExports
lookupPrices
Computes effective prices and price history for a list of products against a list of price lists, scoped to one currency and one time window.
Filtrera type signature
(params: {
productNumbers: [text]
currencyCode: text
priceListKeys: [text]
window: duration
}) => { text -> {
currentPrice: number | nothing
history: [{ at: instant, price: number | nothing }]
lowestPrice: number | nothing
highestPrice: number | nothing
omnibusPrice: number | nothing
expiresAt: instant | nothing
} }Inputs
| Field | Type | Description |
|---|---|---|
productNumbers | [text] | Asset numbers of the products to look up. |
currencyCode | text | ISO 4217 currency code. Only prices in this currency are considered. |
priceListKeys | [text] | Price lists to consider. Only prices on these lists are considered. |
window | duration | How far back to compute history, lowest, and highest. For EU Omnibus, use 30 days. |
Output
A map keyed by productNumber (asset.product.assetNumber) where each entry has:
| Field | Type | Description |
|---|---|---|
currentPrice | number | nothing | The current best price across active price lists, ignoring deactivated prices. nothing if no active price applies. |
history | [{ at: instant, price: number | nothing }] | Chronologically-ordered entries — one per change of the effective best price within window. price is nothing when the effective price was not defined (no active list, or all lists deactivated) at that moment. |
lowestPrice | number | nothing | Lowest effective price observed within window. |
highestPrice | number | nothing | Highest effective price observed within window. |
omnibusPrice | number | nothing | Lowest price observed within window that differs from currentPrice. Equal to currentPrice when no other price exists (i.e., the price has never changed). Used for "was" price / strikethrough calculations. |
expiresAt | instant | nothing | The absolute instant when the earliest event still within window will slide past the window limit and the result may change. nothing when no events exist in the window and the result is indefinitely stable. Consumers that cache results or export asynchronously can use this to schedule a refresh. |
Window semantics
- The history is computed by replaying activity-log events (
priceChange,priceDeactivation,priceReactivation,priceListActivation,priceListDeactivation— see Activity log event types) from the start of the window forward. - An entry is appended to
historywhenever the effective best price for the product changes — not on every individualpriceChangeevent. Activations and deactivations of price lists or individual prices contribute to this only when they affect the effective price. - Products that exist but have no matching active prices appear in the result with
currentPrice = nothingand an empty or deactivation-onlyhistory. - Products that don't exist do not appear in the result map.
Declaring the dependency
Add this to your app's h_app.yaml:
requires:
modules:
/apps/products/price-lookup.module.hrc:
exports:
lookupPrices:
type: '(params: { productNumbers: [text], currencyCode: text, priceListKeys: [text], window: duration }) => { text -> { currentPrice: number | nothing, history: [{ at: instant, price: number | nothing }], lowestPrice: number | nothing, highestPrice: number | nothing, omnibusPrice: number | nothing, expiresAt: instant | nothing } }'See Declaring App Dependencies for the surrounding context.
Use cases
Custom price-lookup ingress
Expose a project-specific HTTP ingress backed by lookupPrices. The contract — request body, response shape, auth, channel filtering, tax handling — is intentionally up to the implementer because storefront pricing rules vary widely.
import './price-lookup.module.hrc' as priceLookup
param request: {
productNumbers: [text]
currencyCode: text
priceListKeys: [text]
}
let result =
priceLookup.lookupPrices {
productNumbers = request.productNumbers
currencyCode = request.currencyCode
priceListKeys = request.priceListKeys
window = 30 days
}
from {
currencyCode = request.currencyCode
prices =
result entries
select e => {
productNumber = e.key
currentPrice = e.value.currentPrice
lowestIn30Days = e.value.lowestPrice
timeline = e.value.history
}
}Register the component as a public HTTP ingress in your h_app.yaml and your storefront can call it directly.
Bulk price export
Call lookupPrices from a job that emits an export feed for ERP, PIM, or marketing platforms. Batch the input list to keep memory usage bounded and to fit any downstream API limits.
import './price-lookup.module.hrc' as priceLookup
param batch: {
productNumbers: [text]
priceListKeys: [text]
currencyCode: text
}
let prices =
priceLookup.lookupPrices {
productNumbers = batch.productNumbers
currencyCode = batch.currencyCode
priceListKeys = batch.priceListKeys
window = 30 days
}
from
prices entries
select e => {
productNumber = e.key
price = e.value.currentPrice
lowestPrice = e.value.lowestPrice
}EU Omnibus 30-day-low
For storefront price displays that need to show the lowest price in the last 30 days (per the EU Omnibus Directive), pass window = 30 days and surface lowestPrice alongside currentPrice. The history field is available if you want to render a price-over-time chart or audit trail.