# 📱 NumoniX Ear Training & Access Control Engine: Flutter API Integration Guide

**Version:** 3.2  
**Specification:** Functional Ear Training CMS & Engine Automated Model  
**Last Updated:** August 2026

This document outlines the API endpoints and integration specifications for the Flutter mobile application to communicate with the **NumoniX Automated Ear Training Engine** and the **Unified Access Control & Subscription System**.

---

## 1. Access Control & Tier System Specification

The application operates on a 3-tier subscription model combined with a 3-level override hierarchy.

### 1.1 Subscription Tiers
- **Tier 1 (Free / Guest):** Unregistered guests and free registered users. Governed by category item limits (e.g., 6 lessons, 1 FNH architecture item) and tier ear training toggles.
- **Tier 2 (Partial Premium):** Subscribed Tier 2 users. Governed by Tier 2 limit settings (default 99 = unlimited).
- **Tier 3 (Full Premium):** Subscribed Tier 3 users. Access to all content unless hidden by admin.

### 1.2 Access Evaluation Hierarchy (Order of Priority)
When evaluating if an item (Learning or Ear Training Level) is accessible:
1. **Step 1: Explicit Admin Guest Override (`guest_access_override`)**
   - `Always Unlocked` (`true` / `Unlocked`): Explicitly unlocked for guests/all users regardless of category limits.
   - `Always Locked` (`false` / `Locked`): Explicitly locked for Tier 1/guests regardless of category limits.
   - `Follow Category Default` (`null` / `Use Level Rule`): Defer to stage/category default rules.
2. **Step 2: Admin Explicit Premium Flag (`is_premium`)**
   - If `is_premium = true` on the item or stage, subscription (Tier 2+) is required.
3. **Step 3: Dashboard Category & Tier Limits**
   - Category limits (`tier1_limit_*`, `tier2_limit_*`) and difficulty toggles determine unlock status up to the configured count in sort order.

---

## 2. Ear Training Engine API Endpoints

**Base URL:** `https://leemcwest.thewarriors.team`

### 2.1 Fetching Ear Training Stages
**Endpoint:** `GET https://leemcwest.thewarriors.team/api/ear-training/stages`  
**Authentication:** Optional (Headers: `Authorization: Bearer <token>` or Guest session)

Returns all top-level Ear Training Stages (Beginner, Intermediate, Advanced) and overall user progress tally.

**Response Payload:**
```json
{
  "success": true,
  "message": "Ear training stages fetched successfully.",
  "data": {
    "overall_progress": {
      "practised_count": 2,
      "total_levels": 12
    },
    "levels": [
      {
        "id": 1,
        "title": "Beginner",
        "subtitle": "Recognise core functional movements",
        "patterns_label": "T-S-T, T-D-T, S-D-T",
        "description": "Hear home (T), departure (S), and tension (D).",
        "accent_colour": "#3B82F6",
        "is_premium": false,
        "is_locked_by_default": false,
        "guest_access_override": "Use Level Rule",
        "next_stage_teaser": "Unlock Intermediate to explore secondary dominants.",
        "status": "practised",
        "levels_count": 3
      }
    ]
  }
}
```
*(Note: `levels` key returns the list of stages for backward compatibility with existing Flutter UI controllers).*

---

### 2.2 Fetching Levels for a Stage
**Endpoint:** `GET https://leemcwest.thewarriors.team/api/ear-training/stages/{stageId}/levels`  
**Authentication:** Optional

Returns all exercise sets (Levels) belonging to a specific stage along with their generation parameters and user progress.

**Response Payload:**
```json
{
  "success": true,
  "message": "Level quizzes fetched successfully.",
  "data": [
    {
      "id": 1,
      "stage_id": 1,
      "level_number": 1,
      "title": "T and S Basics",
      "description": "Hear the difference between home (T) and departure (S).",
      "is_premium": false,
      "is_locked": false,
      "lock_reason": null,
      "is_guest_accessible": true,
      "guest_access_override": "Use Level Rule",
      "status": "not_started",
      "attempts": 0,
      "last_practised": null,
      "best_score": 0,
      "passed": false,

      // --- ENGINE GENERATION CONTRACT ---
      "exercise_type": "identify_function",
      "exercise_count": 10,
      "function_patterns": [
        ["T", null, "S", null],
        ["T", null, "S", "T"]
      ],
      "tempo_bpm": 72,
      "midi_program": 0,
      "chord_voicing": "block",
      "chord_size": 3,
      "chord_purity": "pure",
      "answer_display": "leadership_only",
      "chord_regularity": "regular",
      "bar_length": 4,
      "closes_on_following_one": false,
      "repetitions": 2,
      "question_text_override": null,

      // --- LEVEL COMPLETE CONTENT ---
      "completion_heading": "Level 1 Complete",
      "completion_body": "You can now hear T and S.|Tonic grounds, S departs.",
      "completion_footer": "Next: add D to the mix."
    }
  ]
}
```

#### Engine Contract Parameters Breakdown:
* **`exercise_type`**: `identify_function` (single function isolation) or `identify_progression` (full progression flow).
* **`function_patterns`**: Beat grid pool array. Each sub-array is a beat sequence.
* **FNH Function Tokens (Case-Sensitive!):**
  * Core Tokens: `T` (Tonic), `S` (Subdominant), `D` (Dominant)
  * Sharp Cores: `T#`, `S#`, `D#`
  * Coreless Sharp: `d#` (raised leading tone 7♯, lowercase by design)
  * Flat Dominant: `Db`
  * Compound Tokens: `d#T`, `d#S`, `d#D`, `d#T#`, `d#S#`, `d#D#` (colour first, leader last)
  * Empty cell: `null`
  *(Note: Server-side validation strictly enforces this token set on save. Any value outside this exact case-sensitive vocabulary will be rejected.)*
