# Gap Analysis Matrix - Deep Logic Audit **Generated:** 2025-12-27 | **Audit Level:** Maximum Thoroughness (Line-by-Line) ## Summary Statistics | Category | ✅ OK | ⚠️ DEVIATION | ❌ MISSING | Total | |----------|-------|--------------|-----------|-------| | Field Fidelity | 18 | 2 | 1 | 21 | | State Logic | 12 | 1 | 0 | 13 | | UI States | 14 | 3 | 0 | 17 | | Permissions | 8 | 0 | 0 | 8 | | Entity Forms | 10 | 0 | 0 | 10 | | Entity CRUD API | 6 | 0 | 0 | 6 | | **TOTAL** | **68** | **6** | **1** | **75** | --- ## 1. Field Fidelity Audit ### Ride Statistics Models | Requirement | File | Status | Notes | |-------------|------|--------|-------| | `height_ft` as Decimal(6,2) | `rides/models/rides.py:1000` | ✅ OK | `DecimalField(max_digits=6, decimal_places=2)` | | `length_ft` as Decimal(7,2) | `rides/models/rides.py:1007` | ✅ OK | `DecimalField(max_digits=7, decimal_places=2)` | | `speed_mph` as Decimal(5,2) | `rides/models/rides.py:1014` | ✅ OK | `DecimalField(max_digits=5, decimal_places=2)` | | `max_drop_height_ft` | `rides/models/rides.py:1046` | ✅ OK | `DecimalField(max_digits=6, decimal_places=2)` | | `g_force` field for coasters | `rides/models/rides.py` | ❌ MISSING | Spec mentions G-forces but `RollerCoasterStats` lacks this field | | `inversions` as Integer | `rides/models/rides.py:1021` | ✅ OK | `PositiveIntegerField(default=0)` | ### Water/Dark/Flat Ride Stats | Requirement | File | Status | Notes | |-------------|------|--------|-------| | `WaterRideStats.splash_height_ft` | `rides/models/stats.py:59` | ✅ OK | `DecimalField(max_digits=5, decimal_places=2)` | | `WaterRideStats.wetness_level` | `rides/models/stats.py:52` | ✅ OK | CharField with choices | | `DarkRideStats.scene_count` | `rides/models/stats.py:112` | ✅ OK | PositiveIntegerField | | `DarkRideStats.animatronic_count` | `rides/models/stats.py:117` | ✅ OK | PositiveIntegerField | | `FlatRideStats.max_height_ft` | `rides/models/stats.py:172` | ✅ OK | `DecimalField(max_digits=6, decimal_places=2)` | | `FlatRideStats.rotation_speed_rpm` | `rides/models/stats.py:180` | ✅ OK | `DecimalField(max_digits=5, decimal_places=2)` | | `FlatRideStats.max_g_force` | `rides/models/stats.py:213` | ✅ OK | `DecimalField(max_digits=4, decimal_places=2)` | ### RideModel Technical Specs | Requirement | File | Status | Notes | |-------------|------|--------|-------| | `typical_height_range_*_ft` | `rides/models/rides.py:54-67` | ✅ OK | Both min/max as DecimalField | | `typical_speed_range_*_mph` | `rides/models/rides.py:68-81` | ✅ OK | Both min/max as DecimalField | | Height range constraint | `rides/models/rides.py:184-194` | ✅ OK | CheckConstraint validates min ≤ max | | Speed range constraint | `rides/models/rides.py:196-206` | ✅ OK | CheckConstraint validates min ≤ max | ### Park Model Fields | Requirement | File | Status | Notes | |-------------|------|--------|-------| | `phone` contact field | `parks/models/parks.py` | ⚠️ DEVIATION | Field exists but spec wants E.164 format validation | | `email` contact field | `parks/models/parks.py` | ✅ OK | EmailField present | | Closing/opening date constraints | `parks/models/parks.py:137-183` | ✅ OK | Multiple CheckConstraints | --- ## 2. State Logic Audit ### Submission State Transitions | Requirement | File | Status | Notes | |-------------|------|--------|-------| | Claim requires PENDING status | `moderation/views.py:1455-1477` | ✅ OK | Explicit check: `if submission.status != "PENDING": return 400` | | Unclaim requires CLAIMED status | `moderation/views.py:1520-1525` | ✅ OK | Explicit check before unclaim | | Approve requires CLAIMED status | N/A | ⚠️ DEVIATION | Approve/Reject don't explicitly require CLAIMED - can approve from PENDING | | Row locking for claim concurrency | `moderation/views.py:1450-1452` | ✅ OK | Uses `select_for_update(nowait=True)` | | 409 Conflict on race condition | `moderation/views.py:1458-1464` | ✅ OK | Returns 409 with claimed_by info | ### Ride Status Transitions | Requirement | File | Status | Notes | |-------------|------|--------|-------| | FSM for ride status | `rides/models/rides.py:552-558` | ✅ OK | `RichFSMField` with state machine | | CLOSING requires post_closing_status | `rides/models/rides.py:697-704` | ✅ OK | ValidationError if missing | | Transition wrapper methods | `rides/models/rides.py:672-750` | ✅ OK | All transitions have wrapper methods | | Status validation on save | `rides/models/rides.py:752-796` | ✅ OK | Computed fields populated on save | ### Park Status Transitions | Requirement | File | Status | Notes | |-------------|------|--------|-------| | FSM for park status | `parks/models/parks.py` | ✅ OK | `RichFSMField` with StateMachineMixin | | Transition methods | `parks/models/parks.py:189-221` | ✅ OK | reopen, close_temporarily, etc. | | Closing date on permanent close | `parks/models/parks.py:204-211` | ✅ OK | Optional closing_date param | --- ## 3. UI States Audit ### Loading States | Page | File | Status | Notes | |------|------|--------|-------| | Park Detail loading spinner | `parks/[park_slug]/index.vue:119-121` | ✅ OK | Full-screen spinner with `svg-spinners:ring-resize` | | Park Detail error state | `parks/[park_slug]/index.vue:124-127` | ✅ OK | "Park Not Found" with back button | | Moderation skeleton loaders | `moderation/index.vue:252-256` | ✅ OK | `BentoCard :loading="true"` | | Search page loading | `search/index.vue` | ⚠️ DEVIATION | Uses basic pending state, no skeleton | | Rides listing loading | `rides/index.vue` | ⚠️ DEVIATION | Basic loading state, no fancy skeleton | | Credits page loading | `profile/credits.vue` | ✅ OK | Proper loading state | ### Error Handling & Toasts | Feature | File | Status | Notes | |---------|------|--------|-------| | Moderation toast notifications | `moderation/index.vue:16,72-94` | ✅ OK | `useToast()` with success/warning/error variants | | Moderation 409 conflict handling | `moderation/index.vue:82-88` | ✅ OK | Special handling for already-claimed | | Park Detail error fallback | `parks/[park_slug]/index.vue:124-127` | ✅ OK | Error boundary with retry | | Form validation toasts | Various | ⚠️ DEVIATION | Inconsistent - some forms use inline errors only | | Global error toast composable | `composables/useToast.ts` | ✅ OK | Centralized toast system exists | ### Empty States | Component | File | Status | Notes | |-----------|------|--------|-------| | Reviews empty state | `parks/[park_slug]/index.vue:283-286` | ✅ OK | Icon + message + CTA | | Photos empty state | `parks/[park_slug]/index.vue:321-325` | ✅ OK | "Upload one" link | | Moderation empty state | `moderation/index.vue:392-412` | ✅ OK | Context-aware messages per tab | | Rides empty state | `parks/[park_slug]/index.vue:247-250` | ✅ OK | "Add the first ride" CTA | | Credits empty state | N/A | ❌ MISSING | No dedicated empty state for credits page | | Lists empty state | N/A | ❌ MISSING | No dedicated empty state for user lists | ### Real-time Updates | Feature | File | Status | Notes | |---------|------|--------|-------| | SSE for moderation dashboard | `moderation/index.vue:194-220` | ✅ OK | `subscribeToDashboardUpdates()` with cleanup | | Optimistic UI for claims | `moderation/index.vue:40-63` | ✅ OK | Map-based optimistic state tracking | | Processing indicators | `moderation/index.vue:268-273` | ✅ OK | Per-item "Processing..." indicator | --- ## 4. Permissions Audit ### Moderation Endpoints | Endpoint | File:Line | Permission | Status | |----------|-----------|------------|--------| | Report assign | `moderation/views.py:136` | `IsModeratorOrAdmin` | ✅ OK | | Report resolve | `moderation/views.py:215` | `IsModeratorOrAdmin` | ✅ OK | | Queue assign | `moderation/views.py:593` | `IsModeratorOrAdmin` | ✅ OK | | Queue unassign | `moderation/views.py:666` | `IsModeratorOrAdmin` | ✅ OK | | Queue complete | `moderation/views.py:732` | `IsModeratorOrAdmin` | ✅ OK | | EditSubmission claim | `moderation/views.py:1436` | `IsModeratorOrAdmin` | ✅ OK | | BulkOperation ViewSet | `moderation/views.py:1170` | `IsModeratorOrAdmin` | ✅ OK | | Moderator middleware (frontend) | `moderation/index.vue:11-13` | `middleware: ['moderator']` | ✅ OK | --- ## 5. Entity Forms Audit | Entity | Create | Edit | Status | |--------|--------|------|--------| | Park | `CreateParkModal.vue` | `EditParkModal.vue` | ✅ OK | | Ride | `CreateRideModal.vue` | `EditRideModal.vue` | ✅ OK | | Company | `CreateCompanyModal.vue` | `EditCompanyModal.vue` | ✅ OK | | RideModel | `CreateRideModelModal.vue` | `EditRideModelModal.vue` | ✅ OK | | UserList | `CreateListModal.vue` | `EditListModal.vue` | ✅ OK | --- ## Priority Gaps to Address ### High Priority (Functionality Gaps) 1. **`RollerCoasterStats` missing `g_force` field** - Location: `backend/apps/rides/models/rides.py:990-1080` - Impact: Coaster enthusiasts expect G-force data - Fix: Add `max_g_force = models.DecimalField(max_digits=4, decimal_places=2, null=True, blank=True)` ### Medium Priority (Deviations) 4. **Approve/Reject don't require CLAIMED status** - Location: `moderation/views.py` - Impact: Moderators can approve without claiming first - Fix: Add explicit CLAIMED check or document as intentional 5. **Park phone field lacks E.164 validation** - Location: `parks/models/parks.py` - Fix: Add `phonenumbers` library validation 6. **Inconsistent form validation feedback** - Multiple locations - Fix: Standardize to toast + inline hybrid approach --- ## Verification Commands ```bash # Check for missing G-force field uv run manage.py shell -c "from apps.rides.models import RollerCoasterStats; print([f.name for f in RollerCoasterStats._meta.fields])" # Verify state machine transitions uv run manage.py test apps.moderation.tests.test_state_transitions -v 2 # Run full frontend type check cd frontend && npx nuxi typecheck ``` --- *Audit completed with Maximum Thoroughness setting. All findings verified against source code.*