Release Notes
This page tracks user-visible changes shipped to the Formation app — new features, behaviour changes, and notable fixes. Internal refactors that don’t change how the app looks or works are omitted.
Versions match the Version shown on the Settings → System Info page.
Investment size for every ownership type, with smart scheme defaults
Section titled “Investment size for every ownership type, with smart scheme defaults”Investment-event size, size unit, and units fields used to appear only when the Ownership Type was Strata. They now appear for Whole, Share, and Strata alike — record size on any deal.
Two related conveniences:
- Auto-fill from the linked scheme. When you add an investment against a scheme that has a recorded size, the Size and Size Unit fields are pre-filled from the scheme’s total — using your preferred Net/Gross basis. The seed unit is sqm (the basis the scheme stores internally); switch unit or type a different value to override — your edit always wins. Auto-fill only runs when one scheme is linked, so multi-scheme investments stay blank for you to enter manually.
- Auto-create scheme from address. Adding an investment against an address that has no schemes yet now creates the scheme, a development, and a use behind the scenes (marked unverified so you can tell it apart from curated schemes). The development’s use size matches the investment, so the scheme totals are populated from day one. The new scheme uses the Building Type you pick on the investment form.
Once recorded, the investment size and the scheme size are independent — editing one never silently rewrites the other. To make that explicit, editing a development’s use sizes on a scheme that already has investments now shows a confirmation dialog: “You’re changing the scheme size. Existing investment sizes won’t be updated. Continue?”
Reporting → Formation navigation in Fabric (Analysts)
Section titled “Reporting → Formation navigation in Fabric (Analysts)”Correction to the previous release-notes entry on EncodedId columns.
Those columns are still in the OLTP database, but Fabric mirroring
doesn’t replicate computed columns even when marked PERSISTED, so
they don’t reach a Fabric-mirrored warehouse. The “no extra deploy”
framing in the original entry was wrong for the Fabric pipeline
specifically.
The cleanest way to take an analyst from a record they spot in reporting back to the matching page in Formation is:
- Add a DAX calculated column to the Fabric semantic model:
URL = "https://formation.pma.co.uk/schemes/" & FORMAT([SchemeId], "0") - Set its Data Category to
Web URL. - Excel pivot tables, Excel via Analyze in Excel, and Power BI visuals all render it as a clickable hyperlink.
It works because SchemeId (the integer PK) mirrors as an ordinary
column, and the API already accepts /schemes/{SchemeId} URLs
alongside the encoded form. No OLTP schema change needed; same shape
for the other four entities — swap schemes and SchemeId for the
matching slug + PK column.
The EncodedId columns on the OLTP tables stay where they are — they
remain useful for ad-hoc SQL queries against the OLTP database
directly (SELECT EncodedId FROM app.Scheme WHERE SchemeId = 42),
and for any non-Fabric replication pipeline that does carry computed
columns through.
Faster list-page search, with a “Deep search” toggle (default behaviour change)
Section titled “Faster list-page search, with a “Deep search” toggle (default behaviour change)”Searches on the list pages — Schemes, Addresses, Companies, Investment Events, Occupier Events, Portfolios — now run a fast full-text query by default and skip the slower pattern-matching pass that previously ran on every search. In our load tests against dev, this drops typical search latency from 5–25 seconds down to sub-second.
The pattern-matching pass occasionally found rows the fast index missed (mostly case variants and unusual single-character tokens), so we’ve kept it available as an opt-in. There’s now a Deep search switch on every list-page toolbar, next to the Verified toggle — flip it on to union the slower scan back in. Your choice is remembered across sessions.
Exports follow the same default. If you noticed a drop in row counts on an export, re-run it with Deep search switched on first; the export will pick up whatever the list view is currently showing.
EF Core slow-query telemetry (Ops / DBA)
Section titled “EF Core slow-query telemetry (Ops / DBA)”The API now records every EF Core DbCommand execution as a histogram
(formation.efcommand.duration, tagged with command type and success)
and attaches an ef.slow_command span event to the current Activity
whenever a command exceeds 500 ms (configurable via
Telemetry:SlowQueryThresholdMs). Both signals ship to Application
Insights via the existing Azure Monitor exporter, so when an endpoint
feels slow you can now answer “which SQL was the culprit” with a
KQL query rather than a guess. See
Observability for the
queries to copy/paste and the configuration knobs.
Behaviour change is purely additive — no app or DB schema changes, no impact on user-visible flows.
Portfolio market-sector breakdowns sum to exactly 100%, auto-rebalance on edit
Section titled “Portfolio market-sector breakdowns sum to exactly 100%, auto-rebalance on edit”Portfolios now always carry at least one market sector, and the shares across sectors always sum to exactly 100%. The old “up to 100%” rule meant a breakdown could sit at 80% forever without anyone noticing; the new rule makes the breakdown trustworthy.
What this changes in practice:
- Creating a portfolio now asks for at least one sector up front — there’s a new inline table on the create form with one row pre-filled at 100%. Add more sectors if the deal spans several building types; each row needs a Building Type at minimum.
- Editing a share on the detail page redistributes the delta proportionally across the other sectors. If a three-way split was 33.33% / 33.33% / 33.34%, changing the first to 40% now leaves the others at 30% / 30% automatically, rather than you having to type repeating decimals into each row by hand.
- Adding a sector scales the existing rows down proportionally and gives the new row an equal slice. Removing a sector scales the survivors up. The total always stays at exactly 100%.
- The last remaining sector can’t be deleted — the trash icon disables with a tooltip. Replace it with a different sector first if you need to change direction.
Building Type on the Portfolio itself (the single field separate from the sector breakdown) still works the same way for this release — the plan to derive it from the sectors ships in a later change.
Int PKs in URLs for Address, Scheme, Investment Event, Occupier Event, Portfolio (Analysts / DBAs)
Section titled “Int PKs in URLs for Address, Scheme, Investment Event, Occupier Event, Portfolio (Analysts / DBAs)”The five entity routes that carry an EncodedId column in the database
now accept their integer primary key directly in the URL too. So an
analyst looking at SchemeId = 42 in SQL can paste /schemes/42 into a
browser and land on the record — no decoder step needed. The same is
true inside OData filters: /Schemes?$filter=Id eq '42' matches the
same row that /Schemes?$filter=Id eq '<encoded>' would.
The regular encoded-form URLs keep working unchanged; this is an
additive power-user shortcut. The frontend still emits encoded URLs
everywhere it navigates, so normal user workflows are unaffected. Rule
of thumb: a path segment that’s entirely digits is treated as an int;
anything with letters or = padding flows through the encoded decoder
as before. Full reference:
Entity Identifiers → Int PKs work directly in URLs too.
EncodedId column on key entity tables (Analysts / DBAs)
Section titled “EncodedId column on key entity tables (Analysts / DBAs)”The app.Address, app.Scheme, app.InvestmentEvent, app.OccupierEvent
and app.Portfolio tables now carry an EncodedId column alongside the
integer primary key. It holds the same 8-character identifier the app
shows in URLs (e.g. SC1b2Cd==), so a bug ticket referencing /schemes/SC1b2Cd
can be traced to a row in SQL with a direct WHERE EncodedId = '…' query —
no decoder step required. Because the column is physically persisted, it
replicates to the data warehouse alongside everything else. Two helper
functions (app.EncodeEntityId, app.DecodeEntityId) cover every entity
type in case you need to translate an ID on a table that doesn’t have the
column yet. Full reference:
EncodedId columns on entity tables.
Market filter: pick between “self only” and “descendants only”
Section titled “Market filter: pick between “self only” and “descendants only””On the Schemes and Occupier Events list pages, the Market filter tree now shows a small mode chip next to each parent market you’ve ticked. The chip cycles through three modes when clicked:
- any (default) — the tick includes this market and everything under it. Same as before.
- self only — only rows whose Market is exactly this level (e.g. filter for Austria and get only country-level rows, not Vienna-level ones).
- descendants only — everything under this market, excluding rows at the market’s own level.
The chip only appears on parent markets that have children and that you’ve explicitly ticked. Leaf markets have no distinction to make and still look the same. The choice is saved in the URL, so filter links keep working when shared or reloaded.
The other list pages (Portfolios, Addresses, Investment Events) still use the simpler “self + descendants” behaviour; they’ll pick up the chip in a later release once their Market field shape supports the new modes.
Market Boundaries editor (Admin)
Section titled “Market Boundaries editor (Admin)”Admin-only. Admins can now create, edit, and delete market boundary polygons directly in the app — no more ArcGIS round-trip. From the Market Boundaries list, New Market Boundary opens a map with a blank canvas and metadata fields on the side. Opening any existing boundary drops admins straight into the full polygon editor — no separate Edit page or Edit button; the detail page itself is the editor for admins, with Save / Delete controls on the left and the map + drawing tools on the right.
- A vertex-count banner at the top of the map goes green / amber / red as the polygon grows, with a Simplify… button at amber and above that previews the reduced vertex count before committing.
- Polygons over 10,000 vertices can’t be saved — the banner turns red and the save button disables until the shape is simplified.
- Saving a boundary automatically re-syncs the address-to-boundary links for every address that entered or exited the polygon; the linked-address count on the detail panel refreshes straight after.
- Delete on the left panel prompts for confirmation and notes how many linked addresses will be re-synced.
- Non-admins continue to see the read-only detail panel.
- If another admin saves the same boundary while you’re editing it, your save is rejected rather than silently overwriting theirs — the page reloads and you’ll need to re-apply your changes.
Market Boundaries list + detail pages (Admin)
Section titled “Market Boundaries list + detail pages (Admin)”Admin-only. A new Admin group in the main sidebar now carries a Market Boundaries entry. The list view is searchable and filterable by name, code, sector and market, with a map projection showing each boundary’s centroid. Clicking through lands on a detail page with the boundary’s metadata, a preview map pinned to its centroid, and a badge showing how many addresses currently fall inside the polygon. The Edit button is visible on the detail page, but the in-app polygon editor itself lands in a later release.
Unlink a portfolio from a scheme
Section titled “Unlink a portfolio from a scheme”Linked portfolios on a scheme’s Investments panel now show an unlink icon in the top right of each card. Clicking it opens a confirmation dialog, and on confirm removes the link between the scheme and the portfolio without deleting the portfolio itself — you can link it back later if needed.
Search ranks near-match addresses higher
Section titled “Search ranks near-match addresses higher”List-page search (Addresses, Companies, Schemes, Portfolios, Investment
Events, Occupier Events) now boosts rows where every word of the query
appears as a word-prefix in the row — even if one word has a mid-word
typo. Searching 396-398 cit road now surfaces 396-398 City Road
on the first page instead of burying it behind rows that only share a
partial number or a phonetically similar word. True typo correction
(character-level edit distance) still isn’t handled — a missing space
or a wrong character at the start of a word will still miss — but
queries where every word has a valid prefix in the target now rank
strongly.
Scheme Statistics grid usability
Section titled “Scheme Statistics grid usability”- Delete quarter rows and statistic columns directly from the grid. Every row header and column header carries a red trash icon; a confirmation dialog runs first when the target has saved values.
- Occupancy Rate and Vacancy Rate are always shown as % regardless of the unit chosen on the compound — the rule is now enforced at render time, not just in the data model.
- Clearing one of your entered values in a compound statistic (e.g. Vacancy & Occupancy) also clears the auto-calculated siblings so the row doesn’t show stale figures derived from a value you removed. The row itself stays on the grid until you explicitly remove it with the row trash icon.
- The Add Statistic trigger sits above the table at the top-right so it stays visible no matter how wide the grid scrolls, and hides once every statistic type has been added to the scheme.
- A short “Tips” panel below the grid explains save-on-blur, the 2-of-5 compound rule, the % pin on rate components, paste-from-Excel, and what the trash icons do.
- The Add Statistic flyout clarifies which components the unit applies to when you pick a compound (it only covers the quantitative ones; rates are always %).
System Info panel
Section titled “System Info panel”- App Version and App Build Time now populate correctly on the deployed app — previously they appeared blank because of a build-arg name mismatch in the container image.
- Both the app and API Build Time values render in the format configured under Settings → Formatting, honouring both Date Format and Time Format preferences.
- A “View release notes” link next to the App Version opens this page in a new tab.
Scheme sector change with cascade confirmation
Section titled “Scheme sector change with cascade confirmation”Changing a scheme’s sector (Building Type) is allowed again even when child Occupier Events or Development Uses have a Building Use set. A confirmation dialog reports how many rows will be affected and — on confirm — clears the Building Use only on the rows that are incompatible with the new sector, leaving compatible ones untouched. You then re-pick the Building Use on each affected row.
Occupier Scheme picker on deployed environments
Section titled “Occupier Scheme picker on deployed environments”The same-address scheme picker on Occupier Events now loads correctly on the deployed dev environment. The underlying OData query was being truncated by the ingress layer, which made the picker fail to render.
Entity pickers now use the same search as the list pages
Section titled “Entity pickers now use the same search as the list pages”Linking a Portfolio, Company, Address, Scheme, Investment Event, or Occupier Event from a flyout now searches the same full-text-indexed endpoint used by the corresponding list page. Results match what you see when searching the list view for the same query.
Scheme Statistics grid
Section titled “Scheme Statistics grid”Schemes now carry an Excel-like Statistics grid with quarter rows, dynamic columns, duplicate detection, and currency conversion across Local / EUR / GBP / USD. Includes a compound Vacancy & Occupancy statistic where any two of the five components (Total Size, Occupancy, Vacancy, Occupancy Rate, Vacancy Rate) auto-derive the other three.
Multi-block duplicate detection
Section titled “Multi-block duplicate detection”Address, Company, and Scheme duplicate detection now scans multiple candidate blocks per entity, catching near-duplicates that a single-block scan would miss.
List-page search improvements
Section titled “List-page search improvements”- List-page search now ranks exact phrase matches above partial matches and is case-insensitive across the board.
- Portfolio search migrated to the same consolidated search stack as the other list pages.
- Results are sorted by a date tiebreak (newest first) so ties feel intuitive.
Occupier picker shows more context
Section titled “Occupier picker shows more context”The same-address scheme picker on Occupier Events now shows sector, development type, and year alongside the scheme name, so sibling schemes with identical names are easier to tell apart.
Size + currency display consistency
Section titled “Size + currency display consistency”- Measurement and currency display consolidated into a shared set of
components, so every
sqm/sqft/psf/psmlabel across the app reads the same and reacts to your preferences. - Detail cards now show sizes in full rather than truncating.
- Occupier sizes validate against the Local value only; size triplets self-heal when Local is missing but Net/Gross are present.
- Portfolio deal-summary totals row is now correct.
- Investment Event currency validation no longer shows a spurious “field required” error.
For the full commit history behind any release, see
main on GitHub.