Unverified Commit 77247238 authored by Claude's avatar Claude
Browse files

Transform AI toggle into Teacher Parameters with simulation type control

Backend changes (app.py):
- Added _simulation_type global variable (normal/external_event)
- Created /api/teacher_params GET/POST endpoints for managing both AI and simulation settings
- Updated load_test_data() to load appropriate test data based on simulation type
- Added support for external events test data file
- Kept legacy /api/ai_status and /api/toggle_ai endpoints for backward compatibility

Frontend changes (base.html):
- Replaced "AI Toggle" button with "Teacher Parameters" button
- Added status indicators for AI (green/red) and Simulation type (green/yellow)
- Created comprehensive Teacher Parameters modal with:
  * Password authentication step
  * AI enable/disable toggle
  * Simulation type selection (normal vs external event)
  * Two-step flow: authenticate first, then configure parameters
- Updated JavaScript to handle teacher parameters and status indicators
- Status indicators update in real-time when parameters change

Features:
- Teachers can now control both AI availability and which 11th year dataset is used
- Visual indicators show current state at a glance
- Same password authentication as before (HorloML-AI)
- Seamless UX with clear feedback on parameter changes
parent 744bce98
Loading
Loading
Loading
Loading
+64 −7
Original line number Diff line number Diff line
@@ -34,13 +34,15 @@ app.config['SESSION_TYPE'] = 'filesystem'
# Load dataset
DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
TRAINING_DATA_PATH = os.path.join(DATA_DIR, 'sim2_supply_chain_data_training.json')
TEST_DATA_PATH = os.path.join(DATA_DIR, 'sim2_supply_chain_data_test.json')
TEST_DATA_PATH_NORMAL = os.path.join(DATA_DIR, 'sim2_supply_chain_data_test.json')
TEST_DATA_PATH_EXTERNAL = os.path.join(DATA_DIR, 'sim2_data_test_external_events.json')

# TiRex model (loaded lazily on first request)
_tirex_model = None

# AI activation state (disabled by default)
# Teacher parameters (disabled by default)
_ai_enabled = False
_simulation_type = 'normal'  # 'normal' or 'external_event'
AI_PASSWORD = os.environ.get('AI_PASSWORD', 'HorloML-AI')


@@ -59,8 +61,23 @@ def load_training_data():


def load_test_data():
    """Load the year 11 test data (ground truth)"""
    with open(TEST_DATA_PATH, 'r') as f:
    """Load the year 11 test data (ground truth) based on simulation type"""
    global _simulation_type

    if _simulation_type == 'external_event':
        # Load external events data
        if os.path.exists(TEST_DATA_PATH_EXTERNAL):
            with open(TEST_DATA_PATH_EXTERNAL, 'r') as f:
                data = json.load(f)
                # External events file has different structure: {'metadata': ..., 'test_data': [...]}
                return data.get('test_data', data)
        else:
            # Fallback to normal if external file doesn't exist
            with open(TEST_DATA_PATH_NORMAL, 'r') as f:
                return json.load(f)
    else:
        # Load normal test data
        with open(TEST_DATA_PATH_NORMAL, 'r') as f:
            return json.load(f)


@@ -594,16 +611,56 @@ def results():
                          test_data=test_data)


@app.route('/api/teacher_params', methods=['GET'])
def get_teacher_params():
    """Get current teacher parameters (AI status and simulation type)"""
    global _ai_enabled, _simulation_type
    return jsonify({
        'ai_enabled': _ai_enabled,
        'simulation_type': _simulation_type
    })


@app.route('/api/teacher_params', methods=['POST'])
def update_teacher_params():
    """Update teacher parameters with password verification"""
    global _ai_enabled, _simulation_type

    data = request.get_json()
    password = data.get('password', '')

    if password != AI_PASSWORD:
        return jsonify({'success': False, 'error': 'Invalid password'}), 401

    # Update parameters if provided
    if 'ai_enabled' in data:
        _ai_enabled = data['ai_enabled']

    if 'simulation_type' in data:
        sim_type = data['simulation_type']
        if sim_type in ['normal', 'external_event']:
            _simulation_type = sim_type
        else:
            return jsonify({'success': False, 'error': 'Invalid simulation type'}), 400

    return jsonify({
        'success': True,
        'ai_enabled': _ai_enabled,
        'simulation_type': _simulation_type
    })


