jcbowyer's picture
Clean HuggingFace deployment without binary files
e59d91d
|
Raw
History Blame Contribute Delete
5.31 kB
# Database & Data Migrations
This directory contains migration scripts for schema changes and data transformations.
## Available Migrations
### migrate_state_naming.py
**Purpose:** Standardize state field naming across all database tables and parquet files
**What it does:**
- Renames `state` (2-char codes) → `state_code`
- Renames `state_name` (full names) → `state`
- Renames `state_abbr``state_code`
- Adds missing `state` (full name) columns where needed
- Populates full state names from codes using lookup table
**Usage:**
```bash
# Dry run (recommended first)
python scripts/migrations/migrate_state_naming.py --dry-run --all
# Migrate database tables only
python scripts/migrations/migrate_state_naming.py --tables
# Migrate parquet files only
python scripts/migrations/migrate_state_naming.py --parquets
# Migrate everything
python scripts/migrations/migrate_state_naming.py --all
```
**Safety:**
- Always runs in dry-run mode by default if no target specified
- Creates `.parquet.backup` files before modifying parquet files
- Shows all changes before applying them
- Can be run multiple times safely (idempotent)
**Affected Tables:**
- `jurisdiction`
- `contact`
- `event`
- `organization_nonprofit`
- `bills_search`
- `bill_map_aggregate`
- `jurisdictions_details_search`
- `zip_county_mapping`
- + 8 more tables with state columns
**Affected Files:**
- `data/gold/states/{STATE}/*.parquet` (all state-specific parquets)
- `data/gold/jurisdictions_details.parquet`
## Best Practices
### Before Running Migrations
1. **Backup your database:**
```bash
pg_dump -h localhost -p 5433 -U postgres open_navigator > backup_$(date +%Y%m%d).sql
```
2. **Test in dry-run mode:**
```bash
python scripts/migrations/migrate_state_naming.py --dry-run --all
```
3. **Review the changes:**
- Check the migration plan
- Verify table/file identification
- Confirm backup locations
### After Running Migrations
1. **Verify the migration:**
```sql
-- Check table structure
\d+ jurisdiction
-- Verify data
SELECT state_code, state, COUNT(*)
FROM jurisdiction
GROUP BY state_code, state
LIMIT 10;
```
2. **Test parquet files:**
```python
import pandas as pd
df = pd.read_parquet('data/gold/states/AL/contact_official.parquet')
print(df.columns.tolist())
print(df[['state_code', 'state']].drop_duplicates())
```
3. **Update dependent code:**
- Search for: `WHERE state =`
- Update API endpoints
- Update frontend filters
- Update data loading scripts
## Creating New Migrations
### Template
```python
#!/usr/bin/env python3
"""
Migration: [Brief description]
Changes:
- [Change 1]
- [Change 2]
Usage:
python scripts/migrations/migrate_xxx.py --dry-run
python scripts/migrations/migrate_xxx.py --apply
"""
import os
import sys
from pathlib import Path
import psycopg2
from loguru import logger
from dotenv import load_dotenv
load_dotenv()
DATABASE_URL = os.getenv('NEON_DATABASE_URL_DEV',
'postgresql://postgres:password@localhost:5433/open_navigator')
class XXXMigrator:
def __init__(self, dry_run: bool = True):
self.dry_run = dry_run
def migrate(self):
logger.info("Starting migration...")
conn = psycopg2.connect(DATABASE_URL)
cursor = conn.cursor()
try:
# Migration logic here
if not self.dry_run:
conn.commit()
else:
conn.rollback()
finally:
cursor.close()
conn.close()
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--dry-run', action='store_true', default=True)
parser.add_argument('--apply', action='store_true')
args = parser.parse_args()
migrator = XXXMigrator(dry_run=not args.apply)
migrator.migrate()
```
### Naming Convention
- Use descriptive names: `migrate_{what}_{when}.py`
- Include date for tracking: `migrate_state_naming_20260503.py`
- Keep migrations in order: `001_`, `002_`, etc.
## Migration History
| Date | Script | Description | Status |
|------|--------|-------------|--------|
| 2026-05-03 | `migrate_state_naming.py` | Standardize state field naming | ✅ Ready |
## Troubleshooting
### Migration failed mid-way
1. Check the error message
2. Restore from backup:
```bash
psql -h localhost -p 5433 -U postgres open_navigator < backup_20260503.sql
```
3. Fix the issue
4. Run migration again
### Parquet file corruption
1. Backups are created as `.parquet.backup`
2. Restore:
```bash
mv file.parquet file.parquet.failed
mv file.parquet.backup file.parquet
```
### Dependent code breaking
1. Search for state field references:
```bash
grep -r "WHERE state = " api/ scripts/ neon/
```
2. Update to use `state_code`:
```python
# Before
query = "SELECT * FROM jurisdictions WHERE state = 'AL'"
# After
query = "SELECT * FROM jurisdictions WHERE state_code = 'AL'"
```
## See Also
- [State Field Naming Standard](../../website/docs/development/state-field-naming-standard.md)
- [GitHub Copilot Instructions](../../.github/copilot-instructions.md)
- [Database Schema](../../packages/hosting/src/hosting/neon/schema.sql)