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.

1interface Availability {
2 effective_price: number | null;
3 min_effective_price: number | null;
4 is_floorplan: boolean;
5 // ... other fields
6}
7
8interface PropertyDetailsResponse {
9 building_availability: Availability[];
10 // ... other fields
11}
12
13function getAverageEffectiveRent(property: PropertyDetailsResponse): number | null {
14 // Check if we have actual units (non-floorplans)
15 const hasActualUnits = property.building_availability.some(
16 (unit) => !unit.is_floorplan
17 );
18
19 // Filter units: skip floorplans if actual units exist
20 const validUnits = property.building_availability.filter((unit) => {
21 if (hasActualUnits && unit.is_floorplan) {
22 return false;
23 }
24 return true;
25 });
26
27 // Extract effective prices (use effective_price or fallback to min_effective_price)
28 const effectivePrices = validUnits
29 .map((unit) => unit.effective_price ?? unit.min_effective_price)
30 .filter((price): price is number => price !== null);
31
32 if (effectivePrices.length === 0) {
33 return null;
34 }
35
36 // Calculate average
37 const sum = effectivePrices.reduce((acc, price) => acc + price, 0);
38 return sum / effectivePrices.length;
39}
40
41// Usage:
42// const property = await fetchProperty(id);
43// 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.

1function getAverageAskingRent(property: PropertyDetailsResponse): number | null {
2 const hasActualUnits = property.building_availability.some(
3 (unit) => !unit.is_floorplan
4 );
5
6 const validUnits = property.building_availability.filter((unit) => {
7 if (hasActualUnits && unit.is_floorplan) {
8 return false;
9 }
10 return true;
11 });
12
13 // Extract asking prices (use price or fallback to min_price)
14 const askingPrices = validUnits
15 .map((unit) => unit.price ?? unit.min_price)
16 .filter((price): price is number => price !== null);
17
18 if (askingPrices.length === 0) {
19 return null;
20 }
21
22 const sum = askingPrices.reduce((acc, price) => acc + price, 0);
23 return sum / askingPrices.length;
24}

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.

1function getAverageSqft(property: PropertyDetailsResponse): number | null {
2 const hasActualUnits = property.building_availability.some(
3 (unit) => !unit.is_floorplan
4 );
5
6 const validUnits = property.building_availability.filter((unit) => {
7 if (hasActualUnits && unit.is_floorplan) {
8 return false;
9 }
10 return true;
11 });
12
13 // Extract square footages (use sqft or fallback to min_sqft)
14 const sqfts = validUnits
15 .map((unit) => unit.sqft ?? unit.min_sqft)
16 .filter((sqft): sqft is number => sqft !== null);
17
18 if (sqfts.length === 0) {
19 return null;
20 }
21
22 const sum = sqfts.reduce((acc, sqft) => acc + sqft, 0);
23 return sum / sqfts.length;
24}

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.

1function getAverageEffectivePsf(property: PropertyDetailsResponse): number | null {
2 const hasActualUnits = property.building_availability.some(
3 (unit) => !unit.is_floorplan
4 );
5
6 const validUnits = property.building_availability.filter((unit) => {
7 if (hasActualUnits && unit.is_floorplan) {
8 return false;
9 }
10 return true;
11 });
12
13 // Collect price and sqft pairs (both must be non-null)
14 const priceSqftPairs: { price: number; sqft: number }[] = [];
15
16 for (const unit of validUnits) {
17 const price = unit.effective_price ?? unit.min_effective_price;
18 const sqft = unit.sqft ?? unit.min_sqft;
19
20 if (price !== null && sqft !== null) {
21 priceSqftPairs.push({ price, sqft });
22 }
23 }
24
25 if (priceSqftPairs.length === 0) {
26 return null;
27 }
28
29 // Weighted average: sum of prices / sum of sqfts
30 const totalPrice = priceSqftPairs.reduce((sum, p) => sum + p.price, 0);
31 const totalSqft = priceSqftPairs.reduce((sum, p) => sum + p.sqft, 0);
32
33 return totalPrice / totalSqft;
34}

Average Asking Price Per Square Foot (PSF)

Same as effective PSF, but using asking prices instead.

Formula: sum(all_asking_prices) / sum(all_sqfts)

