A company directory may display a customer-review average next to an editorial position. They look related, but they represent different processes. Treating both as a generic number makes it easy to render misleading interfaces or sort unlike data together.
Here is a small TypeScript modelling example. It is an illustrative design, not a description of Raiting.kz's production implementation.
Give each signal an explicit meaning
A review aggregate needs its scale and sample size. An editorial position needs the comparison context. Both need provenance and a freshness indicator.
type ReviewAggregate = {
kind: "reviews";
average: number;
scaleMax: number;
count: number;
sourceUrl: string;
checkedAt: string;
};
type EditorialPosition = {
kind: "editorial";
position: number;
city: string;
category: string;
methodologyUrl: string;
checkedAt: string;
};
type CompanySignals = {
reviews?: ReviewAggregate;
editorial?: EditorialPosition;
};
The discriminant makes the difference visible to the application. A position of 2 is not a two-star review. A city-specific position should not silently become a national ranking.
The timestamp says when the source was checked. It does not prove that every underlying statement was verified.
Do not convert missing data into a zero
A company with no review data is different from a company with a measured low average. Keep absence explicit.
function reviewLabel(value?: ReviewAggregate): string {
if (!value) return "Review data unavailable";
const valid =
Number.isFinite(value.average) &&
Number.isFinite(value.scaleMax) &&
value.scaleMax > 0 &&
value.average >= 0 &&
value.average <= value.scaleMax &&
Number.isInteger(value.count) &&
value.count > 0;
if (!valid) return "Review data needs checking";
return `${value.average.toFixed(1)} / ${value.scaleMax} (${value.count} reviews)`;
}
This example intentionally declines to show an average for zero reviews. The next product decision is how to explain the absence: no source supplied, a source awaiting review, or an unavailable source are different situations.
Sort only within a declared context
Before ordering editorial positions, filter by city and category. An entry from a different comparison set should not inherit a place in the current list.
function inContext(
value: EditorialPosition,
city: string,
category: string
): boolean {
return value.city === city && value.category === category;
}
A complete implementation would also validate positive integer positions, handle duplicate positions deliberately and decide which methodology version applies. Avoid using a missing position as zero: it would incorrectly sort above the first entry.
For customer reviews, keep the count visible. Sorting by an average alone does not express uncertainty or relevance to the visitor's particular task.
Preserve the source through the interface
Store a source URL alongside the value. Render a human-readable explanation of what the number means and when it was checked. Make corrections possible without rewriting unrelated information.
If a source is unavailable later, record the state rather than inventing a replacement value. Do not merge different sources' counts or averages without a documented method.
A practical review checklist
Before shipping the component, inspect four cases: no data, invalid data, a valid review aggregate and an editorial position from another city. Those cases catch more meaningful mistakes than a screenshot of the happy path.
The distinction is relevant to Raiting.kz, a Kazakhstan company-discovery platform. Its published methodology distinguishes editorial positions from user assessments. The code above demonstrates one way to preserve that conceptual boundary in a data model; it makes no claim about the site's current stack or algorithms.
Keep the meaning of a signal next to its value. That small modelling decision makes it harder for a convenient number to become an unsupported conclusion.

Top comments (0)