Building a Temporal ERP Stock Ontology in H-Logic
Documentation status: reference — see Maturity and evidence.
This page documents, step by step, how a small ERP stock-management specification is transformed into an H-Logic ontology suitable for LLM-assisted analysis, logical propagation, indexed hypergraph queries, and a later transformation into logiCells .model artifacts.
The resulting ontology has already been accepted by the GOLD parser. This validates its grammar. Logical execution and propagation by the H-Logic engine remain a separate validation step.
1. Start from a Small Business Specification
The module manages products stored in warehouses.
It records four stock movement kinds:
- receipt;
- reservation;
- release;
- shipment.
For each product and warehouse, the system must be able to determine the stock state at a given time:
- physical quantity on hand;
- reserved quantity;
- available quantity.
A reorder policy defines, for a product and warehouse at a given time:
- a reorder threshold;
- a target stock level.
The following rules must be represented:
- available stock is
onHand - reserved; - physical stock cannot be negative;
- reserved stock cannot be negative;
- reserved stock cannot exceed physical stock;
- a product requires replenishment when available stock is below the applicable threshold;
- all deductions must remain attached to the time at which their source facts are valid.
This last requirement is important. A stock model without time represents only a static photograph. In an ERP, a conclusion such as “product P requires replenishment” must mean:
Product
Prequires replenishment in warehouseWat timeT.
2. Separate Business Entities from Pure Relations
A central modeling decision is whether a concept is an identified entity or a pure polyadic relation.
In H-Logic, introducing me gives the concept an entity identity:
define .:#StockMovement(me)[
product:#Product,
warehouse:#Warehouse,
kind:#MovementKind,
quantity:#Quantity,
occurredAt:#Time
];
#StockMovement is therefore an identified ERP object. The remaining values are modeled as attributes of that movement:
product;warehouse;kind;quantity;occurredAt.
This is appropriate because a movement may later receive additional audit information, document references, status, provenance, or correction history.
The same reasoning applies to a stock position and a reorder policy:
define .:#StockPosition(me)[
product:#Product,
warehouse:#Warehouse,
at:#Time,
onHand:#Quantity,
reserved:#Quantity,
available:#Quantity
];
define .:#ReorderPolicy(me)[
product:#Product,
warehouse:#Warehouse,
validAt:#Time,
threshold:#Quantity,
targetLevel:#Quantity
];
These concepts are identified entities whose attributes describe a temporal business state or policy.
By contrast, derived facts such as #ReorderRequired(product, warehouse, at) are relations. They express conclusions and do not need their own identity.
3. Declare the Foundational Types First
All types used structurally are declared before they appear in entity attributes or relation slots:
define .:#Product(me);
define .:#Warehouse(me);
define .:#Time(me);
define .:#Quantity(me);
This gives the ontology an explicit domain vocabulary and prevents the LLM or a later model generator from silently treating role names as untyped values.
4. Model a Closed Business Vocabulary Correctly
Movement kinds form a finite business vocabulary.
First, declare the type:
define .:#MovementKind(me);
Then declare it complete with the second-order type #Complete:
#MovementKind::#Complete;
Finally, declare the direct instances using ::
#Receipt:#MovementKind;
#Reservation:#MovementKind;
#Release:#MovementKind;
#Shipment:#MovementKind;
This distinction matters:
:declares a direct instance of a type;::attaches a higher-order type or interface;#MovementKind::#Completestates that the type is complete;- the four movement kinds are not singleton types but instances of the complete type.
There is no need to reproduce completeness with a manually written disjunction rule.
5. Add Time to States, Policies, Events, and Conclusions
Temporal consistency is obtained by carrying the same time variable through the entire inference chain.
A movement is dated by occurredAt, a stock position is valid at at, and a policy is applicable at validAt.
Derived facts also carry time:
define .:#BelowReorderThreshold(
position:#StockPosition,
product:#Product,
warehouse:#Warehouse,
at:#Time
);
define .:#ReorderRequired(
product:#Product,
warehouse:#Warehouse,
at:#Time
);
The same variable t is propagated from the position and policy into the conclusion. This prevents facts valid at different times from being accidentally combined.
6. Keep the Recommendation on the Business Object
The replenishment conclusion must concern the product, contextualized by warehouse and time:
.:#BelowReorderThreshold(
position,
product,
warehouse,
t
)
->
.:#ReorderRequired(
product,
warehouse,
t
);
The stock position is evidence used by the rule. The product is what must be reordered.
7. Represent Arithmetic as Explicit Support Relations
The ontology introduces typed relations for arithmetic support:
define .:#Subtract(
left:#Quantity,
right:#Quantity,
result:#Quantity
);
define .:#LessThan(
left:#Quantity,
right:#Quantity
);
define .:#GreaterThan(
left:#Quantity,
right:#Quantity
);
define .:#Negative(
value:#Quantity
);
This separates the conceptual rule from the implementation of arithmetic evaluation.
The stock-position consistency rule becomes:
.:#StockPosition(position)[
product=product,
warehouse=warehouse,
at=t,
onHand=onHand,
reserved=reserved,
available=available
]
and
.:#Subtract(onHand, reserved, available)
->
.:#ConsistentStockPosition(
position,
product,
warehouse,
t
);
8. Propagate Movement Classification from Attributes
Because kind is an attribute of the identified movement, specialized movement concepts are derived from its value:
.:#StockMovement(movement)[
kind=#Receipt
]
->
.:#ReceiptMovement(movement);
The same pattern is applied to reservations, releases, and shipments.
9. Build the Temporal Reorder Rule
The reorder condition requires a compatible stock position, policy, and numerical comparison:
.:#StockPosition(position)[
product=product,
warehouse=warehouse,
at=t,
available=available
]
and
.:#ReorderPolicy(policy)[
product=product,
warehouse=warehouse,
validAt=t,
threshold=threshold
]
and
.:#LessThan(available, threshold)
->
.:#BelowReorderThreshold(
position,
product,
warehouse,
t
);
Then:
.:#BelowReorderThreshold(
position,
product,
warehouse,
t
)
->
.:#ReorderRequired(
product,
warehouse,
t
);
The chain is:
StockPosition at T
+ ReorderPolicy valid at T
+ available < threshold
→ BelowReorderThreshold at T
→ ReorderRequired for Product and Warehouse at T
10. Model Inconsistency as Positive Diagnostic Facts
The ontology avoids using a complement expression against #Bottom to represent invalidity. With #Bottom as the empty set, the complement of #Bottom is #Top, so such an expression would not mean “invalid”.
Instead, invalidity is represented as an explicit derived fact:
define .:#InvalidStockPosition(
position:#StockPosition,
product:#Product,
warehouse:#Warehouse,
at:#Time
);
It is propagated when:
onHandis negative;reservedis negative;reservedis greater thanonHand.
This form is explicit, queryable, and explainable.
11. Understand the Native Hypergraph Indexes
A relation already produces native indexes according to its slots.
For:
.:#K(x, y)
corresponding to the triple (K, x, y), the hypergraph already maintains:
(x, y)on theKposition;(K, y)on thexposition;(K, x)on theyposition.
An explicit #Indexed projection should therefore be added only when:
- source values are stored as attributes rather than slots;
- a frequent query needs another leading-slot order;
- a derived conclusion needs a dedicated materialized access path.
12. Turn Entity Attributes into Indexed Tuples
Entity attributes are propagated into ordered tuple relations:
define .:#StockMovementByWarehouseTimeKind(
warehouse:#Warehouse,
occurredAt:#Time,
kind:#MovementKind,
product:#Product,
movement:#StockMovement
);
#StockMovementByWarehouseTimeKind::#Indexed;
.:#StockMovement(movement)[
product=product,
warehouse=warehouse,
kind=kind,
occurredAt=occurredAt
]
->
.:#StockMovementByWarehouseTimeKind(
warehouse,
occurredAt,
kind,
product,
movement
);
The rule extracts attribute values and materializes them as an indexed tuple.
13. Choose Indexes from Real Query Patterns
The useful query orders identified for this module are:
Movement searches
warehouse → time → kind → product → movement
product → time → warehouse → kind → movement
kind → time → warehouse → product → movement
Stock-position searches
product → warehouse → time → position
warehouse → time → product → position
time → warehouse → product → position
Reorder-policy searches
product → warehouse → time → policy
warehouse → time → product → policy
Derived alert searches
warehouse → time → product → position
warehouse → time → product
product → time → warehouse
Only useful orders are materialized. The ontology does not create every possible permutation.
14. #Indexed as Logical Materialization
A second-order declaration marks the target relation as an effective index:
#StockPositionByWarehouseTimeProduct::#Indexed;
Its implication defines the propagation:
.:#StockPosition(position)[
product=product,
warehouse=warehouse,
at=at
]
->
.:#StockPositionByWarehouseTimeProduct(
warehouse,
at,
product,
position
);
The mechanism is:
source entity or derived fact
→ logical implication
→ projected tuple
→ #Indexed type
→ effective hypergraph index
Indexed types do not introduce new business meaning. They materialize an existing fact in a query-oriented order.
15. Consistency Requirements for Inferred Indexes
Engine validation should verify that:
- source facts create the expected indexed tuples;
- repeated propagation does not create incoherent duplicates;
- multiple justifications are tracked;
- removing the last justification retracts the tuple;
- temporal indexes retain their time role;
- index rules do not form self-recreating cycles.
A useful design rule is:
A
#Indexedtype must not introduce new business meaning. It must materialize a projection or ordering of meaning already present in source facts or deductions.
16. Manual Propagation Example
Assume at T1:
Product = P1
Warehouse = W1
onHand = 10
reserved = 4
available = 6
threshold = 7
Arithmetic support gives:
Subtract(10, 4, 6)
LessThan(6, 7)
Logical propagation gives:
ConsistentStockPosition(Position1, P1, W1, T1)
BelowReorderThreshold(Position1, P1, W1, T1)
ReorderRequired(P1, W1, T1)
Indexed propagation gives:
StockPositionByWarehouseTimeProduct(W1, T1, P1, Position1)
BelowReorderThresholdByWarehouseTimeProduct(W1, T1, P1, Position1)
ReorderRequiredByWarehouseTimeProduct(W1, T1, P1)
ReorderRequiredByProductTimeWarehouse(P1, T1, W1)
If instead:
onHand = 5
reserved = 7
then:
GreaterThan(7, 5)
→ InvalidStockPosition(Position2, P1, W1, T1)
→ InvalidStockPositionByWarehouseTimeProduct(W1, T1, P1, Position2)
17. Validation Status
The current construction has passed the GOLD parser, including:
- entity definitions with
me; - typed attributes;
- complete types;
- direct instances;
- temporal relations;
- implications;
- arithmetic support relations;
#Indexeddeclarations;- attribute-to-tuple propagation;
- alternative index orders.
This confirms grammar acceptance.
The H-Logic engine validation phase should next test:
- movement classification;
- arithmetic relation evaluation;
- temporal unification;
- reorder propagation;
- invalid-state propagation;
- indexed tuple creation;
- duplicate and justification handling;
- retraction.
18. Complete Parsed Ontology
// ============================================================================
// ERP Stock Management Ontology
// H-Logic pivot representation
// ============================================================================
//
// Scope:
// - identified business entities for products, warehouses, movements,
// stock positions and reorder policies;
// - a complete vocabulary for movement kinds;
// - temporal stock state and temporal deductions;
// - explicit arithmetic support relations;
// - logical propagation rules.
//
// This ontology is intended for parser and engine validation in a later step.
// ============================================================================
// -----------------------------------------------------------------------------
// 1. Foundational concepts
// -----------------------------------------------------------------------------
define .:#Product(me);
define .:#Warehouse(me);
define .:#Time(me);
define .:#Quantity(me);
// -----------------------------------------------------------------------------
// 2. Complete movement-kind vocabulary
// -----------------------------------------------------------------------------
define .:#MovementKind(me);
#MovementKind::#Complete;
#Receipt:#MovementKind;
#Reservation:#MovementKind;
#Release:#MovementKind;
#Shipment:#MovementKind;
// -----------------------------------------------------------------------------
// 3. Identified business entities
// -----------------------------------------------------------------------------
// A stock movement is an identified ERP object.
// Its business fields are attributes of the movement entity.
define .:#StockMovement(me)[
product:#Product,
warehouse:#Warehouse,
kind:#MovementKind,
quantity:#Quantity,
occurredAt:#Time
];
// A stock position is an identified temporal state.
// It represents the state of one product in one warehouse at one time.
define .:#StockPosition(me)[
product:#Product,
warehouse:#Warehouse,
at:#Time,
onHand:#Quantity,
reserved:#Quantity,
available:#Quantity
];
// A reorder policy is an identified temporal policy.
define .:#ReorderPolicy(me)[
product:#Product,
warehouse:#Warehouse,
validAt:#Time,
threshold:#Quantity,
targetLevel:#Quantity
];
// -----------------------------------------------------------------------------
// 4. Movement classifications
// -----------------------------------------------------------------------------
define .:#ReceiptMovement(me);
define .:#ReservationMovement(me);
define .:#ReleaseMovement(me);
define .:#ShipmentMovement(me);
// -----------------------------------------------------------------------------
// 5. Derived temporal relations
// -----------------------------------------------------------------------------
define .:#ConsistentStockPosition(
position:#StockPosition,
product:#Product,
warehouse:#Warehouse,
at:#Time
);
define .:#BelowReorderThreshold(
position:#StockPosition,
product:#Product,
warehouse:#Warehouse,
at:#Time
);
define .:#ReorderRequired(
product:#Product,
warehouse:#Warehouse,
at:#Time
);
define .:#InvalidStockPosition(
position:#StockPosition,
product:#Product,
warehouse:#Warehouse,
at:#Time
);
// -----------------------------------------------------------------------------
// 6. Arithmetic support relations
// -----------------------------------------------------------------------------
define .:#Subtract(
left:#Quantity,
right:#Quantity,
result:#Quantity
);
define .:#LessThan(
left:#Quantity,
right:#Quantity
);
define .:#GreaterThan(
left:#Quantity,
right:#Quantity
);
define .:#Negative(
value:#Quantity
);
// -----------------------------------------------------------------------------
// 7. Movement-kind propagation
// -----------------------------------------------------------------------------
.:#StockMovement(movement)[
kind=#Receipt
]
->
.:#ReceiptMovement(movement);
.:#StockMovement(movement)[
kind=#Reservation
]
->
.:#ReservationMovement(movement);
.:#StockMovement(movement)[
kind=#Release
]
->
.:#ReleaseMovement(movement);
.:#StockMovement(movement)[
kind=#Shipment
]
->
.:#ShipmentMovement(movement);
// -----------------------------------------------------------------------------
// 8. Stock-position consistency
// -----------------------------------------------------------------------------
// available = onHand - reserved, at the same temporal stock position.
.:#StockPosition(position)[
product=product,
warehouse=warehouse,
at=t,
onHand=onHand,
reserved=reserved,
available=available
]
and
.:#Subtract(onHand, reserved, available)
->
.:#ConsistentStockPosition(
position,
product,
warehouse,
t
);
// -----------------------------------------------------------------------------
// 9. Temporal reorder propagation
// -----------------------------------------------------------------------------
// A stock position is below threshold only when the position and policy
// concern the same product, warehouse and time.
.:#StockPosition(position)[
product=product,
warehouse=warehouse,
at=t,
available=available
]
and
.:#ReorderPolicy(policy)[
product=product,
warehouse=warehouse,
validAt=t,
threshold=threshold
]
and
.:#LessThan(available, threshold)
->
.:#BelowReorderThreshold(
position,
product,
warehouse,
t
);
// The recommendation concerns the product in its warehouse at that time.
.:#BelowReorderThreshold(
position,
product,
warehouse,
t
)
->
.:#ReorderRequired(
product,
warehouse,
t
);
// -----------------------------------------------------------------------------
// 10. Invalid temporal stock states
// -----------------------------------------------------------------------------
// Physical stock cannot be negative.
.:#StockPosition(position)[
product=product,
warehouse=warehouse,
at=t,
onHand=onHand
]
and
.:#Negative(onHand)
->
.:#InvalidStockPosition(
position,
product,
warehouse,
t
);
// Reserved stock cannot be negative.
.:#StockPosition(position)[
product=product,
warehouse=warehouse,
at=t,
reserved=reserved
]
and
.:#Negative(reserved)
->
.:#InvalidStockPosition(
position,
product,
warehouse,
t
);
// Reserved stock cannot exceed physical stock.
.:#StockPosition(position)[
product=product,
warehouse=warehouse,
at=t,
onHand=onHand,
reserved=reserved
]
and
.:#GreaterThan(reserved, onHand)
->
.:#InvalidStockPosition(
position,
product,
warehouse,
t
);
// -----------------------------------------------------------------------------
// 11. Indexed query projections
// -----------------------------------------------------------------------------
//
// Native relation indexes are not duplicated here.
//
// For a relation such as .:#K(x, y), the hypergraph already maintains the
// natural indexes associated with each slot:
// - x, y on the relation position K;
// - K, y on the x position;
// - K, x on the y position.
//
// The projections below are therefore introduced only when:
// - source values are stored as entity attributes and must become indexed slots;
// - a frequent query needs a different leading-slot order;
// - a derived business conclusion needs a dedicated query-oriented order.
//
// These indexed types do not introduce new business meaning. They materialize
// projections of existing facts.
// -----------------------------------------------------------------------------
// 11.1 Stock-movement indexes
// -----------------------------------------------------------------------------
// Query:
// Find movements for one warehouse and one time, then refine by kind.
define .:#StockMovementByWarehouseTimeKind(
warehouse:#Warehouse,
occurredAt:#Time,
kind:#MovementKind,
product:#Product,
movement:#StockMovement
);
#StockMovementByWarehouseTimeKind::#Indexed;
.:#StockMovement(movement)[
product=product,
warehouse=warehouse,
kind=kind,
occurredAt=occurredAt
]
->
.:#StockMovementByWarehouseTimeKind(
warehouse,
occurredAt,
kind,
product,
movement
);
// Query:
// Reconstruct the movement history of a product in chronological context.
define .:#StockMovementByProductTimeWarehouse(
product:#Product,
occurredAt:#Time,
warehouse:#Warehouse,
kind:#MovementKind,
movement:#StockMovement
);
#StockMovementByProductTimeWarehouse::#Indexed;
.:#StockMovement(movement)[
product=product,
warehouse=warehouse,
kind=kind,
occurredAt=occurredAt
]
->
.:#StockMovementByProductTimeWarehouse(
product,
occurredAt,
warehouse,
kind,
movement
);
// Query:
// Find movements of a given kind over time, independently of their entity
// attribute layout.
define .:#StockMovementByKindTimeWarehouse(
kind:#MovementKind,
occurredAt:#Time,
warehouse:#Warehouse,
product:#Product,
movement:#StockMovement
);
#StockMovementByKindTimeWarehouse::#Indexed;
.:#StockMovement(movement)[
product=product,
warehouse=warehouse,
kind=kind,
occurredAt=occurredAt
]
->
.:#StockMovementByKindTimeWarehouse(
kind,
occurredAt,
warehouse,
product,
movement
);
// -----------------------------------------------------------------------------
// 11.2 Stock-position indexes
// -----------------------------------------------------------------------------
// Query:
// Find the state of a product in a warehouse at a given time.
define .:#StockPositionByProductWarehouseTime(
product:#Product,
warehouse:#Warehouse,
at:#Time,
position:#StockPosition
);
#StockPositionByProductWarehouseTime::#Indexed;
.:#StockPosition(position)[
product=product,
warehouse=warehouse,
at=at
]
->
.:#StockPositionByProductWarehouseTime(
product,
warehouse,
at,
position
);
// Query:
// Browse all product positions for a warehouse at a given time.
define .:#StockPositionByWarehouseTimeProduct(
warehouse:#Warehouse,
at:#Time,
product:#Product,
position:#StockPosition
);
#StockPositionByWarehouseTimeProduct::#Indexed;
.:#StockPosition(position)[
product=product,
warehouse=warehouse,
at=at
]
->
.:#StockPositionByWarehouseTimeProduct(
warehouse,
at,
product,
position
);
// Query:
// Browse the complete stock state at one time, grouped by warehouse.
define .:#StockPositionByTimeWarehouseProduct(
at:#Time,
warehouse:#Warehouse,
product:#Product,
position:#StockPosition
);
#StockPositionByTimeWarehouseProduct::#Indexed;
.:#StockPosition(position)[
product=product,
warehouse=warehouse,
at=at
]
->
.:#StockPositionByTimeWarehouseProduct(
at,
warehouse,
product,
position
);
// -----------------------------------------------------------------------------
// 11.3 Reorder-policy indexes
// -----------------------------------------------------------------------------
// Query:
// Resolve the policy applicable to a product, warehouse and time.
define .:#ReorderPolicyByProductWarehouseTime(
product:#Product,
warehouse:#Warehouse,
validAt:#Time,
policy:#ReorderPolicy
);
#ReorderPolicyByProductWarehouseTime::#Indexed;
.:#ReorderPolicy(policy)[
product=product,
warehouse=warehouse,
validAt=validAt
]
->
.:#ReorderPolicyByProductWarehouseTime(
product,
warehouse,
validAt,
policy
);
// Query:
// Browse all policies applicable to one warehouse at a given time.
define .:#ReorderPolicyByWarehouseTimeProduct(
warehouse:#Warehouse,
validAt:#Time,
product:#Product,
policy:#ReorderPolicy
);
#ReorderPolicyByWarehouseTimeProduct::#Indexed;
.:#ReorderPolicy(policy)[
product=product,
warehouse=warehouse,
validAt=validAt
]
->
.:#ReorderPolicyByWarehouseTimeProduct(
warehouse,
validAt,
product,
policy
);
// -----------------------------------------------------------------------------
// 11.4 Derived-alert indexes
// -----------------------------------------------------------------------------
// The original relation order is:
// position, product, warehouse, at.
//
// This projection supports the frequent warehouse-at-time access path.
define .:#BelowReorderThresholdByWarehouseTimeProduct(
warehouse:#Warehouse,
at:#Time,
product:#Product,
position:#StockPosition
);
#BelowReorderThresholdByWarehouseTimeProduct::#Indexed;
.:#BelowReorderThreshold(
position,
product,
warehouse,
at
)
->
.:#BelowReorderThresholdByWarehouseTimeProduct(
warehouse,
at,
product,
position
);
// The original relation order is:
// product, warehouse, at.
//
// This projection gives warehouse and time the leading positions.
define .:#ReorderRequiredByWarehouseTimeProduct(
warehouse:#Warehouse,
at:#Time,
product:#Product
);
#ReorderRequiredByWarehouseTimeProduct::#Indexed;
.:#ReorderRequired(
product,
warehouse,
at
)
->
.:#ReorderRequiredByWarehouseTimeProduct(
warehouse,
at,
product
);
// This projection supports product-centric planning over time.
define .:#ReorderRequiredByProductTimeWarehouse(
product:#Product,
at:#Time,
warehouse:#Warehouse
);
#ReorderRequiredByProductTimeWarehouse::#Indexed;
.:#ReorderRequired(
product,
warehouse,
at
)
->
.:#ReorderRequiredByProductTimeWarehouse(
product,
at,
warehouse
);
// The original relation order is:
// position, product, warehouse, at.
//
// This projection supports warehouse-at-time consistency diagnostics.
define .:#InvalidStockPositionByWarehouseTimeProduct(
warehouse:#Warehouse,
at:#Time,
product:#Product,
position:#StockPosition
);
#InvalidStockPositionByWarehouseTimeProduct::#Indexed;
.:#InvalidStockPosition(
position,
product,
warehouse,
at
)
->
.:#InvalidStockPositionByWarehouseTimeProduct(
warehouse,
at,
product,
position
);
19. Reusable Construction Method
The same methodology can be used for other ERP modules:
- write a compact business specification;
- identify entities and pure relations;
- use
meonly where identity is required; - place entity data in attributes;
- declare structural types first;
- model closed vocabularies with
#Complete; - use
:for direct instances; - propagate time through facts and deductions;
- attach conclusions to the correct business object;
- express arithmetic through typed support relations;
- derive explicit diagnostic facts;
- rely on native slot indexes when sufficient;
- create
#Indexedprojections for attributes or alternative orders; - select indexes from real query patterns;
- validate grammar, then engine semantics, then
.modelgeneration.
H-Logic acts here as a controlled pivot language between requirements, LLM analysis, logical validation, indexed hypergraph execution, and generated logiCells models.