# Legacy endpoints for backward compatibility
@app.route('/api/ai_status', methods=['GET'])
def ai_status():
    """Get current AI activation status"""
    """Get current AI activation status (legacy endpoint)"""
    global _ai_enabled
    return jsonify({'enabled': _ai_enabled})


@app.route('/api/toggle_ai', methods=['POST'])
def toggle_ai():
    """Toggle AI activation with password verification"""
    """Toggle AI activation with password verification (legacy endpoint)"""
    global _ai_enabled

    data = request.get_json()
+211 −55
Original line number Diff line number Diff line
@@ -73,11 +73,25 @@

            <div class="navbar-end">
                <div class="navbar-item">
                    <button class="button is-danger" id="aiToggleButton" onclick="toggleAI()" style="min-width: 150px;">
                        <span class="icon"><i class="fas fa-brain"></i></span>
                        <span id="aiToggleText">Activate AI</span>
                    <div style="display: flex; align-items: center; gap: 10px;">
                        <!-- Status Indicators -->
                        <div style="display: flex; gap: 8px; margin-right: 5px;">
                            <span class="tag" id="aiStatusIndicator" title="AI Status">
                                <span class="icon is-small"><i class="fas fa-brain"></i></span>
                                <span id="aiStatusText">AI</span>
                            </span>
                            <span class="tag" id="simStatusIndicator" title="Simulation Type">
                                <span class="icon is-small"><i class="fas fa-calendar"></i></span>
                                <span id="simStatusText">Sim</span>
                            </span>
                        </div>
                        <!-- Teacher Parameters Button -->
                        <button class="button is-info" id="teacherParamsButton" onclick="openTeacherParams()" style="min-width: 180px;">
                            <span class="icon"><i class="fas fa-cog"></i></span>
                            <span>Teacher Parameters</span>
                        </button>
                    </div>
                </div>
                <div class="navbar-item">
                    <a class="button is-light" href="{{ url_for('reset') }}">
                        <span class="icon"><i class="fas fa-redo"></i></span>
