major changes, including tailwind v4

This commit is contained in:
pacnpal
2025-08-15 12:24:20 -04:00
parent f6c8e0e25c
commit da7c7e3381
261 changed files with 22783 additions and 10465 deletions

View File

@@ -0,0 +1,321 @@
# Location Model Design Document
## ParkLocation Model
```python
from django.contrib.gis.db import models as gis_models
from django.db import models
from parks.models import Park
class ParkLocation(models.Model):
park = models.OneToOneField(
Park,
on_delete=models.CASCADE,
related_name='location'
)
# Geographic coordinates
point = gis_models.PointField(
srid=4326, # WGS84 coordinate system
null=True,
blank=True,
help_text="Geographic coordinates as a Point"
)
# Address components
street_address = models.CharField(max_length=255, blank=True, null=True)
city = models.CharField(max_length=100, blank=True, null=True)
state = models.CharField(max_length=100, blank=True, null=True, help_text="State/Region/Province")
country = models.CharField(max_length=100, blank=True, null=True)
postal_code = models.CharField(max_length=20, blank=True, null=True)
# Road trip metadata
highway_exit = models.CharField(
max_length=100,
blank=True,
null=True,
help_text="Nearest highway exit (e.g., 'Exit 42')"
)
parking_notes = models.TextField(
blank=True,
null=True,
help_text="Parking information and tips"
)
# OSM integration
osm_id = models.BigIntegerField(
blank=True,
null=True,
help_text="OpenStreetMap ID for this location"
)
osm_data = models.JSONField(
blank=True,
null=True,
help_text="Raw OSM data snapshot"
)
class Meta:
indexes = [
models.Index(fields=['city']),
models.Index(fields=['state']),
models.Index(fields=['country']),
models.Index(fields=['city', 'state']),
]
# Spatial index will be created automatically by PostGIS
def __str__(self):
return f"{self.park.name} Location"
@property
def coordinates(self):
"""Returns coordinates as a tuple (latitude, longitude)"""
if self.point:
return (self.point.y, self.point.x)
return None
def get_formatted_address(self):
"""Returns a formatted address string"""
components = []
if self.street_address:
components.append(self.street_address)
if self.city:
components.append(self.city)
if self.state:
components.append(self.state)
if self.postal_code:
components.append(self.postal_code)
if self.country:
components.append(self.country)
return ", ".join(components) if components else ""
```
## RideLocation Model
```python
from django.contrib.gis.db import models as gis_models
from django.db import models
from parks.models import ParkArea
from rides.models import Ride
class RideLocation(models.Model):
ride = models.OneToOneField(
Ride,
on_delete=models.CASCADE,
related_name='location'
)
# Optional coordinates
point = gis_models.PointField(
srid=4326,
null=True,
blank=True,
help_text="Precise ride location within park"
)
# Park area reference
park_area = models.ForeignKey(
ParkArea,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='ride_locations'
)
class Meta:
indexes = [
models.Index(fields=['park_area']),
]
def __str__(self):
return f"{self.ride.name} Location"
@property
def coordinates(self):
"""Returns coordinates as a tuple (latitude, longitude) if available"""
if self.point:
return (self.point.y, self.point.x)
return None
```
## CompanyHeadquarters Model
```python
from django.db import models
from parks.models import Company
class CompanyHeadquarters(models.Model):
company = models.OneToOneField(
Company,
on_delete=models.CASCADE,
related_name='headquarters'
)
city = models.CharField(max_length=100)
state = models.CharField(max_length=100, help_text="State/Region/Province")
class Meta:
verbose_name_plural = "Company headquarters"
indexes = [
models.Index(fields=['city']),
models.Index(fields=['state']),
models.Index(fields=['city', 'state']),
]
def __str__(self):
return f"{self.company.name} Headquarters"
```
## Shared Functionality Protocol
```python
from typing import Protocol, Optional, Tuple
class LocationProtocol(Protocol):
def get_coordinates(self) -> Optional[Tuple[float, float]]:
"""Get coordinates as (latitude, longitude) tuple"""
...
def get_location_name(self) -> str:
"""Get human-readable location name"""
...
def distance_to(self, other: 'LocationProtocol') -> Optional[float]:
"""Calculate distance to another location in meters"""
...
```
## Index Strategy
1. **ParkLocation**:
- Spatial index on `point` (PostGIS GiST index)
- Standard indexes on `city`, `state`, `country`
- Composite index on (`city`, `state`) for common queries
- Index on `highway_exit` for road trip searches
2. **RideLocation**:
- Spatial index on `point` (PostGIS GiST index)
- Index on `park_area` for area-based queries
3. **CompanyHeadquarters**:
- Index on `city`
- Index on `state`
- Composite index on (`city`, `state`)
## OSM Integration Plan
1. **Data Collection**:
- Store OSM ID in `ParkLocation.osm_id`
- Cache raw OSM data in `ParkLocation.osm_data`
2. **Geocoding**:
- Implement Nominatim geocoding service
- Create management command to geocode existing parks
- Add geocoding on ParkLocation save
3. **Road Trip Metadata**:
- Map OSM highway data to `highway_exit` field
- Extract parking information to `parking_notes`
## Migration Strategy
### Phase 1: Add New Models
1. Create new models (ParkLocation, RideLocation, CompanyHeadquarters)
2. Generate migrations
3. Deploy to production
### Phase 2: Data Migration
1. Migrate existing Location data:
```python
for park in Park.objects.all():
if park.location.exists():
loc = park.location.first()
ParkLocation.objects.create(
park=park,
point=loc.point,
street_address=loc.street_address,
city=loc.city,
state=loc.state,
country=loc.country,
postal_code=loc.postal_code
)
```
2. Migrate company headquarters:
```python
for company in Company.objects.exclude(headquarters=''):
city, state = parse_headquarters(company.headquarters)
CompanyHeadquarters.objects.create(
company=company,
city=city,
state=state
)
```
### Phase 3: Update References
1. Update Park model to use ParkLocation
2. Update Ride model to use RideLocation
3. Update Company model to use CompanyHeadquarters
4. Remove old Location model
### Phase 4: OSM Integration
1. Implement geocoding command
2. Run geocoding for all ParkLocations
3. Extract road trip metadata from OSM data
## Relationship Diagram
```mermaid
classDiagram
Park "1" --> "1" ParkLocation
Ride "1" --> "1" RideLocation
Company "1" --> "1" CompanyHeadquarters
RideLocation "1" --> "0..1" ParkArea
class Park {
+name: str
}
class ParkLocation {
+point: Point
+street_address: str
+city: str
+state: str
+country: str
+postal_code: str
+highway_exit: str
+parking_notes: str
+osm_id: int
+get_coordinates()
+get_formatted_address()
}
class Ride {
+name: str
}
class RideLocation {
+point: Point
+get_coordinates()
}
class Company {
+name: str
}
class CompanyHeadquarters {
+city: str
+state: str
}
class ParkArea {
+name: str
}
```
## Rollout Timeline
1. **Week 1**: Implement models and migrations
2. **Week 2**: Migrate data in staging environment
3. **Week 3**: Deploy to production, migrate data
4. **Week 4**: Implement OSM integration
5. **Week 5**: Optimize queries and indexes