Metrics (metrics:) and business terms (@terms)
"What is revenue?" and "does the order count include cancelled orders?" are core domain facts. Defined
separately in code, SQL and each BI tool, they give different numbers. Write them once in the DSL;
the Apache Ossie export carries them to BI and AI tools as Ossie
metrics. No application code or query API is generated from them.
metrics:
revenue: sum(PurchaseOrder.total) where status != CANCELLED @terms(sales, 매출액) -- 매출 (취소 제외, VAT 포함·할인 후 금액)
orderCount: count(PurchaseOrder) where status != CANCELLED @terms(orders, 주문 건수) -- 주문 수 (취소 제외)
averageOrder: revenue / orderCount @terms(AOV, 객단가) -- 주문 1건 평균 금액
shippedOrders: count(PurchaseOrder) where status = SHIPPED -- 배송된 주문 수
cancelledOrders: count(PurchaseOrder) where status = CANCELLED -- 취소된 주문 수
customerCount: count(Customer) where role = USER @terms(members, 회원 수) -- 고객 수 (관리자 제외)
(The descriptions after -- are the sample's own, in Korean: revenue without cancelled orders, VAT
included and the discount taken off; orders without cancelled ones; the average order; shipped;
cancelled; customers without administrators.)
metrics: is a top-level section in any model file of the domain. One line per measure:
name: expression [@terms(...)] [-- description].
Syntax
| Write | Means | Exported SQL (OSSIE_SQL_2026) |
|---|---|---|
sum(Entity.field) / avg / min / max | an aggregate of one entity's field | SUM(PurchaseOrder.total) |
count(Entity) | rows | COUNT(PurchaseOrder.order_id) - the key column, so a BI tool knows which dataset is counted |
count(Entity.field) | rows with a value | COUNT(PurchaseOrder.total) |
... where field != value and field >= value | a condition: =, !=, <, <=, >, >=, and | SUM(CASE WHEN ... THEN ... END) |
| values | numbers, 'text' (a ' inside written ''), true/false, enum constant names | the stored value: an @db code ('CAN'), the name with @jpa:Enumerated(STRING), otherwise the ordinal |
metricA / metricB (+ - * /) | measures declared above it | (...) / NULLIF((...), 0) - a division never divides by zero; a count over a count is a decimal |
@terms(sales, 매출액) | business terms and synonyms | ai_context.synonyms |
-- revenue | the description | description |
Data types: count is Integer; sum, min, max the field's type; avg and divisions Decimal
(Float for a float field); + - * Integer when both sides are, otherwise Decimal. sum and
avg take numeric fields only.
What the export wrote for revenue:
metrics:
- name: revenue
expression:
dialects:
- dialect: OSSIE_SQL_2026
expression: "SUM(CASE WHEN PurchaseOrder.status <> 'CAN' THEN PurchaseOrder.total END)"
description: "매출 (취소 제외, VAT 포함·할인 후 금액)"
datatype: Decimal
ai_context:
synonyms: ["sales", "매출액"]
Limits
- One entity at a time. A measure that needs a condition on another entity ("items sold, counting only orders not cancelled" - an order line and its order's status) cannot be written yet. Do not force it: it would count the cancelled ones too.
- No time axis in a measure. Monthly revenue is the BI tool's: it slices by a date field
(
orderDate, Ossie's time dimension). - A line ElastiCORE cannot read - an unknown entity or field, an enum constant the enum does not have,
a measure declared below, an unquoted value for a field that is not an enum - is left out with a
warning
[ossie] [domain] metric name: ... ; it is left out.Nothing is guessed. - A line with
#or:in it must be quoted as a whole, or YAML cuts it or cannot read the file:ratio: "shippedOrders / orderCount -- shipped ratio: shipped / all".
@terms - the words people use
@terms on an entity's meta: line, a field or a measure lists what the business calls it. It does
not change generated code; it goes to Ossie's ai_context.synonyms, so a question about "last
month's purchase amount" leads an AI to PurchaseOrder and revenue.
PurchaseOrder:
meta: entity @root @expose(20) @terms(order, purchase, 구매) -- 주문
Item:
meta: entity @root @editable @terms(pet, 반려동물) -- 판매 품목
fields:
unitCost: money(10.2)! @terms(price, 가격)
An API that answers a measure
The metrics generate no endpoint. An administrator's summary is a method of your own in the owned
service, behind ACCESS.require(currentUser, "report") - see the shop recipe's report in
Your logic.
Checked: the pet store's MetricsTest places, ships and cancels orders, runs each exported metric's
SQL on the database, and compares it with the same numbers computed from the entities.