Reporting, Dashboards & Exports (RPT)
Module code RPT · Release R1 (standard reports, dashboards, export), R2 (schedules, saved views), R4 (custom report builder, analytics warehouse) · Packaging Core (advanced analytics on Professional+) · Requirements
FR-RPT-*,BR-RPT-*in the PRD and BRD
1. Overview and scope
RPT is a framework, not a pile of reports. A report is a dataset definition (columns, joins through module read models, filters, default sort) evaluated under the viewer's permission scope. "The manager's attendance report" and "the HR attendance report" are the same dataset with a different scope. The framework also renders dashboards (KPI tiles and charts) and runs exports and schedules asynchronously.
In scope: dataset registry (code-defined per module); standard filter idiom (location, department, manager, employee, period); column chooser; saved views; Excel, CSV and PDF export through background jobs; scheduled email delivery; persona dashboards (owner, HR, manager, employee, finance, sales); drill-down from tile to rows; masking of sensitive columns; export audit.
R4: custom report builder over a semantic layer, trend analytics (attrition, absenteeism, OT cost, payroll cost), a columnar replica for heavy queries.
Out of scope: statutory files (PAY generates them), operational lists inside module screens (they use the same datasets but are not "reports").
2. Learned from the reference product
Source: Spryple analysis, §3.3, §14 and §21.
- [Observed] Almost every report offers Excel and PDF export (SP-X-05).
- [Observed] A shared filter idiom: City-Branch → Manager → Employee → Period, with Search and Clear (SP-X-06).
- [Observed] The Super Admin dashboard shows employees by location and department, headcount (active, new, exit), attendance by work type, employees on leave, shift-based headcount and payroll amount by department by month (SA: Dashboard).
- [Observed] The employee report has a column chooser (SP-RPT-01).
- [Inferred] Reports are re-implemented per role and module, with no saved filters, schedules, drill-down or trends (§14).
3. Our design decisions
| ID | Decision [Proposed] | Reason |
|---|---|---|
| D-RPT-01 | A dataset is defined once in code (packages/contracts plus a module read query) and registered with RPT; scope is applied by the framework using the IAM guard. | One implementation per report; consistent security. |
| D-RPT-02 | Exports always run as jobs, with a row-limited synchronous preview; files are stored behind a short-lived presigned URL. | Month-end export spikes must not hurt the API. |
| D-RPT-03 | Sensitive columns (salary, bank, PAN, Aadhaar) are excluded unless the viewer has the field permission; every export is audited. | DPDP and insider-risk controls. |
| D-RPT-04 | Dashboards are composed from widget definitions bound to datasets with an aggregation; per-persona defaults that the tenant can rearrange. | Persona KPIs without bespoke screens. |
| D-RPT-05 | Reports read from module read models or a read replica, never from module tables directly. | Respects module boundaries; protects the primary. |
4. Domain model
rpt_dataset is code-defined reference data (no tenant_id).
5. State machines
5.1 Export job
6. Key business logic
runReport(viewer, dataset, params):
require can(viewer, dataset.required_permission, any scope)
scope = widest scope the viewer holds for that permission
rows = dataset.query(params, scopeFilter(viewer, scope)) # employee ids in scope
cols = dataset.columns minus sensitive columns the viewer lacks permission for
if params.preview: return first 500 rows
enqueue export job ; audit (viewer, dataset, filters, row_count)
Period semantics: every dataset declares whether "period" means the pay period (from PAY schedules), the attendance window or the calendar month, so the same filter means the same thing across reports.
KPI definitions are fixed in code so every dashboard agrees:
| KPI | Definition |
|---|---|
| Headcount | Employees with a job assignment valid on the date and status not Separated |
| Attrition rate (monthly) | Separations in month / average of opening and closing headcount |
| Absenteeism rate | Unplanned absent and LOP days / scheduled working days |
| Late rate | Days with is_late / present days |
| OT cost | Sum of OT component amounts in locked runs |
| Payroll cost | Gross plus employer contributions in locked runs |
| Approval turnaround | Median hours from submission to final decision |
7. Configuration
| Setting | Type | Default | Description |
|---|---|---|---|
export.max_rows | integer | 200000 | Hard limit per export |
export.link_ttl_hours | integer | 24 | Presigned URL lifetime |
export.pdf_max_rows | integer | 5000 | Larger exports must use Excel or CSV |
schedule.max_per_tenant | integer | 50 | Plan-dependent |
dashboard.refresh_minutes | integer | 15 | Widget cache |
masking.default | boolean | true | Mask sensitive columns unless permitted |
8. Screens by persona
| Persona | Dashboard tiles (defaults) | Report access |
|---|---|---|
org_owner | Headcount by location and department, joiners and exits, attendance by work type, on leave today, payroll cost by department by month | All datasets at ORG |
hr_manager | Today's attendance, pending approvals, probation due, overdue onboarding tasks, leave trend | HR datasets at scope |
line_manager | Team present, absent and late today, pending approvals, team leave calendar | Team datasets |
employee | This month's attendance, leave balances, last payslip | Own data |
payroll_manager | Run status, variance, statutory totals, advance balances | Payroll datasets |
sales_manager | Team on field, visits, km, pipeline by stage (R3) | FFM datasets |
Mobile shows the same tiles for the employee and manager personas (see Mobile).
9. Events
| Event | Consumers |
|---|---|
rpt.export.ready | NTF (in-app link) |
rpt.export.failed | NTF |
rpt.schedule.delivered | Audit |
Consumed: none for computation (reads are pull-based). Cache invalidation listens to att.period.locked and pay.run.locked.
10. API surface
| Resource | Main operations |
|---|---|
/v1/rpt/datasets | GET catalogue visible to the viewer |
/v1/rpt/datasets/:key/query | POST preview with filters |
/v1/rpt/exports | POST, GET /:id |
/v1/rpt/views | CRUD saved views |
/v1/rpt/schedules | CRUD |
/v1/rpt/dashboards | GET by persona, PUT layout |
Detailed in API endpoints.
11. Permissions
Module report permissions (att.report.export, pay.report.export and so on) gate datasets. RPT adds:
| Permission | Scopes | Default roles |
|---|---|---|
rpt.view.share | ORG | hr_manager, org_owner |
rpt.schedule.manage | SELF, ORG | managers and admins |
rpt.dashboard.manage | ORG | org_owner, hr_admin |
rpt.builder.use (R4) | ORG | hr_admin, finance_admin |
12. Reports
The catalogue of standard datasets lives with each module (see the Reports section of each spec). RPT itself provides the export audit log and the schedule delivery log.
13. Release and priority
| Capability | Release | MoSCoW |
|---|---|---|
| Dataset framework, scope filters, column chooser, Excel/CSV/PDF export | R1 | Must |
| Persona dashboards with drill-down | R1 | Must |
| Saved views and schedules | R2 | Should |
| Read replica for reports | R2 | Should |
| Custom report builder and semantic layer | R4 | Could |
| Trend analytics (attrition, absenteeism, OT cost) | R4 | Should |
14. Open questions
- Should employees export their own data in machine-readable form through RPT as part of DPDP access requests? The default is a separate IAM/EMP data-access flow.
- Which charting library meets accessibility requirements in light and dark themes? To be decided with design.
- When do we add the columnar warehouse (CDC to ClickHouse or Redshift)? The architecture trigger is the 300-tenant scale stage.