Analytics Implementation Plan
This document defines how the analytics layer must be designed and implemented in rAInty.
It is intended as the input for phase 2 implementation.
The goal is to build a single, coherent, reusable analytics model that:
- uses only secure user-scoped data
- computes economic results only from reconciled transactions
- supports multiple pages and visualizations
- avoids duplicated calculation logic across frontend pages
- remains scalable as users, transactions, and entities grow
1. Scope and principles
1.1 Functional scope
The analytics layer must support:
- Overview page KPIs
- contract payment health indicators
- bank account economic summary
- property economic summary
- detailed property expense breakdown
- cross-dimensional income/expense reporting
- overdue and disputed item monitoring
1.2 Data inclusion rules
Economic analytics must include only data that is economically validated.
Included:
- bank transactions with
is_reconciled = 1,reconciliation_statusinAUTO_RECONCILEDorMANUALLY_RECONCILED, andreconciliation_outcomeinVALID,SPLIT, orPARTIAL - transaction splits whose parent transaction satisfies the same reconciled transaction rule and whose split source is
RECONCILIATION_RULE,EXPECTED_MATCH,RENT_MATCH, orMANUAL - manual payments that are explicitly
CONFIRMEDorPAIDand linked to an expected expense, utility bill, rent installment, or equivalent payable item - expense records created by valid flows and promoted to
CONFIRMEDorPAID
Excluded:
- ignored transactions, even when operational reconciliation marks them as closed
- non reconciled transactions
- transactions with
reconciliation_statusinUNRECONCILED,IGNORED,AI_SUGGESTED,NEEDS_REVIEW,DUPLICATE, orERROR - transactions with
reconciliation_outcomeinIGNORE,DUPLICATE, orUNKNOWN - draft, pending, ignored, cancelled, disputed, duplicate, or error operational items
The implementation owner for these rules is backend/analytic-service/modules/analytics/inclusionRules.js.
economicFactsBuilder.js must reject any canonical economic fact that does not carry a valid economic validation result or cannot be validated from its source record.
1.3 Core architectural principles
data-serviceremains the security boundary for frontend access- business rules stay in backend
- frontend may perform local visual aggregations only on already canonical datasets
- analytics must not be reimplemented page by page
- pages must consume analytics APIs and shared transform modules
2. Conceptual separation of datasets
The analytics model must separate three concepts.
2.1 Economic actuals
This is the real confirmed economic impact.
Examples:
- rent payment received
- utility bill paid
- condominium installment paid
- manually confirmed expense
This dataset is the basis for:
- income
- expense
- net cashflow
- breakdowns by dimension
- reports
2.2 Expected obligations
This is the operational layer of what should happen.
Examples:
- expected rent installments
- expected expenses
- utility bills due
This dataset is the basis for:
- overdue items
- pending items
- contract health
- operational alerts
2.3 Health indicators
This is a derived status layer built from expected obligations plus actual payments.
Examples:
- contract payment status: green / yellow / red
- expected expense on time / late / overdue
- utility bill received / overdue / disputed / paid
This layer is not a source table. It is a derived view or read model.
3. Backend ownership model
3.1 Security and ownership
All frontend-facing analytics access must pass through data-service.
UserInterface must call only data-service for analytics data and must never call analytic-service directly.
data-service is responsible for:
- deriving the logged-in user
- filtering by ownership and sharing rules
- exposing only authorized data
- returning canonical analytics datasets
If data-service returns data outside the user scope, that is an architectural and security defect.
Frontend-facing analytics routes:
GET /data-service/analytics/overviewGET /data-service/analytics/properties/:id/summaryGET /data-service/analytics/contracts/:id/summaryGET /data-service/analytics/contracts/:id/healthGET /data-service/analytics/bank-accounts/:id/summaryGET /data-service/analytics/report
Implementation files:
backend/data-service/routes/analytics.jsbackend/data-service/modules/analytics/analyticsFacade.js
The route file must remain thin. The facade module derives the trusted user
context from req.userCtx, applies ownership/sharing checks, builds
authorizationContext.accessScope, signs the internal service JWT, calls
analytic-service, and returns only the canonical payload authorized for the
caller.
3.2 Analytics implementation ownership
Analytics must not be implemented inside data-service.
The analytics capability must be owned by a dedicated backend microservice named analytic-service.
This service owns:
- analytics domain rules
- derived analytics schema access
- read model generation
- aggregation logic
- cache keys and cache invalidation rules
- scheduled or event-driven analytics rebuilds
- canonical analytics response shapes
data-service remains the frontend-facing security boundary. It must:
- authenticate the request
- derive the logged-in user from the token
- enforce ownership and sharing filters before exposing results
- call
analytic-servicethrough an internal service-to-service API - return only analytics data the logged-in user is allowed to access
analytic-service must not trust user identifiers supplied by the browser. When it receives a user-scoped request from data-service, it must treat data-service as the authorization boundary and still validate all required internal parameters.
Internal requests from data-service to analytic-service must use internal service-to-service authentication and must carry an authorizationContext produced by data-service.
The request body must not contain a top-level userId. If a user identifier is needed for cache scope or analytics filtering, it must appear only under authorizationContext.subject.userId after data-service has derived it from the authentication token.
Initial internal endpoints:
POST /internal/analytics/overviewPOST /internal/analytics/properties/:propertyId/summaryPOST /internal/analytics/contracts/:contractId/healthPOST /internal/analytics/bank-accounts/:bankAccountId/summaryPOST /internal/analytics/report
These endpoints must stay thin. They may perform internal authentication, contract validation, module lookup, and HTTP response/error mapping only.
Analytics execution must be delegated to analyticsQueryService or another dedicated analytics module.
Routes must not contain aggregation rules, inclusion/exclusion decisions, SQL, cache semantics, or chart-provider adaptation.
Implementation structure:
backend/analytic-service/backend/analytic-service/modules/backend/analytic-service/routes/backend/analytic-service/repositories/backend/analytic-service/docs/
Initial repository files:
backend/analytic-service/repositories/economicFactsRepository.jsbackend/analytic-service/repositories/contractHealthRepository.jsbackend/analytic-service/repositories/monthlyAggregatesRepository.js
Repositories must own SQL persistence only. Analytics rules, rebuild logic, ownership decisions, and payload shaping must remain in dedicated service modules.
Initial business modules:
backend/analytic-service/modules/analytics/economicFactsBuilder.jsbackend/analytic-service/modules/analytics/inclusionRules.jsbackend/analytic-service/modules/analytics/contractHealthBuilder.jsbackend/analytic-service/modules/analytics/monthlyAggregationBuilder.jsbackend/analytic-service/modules/analytics/analyticsQueryService.js
Business modules own deterministic analytics rules and canonical payload composition. They must not own frontend authorization, SQL persistence details, or chart-provider adaptation.
The microservice must follow the standard backend service rules:
- start from
backend/__TemplateService - use
BaseService - use
serverFactory - use shared
logger - expose standard health and status routes
- read configurable parameters through
getConfigValue - document APIs, events, configuration, and ownership in the help documentation
4. Persistence model
4.1 Dedicated analytics schema
Analytics tables should live in a dedicated MySQL schema on the same server, not mixed with the operational schema.
Recommended schemas:
raintyfor operational datarainty_analyticsfor derived analytics data
Reasons:
- clear separation between source of truth and derived data
- easier rebuild and maintenance
- safer migration strategy
- simpler backup and recovery by scope
4.2 Source of truth vs derived data
Source of truth stays in operational tables such as:
bank_transactionsbank_transaction_splitsrent_payment_matchesexpense_payment_matchesexpensesexpected_expensesutility_billsservice_contractscontractspropertiesparties
Derived analytics data lives in rainty_analytics.
Redis is not the source of truth. Redis is only a read cache.
rainty_analytics must contain only derived data and read models. It must not contain operational source-of-truth records.
Allowed content:
- facts derived from confirmed operational records
- read models rebuilt from operational tables
- aggregate buckets
- health status projections
- overdue item projections
- refresh and rebuild execution logs
Forbidden content:
- browser-submitted business data
- contracts, properties, parties, bank transactions, expenses, or documents as primary records
- authorization or ownership source tables
- workflow state that cannot be rebuilt from
rainty - user settings or user preferences
- chart-provider configuration
If rainty_analytics is lost, the platform must be able to rebuild it from rainty plus deterministic analytics rules. Any analytics table that cannot be rebuilt is a design defect.
5. Analytics data model
5.1 Canonical fact model
The foundation is a canonical fact dataset.
Suggested base table:
rainty_analytics.analytics_economic_facts
Each row represents a single economically confirmed fact.
Suggested fields:
iduser_idparty_idsource_typesource_idbank_transaction_idbank_transaction_split_idproperty_idcontract_idservice_contract_idsupplier_party_idtenant_party_idbank_account_idcategory_idservice_typeeconomic_direction(INCOME,EXPENSE)amountcurrencyrecognized_atpayment_datecompetence_datecompetence_monthcompetence_quarterpayment_channel(BANK,MANUAL)document_idreconciliation_run_idmetadata_jsoncreated_atupdated_at
5.2 Source types
Suggested source_type values:
RENT_PAYMENT_MATCHEXPENSE_PAYMENT_MATCHBANK_TRANSACTION_SPLITMANUAL_EXPENSE_PAYMENTUTILITY_BILL_PAYMENTEXPECTED_EXPENSE_PAYMENT
The implementation may evolve, but the principle is fixed:
- each fact row must have one unambiguous source and one economic meaning
5.3 Health and operational datasets
Suggested derived tables:
rainty_analytics.analytics_contract_healthrainty_analytics.analytics_overdue_items
Suggested contract health fields:
user_idcontract_idproperty_idstatus(GREEN,YELLOW,RED)scheduled_countpaid_countoverdue_countfirst_open_due_datemax_delay_daysgrace_days_usedupdated_at
Suggested overdue item fields:
user_iditem_type(EXPECTED_EXPENSE,UTILITY_BILL,RENT_INSTALLMENT)item_idproperty_idcontract_idsupplier_party_idcategory_iddue_dateamountstatusseverityupdated_at
5.4 Aggregated datasets
Suggested first aggregate table:
rainty_analytics.analytics_monthly_aggregates
Suggested fields:
user_idperiod_monthdimension_typedimension_iddimension_labelincome_totalexpense_totalnet_totalfacts_countupdated_at
Dimension types:
PROPERTYSUPPLIERCATEGORYBANK_ACCOUNTCONTRACTSERVICE_TYPE
This table must not attempt to materialize every possible multi-dimensional cube combination.
Start with:
- monthly aggregates by single dimension
- derive more complex combinations from canonical facts when needed
6. Refresh and recalculation strategy
6.1 Recalculation principle
Never rebuild the analytics of the entire platform after every change.
Use delta-based updates:
- update only the facts impacted by the business event
- update only the aggregate buckets touched by those facts
- invalidate only the related cache keys
6.2 Events that trigger recalculation
Analytics refresh must be triggered by:
- automatic reconciliation applied
- manual reconciliation applied
- reconciliation split created or edited
- reconciliation reset
- expected expense status changes
- utility bill status changes
- rent installment status changes
- manual payment registration
Initial Redis event channel:
<ENV>.analytics.refresh
Event type:
analytics.refresh.requested
Initial reason values:
reconciliation.auto_appliedreconciliation.manual_appliedreconciliation.split_changedreconciliation.resetexpected_expense.status_changedutility_bill.status_changedrent_installment.status_changedmanual_payment.registeredanalytics.scheduled_partial_rebuildanalytics.admin_full_rebuild
Recommended event payload:
{
"type": "analytics.refresh.requested",
"reason": "reconciliation.manual_applied",
"scope": {
"type": "TRANSACTION",
"id": 123
},
"impacted": {
"userId": 2,
"propertyId": 10,
"contractId": 20,
"bankAccountId": 30,
"months": ["2026-05"]
},
"requestedBy": {
"service": "reconciliation-service"
}
}
6.3 Recalculation levels
Incremental update
Default path.
Update:
- affected rows in
analytics_economic_facts - affected monthly aggregate buckets
- affected contract health rows
- affected overdue rows
Scheduled partial rebuild
Nightly or periodic.
Recompute:
- recent months
- recently touched users
- recently touched entities
Goal:
- fix drift
- catch missed events
- keep cost bounded
Full rebuild
Admin-only or DEV-only maintenance path.
Use only for:
- model changes
- bug recovery
- historical rebuild
- large migration
GLOBAL scope must be rejected for normal events. It is allowed only for an
explicit command with:
triggerType: "FULL_REBUILD"adminConfirmed: true- admin execution context, or DEV/LOCAL environment
6.4 Delta update keying
Every business event should provide enough information to narrow the recalculation scope.
Typical impacted keys:
user_idproperty_idcontract_idsupplier_party_idbank_account_idcategory_id- affected month(s)
The refresh pipeline must operate on those keys, not on the full dataset.
Initial bounded scopes:
TRANSACTIONBANK_TRANSACTION_SPLITPROPERTYCONTRACTBANK_ACCOUNTEXPECTED_EXPENSEUTILITY_BILLUSER
Implementation files:
backend/analytic-service/modules/analytics/refreshEvents.jsbackend/analytic-service/modules/analytics/refreshPlanner.jsbackend/analytic-service/modules/analytics/analyticsRefreshService.jsbackend/analytic-service/repositories/refreshRunsRepository.jsbackend/analytic-service/events.manifest.json
The first refresh implementation records the run and creates a bounded plan. The concrete source readers and read-model rebuild workers are the next implementation step.
6.5 Economic fact generation worker
The first concrete refresh worker generates analytics_economic_facts.
Operational sources:
bank_transaction_splitsrent_payment_matchesexpense_payment_matchesutility_bill_payment_matches- confirmed or paid manual
expenseswithout a linked bank transaction
Generation behavior:
- read operational data from the
raintyschema - apply the inclusion/exclusion rules through
economicFactsBuilder - fan out generated facts per authorized user context derived from property, contract, or bank account relationships
- delete existing facts for the bounded refresh scope before upserting regenerated facts
- prefer split facts over rent or expected-expense match facts for the same transaction and payable item to avoid double counting
Initial supported refresh scopes for fact generation:
TRANSACTIONBANK_TRANSACTION_SPLITPROPERTYCONTRACTBANK_ACCOUNTEXPECTED_EXPENSEUTILITY_BILLUSER
Implementation files:
backend/analytic-service/modules/db/operationalDatabase.jsbackend/analytic-service/modules/analytics/economicFactsSourceReader.jsbackend/analytic-service/modules/analytics/economicFactsGenerationService.jsbackend/analytic-service/modules/analytics/monthlyAggregatesRefreshService.jsbackend/analytic-service/modules/analytics/contractHealthSourceReader.jsbackend/analytic-service/modules/analytics/contractHealthRefreshService.js
6.6 Monthly aggregate refresh worker
Monthly aggregates are derived from analytics_economic_facts.
After every bounded fact rebuild:
- collect buckets touched by facts that existed before the rebuild
- collect buckets touched by facts generated by the rebuild
- delete those buckets from
analytics_monthly_aggregates - rebuild aggregates from the current generated facts
Initial dimensions:
PROPERTYCATEGORYBANK_ACCOUNTCONTRACTSERVICE_TYPE
This keeps aggregates consistent with facts and avoids a global aggregate rebuild after every operational event.
6.7 Contract health refresh worker
Contract health is derived from rental contract obligations and actual payment evidence.
Operational obligations:
rent_installments- contract-linked
expected_expenses
Operational payment evidence:
rent_payment_matchesexpense_payment_matchesbank_transaction_splits
Refresh rules:
- only reconciled, economically valid bank transactions are counted as payments
- valid splits win over direct payment matches for the same payable item
PAIDandMATCHEDsource statuses are used as fallback only when no payment rows exist- cancelled obligations are ignored
- rebuild deletes and rewrites only the impacted contract health rows
Implementation files:
backend/analytic-service/modules/analytics/contractHealthSourceReader.jsbackend/analytic-service/modules/analytics/contractHealthRefreshService.jsbackend/analytic-service/modules/analytics/contractHealthBuilder.jsbackend/analytic-service/repositories/contractHealthRepository.js
7. Caching strategy
7.1 Redis role
Redis is used only as a read cache.
Do not store analytics truth only in Redis.
7.2 Good cache candidates
- overview summary
- overview alerts
- property summary
- bank account summary
- contract health lists
- common report payloads with repeated filters
7.3 Cache key examples
analytics:overview:user:2:<scope-hash>analytics:property:15:summary:user:2:<scope-hash>analytics:bank-account:7:summary:user:2:<scope-hash>analytics:contract-health:user:2:contract:9:<scope-hash>analytics:report:user:2:<filters-hash>
7.4 Cache invalidation
Invalidate only affected keys when facts, monthly aggregates, or health models change.
Do not clear the entire analytics cache.
Implementation rules:
- refresh planning emits targeted
analytics:*cache tags - concrete keys include filter/access-scope hashes, so invalidation deletes by analytics prefix/tag
- only keys beginning with
analytics:may be invalidated by this worker - cache invalidation runs after the rebuild workers complete successfully
8. Backend API strategy
8.1 API style
The backend must expose analytics-ready datasets, not raw operational tables.
The frontend should not reconstruct economic truth from:
- bank transactions
- matches
- utility bills
- expected expenses
The backend must return a canonical analytics payload that is:
- secure
- user-scoped
- economically correct
- consistent across pages
8.2 First API set
Recommended first endpoints:
GET /data-service/analytics/overviewGET /data-service/analytics/properties/:id/summaryGET /data-service/analytics/contracts/:id/healthGET /data-service/analytics/bank-accounts/:id/summaryGET /data-service/analytics/report
8.3 API responsibilities
Backend APIs must handle:
- user security filtering
- business inclusion rules
- canonical row building
- optional pre-aggregations
- user settings application where business-critical
8.4 Frontend API contract style
Recommended payload style:
summaryfactsseriesbreakdownalertsmeta
This allows one fetch per page and multiple local visual aggregations in frontend.
All analytics responses must use this canonical top-level shape.
{
"summary": [],
"facts": [],
"series": [],
"breakdown": [],
"alerts": [],
"meta": {
"schemaVersion": "analytics-canonical-v1",
"generatedAt": "2026-05-13T00:00:00.000Z",
"currency": "EUR"
}
}
Section responsibilities:
summary: KPI-style metrics and totalsfacts: canonical economic facts after backend inclusion and exclusion rulesseries: chart-neutral time or category seriesbreakdown: grouped values by dimensionalerts: analytics-derived warnings and actionable statesmeta: schema version, filters, period, source, cache and safe diagnostics
Payload rules:
- backend responses must not contain
Chart.jsdatasets or options - backend responses must not contain
react-chartjs-2props - backend responses must not contain vendor-specific chart or market analytics engine payloads
- chart-provider adaptation belongs only in UserInterface analytics page adapters
- external analytics engine output must be normalized into this canonical shape before reaching UserInterface
9. Frontend architecture
9.1 Core principle
Frontend may aggregate locally only after backend has returned a secure and canonical analytics dataset.
Frontend must not rebuild domain rules.
9.2 Separation of responsibilities
API layer
Files dedicated to analytics fetches.
Suggested:
frontend/UserInterface/src/api/dataService/analytics.ts
Responsibilities:
- fetch only
- no business calculations
Analytics transform layer
This is the key reusable layer.
Suggested:
frontend/UserInterface/src/features/analytics/
Example structure:
types.tsdimensions.tsfilters.tstransforms/hooks/
This layer must be designed as a reusable analytics engine, not as page-specific helpers.
Hooks layer
Suggested:
useOverviewAnalytics.tsusePropertyAnalytics.tsuseIncomeExpenseReport.ts
Responsibilities:
- call APIs
- apply reusable transforms
- expose view-ready data to pages
Presentation layer
Pages and components must only:
- choose filters
- call hooks
- render data
Pages must not contain local business aggregation logic.
10. Frontend analytics transform layer design
10.1 Goal
The transform layer must be highly reusable because:
- dimensions are finite
- measures are finite
- views are many
It should behave like a lightweight local analytics engine over a canonical dataset.
10.2 Canonical input model
All frontend transforms should start from a single canonical type, for example:
AnalyticsFact
Suggested fields:
propertyIdpropertyNamecontractIdcontractNamesupplierIdsupplierNamecategoryIdcategoryNamebankAccountIdbankAccountNameserviceTypedirectionamountcompetenceDatepaymentDatestatus
10.3 Dimension registry
Dimensions should be centrally registered instead of handled with repeated if and switch.
Suggested dimensions:
propertysuppliercategorybankAccountcontractserviceTypemonthquarter
The registry should define:
- id selector
- label selector
- bucket strategy where applicable
10.4 Primitive transform operations
The transform layer should provide generic primitives such as:
- filter facts
- group facts by dimension
- aggregate facts by metric
- build time series
- rank groups
- compute totals
The layer must be query-oriented, not page-oriented.
10.5 Output model standards
To maximize UI reuse, transforms should output a small set of standard view models.
Suggested outputs:
SummaryMetric[]BreakdownRow[]TimeSeriesPoint[]HealthRow[]RankingRow[]
10.6 Analytics view adapters
On top of generic primitives, create adapters that map generic analytics outputs into UI-specific models.
Examples:
- overview KPI adapter
- property summary adapter
- supplier ranking adapter
- expense category chart adapter
- contract health adapter
These adapters must remain inside the analytics feature or the analytics pages that use them. They are not a global application abstraction and must not leak chart-library-specific models into shared business APIs.
10.7 What must stay out of pages
Pages must not contain:
reduce(...)business logicgroupBy(...)business logic- local cashflow derivation logic
- local health color rules
Those belong to the transform layer or to backend if business-critical.
10.8 Chart provider strategy
The Phase 1 chart provider for UserInterface analytics is Chart.js through react-chartjs-2.
This is an implementation choice for rendering charts in the frontend, not part of the analytics API contract.
Rules:
analytic-servicemust return canonical analytics data, notChart.jsdatasets or optionsdata-servicemust not expose chart-provider-specific payloads- analytics pages may adapt canonical view models into
Chart.jsmodels locally - chart adapters must stay inside analytics-specific frontend code
- shared UI components must receive rAInty-owned view models, not
Chart.jsobjects - pages outside analytics must not depend on analytics chart adapters
Recommended frontend shape:
frontend/UserInterface/src/features/analytics/types.tsfrontend/UserInterface/src/features/analytics/transforms/frontend/UserInterface/src/components/pages/analytics/adapters/chartJsAdapter.tsfrontend/UserInterface/src/components/pages/analytics/components/AnalyticsLineChart.tsxfrontend/UserInterface/src/components/pages/analytics/components/AnalyticsBreakdownChart.tsx
The wrappers may use react-chartjs-2 internally, but analytics pages should render rAInty-owned components such as AnalyticsLineChart or AnalyticsBreakdownChart instead of importing Line, Doughnut, or other provider components directly.
Provider replacement rule:
If rAInty replaces Chart.js with another JavaScript chart library, only the analytics chart wrappers and local chart adapters should change. Backend APIs, analytics transforms, page orchestration, and shared UI models must remain stable.
External analytics engine rule:
If rAInty later replaces analytic-service with a market analytics engine, the integration must still normalize provider output into rAInty canonical analytics models before it reaches UserInterface pages. Market-engine query languages, chart specifications, or vendor-specific payloads must not become UserInterface contracts.
11. User settings for analytics
Analytics and health indicators depend on user-configurable thresholds.
These settings must be stored in user_settings and exposed through data-service.
Candidate keys:
reconciliation.expected_expense_default_tolerancereconciliation.expected_expense_fallback_window_daysreconciliation.utility_bill_default_tolerancereconciliation.utility_bill_window_daysanalytics.contract_payment_grace_daysanalytics.expected_expense_grace_days
Guideline:
- keep user-wide defaults first
- add per-property or per-contract overrides only if clearly needed later
12. UI reuse strategy
12.1 Shared UI building blocks
Analytics presentation should reuse shared UI components.
Suggested atoms:
- trend value
- delta badge
- health indicator
- period label
Suggested molecules:
- KPI card
- breakdown row
- health status card
- dimension filter bar
Suggested organisms:
- analytics KPI grid
- expense breakdown section
- contract health list
- overdue items section
- cashflow chart section
12.2 Page usage
The same base datasets and transforms must feed:
- Overview
- Property detail
- Bank account detail
- Income expense report
- future analytics sections in supplier or contract detail if needed
13. MVP implementation order
Recommended execution order:
Phase A: foundations
- define final analytics inclusion/exclusion rules
- define canonical fact schema
- define dedicated analytics schema and initial tables
- define refresh events and delta rules
Phase B: backend MVP
- implement fact generation
- implement monthly aggregates
- implement contract health
- implement overview analytics API
- implement property summary API
- implement report API
Phase C: frontend MVP
- create frontend analytics API file
- create transform engine foundation
- create analytics hooks
- create shared KPI / breakdown components
- wire Overview
- wire Property detail
- wire Income expense report
Phase D: optimization
- add Redis cache for common analytics reads
- add scheduled partial rebuild
- add full rebuild admin path
14. Risks and design warnings
14.1 What to avoid
- rebuilding all analytics after every change
- page-specific duplicated aggregation logic
- exposing raw operational tables for frontend analytics
- putting analytics truth only in Redis
- precomputing every possible dimension combination
14.2 Redis analytics cache
Redis may be used only as a read-through cache after the first complete analytics query path is functional.
Initial cache key patterns:
analytics:overview:user:<id>:<scope-hash>analytics:property:<id>:summary:user:<id>:<scope-hash>analytics:contract-health:user:<id>:contract:<id>:<scope-hash>analytics:bank-account:<id>:summary:user:<id>:<scope-hash>analytics:report:user:<id>:<scope-hash>
The scope hash must include filters, dimensions, metrics, and
authorizationContext.accessScope. This prevents data cached for one
authorization scope from being reused for another user or sharing context.
Rules:
- Redis is never source of truth
- cache failures must not fail analytics reads
- cached payloads must be canonical analytics payloads
- cache TTL must be finite and configurable
- invalidation can start with TTL-based expiry and later move to event-driven deletion when refresh/rebuild events are implemented
Configuration:
ANALYTICS_CACHE_ENABLED, defaulttrueANALYTICS_CACHE_TTL_SECONDS, default120
14.3 Main consistency risks
- double counting due to both match and split paths
- wrong competence month assignment
- stale health indicators after manual override
- cache invalidation gaps
- partial resets leaving derived analytics stale
These must be explicitly tested.
15. Validation and testing strategy
The analytics layer must be validated against representative cases:
- single full reconciliation
- multi split reconciliation
- manual expected expense payment
- utility bill payment with tolerance
- reconciliation reset
- ignored transaction excluded from economic facts
- unreconciled transaction excluded from economic facts
- draft or pending expense excluded from economic facts
- overdue item becoming paid
- contract moving from green to yellow to red
Validation should compare:
- source operational rows
- generated fact rows
- aggregate totals
- page payload output
16. Parallelization opportunities
The following workstreams can be parallelized once the canonical fact contract is frozen.
16.1 Backend schema and foundation
Can run in parallel:
- analytics schema DDL
- base analytics repository / module structure
- event contract definition for refresh triggers
Dependency:
- requires agreement on canonical fact model
16.2 Backend read models
Can run in parallel:
- overview analytics endpoint
- property summary endpoint
- contract health endpoint
- bank account summary endpoint
Dependency:
- requires canonical fact generation to be defined
16.3 Frontend analytics foundation
Can run in parallel:
- analytics API file
- analytics type definitions
- dimension registry
- transform primitives
- output view-model contracts
Dependency:
- requires API payload contract draft
16.4 Shared UI components
Can run in parallel:
- KPI cards
- breakdown sections
- health components
- chart wrappers
Dependency:
- requires standard output models such as
SummaryMetric[]andBreakdownRow[]
16.5 Page integration
Can run in parallel after the shared frontend foundation exists:
- Overview analytics integration
- Property detail analytics integration
- Income expense report analytics integration
- Bank account detail analytics integration
16.6 Cache and scheduled rebuild
Can run in parallel after core backend analytics APIs are stable:
- Redis cache layer
- scheduler partial rebuild job
- admin rebuild tools
16.7 Recommended sequence for parallel work
- freeze canonical fact contract
- split backend schema/facts from frontend transform foundation
- build shared UI components in parallel with backend read models
- integrate pages after both backend payloads and transform outputs are stable
- add cache and scheduled rebuild only after the first analytics flow is correct
17. Deployment note
The current environment-sync-service schema migration path delegates execution to datahub.
At the moment, datahub/modules/schemaMigrationRunner.js accepts only a restricted additive DDL subset and rejects or does not support statements required by the initial analytics schema setup:
CREATE DATABASEUSE <schema>CREATE USERGRANT- schema-qualified
CREATE TABLEstatements such asrainty_analytics.analytics_economic_facts
For TEST deployment, the initial creation of rainty_analytics and its grants must therefore be executed by a database admin/deploy step, or the schema migration runner must be adapted first.
Required future adaptation:
- allow a guarded
CREATE DATABASE IF NOT EXISTS rainty_analytics - allow schema-qualified additive
CREATE TABLE IF NOT EXISTS rainty_analytics.<table> - decide whether grants remain a DBA-only operation or are handled by an admin migration path
- keep destructive operations forbidden
- keep migration execution tracked in
schema_migrations
Until this adaptation is complete, do not assume environment-sync-service can create the analytics schema in TEST.
18. TEST deployment checklist
The first TEST deployment of analytics must verify infrastructure, internal authentication, and database state before functional validation.
Required checks:
analytic-serviceis included in TEST deployment:docker-compose.test.ymldefines theanalytic-serviceservice.COMPOSE_PROFILESincludesanalytic-service.ANALYTIC_SERVICE_VERSIONis available as a GitHub environment variable.
- Internal JWT keys are configured in the TEST GitHub environment:
INTERNAL_JWT_PRIVATE_KEYis available as a secret fordata-service.INTERNAL_JWT_PUBLIC_KEYis available as a secret foranalytic-service.- both keys belong to the same key pair.
data-servicecallsanalytic-serviceinternally throughANALYTIC_SERVICE_URL=http://analytic-service:3013.- The
rainty_analyticsschema exists before analytics refresh/rebuild is executed. - Until
environment-sync-serviceanddatahubsupport guarded cross-schema migrations, run the initial schema setup as a DBA/deploy-admin step:
CREATE DATABASE IF NOT EXISTS `rainty_analytics`;
GRANT ALL PRIVILEGES ON `rainty_analytics`.* TO 'rainty_user'@'%';
FLUSH PRIVILEGES;
- Apply
db/migrations/001_analytic_service_schema.sqland later analytics migrations manually or through the adapted migration path. - Verify the schema and derived tables in TEST:
SHOW DATABASES LIKE 'rainty_analytics';
SHOW TABLES FROM `rainty_analytics`;
- Verify service health and logs:
docker compose --env-file .env -f docker-compose.test.yml -p test ps analytic-service
docker logs --tail=100 test-analytic-service-1
- Verify the public analytics flow only through
data-servicewith an authenticated user session. UserInterface must never callanalytic-servicedirectly.