* **`tempo_bpm`**: Playback tempo in BPM (1 beat = 60,000 / `tempo_bpm` ms).
* **`midi_program`**: General MIDI patch number (0 = Acoustic Grand Piano).
* **`chord_voicing`**: `block` (simultaneous) or `arpeggiated` (sequential).
* **`chord_size`**: `3`, `4`, or `5` notes per chord.
* **`chord_purity`**: `pure`, `mixed`, or `any`.
* **`answer_display`**: `leadership_only` (shows leading function) or `with_influences` (shows underlying Core stacks).
* **`chord_regularity`**: `regular`, `irregular`, or `any`.
* **`bar_length`**: Time signature beat count per bar (e.g. 4).
* **`closes_on_following_one`**: `true` if phrase includes beat 1 of next bar.
* **`completion_body`**: Pipe-separated (`|`) paragraphs for the completion screen.

#### Scoring Rules & Status:
* **`status`**: Exactly one of `"locked"`, `"not_started"`, or `"practised"`. Note: levels are never marked `"completed"`.
* **`passed`**: `true` if `best_score / exercise_count >= 0.8` (80% threshold).
* **`attempts`**: Integer representing how many times the user has submitted this level. Increments automatically upon submission.
* **`last_practised`**: ISO 8601 timestamp of the user's most recent submission for this level (or `null` if never practised).
* **Tallies**: Displayed as `"X of Y practised"` (e.g. 2 of 10 practised).

---

### 2.3 Submitting Level Progress
**Endpoint:** `POST https://leemcwest.thewarriors.team/api/ear-training/submit/{levelId}`  
**Authentication:** Optional (Supports authenticated users and guest session tokens)

Submits session attempt stats upon completing a level run. 
**Note:** Hitting this endpoint will automatically increment the user's `attempts` count by 1 and set their `last_practised` timestamp to the current time, even if they score lower than their previous `best_score`.

**Request Payload:**
```json
{
  "correct_answers": 8,
  "total_exercises": 10,
  "answers": [
    {
      "exercise_index": 0,
      "user_answer": "T -> S -> T",
      "correct_answer": "T -> S -> T",
      "is_correct": true,
      "hint_used": false
    }
  ]
}
```

**Response Payload:**
```json
{
  "success": true,
  "message": "Progress recorded successfully.",
  "data": {
    "level_id": 1,
    "score": 8,
    "total": 10,
    "best_score": 8,
    "passed": true,
    "status": "practised"
  }
}
```

---

### 2.4 Fetching Explanation Templates
**Endpoint:** `GET https://leemcwest.thewarriors.team/api/ear-training/explanation-templates`  
**Authentication:** Optional

Returns the full library of explanation and hint templates for the engine to render feedback.

**Response Payload:**
```json
{
  "success": true,
  "message": "Explanation templates fetched successfully.",
  "data": [
    {
      "id": 1,
      "type": "transition",
      "from_function": "T",
      "to_function": "S",
      "phrase": "from the tonic the phrase steps outward to the subdominant",
      "pattern": null,
      "title": null,
      "body": null,
      "hint_text": null,
      "display_value": null,
      "exercise_type": null,
      "prompt_text": null
    }
  ]
}
```

---

## 3. Database Schema Overview

| Table | Role & Function |
|---|---|
| `ear_training_stages` | Top-level tiers (Beginner, Intermediate, Advanced). Holds presentation cards, accent colors, default lock status, and stage-scope `guest_access_override`. |
| `ear_training_levels` | Engine exercise sets. Holds all generation parameters (`function_patterns`, `tempo_bpm`, `chord_voicing`, etc.), level completion text, and level-scope `guest_access_override`. |
| `ear_training_user_progress` | User/Guest level progress tracking (`status`, `best_score`, `attempts`, `last_practised`). |
| `ear_training_user_answers` | Individual answer attempts for analytics dashboard. |
| `guest_access_settings` | System-wide tier limits, hidden categories, and category toggles. |

---

## 4. App Update Alert Endpoints

Public endpoints for mobile app update notifications. Managed via backend **Settings → Update Alerts**. 
These endpoints do not require authentication (no API key or bearer token needed).

### 4.1 Soft Update Alert
**Endpoint:** `GET https://leemcwest.thewarriors.team/api/app-update/soft`

### 4.2 Force Update Alert
**Endpoint:** `GET https://leemcwest.thewarriors.team/api/app-update/force`

### Response Payload:
```json
{
  "status": 200,
  "message": "Soft update alert status fetched successfully.",
  "data": true,
  "latest_version": "1.0.0",
  "app_store_url": "https://apps.apple.com/app/...",
  "play_store_url": "https://play.google.com/store/apps/details?..."
}
```
* **`data` = `true`**: Display the update dialog.
* **`latest_version`**: The minimum required app version. This is provided as a single string for both platforms (not separated for iOS vs Android).
* **Soft Update**: Dismissible prompt.
* **Force Update**: Non-dismissible blocking overlay.