1function getAverageAskingPsf(property: PropertyDetailsResponse): number | null {
2 const hasActualUnits = property.building_availability.some(
3 (unit) => !unit.is_floorplan
4 );
5
6 const validUnits = property.building_availability.filter((unit) => {
7 if (hasActualUnits && unit.is_floorplan) {
8 return false;
9 }
10 return true;
11 });
12
13 // Collect price and sqft pairs (both must be non-null)
14 const priceSqftPairs: { price: number; sqft: number }[] = [];
15
16 for (const unit of validUnits) {
17 const price = unit.price ?? unit.min_price;
18 const sqft = unit.sqft ?? unit.min_sqft;
19
20 if (price !== null && sqft !== null) {
21 priceSqftPairs.push({ price, sqft });
22 }
23 }
24
25 if (priceSqftPairs.length === 0) {
26 return null;
27 }
28
29 // Weighted average: sum of prices / sum of sqfts
30 const totalPrice = priceSqftPairs.reduce((sum, p) => sum + p.price, 0);
31 const totalSqft = priceSqftPairs.reduce((sum, p) => sum + p.sqft, 0);
32
33 return totalPrice / totalSqft;
34}

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.

1function getAverageConcession(property: PropertyDetailsResponse): number | null {
2 const hasActualUnits = property.building_availability.some(
3 (unit) => !unit.is_floorplan
4 );
5
6 const validUnits = property.building_availability.filter((unit) => {
7 if (hasActualUnits && unit.is_floorplan) {
8 return false;
9 }
10 return true;
11 });
12
13 // Calculate concession for each unit (asking - effective)
14 const concessions: number[] = [];
15
16 for (const unit of validUnits) {
17 const askingPrice = unit.price ?? unit.min_price;
18 const effectivePrice = unit.effective_price ?? unit.min_effective_price;
19
20 if (askingPrice !== null && effectivePrice !== null) {
21 concessions.push(askingPrice - effectivePrice);
22 }
23 }
24
25 if (concessions.length === 0) {
26 return null;
27 }
28
29 const sum = concessions.reduce((acc, concession) => acc + concession, 0);
30 return sum / concessions.length;
31}

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.

1function getOccupancyRate(
2 property: PropertyDetailsResponse,
3 asOfDate: string // ISO date string like "2024-01-15"
4): number | null {
5 if (!property.number_units || property.number_units === 0) {
6 return null;
7 }
8
9 const hasActualUnits = property.building_availability.some(
10 (unit) => !unit.is_floorplan
11 );
12
13 // Count units that are currently available (not leased)
14 let availableUnits = 0;
15
16 for (const unit of property.building_availability) {
17 // Skip floorplans if actual units exist
18 if (hasActualUnits && unit.is_floorplan) {
19 continue;
20 }
21
22 // Check if unit has an active availability period on this date
23 const hasActivePeriod = (unit.availability_periods || []).some((period) => {
24 const enterMarket = period.enter_market;
25 const exitMarket = period.exit_market;
26
27 // Unit is active if:
28 // - enter_market is null or <= asOfDate
29 // - exit_market is null or >= asOfDate
30 const entered = !enterMarket || enterMarket <= asOfDate;
31 const notExited = !exitMarket || exitMarket >= asOfDate;
32
33 return entered && notExited;
34 });
35
36 if (hasActivePeriod) {
37 availableUnits++;
38 }
39 }
40
41 const occupiedUnits = property.number_units - availableUnits;
42 return (occupiedUnits / property.number_units) * 100;
43}

Complete Example

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

1interface PropertyMetrics {
2 averageEffectiveRent: number | null;
3 averageAskingRent: number | null;
4 averageSqft: number | null;
5 averageEffectivePsf: number | null;
6 averageAskingPsf: number | null;
7 averageConcession: number | null;
8 occupancyRate: number | null;
9}
10
11function calculateAllMetrics(
12 property: PropertyDetailsResponse,
13 asOfDate?: string
14): PropertyMetrics {
15 return {
16 averageEffectiveRent: getAverageEffectiveRent(property),
17 averageAskingRent: getAverageAskingRent(property),
18 averageSqft: getAverageSqft(property),
19 averageEffectivePsf: getAverageEffectivePsf(property),
20 averageAskingPsf: getAverageAskingPsf(property),
21 averageConcession: getAverageConcession(property),
22 occupancyRate: asOfDate ? getOccupancyRate(property, asOfDate) : null,
23 };
24}

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.