Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Business modules

Explanation

Analytics facts

How each module declares its analysable data in analytics_facts.py and how the analytics module collects, validates, and permission-filters those fact sets over the EventBus.

This page explains how data reaches the analytics module. Nine modules each keep a file analytics_facts.py that declares one or more fact sets. The analytics module emits DescribeAnalyticsFacts on the EventBus, collects the answers in src/cluster_erp/modules/analytics/registry.py, validates them, and builds the catalogue every analysis reads. No module imports the analytics module, and the analytics module imports no module (src/cluster_erp/core/contracts.py:2055-2057).

In short

  • A fact set (AnalyticsFactSet) names its module, a key, a label, its grain, the physical source table, its dimensions and measures, a read permission, and a live filter (src/cluster_erp/core/contracts.py:2126-2154).
  • Each contributing module subscribes a handler to DescribeAnalyticsFacts that returns its fact sets (src/cluster_erp/modules/fleet/module.py:338-346).
  • registry.collect emits the event, type-checks and validates every answer, refuses duplicates, and returns a FactCatalogue (src/cluster_erp/modules/analytics/registry.py:242-283).
  • A caller sees only fact sets whose read_permission they hold (src/cluster_erp/modules/analytics/registry.py:100-109).

How it works

Collection path

sequenceDiagram
 participant S as AnalyticsService
 participant R as registry.collect
 participant B as EventBus
 participant M as module analytics_facts.py
 S->>R: catalogue(permission_codes, locale)
 R->>B: emit DescribeAnalyticsFacts
 B->>M: describe_analytics_facts(event)
 M-->>B: AnalyticsFactSet or list
 B-->>R: results of all handlers
 R->>R: type check, validate_fact, duplicate key check
 R-->>S: FactCatalogue
 S->>S: visible_to(permission_codes)

In prose:

  1. AnalyticsService.catalogue calls registry.collect with the caller's locale (src/cluster_erp/modules/analytics/service.py:55-62).
  2. collect emits DescribeAnalyticsFacts(source_module="analytics", locale=...). The locale only selects labels; keys stay language independent (src/cluster_erp/modules/analytics/registry.py:253, src/cluster_erp/core/contracts.py:2060-2073).
  3. The EventBus calls every subscribed handler and returns their results. A handler that raises is logged and contributes nothing (src/cluster_erp/core/event_bus.py:83-96).
  4. collect accepts one fact set or a list per handler. Anything else fails with ANALYTICS_INVALID_CONTRIBUTION (src/cluster_erp/modules/analytics/registry.py:254-266).
  5. validate_fact checks each set; a second set with the same key fails instead of overwriting (src/cluster_erp/modules/analytics/registry.py:267-277).
  6. visible_to keeps the sets whose read_permission is among the caller's permission codes (src/cluster_erp/modules/analytics/registry.py:100-109).

What a contribution declares

Part Rule checked by the registry Source
key, label, grain key matches a simple pattern; label and grain are required src/cluster_erp/modules/analytics/registry.py:183-190
source a table or schema-qualified relation name matching a strict pattern src/cluster_erp/modules/analytics/registry.py:45, src/cluster_erp/modules/analytics/registry.py:191-192
read_permission required src/cluster_erp/modules/analytics/registry.py:193-194
live_filter a column and is_null or is_false; the default is deleted_at with is_null src/cluster_erp/modules/analytics/registry.py:198-201, src/cluster_erp/core/contracts.py:2154
Dimensions data_type among text, date, number, boolean, reference; a reference names its target; a date declares grains; rollup only on references src/cluster_erp/modules/analytics/registry.py:121-147
Measures aggregation among sum, avg, min, max, count, count_distinct, median; additivity among additive, semi_additive, non_additive; a semi_additive measure names an existing axis src/cluster_erp/modules/analytics/registry.py:50-51, src/cluster_erp/modules/analytics/registry.py:150-180, src/cluster_erp/modules/analytics/registry.py:222-230
Money a non-empty currency_field marks a monetary measure and must also be exposed as a dimension src/cluster_erp/core/contracts.py:2110-2113, src/cluster_erp/modules/analytics/registry.py:231-240

Contributions by module

Found with grep -rl analytics_facts src/cluster_erp/modules: nine modules, 13 fact sets.

