Common Calculations

How to compute metrics from property data

The HelloData API returns raw property data, giving you the freedom to aggregate and analyze it according to your needs. This guide shows you how to compute common metrics like average effective rent, occupancy, and price per square foot (PSF) from the /property/{id} response.

All examples assume you have a PropertyDetailsResponse object from GET /property/{id}. See the Property Details guide for how to fetch this data.

Important: Filtering Units

Before computing any metrics, you need to filter units correctly:

  • Skip floorplans when actual units exist: If a property has both floorplans (is_floorplan: true) and actual units, only use the actual units for calculations.
  • Handle null values: Filter out units with null values before averaging.
  • Use current vs historical data: The examples below use the top-level fields (price, effective_price, sqft) which represent the latest values. For historical calculations, use the history array.

Average Effective Rent

Average effective rent is the mean of all unit effective rents (price after discounts/concessions).

Formula: Average of effective_price or min_effective_price (if effective_price is null) for all valid units.

interface Availability {
effective_price: number | null;
min_effective_price: number | null;
is_floorplan: boolean;
// ... other fields
}
interface PropertyDetailsResponse {
building_availability: Availability[];
// ... other fields
}
function getAverageEffectiveRent(property: PropertyDetailsResponse): number | null {
// Check if we have actual units (non-floorplans)
const hasActualUnits = property.building_availability.some(
(unit) => !unit.is_floorplan
);
// Filter units: skip floorplans if actual units exist
const validUnits = property.building_availability.filter((unit) => {
if (hasActualUnits && unit.is_floorplan) {
return false;
}
return true;
});
// Extract effective prices (use effective_price or fallback to min_effective_price)
const effectivePrices = validUnits
.map((unit) => unit.effective_price ?? unit.min_effective_price)
.filter((price): price is number => price !== null);
if (effectivePrices.length === 0) {
return null;
}
// Calculate average
const sum = effectivePrices.reduce((acc, price) => acc + price, 0);
return sum / effectivePrices.length;
}
// Usage:
// const property = await fetchProperty(id);
// const avgEffectiveRent = getAverageEffectiveRent(property);

Average Asking Rent

Average asking rent is the mean of all unit asking rents (advertised price before discounts).

Formula: Average of price or min_price (if price is null) for all valid units.

function getAverageAskingRent(property: PropertyDetailsResponse): number | null {
const hasActualUnits = property.building_availability.some(
(unit) => !unit.is_floorplan
);
const validUnits = property.building_availability.filter((unit) => {
if (hasActualUnits && unit.is_floorplan) {
return false;
}
return true;
});
// Extract asking prices (use price or fallback to min_price)
const askingPrices = validUnits
.map((unit) => unit.price ?? unit.min_price)
.filter((price): price is number => price !== null);
if (askingPrices.length === 0) {
return null;
}
const sum = askingPrices.reduce((acc, price) => acc + price, 0);
return sum / askingPrices.length;
}

Average Square Footage

Average square footage is the mean of all unit sizes.

Formula: Average of sqft or min_sqft (if sqft is null) for all valid units.

function getAverageSqft(property: PropertyDetailsResponse): number | null {
const hasActualUnits = property.building_availability.some(
(unit) => !unit.is_floorplan
);
const validUnits = property.building_availability.filter((unit) => {
if (hasActualUnits && unit.is_floorplan) {
return false;
}
return true;
});
// Extract square footages (use sqft or fallback to min_sqft)
const sqfts = validUnits
.map((unit) => unit.sqft ?? unit.min_sqft)
.filter((sqft): sqft is number => sqft !== null);
if (sqfts.length === 0) {
return null;
}
const sum = sqfts.reduce((acc, sqft) => acc + sqft, 0);
return sum / sqfts.length;
}

Average Effective Price Per Square Foot (PSF)

Important: PSF must be calculated as a weighted average, not as the average of individual unit PSF values.

Formula: sum(all_effective_prices) / sum(all_sqfts)

This gives you the true average PSF, accounting for unit size differences. Calculating average(price/sqft) would incorrectly weight all units equally regardless of size.

function getAverageEffectivePsf(property: PropertyDetailsResponse): number | null {
const hasActualUnits = property.building_availability.some(
(unit) => !unit.is_floorplan
);
const validUnits = property.building_availability.filter((unit) => {
if (hasActualUnits && unit.is_floorplan) {
return false;
}
return true;
});
// Collect price and sqft pairs (both must be non-null)
const priceSqftPairs: { price: number; sqft: number }[] = [];
for (const unit of validUnits) {
const price = unit.effective_price ?? unit.min_effective_price;
const sqft = unit.sqft ?? unit.min_sqft;
if (price !== null && sqft !== null) {
priceSqftPairs.push({ price, sqft });
}
}
if (priceSqftPairs.length === 0) {
return null;
}
// Weighted average: sum of prices / sum of sqfts
const totalPrice = priceSqftPairs.reduce((sum, p) => sum + p.price, 0);
const totalSqft = priceSqftPairs.reduce((sum, p) => sum + p.sqft, 0);
return totalPrice / totalSqft;
}

Average Asking Price Per Square Foot (PSF)

Same as effective PSF, but using asking prices instead.

Formula: sum(all_asking_prices) / sum(all_sqfts)

