diff --git a/docs/ADMIN_ARCHITECTURE_OPTIONS.md b/docs/ADMIN_ARCHITECTURE_OPTIONS.md
new file mode 100644
index 00000000..7c824814
--- /dev/null
+++ b/docs/ADMIN_ARCHITECTURE_OPTIONS.md
@@ -0,0 +1,634 @@
+# Admin UI Architecture Options for NMSampleLocations
+
+## Context
+
+**Current Situation:**
+- AMPAPI (SQL Server) data needs to migrate to NMSampleLocations (PostgreSQL)
+- OcotilloUI (React + Refine.dev) is the current admin interface
+- Proposal: Temporarily replace OcotilloUI with Starlette Admin during migration
+
+**Two Critical Questions:**
+1. **Do we need to consider Starlette Admin alternatives?**
+2. **Can Starlette Admin exist within NMSampleLocations as a monolithic repo?**
+
+---
+
+## Question 1: Starlette Admin vs. Alternatives
+
+### Full Spectrum of Options
+
+We've evaluated framework-based solutions, but let's consider the **complete architectural spectrum**:
+
+| Option | Description | Pros | Cons | Effort |
+|--------|-------------|------|------|--------|
+| **1. Starlette Admin** | Python admin framework mounted to FastAPI | Native SQLAlchemy, GeoAlchemy2 support | Less flashy UI, custom views need work | 32-42 hrs |
+| **2. Keep OcotilloUI** | Continue using React + Refine.dev | Already built, familiar to staff | Requires maintaining React frontend | 0 hrs (status quo) |
+| **3. Simplify OcotilloUI** | Remove unused features, focus on CRUD | Familiar UI, less maintenance | Still requires React/Node.js stack | 16-24 hrs |
+| **4. Django Admin (microservice)** | Separate Django service sharing PostgreSQL | Enterprise-grade admin, battle-tested | Additional service to deploy/maintain | 40-60 hrs |
+| **5. SQLAdmin** | Lightweight SQLAlchemy admin for FastAPI | Simpler than Starlette Admin | Weaker RBAC, limited geospatial | 24-32 hrs |
+| **6. No Admin UI** | Direct database access (psql, pgAdmin, DBeaver) | Zero development effort | Not user-friendly for non-technical staff | 0 hrs |
+| **7. Custom FastAPI Endpoints** | Build minimal admin API, no UI | Full control, lightweight | Staff need Postman/curl | 8-12 hrs |
+| **8. React Admin (custom)** | Build from scratch with react-admin/Refine | Perfect fit for needs | 3-4 months development time | 300-400 hrs |
+
+---
+
+### Decision Matrix: When to Use Each Option
+
+#### ✅ **Use Starlette Admin If:**
+- You want a **temporary solution** during migration (2-6 months)
+- Core CRUD is sufficient for 80% of workflows
+- You can defer maps/custom forms to later
+- You want Python-only stack (no Node.js)
+- Timeline: **2-4 weeks**
+
+**Verdict**: **Best for migration MVP** - balances speed, functionality, low risk
+
+---
+
+#### ✅ **Keep OcotilloUI If:**
+- Staff **cannot tolerate** deferred features (maps, forms)
+- Well Inventory Form or Groundwater Level Form are **critical path**
+- React expertise is available for maintenance
+- You're comfortable maintaining dual stack (Python + Node.js)
+
+**Verdict**: **Safest option** - no changes, no risk, but doesn't reduce complexity
+
+---
+
+#### ✅ **Simplify OcotilloUI If:**
+- You want to **reduce OcotilloUI complexity** but keep React
+- Custom forms are critical, but dashboard/apps are not
+- You want to keep the map view
+- Timeline: **2-3 weeks** to strip down features
+
+**Pros**:
+- Keep the 20% of features you actually use (maps, forms)
+- Remove the 80% you don't (coming soon apps, experimental features)
+
+**Cons**:
+- Still maintaining React frontend
+- Doesn't help with AMPAPI migration directly
+
+**Verdict**: **Consider if Starlette Admin fails UAT**
+
+---
+
+#### ✅ **Use Django Admin (Microservice) If:**
+- You need **enterprise-grade admin** long-term
+- Team has Django expertise
+- You're okay with microservice complexity
+- Timeline: **6-8 weeks**
+
+**Architecture**:
+```
+┌─────────────────┐ ┌─────────────────┐
+│ Django Admin │ │ NMSampleLocations│
+│ (Port 8001) │────────▶│ PostgreSQL DB │
+│ Admin UI only │ Shares │ │
+└─────────────────┘ DB └─────────────────┘
+ ▲
+ │
+ ┌────────┴────────┐
+ │ FastAPI API │
+ │ (Port 8000) │
+ └─────────────────┘
+```
+
+**Pros**:
+- Best-in-class admin experience (Django Admin is gold standard)
+- Automatic admin for all models
+- Built-in user management, permissions, audit logs
+- Geospatial admin via django.contrib.gis
+
+**Cons**:
+- **Two separate services** to deploy and maintain
+- **Duplicate model definitions** (SQLAlchemy → Django ORM conversion)
+- **Authentication sync** complexity
+- More moving parts in production
+
+**Verdict**: **Overkill for temporary migration** - consider for long-term if Starlette Admin proves insufficient
+
+---
+
+#### ✅ **Use SQLAdmin If:**
+- You want **simpler** alternative to Starlette Admin
+- You don't need complex RBAC
+- Geospatial fields are not a priority
+- Timeline: **3-4 weeks**
+
+**Key Differences from Starlette Admin**:
+
+| Feature | Starlette Admin | SQLAdmin |
+|---------|----------------|----------|
+| RBAC | ✅ Built-in flexible system | ⚠️ Basic, manual |
+| GeoAlchemy2 | ✅ Native support | ⚠️ Basic (shows WKT) |
+| Custom Actions | ✅ Decorator-based | ⚠️ Limited |
+| UI Customization | ✅ Extensive | ⚠️ Basic |
+| File Uploads | ✅ Supported | ✅ Supported |
+
+**Verdict**: **Fallback if Starlette Admin is too complex** - but unlikely given your needs
+
+---
+
+#### ✅ **No Admin UI If:**
+- Users are **highly technical** (can use psql, pgAdmin, DBeaver)
+- Data entry volume is **very low** (< 10 records/week)
+- You have **strict timeline** (need admin access immediately)
+
+**Tools**:
+- **psql**: Command-line PostgreSQL client
+- **pgAdmin 4**: GUI database browser
+- **DBeaver**: Universal database tool with PostGIS support
+
+**Pros**:
+- Zero development effort
+- Direct database access (no abstraction bugs)
+- Full SQL power for complex queries
+
+**Cons**:
+- Not suitable for non-technical staff
+- Easy to make mistakes (no validation)
+- No audit trail
+- No business logic enforcement
+
+**Verdict**: **Only for developers** - not suitable for data stewards/staff
+
+---
+
+#### ✅ **Custom FastAPI Endpoints If:**
+- You need **minimal admin API** for scripts/automation
+- No UI needed (staff use Postman, curl, or scripts)
+- Timeline: **1 week**
+
+**Example**:
+```python
+# api/admin_operations.py
+from fastapi import APIRouter, Depends
+from schemas.admin import BulkPublishRequest
+
+router = APIRouter(prefix="/admin", tags=["admin"])
+
+@router.post("/bulk-publish")
+async def bulk_publish(
+ request: BulkPublishRequest,
+ user: User = Depends(require_admin)
+):
+ """Bulk change release_status to 'public' for given location IDs."""
+ await session.execute(
+ update(Location)
+ .where(Location.id.in_(request.location_ids))
+ .values(release_status='public')
+ )
+ return {"updated": len(request.location_ids)}
+```
+
+**Staff Usage**:
+```bash
+# Via curl
+curl -X POST http://localhost:8000/admin/bulk-publish \
+ -H "Authorization: Bearer $TOKEN" \
+ -d '{"location_ids": ["uuid1", "uuid2", "uuid3"]}'
+
+# Via Python script
+import requests
+response = requests.post(
+ "http://localhost:8000/admin/bulk-publish",
+ json={"location_ids": ["uuid1", "uuid2", "uuid3"]},
+ headers={"Authorization": f"Bearer {token}"}
+)
+```
+
+**Verdict**: **Supplement to any admin UI** - useful for automation, not replacement
+
+---
+
+#### ❌ **Custom React Admin (from scratch)**
+- **Development Time**: 3-4 months
+- **Effort**: 300-400 hours
+- **Verdict**: **Not recommended** - too slow for migration timeline
+
+---
+
+### Recommendation: Decision Tree
+
+```
+START: Do you need admin UI immediately (< 2 weeks)?
+├─ YES → No Admin UI (use pgAdmin/psql temporarily)
+└─ NO → Continue
+
+Can staff tolerate deferred features (maps, forms) for 2-4 months?
+├─ NO → Keep OcotilloUI (safest, no changes)
+└─ YES → Continue
+
+Do you want Python-only stack (no Node.js)?
+├─ YES → Starlette Admin ⭐ RECOMMENDED
+└─ NO → Simplify OcotilloUI (strip unused features)
+
+If Starlette Admin proves insufficient after MVP:
+├─ Short-term fix → Restore OcotilloUI for missing features
+├─ Long-term solution → Django Admin microservice
+└─ Hybrid approach → Starlette Admin + custom React forms
+```
+
+**Final Recommendation**: **Start with Starlette Admin** for 3 reasons:
+1. **Speed**: 2-4 weeks to MVP vs. 6-8 weeks for Django Admin
+2. **Low Risk**: Can fall back to OcotilloUI if it fails
+3. **Low Lock-in**: Uses your existing SQLAlchemy models
+
+---
+
+## Question 2: Monolithic vs. Separate Repository
+
+### Can Starlette Admin Live in NMSampleLocations Repo?
+
+**Short Answer**: ✅ **YES** - Starlette Admin can (and should) live in the NMSampleLocations monolithic repo.
+
+### Current NMSampleLocations Architecture
+
+**Repo Structure**:
+```
+NMSampleLocations/
+├── main.py # FastAPI entry point
+├── core/
+│ ├── app.py # FastAPI app initialization
+│ └── initializers.py # Route registration
+├── api/ # REST API endpoints
+│ ├── location.py
+│ ├── sensor.py
+│ ├── sample.py
+│ └── ...
+├── db/ # SQLAlchemy models (50+ models)
+│ ├── location.py
+│ ├── sensor.py
+│ └── ...
+├── schemas/ # Pydantic schemas
+└── services/ # Business logic
+```
+
+**Current Pattern**: **Monolithic FastAPI application**
+- All API routes registered in `core/initializers.py`
+- Single deployment unit
+- Shared database session management
+
+---
+
+### Option A: Monolithic (Admin in Same Repo) ⭐ **RECOMMENDED**
+
+**Structure**:
+```
+NMSampleLocations/
+├── main.py # Mount both API and admin
+├── core/
+│ ├── app.py
+│ └── initializers.py # Register API routes AND admin
+├── api/ # REST API endpoints
+│ ├── location.py
+│ └── ...
+├── admin/ # ← NEW: Starlette Admin
+│ ├── __init__.py
+│ ├── auth.py # Auth provider for admin
+│ ├── views.py # Custom ModelViews
+│ └── actions.py # Bulk actions (publish, export)
+├── db/ # Shared SQLAlchemy models
+├── schemas/ # Pydantic schemas (API only)
+└── services/ # Shared business logic
+```
+
+**Updated `core/initializers.py`**:
+```python
+def register_routes(app):
+ # Existing API routes
+ from api.location import router as location_router
+ from api.sensor import router as sensor_router
+ # ... (all existing routers)
+
+ app.include_router(location_router)
+ app.include_router(sensor_router)
+ # ...
+
+ # NEW: Mount admin interface
+ from admin import create_admin
+ admin = create_admin()
+ admin.mount_to(app) # Mounts at /admin by default
+```
+
+**New `admin/__init__.py`**:
+```python
+from starlette_admin.contrib.sqla import Admin, ModelView
+from db import Location, Sensor, Sample, Contact, Asset, Group
+from db.engine import engine
+from .auth import NMBGMRAuthProvider
+from .views import (
+ LocationAdmin, SensorAdmin, SampleAdmin,
+ ContactAdmin, AssetAdmin, GroupAdmin
+)
+
+def create_admin():
+ admin = Admin(
+ engine,
+ title="NMSampleLocations Admin",
+ base_url="/admin",
+ auth_provider=NMBGMRAuthProvider()
+ )
+
+ # Register model views
+ admin.add_view(LocationAdmin(Location))
+ admin.add_view(SensorAdmin(Sensor))
+ admin.add_view(SampleAdmin(Sample))
+ admin.add_view(ContactAdmin(Contact))
+ admin.add_view(AssetAdmin(Asset))
+ admin.add_view(GroupAdmin(Group))
+
+ return admin
+```
+
+**Deployment**:
+```bash
+# Single service
+uvicorn main:app --host 0.0.0.0 --port 8000
+
+# Routes available:
+# - http://localhost:8000/docs → OpenAPI (public)
+# - http://localhost:8000/docs-auth → OpenAPI (authenticated)
+# - http://localhost:8000/location → REST API
+# - http://localhost:8000/admin → Starlette Admin UI ← NEW
+```
+
+**Docker Compose** (unchanged):
+```yaml
+services:
+ app:
+ build: .
+ ports:
+ - "8000:8000"
+ environment:
+ - DATABASE_URL=postgresql://...
+ # Single container runs both API and admin
+```
+
+---
+
+### Option B: Separate Repository (Admin as Microservice)
+
+**Structure**:
+```
+NMSampleLocations/ # Existing FastAPI API
+├── api/
+├── db/
+└── ...
+
+NMSampleLocations-Admin/ # NEW: Separate admin service
+├── main.py
+├── admin/
+│ ├── views.py
+│ └── auth.py
+├── db/ # ← DUPLICATED models
+│ ├── location.py # Same as NMSampleLocations/db/location.py
+│ └── ...
+└── requirements.txt
+```
+
+**Deployment**:
+```yaml
+services:
+ api:
+ build: ./NMSampleLocations
+ ports:
+ - "8000:8000"
+
+ admin:
+ build: ./NMSampleLocations-Admin
+ ports:
+ - "8001:8000"
+
+ db:
+ image: postgis/postgis
+ # Both services connect to same database
+```
+
+**Routes**:
+- API: `http://localhost:8000/location`
+- Admin: `http://localhost:8001/admin`
+
+---
+
+### Comparison: Monolithic vs. Microservice
+
+| Aspect | Monolithic (Option A) | Microservice (Option B) |
+|--------|----------------------|------------------------|
+| **Code Duplication** | ✅ No duplication (shared models) | ❌ Models duplicated across repos |
+| **Deployment** | ✅ Single service | ⚠️ Two services to deploy |
+| **Development** | ✅ Single repo, single environment | ⚠️ Two repos, sync models manually |
+| **Authentication** | ✅ Shared auth logic | ⚠️ Auth sync complexity |
+| **Database Migrations** | ✅ Single Alembic migration | ⚠️ Must coordinate migrations |
+| **Independent Scaling** | ❌ Can't scale admin separately | ✅ Can scale admin independently |
+| **Technology Lock-in** | ⚠️ Admin tied to FastAPI | ✅ Can use different framework |
+| **CI/CD Complexity** | ✅ Single pipeline | ⚠️ Two pipelines |
+| **Monitoring** | ✅ Single service to monitor | ⚠️ Two services to monitor |
+| **Ease of Swapping** | ⚠️ Harder to replace admin | ✅ Easy to replace admin service |
+
+---
+
+### Hybrid Option C: Monolithic with Admin Toggle
+
+**Best of Both Worlds**: Admin lives in same repo but can be **optionally disabled** via environment variable.
+
+```python
+# core/initializers.py
+def register_routes(app):
+ # Always register API routes
+ from api.location import router as location_router
+ # ...
+ app.include_router(location_router)
+
+ # Conditionally mount admin
+ if settings.ENABLE_ADMIN: # ← Environment variable
+ from admin import create_admin
+ admin = create_admin()
+ admin.mount_to(app)
+```
+
+**.env**:
+```bash
+# Production API deployment (no admin)
+ENABLE_ADMIN=false
+
+# Development or admin-only deployment
+ENABLE_ADMIN=true
+```
+
+**Deployment Flexibility**:
+```yaml
+# Option 1: Combined deployment
+services:
+ app:
+ environment:
+ - ENABLE_ADMIN=true # Both API and admin
+
+# Option 2: Separate deployments (same codebase!)
+services:
+ api:
+ environment:
+ - ENABLE_ADMIN=false # API only
+
+ admin:
+ environment:
+ - ENABLE_ADMIN=true # Admin only
+```
+
+**Benefits**:
+- ✅ Single codebase, shared models
+- ✅ Flexibility to deploy separately later
+- ✅ Easy to disable admin in production if needed
+- ✅ Can scale admin separately if using separate deployments
+
+---
+
+## Architectural Recommendation
+
+### For AMPAPI Migration: **Option A (Monolithic)** ⭐
+
+**Rationale**:
+
+1. **Simplicity**: NMSampleLocations is already monolithic - adding admin doesn't change architecture
+2. **Speed**: No need to set up separate repo, CI/CD, deployment
+3. **Code Sharing**: Admin views use existing SQLAlchemy models, no duplication
+4. **Easy Testing**: Test both API and admin in single pytest suite
+5. **Migration Focus**: Reduces complexity during critical migration period
+
+**Implementation Path**:
+```bash
+# Week 1: Add admin to existing repo
+cd NMSampleLocations
+mkdir admin
+touch admin/__init__.py admin/auth.py admin/views.py
+
+# Week 2-4: Implement admin views
+# (No separate repo, no separate deployment)
+
+# Production: Single deployment, both API and admin
+docker build -t nmsamplelocations .
+docker run -p 8000:8000 nmsamplelocations
+# → http://localhost:8000/location (API)
+# → http://localhost:8000/admin (Admin UI)
+```
+
+**Future Flexibility**: If you later decide admin needs to be separate:
+1. Move `admin/` directory to new repo
+2. Copy `db/` models (or use shared package)
+3. Deploy as separate service
+
+**Low lock-in**: Adding admin to monolith doesn't prevent future microservice split.
+
+---
+
+### When to Use Separate Repository (Option B)
+
+✅ **Use microservice approach if:**
+
+1. **Admin is long-term** (not temporary for migration)
+2. **Different scaling needs** (admin has different load pattern than API)
+3. **Different teams** (separate admin team, separate API team)
+4. **Different tech stacks** (e.g., Django Admin for admin, FastAPI for API)
+5. **Security isolation** (admin on internal network, API public)
+
+❌ **Don't use microservice if:**
+- Admin is **temporary** during migration
+- You have **limited DevOps resources**
+- You want **fast iteration** (monolith is faster to develop)
+
+---
+
+## Migration Strategy with Monolithic Admin
+
+### Phase 1: Add Admin to Monolith (Week 1)
+```python
+# NMSampleLocations/admin/__init__.py
+from starlette_admin.contrib.sqla import Admin
+from db.engine import engine
+
+def create_admin():
+ admin = Admin(engine, title="NM Sample Locations")
+ # Start with 3 models for testing
+ from admin.views import LocationAdmin, SensorAdmin, ContactAdmin
+ admin.add_view(LocationAdmin)
+ admin.add_view(SensorAdmin)
+ admin.add_view(ContactAdmin)
+ return admin
+```
+
+### Phase 2: Expand Models (Week 2)
+- Add remaining 7 models (10 total)
+- Implement auth provider
+- Add RBAC permissions
+
+### Phase 3: Custom Features (Week 3-4)
+- Bulk actions
+- Export functionality
+- Custom dashboards
+
+### Phase 4: Production (Week 5)
+- Deploy single service with both API and admin
+- Monitor usage
+- Gather feedback
+
+### Post-Migration: Reevaluate Architecture
+**After 3-6 months of usage**, decide:
+- ✅ Keep monolithic admin (if working well)
+- ↔️ Split into microservice (if scaling/isolation needed)
+- ↩️ Restore OcotilloUI (if Starlette Admin insufficient)
+- ⏩ Build custom React admin (if long-term investment justified)
+
+---
+
+## Final Recommendations
+
+### Question 1: Starlette Admin vs. Alternatives?
+
+**Answer**: ✅ **Starlette Admin is the right choice** for these reasons:
+
+1. **Speed**: 2-4 weeks vs. 6-8 weeks (Django Admin) or 3-4 months (custom React)
+2. **Low Risk**: Can fall back to OcotilloUI if it fails UAT
+3. **Python-Only**: No Node.js, simpler stack
+4. **Native Integration**: Works directly with your SQLAlchemy models
+5. **Temporary Intent**: Perfect for migration period, not over-engineering
+
+**Alternative to Consider**: Keep OcotilloUI if staff reject Starlette Admin during UAT.
+
+---
+
+### Question 2: Monolithic vs. Separate Repo?
+
+**Answer**: ✅ **Monolithic (admin in NMSampleLocations repo)** for these reasons:
+
+1. **Already Monolithic**: NMSampleLocations is single-service architecture
+2. **Code Sharing**: No model duplication, single source of truth
+3. **Simple Deployment**: Single Docker container, single CI/CD pipeline
+4. **Migration Focus**: Reduces complexity during critical period
+5. **Future Flexibility**: Can split later if needed (low lock-in)
+
+**Implementation**:
+```
+NMSampleLocations/
+├── api/ # Existing REST API
+├── admin/ # ← NEW: Starlette Admin views
+├── db/ # Shared SQLAlchemy models
+├── core/initializers.py # Register both API routes and admin
+└── main.py # Single entry point
+```
+
+---
+
+## Next Steps
+
+1. ✅ **Approve Starlette Admin** as temporary admin framework
+2. ✅ **Confirm monolithic approach** (admin in NMSampleLocations repo)
+3. ⏭️ **Build prototype** (Week 1): Add 2-3 models to admin UI
+4. ⏭️ **User acceptance testing** (Week 2): Show to staff, gather feedback
+5. ⏭️ **Full implementation** (Week 3-4): Add all models, auth, custom actions
+6. ⏭️ **Production deployment** (Week 5): Single service with API + admin
+7. ⏭️ **Post-migration review** (3-6 months): Keep, replace, or enhance admin
+
+**Timeline**: **4-5 weeks** from decision to production-ready admin UI.
+
+**Risk**: **Low** - can keep OcotilloUI running in parallel as safety net.
diff --git a/docs/ADMIN_FRAMEWORK_EVALUATION.md b/docs/ADMIN_FRAMEWORK_EVALUATION.md
new file mode 100644
index 00000000..49b0128e
--- /dev/null
+++ b/docs/ADMIN_FRAMEWORK_EVALUATION.md
@@ -0,0 +1,310 @@
+# Admin Framework Evaluation for NMSampleLocations
+
+## Project Context
+
+**NMSampleLocations** is a production-oriented geospatial sample data management system for the New Mexico Bureau of Geology and Mineral Resources.
+
+### Current Stack
+- **Backend**: FastAPI / Starlette
+- **ORM**: SQLAlchemy 2.0.43
+- **Database**: PostgreSQL + PostGIS (geospatial)
+- **Auth**: Role-based access control (Admin/Editor/Viewer)
+- **Data Model**: 33+ SQLAlchemy models with complex relationships
+- **Special Requirements**:
+ - GeoAlchemy2 for geospatial data
+ - Data visibility controls (`release_status` field)
+ - Versioned data (SQLAlchemy-Continuum)
+ - Complex domain: locations, things, samples, observations, sensors, deployments
+
+---
+
+## Executive Recommendation
+
+**Primary Choice: Starlette Admin**
+
+### Rationale
+
+1. **Perfect ORM Fit**: Native SQLAlchemy support (you already use SQLAlchemy 2.0.43)
+2. **Geospatial Ready**: Works with GeoAlchemy2 out of the box
+3. **RBAC Built-in**: Matches your existing permission model (Admin/Editor/Viewer)
+4. **Production Maturity**: Battle-tested, active maintenance
+5. **Extensibility**: Can handle complex workflows and custom business logic
+6. **Low Lock-in**: Uses standard SQLAlchemy models, easy to migrate away if needed
+
+---
+
+## Detailed Evaluation for NMSampleLocations
+
+### Option 1: Starlette Admin ⭐ **RECOMMENDED**
+
+**Installation**
+```bash
+pip install starlette-admin[i18n]
+# Or with uv:
+uv add starlette-admin[i18n]
+```
+
+**Why It's Perfect for You**
+
+✅ **SQLAlchemy Integration**
+- Works directly with your existing models
+- No schema duplication needed
+- Supports SQLAlchemy-Continuum (versioned data)
+
+✅ **Geospatial Support**
+```python
+from starlette_admin.contrib.sqla import Admin, ModelView
+from geoalchemy2 import Geometry
+
+# Your Location model will work out of the box
+class LocationAdmin(ModelView):
+ fields = ['id', 'point', 'elevation', 'release_status', ...]
+ # PostGIS geometry fields render properly
+```
+
+✅ **RBAC Implementation**
+```python
+from starlette_admin.auth import AdminUser, AuthProvider
+
+class NMBGMRAuthProvider(AuthProvider):
+ async def is_authenticated(self, request):
+ # Your existing auth logic
+ user = get_current_user(request)
+ return user is not None
+
+ async def get_admin_user(self, request):
+ user = get_current_user(request)
+ return AdminUser(
+ username=user.name,
+ roles=user.permissions # ['Admin', 'Editor', 'Viewer']
+ )
+
+# In your admin views:
+class LocationAdmin(ModelView):
+ def can_create(self, request):
+ return 'Admin' in request.state.user.roles
+
+ def can_edit(self, request):
+ return any(role in request.state.user.roles for role in ['Admin', 'Editor'])
+```
+
+✅ **Data Visibility Controls**
+```python
+class LocationAdmin(ModelView):
+ # Override queryset based on user role
+ async def get_list_query(self, request):
+ query = select(Location)
+
+ # Staff see all data
+ if request.state.user:
+ return query
+
+ # Public see only public data
+ return query.where(Location.release_status == 'public')
+```
+
+✅ **Custom Actions**
+```python
+class LocationAdmin(ModelView):
+ actions = ['bulk_publish', 'export_geojson']
+
+ async def bulk_publish(self, request, pks):
+ # Bulk change release_status to 'public'
+ await session.execute(
+ update(Location)
+ .where(Location.id.in_(pks))
+ .values(release_status='public')
+ )
+```
+
+**Example Implementation**
+```python
+# admin_setup.py
+from starlette_admin import CustomView
+from starlette_admin.contrib.sqla import Admin, ModelView
+from starlette.applications import Starlette
+
+from db import Location, Thing, Sample, Observation, Sensor
+from db.engine import engine
+
+# Create admin
+admin = Admin(
+ engine,
+ title="NMSampleLocations Admin",
+ base_url='/admin',
+ auth_provider=NMBGMRAuthProvider()
+)
+
+# Register model views
+admin.add_view(LocationAdmin(Location))
+admin.add_view(ModelView(Thing))
+admin.add_view(ModelView(Sample))
+admin.add_view(ModelView(Observation))
+admin.add_view(ModelView(Sensor))
+
+# Mount to FastAPI
+from fastapi import FastAPI
+app = FastAPI()
+admin.mount_to(app)
+```
+
+**Estimated Setup Time**: 1-2 days for basic CRUD, 1 week for full customization
+
+**Pros**
+- ✓ Works with all 33+ of your models immediately
+- ✓ Handles complex relationships
+- ✓ GeoJSON/PostGIS support
+- ✓ Customizable per-model permissions
+- ✓ Active community
+
+**Cons**
+- ⚠ UI is functional but not flashy (admin-focused, not customer-facing)
+- ⚠ Requires custom views for complex workflows
+
+**Best For**
+- Internal staff tool for data management
+- RBAC-driven access to all models
+- Quick setup with room to grow
+
+---
+
+### Option 2: SQLAlchemy Admin (SQLAdmin)
+
+**Why Consider It**
+
+Simpler alternative if you don't need geospatial UI features or complex RBAC.
+
+✅ **Strengths**
+- Lightweight, minimal configuration
+- Clean UI
+- Works with your SQLAlchemy models
+
+❌ **Limitations for Your Project**
+- Less flexible permission system
+- Weaker geospatial rendering
+- Limited custom action support
+
+**Verdict**: Good for simple admin needs, but Starlette Admin is more future-proof for your use case.
+
+---
+
+### Option 3: FastAPI Amis Admin
+
+**Why It Might Appeal**
+
+If you want a highly customizable, dashboard-heavy admin with charts and complex forms.
+
+✅ **Strengths**
+- Beautiful, modern UI
+- Supports complex dashboards
+- Works with SQLAlchemy
+
+❌ **Limitations for Your Project**
+- Steeper learning curve (AMIS schema DSL)
+- Overkill for internal tooling
+- More maintenance overhead
+
+**Verdict**: Consider if you need public-facing dashboards or complex visualization. Not recommended for internal-only admin.
+
+---
+
+### ❌ Options to Avoid
+
+**FastAPI Admin**: Requires Tortoise ORM (you use SQLAlchemy)
+**Piccolo Admin**: Requires Piccolo ORM (you use SQLAlchemy)
+
+---
+
+## Comparison Matrix for NMSampleLocations
+
+| Criteria | Starlette Admin | SQLAdmin | Amis Admin |
+|----------|----------------|----------|------------|
+| **SQLAlchemy Support** | ✅ Native | ✅ Native | ✅ Native |
+| **GeoAlchemy2/PostGIS** | ✅ Built-in | ⚠ Basic | ⚠ Basic |
+| **RBAC Implementation** | ✅ Flexible | ⚠ Manual | ✅ Good |
+| **Complex Relationships** | ✅ Excellent | ✅ Good | ✅ Good |
+| **Custom Actions** | ✅ Easy | ⚠ Limited | ✅ Flexible |
+| **Setup Time** | Medium | Fast | Slow |
+| **Maintenance Burden** | Low | Very Low | Medium-High |
+| **Future-Proof** | ✅ High | ✅ Medium | ⚠ Medium |
+
+---
+
+## Implementation Roadmap
+
+### Phase 1: Core Setup (Week 1)
+1. Install Starlette Admin
+2. Create basic ModelViews for top 5 models (Location, Thing, Sample, Observation, Sensor)
+3. Implement authentication provider using existing permissions.py
+4. Test geospatial field rendering
+
+### Phase 2: Authorization (Week 2)
+1. Implement role-based view filtering
+2. Add per-model permission checks (can_create, can_edit, can_delete)
+3. Apply data visibility rules (release_status filtering)
+
+### Phase 3: Custom Actions (Week 3)
+1. Bulk publish/unpublish actions
+2. GeoJSON export action
+3. Data quality validation actions
+
+### Phase 4: Advanced Features (Week 4+)
+1. Custom dashboard views
+2. Audit log integration
+3. Advanced search/filtering
+4. Workflow automation (e.g., provisional → approved status transitions)
+
+---
+
+## Alternatives if Starlette Admin Doesn't Work
+
+### Plan B: Django Admin (Separate Service)
+If you need enterprise-grade features:
+- Run Django admin as separate microservice
+- Share PostgreSQL database
+- Use for admin only, keep FastAPI for API
+
+**Tradeoff**: Added complexity, but best-in-class admin experience.
+
+### Plan C: Custom React Admin
+If internal team has strong frontend capacity:
+- React + Refine.dev or React-Admin
+- Full control over UX
+- More work upfront, but maximum flexibility
+
+**Tradeoff**: 2-3x development time, but perfect fit for your needs.
+
+---
+
+## Final Recommendation
+
+**Start with Starlette Admin.** It's the best fit for:
+- Your current tech stack (FastAPI + SQLAlchemy + PostGIS)
+- Your permission model (Admin/Editor/Viewer RBAC)
+- Your data model complexity (33+ models, geospatial, versioned)
+- Your timeline (production-ready admin in 2-4 weeks)
+
+If requirements evolve beyond CRUD (e.g., complex workflows, customer-facing dashboards), you can:
+1. Keep Starlette Admin for internal data management
+2. Build custom UI for specific workflows
+3. Migrate to Django Admin or custom solution
+
+**Low risk, high value, production-ready.**
+
+---
+
+## Next Steps
+
+1. **Prototype**: Spend 1 day building basic Starlette Admin setup for Location model
+2. **Evaluate**: Test with real data and your auth system
+3. **Decide**: If it works, expand to all models. If not, try SQLAdmin as backup.
+4. **Document**: Update this doc with learnings and final choice
+
+---
+
+## Resources
+
+- [Starlette Admin Documentation](https://jowilf.github.io/starlette-admin/)
+- [Starlette Admin + GeoAlchemy2 Example](https://github.com/jowilf/starlette-admin/tree/main/examples/sqla)
+- [SQLAlchemy Admin (SQLAdmin)](https://aminalaee.dev/sqladmin/)
+- [FastAPI Amis Admin](https://docs.amis.work/)
diff --git a/docs/MS_ACCESS_TO_STARLETTE_ADMIN_MIGRATION.md b/docs/MS_ACCESS_TO_STARLETTE_ADMIN_MIGRATION.md
new file mode 100644
index 00000000..944756d9
--- /dev/null
+++ b/docs/MS_ACCESS_TO_STARLETTE_ADMIN_MIGRATION.md
@@ -0,0 +1,582 @@
+# MS Access → Starlette Admin Migration Analysis
+
+## Critical Context: Staff's Current Admin Interface
+
+**Legacy System**: MS Access connected to SQL Server (AMPAPI)
+
+This fundamentally changes our evaluation. Staff are not accustomed to modern web UIs like OcotilloUI - they're accustomed to **MS Access forms and datasheets**.
+
+---
+
+## MS Access vs. Starlette Admin: Feature Comparison
+
+### What Staff Are Used To (MS Access)
+
+**MS Access Workflow**:
+1. Open Access database file (.accdb or .mdb)
+2. Navigate to **Tables** or **Forms**
+3. Use **Datasheet View** for list/table operations
+4. Use **Form View** for create/edit
+5. Run **Queries** for filtering/searching
+6. Use **Reports** for exports
+
+**MS Access Strengths**:
+- ✅ Familiar to non-technical users
+- ✅ Forms-based data entry
+- ✅ Datasheet view for bulk operations
+- ✅ Built-in search/filter
+- ✅ Direct SQL query access (Power Users)
+- ✅ Works offline (local file)
+
+**MS Access Weaknesses**:
+- ❌ Windows-only (no Mac/Linux/web)
+- ❌ Single-user concurrency issues (file locking)
+- ❌ No audit trail (who changed what)
+- ❌ Version control nightmare (binary files)
+- ❌ Security issues (file-based permissions)
+- ❌ No remote access without VPN/Remote Desktop
+
+---
+
+## Starlette Admin as MS Access Replacement
+
+### Feature Parity Matrix
+
+| Feature | MS Access | Starlette Admin | Comparison |
+|---------|-----------|----------------|------------|
+| **Datasheet View** | ✅ Familiar grid/table view | ✅ List view with sorting/filtering | ✅ **Equivalent** |
+| **Form View** | ✅ Custom forms for create/edit | ✅ Auto-generated forms | ✅ **Better** (web-based) |
+| **Search/Filter** | ✅ Filter-by-selection, query builder | ✅ Column filters, full-text search | ✅ **Equivalent** |
+| **CRUD Operations** | ✅ Create, Read, Update, Delete | ✅ Create, Read, Update, Delete | ✅ **Equivalent** |
+| **Relationships** | ✅ Combo boxes for foreign keys | ✅ Dropdown selects for foreign keys | ✅ **Equivalent** |
+| **Bulk Operations** | ✅ Datasheet multi-select + action | ✅ Bulk actions (select → action) | ✅ **Equivalent** |
+| **Data Validation** | ✅ Form validation rules | ✅ Pydantic/SQLAlchemy validation | ✅ **Better** (enforced in DB) |
+| **Export** | ✅ Export to Excel, CSV | ✅ CSV export (built-in) | ✅ **Equivalent** |
+| **Reports** | ✅ Access Reports | ❌ No built-in reports | ⚠️ **Missing** (use API + Excel) |
+| **Queries** | ✅ Query Designer, SQL view | ⚠️ Admin can filter, devs write SQL | ⚠️ **Partial** |
+| **Offline Access** | ✅ Works offline (local file) | ❌ Requires internet connection | ⚠️ **Web-only** |
+| **Multi-User** | ⚠️ Limited (file locking issues) | ✅ Full concurrent access | ✅ **Better** |
+| **Audit Trail** | ❌ No built-in audit log | ⚠️ Can add custom audit log | ✅ **Better** (if implemented) |
+| **Remote Access** | ❌ Requires VPN/RDP | ✅ Web-based, anywhere access | ✅ **Much better** |
+| **Cross-Platform** | ❌ Windows only | ✅ Any browser (Mac/Linux/iPad) | ✅ **Much better** |
+| **Security** | ⚠️ File-level permissions | ✅ User-based RBAC | ✅ **Better** |
+| **UI Polish** | ⚠️ 1990s look and feel | ⚠️ 2010s admin UI | ↔️ **Both functional** |
+
+---
+
+## Key Insights: Why Starlette Admin is a Good Fit
+
+### 1. **Similar Mental Model**
+
+**MS Access Users Think In Terms Of:**
+- Tables = Lists of records
+- Forms = Create/Edit screens
+- Datasheets = Browse/filter records
+- Queries = Find specific records
+
+**Starlette Admin Provides:**
+- **List View** = Access Datasheet View (grid of records with sorting/filtering)
+- **Create/Edit Forms** = Access Form View (auto-generated from model)
+- **Detail View** = Access Form View (read-only)
+- **Search** = Access Query-by-Form (filter records)
+
+**Visual Comparison**:
+
+```
+MS ACCESS DATASHEET VIEW STARLETTE ADMIN LIST VIEW
+┌──────────────────────────────────┐ ┌──────────────────────────────────┐
+│ ▼ Location Table │ │ 🔍 Search: ________ │
+│ ┌─────┬─────────┬──────────┬───┐ │ │ ┌─────┬─────────┬──────────┬───┐│
+│ │ ID │ PointID │ Latitude │...│ │ │ │ ID ↑│ PointID │ Latitude │...││
+│ ├─────┼─────────┼──────────┼───┤ │ │ ├─────┼─────────┼──────────┼───┤│
+│ │ 123 │ WELL001 │ 35.1234 │...│ │ │ │ 123 │ WELL001 │ 35.1234 │...││
+│ │ 124 │ WELL002 │ 35.2345 │...│ │ │ │ 124 │ WELL002 │ 35.2345 │...││
+│ └─────┴─────────┴──────────┴───┘ │ │ └─────┴─────────┴──────────┴───┘│
+│ [Filter] [Sort] [New Record] │ │ [1] 2 3 ... Next » [+ Create] │
+└──────────────────────────────────┘ └──────────────────────────────────┘
+```
+
+**Staff Won't Feel Lost** - The paradigm is nearly identical.
+
+---
+
+### 2. **UI Expectations Are Low (Good Thing!)**
+
+**Access Users Are Accustomed To:**
+- 🎨 **Plain, utilitarian UI** (gray forms, basic controls)
+- 📝 **Function over form** (no flashy animations or modern UX)
+- 🖱️ **Click-heavy workflows** (lots of navigation)
+
+**Starlette Admin Provides:**
+- 🎨 **Clean, functional admin UI** (Bootstrap-based, better than Access)
+- 📝 **Focus on CRUD efficiency** (same as Access)
+- 🖱️ **Straightforward navigation** (sidebar menu, breadcrumbs)
+
+**Expectation Management**: Staff coming from Access will likely **prefer** Starlette Admin's web UI to Access's dated interface.
+
+**OcotilloUI comparison**: If staff were using OcotilloUI (modern React UI with Material Design), switching to Starlette Admin would feel like a downgrade. But coming from **MS Access → Starlette Admin feels like an upgrade**.
+
+---
+
+### 3. **Missing Features Staff Might Miss**
+
+| MS Access Feature | How to Replicate in Starlette Admin | Effort |
+|-------------------|-------------------------------------|--------|
+| **Complex Queries** | Use FastAPI endpoints + Postman, or staff request custom filters | Medium |
+| **Access Reports** | Export CSV → open in Excel → create pivot tables/charts | Easy |
+| **Offline Work** | Not possible (web-based), need internet | N/A |
+| **Macros/VBA** | Replace with Starlette Admin custom actions | Medium-High |
+| **Linked Tables** | Use foreign key relationships (built-in) | Trivial |
+
+**Most Critical Loss**: **Offline access** (Access works without internet)
+
+**Mitigation**:
+- Web-based admin requires internet connection
+- If staff work in field without internet → provide CSV export for offline review
+- For data entry in field → use mobile-friendly admin UI (Starlette Admin is responsive)
+
+---
+
+### 4. **Gains Over MS Access**
+
+| Gain | Impact | Staff Benefit |
+|------|--------|---------------|
+| **Multi-user concurrency** | High | No more "database is locked" errors |
+| **Remote access** | High | Work from home without VPN/RDP |
+| **Cross-platform** | Medium | Mac/Linux users can access |
+| **Audit trail** | High | Track who changed what when |
+| **Better security** | High | User-level permissions, not file permissions |
+| **Version control** | Medium | Admin code is in Git, not binary .accdb files |
+| **Integration** | High | Same system as REST API (NMSampleLocations) |
+| **No corruption** | High | PostgreSQL is more robust than Access .accdb files |
+
+**Staff Will Appreciate**: No more Access file corruption, no more "compact & repair database", no more file locking issues.
+
+---
+
+## Recommended Migration Path: MS Access → Starlette Admin
+
+### Phase 0: Document Access Workflows (Week 0)
+
+**Action**: Catalog what staff **actually do** in MS Access:
+
+```
+TASK INVENTORY TEMPLATE:
+┌─────────────────────────────────────────────────────┐
+│ Task: Add new well location │
+│ Frequency: 3-5 times per week │
+│ Current Tool: MS Access "Location Entry Form" │
+│ Steps: │
+│ 1. Open Access DB │
+│ 2. Navigate to "Location Entry Form" │
+│ 3. Click "New Record" │
+│ 4. Fill in fields (PointID, Lat/Lon, etc.) │
+│ 5. Select Owner from dropdown │
+│ 6. Save record │
+│ Starlette Equivalent: │
+│ 1. Navigate to /admin/location │
+│ 2. Click "Create" │
+│ 3. Fill in fields │
+│ 4. Select Owner from dropdown │
+│ 5. Click "Save" │
+│ Migration Effort: Trivial (1:1 mapping) │
+└─────────────────────────────────────────────────────┘
+```
+
+**Deliverable**: Spreadsheet of:
+- Task name
+- Frequency (daily/weekly/monthly/rare)
+- Current Access workflow (steps)
+- Missing features in Starlette Admin (if any)
+- Workaround or custom implementation needed
+
+---
+
+### Phase 1: Prototype with 3 Most-Used Tables (Week 1)
+
+**Goal**: Build admin for staff's **top 3 most-used tables** from Access.
+
+**Example** (adjust based on your Access DB):
+1. **Location** (most frequent CRUD operations)
+2. **Well** (second most frequent)
+3. **WaterLevel** (third most frequent)
+
+**Implementation**:
+```python
+# admin/views.py
+from starlette_admin.contrib.sqla import ModelView
+
+class LocationAdmin(ModelView):
+ model = Location
+ name = "Locations"
+ icon = "fa fa-map-marker"
+
+ # Match Access datasheet column order
+ column_list = ['id', 'point_id', 'latitude', 'longitude', 'elevation']
+
+ # Match Access form field order
+ fields = ['point_id', 'latitude', 'longitude', 'elevation', 'county', 'owner']
+
+ # Enable search (like Access filter-by-selection)
+ search_fields = ['point_id', 'county']
+
+# Similar for Well, WaterLevel
+```
+
+**UAT**: Have 2-3 staff members test prototype:
+- Can they create a new record?
+- Can they find and edit existing records?
+- Can they export to CSV?
+- What feels confusing vs. Access?
+
+---
+
+### Phase 2: Add Remaining Tables (Week 2)
+
+Based on Access database schema, add all remaining tables to Starlette Admin.
+
+**Auto-generation**: Starlette Admin can auto-generate basic views for all SQLAlchemy models:
+
+```python
+# Quick approach: Auto-add all models
+from db import (
+ Location, Well, Spring, Sample, Sensor,
+ Contact, Owner, WaterLevel, WaterQuality, # ...
+)
+
+models = [Location, Well, Spring, Sample, Sensor, ...]
+
+for model in models:
+ admin.add_view(ModelView(model))
+```
+
+**Then customize** the 5-10 most frequently used tables with:
+- Custom column ordering
+- Search fields
+- Filters
+- Custom form layouts
+
+---
+
+### Phase 3: Replicate Access Forms (Week 3)
+
+**Access Forms → Starlette Admin Custom Forms**
+
+**Example: MS Access "Well Entry Form" (complex multi-section form)**
+
+```
+MS ACCESS FORM STARLETTE ADMIN EQUIVALENT
+┌────────────────────────────┐ ┌────────────────────────────┐
+│ Well Information │ │ Create Well │
+│ ┌────────────────────────┐ │ │ ┌────────────────────────┐ │
+│ │ PointID: [WELL001 ] │ │ │ │ PointID: [WELL001 ] │ │
+│ │ Latitude: [35.1234 ] │ │ │ │ Latitude: [35.1234 ] │ │
+│ │ Longitude: [-106.123 ] │ │ │ │ Longitude: [-106.123 ] │ │
+│ └────────────────────────┘ │ │ └────────────────────────┘ │
+│ Well Construction │ │ Well Construction │
+│ ┌────────────────────────┐ │ │ ┌────────────────────────┐ │
+│ │ Depth: [150 ] │ │ │ │ Depth: [150 ] │ │
+│ │ Casing: [6 inch ] │ │ │ │ Casing: [6 inch ] │ │
+│ └────────────────────────┘ │ │ └────────────────────────┘ │
+│ [Save] [Cancel] │ │ [Save] [Cancel] │
+└────────────────────────────┘ └────────────────────────────┘
+```
+
+**Implementation**:
+```python
+from starlette_admin import fields
+
+class WellAdmin(ModelView):
+ # Group fields into sections (like Access form sections)
+ fields = [
+ fields.StringField('point_id', label='PointID'),
+ fields.DecimalField('latitude'),
+ fields.DecimalField('longitude'),
+ '---', # Separator
+ fields.IntegerField('depth_ft', label='Depth (ft)'),
+ fields.StringField('casing_diameter', label='Casing'),
+ ]
+```
+
+**For Complex Forms**: If Access form has custom VBA logic, implement as custom Starlette Admin view or FastAPI endpoint.
+
+---
+
+### Phase 4: Replicate Access Queries (Week 3-4)
+
+**MS Access Saved Queries → Starlette Admin Filters or Custom Views**
+
+**Example: Access Query "Wells in Bernalillo County"**
+
+```sql
+-- Access Query (SQL View)
+SELECT * FROM Wells
+WHERE County = 'Bernalillo'
+ORDER BY PointID;
+```
+
+**Starlette Admin Equivalent**:
+
+**Option A: Built-in Filters**
+```python
+class WellAdmin(ModelView):
+ # Add county as filterable column
+ column_filters = ['county', 'depth_ft', 'drill_date']
+```
+
+Staff can then:
+1. Go to Wells list view
+2. Click "Filter" button
+3. Select "County = Bernalillo"
+4. Results show filtered list
+
+**Option B: Custom Dashboard Widget**
+```python
+from starlette_admin import CustomView
+
+class DashboardView(CustomView):
+ async def render(self, request):
+ # Query for Bernalillo wells
+ wells = session.query(Well).filter_by(county='Bernalillo').all()
+ return self.templates.TemplateResponse(
+ 'dashboard.html',
+ {'wells': wells}
+ )
+
+admin.add_view(DashboardView(name="Dashboard"))
+```
+
+**Option C: Saved Filters (Future Enhancement)**
+Could implement "saved search" feature similar to Access saved queries.
+
+---
+
+### Phase 5: Replicate Access Reports (Week 4 - Optional)
+
+**MS Access Reports → CSV Export + Excel Pivot Tables**
+
+**Access Reports Are Used For**:
+1. Summary reports (e.g., "Wells by County")
+2. Printable forms (e.g., "Well Construction Record")
+3. Data exports (e.g., "All water levels for 2024")
+
+**Starlette Admin Approach**:
+
+**Simple Reports** → CSV Export
+```python
+class WellAdmin(ModelView):
+ # Built-in CSV export
+ can_export = True # Adds "Export to CSV" button
+```
+
+Staff workflow:
+1. Filter wells (e.g., county = Bernalillo)
+2. Click "Export to CSV"
+3. Open in Excel
+4. Create pivot table or chart
+
+**Complex Reports** → FastAPI Endpoint + Excel Template
+```python
+# api/reports.py
+@router.get("/reports/wells-by-county")
+async def wells_by_county_report():
+ """Generate Excel report of wells grouped by county."""
+ # Use openpyxl or xlsxwriter to generate .xlsx file
+ return FileResponse("wells_by_county.xlsx")
+```
+
+**Printable Forms** → HTML/PDF Generation
+```python
+from starlette_admin import action
+
+class WellAdmin(ModelView):
+ @action(
+ name="print_construction_record",
+ text="Print Construction Record",
+ )
+ async def print_construction_record(self, request, pks):
+ # Generate PDF from template
+ pass
+```
+
+---
+
+## Staff Training: MS Access → Starlette Admin
+
+### Training Session 1: Core Concepts (1 hour)
+
+**Cover**:
+1. **What changed**: Desktop app → Web app
+2. **What stayed the same**: Tables, Forms, CRUD operations
+3. **Key improvements**: Multi-user, remote access, no file corruption
+
+**Demo**:
+- Access Datasheet View → Starlette List View (side-by-side comparison)
+- Access Form View → Starlette Create/Edit Form
+- Access Filter-by-Selection → Starlette Column Filters
+
+---
+
+### Training Session 2: Common Tasks (1 hour)
+
+**Hands-on exercises**:
+
+| Task | MS Access Steps | Starlette Admin Steps |
+|------|----------------|----------------------|
+| **Add new well** | Open DB → Forms → Well Entry → New | Login → Wells → Create |
+| **Find well by ID** | Open Wells table → Ctrl+F → Search | Go to Wells → Search box |
+| **Edit existing record** | Find record → Click → Edit | Find record → Click row → Edit |
+| **Export to Excel** | File → Export → Excel | Select records → Export → CSV |
+| **Filter by county** | Query Designer → Run query | Wells → Filter → County |
+
+---
+
+### Training Session 3: Differences & Workarounds (30 min)
+
+**What's Different**:
+
+| Access Feature | Starlette Admin | Workaround |
+|---------------|----------------|------------|
+| **Offline work** | ❌ Requires internet | Export CSV for offline review |
+| **Custom reports** | ❌ No built-in report designer | Export CSV → Excel pivot tables |
+| **Complex queries** | ⚠️ Limited query builder | Contact admin to add custom filters |
+| **VBA macros** | ❌ No scripting | Request custom actions from developers |
+
+---
+
+## Risk Assessment: MS Access → Starlette Admin
+
+| Risk | Likelihood | Impact | Mitigation |
+|------|-----------|--------|------------|
+| **Staff reject web UI** | Low | High | UAT before full rollout; keep Access read-only as fallback |
+| **Missing critical Access feature** | Medium | Medium | Document Access workflows upfront; implement workarounds |
+| **Internet dependency** | Low | Medium | Ensure reliable internet; provide CSV exports for offline |
+| **Learning curve** | Medium | Low | Staff are data-savvy (used Access), web UI is easier |
+| **Lost custom Access forms/macros** | High | Medium | Catalog all Access objects; prioritize most-used |
+
+**Overall Risk**: **Low-Medium**
+
+**Why Low Risk**:
+- Staff already understand database concepts (tables, forms, queries)
+- Starlette Admin is **simpler** than Access in many ways (no VBA, no query designer)
+- Web UI is **more intuitive** than Access for basic CRUD
+
+**Key Risk**: Custom Access VBA macros or complex reports that can't be easily replicated.
+
+**Mitigation**: Catalog all Access macros/reports during Phase 0; prioritize top 10 for custom implementation.
+
+---
+
+## Decision Matrix: MS Access Context Changes Everything
+
+### With MS Access Context, Should You Use Starlette Admin?
+
+**✅ YES - Even Stronger Case Now**
+
+| Factor | Without MS Access Context | With MS Access Context |
+|--------|--------------------------|----------------------|
+| **UI Expectations** | "Downgrade from OcotilloUI's modern React UI" | **"Upgrade from Access's 1990s UI"** |
+| **Feature Parity** | "Missing maps, forms, apps" | **"Has everything Access has (tables, forms, search)"** |
+| **Learning Curve** | "New paradigm for staff" | **"Familiar paradigm (tables → forms)"** |
+| **Staff Adoption** | "May resist change" | **"Will welcome web-based improvement"** |
+| **Implementation Effort** | "Need to match OcotilloUI features" | **"Just need CRUD + basic filters"** |
+
+**Conclusion**: MS Access users will **prefer Starlette Admin** to Access because:
+1. ✅ Web-based (no more file corruption, no more .accdb issues)
+2. ✅ Multi-user (no more "database locked" errors)
+3. ✅ Remote access (work from anywhere)
+4. ✅ Better security (user-level permissions)
+5. ✅ Modern UI (still functional, but nicer than Access)
+
+---
+
+## Updated Recommendation
+
+### Primary Recommendation: Starlette Admin (Monolithic)
+
+**Confidence Level**: **High** (was Medium, now High with Access context)
+
+**Why Confidence Increased**:
+- Staff mental model aligns perfectly with Starlette Admin
+- Staff will see this as an **upgrade**, not a lateral move
+- Lower UI expectations make Starlette Admin more than sufficient
+- Access features map 1:1 to Starlette Admin capabilities
+
+**Implementation Priority**:
+1. **Week 1**: Prototype with 3 most-used Access tables
+2. **Week 2**: Staff UAT (critical - get buy-in early)
+3. **Week 3**: Add all tables, replicate Access forms
+4. **Week 4**: Custom actions, bulk operations, exports
+5. **Week 5**: Production rollout, parallel with Access (read-only)
+
+**Parallel Operation Period**: 1 month
+- Keep Access database **read-only** (for reference/reports)
+- All new data entry in Starlette Admin
+- Staff can verify data in Access if needed
+
+**After 1 Month**: Retire Access completely (archive .accdb file)
+
+---
+
+## Next Steps
+
+1. **✅ Approve Starlette Admin** (even stronger case with MS Access context)
+2. **⏭️ Catalog Access workflows** (1-2 days)
+ - List all Access forms, queries, reports
+ - Identify top 10 most-used features
+ - Document any custom VBA macros
+3. **⏭️ Build prototype** (Week 1)
+ - 3 most-used tables
+ - Show to staff for feedback
+4. **⏭️ Staff UAT** (Week 2)
+ - Demo prototype side-by-side with Access
+ - Gather feedback on missing features
+5. **⏭️ Full implementation** (Week 3-5)
+6. **⏭️ Parallel operation** (1 month)
+7. **⏭️ Retire Access** (archive for reference)
+
+**Timeline**: **5-6 weeks** from approval to full Starlette Admin deployment.
+
+**Success Criteria**: Staff complete 95% of daily tasks in Starlette Admin without needing Access.
+
+---
+
+## Appendix: MS Access Database Inventory Template
+
+Use this template to document what exists in the legacy Access database:
+
+### Tables
+| Table Name | Record Count | Primary Use | Migration Priority |
+|------------|-------------|-------------|-------------------|
+| Location | 1,234 | Well/spring locations | P0 - Critical |
+| WaterLevel | 45,678 | Water level measurements | P0 - Critical |
+| Owner | 156 | Property owners | P1 - High |
+| ... | ... | ... | ... |
+
+### Forms
+| Form Name | Frequency of Use | Complexity | Migration Plan |
+|-----------|-----------------|-----------|----------------|
+| Location Entry | Daily | Simple | Starlette auto-form |
+| Well Inventory | Weekly | Complex (3 sections) | Custom Starlette view |
+| ... | ... | ... | ... |
+
+### Queries
+| Query Name | Use Case | Migration Plan |
+|------------|----------|----------------|
+| Wells in Bernalillo | Monthly report | Built-in filter |
+| Depth > 200 ft | Ad-hoc analysis | Built-in filter |
+| Custom complex query | Annual report | FastAPI endpoint |
+
+### Reports
+| Report Name | Output Format | Migration Plan |
+|-------------|---------------|----------------|
+| Well Summary | Printed PDF | CSV export → Excel |
+| Annual Water Levels | Excel spreadsheet | FastAPI endpoint |
+| ... | ... | ... |
+
+### Macros/VBA
+| Macro Name | Functionality | Migration Plan |
+|------------|--------------|----------------|
+| AutoPublish | Bulk update release_status | Starlette bulk action |
+| ValidateCoordinates | Check lat/lon in NM | Pydantic validator |
+| ... | ... | ... |
diff --git a/docs/OCOTILLOUI_STARLETTE_ADMIN_PARITY.md b/docs/OCOTILLOUI_STARLETTE_ADMIN_PARITY.md
new file mode 100644
index 00000000..2b9c33a5
--- /dev/null
+++ b/docs/OCOTILLOUI_STARLETTE_ADMIN_PARITY.md
@@ -0,0 +1,583 @@
+# OcotilloUI → Starlette Admin Feature Parity Analysis
+
+## Executive Summary
+
+This document analyzes the feature gap between **OcotilloUI** (React + Refine.dev admin dashboard) and **Starlette Admin** (Python-based admin framework) for NMSampleLocations during the migration period from AMPAPI.
+
+**Purpose**: Determine the minimum viable feature set required for Starlette Admin to temporarily replace OcotilloUI during the AMPAPI → NMSampleLocations database migration.
+
+**Key Finding**: ~80% of OcotilloUI features are standard CRUD operations that Starlette Admin auto-generates. The remaining ~20% require custom implementation (map views, specialized forms, batch upload).
+
+---
+
+## OcotilloUI Current Feature Inventory
+
+### Core Tech Stack
+
+**Frontend:**
+- React 18 + TypeScript
+- Refine.dev 4.x (admin framework)
+- Material UI 6.x (component library)
+- Mapbox GL JS 3.x (mapping)
+- TanStack Query (data fetching)
+- React Hook Form + Zod (form validation)
+
+**Backend API:**
+- NMSampleLocations FastAPI (PostgreSQL + PostGIS)
+- RESTful endpoints
+- OpenAPI schema generation
+
+**Authentication:**
+- Authentik (OAuth/OIDC provider)
+- Role-based access control
+
+---
+
+## Resource-by-Resource Comparison
+
+### 1. Core Tables (Standard CRUD)
+
+| Resource | OcotilloUI Pages | Starlette Admin Equivalent | Implementation Effort |
+|----------|------------------|---------------------------|----------------------|
+| **Asset** | List, Create, Edit, Show | `ModelView(Asset)` | ✅ **Trivial** - auto-generated |
+| **Contact** | List, Create, Edit, Show | `ModelView(Contact)` | ✅ **Trivial** - auto-generated |
+| **Location** | List, Create, Edit, Show | `ModelView(Location)` | ✅ **Trivial** - auto-generated |
+| **Sensor** | List, Create, Edit, Show | `ModelView(Sensor)` | ✅ **Trivial** - auto-generated |
+| **Sample** | List, Create, Edit, Show | `ModelView(Sample)` | ✅ **Trivial** - auto-generated |
+| **Group** | List, Create, Edit, Show | `ModelView(Group)` | ✅ **Trivial** - auto-generated |
+
+**Notes:**
+- All six resources have identical CRUD patterns
+- Starlette Admin auto-generates list views with pagination, search, sorting
+- Form fields are auto-generated from SQLAlchemy models
+- Relationships are handled automatically (foreign key dropdowns)
+
+**Example Implementation:**
+```python
+# admin_setup.py
+from starlette_admin.contrib.sqla import Admin, ModelView
+from db import Asset, Contact, Location, Sensor, Sample, Group
+
+admin = Admin(engine, title="NMSampleLocations Admin")
+
+# Auto-generates full CRUD for each model
+admin.add_view(ModelView(Asset))
+admin.add_view(ModelView(Contact))
+admin.add_view(ModelView(Location))
+admin.add_view(ModelView(Sensor))
+admin.add_view(ModelView(Sample))
+admin.add_view(ModelView(Group))
+```
+
+**Effort**: **1-2 hours** total for all six resources
+
+---
+
+### 2. Thing Resources (Nested Models)
+
+| Resource | OcotilloUI Implementation | Starlette Admin Approach | Effort |
+|----------|---------------------------|-------------------------|--------|
+| **Well** | Dedicated pages (List, Create, Edit, Show) | `ModelView(ThingWell)` with custom filters | ⚠️ **Easy** - minor customization |
+| **Spring** | Dedicated pages (List, Create, Show) | `ModelView(ThingSpring)` with custom filters | ⚠️ **Easy** - minor customization |
+| **Thing-ID-Link** | List, Create, Edit, Show | `ModelView(ThingIdLink)` | ✅ **Trivial** - auto-generated |
+| **Well-Screen** | List, Create, Edit, Show | `ModelView(WellScreen)` | ✅ **Trivial** - auto-generated |
+
+**Challenge**: OcotilloUI treats Wells and Springs as separate top-level resources, but they're actually subtypes of the polymorphic `Thing` model.
+
+**Starlette Admin Solution:**
+```python
+class WellAdmin(ModelView):
+ model = ThingWell
+ name = "Wells"
+ icon = "fa fa-tint"
+
+ # Filter to show only wells
+ def get_list_query(self, request):
+ return select(ThingWell).where(Thing.thing_type == 'well')
+
+ # Custom form to handle well-specific fields
+ fields = [
+ 'name', 'description', 'location_id',
+ 'well_depth', 'casing_diameter', 'drill_date', # well-specific
+ ]
+
+class SpringAdmin(ModelView):
+ model = ThingSpring
+ name = "Springs"
+
+ def get_list_query(self, request):
+ return select(ThingSpring).where(Thing.thing_type == 'spring')
+```
+
+**Effort**: **4-6 hours** (handling polymorphic models, custom filtering)
+
+---
+
+### 3. Observations (Time-Series Data)
+
+| Resource | OcotilloUI Pages | Starlette Admin Approach | Effort |
+|----------|------------------|-------------------------|--------|
+| **Groundwater Level Observation** | List, Create (no Edit/Show) | `ModelView(GroundwaterLevelObservation)` | ⚠️ **Easy** - basic CRUD |
+
+**Notes:**
+- OcotilloUI only implements List + Create (observations are typically immutable)
+- Starlette Admin can replicate this pattern with permission restrictions
+
+**Implementation:**
+```python
+class GroundwaterLevelObservationAdmin(ModelView):
+ model = GroundwaterLevelObservation
+ name = "Groundwater Levels"
+
+ # Allow create but not edit (observations are immutable)
+ can_edit = False
+ can_delete = False # Or only for admins
+
+ # Custom list display
+ column_list = ['observation_date', 'water_level_value', 'sensor', 'location']
+ column_sortable_list = ['observation_date', 'water_level_value']
+ column_default_sort = ('observation_date', True) # descending
+```
+
+**Effort**: **2-3 hours**
+
+---
+
+### 4. Lexicon (Controlled Vocabulary)
+
+| Resource | OcotilloUI Implementation | Starlette Admin Approach | Effort |
+|----------|---------------------------|-------------------------|--------|
+| **Lexicon** | Hierarchical list (Categories → Terms) | Two `ModelView`s (Category, Term) | ⚠️ **Medium** - custom UI |
+| **Term** | Create, Edit (nested under category) | `ModelView(LexiconTerm)` | ⚠️ **Easy** |
+| **Category** | Create, Edit | `ModelView(LexiconCategory)` | ⚠️ **Easy** |
+
+**OcotilloUI Pattern:**
+- Single "Lexicon" page showing all categories
+- Expandable/collapsible categories
+- Inline term creation
+
+**Starlette Admin Pattern:**
+- Separate views for Categories and Terms
+- Category dropdown in Term form (foreign key)
+- Less hierarchical UI, more traditional CRUD
+
+**Trade-off**: Starlette Admin won't replicate the nested expandable UI, but will provide functional CRUD.
+
+```python
+admin.add_view(ModelView(LexiconCategory, name="Lexicon Categories"))
+admin.add_view(ModelView(LexiconTerm, name="Lexicon Terms"))
+```
+
+**Effort**: **2-3 hours** (acceptable UX difference)
+
+---
+
+### 5. Custom Forms (Complex Workflows)
+
+| Form | OcotilloUI Implementation | Starlette Admin Feasibility | Recommendation |
+|------|---------------------------|----------------------------|----------------|
+| **Well Inventory Form** | Multi-step wizard (stepper UI), Mapbox geocoding, owner search dialog | ❌ **Not feasible** in Starlette Admin | **Defer** - keep as API endpoint, add later |
+| **Groundwater Level Form** | Multi-step wizard, well selection, batch entry | ❌ **Not feasible** in Starlette Admin | **Defer** - use direct model CRUD instead |
+
+**Analysis:**
+These forms are **specialized data entry workflows** with:
+- Multi-step wizards
+- Real-time geocoding and validation
+- Complex cross-model validation
+- Custom UX optimizations
+
+**Migration Strategy:**
+1. **Phase 1 (Immediate)**: Skip custom forms, use direct CRUD on underlying models
+ - Staff can create Wells directly via Well CRUD
+ - Staff can create Observations directly via Observation CRUD
+2. **Phase 2 (Post-Migration)**: Re-implement forms as:
+ - **Option A**: Custom Starlette Admin views (more work)
+ - **Option B**: Separate React mini-app mounted alongside Starlette Admin
+ - **Option C**: Restore OcotilloUI for forms only
+
+**Effort**: **N/A** (out of scope for MVP)
+
+---
+
+### 6. Apps (Specialized Tools)
+
+| App | OcotilloUI Implementation | Starlette Admin Feasibility | Recommendation |
+|-----|---------------------------|----------------------------|----------------|
+| **Hydrograph Corrector** | Interactive chart editing (ECharts), data correction UI | ❌ **Not feasible** | **Defer** - "Coming Soon" status in OcotilloUI |
+| **Water Chemistry Import** | CSV upload, validation, preview, batch import | ⚠️ **Possible** with custom view | **Defer** or implement as custom action |
+
+**Analysis:**
+- **Hydrograph Corrector**: Currently "coming soon" in OcotilloUI, can remain deferred
+- **Water Chemistry Import**: Also "coming soon", could be implemented as Starlette Admin custom action
+
+**CSV Import Pattern (if needed):**
+```python
+from starlette_admin import action
+
+class SampleAdmin(ModelView):
+ @action(
+ name="import_csv",
+ text="Import CSV",
+ confirmation="Upload and import samples from CSV?",
+ )
+ async def import_csv(self, request, pks):
+ # Custom file upload and import logic
+ pass
+```
+
+**Effort**: **8-12 hours** per app if implemented; **0 hours** if deferred
+
+---
+
+### 7. Map View (Geospatial Visualization)
+
+| Feature | OcotilloUI Implementation | Starlette Admin Feasibility | Recommendation |
+|---------|---------------------------|----------------------------|----------------|
+| **Map View** | Mapbox GL JS, location markers, popups, spatial search | ❌ **Not feasible** natively | **Defer** or custom view |
+
+**Analysis:**
+OcotilloUI's map view provides:
+- Interactive Mapbox map showing all locations
+- Popup details on click
+- Spatial search (draw polygon, query locations)
+- Basemap selection
+
+**Starlette Admin Limitations:**
+- No built-in mapping UI
+- Can display WKT/coordinates in forms, but not interactive maps
+
+**Options:**
+1. **Defer**: Staff can view coordinates in Location list view (lat/lon columns)
+2. **Custom View**: Create custom Starlette Admin page with embedded Mapbox map
+3. **External Tool**: Use QGIS or PostGIS queries for spatial analysis
+
+**Effort**:
+- Option 1: **0 hours** (acceptable for MVP)
+- Option 2: **16-20 hours** (full custom view)
+- Option 3: **N/A** (use existing tools)
+
+---
+
+## Feature Parity Matrix
+
+| Feature Category | OcotilloUI Capability | Starlette Admin Capability | Gap | Priority |
+|------------------|----------------------|---------------------------|-----|----------|
+| **CRUD Operations** | ✅ Full CRUD for 10+ models | ✅ Auto-generated CRUD | ✅ **No gap** | **P0** |
+| **Authentication** | ✅ Authentik OAuth | ✅ Custom auth provider | ✅ **No gap** (integration needed) | **P0** |
+| **RBAC Permissions** | ✅ Role-based resource access | ✅ Can/Create/Edit/Delete per model | ✅ **No gap** | **P0** |
+| **Search/Filter** | ✅ Full-text search, column filters | ✅ Built-in search and filters | ✅ **No gap** | **P0** |
+| **Pagination** | ✅ DataGrid pagination | ✅ Built-in pagination | ✅ **No gap** | **P0** |
+| **Relationships** | ✅ Foreign key dropdowns | ✅ Auto-generated selects | ✅ **No gap** | **P0** |
+| **Data Validation** | ✅ Zod schemas | ✅ Pydantic/SQLAlchemy validation | ✅ **No gap** (server-side) | **P0** |
+| **Map Visualization** | ✅ Interactive Mapbox maps | ❌ Not supported | ⚠️ **Major gap** | **P2** (defer) |
+| **Multi-Step Forms** | ✅ Well Inventory, GW Level wizards | ❌ Not supported | ⚠️ **Major gap** | **P2** (defer) |
+| **CSV Import/Export** | ✅ Chemistry import app | ⚠️ Possible via custom actions | ⚠️ **Moderate gap** | **P2** (defer) |
+| **Chart Editing** | ✅ Hydrograph corrector | ❌ Not supported | ⚠️ **Major gap** | **P3** (coming soon) |
+| **Batch Operations** | ⚠️ Limited | ✅ Built-in bulk actions | ✅ **Better in SA** | **P1** |
+| **Audit Logging** | ❌ Not implemented | ⚠️ Possible via custom logic | ⚠️ **No gap** (neither has it) | **P3** |
+
+**Legend:**
+- **P0**: Critical for MVP (must have)
+- **P1**: High value (should have)
+- **P2**: Nice to have (can defer)
+- **P3**: Future enhancement
+
+---
+
+## MVP Feature Set for Starlette Admin
+
+### Phase 1: Core CRUD (Week 1) - **P0**
+
+**Goal**: Replace OcotilloUI for basic data management
+
+**Deliverables:**
+1. ✅ Authentication integration (existing NMSampleLocations auth)
+2. ✅ CRUD for 10 core models:
+ - Asset, Contact, Location, Sensor, Sample, Group
+ - ThingWell, ThingSpring, ThingIdLink, WellScreen
+3. ✅ Groundwater Level Observation (list + create only)
+4. ✅ Lexicon (categories + terms as separate views)
+
+**Estimated Effort**: **8-12 hours**
+
+**Acceptance Criteria:**
+- [ ] Staff can log in with existing credentials
+- [ ] All 10 models have list, create, edit, show pages
+- [ ] Relationships render as dropdowns (e.g., Sample → Location)
+- [ ] Search and pagination work on all list views
+- [ ] Data validation prevents invalid entries
+
+---
+
+### Phase 2: Authorization & Data Visibility (Week 2) - **P0**
+
+**Goal**: Match OcotilloUI's permission model
+
+**Deliverables:**
+1. ✅ Role-based access control (Admin, Editor, Viewer)
+ - Admin: Full CRUD on all models
+ - Editor: Create/Edit/View, no delete
+ - Viewer: Read-only access
+2. ✅ Data visibility controls (`release_status` field)
+ - Public-released data visible to all
+ - Provisional/internal data visible to staff only
+3. ✅ Per-model permission overrides
+
+**Implementation:**
+```python
+from starlette_admin.auth import AuthProvider
+
+class NMBGMRAuthProvider(AuthProvider):
+ async def get_admin_user(self, request):
+ user = get_current_user(request)
+ return AdminUser(
+ username=user.name,
+ roles=user.permissions # ['admin', 'editor', 'viewer']
+ )
+
+class LocationAdmin(ModelView):
+ def can_delete(self, request):
+ return 'admin' in request.state.user.roles
+
+ def can_edit(self, request):
+ return 'admin' in request.state.user.roles or 'editor' in request.state.user.roles
+```
+
+**Estimated Effort**: **6-8 hours**
+
+**Acceptance Criteria:**
+- [ ] Viewers cannot create/edit/delete any records
+- [ ] Editors can create/edit but not delete
+- [ ] Admins have full access
+- [ ] Non-authenticated users see only public-released data
+
+---
+
+### Phase 3: Custom Actions (Week 3) - **P1**
+
+**Goal**: Add bulk operations and export functionality
+
+**Deliverables:**
+1. ✅ Bulk publish/unpublish (change `release_status`)
+2. ✅ GeoJSON export for locations
+3. ✅ CSV export for all models
+
+**Implementation:**
+```python
+class LocationAdmin(ModelView):
+ actions = ['bulk_publish', 'export_geojson']
+
+ @action(
+ name="bulk_publish",
+ text="Publish Selected",
+ confirmation="Make selected locations public?",
+ )
+ async def bulk_publish(self, request, pks):
+ await session.execute(
+ update(Location)
+ .where(Location.id.in_(pks))
+ .values(release_status='public')
+ )
+
+ @action(
+ name="export_geojson",
+ text="Export GeoJSON",
+ )
+ async def export_geojson(self, request, pks):
+ # Generate GeoJSON FeatureCollection
+ pass
+```
+
+**Estimated Effort**: **8-10 hours**
+
+---
+
+### Phase 4: Polish & Optimization (Week 4) - **P1/P2**
+
+**Deliverables:**
+1. ⚠️ Custom dashboard (summary stats, recent activity)
+2. ⚠️ Advanced search (full-text across all models)
+3. ⚠️ Custom list columns (show related data)
+4. ⚠️ Improved geospatial field rendering (show coordinates)
+
+**Estimated Effort**: **10-12 hours**
+
+---
+
+## Deferred Features (Post-Migration)
+
+### Not Included in MVP
+
+| Feature | Reason for Deferral | Future Plan |
+|---------|---------------------|------------|
+| **Map View** | Complex custom UI required | Option 1: Custom Starlette view
Option 2: Keep OcotilloUI for maps only
Option 3: Use QGIS |
+| **Well Inventory Form** | Multi-step wizard not in SA core | Re-implement as custom form or restore OcotilloUI |
+| **Groundwater Level Form** | Multi-step wizard not in SA core | Use direct Observation CRUD for now |
+| **Hydrograph Corrector** | Already "coming soon" in OcotilloUI | Build as separate tool when needed |
+| **Water Chemistry Import** | Already "coming soon" in OcotilloUI | Implement as SA custom action later |
+
+**Rationale**: These features represent **~20% of usage** but **~80% of implementation effort**. Deferring allows for faster migration and validation of core functionality.
+
+---
+
+## Implementation Roadmap
+
+### Week 1: Core CRUD Setup
+- **Day 1-2**: Install Starlette Admin, create basic setup, test with 2-3 models
+- **Day 3-4**: Add all 10 core models, test CRUD operations
+- **Day 5**: QA testing, bug fixes
+
+**Milestone**: Staff can perform basic CRUD on all models
+
+---
+
+### Week 2: Authorization
+- **Day 1-2**: Implement auth provider integration
+- **Day 3**: Add role-based permissions
+- **Day 4**: Add data visibility controls
+- **Day 5**: QA testing with different user roles
+
+**Milestone**: Permission model matches OcotilloUI
+
+---
+
+### Week 3: Custom Actions
+- **Day 1-2**: Bulk publish/unpublish actions
+- **Day 3**: GeoJSON export
+- **Day 4**: CSV export
+- **Day 5**: QA testing, documentation
+
+**Milestone**: Staff can perform bulk operations
+
+---
+
+### Week 4: Polish & Launch
+- **Day 1-3**: Dashboard, advanced search, custom columns
+- **Day 4**: User acceptance testing
+- **Day 5**: Production deployment, monitor for issues
+
+**Milestone**: Starlette Admin replaces OcotilloUI for core workflows
+
+---
+
+## Success Criteria
+
+### MVP Launch Checklist
+
+- [ ] All 10 core models have full CRUD functionality
+- [ ] Authentication works with existing NMSampleLocations users
+- [ ] Role-based permissions match OcotilloUI
+- [ ] Data visibility controls enforce public/provisional status
+- [ ] Search, filter, pagination work correctly
+- [ ] Foreign key relationships render as dropdowns
+- [ ] Bulk publish/unpublish actions work
+- [ ] CSV export works for all models
+- [ ] GeoJSON export works for locations
+- [ ] No data loss during migration
+- [ ] Performance is acceptable (< 2s page load)
+
+### Post-Launch Metrics
+
+**Measure after 2 weeks of use:**
+- User adoption rate (% of staff using Starlette Admin vs. OcotilloUI)
+- User satisfaction score (survey)
+- Bug reports per week
+- Feature requests vs. deferred features list
+
+---
+
+## Risk Assessment
+
+| Risk | Impact | Likelihood | Mitigation |
+|------|--------|-----------|------------|
+| **Staff reject Starlette Admin UX** | High | Medium | Conduct UAT before full cutover; keep OcotilloUI available as backup |
+| **Missing critical workflow** | High | Low | Identify all workflows upfront; defer non-critical features |
+| **Performance issues with large datasets** | Medium | Low | Add pagination, indexing; test with production data volume |
+| **Auth integration complexity** | Medium | Medium | Test auth early; use existing NMSampleLocations auth code |
+| **Geospatial field rendering issues** | Low | Medium | Acceptable to show WKT/coords as text for MVP |
+
+---
+
+## Decision: Go/No-Go for Starlette Admin
+
+### ✅ Proceed with Starlette Admin If:
+- Core CRUD needs are met (10 models)
+- Staff can tolerate deferred features (maps, forms) for 2-4 weeks
+- MVP can be delivered in 2-3 weeks
+
+### ❌ Use Alternative If:
+- Map view is non-negotiable for daily work → **Keep OcotilloUI for maps**
+- Well Inventory Form is critical path → **Keep OcotilloUI or build custom form**
+- Staff need all current features → **Keep OcotilloUI, revisit after migration**
+
+---
+
+## Recommendation
+
+**Proceed with Starlette Admin as temporary admin UI** with these caveats:
+
+1. **MVP Scope**: 10 core models + auth + permissions + bulk actions (3-4 weeks)
+2. **Deferred Features**: Maps, custom forms, specialized apps (re-add post-migration)
+3. **Parallel Operation**: Keep OcotilloUI available as fallback for first month
+4. **User Training**: Brief staff on UX differences and workarounds
+
+**Total Estimated Effort**: **32-42 hours** (1 developer, 4 weeks part-time)
+
+**Risk Level**: **Low-Medium** (core functionality is straightforward, deferred features are edge cases)
+
+**Value**: **High** (unblocks AMPAPI migration, validates new database schema, reduces dependency on React frontend)
+
+---
+
+## Appendix: Model Mapping
+
+### NMSampleLocations Models → Starlette Admin Views
+
+From `db/__init__.py`, NMSampleLocations has **50+ models**. OcotilloUI currently exposes **~15 resources**. Here's the mapping:
+
+| NMSampleLocations Model | OcotilloUI Resource | Starlette Admin Priority |
+|------------------------|---------------------|-------------------------|
+| `Asset` | ✅ asset | P0 - Core MVP |
+| `Contact` | ✅ contact | P0 - Core MVP |
+| `Location` | ✅ location | P0 - Core MVP |
+| `Sensor` | ✅ sensor | P0 - Core MVP |
+| `Sample` | ✅ sample | P0 - Core MVP |
+| `Group` | ✅ group | P0 - Core MVP |
+| `ThingWell` | ✅ well | P0 - Core MVP |
+| `ThingSpring` | ✅ spring | P0 - Core MVP |
+| `ThingIdLink` | ✅ thing-id-link | P0 - Core MVP |
+| `WellScreen` | ✅ well-screen | P0 - Core MVP |
+| `GroundwaterLevelObservation` | ✅ groundwater-level-observation | P0 - Core MVP |
+| `LexiconCategory` | ✅ lexicon (category) | P0 - Core MVP |
+| `LexiconTerm` | ✅ lexicon (term) | P0 - Core MVP |
+| `Deployment` | ❌ Not in OcotilloUI | P1 - Add later |
+| `Observation` | ❌ Not in OcotilloUI (base class) | P1 - Add later |
+| `Thing` | ❌ Not in OcotilloUI (base class) | P1 - Add later |
+| `Publication` | ❌ Not in OcotilloUI | P2 - Add later |
+| `DataProvenance` | ❌ Not in OcotilloUI | P2 - Add later |
+| `AquiferSystem` | ❌ Not in OcotilloUI | P2 - Add later |
+| `GeologicFormation` | ❌ Not in OcotilloUI | P2 - Add later |
+| *(35+ more models)* | ❌ Not exposed in OcotilloUI | P2/P3 - Future |
+
+**Key Insight**: OcotilloUI only exposes **~25% of NMSampleLocations models**. Starlette Admin can initially match this subset, then expand to cover more models over time.
+
+---
+
+## Next Steps
+
+1. **Review this document** with stakeholders (data managers, developers)
+2. **Confirm MVP scope** - are deferred features acceptable?
+3. **Prototype Starlette Admin** with 2-3 models (4-6 hours)
+4. **User acceptance testing** - show prototype to staff, gather feedback
+5. **Go/No-Go decision** based on UAT feedback
+6. **Full implementation** if approved (follow 4-week roadmap)
+
+---
+
+## Resources
+
+- [Starlette Admin Documentation](https://jowilf.github.io/starlette-admin/)
+- [Starlette Admin SQLAlchemy Example](https://github.com/jowilf/starlette-admin/tree/main/examples/sqla)
+- [ADMIN_FRAMEWORK_EVALUATION.md](./ADMIN_FRAMEWORK_EVALUATION.md) - Technical framework comparison
+- [OcotilloUI Source Code](../../OcotilloUI/) - Current admin implementation
+- [NMSampleLocations Models](../db/) - Database schema