Chapter 1 · Purpose, scope and standards
One registry, one number
Most reporting tools let anyone calculate a figure inside a chart. This module does the opposite: dashboards, reports and alerts can show only numbers defined once, in the data fabric's metric registry, so the same figure means the same thing on every screen.
20 min8 dash_ tables9 capabilities4 standards
By the end of this chapter you can
- Explain why a figure is defined once in a registry and read everywhere, instead of calculated in each chart.
- Say what the module covers, and what stays with the data fabric.
- Name the module's two pitfalls and the four standards it applies.
Why a figure is defined once
Business intelligence tools such as Microsoft Power BI, Tableau, Looker, Metabase, Grafana, Domo, Qlik Sense and Klipfolio let a builder drag a field onto a chart and calculate a figure there. That freedom is their strength. It is also how two reports come to show different answers for the same week: each chart keeps its own copy of the formula, and the copies drift apart.
This module gives every role the numbers it decides with from one source. A dashboard panel names a metric that is registered in the data fabric, and the platform reads the figure from the registry. The panel cannot hold a typed number or a figure calculated on the screen. The result is that the screen, the emailed report and the alert all show the same number, and any number can be followed back to its definition and its data.
What the module covers
The module builds dashboards and panels on registered metrics and dimensions, shares them by role through the Role and Exposure Matrix, adds filters, drill-down and time comparison, sends scheduled reports, raises alerts that a person acknowledges, and shows where any number comes from. It also embeds the same views on the three surfaces: operator at the machine, supervisor on the floor and manager in the office.
| Capability | What it does | Tier |
|---|---|---|
| Metric-bound panels | Every panel names a registered metric and its dimensions. It cannot hold a typed or computed-on-screen number. | MVP |
| Dashboards and layout | Dashboards per role and surface; versioned layouts; drafts and publish. | MVP |
| Filters and drill-down | Site, line, product and period filters; drill to the records behind a number. | MVP |
| Comparisons | Period over period, and target against actual, with the target's owner and source. | STD |
| Sharing and access | Shared by role through the Role and Exposure Matrix; row-level security on every query. | MVP |
| Scheduled reports | PDF or CSV snapshots on a schedule, with a delivery log. | STD |
| Alerts | Threshold and anomaly alerts on a metric, with acknowledgement and escalation. | STD |
| Lineage | From any value to its metric definition, version and source data. | MVP |
| Coverage reporting | Shows which panels have data behind them. A panel with no data says "no data", never zero. | MVP |
MVP capabilities are the must-haves before a business can cancel its old tool with confidence. STD capabilities match what mainstream tools offer.
Two pitfalls to design out
| Pitfall | How it shows | What the module does |
|---|---|---|
| Calculating a KPI in the chart | Two reports give two answers for one figure, and nobody knows which is right. | The panel stores only a metric key. The formula lives once in the registry, with an owner and a version. |
| Showing zero when there is no data | A line that did not run, or a site with no records, reads as a perfect score. | No rows for the filter and period means "no data" with the reason. Only a stored 0 is shown as 0. |
Four standards the module applies
| Standard | Use in this module |
|---|---|
| ISO 22400-2 | Definitions of manufacturing KPIs shown on operations dashboards [1]. |
| IBCS | Consistent notation for business charts and variance displays [2]. |
| ISO 8601 | Dates, times and periods in filters and exports [3]. |
| W3C WCAG 2.2 AA | Accessible charts: contrast, text alternatives and keyboard use [4]. |
You need: The reporting tool or spreadsheets your business uses today, and the person who owns the main report
Do this on your own business's reports. Use roles, never names, and keep any export in a private folder outside the repository.
Outcome: A list of the report's figures sorted by where each is calculated, one pair of figures that disagree (or a note that you found none), and one example of zero shown for no data.
Knowledge check
A report shows scrap rate calculated in the chart, and a second report shows it calculated differently. What is the lasting fix?
Knowledge check
A line did not run last week and the source has no rows for it. What should its panel show?
References
- ISO 22400-2:2014 Key performance indicators for manufacturing operations management. https://www.iso.org/standard/54497.html
- International Business Communication Standards (IBCS). https://www.ibcs.com/standards/
- ISO 8601 date and time format. https://www.iso.org/iso-8601-date-and-time-format.html
- W3C Web Content Accessibility Guidelines (WCAG) 2.2. https://www.w3.org/TR/WCAG22/
Chapter 2 · The data model
Tables, panels and versions
Eight dash_ tables hold dashboards, panels, shares, report schedules and alerts. They point at a small registry of metrics and at the spine's people, roles and files, and a set of database rules keeps every number honest.
25 min8 dash_ tables5 registry tables7 chart types
By the end of this chapter you can
- Name the eight dash_ tables, the registry tables they read and the spine tables they use.
- Read the column tables: types, required columns and allowed values.
- Explain draft, publish and immutable versions, and the rules the database enforces.
The tables and what they point at
Every table in the module starts with the standard columns that every spine table has: id, tenant_id, created_at, created_by, updated_at, updated_by, row_version, archived_at and ext. The module has no people table, role list or file store of its own. It points at core_person, core_job_role, core_org_unit, core_attachment and core_action_item.
| Table | What a row is |
|---|---|
| dash_dashboard | A dashboard for a role and a surface. |
| dash_dashboard_version | An immutable published layout. |
| dash_panel | A panel bound to a registered metric. |
| dash_dashboard_share | Who may see a dashboard: a job role, an org unit or a person. |
| dash_report_schedule | A scheduled snapshot of a dashboard, as PDF or CSV. |
| dash_report_delivery | One scheduled report sent, failed or skipped. |
| dash_alert_rule | A threshold or anomaly rule on a metric. |
| dash_alert_event | An alert raised, and how it was handled. |
Column reference
These are the columns your AI coding agent builds from. Required means not null; allowed values are check constraints, which the database tests on every row. A foreign key is a column whose value must match the id of a row in another table. jsonb is a column type that holds structured data written like {"site": "S1"}, which Postgres can search inside.
dash_dashboard
| Column | Type | Required | Allowed values or note |
|---|---|---|---|
| key | text | yes | Unique per business, for example line-1-operator. |
| title | text | yes | |
| surface | text | yes | operator, supervisor, manager |
| owner_id | uuid | yes | Foreign key to core_person. |
| status | text | yes | draft, published, archived |
| published_version_id | uuid | no | Must name a version of this same dashboard. |
dash_dashboard_version
| Column | Type | Required | Allowed values or note |
|---|---|---|---|
| dashboard_id | uuid | yes | Foreign key to dash_dashboard. |
| version_no | int | yes | Unique per dashboard. |
| layout | jsonb | yes | The panels as published. Holds no value, number or formula of its own. |
| published_at | timestamp | no | ISO 8601 in UTC. |
| published_by | uuid | no | Foreign key to core_person. |
dash_panel
| Column | Type | Required | Allowed values or note |
|---|---|---|---|
| dashboard_id | uuid | yes | Foreign key to dash_dashboard. |
| title | text | yes | |
| metric_key | text | yes | The code of an approved metric in the registry. Not a foreign key: a rule checks it. |
| dimensions | jsonb | no | A filter of dimension codes and values, and an optional group_by. |
| chart | text | yes | number, line, bar, table, gauge, heatmap, pareto |
| default_period | text | yes | An ISO 8601 duration of the form PnD or PnW, such as P7D. |
| target_metric_key | text | no | Used only when the target is itself a registered metric. |
| seq | int | yes | Order on the dashboard. |
dash_dashboard_share
| Column | Type | Required | Allowed values or note |
|---|---|---|---|
| dashboard_id | uuid | yes | Foreign key to dash_dashboard. |
| job_role_id | uuid | no | Foreign key to core_job_role. |
| org_unit_id | uuid | no | Foreign key to core_org_unit. |
| person_id | uuid | no | Foreign key to core_person. Exactly one of the three targets must be set. |
dash_report_schedule
| Column | Type | Required | Allowed values or note |
|---|---|---|---|
| dashboard_id | uuid | yes | Foreign key to dash_dashboard. |
| cron | text | yes | Five fields: minute, hour, day of month, month, day of week. |
| format | text | yes | pdf, csv |
| recipients | jsonb | yes | A list of core_person ids, never typed email addresses. |
| filters | jsonb | no | Dimension codes and values that narrow every panel. |
| active | bool | yes |
dash_report_delivery
| Column | Type | Required | Allowed values or note |
|---|---|---|---|
| report_schedule_id | uuid | yes | Foreign key to dash_report_schedule. |
| at | timestamp | yes | |
| status | text | yes | sent, failed, skipped_no_data |
| attachment_id | uuid | no | Foreign key to core_attachment. Empty when skipped_no_data. |
| error | text | no | The error text of a failed delivery. |
dash_alert_rule
| Column | Type | Required | Allowed values or note |
|---|---|---|---|
| metric_key | text | yes | The code of an approved metric. |
| dimensions | jsonb | no | Dimension codes and values the rule watches. |
| kind | text | yes | threshold, anomaly |
| operator | text | yes | gt, lt, outside_band |
| value | numeric | no | The limit, or the width of the band. |
| severity | text | yes | info, warning, critical |
| owner_id | uuid | yes | Foreign key to core_person. |
| active | bool | yes |
dash_alert_event
| Column | Type | Required | Allowed values or note |
|---|---|---|---|
| alert_rule_id | uuid | yes | Foreign key to dash_alert_rule. |
| raised_at | timestamp | yes | |
| observed_value | numeric | no | The value that broke the rule. |
| status | text | yes | open, acknowledged, resolved |
| acknowledged_by | uuid | no | Foreign key to core_person. Always the signed-in person. |
| acknowledged_at | timestamp | no | |
| action_item_id | uuid | no | Foreign key to core_action_item. |
The registry tables the dashboards read
The data fabric owns these. The course builds the five it needs, each with the standard columns and row-level security. Keep numerator, denominator and value as exact decimals (numeric), never floating point, so a rate adds up the same way every time.
| Table | Columns |
|---|---|
| fab_entity_registry | table_name (unique), module_code, entity_kind (master, transaction, event, timeseries, aggregate, metadata, academy), description, classification (public, internal, confidential, restricted), contains_pii, contains_health. |
| fab_dimension | code (unique), name, source_entity_id, hierarchy (jsonb), status (active, deprecated). |
| fab_metric | code (unique), name, definition, formula, standard_ref, uom_id, grain (event, shift, day, week, month, order, lot, asset, person), direction (higher_better, lower_better, on_target), target (number), owner_module, status (draft, approved, deprecated). |
| fab_metric_input | metric_id, entity_id, role (numerator, denominator, filter, dimension, source). |
| fab_metric_value | metric_id, grain, period_start, period_end, dimension_key (jsonb), value, numerator, denominator, computed_at, quality_score. Append-only: a correction is a new row with a later computed_at. Every dimension_key includes the site code, because operator and supervisor reads are limited to their own site by it. |
A panel is a question, not a number
A panel row says which metric to ask for, how to slice it, how to draw it and over which period. It never says what the answer is.
{
"dashboard": "line-1-operator",
"title": "Scrap rate, last 7 days",
"metric_key": "scrap_rate",
"dimensions": { "filter": { "site": "S1", "line": "L1" } },
"chart": "number",
"default_period": "P7D",
"target_metric_key": null,
"seq": 1
}| Chart | Use it to |
|---|---|
| number | Show one figure, with its target and the previous period. |
| line | Show a trend over days. |
| bar | Compare the members of one dimension, such as lines. |
| table | Show exact values. It also serves as the text alternative of the other charts. |
| gauge | Show one value against its target. |
| heatmap | Show two dimensions at once, such as line and day. |
| pareto | Rank causes by size, with the running share. |
Draft, publish, version
A builder edits a dashboard as a draft. Publishing writes the next row in dash_dashboard_version from the saved panels, sets published_version_id, and records a dash.dashboard.published event. Viewers read only the published version, so editing a panel changes nothing for them until the next publish. A published layout never changes: a change is a new version, and the older ones remain as the record of what readers saw.
The module publishes dash.dashboard.published, dash.alert.raised and dash.alert.acknowledged. It consumes fab.metric.version_published, so the owners of dashboards that use a metric are told when its meaning changes, and fab.quality.score_dropped. A metric's version is its row_version, and ISO 8601 [2] fixes how every date and period is written. A metric may carry a reference to a standard such as ISO 22400-2 in standard_ref [3].
You need: One figure from your own main report (from the previous exercise) and a text editor
Do this on paper or in a text file. Nothing is built yet.
Outcome: A one-page metric definition and a panel row that names it, with no value or formula in the panel.
Knowledge check
What makes sure a panel cannot hold its own number?
Knowledge check
A published dashboard needs a change to one panel. What happens?
References
- PostgreSQL documentation: Row security policies. https://www.postgresql.org/docs/current/ddl-rowsecurity.html
- ISO 8601 date and time format. https://www.iso.org/iso-8601-date-and-time-format.html
- ISO 22400-2:2014 Key performance indicators for manufacturing operations management. https://www.iso.org/standard/54497.html
Chapter 3 · Roles, rules and lineage
Who sees what, and how a number is read
The same dashboard can be shared with a role and still show a person only their site's rows. One read function answers every question, ratios are added the right way, a missing value says no data, and any number opens to its records and its definition.
30 min8 roles1 read function3 surfaces
By the end of this chapter you can
- Say who may define, build, view, schedule and acknowledge, and how sharing differs from row-level security.
- Add up a ratio correctly, show no data instead of zero, and write a period and a comparison.
- Open a number to its records and its metric definition, and make a chart accessible and consistent.
Roles and surfaces
Roles are jobs, not names. Five are people; three are not and have credentials of their own, which you set in your host's environment settings and never paste into a chat or a file.
| Role | Does |
|---|---|
| Metric steward | Registers, approves, changes and retires metrics in the fabric, and registers the source table of each figure. Cannot edit a dashboard. |
| Dashboard builder | Drafts, publishes and shares dashboards, schedules reports and creates alert rules. Cannot approve a metric. |
| Operator | Reads dashboards shared with them, on the operator surface (phone, at the machine). |
| Supervisor | Reads dashboards, acknowledges alerts and creates action items, for their own site. |
| Manager | Reads dashboards for every site, acknowledges alerts, sees the delivery log and the health measures. |
| Report service | Not a person. Runs the scheduled reports and writes the delivery log. |
| Alert service | Not a person. Evaluates alert rules, escalates, and reacts to registry events. |
| Metric job | Not a person. Computes the values of your own figures. Reads no dash_ table. |
Two locks protect a dashboard. The first is sharing: a row in dash_dashboard_share names a job role, an org unit or a person, and a dashboard that is not shared with you never appears. The second is row-level security: every panel query runs with the reader's rights, so a reader gets only the rows of their site (a manager, every site). A dashboard can be shared with a person and still show them "no data" for rows they may not read.
The Postgres facts that decide how the access rights are written: an update or delete finds rows only through the role's read right, so a role that updates rows must also read them, and the changed row must still pass that read rule. An insert that returns its row needs the read right too. The rights written in the Role and Exposure Matrix must cover everything the later sessions ask a role to do, because the database rules are generated from it.
| Surface | Screens |
|---|---|
| Operator (phone) | /dashboards (the dashboards you can read), /dashboards/view/[key] (one dashboard), /dashboards/number/[key]/[panel] (drill and lineage), /dashboards/metric/[code] (a definition, read only). |
| Supervisor | /dashboards/alerts (acknowledge), /dashboards/reports (the reports listing). |
| Manager | /dashboards/health (panels with data, acknowledgement time, delivery success). |
| Builder and steward only | /dashboards/build (builder) and /fabric/metrics (steward). |
One read function
Every screen, report and alert reads a number through one database function, read_metric(code, from, to, filter, group_by). It runs with the caller's rights, so row-level security applies to every call. It keeps, for each day, dimension and metric, the row with the latest computed_at (a correction replaces a value without editing it), combines rows by the metric's aggregation, and returns the value, numerator, denominator, the number of days with data and days in the period, with the previous period and the metric's target and version. The browser never calculates.
How rows combine matters. A rate is 100 times the sum of the numerators divided by the sum of the denominators. It is never the average of averages.
Round once, at display, to one decimal place with exact halves going up, and compare the unrounded numbers. Totals, targets and variances are worked out before rounding.
No data, never zero
No value row for the filter and period means no data, with the reason. A stored 0 is shown as 0. If only some days have data, the panel says how many: "5 of 7 days". A filter that selects different members of the same dimension at once gives no data, because no row can be two members.
Periods, filters and comparisons
A period is written as an ISO 8601 duration or interval [1], and a time of day as a timestamp such as 2026-03-16T06:05:00Z [2]. Here P7D means the 7 whole days ending the day before the as-of date. With the as-of date 2026-03-16, the period is 2026-03-09/2026-03-15 and the previous period, the same length immediately before it, is 2026-03-02/2026-03-08. One clock function gives every date, so a test can say "today is 2026-03-16".
Filters (site, line, product, period) change the question a panel asks. They never change what a panel holds. A filter is intersected with the panel's own filter and with the reader's row-level scope. A target comes from the metric (its target, owner module and version), so the screen can show who owns the target and where it came from.
| On the practice data | Scrap rate | Scrap, units made |
|---|---|---|
| Period 2026-03-09/2026-03-15 | 3.3 | 31 of 950 |
| Previous period 2026-03-02/2026-03-08 | 2.0 | 19 of 950 |
| Variance | up 1.3 points | 3.263 minus 2.0, then rounded |
| Target (a practice value, not a recommended limit) | 3.0 | from the metric, with its owner |
Drill and lineage
A number is a link. The first click shows the value rows behind it: period, dimension, numerator, denominator, when each was computed and the metric version it was computed on. The second shows the metric's definition page: code, definition, formula, standard reference, grain, direction, target with its owner module, status and version. The third shows the source: the entities the metric reads, linked to the owning module's screen where one exists.
When a metric's definition changes, the registry publishes fab.metric.version_published. The alert service finds every published layout that names the metric and gives each dashboard owner one action item. The published versions do not change; the lineage page shows value rows computed on the old version beside the current one.
Charts people can read
- Text alternative. Each chart has the same numbers available as a table, so a screen reader or a printed copy loses nothing (WCAG 1.1.1) [4].
- Contrast. Text is at least 4.5 to 1 against its background (1.4.3) [5], and chart lines and bars at least 3 to 1 (1.4.11).
- Not by colour alone. Above or below target is written as a word as well as shown in a colour (1.4.1).
- Keyboard. Filters, drill links and the table toggle work from the keyboard (2.1.1) [3].
- Consistent notation. Following IBCS [6], use the same look for actual on every chart and a different but consistent look for the previous period and for the target; keep the same scale on charts that are compared; show a variance as a difference.
You need: A figure from your own data that is a ratio, a calculator or spreadsheet, and the record counts behind it
Use your own numbers. The aim is to prove the ratio of sums by hand before any screen is built.
Outcome: A hand-worked combined rate, the average of averages beside it, the week-on-week variance, and one note saying which days read as no data.
Knowledge check
Line A scraps 20 of 500 units and line B scraps 30 of 1,500. What is the plant's scrap rate?
Knowledge check
A supervisor at site S2 opens a dashboard that is shared with the supervisor role, but every panel is filtered to site S1. What do they see?
References
- ISO 8601 date and time format. https://www.iso.org/iso-8601-date-and-time-format.html
- RFC 3339: Date and Time on the Internet: Timestamps. https://www.rfc-editor.org/rfc/rfc3339
- W3C Web Content Accessibility Guidelines (WCAG) 2.2. https://www.w3.org/TR/WCAG22/
- W3C: Understanding Success Criterion 1.1.1 Non-text Content. https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html
- W3C: Understanding Success Criterion 1.4.3 Contrast (Minimum). https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html
- International Business Communication Standards (IBCS). https://www.ibcs.com/standards/
Chapter 4 · Sending, raising and replacing
Reports, alerts and cut-over
The numbers leave the screen as scheduled reports and alerts, the module measures itself with four figures, and the old tool is retired only after every number has been compared side by side.
25 min3 delivery statuses4 measures6-step cut-over
By the end of this chapter you can
- Describe a report run, its three delivery statuses and why an empty report never reads as zero.
- Explain how an alert is raised, acknowledged, escalated and linked to an action.
- Read the module's four measures and plan a side-by-side cut-over.
Scheduled reports
A dash_report_schedule row holds a five-field cron, a format (PDF or CSV), a list of recipients and the filters that narrow every panel. The report is built with the same read function as the screens, so it shows the same numbers. Recipients are people, never typed email addresses, and each must be able to read the dashboard and all the rows the filters select: the save route refuses a recipient who could not see it.
| Status | Meaning |
|---|---|
| sent | The file is stored and shown to every recipient, and emailed only when you have switched email on. |
| skipped_no_data | Every panel had no data. No file is made. |
| failed | The run failed. The error text is kept and the report is tried again at the next run as a new row. |
Alerts
An alert rule watches one registered metric. A threshold rule fires when a day's value is greater than (gt) or less than (lt) the rule's value. An anomaly rule (outside_band) fires when the day is further from the mean of the previous 7 daily values than the rule's value times their standard deviation, which is a measure of how spread out the values are. With fewer than 7 previous values it does not fire and says "not enough history". A flat history makes the band zero wide, so write down the smallest band you will accept on real data.
| Severity or status | Meaning |
|---|---|
| info, warning, critical | How serious the rule's owner judges a breach to be, set on the rule. |
| open | Raised and not yet seen by a person. |
| acknowledged | A signed-in person took it, and the time is recorded. They may link an action item. |
| resolved | The cause was handled. |
The module's four measures
The module measures itself with four figures. The first three come from its own tables, so none is typed in. They are module self-measures, worked out from the module's own dash_ tables and not read through the metric read function; they are not registered metrics, because the metric job cannot read a dash_ table and a dashboard that measured itself through its own panels would be circular.
| Measure | Definition | Practice data |
|---|---|---|
| Panels with data | Panels whose metric has data in the period, divided by all panels of the latest published versions the reader can read. Read as the dashboard builder, who can read every dashboard. | 9 of 10, 90.0% |
| Alert acknowledgement time | The median minutes from raised to acknowledged. The median is the middle value, or the mean of the two middle values. | 60 minutes |
| Report delivery success | The due times that have a sent delivery, divided by the due times in the period. A run considers the due times in the 24 hours up to its clock, so those are the due times inside the look-backs of the runs made in the period; a due time older than that is never back-filled. No table stores runs: they are inferred from the delivery rows, one run per distinct delivery time, so a run that found nothing due, or a missed run, leaves no trace. | 2 of 3, 66.7% |
| Retired BI seats | Paid licences cancelled after the cut-over, counted from the vendor's own seat list. | recorded in the cut-over notes |
Retired BI seats is counted outside the dashboards. A seat count typed into a panel would break the rule this module teaches.
Replace the old tool
List the old tool's most-used reports, define each figure once in the registry, rebuild those reports as dashboards, and run both side by side for a period. Compare every number before cancelling any seat.
- Explain every difference. Typical causes: the old chart's own formula against the registered definition, the day boundary or time zone, a filter, rounding, the refresh time, and zero shown by the old tool where the source had no data. A figure where the platform says "no data" and the old tool says 0 is a finding about the old tool.
- Decide with the figure's owner. If the old formula is right, change the definition in the registry once, as the metric steward, never in a chart.
- Write a rollback plan first. Name who tells the readers, the date after which the old reports are no longer used for decisions, who keeps the old tool read only, and who decides to roll back.
- Cancel with the facts. Check the vendor's notice period and how long you can still export after cancelling. Write the saving against the Retired BI seats measure.
You need: Your list of the five most-used reports, the old tool's own exports for one period, and the vendor's cancellation terms
Do this before you build anything. The old tool's exports stay in a private folder outside the repository.
Outcome: A comparison table ready to fill in, a cut-over date with a rollback plan, and the cancellation notice date written next to it.
Knowledge check
Every panel on a scheduled report has no data for the period. What should the delivery do?
Knowledge check
During the side-by-side week, one figure differs between the old tool and the registry. What is the right next step?
References
- ISO 22400-2:2014 Key performance indicators for manufacturing operations management. https://www.iso.org/standard/54497.html
- International Business Communication Standards (IBCS). https://www.ibcs.com/standards/
- ISO 8601 date and time format. https://www.iso.org/iso-8601-date-and-time-format.html
Chapter 5 · 12 questions · 80% passes
Final assessment
Twelve questions across the element. Score 80% (10 of 12) to pass. Your LMS records your score and each answer; you can review the chapters and try again.
15 min12 questions≈ 15 minutesRetake allowed
Your result
CivOps AI Academy
Dashboards and Reporting: Every Number from One Metric Registry
Element M23 complete · Learner
Your LMS records this completion. For the CivOps Foundation certificate, finish the Foundation Course at https://civops.io/learn.