Implementation of phase 4.1

This commit is contained in:
Nitzan Pomerantz
2025-10-26 10:58:46 +02:00
parent 4cb70c547b
commit 0ac9d136cd
13 changed files with 2115 additions and 373 deletions
+76 -35
View File
@@ -5,18 +5,21 @@ This module provides pure mathematical functions for analyzing real estate deal
"""
from collections import Counter
from typing import Any, Dict, List
from typing import List
from datetime import datetime
from .models import Deal, DealStatistics
def calculate_deal_statistics(deals: List[Dict[str, Any]]) -> Dict[str, Any]:
def calculate_deal_statistics(deals: List[Deal]) -> DealStatistics:
"""
Calculate statistical aggregations on deal data.
Args:
deals: List of deal dictionaries
deals: List of Deal model instances
Returns:
Dictionary with statistical metrics
DealStatistics model with comprehensive metrics
Raises:
ValueError: If deals is not a valid list
@@ -25,46 +28,52 @@ def calculate_deal_statistics(deals: List[Dict[str, Any]]) -> Dict[str, Any]:
raise ValueError("deals must be a list")
if not deals:
return {
"count": 0,
"price_stats": {},
"area_stats": {},
"price_per_sqm_stats": {},
"room_distribution": {},
}
return DealStatistics(
total_deals=0,
price_statistics={},
area_statistics={},
price_per_sqm_statistics={},
property_type_distribution={},
date_range=None,
)
# Extract numeric values
prices = []
areas = []
price_per_sqm_values = []
rooms = []
property_types = []
deal_dates = []
for deal in deals:
price = deal.get("dealAmount")
if isinstance(price, (int, float)) and price > 0:
prices.append(price)
# Prices
if deal.deal_amount and deal.deal_amount > 0:
prices.append(deal.deal_amount)
area = deal.get("assetArea")
if isinstance(area, (int, float)) and area > 0:
areas.append(area)
# Areas
if deal.asset_area and deal.asset_area > 0:
areas.append(deal.asset_area)
pps = deal.get("price_per_sqm")
if pps is None and price and area and area > 0:
pps = price / area
if isinstance(pps, (int, float)) and pps > 0:
price_per_sqm_values.append(pps)
# Price per sqm (use computed field)
if deal.price_per_sqm:
price_per_sqm_values.append(deal.price_per_sqm)
room_count = deal.get("assetRoomNum")
if isinstance(room_count, (int, float)):
rooms.append(room_count)
# Property types
if deal.property_type_description:
property_types.append(deal.property_type_description)
# Deal dates
if deal.deal_date:
deal_dates.append(deal.deal_date)
# Calculate statistics
stats: Dict[str, Any] = {"count": len(deals)}
price_stats = {}
area_stats = {}
price_per_sqm_stats = {}
# Price statistics
if prices:
sorted_prices = sorted(prices)
stats["price_stats"] = {
price_stats = {
"mean": round(sum(prices) / len(prices), 2),
"median": (sorted_prices[len(sorted_prices) // 2] + sorted_prices[(len(sorted_prices) - 1) // 2]) / 2,
"min": min(prices),
@@ -78,7 +87,7 @@ def calculate_deal_statistics(deals: List[Dict[str, Any]]) -> Dict[str, Any]:
# Area statistics
if areas:
sorted_areas = sorted(areas)
stats["area_stats"] = {
area_stats = {
"mean": round(sum(areas) / len(areas), 2),
"median": sorted_areas[len(sorted_areas) // 2],
"min": min(areas),
@@ -90,7 +99,7 @@ def calculate_deal_statistics(deals: List[Dict[str, Any]]) -> Dict[str, Any]:
# Price per sqm statistics
if price_per_sqm_values:
sorted_pps = sorted(price_per_sqm_values)
stats["price_per_sqm_stats"] = {
price_per_sqm_stats = {
"mean": round(sum(price_per_sqm_values) / len(price_per_sqm_values), 2),
"median": round(sorted_pps[len(sorted_pps) // 2], 2),
"min": round(min(price_per_sqm_values), 2),
@@ -99,12 +108,44 @@ def calculate_deal_statistics(deals: List[Dict[str, Any]]) -> Dict[str, Any]:
"p75": round(sorted_pps[(3 * len(sorted_pps)) // 4], 2),
}
# Room distribution
if rooms:
room_counts = Counter(rooms)
stats["room_distribution"] = dict(sorted(room_counts.items()))
# Property type distribution
property_type_dist = {}
if property_types:
type_counts = Counter(property_types)
property_type_dist = dict(sorted(type_counts.items()))
return stats
# Date range
date_range_dict = None
if deal_dates:
try:
# Parse ISO date strings to get earliest and latest
parsed_dates = []
for date_str in deal_dates:
try:
# Handle ISO format with timezone (e.g., "2025-01-01T00:00:00.000Z")
if 'T' in date_str:
date_str = date_str.split('T')[0]
parsed_dates.append(date_str)
except:
continue
if parsed_dates:
sorted_dates = sorted(parsed_dates)
date_range_dict = {
"earliest": sorted_dates[0],
"latest": sorted_dates[-1],
}
except:
pass
return DealStatistics(
total_deals=len(deals),
price_statistics=price_stats,
area_statistics=area_stats,
price_per_sqm_statistics=price_per_sqm_stats,
property_type_distribution=property_type_dist,
date_range=date_range_dict,
)
def calculate_std_dev(values: List[float]) -> float: