> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.hellodata.ai/llms.txt.

# Common Calculations

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.

> **Info**
>
> All examples assume you have a `PropertyDetailsResponse` object from `GET /property/{id}`. See the [Property Details](/docs/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.

#### TypeScript

```typescript
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);
```

#### Python

```python
from typing import Optional

def get_average_effective_rent(property_data: dict) -> Optional[float]:
    """
    Calculate average effective rent from property data.
    
    Args:
        property_data: PropertyDetailsResponse from GET /property/{id}
    
    Returns:
        Average effective rent or None if no valid units found
    """
    units = property_data.get("building_availability", [])
    
    # Check if we have actual units (non-floorplans)
    has_actual_units = any(not unit.get("is_floorplan", False) for unit in units)
    
    # Filter units: skip floorplans if actual units exist
    valid_units = [
        unit for unit in units
        if not (has_actual_units and unit.get("is_floorplan", False))
    ]
    
    # Extract effective prices (use effective_price or fallback to min_effective_price)
    effective_prices = [
        unit.get("effective_price") or unit.get("min_effective_price")
        for unit in valid_units
        if unit.get("effective_price") is not None or unit.get("min_effective_price") is not None
    ]
    
    if not effective_prices:
        return None
    
    # Calculate average
    return sum(effective_prices) / len(effective_prices)

# Usage:
# import requests
# response = requests.get(f"https://api.hellodata.ai/property/{property_id}", 
#                        headers={"x-api-key": API_KEY})
# property_data = response.json()
# avg_effective_rent = get_average_effective_rent(property_data)
```

---

## 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.

#### TypeScript

```typescript
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;
}
```

#### Python

```python
def get_average_asking_rent(property_data: dict) -> Optional[float]:
    """Calculate average asking rent from property data."""
    units = property_data.get("building_availability", [])
    
    has_actual_units = any(not unit.get("is_floorplan", False) for unit in units)
    
    valid_units = [
        unit for unit in units
        if not (has_actual_units and unit.get("is_floorplan", False))
    ]
    
    # Extract asking prices (use price or fallback to min_price)
    asking_prices = [
        unit.get("price") or unit.get("min_price")
        for unit in valid_units
        if unit.get("price") is not None or unit.get("min_price") is not None
    ]
    
    if not asking_prices:
        return None
    
    return sum(asking_prices) / len(asking_prices)
```

---

## 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.

#### TypeScript

```typescript
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;
}
```

#### Python

```python
def get_average_sqft(property_data: dict) -> Optional[float]:
    """Calculate average square footage from property data."""
    units = property_data.get("building_availability", [])
    
    has_actual_units = any(not unit.get("is_floorplan", False) for unit in units)
    
    valid_units = [
        unit for unit in units
        if not (has_actual_units and unit.get("is_floorplan", False))
    ]
    
    # Extract square footages (use sqft or fallback to min_sqft)
    sqfts = [
        unit.get("sqft") or unit.get("min_sqft")
        for unit in valid_units
        if unit.get("sqft") is not None or unit.get("min_sqft") is not None
    ]
    
    if not sqfts:
        return None
    
    return sum(sqfts) / len(sqfts)
```

---

## 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.

#### TypeScript

```typescript
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;
}
```

#### Python

```python
def get_average_effective_psf(property_data: dict) -> Optional[float]:
    """
    Calculate average effective PSF using weighted average.
    
    This uses sum(prices) / sum(sqfts), NOT average(price/sqft).
    """
    units = property_data.get("building_availability", [])
    
    has_actual_units = any(not unit.get("is_floorplan", False) for unit in units)
    
    valid_units = [
        unit for unit in units
        if not (has_actual_units and unit.get("is_floorplan", False))
    ]
    
    # Collect price and sqft pairs (both must be non-null)
    price_sqft_pairs = []
    for unit in valid_units:
        price = unit.get("effective_price") or unit.get("min_effective_price")
        sqft = unit.get("sqft") or unit.get("min_sqft")
        
        if price is not None and sqft is not None:
            price_sqft_pairs.append({"price": price, "sqft": sqft})
    
    if not price_sqft_pairs:
        return None
    
    # Weighted average: sum of prices / sum of sqfts
    total_price = sum(p["price"] for p in price_sqft_pairs)
    total_sqft = sum(p["sqft"] for p in price_sqft_pairs)
    
    return total_price / total_sqft
```

---

## Average Asking Price Per Square Foot (PSF)

Same as effective PSF, but using asking prices instead.

**Formula**: `sum(all_asking_prices) / sum(all_sqfts)`

#### TypeScript

```typescript
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;
}
```

#### Python

```python
def get_average_asking_psf(property_data: dict) -> Optional[float]:
    """Calculate average asking PSF using weighted average."""
    units = property_data.get("building_availability", [])
    
    has_actual_units = any(not unit.get("is_floorplan", False) for unit in units)
    
    valid_units = [
        unit for unit in units
        if not (has_actual_units and unit.get("is_floorplan", False))
    ]
    
    # Collect price and sqft pairs (both must be non-null)
    price_sqft_pairs = []
    for unit in valid_units:
        price = unit.get("price") or unit.get("min_price")
        sqft = unit.get("sqft") or unit.get("min_sqft")
        
        if price is not None and sqft is not None:
            price_sqft_pairs.append({"price": price, "sqft": sqft})
    
    if not price_sqft_pairs:
        return None
    
    # Weighted average: sum of prices / sum of sqfts
    total_price = sum(p["price"] for p in price_sqft_pairs)
    total_sqft = sum(p["sqft"] for p in price_sqft_pairs)
    
    return total_price / total_sqft
```

---

## 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.

#### TypeScript

```typescript
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;
}
```

#### Python

```python
def get_average_concession(property_data: dict) -> Optional[float]:
    """Calculate average concession amount (asking - effective rent)."""
    units = property_data.get("building_availability", [])
    
    has_actual_units = any(not unit.get("is_floorplan", False) for unit in units)
    
    valid_units = [
        unit for unit in units
        if not (has_actual_units and unit.get("is_floorplan", False))
    ]
    
    # Calculate concession for each unit (asking - effective)
    concessions = []
    for unit in valid_units:
        asking_price = unit.get("price") or unit.get("min_price")
        effective_price = unit.get("effective_price") or unit.get("min_effective_price")
        
        if asking_price is not None and effective_price is not None:
            concessions.append(asking_price - effective_price)
    
    if not concessions:
        return None
    
    return sum(concessions) / len(concessions)
```

---

## 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`

> **Warning**
>
> 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.

#### TypeScript

```typescript
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;
}
```

#### Python

```python
from datetime import datetime

def get_occupancy_rate(property_data: dict, as_of_date: str) -> Optional[float]:
    """
    Calculate occupancy rate for a specific date.
    
    Args:
        property_data: PropertyDetailsResponse from GET /property/{id}
        as_of_date: ISO date string like "2024-01-15"
    
    Returns:
        Occupancy percentage (0-100) or None if invalid
    """
    number_units = property_data.get("number_units")
    if not number_units or number_units == 0:
        return None
    
    units = property_data.get("building_availability", [])
    
    has_actual_units = any(not unit.get("is_floorplan", False) for unit in units)
    
    # Count units that are currently available (not leased)
    available_units = 0
    
    for unit in units:
        # Skip floorplans if actual units exist
        if has_actual_units and unit.get("is_floorplan", False):
            continue
        
        # Check if unit has an active availability period on this date
        availability_periods = unit.get("availability_periods", [])
        has_active_period = False
        
        for period in availability_periods:
            enter_market = period.get("enter_market")
            exit_market = period.get("exit_market")
            
            # Unit is active if:
            # - enter_market is null or <= as_of_date
            # - exit_market is null or >= as_of_date
            entered = not enter_market or enter_market <= as_of_date
            not_exited = not exit_market or exit_market >= as_of_date
            
            if entered and not_exited:
                has_active_period = True
                break
        
        if has_active_period:
            available_units += 1
    
    occupied_units = number_units - available_units
    return (occupied_units / number_units) * 100
```

---

## Complete Example

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

#### TypeScript

```typescript
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,
  };
}
```

#### Python

```python
from typing import Optional, Dict, Any
from datetime import datetime

def calculate_all_metrics(
    property_data: dict, as_of_date: Optional[str] = None
) -> Dict[str, Optional[float]]:
    """Calculate all common metrics from property data."""
    return {
        "average_effective_rent": get_average_effective_rent(property_data),
        "average_asking_rent": get_average_asking_rent(property_data),
        "average_sqft": get_average_sqft(property_data),
        "average_effective_psf": get_average_effective_psf(property_data),
        "average_asking_psf": get_average_asking_psf(property_data),
        "average_concession": get_average_concession(property_data),
        "occupancy_rate": (
            get_occupancy_rate(property_data, as_of_date)
            if as_of_date
            else None
        ),
    }
```

---

## 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.

> **Info**
>
> 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.