Aletheia Finance Capabilities Roadmap¶
Status: Draft v1 (2026-04-12)
Audience: Executive team, finance team, engineering leads, practice managers
Related: docs/entity-model.md — the shared foundation for entities, ownership, consolidation, intercompany
1. Executive summary¶
- Opportunity. Aletheia already holds the group's PMS data (patients, appointments, procedures, payments, practitioners, equipment, budgets). Adding Pennylane-sourced financial data on top creates a class of reports that no third-party BI tool, no Pennylane dashboard, and no standalone finance app can reproduce — because only Aletheia can join finance numbers with per-dentist, per-appointment, per-treatment operational data.
- We are not rebuilding accounting. Pennylane stays the ledger of record for the 7 FR entities. Excel GL imports cover the 4 non-Pennylane entities (SUI, AST, SDT, and CHC until it goes live). Aletheia builds the operational + analytical finance layer on top: consolidation, dashboards, forecasts, alerting, AI enrichment.
- Foundation first. Phase 1 is unglamorous but load-bearing: an entity data model, unified GL storage, Excel import pipeline, intercompany elimination engine, and a rebuild of the current "Groupe Suffren" management reporting format in-app. Without it, every later feature is unstable.
- Moat features in Phase 2. Once the foundation lands, Phase 2 ships the revenue-per-dentist join, lab/prosthetics cost analysis, no-show cost analysis, treatment-to-invoice funnel, and other finance↔PMS joins. These are the features that justify building this inside Aletheia.
- Automation and AI in Phase 3. Once the data and joins are solid, Phase 3 adds workflow (approval routing, close checklist), AI enrichment (invoice line extraction, bank transaction categorization, NL Q&A), and cash flow forecasting.
- Parallel cap table roadmap. A sibling roadmap covers individual holder tracking, PV d'AG / ODM / CEL generation, and partner register automation. Both roadmaps share the data model in
docs/entity-model.md. Neither may redefine entities or ownership; one populates individual holders, the other populates GL + eliminations. - Respecting what exists. The roadmap reuses Aletheia's Django 5.2 / PostgreSQL / Celery / Bootstrap stack, the
apps/imports/AbstractImporterbase class, theapps/core/permissions.feature_requireddecorator, theapps/budgetsversioning pattern, and the existing UI skeletons intemplates/skeletons/. No rewrites. - Growing platform. The group expects 5-10 new practice acquisitions per year. The entity model, aggregation scopes, and onboarding workflows must handle this as routine operations, not exceptional admin tasks.
- Non-goals (Phase 1-3). Not building: a French accounting engine, a FEC export, a TVA declaration tool, a bank feed aggregator, a payroll system, a patient billing tool. Some of these may come later; all are explicitly deferred so we don't pad Phase 1.
- Biggest risks. (a) The management reporting goes through close iterations (v1 → v6 within a single month's close cycle) and the report format itself evolves between fiscal years. Both must be handled. (b) The non-Pennylane ingestion formats are fragmented (AST/SDT have LOCATION+PROJECT dimensions, SUI has none) and must be harmonized to one template before consolidation works cleanly. (c) Intercompany elimination rules are currently implicit in Excel formulas — extracting them is a finance-team interview, not a reading exercise.
2. Current state of Aletheia¶
Stack and conventions¶
- Framework. Django 5.2, Python 3.13, PostgreSQL (shared
shared_postgrescontainer), Celery 5.6 + Redis, Gunicorn, Sentry, DRF, Crispy Forms + Bootstrap 5, i18n (French default, English, Dutch-BE). - App structure. All apps under
apps/. Each app owns its models, views, templates, URLs, and migrations.apps/core/holds abstract base models (TimeStampedModel,AuditModel,SoftDeleteModel) and the feature-permission system. - Auth. Custom
Usermodel (accounts.User) with email login. Users have many-to-many access toPracticeviaUserPracticeAccess. DjangoGroupsmap to feature sets inapps/core/permissions.py(e.g.,Finance Manager,Practice Manager,HR Manager,Admin). Decorator@feature_required('feature_name')+ class-basedFeatureRequiredMixinenforce access. - Deployment.
make deploy ENV=staging|prod REF=<tag>. No directdocker compose. Staging auto-deploys fromdevelop, production from tags. - UI skeletons.
templates/skeletons/skeleton_list.html,skeleton_detail.html,skeleton_form.htmlare the starting points for new pages. CSS tokens frombase.css(var(--border-primary),var(--bg-card),var(--radius-lg), etc.) — never hardcode colors. Dark mode is automatic via tokens. - Imports.
apps/imports/providesAbstractImporteras the base for all file-driven imports (dentist, patient, procedure, appointment, GL payment). Tracks row-level success/failure inImportRow, supports dry-run and bulk mode, integrates with Celery for large files. - Budgets.
apps/budgets/already implements a budget-version workflow (BudgetVersionwith draft → submitted → validated states, per-practice, per-fiscal-year, withDentistTimeOff,PlannedCapacity,DentistRevenueTargetassumptions). This pattern is reusable for financial forecasting/scenario modelling.
PMS data model¶
| Model | File | Purpose |
|---|---|---|
Practice |
apps/practices/models.py |
Operational unit. Has address, business hours, rooms, equipment, fiscal year config, Doctolib linkage. |
Dentist + DentistContract |
apps/dentists/models.py |
Practitioner profile and contract to a practice. Contract has contracted_working_days_per_week, contracted_weeks_off_per_year. |
DentistWorkSchedule + DentistRevenueTarget |
same | Per-weekday schedule and daily target with valid_from/valid_to versioning. |
Patient |
apps/patients/models.py |
Patient record imported from Logosw. |
Procedure |
apps/procedures/models.py |
One dental act. Has fees, amo_reimbursement_base, overruns_amount, ccam_code, ccam_grouping, type, etc. |
Payment |
apps/procedures/models.py |
One payment event (patient or insurance). Sourced from Logosw ACTES_2 or GL+REGL. |
Appointment |
apps/appointments/models.py |
Scheduled and historical appointments from Doctolib + Logosw. |
PracticeEquipment |
apps/practices/models.py |
Equipment with install_date, service_interval_days, service history. |
Key aggregations already possible (PMS side): revenue by day/practice/dentist, appointment load by dentist, no-show rate, procedure-type mix, treatment plan → actual conversion, equipment service backlog, patient retention.
What exists on the finance side¶
Almost nothing. Confirmed by searching the codebase for pennylane, legal_entity, LegalEntity, holding_company, share_class, cap_table, shareholder, general_ledger, gl_account, chart_of_accounts, trial_balance. Zero matches. Explicit implications:
- No entity/legal-entity concept.
Practice.internal_codeis close but is an operational code, not a legal entity. There is no model that would let you say "PDS and PDSh are two legal entities in the same practice group". - No consolidation, no P&L, no balance sheet, no cash flow statement.
- No Pennylane integration. The
pennylane_reusablebundle is sitting intemp/ready to be copied into anapps/pennylane_sync/(or similar) module but has not been wired into Aletheia yet. - No GL import. The
apps/imports/engine handles PMS files (CIVIL.txt, ACTES_2.txt, GL_REGL for payments) but not accounting trial balances or journal entries. - No chart-of-accounts harmonization. No per-GL-account mapping table exists.
- No intercompany elimination. No rules engine, no elimination tracking.
What adjacent features bring¶
apps/annuaire/integrates the French RPPS/FHIR directory for every dental organization registered nationally.AnnuaireOrganizationhas SIREN/SIRET/statut_juridique/address for every registered French dental practice. It is reference data — useful for auto-filling entity SIRENs when creating newentitiesrows, but is not our internal legal-entity registry.apps/annuaire/services/inpi.pyalready talks to the INPI RNE API (username/password in settings). This could support automatic legal-form / dirigeants lookup when onboarding a new group entity, for free.apps/crm/is the newest feature (2026-04) and shows current conventions for service-layer code (apps/crm/services/), CRM-pattern forms, and kanban-style UIs. Good reference for what "good" looks like in Aletheia today.apps/websites/has a REST-API-first pattern with DRF viewsets — relevant if Helios or a future frontend needs to consume finance data later.apps/dashboard/exists as a placeholder today (the CLAUDE.md comment "Analytics (placeholder)") but has a service layer ready for new KPIs.
3. Current state of finance reporting¶
The finance team currently produces a monthly "Groupe Suffren" management reporting Excel, versioned per period and per close iteration. The latest file in the reference folder is FY25 Suffren - 02 2026 - Mngt reporting v6.xlsx.
Workbook anatomy¶
The workbook is large (~50 sheets) and structured in regions:
- Consolidated reports.
Conso.(consolidated P&L + BS + CF),Contrib.(per-entity contribution to each line),Compil.(monthly time-series of the P&L). - Per-entity reports. One sheet per entity: PDS, PDSh, THS, SDV, CDA, VSMh, VSM, AST, SDT, SUI. Each sheet has its own P&L/BS/CF built off that entity's trial balance via formulas.
- Trial balances and mappings.
FR entities FY26(flattened Pennylane GL for all 7 FR entities, ~1000 rows),FR MAPPING(GL→report line mapping, ~469 rows),AST FY26,SDT FY26,SUI FY26(non-Pennylane TBs imported from local accounting systems). FY25 equivalents for comparison. - Adjustment sheets. One
Adjust. <entity> FY25per entity, holding manual accounting adjustments (reclassifications, cutoff entries, restatements).
No CHC sheet — confirms CHC is not yet producing financial statements into this reporting.
P&L format (the canonical report lines)¶
Reproduced from Conso. sheet of the latest file. Any Phase 1 dashboard must match this layout exactly:
| Section | Lines |
|---|---|
| Revenue | Private Turnover (Private Revenue), NHS Turnover (Social fund Revenue), Scheme Turnover (Subscriptions Revenue), PPE Turnover (Rental Income), Lab revenue, Training centre revenue |
| Cost of sales — Laboratory | Lab Materials, Lab direct labour, Lab other |
| Cost of sales — Practices | Associate Fees (rétrocessions to practitioners), Prothesis (prosthetics labs), Implants, Clear Aligners, Materials, Hygiene Fees |
| Gross Profit | (subtotal) |
| Overheads | Colleague Costs, Property Costs, Repairs and Maintenance, Legal and Professional, Other expenses |
| Other Incomes | (below overheads, added back) |
| EBITDA before leasing | (subtotal) |
| Leasing costs | Exceptional — one line |
| EBITDA before central costs | (subtotal) |
| Central costs reinvoicing | (SDV → practice invoicing) |
| EBITDA | (subtotal) |
| Exceptional result | |
| Depreciation & Amortisation | Intangible, Tangible, (Financial) |
| PBIT | (subtotal) |
| Financial incomes | |
| Financial expenses | |
| Financial result | (subtotal) |
| Corporation Tax | |
| Net Profit | (bottom line) |
Below the P&L, an Analysis Table shows:
- Each P&L line as a percentage of revenue
- Revenue per dentist (top 5 + "Other dentists" + Total) — in €k
- Revenue repartition (% of total) per dentist
- Associate fees breakdown per dentist
- Dentist fees as % of revenue
- Colleague costs breakdown (Gross wages, Social security contributions, Change in paid holiday accrual, Apprenticeship grants, Other payroll costs)
- KPIs: Social security contribution rate, FTEs by role (Office manager, Secretary, Other, Cleaning staff, Dental assistant), Dental assistant FTE per dentist, Average gross wage/cost per FTE
- Other expenses detail (15+ sub-categories from Advertising → Workwear expenses)
Balance Sheet format¶
| Section | Lines |
|---|---|
| Fixed assets | Intangible (Gross, Amortization, Depreciation, Net), Tangible (same trio), Financial (same trio) |
| Current assets | Inventories, Trade receivables, Prepayments and accrued income, Social receivables, Other receivables |
| Current liabilities | Trade payables, Accruals, Social payables, Associate fees payable, Other taxes payables, Other payables |
| Net working capital (excl. CIT) | (subtotal) |
| CIT & other current assets/liabilities | Corporate income tax receivable/payable, Dividends payable |
| Capital employed | (subtotal) |
| Shareholders' equity | Share Capital, Legal reserve, Other reserves, Net income/(loss) |
| Net financial debt / (cash) | Bank debt, External debt, Cash & cash equivalents |
| Intercompany | Intercompany receivables/(payables), Cashpooling receivables/(payables), Intercompany loan, Intercompany loan interests, Intercompany loan & interests impairment |
| Sources of funds | (subtotal, should equal Capital employed) |
Cash Flow Statement format¶
Indirect method, starting from EBITDA:
EBITDA before leasing
(-) Leasing costs → EBITDA before central costs
(-) Central costs reinvoicing → EBITDA
(-) Capex: Intangible / Tangible / Financial Fixed Assets
(-) Change in Net Working Capital (excl. CIT)
→ Free Cash Flow (pre-tax)
(-) Change in CIT payable / CIT P&L / CIT paid
→ Free Cash Flow (post-tax)
(+/-) Exceptional result
→ Operating cash flow
(+) Financial income / (+) Financial expenses
(+/-) Debt drawdown / repayment / Debt service
(-) Change in intercompany / cashpooling / loan / loan interest balances
(-) Capital increase / Dividend paid / Write off
(+/-) Unreconciled difference / Other cash flow items
→ Net cash flow
Cash & cash equivalents — BoP
Cash & cash equivalents — EoP
GL → report line mapping¶
- ~469 account codes currently mapped in
FR MAPPING(Pennylane/PCG accounts → "Mapping 1" = one of 47 report lines). - Mapping 2 (20 values) adds sub-line detail for "Other expenses" and "Colleague Costs" analysis breakdowns.
- Account granularity varies from aggregate (
60101000000 Achat de prothèses→ Prothesis) to per-practitioner (60430000000 RETRO MAUREL BERENGERE→ Associate Fees — each practicing dentist has their own rétrocession account). - The per-practitioner Associate Fees accounts are the moat anchor. Each
RETRO <FIRST LAST>account maps naturally to an AletheiaDentistrow by name match, letting Aletheia auto-populate the "Associate fees per dentist" analysis table without a human editing Excel.
Non-Pennylane entity ingestion¶
Three different Excel shapes today, all imported into the consolidation manually:
| Entity | Country | Format |
|---|---|---|
| AST | BE | Compte / Libellé local (NL) / Libellé FR / TYPOLOGIE (Holding/Clinic) / PROJECT / monthly columns — Belgian PCMN chart. LOCATION and PROJECT dimensions split the balance (Holding vs Clinic within the same entity; PROJECT tracks CL interest, audit fees, intercompany loans). |
| SDT | BE | Same as AST. |
| SUI | LU | Reference / Description / Description 2 / monthly columns — LU chart. No dimensions. |
Three different templates, three different charts of accounts, one shared target report format. Today, each entity has its own VLOOKUP formula mapping its accounts to the 47 Mapping 1 lines. The finance roadmap must harmonize these to one canonical Excel template so consolidation runs the same code path regardless of source.
KPIs tracked¶
From the Analysis Table and elsewhere in the workbook:
- Financial KPIs. Revenue, Gross Profit, EBITDA (before leasing / before central / after central), Net Profit, CAPEX, Free Cash Flow (pre-tax and post-tax), Net cash flow, Shareholders' equity, Net financial debt, Capital employed, margins at every subtotal.
- Operational KPIs (from P&L). Revenue per dentist (top 5 + other + total), Associate fees per dentist, Associate fees / revenue ratio, Dental assistant FTE per dentist, Average gross wage per FTE, Average cost per FTE, FTE counts by role, Social security contribution rate.
- Sub-category expense tracking. Other expenses broken into 15+ categories (Advertising, Bank charges, Doctolib access, PMS tool, Training, Travel, ...).
Close iterations and format evolution¶
The v1 → v6 versioning within a single file represents monthly close iterations for one period, not format changes throughout the year: - v1 = first GL pull (~6th of month+1) - Progressive corrections during the 6th-15th (chase missing invoices, accountant Q&A) - v6 = final closed accounts (~20th)
The close calendar: - ~6th of month+1: first GL extract (preliminary) - 6th–15th: chase missing invoices, corrections, accountant Q&A - ~20th: final closed accounts
The report format (line structure, KPIs) does evolve, but between fiscal years or occasionally mid-year — separately from the close iterations.
This means Phase 1 needs a monthly close workflow (open → in_close → closed status per entity/period, with "as-reported" vs "current truth" views) more urgently than it needs report-format versioning. Two implications for the data model:
- Report line identity must be stable across format versions even as labels change. The data model should have an internal code for each report line (e.g.,
PNL_GROSS_PROFIT) separate from its display label (which can be renamed). - Mapping changes must be version-tracked — adding a new GL account to "Other expenses" in March should not retroactively change January's number. The
FR MAPPINGtable in the new system needseffective_from/effective_tocolumns.
Gaps observed in the current Excel (Phase 1 improvement opportunities)¶
- No CHC sheet. Will need to be added when CHC goes live on Pennylane.
- "FYE 31/12" label on labels inside the Conso sheet is a stale typo — confirmed. Management and legal FY is Oct 1 – Sept 30 across all entities.
- Non-FR charts of accounts not in any shared mapping table. Belgian and Luxembourg accounts are mapped via per-sheet VLOOKUP formulas, not a shared rulebase. Migrating these to a central per-country mapping is Phase 1 cleanup.
- Adjustments are per-entity sheets with no audit trail. Who entered them, when, and why is not captured in the file. The new system should track adjustments as explicit database rows with author and justification.
- Cross-entity elimination is implicit in the formulas. There is no declarative "SDT↔VSMh CB interest eliminates at FR practices total" rule anywhere — the elimination is baked into Conso sheet arithmetic. Extracting these rules is a finance interview task for Phase 1.
- Time-series comparability relies on manual column copying between files. A proper database-backed history eliminates this.
4. Strategic framing¶
Moat¶
Every feature in this roadmap is evaluated by a single question: does it require joining Pennylane financial data with Aletheia PMS operational data?
- High moat (only Aletheia can build): features that need both sides.
- Medium moat: Pennylane-only features where our domain-specific UX + the Groupe Suffren entity structure provide some lock-in.
- Low moat: pure financial reporting that a generic BI tool (Metabase, Superset, Pennylane's own dashboards) could ship.
Priority goes to high-moat features, gated by the foundation they need.
Integration philosophy¶
- Pennylane stays the source of truth for the ledger on FR entities. We never write back. We sync, enrich, and report on top.
- Excel GL import covers non-Pennylane entities (SUI, AST, SDT, CHC pre-live). One canonical template for all of them, read into the same unified data model.
- The unified data model is source-agnostic. Consolidation, dashboards, and exports never know whether the GL came from Pennylane or Excel. The moment they branch on source, we have a bug-class.
- The Pennylane-reusable bundle (
temp/pennylane_reusable/) is the Pennylane access layer — copy it intoapps/pennylane_sync/and adapt for Aletheia's Celery + settings conventions. Do not re-implement.
Non-goals¶
- Not rebuilding accounting. No journal entry creation, no invoice creation, no posting to GL. Pennylane handles all of that.
- Not replacing Pennylane's workflows (invoice approval, bank reconciliation in Pennylane, TVA declaration via Pennylane). We extend, we don't replicate.
- Not a payroll system. Payroll data comes from GL accounts in class 64 and the social declarations stay in the HR provider (DSN / URSSAF interface). We visualize and analyze; we do not run payroll.
- Not a patient billing tool. Patient invoicing is in Logosw/Doctolib; we import it read-only.
- Not a cap table tool (that's the parallel roadmap).
- Not a CRM replacement.
apps/crm/is for M&A pipeline; finance features should not duplicate that surface.
Relationship to the cap table roadmap¶
The two roadmaps are independent in deliverables, coupled in data model:
- Finance roadmap (this doc) ships GL data, consolidation, dashboards, cost analytics, cash flow, intercompany elimination, Excel import.
- Cap table roadmap (sibling doc) ships individual holder tracking, ownership events, PV d'AG / ODM / DUA / CEL / loan contract / OCD letter document automation, partner register.
- Both build on
docs/entity-model.md. The finance roadmap can ship with aggregated holders only (e.g., "practicing dentists at VSM, ~1%"). The cap table roadmap populates individual holders and drives legal automation. Neither redefines the entity, ownership, or consolidation model.
5. Foundation reference¶
The single shared foundation for this roadmap is docs/entity-model.md. That document defines:
- Share class vocabulary (OS, CSa/b/c, CL, CB) and the independence of the CVR and FR cascades
- The full legal ownership tree (ASCII + canonical entity table) for the 11 current entities (CHC, SUI, AST, SDT, SDV, THS, CDA, PDSh, PDS, VSMh, VSM)
- Consolidation methodology (full consolidation across the board, FR-based minority interest, practicing-dentist ~1% bucket rule)
- Convertible instrument (CL/CB) handling — debt today, equity tomorrow, economic FR attributed to the lender in either case
- Proposed data model:
entities,ownership_links(derived/cached from cap table registers),holders,ownership_events,aggregation_scopes,intercompany_elimination_rules,fiscal_integration_groups,fiscal_integration_memberships,period_close - The management aggregation tree (practice groups → subtotals → group totals, with vintage cohorts)
- Intercompany elimination rules (seed list and how they're declared)
- Open questions forwarded to both roadmaps' Decisions needed sections
This roadmap does not redefine those concepts. Any feature description below that mentions entities, ownership percentages, scopes, or eliminations references the foundation by section number. If you find an inconsistency between this file and the foundation, the foundation wins — update this file, not the other way around.
How this roadmap builds on the foundation:
- Phase 1 implements the foundation's data model as a Django app (
apps/entities/) and seeds it with the 11 entities, their ownership links (aggregated granularity), the 8 aggregation scopes, and the seed intercompany rules. - Phase 1 depends on the foundation's unified financial data model (source-agnostic GL storage) and the declarative intercompany rules engine.
- Phases 2 and 3 query the entity model to scope every report and every dashboard. A feature like "revenue per dentist" must answer "at which scope? Per entity / per practice group / FR practices total / Group incl. BE?" before it renders anything.
6. Multi-source financial data ingestion¶
Every finance feature in the roadmap reads from a unified financial data model — a source-agnostic store of GL lines, trial balances, and month-end statements. Two ingestion paths write into it.
Path A — Pennylane sync (7 FR entities)¶
Reuses temp/pennylane_reusable/:
- Redshift Data Sharing, accessed via
db.py(dual Redshift / DuckDB backend). Providescompanies,general_ledger(date-level journal entries),trial_balance,customer_invoices,supplier_invoices,bank_accounts,bank_transactions. - REST API v2, via
pennylane_api.py. Providescustomers,suppliers,products,plan_items, individualinvoice_lines, and attached PDFs. - Incremental sync, via
sync_redshift_to_local.py. Usessynchronized_atcheckpoints stored in a_sync_metatable for per-company incremental fetches. Parallelized per company viaThreadPoolExecutor.
Adapted for Aletheia:
- Copy the bundle into
apps/pennylane_sync/(orapps/finance_sync/). - Wire up as Celery tasks. Nightly full sync at (say) 4 AM, after Doctolib sync finishes.
- Per-company API tokens live in Django settings via
python-decouple(existing convention). - All financial tables live in the shared PostgreSQL database alongside PMS tables. DuckDB was prototype-only.
- The
companiesRedshift table maps toentities.pennylane_company_id(nullable FK on the entity row). Every Pennylane-synced entity must have a seededentitiesrow with this FK populated.
Path B — Excel GL import (non-Pennylane entities)¶
Reuses apps/imports/AbstractImporter:
- One canonical Excel template for SUI, AST, SDT (and CHC pre-live, and any future foreign entity). Today those three entities use three different shapes — this is not tenable past Phase 1.
- Template columns (proposed):
entity_code(must match anentities.code)period(YYYY-MM)account_code(native chart of accounts)account_label_local(in local language)account_label_fr(translated; optional but preferred)dimension_1/dimension_2(optional, for AST/SDT's LOCATION and PROJECT; ignored by SUI)amount(signed; debit positive or credit negative, convention decided by config)- A new importer class
GLTrialBalanceImporter(AbstractImporter)validates column structure, validatesentity_codeagainstentitiestable, splits by (entity, period), and writes into the unified GL store. - Chart-of-accounts mapping is applied at query time (resolved — enables both "as-reported" and "restated" views without re-importing historical data; if performance concerns materialize, add materialized views). Mapping is stored in a single
gl_mapping_ruletable with acountrycolumn andeffective_from/effective_to. - Template versioning. The template itself evolves.
GLTrialBalanceImporterdeclares the supported version and refuses files with an incompatible header row. - Dry run and error reporting. Same semantics as the PMS importers — rows that fail validation are flagged in
ImportRowand exported to Excel for the finance team to fix.
The unified financial data model¶
New tables (in apps/finance/ or apps/gl/):
| Table | Purpose |
|---|---|
gl_account |
One row per (entity, account_code) pair. Has account_label, account_label_fr, account_type (asset/liability/equity/revenue/expense), first_seen, last_seen, provenance (Pennylane or Excel). |
gl_transaction (or gl_entry) |
Date-level journal entries. entity_id, account_id, accounting_date (economic date), booking_date (when recorded/synced — critical for close workflow), debit, credit, balance, dimension_1, dimension_2, source (pennylane/excel), source_ref. Monthly views are query-time aggregation. |
trial_balance |
Month-end snapshot per (entity, account, period). Built from gl_transaction on sync; used by report queries. |
gl_mapping_rule |
Single table with country column. Maps account_code → report_line (internal code) + report_sub_line (internal code for Mapping 2). Has effective_from/effective_to for the versioning requirement above. |
report_line_catalog |
The 47 Mapping 1 lines + 20 Mapping 2 sub-lines + subtotals, with internal codes, display labels per language, ordering, and parent-subtotal relationships. |
gl_adjustment |
Manual adjustments made outside Pennylane (reclassifications, cutoff, audit adjustments). Replaces the Adjust. <entity> sheets. Has author, timestamp, justification, period, and the P&L/BS impact. |
period_close |
Per-entity monthly close status (open → in_close → closed). See docs/entity-model.md §4 for schema. Drives the dual-view pattern below. |
Multi-currency note: all entities operate in EUR natively. No FX conversion logic is required. Pennylane handles any occasional foreign-currency conversion at entry time.
Downstream features never query Pennylane or Excel directly. They query the unified model.
Dual-view reporting pattern¶
Every report in the system supports two views (see also docs/entity-model.md §4a):
- "As-reported" (default) — shows the numbers as they were when the period was closed. Uses the GL mapping version in force at the close date and only entries that had been booked at or before the close date. The frozen close snapshot. Numbers never change.
- "Restated" (toggle) — shows what the numbers would look like with current knowledge. Uses the current/latest GL mapping (applied retroactively) and all entries by
accounting_date, including late-arriving ones. Live GL data.
The UI exposes: period selector + "As reported | Restated" toggle + delta view (what changed between the two). This pattern applies uniformly to P&L / BS / CF at any scope, per-entity reports, consolidated reports, and historical comparisons (e.g., compare "FY25 as-reported" vs "FY25 restated").
Validation and reconciliation¶
Every sync / import run produces a reconciliation report:
- Does the trial balance still balance (assets = liabilities + equity)?
- Do monthly revenues match Logosw patient payments (within tolerance)?
- Are all
RETRO <NAME>accounts matched to a knownDentist? - Are any new GL accounts missing a mapping rule?
- Do intercompany eliminations net to zero at Belgium entities total and Group incl. BE?
Unreconciled deltas are flagged and the finance team gets an email via existing Celery + Sentry plumbing.
7. Phase 1 — Foundation¶
Theme. Stand up the entity model, ingest Pennylane + Excel GL, and rebuild the current Groupe Suffren management reporting format in-app. No new finance capability beyond "what they already see in Excel, but live."
Why this phase now. - Without an entity model, no later feature can scope correctly. - Without unified GL storage, every feature has to branch on source (Pennylane vs Excel) — unacceptable. - Without intercompany elimination, the consolidated P&L is wrong by millions (CL/CB interest flows alone are 7-figure). - Without rebuilding the existing format, the finance team has no reason to switch to Aletheia. They already have the Excel.
Foundational work required. All of apps/entities/, apps/finance/, apps/pennylane_sync/, and the GL import pipeline. No reuse of pre-existing finance code (there isn't any). Heavy reuse of apps/imports/AbstractImporter, apps/core/AuditModel, apps/core/permissions, apps/budgets/BudgetVersion approval pattern, Celery, the existing UI skeletons, and the Pennylane-reusable bundle.
Success metrics. - All 11 entities seeded and visible in the admin UI with correct CVR/FR percentages and ownership links. - Monthly Pennylane sync runs cleanly at 4 AM for all 7 FR entities, with checkpoint-based incremental loading. Reconciliation report clean. - Monthly Excel GL import accepts one canonical template for SUI, AST, SDT (and CHC when needed). Finance team uses it instead of maintaining the parallel Excel workbook TBs. - Consolidated P&L, BS, CF matches the current Excel within a rounding tolerance (say, ±€50 per line) at the Group incl. BE scope for FY25 and FY26 YTD. - Intercompany eliminations declared in config (not code), validated by a reconciliation check, balanced to zero at Belgium entities total and Group incl. BE. - The Finance Manager role sees the group + practice-group P&L in-app and can drill down to any scope.
Key risks and open questions.
- The non-Pennylane Excel formats are still fragmented — getting AST/SDT/SUI to all use one template requires finance-team buy-in and may need per-entity translation work.
- Intercompany rules are implicit in Excel formulas today. Extracting them to a declarative rules engine is a finance interview, not a code task. Budget for a workshop.
- The close cycle (v1 → v6 per month) requires a monthly close workflow from day one. The report format itself also evolves between fiscal years — the data model needs effective_from/effective_to on mapping rules, report line catalog entries, and elimination rules.
- CHC's dual role (top holding + occasional service co) is expressed via scope membership and separate elimination rules (resolved — see entity-model.md §7 Q1). Defer the CHC-as-service-co elimination rules to when CHC starts invoicing.
Phase 1 features¶
| # | Feature | Pennylane data | PMS join | Scope(s) | Excel GL? | Persona | Complexity | Moat |
|---|---|---|---|---|---|---|---|---|
| 1.1 | Entity model & admin | — | — | All scopes | No | Admin / Finance | L | Low (foundation) |
| 1.2 | Pennylane sync pipeline | Data Sharing: companies, general_ledger, trial_balance, customer_invoices, supplier_invoices, bank_accounts, bank_transactions; API v2: invoice_lines, suppliers |
— | Per entity | No | Sync (system) | L | Low |
| 1.3 | Excel GL import template | — | — | Per entity | Yes | Finance | M | Low |
| 1.4 | Unified financial data model + GL mapping table | Both paths | — | — | Both | — | M | Low |
| 1.5 | Intercompany elimination rules engine | Both | — | Practice group+ | Both | — | L | Low |
| 1.6 | Consolidated P&L / BS / CF rebuild (matching current "Groupe Suffren" format) | Both | Associate fees per dentist (optional v1 PMS join) | All scopes | Both | Finance / Exec | L | High (the join) |
| 1.7 | Practice group scorecards | P&L per entity (Phase 1 cut) | Dentist headcount, working days, revenue targets | Per practice group | Both | Practice Manager | M | High |
| 1.8 | Historical versioning of report line catalog + GL mapping | Both | — | — | Both | Finance | M | Low |
| 1.9 | Monthly close workflow | Both paths | — | Per entity | Both | Finance | M | Medium |
1.1 — Entity model & admin¶
Description. Implement the apps/entities/ Django app. Models: Entity, OwnershipLink, Holder, OwnershipEvent, AggregationScope, IntercompanyEliminationRule, FiscalIntegrationGroup, FiscalIntegrationMembership, PeriodClose. Individual holders are populated from day one (both roadmaps ship Phase 1 together). Share class and register tables are owned by the cap table roadmap. Seed the 11 entities, their ownership links, the aggregation scope rules, and the initial set of ~7 intercompany rules. Admin UI for creating, editing, and time-versioning entity changes. Feature permissions: finance_manage, entities_manage, entities_view added to apps/core/permissions.py.
Entity onboarding must be a smooth, repeatable workflow (5-10 acquisitions per year). When a new practice is acquired: (1) create entity row with acquisition_date and consolidation_start_date; (2) create practice row linked to entity; (3) aggregation scope memberships auto-populate from entity attributes; (4) if on Pennylane, add pennylane_company_id — sync starts automatically next nightly run; (5) if not on Pennylane, Excel GL import template is ready for finance upload. This doesn't need a full admin wizard in Phase 1, but the data model and seed tooling must support it without engineering intervention per acquisition.
Why. Every later feature depends on this.
Pennylane data. None directly (but stores pennylane_company_id for later linking).
PMS join. Adds a nullable entity_id FK to apps/practices/Practice so practices can be mapped to legal entities.
Scope. All aggregation scopes are defined here.
Complexity. L. Schema is non-trivial, seeding is careful work, admin UI needs a custom time-slider for ownership changes.
Moat. Low. This is infrastructure; the moat comes later.
1.2 — Pennylane sync pipeline¶
Description. Copy temp/pennylane_reusable/ into apps/pennylane_sync/. Adapt for Aletheia's settings (credentials via python-decouple, Celery for scheduling, Sentry for errors, logging to the existing logs/ directory). Wire up a nightly Celery task pennylane_sync.nightly_sync that calls sync_redshift_to_local.py in incremental mode. Add a dashboard page showing sync health per entity: last sync time, row count delta, errors, reconciliation status. Extend the existing Collection-agent health-check pattern (celerybeat-schedule in config/settings/base.py) with a pennylane-sync-health check every 30 minutes.
Why. First time we ingest ledger data for the 7 FR entities. Pennylane data. Data Sharing + API v2, all FR entities. PMS join. None in this feature. Scope. Per entity. Complexity. L. Not novel (the bundle exists) but integration work is real: Celery wiring, settings, logging, monitoring, error surfaces, permissions, admin UI for sync status. Moat. Low (infrastructure).
1.3 — Excel GL import template and pipeline¶
Description. Define one canonical Excel template (Groupe Suffren GL import v1.xlsx) that replaces the three current non-Pennylane shapes. Implement GLTrialBalanceImporter(AbstractImporter) that validates, splits by (entity, period), applies country-specific chart mapping, and writes to gl_transaction + trial_balance. Support dimension columns for AST/SDT (LOCATION, PROJECT); SUI uses null dimensions. A /finance/imports/gl/ page exposes an upload UI for the Finance team. Dry-run mode by default.
Why. Today the non-Pennylane entities are fragmented. This phase consolidates them onto one template so the consolidation engine can treat them uniformly.
Pennylane data. None.
PMS join. None.
Scope. Per entity.
Excel GL import? Yes — this is the pipeline.
Persona. Finance (monthly upload).
Complexity. M. Template design + one new AbstractImporter subclass + validation + per-country chart mapping + admin UI.
Moat. Low.
1.4 — Unified financial data model + GL mapping table¶
Description. Create gl_account, gl_transaction, trial_balance, gl_mapping_rule, report_line_catalog, gl_adjustment tables (see §6). Seed report_line_catalog with the 47 Mapping 1 lines + 20 Mapping 2 sub-lines from the current Excel, using stable internal codes (PNL_REVENUE_PRIVATE, PNL_COGS_PROTHESIS, BS_TRADE_RECEIVABLES, CF_FCF_PRETAX, ...). Seed gl_mapping_rule for FR (Pennylane PCG → Mapping 1) from the existing FR MAPPING sheet (~469 rows). Defer BE and LU mapping tables to a Phase-1 work item shortly after the FR mapping — seeding them needs finance-team interview.
Why. Downstream reports never branch on source; they query trial_balance via report_line_catalog.
Pennylane data. Stores data imported by 1.2.
PMS join. None in this feature (the moat joins come in 1.6).
Scope. Foundation for all scopes.
Excel GL? Yes — stores Excel-imported GL alongside Pennylane GL.
Persona. —
Complexity. M. Schema is not large, but seeding the FR MAPPING table requires carefully preserving the ~469 rules, and report_line_catalog needs i18n for display.
Moat. Low (foundation).
1.5 — Intercompany elimination rules engine¶
Description. Implement the intercompany_elimination_rules table from the foundation. Build a rules engine that, given a requested scope and period, computes the elimination postings in memory (never writes them to GL) and applies them at report-generation time. Seed the initial rules (see foundation §6) via a management command. Add a validation run on every sync that reports unbalanced eliminations as warnings via Sentry.
Why. Without this, the consolidated P&L and BS are wrong — CL/CB interest alone creates 7-figure errors at practice group level and above. Pennylane data. Both. PMS join. None. Scope. Practice group level and above (elimination only applies above individual-entity level). Excel GL? Yes. Persona. Finance. Complexity. L. The rule DSL needs to be expressive enough to match the existing Excel formulas (investment ↔ equity, loan ↔ interest, receivable ↔ payable) but not so expressive that it becomes a programming language. Validation and reconciliation are non-trivial. Moat. Low (infrastructure) but essential.
1.6 — Consolidated P&L / BS / CF rebuild¶
Description. Build in-app pages that render the current "Groupe Suffren" management reporting format at any requested scope (per entity, per practice group, FR practices, Group excl./incl. BE) and any requested period. Layout matches the current Excel (Conso. sheet) exactly — same line order, same subtotals, same Analysis Table, same KPI block. Use existing template skeletons and Chart.js (or ApexCharts, consistent with existing dashboard style) for visualizations. Drill-down from report line → GL account → journal entry works for the Pennylane entities and shows "imported from Excel" for the rest.
Associate fees per dentist join (v1 cut). Read the GL accounts RETRO <FIRST LAST> via the unified GL store and match each to an apps/dentists/Dentist row via first name + last name (case-insensitive). Expose an admin mapping UI for manual overrides when the match fails. Use the matched data to auto-populate the "Associate fees per dentist" and "Revenue per dentist" rows of the Analysis Table. This is the first moat feature — it lands inside the rebuilt P&L, not as a separate page, so practice managers see it immediately.
Why. Finance and practice managers stop reading the Excel, start reading Aletheia. This is the reason the foundation exists.
Pennylane data. general_ledger, trial_balance, supplier_invoices, customer_invoices (for drill-down).
PMS join. Dentist name → RETRO account matching, for the associate-fees / revenue-per-dentist join.
Scope. All scopes.
Excel GL? Yes.
Persona. Finance, Practice Manager, Executive.
Complexity. L. The layout is complex, the drill-down is complex, the moat join is complex, and the scope selector must gate all of it.
Moat. High — the dentist join is unique to Aletheia. Even the Phase 1 cut beats what Pennylane + Excel + BI tool can produce.
1.7 — Practice group scorecards¶
Description. A per-practice-group "scorecard" page showing: current-period P&L summary (revenue, gross profit, EBITDA, net profit + YoY), cash position, top 5 practitioners by revenue, no-show rate, equipment service status, current budget vs actuals (reusing apps/budgets/BudgetVersion data at practice level). This is the daily-use dashboard for a practice manager — shorter and more actionable than the full P&L.
Why. Practice managers don't need the full 47-line P&L; they need a decision dashboard. This is the first time a practice manager sees finance and PMS data side-by-side without opening two apps. Pennylane data. Monthly P&L per entity. PMS join. Dentist revenue, no-show rate, equipment service, budget actuals. Scope. Per practice group by default; toggle to per-entity (legal entity) for finance users. Excel GL? Only for entities that need Excel (none of the practice-group operating practices — they're all on Pennylane). Persona. Practice Manager primarily, Operations Manager secondarily. Complexity. M. Reuses 1.6's data layer. Moat. High. This is a finance ↔ PMS join by definition — no other tool has both sides.
1.8 — Historical versioning of report line catalog + GL mapping¶
Description. Add effective_from / effective_to columns on report_line_catalog and gl_mapping_rule. Every change goes through a new row (never in-place updates). Historical reports always fetch "the version of the catalog / mapping in force at the close of period P". Add an admin UI to preview the impact of a proposed mapping change on historical numbers before committing it. Ship a diff/audit log so finance can see every mapping change in a single place.
Why. GL mappings and report formats evolve over time. Without versioning, changing a mapping in March retroactively changes January's numbers, and historical comparisons break. The "as-reported" vs "restated" dual-view depends on this. Pennylane data. Both. PMS join. None. Scope. Foundation. Complexity. M. Schema + admin UI + preview logic + audit log. Moat. Low.
1.9 — Monthly close workflow¶
Description. Per-entity monthly close status (open → in_close → closed). Close calendar enforcement (~6th preliminary, ~20th final). "Frozen at close" snapshot preserving exactly what was reported. "Current truth" toggle showing late-arriving entries by accounting_date. Delta view (what changed since close). Re-open requires CFO-role approval with audit trail. Notifications to finance team at each stage via existing Celery + email plumbing.
Why. The finance team's current Excel close process (v1 → v6) is what gives them confidence in the numbers. Without a close workflow in Aletheia, previously-reported numbers can drift as new entries arrive, destroying trust and blocking the switch from Excel.
Pennylane data. Both paths.
PMS join. None.
Scope. Per entity.
Excel GL? Both.
Persona. Finance.
Complexity. M. Schema (see period_close in entity-model.md §4), snapshot logic, dual-view queries, re-open approval flow.
Moat. Medium. The close workflow is specific to the group's process.
Phase 1 — what it explicitly does not include¶
- No budgeting redesign (budgets stays per-practice for now; entity-scoped budgeting comes in Phase 2).
- No forecasting (no 13-week rolling, no annual P&L forecast, no scenario modelling) — deferred to Phase 2.
- No cost-of-sale deep dives (prosthetics lab analysis, supplier deep-dives) — Phase 2.
- No cash flow forecast — Phase 2/3.
- No AI / NL features — Phase 3.
- No FEC export, no TVA dashboard — Phase 3 (or never, depending on Decisions needed).
- No direct Pennylane write-back — permanently out of scope.
8. Phase 2 — Moat features¶
Theme. The finance ↔ PMS joins that justify building this inside Aletheia. Cost intelligence on the biggest variable cost (prosthetics labs) and the biggest operational lever (per-dentist performance). Entity-scoped budgeting and the first forecasting capability.
Why this phase now. - Phase 1 delivered the platform. Phase 2 delivers the differentiator. - Phase 1 rebuilt what the Excel already had. Phase 2 delivers what the Excel cannot. - Each feature is a finance↔PMS join that Pennylane + BI cannot do alone.
Foundational work required. Minimal new foundation — Phase 2 runs on the Phase 1 data model. Two additions: (a) the apps/budgets/ module gets entity-scoped budget versions, and (b) the Pennylane sync extends to pull supplier invoice PDFs for the AI extraction feature.
Success metrics. - Practice managers use the prosthetics cost page monthly instead of asking the finance team for lab breakdowns. - The per-dentist P&L is used in quarterly compensation reviews. - The first annual budget cycle (for FY27) runs on Aletheia, not Excel. - EBITDA forecast accuracy (rolling 3-month) is within ±5% of actual.
Key risks and open questions.
- The prosthetics AI extraction was proven in penlane_tst but only on one vendor format. Generalizing to all prosthetics labs needs sample data from each vendor.
- Per-dentist P&L requires a clean name-to-Dentist match for every RETRO <NAME> account — the admin override UI from Phase 1 must be well-maintained.
- Forecasting accuracy depends on historical quality — the data needs to go back 2+ fiscal years for meaningful trends.
Phase 2 features¶
| # | Feature | Pennylane data | PMS join | Scope | Excel GL? | Persona | Complexity | Moat |
|---|---|---|---|---|---|---|---|---|
| 2.1 | Prosthetics / lab cost deep-dive | Supplier invoices + PDFs via API v2; Claude AI PDF extraction | Procedure type (CCAM), tooth numbers, patient age | Per entity / practice group | Yes (future BE labs) | Finance, Practice Manager | L | High |
| 2.2 | Per-dentist P&L | RETRO <NAME> GL accounts |
DentistContract, Procedure, Payment, WorkSchedule, RevenueTarget | Per entity with practitioner drill-down | No | Finance, Practice Manager, HR | M | High |
| 2.3 | Revenue-to-invoice funnel | Customer invoices (Pennylane) | Procedure fees, Payment records, Appointment status | Per entity / practice group | No | Finance, Practice Manager | M | High |
| 2.4 | No-show cost analysis | Revenue data per practice | Appointment status (absent_non_excuse, absent_excuse) |
Per entity / practice group | No | Practice Manager | S | High |
| 2.5 | Entity-scoped budgeting | Actuals from 1.6 | BudgetVersion per entity, per scope | All scopes | Both | Finance, CFO | M | Medium |
| 2.6 | Annual budget vs actuals dashboard | Actuals | Budget lines | Per entity through Group excl. BE | Both | Finance, Exec | S | Medium |
| 2.7 | Cross-practice benchmarking pack | Actuals | Patient/appointment/equipment data | Practice group peer set / vintage | No | Exec, Ops Manager | M | High |
| 2.8 | Supplier deep-dive | Supplier invoices, bank transactions | None | Per entity through Group excl. BE | No | Finance | M | Medium |
2.1 — Prosthetics / lab cost deep-dive¶
Description. The biggest variable cost in a French dental group is prosthetics. penlane_tst already proved the AI pipeline: download supplier invoice PDFs via the Pennylane API v2, extract structured line items (tooth number, material, unit price, quantity) with Claude, store the extracted data, and produce per-tooth / per-practitioner / per-patient / per-procedure cost reports. Phase 2 productizes that pipeline:
- Nightly Celery task downloads new supplier-invoice PDFs for all labs (filter by supplier-category mapping).
- Each PDF is run through Claude for extraction. The extracted rows are stored in a new
prosthetics_line_itemtable linked tosupplier_invoice. - A finance dashboard shows: lab-by-lab cost trends, material-mix changes, outlier detection (tooth unit price > 2σ from historical), per-practitioner prosthetics cost, per-procedure prosthetics cost, per-patient-age-bracket analysis.
- Drill-down from consolidated P&L line "Prothesis" → lab → monthly PDF → extracted line item.
Moat. High. Pennylane cannot tell you which tooth each invoice line refers to. Aletheia can, because it joins to the Procedure.tooth_number field for the same patient.
Complexity. L. The pipeline exists in prototype; productizing it means handling vendor format variance, re-extraction on mismatch, caching, cost control, and per-vendor validation.
2.2 — Per-dentist P&L¶
Description. For each practicing dentist, produce a monthly P&L showing: revenue generated (Procedure.fees summed from Logosw / PMS), rétrocession paid (from the GL RETRO <NAME> account), net contribution, scheduled working days, actual working days, daily average, quarter-over-quarter change. Split by procedure type (CCAM grouping) for context. Aggregates to the practice-group and entity levels via the foundation's aggregation tree.
Why this is possible and unique. The GL has the rétrocession side, Aletheia has the revenue side — and the tooth-level procedure data that tells you what kind of work produced the revenue. Available as a per-dentist drill-down from the Practice Group Scorecard (1.7) and from the full P&L Analysis Table (1.6). Moat. High. Complexity. M. The data model is mostly in place after Phase 1; the work is layout, joins, and UX for per-dentist comparison.
2.3 — Revenue-to-invoice funnel¶
Description. Track each procedure through a funnel: planned (quote/devis) → performed → invoiced → paid (patient side) → reimbursed (AMO/AMC side). Surface bottlenecks: procedures performed but not invoiced, invoices issued but not paid, AMO rejections awaiting follow-up. The current apps/procedures/ already tracks patient_payment_datetime, amo_amount, amo_paid, amo_response_date — Phase 2 ties that to the Pennylane customer-invoice side for cross-check.
Why this is unique. Pennylane sees the accounting flow (invoice → receivable → cash). Logosw sees the clinical flow (procedure → patient payment). Neither sees both. Aletheia does. Moat. High. Complexity. M. Cross-matching Pennylane customer invoices to Logosw patient payments is fuzzy (dates, amounts, patient IDs). Expect manual-override tooling similar to the RETRO-to-Dentist matcher.
2.4 — No-show cost analysis¶
Description. Compute the financial cost of no-shows: for each no-show appointment, estimate the lost revenue (based on dentist's daily target × duration / working day) and aggregate to practice, dentist, patient-segment, and time-of-day. Expose as a dedicated page and as a line in the Practice Group Scorecard. Trend YoY.
Why. Practice managers have asked for this per the roadmap/ideas/dashboard-analytics.md. The data is already in the PMS side; Phase 2 adds the financial interpretation (lost revenue value).
Moat. High (pure PMS + finance target join).
Complexity. S. Small feature, high signal.
2.5 — Entity-scoped budgeting¶
Description. Extend apps/budgets/BudgetVersion to support entity-scoped budgets (not just practice-scoped). A BudgetVersion targets an Entity OR a Practice (mutually exclusive). New assumption tables: BudgetPnLAssumption (revenue/cost by report line, per month), BudgetScenario (what-if modelling). Use the existing draft → submitted → validated workflow.
Why. Phase 1's budget app is per-practice, which only works for operating practices. Service companies, holdings, and the full group need entity-level budgets. Moat. Medium. The workflow is standard; the entity/scope dimension is the differentiator. Complexity. M.
2.6 — Annual budget vs actuals dashboard¶
Description. Side-by-side dashboard: current actuals (from 1.6) vs active budget (from 2.5), per P&L line, per month, per scope. Highlights variances > threshold. Per-line commentary field for finance to annotate (feeds back into the monthly reporting narrative).
Moat. Medium. Complexity. S.
2.7 — Cross-practice benchmarking pack¶
Description. Peer comparison across practice groups (with vintage cohort grouping for acquisition-year analysis): KPI-by-KPI tables and charts. Metrics include revenue per dentist, associate fees ratio, EBITDA margin, DSO, FTE per dentist, no-show rate. Drill-down from each KPI to the contributing entity data.
Why. Multi-practice groups live or die by being able to compare sites. This is the "scoreboard" feature. Moat. High. The metrics come from both sides. Complexity. M. Reuses 1.6 + 1.7 + 2.2 + 2.4 data; work is layout and comparison logic.
2.8 — Supplier deep-dive¶
Description. For each supplier in Pennylane, show monthly spend, invoice count, average invoice size, payment terms actuals (DPO), and spend trend. Filter by category. Identify suppliers whose spend is growing faster than revenue.
Moat. Medium (Pennylane-only data, no PMS join). Complexity. M.
9. Phase 3 — Automation and AI¶
Theme. Automate the manual work (month-end close, invoice approval, missing-document chasing) and layer AI on top (universal invoice extraction, transaction categorization, natural-language Q&A, cash-flow forecasting).
Why this phase now. - Phase 2 shipped the dashboards. Phase 3 reduces the work that happens before the dashboards. - By Phase 3, the data is rich enough for meaningful AI (multi-quarter history, dentist matching, prosthetics extraction coverage, elimination rules well-understood). - Some features (13-week rolling, NL Q&A, transaction auto-categorization) land late because they depend on data volume.
Foundational work required. A rules engine for the approval routing / missing-doc chaser (new app apps/finance_workflow/). Minor extensions to the Pennylane sync for bank transactions (already in Data Sharing).
Success metrics. - Month-end close time reduced from X days to Y days (measure before starting). - Invoice approval SLA (time from supplier invoice received to approved) reduced measurably. - Cash forecast accuracy (13 weeks) within ±10% of actual. - Finance team uses NL Q&A for ad hoc questions instead of interrupting engineering.
Phase 3 features¶
| # | Feature | Pennylane data | PMS join | Scope | Complexity | Moat |
|---|---|---|---|---|---|---|
| 3.1 | Month-end close checklist & automation | Both paths | — | All scopes | M | Medium |
| 3.2 | Invoice approval routing | Supplier invoices | Practice, entity | Per entity | M | Medium |
| 3.3 | Missing-document chaser | Bank transactions without matching invoices | — | Per entity | S | Medium |
| 3.4 | 13-week rolling cash flow forecast | Bank transactions, customer/supplier invoices | Appointment volume, planned capacity | All scopes | L | High |
| 3.5 | Universal supplier-invoice line extractor (AI) | Supplier invoices + PDFs | — | Per entity | L | Medium |
| 3.6 | Bank transaction auto-categorization (AI) | Bank transactions | — | Per entity | M | Medium |
| 3.7 | Natural-language financial Q&A (AI) | All GL + PMS | Full | Any scope | L | High |
| 3.8 | Anomaly / fraud detection | All GL | PMS patterns | Per entity through Group excl. BE | M | High |
3.1 — Month-end close checklist & automation¶
Description. A checklist UI that tracks the monthly close process per entity: Pennylane sync done, GL imports done, adjustments entered, reconciliations clean, eliminations validated, reports published. Each item has an owner and a due date. Automated where possible (Pennylane sync step auto-checks when sync succeeds). Exportable close report for the audit trail.
Complexity. M. Moat. Medium. The orchestration is specific to the group's process.
3.2 — Invoice approval routing¶
Description. For supplier invoices flagged above a threshold or coming from an unapproved vendor, route for approval before they reach Pennylane's payment queue. Practice manager approves up to €X, ops manager up to €Y, CFO above. Notifications via email (SMTP already configured). Audit trail per approval step.
Complexity. M. Moat. Medium.
3.3 — Missing-document chaser¶
Description. Bank transactions without a matching supplier invoice are flagged. Email chaser to the practice that owns the card/account. Triage UI for finance to close out stale items.
Complexity. S. Moat. Medium.
3.4 — 13-week rolling cash flow forecast¶
Description. Weekly-granularity cash forecast for the next 13 weeks per entity (and aggregated per scope). Inputs: (a) historical bank transaction patterns, (b) known scheduled payments (supplier invoices due), (c) expected receipts (customer invoice aging + historical DSO), (d) forward revenue from Aletheia's appointment book × historical conversion rate × average ticket. The PMS feed is a material input — no standalone BI tool has it.
Moat. High. The appointment-book-driven revenue forecast is unique. Complexity. L. Weekly granularity, multi-source inputs, model validation.
3.5 — Universal supplier-invoice line extractor (AI)¶
Description. Generalize the prosthetics AI extraction from 2.1 to all supplier invoices. Each vendor's PDFs are extracted into structured line items. Extracted data enriches the supplier-deep-dive and feeds the anomaly detector (3.8).
Complexity. L. Vendor-format variance is the hard part. Moat. Medium (Pennylane has the PDFs but not the extraction).
3.6 — Bank transaction auto-categorization (AI)¶
Description. Classify unreconciled bank transactions into GL accounts using Claude + a per-account training history. Finance reviews, accepts / rejects, the model improves. Feeds back into Pennylane by making the approval queue shorter, not by writing back directly.
Complexity. M. Moat. Medium.
3.7 — Natural-language financial Q&A¶
Description. A chat interface that lets finance, practice managers, and the exec team ask questions in natural language ("What was PDS's EBITDA last quarter vs Q4 FY25?", "Which dentist at VSM had the highest associate fee ratio?"). Uses Claude with tool access to the unified GL store and the PMS data model. Scoped by the user's feature permissions.
Moat. High. The tool-calling surface is specific to Aletheia's joined-data model. Pennylane's NL Q&A can't reach the PMS side; a general BI NL tool can't reach either side with the right semantics. Complexity. L. Safety (wrong answer risk), cost control, schema exposition.
3.8 — Anomaly / fraud detection¶
Description. Detect unusual patterns: GL accounts deviating from historical baselines, cash flows inconsistent with appointment volume, duplicate supplier invoices, dentists whose revenue pattern changes abruptly, prosthetics unit costs outside expected range. Uses a mix of classical statistics (z-score on category time-series) and AI for narrative / explanation.
Moat. High. Cross-domain anomaly detection isn't possible without both sides. Complexity. M.
10. Decisions needed¶
Surfacing the tradeoffs — not resolving them. Each item here is a decision the team must make before the relevant phase starts.
Architectural¶
-
Live Redshift queries vs local DuckDB replica.
pennylane_reusablesupports both via one env var. For production, do we query live (fresh data, latency cost, Redshift cost) or replicate nightly (stale data, no query latency, free local queries)? Proposed: nightly replica by default, live query as an admin escape hatch for ad hoc investigations. -
~~Local replica storage: DuckDB vs PostgreSQL.~~ RESOLVED. PostgreSQL. All financial tables live in the shared PostgreSQL database alongside PMS tables. DuckDB was prototype-only.
-
Build in-app dashboards vs pipe to a BI tool. Phase 1 clearly builds in-app (the moat requires it). Phase 3 (especially 3.7 NL Q&A) might benefit from a headless BI backend (Metabase?). Decide per-phase.
-
Refresh cadence and close-window sync. Nightly for Pennylane. On-demand for Excel GL import (triggered by finance upload). Practice-group-scorecard operational data is real-time from the PMS side. Additionally: the monthly close process (6th → 15th → 20th) means finance may re-trigger Pennylane sync multiple times within a close window to pick up accountant corrections. Allow manual sync trigger for Finance Manager role, not just the nightly schedule.
Data and modelling¶
-
~~GL mapping: import-time vs query-time.~~ RESOLVED. Query-time. Enables both "as-reported" and "restated" views without re-importing historical data. If performance concerns materialize, add materialized views — but don't pre-optimize.
-
~~Chart-of-accounts: one global table or per-country.~~ RESOLVED. One
gl_mapping_ruletable with acountrycolumn. Adding a new country = adding rows, not a new table. All countries map to the same 47 report-line target catalog. -
~~Fiscal year alignment.~~ RESOLVED. Management reporting FY is Oct 1 – Sept 30 across all entities and countries. The "31/12" label in the current Excel is a stale typo. Legal FY also aligns to Oct-Sept for all current entities. New acquisitions transition to Sept 30 close via shortened/extended transitional year.
-
~~How many scopes are actually used?~~ RESOLVED. Replaced by the corrected aggregation model in entity-model.md §5. All listed scopes (practice groups, subtotals, group totals, vintages) are used or planned. Scope membership is computed from entity attributes, not manually enumerated.
-
SPFPL holding toggle. Are PDSh and VSMh always hidden in the practice manager view (default practice group) or exposed as a toggle? Finance needs them visible; practice managers don't. Proposed: always hidden by default, with a per-feature-permission toggle.
-
Reference format evolution policy. The report format evolves between fiscal years (and occasionally mid-year). Who approves format changes? Is it a CFO sign-off, an engineering release, or both? Are practice managers allowed to see a previous format while finance is on a newer one? Propose: versioned report templates, practice managers see latest-approved, finance sees latest-draft, draft → approved promotes to all users.
Workflow and governance¶
-
Aggregation scope configuration. Where does the aggregation tree live? Proposed: database table (
aggregation_scopes) editable via Django admin by admin-role users. Not a config file (would need redeploys), not code (same issue). -
Excel GL import template ownership. Who owns the canonical template and its validation rules? Proposed: finance owns the business logic (which columns exist, what's required), engineering owns the validation implementation (the importer class). A
Finance Manager-role user can update the mapping rules via admin UI. -
Intercompany elimination: declarative vs imperative. Already decided in the foundation — declarative, stored in
intercompany_elimination_rules. The question is how expressive the DSL needs to be: are all current Excel eliminations reducible to(entity_from, entity_to, lines, scope_level)tuples, or do we need a more flexible rule language for edge cases like equity-method eliminations that chain across multiple entities? Need a finance interview to verify. -
Non-Pennylane entity cadence. Monthly upload only, or push toward more frequent imports as Luxembourg/Belgium ramp up? Proposed: monthly is sufficient today; add a "partial/interim upload" mode if pressure builds.
-
CHC onboarding cutover. CHC is coming onto Pennylane soon. What's the cutover plan? Proposed: Phase 1 supports Excel GL for CHC; when Pennylane is live, add CHC's
pennylane_company_idto theentitiesrow and flippennylane_status='active'. Duplicate data during the overlap period is accepted; a reconciliation report catches drift. -
CL / CB conversion preview. When a convertible eventually converts, the consolidation impact must be preview-able. Build this as a Phase 1 "fire drill" feature (simulate the conversion, show the delta on the consolidated P&L / BS / CF / tax), or defer to the cap table roadmap? Proposed: build the simulation in the cap table roadmap, but use the finance roadmap's rules engine so both sides see the same preview.
-
~~Cap table roadmap dependency gating.~~ RESOLVED. Individual holders are populated from day one — both roadmaps ship Phase 1 together. No phased migration needed.
French-specific / regulatory¶
-
FEC generation. French tax authorities require a standardized Fichier des Écritures Comptables (FEC) on audit request. Pennylane likely generates it for the 7 FR entities. Does Aletheia need to generate one for consolidation purposes? Probably not — audit applies per legal entity and Pennylane handles that. Not in scope for Phases 1–3. Revisit if an audit requires a consolidated FEC.
-
TVA dashboard. French VAT is complex and Pennylane handles the declaration. An in-app TVA dashboard is low moat (pure Pennylane data). Propose: defer indefinitely; add only if explicitly requested.
-
URSSAF / social charges. Payroll data is in class 64 GL accounts. The reporting already captures "Colleague Costs" with a breakdown (Gross wages, Social security contributions, Other payroll costs). A Phase 3 feature could track social charge rates over time and flag anomalies. Not in current scope.
-
PCG conformity monitor. Check that the
FR MAPPINGtable covers all Pennylane GL accounts and flags new unmapped accounts. Small feature, belongs in Phase 1 tooling but not highlighted as a headline feature. -
Aggregation scope: computed membership vs explicit membership. The group grows 5-10 practices/year. Should scope membership be computed from entity attributes (country, type, acquisition_date) via rules — or explicitly maintained via M2M tables that must be manually updated on every acquisition? Computed is less maintenance; explicit is more flexible for edge cases. Recommend computed with manual override capability.
-
Close snapshot implementation. Two approaches: (a) store a materialized snapshot per (entity, period) at close time (duplicates data but fast queries), (b) use
booking_date <= closed_atas a query filter on live GL to reconstruct the "as-closed" view (no duplication, but relies onbooking_datebeing accurate). Recommend (a) for trust/auditability — the snapshot is the canonical "what we reported." -
Late entries: book to accounting_date or to current period? With the new system, late entries are stored with their true
accounting_date(January) even if booked in March. The "current truth" view shows them in January. The "as-reported" view still shows the frozen January. This is different from the Excel-era convention (where late entries effectively landed in the current period). Confirm this is the desired behavior. Finance sees both views and can choose.
11. Appendix A — Pennylane capability reference¶
Do not duplicate the Pennylane-reusable README here. Refer to:
- Main index:
temp/pennylane_reusable/README.md— lists every file, access method, architectural pattern, and the bootstrap checklist. - Access modules:
db.py— dual Redshift / DuckDB connection layerpennylane_api.py— REST API v2 wrapper (cursor pagination, retry, per-company tokens, fresh-URL PDF download)sync_redshift_to_local.py— incremental ETL with_sync_metacheckpoints and per-company parallelism- Schema conventions: all tables have
synchronized_at(incremental key),deleted_at(soft delete), andcompany_id(entity scope). Two source schemas exist in Redshift:etlandpennylane. - Data locations — Data Sharing only:
general_ledger,trial_balance,bank_transactions. API v2 only:customers,suppliers,products,plan_items, individualinvoice_lines, PDFs. Both:companies,customer_invoices,supplier_invoices,bank_accounts.
When implementing Phase 1.2 (Pennylane sync pipeline), the first action is to copy the reusable bundle into apps/pennylane_sync/ and run compare_access_methods.py against the real credentials to confirm end-to-end connectivity.
12. Appendix B — Existing reporting format reference¶
Do not duplicate the catalog in temp/pennylane_reusable/reference/README.md. Key pointers for the finance-roadmap work:
- Canonical format file:
temp/pennylane_reusable/reference/reporting incl mappings/FY25 Suffren - 02 2026 - Mngt reporting v6.xlsx(the latest version at time of writing). - Sheets to read first:
Conso.(target format for consolidated P&L / BS / CF + Analysis Table + KPI block),Compil.(monthly time-series structure),Contrib.(per-entity contribution view),FR MAPPING(~469 rows, the GL → Mapping 1/Mapping 2 rulebase for Phase 1.4 seeding). - Non-Pennylane TB sheets:
AST FY26,SDT FY26(Belgian, withCompte / Libellé local (NL) / Libellé FR / TYPOLOGIE / PROJECT / monthly columns),SUI FY26(Luxembourg, withReference / Description / Description 2 / monthly columns). These three shapes must be unified into one canonical template for Phase 1.3. - Group name convention: "Groupe Suffren" is the internal commercial name of the group. When you see "Suffren" in filenames, it refers to the consolidated group, not a specific entity.
- Close iterations: file goes v1 → v6 per month's close cycle (not per fiscal year). v1 = first GL pull ~6th, v6 = final closed ~20th. Report format evolves between fiscal years.
- Known gaps in the current Excel (from §3 above):
- No CHC sheet yet
- "FYE 31/12" label is a stale typo — confirmed Oct-Sept
- Non-FR charts of accounts mapped via per-sheet VLOOKUPs, not a shared mapping
- Adjustments have no audit trail
- Cross-entity eliminations are implicit in formulas
When implementing Phase 1.4 (unified financial data model + GL mapping), the first action is to import FR MAPPING into gl_mapping_rule with country='FR' and effective_from=2024-10-01 (or earliest FY25 date you want history for).
End of roadmap v1. Feedback on this document goes to the finance roadmap owner. Any change that touches the entity, ownership, or consolidation model must be reflected in docs/entity-model.md first.