endpoint setup
This commit is contained in:
50
app/repositories/location_repo.py
Normal file
50
app/repositories/location_repo.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.models.location import Location
|
||||
from geoalchemy2.shape import from_shape
|
||||
from shapely.geometry import Point
|
||||
from geoalchemy2.functions import ST_DWithin
|
||||
|
||||
|
||||
class LocationRepository:
|
||||
|
||||
@staticmethod
|
||||
async def get_by_osm(session: AsyncSession, osm_type: str, osm_id: int):
|
||||
stmt = select(Location).where(
|
||||
Location.osm_type == osm_type,
|
||||
Location.osm_id == osm_id
|
||||
)
|
||||
result = await session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
@staticmethod
|
||||
async def create(session, data):
|
||||
# For geography this is still correct
|
||||
geom = from_shape(Point(data["longitude"], data["latitude"]), srid=4326)
|
||||
|
||||
location = Location(
|
||||
**data,
|
||||
geom=geom
|
||||
)
|
||||
|
||||
session.add(location)
|
||||
await session.flush()
|
||||
return location
|
||||
|
||||
|
||||
@staticmethod
|
||||
async def find_within_radius(session, lat, lon, radius_m=2000):
|
||||
# Create a geography point instead of string
|
||||
point = from_shape(Point(lon, lat), srid=4326)
|
||||
|
||||
stmt = select(Location).where(
|
||||
ST_DWithin(
|
||||
Location.geom,
|
||||
point,
|
||||
radius_m
|
||||
)
|
||||
)
|
||||
|
||||
result = await session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
8
app/repositories/owner_repo.py
Normal file
8
app/repositories/owner_repo.py
Normal file
@@ -0,0 +1,8 @@
|
||||
class OwnerRepository:
|
||||
|
||||
@staticmethod
|
||||
async def update(session, owner, data: dict):
|
||||
for key, value in data.items():
|
||||
setattr(owner, key, value)
|
||||
await session.flush()
|
||||
return owner
|
||||
@@ -1,110 +1,20 @@
|
||||
from neo4j import Driver
|
||||
|
||||
QUERY_NEAREST = """
|
||||
WITH point({latitude: $lat, longitude: $lng}) AS userLocation
|
||||
|
||||
MATCH (o:Owner)-[:OWNS]->(p:Property)-[:LOCATED_AT]->(l:Location)
|
||||
WHERE l.location IS NOT NULL
|
||||
|
||||
WITH o, p,
|
||||
point.distance(l.location, userLocation) AS distanceMeters
|
||||
|
||||
ORDER BY distanceMeters ASC
|
||||
LIMIT 1
|
||||
|
||||
RETURN
|
||||
|
||||
// Owner Projection (API Safe)
|
||||
{
|
||||
owner_id: o.owner_id,
|
||||
full_name: o.full_name,
|
||||
email: o.email,
|
||||
phone: o.phone,
|
||||
entity_type: o.entity_type,
|
||||
mailing_address: o.mailing_address,
|
||||
credit_score_est: o.credit_score_est,
|
||||
income_bracket: o.income_bracket,
|
||||
net_worth_est: o.net_worth_est,
|
||||
portfolio_size: o.portfolio_size,
|
||||
min_price_expectation: o.min_price_expectation,
|
||||
preferred_close_days: o.preferred_close_days,
|
||||
urgency_score: o.urgency_score,
|
||||
is_absentee: o.is_absentee,
|
||||
willing_to_sell: o.willing_to_sell,
|
||||
willing_to_partner: o.willing_to_partner
|
||||
} AS owner,
|
||||
|
||||
// Property Projection (API Safe)
|
||||
{
|
||||
property_id: p.property_id,
|
||||
internal_asset_code: p.internal_asset_code,
|
||||
structure_type: p.structure_type,
|
||||
listing_status: p.listing_status,
|
||||
occupancy_status: p.occupancy_status,
|
||||
property_grade: p.property_grade,
|
||||
energy_rating: p.energy_rating,
|
||||
|
||||
year_built: p.year_built,
|
||||
floors_count: p.floors_count,
|
||||
bedrooms: p.bedrooms,
|
||||
bathrooms: p.bathrooms,
|
||||
total_built_sqft: p.total_built_sqft,
|
||||
lot_size_sqft: p.lot_size_sqft,
|
||||
garage_spaces: p.garage_spaces,
|
||||
|
||||
purchase_price: p.purchase_price,
|
||||
expected_sale_price: p.expected_sale_price,
|
||||
market_value_est: p.market_value_est,
|
||||
current_rent: p.current_rent,
|
||||
rental_yield_percent: p.rental_yield_percent,
|
||||
vacancy_days: p.vacancy_days,
|
||||
tenant_present: p.tenant_present,
|
||||
|
||||
exterior_condition: p.exterior_condition,
|
||||
foundation_type: p.foundation_type,
|
||||
roof_type: p.roof_type,
|
||||
roof_material: p.roof_material,
|
||||
roof_condition: p.roof_condition,
|
||||
roof_pitch: p.roof_pitch,
|
||||
roof_age_years: p.roof_age_years,
|
||||
siding_material: p.siding_material,
|
||||
gutter_status: p.gutter_status,
|
||||
hvac_type: p.hvac_type,
|
||||
electric_type: p.electric_type,
|
||||
plumbing_type: p.plumbing_type,
|
||||
solar_installed: p.solar_installed,
|
||||
|
||||
mold_risk_level: p.mold_risk_level,
|
||||
termite_risk_level: p.termite_risk_level,
|
||||
structural_risk_level: p.structural_risk_level,
|
||||
fire_damage_flag: p.fire_damage_flag,
|
||||
water_damage_flag: p.water_damage_flag,
|
||||
|
||||
created_at: toString(p.created_at),
|
||||
updated_at: toString(p.updated_at)
|
||||
|
||||
} AS property,
|
||||
|
||||
distanceMeters
|
||||
"""
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
from app.db.models.property import Property
|
||||
|
||||
|
||||
def find_nearest_property_and_owner(driver: Driver, lat: float, lng: float):
|
||||
class PropertyRepository:
|
||||
@staticmethod
|
||||
async def get_by_location_id(session, location_id: int):
|
||||
|
||||
with driver.session(database="neo4j") as session:
|
||||
result = session.run(
|
||||
QUERY_NEAREST,
|
||||
lat=lat,
|
||||
lng=lng
|
||||
stmt = (
|
||||
select(Property)
|
||||
.options(
|
||||
selectinload(Property.owner),
|
||||
selectinload(Property.location),
|
||||
)
|
||||
.where(Property.location_id == location_id)
|
||||
)
|
||||
|
||||
record = result.single()
|
||||
|
||||
if not record:
|
||||
return None
|
||||
|
||||
return {
|
||||
"owner": record["owner"],
|
||||
"property": record["property"],
|
||||
"distanceMeters": record["distanceMeters"]
|
||||
}
|
||||
result = await session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
Reference in New Issue
Block a user