Module Fact keys Source table Read permission Monetary measures (currency_field) Source
fleet fleet_fuel, fleet_work_order, fleet_penalty, fleet_compliance fleet_fuel_entries, fleet_work_orders, fleet_penalties, fleet_compliance_items fleet_asset.read none declared src/cluster_erp/modules/fleet/analytics_facts.py:35-310
inventory goods_movement inventory_goods_movements goods_movement.read none; the module stores no money src/cluster_erp/modules/inventory/analytics_facts.py:29-109
parties party parties_parties party.read none; count only src/cluster_erp/modules/parties/analytics_facts.py:17-77
procurement purchase_order, purchase_request procurement_purchase_orders, procurement_purchase_requests purchase_order.read, purchase_request.read 4 src/cluster_erp/modules/procurement/analytics_facts.py:19-191
production production_order production_production_orders production_order.read none src/cluster_erp/modules/production/analytics_facts.py:18-110
products_and_services product products_products product.read none src/cluster_erp/modules/products_and_services/analytics_facts.py:21-94
purchasing_quotations purchasing_quotation purq_quotations purchasing_quotation.read 2 src/cluster_erp/modules/purchasing_quotations/analytics_facts.py:17-117
quotations sales_quote quot_quotes quote.read 3 src/cluster_erp/modules/quotations/analytics_facts.py:23-115
sales_orders sales_order so_sales_orders sales_order.read 3 src/cluster_erp/modules/sales_orders/analytics_facts.py:20-134

Each module subscribes in its register_events, for example src/cluster_erp/modules/inventory/module.py:119-124, src/cluster_erp/modules/parties/module.py:134-139, and src/cluster_erp/modules/sales_orders/module.py:80-93. procurement subscribes two handlers, one per fact set (src/cluster_erp/modules/procurement/module.py:212-219). Every set uses the default live filter on deleted_at (grep live_filter in the nine files).

Some modules state in their docstrings what they leave out and why. inventory leaves stock levels out (src/cluster_erp/modules/inventory/analytics_facts.py:1-8), and fleet leaves cost per kilometre and CO2 out because a ratio of two aggregates is not additive (src/cluster_erp/modules/fleet/analytics_facts.py:1-11).

Why it is built this way

  • The registry docstring states that the declaration is also the security allow-list: a user query may only reference a key, and a physical name never comes from user input (src/cluster_erp/modules/analytics/registry.py:1-9).
  • It fails loudly because a silently skipped contribution would mean that a module's data never entered the analysis, discovered only through a wrong number (src/cluster_erp/modules/analytics/registry.py:11-16).
  • The contract docstring calls live_filter mandatory because a set without it also counts deactivated records (src/cluster_erp/core/contracts.py:2139-2142).
  • The module docstring states that a future module can contribute with no change in the analytics module (src/cluster_erp/modules/analytics/module.py:18-22).

Rules and boundaries

  • A reference dimension must name its target so that labels resolve instead of showing raw ids (src/cluster_erp/modules/analytics/registry.py:131-135).
  • rollup marks a low-cardinality reference dimension for the summary cells (src/cluster_erp/core/contracts.py:2092-2097); see reports, summaries, and cross.
  • The analytics catalogue is rebuilt from the bus on each catalogue call (src/cluster_erp/modules/analytics/service.py:55-62).

Interaction with other parts

Contract Direction Purpose Source
DescribeAnalyticsFacts analytics emits, nine modules answer collect the declarations src/cluster_erp/core/contracts.py:2060-2073
AnalyticsFactSet, AnalyticsDimension, AnalyticsMeasure modules build the declaration shapes src/cluster_erp/core/contracts.py:2076-2154

The full contract list is on the contracts reference; the bus is described on event bus.

Limits and open points

  • The fleet fact sets sum total_cost and amount but declare no currency_field, so the mixed-currency gate does not apply to them (src/cluster_erp/modules/fleet/analytics_facts.py:98-104, src/cluster_erp/modules/fleet/analytics_facts.py:240-246).
  • A handler that raises an exception is logged by the bus and its fact sets are missing from the catalogue without an error to the caller (src/cluster_erp/core/event_bus.py:94-96).

Terms introduced

Term Code identifier Definition Turkish term Source
fact set AnalyticsFactSet One module's declaration of a single analysable subject with its grain, dimensions, and measures. Olgu kümesi src/cluster_erp/core/contracts.py:2126
grain grain What one row of a fact set is. Tanecik src/cluster_erp/core/contracts.py:2129-2131
additivity additivity Whether a measure may be summed across all, some, or no dimensions. Toplanabilirlik src/cluster_erp/core/contracts.py:2101-2110
live filter live_filter The column test that excludes deactivated rows from a fact set. Canlı süzgeç src/cluster_erp/core/contracts.py:2154

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13