function getAverageAskingPsf(property: PropertyDetailsResponse): number | null {
const hasActualUnits = property.building_availability.some(
(unit) => !unit.is_floorplan
);
const validUnits = property.building_availability.filter((unit) => {
if (hasActualUnits && unit.is_floorplan) {
return false;
}
return true;
});
// Collect price and sqft pairs (both must be non-null)
const priceSqftPairs: { price: number; sqft: number }[] = [];
for (const unit of validUnits) {
const price = unit.price ?? unit.min_price;
const sqft = unit.sqft ?? unit.min_sqft;
if (price !== null && sqft !== null) {
priceSqftPairs.push({ price, sqft });
}
}
if (priceSqftPairs.length === 0) {
return null;
}
// Weighted average: sum of prices / sum of sqfts
const totalPrice = priceSqftPairs.reduce((sum, p) => sum + p.price, 0);
const totalSqft = priceSqftPairs.reduce((sum, p) => sum + p.sqft, 0);
return totalPrice / totalSqft;
}

Average Concession Amount

Concessions are discounts or promotions that reduce the effective rent below the asking rent.

Formula: Average of (asking_price - effective_price) for all units with both values.

function getAverageConcession(property: PropertyDetailsResponse): number | null {
const hasActualUnits = property.building_availability.some(
(unit) => !unit.is_floorplan
);
const validUnits = property.building_availability.filter((unit) => {
if (hasActualUnits && unit.is_floorplan) {
return false;
}
return true;
});
// Calculate concession for each unit (asking - effective)
const concessions: number[] = [];
for (const unit of validUnits) {
const askingPrice = unit.price ?? unit.min_price;
const effectivePrice = unit.effective_price ?? unit.min_effective_price;
if (askingPrice !== null && effectivePrice !== null) {
concessions.push(askingPrice - effectivePrice);
}
}
if (concessions.length === 0) {
return null;
}
const sum = concessions.reduce((acc, concession) => acc + concession, 0);
return sum / concessions.length;
}

Occupancy Rate (Simplified)

Occupancy represents the percentage of units that are leased (not available for rent). This is a simplified calculation for a specific date. For time-series occupancy data, you would need to analyze availability_periods over time.

Formula: (total_units - available_units) / total_units * 100

This simplified calculation assumes number_units from the property represents the total unit count. For lease-up properties or more accurate calculations, you may need to use the occupancyOverTime function logic which considers when units entered/exited the market.

function getOccupancyRate(
property: PropertyDetailsResponse,
asOfDate: string // ISO date string like "2024-01-15"
): number | null {
if (!property.number_units || property.number_units === 0) {
return null;
}
const hasActualUnits = property.building_availability.some(
(unit) => !unit.is_floorplan
);
// Count units that are currently available (not leased)
let availableUnits = 0;
for (const unit of property.building_availability) {
// Skip floorplans if actual units exist
if (hasActualUnits && unit.is_floorplan) {
continue;
}
// Check if unit has an active availability period on this date
const hasActivePeriod = (unit.availability_periods || []).some((period) => {
const enterMarket = period.enter_market;
const exitMarket = period.exit_market;
// Unit is active if:
// - enter_market is null or <= asOfDate
// - exit_market is null or >= asOfDate
const entered = !enterMarket || enterMarket <= asOfDate;
const notExited = !exitMarket || exitMarket >= asOfDate;
return entered && notExited;
});
if (hasActivePeriod) {
availableUnits++;
}
}
const occupiedUnits = property.number_units - availableUnits;
return (occupiedUnits / property.number_units) * 100;
}

Complete Example

Here’s a complete example that computes all metrics at once:

interface PropertyMetrics {
averageEffectiveRent: number | null;
averageAskingRent: number | null;
averageSqft: number | null;
averageEffectivePsf: number | null;
averageAskingPsf: number | null;
averageConcession: number | null;
occupancyRate: number | null;
}
function calculateAllMetrics(
property: PropertyDetailsResponse,
asOfDate?: string
): PropertyMetrics {
return {
averageEffectiveRent: getAverageEffectiveRent(property),
averageAskingRent: getAverageAskingRent(property),
averageSqft: getAverageSqft(property),
averageEffectivePsf: getAverageEffectivePsf(property),
averageAskingPsf: getAverageAskingPsf(property),
averageConcession: getAverageConcession(property),
occupancyRate: asOfDate ? getOccupancyRate(property, asOfDate) : null,
};
}

Key Takeaways

  1. Always filter floorplans: When a property has both floorplans and actual units, use only the actual units for calculations.

  2. Handle null values: Filter out null values before computing averages. Use fallback values (min_price, min_effective_price, min_sqft) when the primary field is null.

  3. PSF is weighted: Always calculate PSF as sum(prices) / sum(sqfts), not average(price/sqft). This accounts for unit size differences.

  4. Use top-level fields for current values: The price, effective_price, and sqft fields represent the latest values. For historical analysis, use the history array.

  5. Occupancy is complex: The simplified occupancy calculation above works for a single date. For accurate time-series occupancy, you need to analyze availability_periods over time, considering when units enter/exit the market.

These calculations match what you see on the HelloData platform. If your results differ, check that you’re filtering units correctly and handling null values as shown above.