Completeness score
Overview
Section titled “Overview”Every primary record in Formation — address, scheme, development, company, investment event, occupier event, portfolio — carries a Completeness Score between 0 and 100. It’s a single number that summarises how thoroughly the record has been filled in: a freshly-created record with only the required fields scores low; a record with every meaningful field populated, verified, and supported by related records scores near 100.
The score appears on every list-page row and on every detail-page header so you can see at a glance which records are well-covered and which need more attention.
How the score is built
Section titled “How the score is built”The score is a weighted average over a set of scored items on the record. The two kinds of scored item are:
- Scored properties — individual fields. Each marked field contributes its weight when populated, 0 when blank.
- Scored collections — child collections. A collection contributes its weight when it has at least the minimum number of items.
Both kinds carry an optional weight that defaults to 1.0. Heavier weights matter more; a weight of 2 on a field means filling that field moves the score twice as much as filling a default-weight field.
The maximum possible weight is the sum of every scored item’s weight. The actual weight is the sum of every populated item’s weight. The score is the second divided by the first, scaled to 0-100.
What “populated” means
Section titled “What “populated” means”For most field types, populated means not null and not empty:
- A string field counts as populated if it has at least one non-whitespace character.
- A number field counts as populated if it has a non-null value (zero counts as a value).
- A lookup field counts as populated if it points at a row in the linked table.
- A flag like Is Verified counts as populated when set to true.
For collections, populated means at least the minimum count (default: 1). So a scheme’s Developments collection is “populated” when it contains at least one development.
Conditional scoring
Section titled “Conditional scoring”Some properties are scored only when another property is set a particular way. For example, a Rent Free field is only scored on occupier events that have a Lease Length — there’s no point checking for a rent-free figure on a Surrender event.
The conditional rules supported on a scored property are:
- When populated — only count the property if some other named property is filled in.
- When not populated — the inverse: only count when the named property is blank.
- When equals — only count when the named property has a specific value (supports a list of values).
- When not equals — the inverse.
- When starts with / not starts with — useful for hierarchical codes (e.g. “F” matches “F”, “F.01”, “F.02”).
- When matches / not matches — for regular-expression-shaped rules.
These conditions are why the score on a record can be 90% with a field blank: the blank field wasn’t being counted in the first place because some other field was set a way that excludes it.
Excluded items on collections
Section titled “Excluded items on collections”Scored collections can exclude items from the count by a property + value match. The canonical example is on Scheme.Companies:
“At least one company required, with Operator roles excluded — unless the scheme’s Building Type is Hotel, in which case Operator counts.”
The exclusion has two halves:
- Item exclude — by default, items in the collection whose role is Operator don’t count.
- Unless the parent says otherwise — if the parent record’s building type is Hotel, the exclusion is waived and operators count again.
This is why a hotel scheme with only an operator listed shows the same completeness score for its Companies collection as a non-hotel scheme with a developer listed: the rule adapts to the parent’s building type.
Child completeness
Section titled “Child completeness”A scored collection can opt into including child completeness in its own contribution. When that’s set:
- The collection contributes a fraction of its weight equal to the average completeness of its children.
- A collection of three developments with average completeness 0.6 contributes 0.6 × weight, rather than the full weight just because items exist.
This makes a parent’s score reflect not just “do I have any developments?” but “are my developments themselves well-filled?”.
When the score recalculates
Section titled “When the score recalculates”Two paths recalculate a score:
After every save (cascading)
Section titled “After every save (cascading)”Saving a record triggers a recalculation for that record and for any parent records that aggregate from it. Saving a development recalculates the parent scheme; saving an occupier event recalculates the scheme; saving an address recalculates… well, it doesn’t cascade upwards (addresses don’t have a parent), but the address’s own score updates.
Nightly job (defensive)
Section titled “Nightly job (defensive)”A background job runs every night that recalculates every record whose score is older than 7 days. This catches edge cases — records whose scoring conditions involve a sibling record’s state that changed without triggering a cascade, or schedule changes that affect “Date is in the past” rules.
The job can be forced to recalculate everything by an Admin via the JobTrigger UI; in normal operation it only touches records that haven’t been scored recently.
What the score means for a user
Section titled “What the score means for a user”It’s a signal, not a gate. A low completeness score doesn’t stop a record being saved, used, or referenced — it tells you the record needs more attention. Specific uses:
- List pages — sort by Completeness to prioritise records that need filling out.
- Filtering — set a minimum completeness in your saved search to focus on well-covered records.
- Cards on detail pages — the score badge next to each related record indicates which sub-records are stubs vs filled in.
Related rules
Section titled “Related rules”- Every entity page references the score in After save and lists which fields feed into it.
- Verified flag — verification is itself a scored property on most entities.
Where this lives
Section titled “Where this lives”- Scored-property attribute:
src/common/models/Attributes/ScoredPropertyAttribute.cs - Scored-collection attribute:
src/common/models/Attributes/ScoredCollectionAttribute.cs - Calculator:
src/common/models/Services/CompletenessScore/CompletenessScoreCalculator.cs - Nightly job:
src/services/job/completionscore/job.completionscore/CompletionScoreWorker.cs - Per-entity attribute placement: any model file under
src/common/models/Models/with[ScoredProperty]or[ScoredCollection]annotations