@@ -99,88 +113,161 @@
        </div>
    </footer>

    <!-- Password Modal -->
    <div class="modal" id="passwordModal">
        <div class="modal-background" onclick="closePasswordModal()"></div>
    <!-- Teacher Parameters Modal -->
    <div class="modal" id="teacherParamsModal">
        <div class="modal-background" onclick="closeTeacherParamsModal()"></div>
        <div class="modal-card">
            <header class="modal-card-head">
                <p class="modal-card-title">Teacher Authentication Required</p>
                <button class="delete" aria-label="close" onclick="closePasswordModal()"></button>
                <p class="modal-card-title">Teacher Parameters</p>
                <button class="delete" aria-label="close" onclick="closeTeacherParamsModal()"></button>
            </header>
            <section class="modal-card-body">
                <div class="field">
                <!-- Password Field (shown first) -->
                <div class="field" id="passwordField">
                    <label class="label">Enter Password</label>
                    <div class="control has-icons-left">
                        <input class="input" type="password" id="aiPassword" placeholder="Enter teacher password">
                        <input class="input" type="password" id="teacherPassword" placeholder="Enter teacher password">
                        <span class="icon is-small is-left">
                            <i class="fas fa-lock"></i>
                        </span>
                    </div>
                    <p class="help is-danger" id="passwordError" style="display: none;">Invalid password. Please try again.</p>
                </div>

                <!-- Parameters Section (hidden until authenticated) -->
                <div id="parametersSection" style="display: none;">
                    <div class="notification is-success is-light">
                        <p><strong>Authenticated successfully!</strong> Configure parameters below.</p>
                    </div>

                    <!-- AI Toggle -->
                    <div class="field">
                        <label class="label">
                            <span class="icon"><i class="fas fa-brain"></i></span>
                            AI Assistant
                        </label>
                        <div class="control">
                            <label class="radio">
                                <input type="radio" name="aiToggle" value="enabled" id="aiEnabled">
                                <span class="tag is-success">Enabled</span>
                            </label>
                            <label class="radio">
                                <input type="radio" name="aiToggle" value="disabled" id="aiDisabled">
                                <span class="tag is-danger">Disabled</span>
                            </label>
                        </div>
                        <p class="help">Enable or disable AI forecasting assistance for students</p>
                    </div>

                    <hr>

                    <!-- Simulation Type Selection -->
                    <div class="field">
                        <label class="label">
                            <span class="icon"><i class="fas fa-calendar"></i></span>
                            11th Year Simulation
                        </label>
                        <div class="control">
                            <label class="radio">
                                <input type="radio" name="simType" value="normal" id="simNormal">
                                <span class="tag is-success">Normal</span>
                            </label>
                            <label class="radio">
                                <input type="radio" name="simType" value="external_event" id="simExternal">
                                <span class="tag is-warning">External Event</span>
                            </label>
                        </div>
                        <p class="help">Choose whether year 11 includes external disrupting events</p>
                    </div>
                </div>
            </section>
            <footer class="modal-card-foot">
                <button class="button is-success" onclick="submitPassword()">Submit</button>
                <button class="button" onclick="closePasswordModal()">Cancel</button>
                <button class="button is-success" id="authenticateButton" onclick="authenticateTeacher()">Authenticate</button>
                <button class="button is-primary" id="saveParamsButton" onclick="saveTeacherParams()" style="display: none;">Save Changes</button>
                <button class="button" onclick="closeTeacherParamsModal()">Cancel</button>
            </footer>
        </div>
    </div>

    <script>
    // AI Toggle functionality
    let currentAIStatus = false;
    // Teacher Parameters functionality
    let currentParams = {
        ai_enabled: false,
        simulation_type: 'normal'
    };
    let isAuthenticated = false;

    // Check AI status on page load
    async function checkAIStatus() {
    // Check teacher parameters on page load
    async function checkTeacherParams() {
        try {
            const response = await fetch('/api/ai_status');
            const response = await fetch('/api/teacher_params');
            const data = await response.json();
            currentAIStatus = data.enabled;
            updateAIButton(currentAIStatus);
            currentParams = data;
            updateStatusIndicators(data.ai_enabled, data.simulation_type);
        } catch (error) {
            console.error('Failed to check AI status:', error);
            console.error('Failed to check teacher params:', error);
        }
    }

    // Update AI button appearance
    function updateAIButton(enabled) {
        const button = document.getElementById('aiToggleButton');
        const text = document.getElementById('aiToggleText');
    // Update status indicators
    function updateStatusIndicators(aiEnabled, simType) {
        const aiIndicator = document.getElementById('aiStatusIndicator');
        const simIndicator = document.getElementById('simStatusIndicator');

        // Update AI indicator
        if (aiEnabled) {
            aiIndicator.classList.remove('is-danger');
            aiIndicator.classList.add('is-success');
        } else {
            aiIndicator.classList.remove('is-success');
            aiIndicator.classList.add('is-danger');
        }

        if (enabled) {
            button.classList.remove('is-danger');
            button.classList.add('is-success');
            text.textContent = 'Deactivate AI';
        // Update Simulation indicator
        if (simType === 'external_event') {
            simIndicator.classList.remove('is-success');
            simIndicator.classList.add('is-warning');
        } else {
            button.classList.remove('is-success');
            button.classList.add('is-danger');
            text.textContent = 'Activate AI';
            simIndicator.classList.remove('is-warning');
            simIndicator.classList.add('is-success');
        }
    }

    // Open password modal
    function toggleAI() {
        document.getElementById('passwordModal').classList.add('is-active');
        document.getElementById('aiPassword').value = '';
    // Open teacher parameters modal
    function openTeacherParams() {
        const modal = document.getElementById('teacherParamsModal');
        modal.classList.add('is-active');

        // Reset modal state
        isAuthenticated = false;
        document.getElementById('teacherPassword').value = '';
        document.getElementById('passwordError').style.display = 'none';
        document.getElementById('passwordField').style.display = 'block';
        document.getElementById('parametersSection').style.display = 'none';
        document.getElementById('authenticateButton').style.display = 'inline-block';
        document.getElementById('saveParamsButton').style.display = 'none';

        // Focus on password input
        setTimeout(() => {
            document.getElementById('aiPassword').focus();
            document.getElementById('teacherPassword').focus();
        }, 100);
    }

    // Close password modal
    function closePasswordModal() {
        document.getElementById('passwordModal').classList.remove('is-active');
    // Close teacher parameters modal
    function closeTeacherParamsModal() {
        document.getElementById('teacherParamsModal').classList.remove('is-active');
        isAuthenticated = false;
    }

    // Submit password
    async function submitPassword() {
        const password = document.getElementById('aiPassword').value;
    // Authenticate teacher
    async function authenticateTeacher() {
        const password = document.getElementById('teacherPassword').value;
        const errorElement = document.getElementById('passwordError');

        try {
            const response = await fetch('/api/toggle_ai', {
            // Verify password by making a GET request with the password
            // We'll fetch current params to verify password
            const response = await fetch('/api/teacher_params', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
@@ -191,33 +278,102 @@
            const data = await response.json();

            if (response.ok && data.success) {
                currentAIStatus = data.enabled;
                updateAIButton(currentAIStatus);
                closePasswordModal();
                isAuthenticated = true;
                currentParams = {
                    ai_enabled: data.ai_enabled,
                    simulation_type: data.simulation_type
                };

                // Reload page to update AI button state on predict page
                if (typeof updateAIPredictButton === 'function') {
                    updateAIPredictButton(currentAIStatus);
                // Show parameters section
                document.getElementById('passwordField').style.display = 'none';
                document.getElementById('parametersSection').style.display = 'block';
                document.getElementById('authenticateButton').style.display = 'none';
                document.getElementById('saveParamsButton').style.display = 'inline-block';

                // Set current values
                if (data.ai_enabled) {
                    document.getElementById('aiEnabled').checked = true;
                } else {
                    document.getElementById('aiDisabled').checked = true;
                }

                if (data.simulation_type === 'external_event') {
                    document.getElementById('simExternal').checked = true;
                } else {
                    document.getElementById('simNormal').checked = true;
                }
            } else {
                errorElement.textContent = 'Invalid password. Please try again.';
                errorElement.style.display = 'block';
            }
        } catch (error) {
            console.error('Failed to toggle AI:', error);
            console.error('Failed to authenticate:', error);
            errorElement.textContent = 'Network error. Please try again.';
            errorElement.style.display = 'block';
        }
    }

    // Allow Enter key to submit password
    // Save teacher parameters
    async function saveTeacherParams() {
        if (!isAuthenticated) {
            alert('Please authenticate first');
            return;
        }

        const password = document.getElementById('teacherPassword').value;
        const aiEnabled = document.getElementById('aiEnabled').checked;
        const simType = document.getElementById('simExternal').checked ? 'external_event' : 'normal';

        try {
            const response = await fetch('/api/teacher_params', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    password: password,
                    ai_enabled: aiEnabled,
                    simulation_type: simType
                })
            });

            const data = await response.json();

            if (response.ok && data.success) {
                currentParams = {
                    ai_enabled: data.ai_enabled,
                    simulation_type: data.simulation_type
                };
                updateStatusIndicators(data.ai_enabled, data.simulation_type);
                closeTeacherParamsModal();

                // Show success notification
                alert('Teacher parameters updated successfully!');

                // Reload page to update AI button state on predict page
                if (typeof updateAIPredictButton === 'function') {
                    updateAIPredictButton(data.ai_enabled);
                }
            } else {
                alert('Failed to save parameters: ' + (data.error || 'Unknown error'));
            }
        } catch (error) {
            console.error('Failed to save parameters:', error);
            alert('Network error. Please try again.');
        }
    }

    // Allow Enter key to authenticate
    document.addEventListener('DOMContentLoaded', function() {
        checkAIStatus();
        checkTeacherParams();

        const passwordInput = document.getElementById('aiPassword');
        const passwordInput = document.getElementById('teacherPassword');
        if (passwordInput) {
            passwordInput.addEventListener('keypress', function(e) {
                if (e.key === 'Enter') {
                    submitPassword();
                    if (!isAuthenticated) {
                        authenticateTeacher();
                    }
                }
            });
        }