MVP
This commit is contained in:
@@ -24,6 +24,9 @@
|
||||
"user": {
|
||||
"name": "Mdares",
|
||||
"email": "mdares@maliountech.com"
|
||||
},
|
||||
"workflow": {
|
||||
"mode": "manual"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
},
|
||||
"debug": {
|
||||
"filter": "filterCurrent"
|
||||
},
|
||||
"git": {
|
||||
"user": {
|
||||
"name": "Mdares",
|
||||
"email": "mdares@maliountech.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Explanation_KPI.txt
Normal file
25
Explanation_KPI.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
Excellent! Now I can see the issue. Your Availability calculation is correct - it's using the proper OEE formula with operatingTime / plannedTimeSec. However, I need to check how operatingTime is being accumulated, because that's likely where the problem is.
|
||||
Let me search for where operatingTime is being updated:
|
||||
Aha! I found the root cause of your issue.
|
||||
The Problem
|
||||
Your Availability calculation is conceptually correct (using the proper OEE formula), BUT the critical variables it depends on are never being updated in your Node-RED flows:
|
||||
❌ global.get("operatingTime") - Never set anywhere in your flows
|
||||
❌ global.get("productionStartTime") - Never set anywhere in your flows
|
||||
This means your Availability calculation is always returning either:
|
||||
0% (when trackingEnabled is false)
|
||||
100% (when trackingEnabled is true but operatingTime is 0)
|
||||
Why Scrap Appears to Affect Availability
|
||||
When you submit scrap, your UI likely refreshes or recalculates, and depending on the state of these undefined variables, the Availability percentage changes unpredictably. It's not actually measuring availability at all - it's just showing placeholder values that change when the UI updates.
|
||||
The Fix
|
||||
You need to add a time tracking mechanism in your "Machine cycles" function. Here's what needs to be added:
|
||||
// In the "Machine cycles" function, ADD THIS LOGIC:const current = Number(msg.payload) || 0;let zeroStreak = flow.get("zeroStreak") || 0;zeroStreak = current === 0 ? zeroStreak + 1 : 0;flow.set("zeroStreak", zeroStreak);const prev = flow.get("lastMachineState") ?? 0;flow.set("lastMachineState", current);global.set("machineOnline", true);let productionRunning = !!global.get("productionStarted");let stateChanged = false;// ===== ADD THIS SECTION FOR TIME TRACKING =====const now = Date.now();let lastUpdateTime = global.get("lastUpdateTime") || now;const deltaTime = (now - lastUpdateTime) / 1000; // seconds// Initialize productionStartTime when tracking beginsconst trackingEnabled = !!global.get("trackingEnabled");if (trackingEnabled && !global.get("productionStartTime")) { global.set("productionStartTime", now); global.set("operatingTime", 0);}// Accumulate operating time when machine is in state 1 (running)if (trackingEnabled && current === 1) { const currentOperatingTime = global.get("operatingTime") || 0; global.set("operatingTime", currentOperatingTime + deltaTime);}global.set("lastUpdateTime", now);// ===== END NEW SECTION =====if (current === 1 && !productionRunning) { productionRunning = true; stateChanged = true;} else if (current === 0 && zeroStreak >= 2 && productionRunning) { productionRunning = false; stateChanged = true;}// ... rest of your existing code
|
||||
Also Update "Work Order buttons" Function
|
||||
When a work order starts, initialize the timers:
|
||||
case "start-work-order": { // ... existing code ... global.set("activeWorkOrder", order); global.set("cycleCount", 0); flow.set("lastMachineState", 0); global.set("scrapPromptIssuedFor", null); // ADD THESE LINES: global.set("productionStartTime", null); // Will be set when START button is clicked global.set("operatingTime", 0); global.set("lastUpdateTime", null); return [null, null, msg, null];}
|
||||
And when START button is clicked:
|
||||
case "start": { // START button clicked from Home dashboard global.set("trackingEnabled", true); // ADD THESE LINES: const now = Date.now(); global.set("productionStartTime", now); global.set("operatingTime", 0); global.set("lastUpdateTime", now); return [null, null, null, null];}
|
||||
When STOP button is clicked or work order completes, reset timers:
|
||||
case "stop": { global.set("trackingEnabled", false); // Optionally preserve timing for resume, or reset: // global.set("productionStartTime", null); // global.set("operatingTime", 0); return [null, null, null, null];}case "complete-work-order": { // ... existing code ... global.set("activeWorkOrder", null); global.set("cycleCount", 0); flow.set("lastMachineState", 0); global.set("scrapPromptIssuedFor", null); // ADD THESE LINES: global.set("productionStartTime", null); global.set("operatingTime", 0); global.set("lastUpdateTime", null); return [null, null, null, msg];}
|
||||
Summary
|
||||
Your instinct was correct - scrap should NOT affect Availability. The issue is that your Availability isn't being calculated at all because the time-tracking variables (operatingTime, productionStartTime) are never set. Once you implement proper time tracking in the machine cycle monitoring, your Availability will measure actual uptime vs. downtime, and scrap entries will correctly only affect the Quality KPI.
|
||||
Would you like me to show you the complete updated functions with all these changes integrated?
|
||||
74
Ignore.txt
Normal file
74
Ignore.txt
Normal file
@@ -0,0 +1,74 @@
|
||||
CRITICAL NODE-RED DASHBOARD CHART.JS FIX - PRESERVE ALL FUNCTIONALITY
|
||||
I'm experiencing a Chart.js error in my Node-RED dashboard: Cannot read properties of undefined (reading 'length') at the determineDataLimits function. This occurs when trying to load charts with time-based x-axes.
|
||||
Current Error Details:
|
||||
|
||||
Error location: app.min.js:556 in a.determineDataLimits
|
||||
Additional 404 error: ui/loading.html not found
|
||||
Chart.js version appears to be v4
|
||||
Time adapter (chartjs-adapter-date-fns) was supposedly added but charts still not rendering
|
||||
|
||||
REQUIREMENTS (ALL MUST BE MET):
|
||||
|
||||
Fix the Chart.js time scale issue:
|
||||
|
||||
Ensure chartjs-adapter-date-fns is properly loaded and initialized BEFORE any charts are created
|
||||
Verify the adapter is compatible with the Chart.js version being used
|
||||
Confirm the time scale is properly registered with Chart.js
|
||||
|
||||
|
||||
Verify mock data structure:
|
||||
|
||||
Mock data must have proper timestamp format (ISO 8601 or Unix timestamps)
|
||||
Data arrays must have matching lengths for x and y values
|
||||
Include realistic variance for: OEE (~72%), Availability (~85%), Performance (~88%), Quality (~94%)
|
||||
|
||||
|
||||
Check loading order:
|
||||
|
||||
Chart.js library must load first
|
||||
Time adapter must load second
|
||||
Chart creation code must execute last
|
||||
Add console logs to verify loading sequence
|
||||
|
||||
|
||||
PRESERVE ALL EXISTING FUNCTIONALITY:
|
||||
|
||||
Home tab - unchanged
|
||||
Alerts tab - unchanged
|
||||
Work Orders tab - unchanged
|
||||
Settings tab - unchanged
|
||||
Help tab - unchanged
|
||||
Only the Graphs tab should be modified
|
||||
|
||||
|
||||
Provide complete debugging steps:
|
||||
|
||||
Show me how to verify the adapter is loaded in browser console
|
||||
Provide fallback options if adapter doesn't load from CDN
|
||||
Include error handling for chart initialization failures
|
||||
|
||||
|
||||
Output format:
|
||||
|
||||
Provide the complete Node-RED flow JSON that I can import
|
||||
Include all necessary HTML/JavaScript for the dashboard template
|
||||
Mark clearly which nodes have been modified vs. unchanged
|
||||
Include step-by-step deployment instructions
|
||||
|
||||
|
||||
|
||||
Additional Context:
|
||||
|
||||
Node-RED dashboard is running on http://10.147.20.185:1880
|
||||
Using Less CSS (app.min.less) for styling
|
||||
Need hard refresh instructions for different browsers after deployment
|
||||
|
||||
Success Criteria:
|
||||
|
||||
No console errors after deployment and hard refresh
|
||||
Charts display with mock data immediately upon loading Graphs tab
|
||||
All filter buttons are clickable and functional
|
||||
All other tabs remain completely functional
|
||||
Solution works without requiring external CDN if possible
|
||||
|
||||
Please provide a complete, tested solution that addresses every point above. Include explanations for each fix so I understand what was wrong and how it's been corrected.
|
||||
1293
Respaldo_Funcional_11_23_25.json
Normal file
1293
Respaldo_Funcional_11_23_25.json
Normal file
File diff suppressed because one or more lines are too long
1377
Respaldo_MVP_Complete_11_23_25.json
Normal file
1377
Respaldo_MVP_Complete_11_23_25.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
198
optimization_prompt.txt
Normal file
198
optimization_prompt.txt
Normal file
@@ -0,0 +1,198 @@
|
||||
# Backend Optimization Requirements for KPI Tracking System
|
||||
|
||||
## Context
|
||||
This Node-RED application tracks manufacturing KPIs (OEE, Availability, Performance, Quality) for a production line. The system monitors machine cycles, work orders, and production metrics. These optimizations address data persistence, session management, and accurate downtime tracking.
|
||||
|
||||
## Critical Issues to Address
|
||||
|
||||
### Issue 1: Data Persistence (Option A - Database Storage)
|
||||
**Problem:** Timing data and context variables are lost on crash or reboot, making it impossible to restore sessions.
|
||||
|
||||
**Solution:** Implement database persistence for timing and context data
|
||||
- Create a new table in the database called `session_state` or `global_context`
|
||||
- Store the following timing data:
|
||||
- `productionStartTime`
|
||||
- `operatingTime`
|
||||
- `lastUpdateTime`
|
||||
- `trackingEnabled` status
|
||||
- `cycleCount`
|
||||
- `activeWorkOrder` details
|
||||
- Any other critical global/flow context variables
|
||||
|
||||
**Implementation Requirements:**
|
||||
1. On system startup, check if there's existing session data in the database
|
||||
2. Restore session state from database if found
|
||||
3. Continuously sync critical timing variables to database (implement throttled writes to avoid database overload - e.g., update every 5-10 seconds or on state changes)
|
||||
4. On clean shutdown or work order completion, ensure final state is written
|
||||
5. On crash recovery, prompt user if they want to restore the previous session or start fresh
|
||||
|
||||
**Database Schema Example:**
|
||||
```sql
|
||||
CREATE TABLE session_state (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT UNIQUE,
|
||||
production_start_time INTEGER,
|
||||
operating_time REAL,
|
||||
last_update_time INTEGER,
|
||||
tracking_enabled INTEGER,
|
||||
cycle_count INTEGER,
|
||||
active_work_order_id TEXT,
|
||||
created_at INTEGER,
|
||||
updated_at INTEGER,
|
||||
is_active INTEGER DEFAULT 1
|
||||
);
|
||||
```
|
||||
|
||||
### Issue 2: Cycle Count Capping
|
||||
**Problem:** Need to prevent cycle counts from exceeding reasonable limits.
|
||||
|
||||
**Solution:** Implement a cap at 100 cycles
|
||||
- When cycle count reaches 100, either:
|
||||
- Stop accepting new cycles and alert the operator
|
||||
- OR automatically prompt for work order completion
|
||||
- Add validation in the cycle counting logic
|
||||
- Display warning in UI when approaching the cap (e.g., at 90+ cycles)
|
||||
|
||||
### Issue 3: Hardware Irregularity Handling
|
||||
**Problem:** Real hardware will be used and may have irregular behavior/timing.
|
||||
|
||||
**Solution:** Design system to handle and track irregularities
|
||||
- Log irregular cycle times (cycles that deviate significantly from average)
|
||||
- Track unexpected state changes
|
||||
- Store metadata about irregular events for later analysis
|
||||
- This data will help diagnose:
|
||||
- Unplanned production stops
|
||||
- Slower than normal cycles
|
||||
- Patterns in machine behavior
|
||||
|
||||
**Implementation Requirements:**
|
||||
1. Calculate rolling average cycle time
|
||||
2. Flag cycles that are >20% slower/faster than average
|
||||
3. Store flagged cycles with timestamps in database
|
||||
4. Create a separate table for anomaly tracking:
|
||||
```sql
|
||||
CREATE TABLE cycle_anomalies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
work_order_id TEXT,
|
||||
cycle_number INTEGER,
|
||||
expected_time REAL,
|
||||
actual_time REAL,
|
||||
deviation_percent REAL,
|
||||
timestamp INTEGER,
|
||||
notes TEXT
|
||||
);
|
||||
```
|
||||
|
||||
### Issue 4: Intelligent Downtime Categorization
|
||||
**Problem:** Not all stops should count as downtime (e.g., lunch breaks vs machine issues).
|
||||
|
||||
**Solution:** Implement stop reason prompt and categorization
|
||||
- When STOP button is clicked, immediately show a prompt asking "Why are you stopping?"
|
||||
- Provide categorized options:
|
||||
- **Planned Stops** (don't affect downtime):
|
||||
- Lunch break
|
||||
- Scheduled break
|
||||
- Shift change
|
||||
- Planned maintenance
|
||||
- **Unplanned Stops** (count as downtime, affect Availability):
|
||||
- Machine malfunction
|
||||
- Material shortage
|
||||
- Quality issue
|
||||
- Operator error
|
||||
- Other (with text input)
|
||||
|
||||
**Implementation Requirements:**
|
||||
1. Modify "Work Order buttons" function to intercept "stop" command
|
||||
2. Display modal/prompt with categorized options
|
||||
3. Store stop reason in database with timestamp
|
||||
4. Only accumulate downtime for unplanned stops
|
||||
5. Create stop_events table:
|
||||
```sql
|
||||
CREATE TABLE stop_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
work_order_id TEXT,
|
||||
stop_time INTEGER,
|
||||
resume_time INTEGER,
|
||||
duration INTEGER,
|
||||
reason_category TEXT, -- 'planned' or 'unplanned'
|
||||
reason_detail TEXT,
|
||||
affects_availability INTEGER,
|
||||
notes TEXT
|
||||
);
|
||||
```
|
||||
|
||||
### Issue 5: Session Management and Pattern Tracking
|
||||
**Problem:** Need to track production patterns and identify when sessions start/stop.
|
||||
|
||||
**Solution:** Create new session each time RESUME is clicked
|
||||
- Each START/RESUME creates a new production session
|
||||
- Track session metadata for pattern analysis
|
||||
- Add time tracking columns to identify:
|
||||
- When production occurs
|
||||
- Unexpected breaks
|
||||
- Patterns (e.g., breaks at certain hours)
|
||||
- Session duration trends
|
||||
|
||||
**Implementation Requirements:**
|
||||
1. Generate unique session_id on each START/RESUME
|
||||
2. Store session metadata in new table:
|
||||
```sql
|
||||
CREATE TABLE production_sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT UNIQUE,
|
||||
work_order_id TEXT,
|
||||
start_time INTEGER,
|
||||
end_time INTEGER,
|
||||
duration INTEGER,
|
||||
cycles_completed INTEGER,
|
||||
reason_for_start TEXT, -- 'initial_start', 'resume_after_planned', 'resume_after_unplanned'
|
||||
reason_for_end TEXT, -- 'planned_stop', 'unplanned_stop', 'work_order_complete'
|
||||
operating_time REAL,
|
||||
downtime REAL,
|
||||
created_at INTEGER
|
||||
);
|
||||
```
|
||||
|
||||
3. Add time tracking columns to work_orders table:
|
||||
```sql
|
||||
ALTER TABLE work_orders ADD COLUMN total_sessions INTEGER DEFAULT 0;
|
||||
ALTER TABLE work_orders ADD COLUMN total_operating_time REAL DEFAULT 0;
|
||||
ALTER TABLE work_orders ADD COLUMN total_downtime REAL DEFAULT 0;
|
||||
ALTER TABLE work_orders ADD COLUMN avg_session_duration REAL DEFAULT 0;
|
||||
```
|
||||
|
||||
4. On RESUME, log the previous session and start a new one
|
||||
5. Track cumulative metrics across all sessions for the work order
|
||||
6. Build analytics to identify:
|
||||
- Peak production times
|
||||
- Frequent break times
|
||||
- Session duration patterns
|
||||
- Correlation between session length and performance
|
||||
|
||||
## Implementation Priority
|
||||
Since these are backend fixes affecting ~1% of Availability KPI accuracy, and UI presentation is the immediate priority:
|
||||
|
||||
**Phase 1 (Later):** Implement Issues 1, 4, 5 (data persistence, stop categorization, session tracking)
|
||||
**Phase 2 (Later):** Implement Issues 2, 3 (cycle capping, irregularity tracking)
|
||||
**Phase 3 (Later):** Build analytics dashboard for pattern analysis
|
||||
|
||||
## Key Functions to Modify
|
||||
1. **Machine cycles function** - Add database sync for timing data
|
||||
2. **Work Order buttons function** - Add session management, stop reason prompt
|
||||
3. **Database initialization** - Add new tables
|
||||
4. **Startup routine** - Add session recovery logic
|
||||
5. **Availability calculation** - Exclude planned stops from downtime calculation
|
||||
|
||||
## Expected Outcome
|
||||
- Data persists through crashes/reboots
|
||||
- Accurate downtime vs planned stop tracking
|
||||
- Session-based production history for pattern analysis
|
||||
- Foundation for predictive maintenance and efficiency optimization
|
||||
- Ability to identify and mitigate unexpected production issues
|
||||
|
||||
## Notes
|
||||
- All timing should use Unix timestamps (milliseconds) for consistency
|
||||
- Implement database connection pooling for performance
|
||||
- Add error handling for database operations
|
||||
- Consider adding a "notes" field to most tables for operator comments
|
||||
- Build admin interface to review historical sessions and patterns
|
||||
145
phase1_database_migration.sql
Normal file
145
phase1_database_migration.sql
Normal file
@@ -0,0 +1,145 @@
|
||||
-- ============================================================================
|
||||
-- PHASE 1: Database Migration for Time Tracking & Alerts
|
||||
-- Database: machine_data
|
||||
-- Version: 1.0
|
||||
-- ============================================================================
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Table 1: KPI Snapshots (Time-series data for graphs)
|
||||
-- ----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS kpi_snapshots (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
timestamp BIGINT NOT NULL COMMENT 'Unix timestamp in milliseconds',
|
||||
work_order_id VARCHAR(255),
|
||||
oee_percent DECIMAL(5,2) DEFAULT 0,
|
||||
availability_percent DECIMAL(5,2) DEFAULT 0,
|
||||
performance_percent DECIMAL(5,2) DEFAULT 0,
|
||||
quality_percent DECIMAL(5,2) DEFAULT 0,
|
||||
cycle_count INT DEFAULT 0,
|
||||
good_parts INT DEFAULT 0,
|
||||
scrap_count INT DEFAULT 0,
|
||||
operating_time DECIMAL(10,2) DEFAULT 0 COMMENT 'Accumulated seconds in state 1',
|
||||
downtime DECIMAL(10,2) DEFAULT 0 COMMENT 'Accumulated seconds in state 0 while tracking',
|
||||
machine_state INT DEFAULT 0 COMMENT 'Current machine state: 0 or 1',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_timestamp (timestamp),
|
||||
INDEX idx_work_order (work_order_id),
|
||||
INDEX idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KPI data snapshots for trending and graphs';
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Table 2: Alert History (Manual + Automatic alerts)
|
||||
-- ----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS alert_history (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
timestamp BIGINT NOT NULL COMMENT 'Unix timestamp in milliseconds',
|
||||
alert_type VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
severity VARCHAR(20) NOT NULL COMMENT 'info, warning, critical',
|
||||
source VARCHAR(50) NOT NULL COMMENT 'manual or automatic',
|
||||
work_order_id VARCHAR(255),
|
||||
acknowledged BOOLEAN DEFAULT 0,
|
||||
acknowledged_at BIGINT,
|
||||
acknowledged_by VARCHAR(100),
|
||||
auto_resolved BOOLEAN DEFAULT 0,
|
||||
resolved_at BIGINT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_timestamp (timestamp),
|
||||
INDEX idx_severity (severity),
|
||||
INDEX idx_acknowledged (acknowledged),
|
||||
INDEX idx_work_order (work_order_id),
|
||||
INDEX idx_source (source)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Alert history for both manual and automatic alerts';
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Table 3: Shift Definitions (Reference data)
|
||||
-- ----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS shift_definitions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
shift_name VARCHAR(50) NOT NULL,
|
||||
start_hour INT NOT NULL COMMENT '0-23',
|
||||
start_minute INT NOT NULL DEFAULT 0,
|
||||
end_hour INT NOT NULL COMMENT '0-23',
|
||||
end_minute INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
UNIQUE KEY unique_shift_name (shift_name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Shift definitions for time range filtering';
|
||||
|
||||
-- Seed shift data
|
||||
INSERT IGNORE INTO shift_definitions (id, shift_name, start_hour, start_minute, end_hour, end_minute) VALUES
|
||||
(1, 'Day Shift', 6, 0, 15, 0),
|
||||
(2, 'Evening Shift', 15, 0, 23, 0),
|
||||
(3, 'Night Shift', 23, 0, 6, 0);
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Table 4: Session State (For crash recovery - Option 2)
|
||||
-- ----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS session_state (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
session_key VARCHAR(50) NOT NULL COMMENT 'Always "current_session" - single row table',
|
||||
work_order_id VARCHAR(255),
|
||||
cycle_count INT DEFAULT 0,
|
||||
production_start_time BIGINT COMMENT 'Unix timestamp when tracking started',
|
||||
operating_time DECIMAL(10,2) DEFAULT 0,
|
||||
downtime DECIMAL(10,2) DEFAULT 0,
|
||||
last_update_time BIGINT,
|
||||
tracking_enabled BOOLEAN DEFAULT 0,
|
||||
machine_state INT DEFAULT 0,
|
||||
scrap_prompt_issued_for VARCHAR(255),
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
UNIQUE KEY unique_session (session_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Current session state for crash recovery';
|
||||
|
||||
-- Initialize the single session row
|
||||
INSERT IGNORE INTO session_state (session_key, work_order_id, cycle_count, tracking_enabled)
|
||||
VALUES ('current_session', NULL, 0, 0);
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Table 5: Add columns to existing work_orders table (if not present)
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Check if scrap_count column exists, add if missing
|
||||
SET @col_exists = 0;
|
||||
SELECT COUNT(*) INTO @col_exists
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'machine_data'
|
||||
AND TABLE_NAME = 'work_orders'
|
||||
AND COLUMN_NAME = 'scrap_count';
|
||||
|
||||
SET @query = IF(@col_exists = 0,
|
||||
'ALTER TABLE work_orders ADD COLUMN scrap_count INT DEFAULT 0 AFTER good_parts',
|
||||
'SELECT "Column scrap_count already exists" AS Info');
|
||||
PREPARE stmt FROM @query;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Check if scrap_parts column exists (legacy), rename to scrap_count if needed
|
||||
SET @col_exists_legacy = 0;
|
||||
SELECT COUNT(*) INTO @col_exists_legacy
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'machine_data'
|
||||
AND TABLE_NAME = 'work_orders'
|
||||
AND COLUMN_NAME = 'scrap_parts';
|
||||
|
||||
SET @query = IF(@col_exists_legacy > 0,
|
||||
'ALTER TABLE work_orders CHANGE scrap_parts scrap_count INT DEFAULT 0',
|
||||
'SELECT "No legacy scrap_parts column to rename" AS Info');
|
||||
PREPARE stmt FROM @query;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Verification Queries (Run these to confirm migration success)
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- SELECT COUNT(*) AS kpi_snapshots_count FROM kpi_snapshots;
|
||||
-- SELECT COUNT(*) AS alert_history_count FROM alert_history;
|
||||
-- SELECT * FROM shift_definitions;
|
||||
-- SELECT * FROM session_state WHERE session_key = 'current_session';
|
||||
-- SHOW COLUMNS FROM work_orders;
|
||||
|
||||
-- ============================================================================
|
||||
-- END OF MIGRATION
|
||||
-- ============================================================================
|
||||
200
phase1_machine_cycles_function.js
Normal file
200
phase1_machine_cycles_function.js
Normal file
@@ -0,0 +1,200 @@
|
||||
// ============================================================================
|
||||
// UPDATED "Machine cycles" Function
|
||||
// Location: flows.json, line 1150, node ID: 0d023d87a13bf56f
|
||||
// Outputs: 3 (added third output for database state backup)
|
||||
// ============================================================================
|
||||
|
||||
const current = Number(msg.payload) || 0;
|
||||
|
||||
// ============================================================================
|
||||
// SECTION 1: TIME TRACKING (NEW - Must run BEFORE any early returns)
|
||||
// ============================================================================
|
||||
const now = Date.now();
|
||||
const trackingEnabled = !!global.get("trackingEnabled");
|
||||
const lastUpdate = global.get("lastUpdateTime") || now;
|
||||
const deltaMs = now - lastUpdate;
|
||||
const deltaSeconds = deltaMs / 1000;
|
||||
|
||||
// Sanity check: Protect against clock skew (negative delta or >5 min gap)
|
||||
if (deltaSeconds < 0 || deltaSeconds > 300) {
|
||||
node.warn(`[TIME] Abnormal delta: ${deltaSeconds.toFixed(2)}s - clock skew or restart detected, resetting timer`);
|
||||
global.set("lastUpdateTime", now);
|
||||
// Don't accumulate time, but continue processing
|
||||
} else {
|
||||
// Normal delta, accumulate time if tracking is enabled
|
||||
if (trackingEnabled) {
|
||||
// Initialize timing vars if they don't exist (handles restart scenario)
|
||||
if (!global.get("productionStartTime")) {
|
||||
node.warn("[TIME] Production start time missing, initializing now");
|
||||
global.set("productionStartTime", now);
|
||||
global.set("operatingTime", 0);
|
||||
global.set("downtime", 0);
|
||||
}
|
||||
|
||||
// Accumulate operating time when machine is running (state 1)
|
||||
if (current === 1) {
|
||||
const opTime = global.get("operatingTime") || 0;
|
||||
global.set("operatingTime", opTime + deltaSeconds);
|
||||
}
|
||||
// Accumulate downtime when machine is stopped (state 0) while tracking
|
||||
else if (current === 0) {
|
||||
const downTime = global.get("downtime") || 0;
|
||||
global.set("downtime", downTime + deltaSeconds);
|
||||
}
|
||||
}
|
||||
global.set("lastUpdateTime", now);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SECTION 2: EXISTING CYCLE COUNTING LOGIC (UNCHANGED)
|
||||
// ============================================================================
|
||||
let zeroStreak = flow.get("zeroStreak") || 0;
|
||||
zeroStreak = current === 0 ? zeroStreak + 1 : 0;
|
||||
flow.set("zeroStreak", zeroStreak);
|
||||
|
||||
const prev = flow.get("lastMachineState") ?? 0;
|
||||
flow.set("lastMachineState", current);
|
||||
|
||||
global.set("machineOnline", true); // force ONLINE for now
|
||||
|
||||
let productionRunning = !!global.get("productionStarted");
|
||||
let stateChanged = false;
|
||||
|
||||
if (current === 1 && !productionRunning) {
|
||||
productionRunning = true;
|
||||
stateChanged = true;
|
||||
} else if (current === 0 && zeroStreak >= 2 && productionRunning) {
|
||||
productionRunning = false;
|
||||
stateChanged = true;
|
||||
}
|
||||
|
||||
global.set("productionStarted", productionRunning);
|
||||
|
||||
const stateMsg = stateChanged
|
||||
? {
|
||||
_mode: "production-state",
|
||||
machineOnline: true,
|
||||
productionStarted: productionRunning
|
||||
}
|
||||
: null;
|
||||
|
||||
const activeOrder = global.get("activeWorkOrder");
|
||||
const cavities = Number(global.get("moldActive") || 0);
|
||||
if (!activeOrder || !activeOrder.id || cavities <= 0) {
|
||||
// We still want to pass along any state change even if there's no active WO.
|
||||
return [null, stateMsg, null];
|
||||
}
|
||||
|
||||
// Check if tracking is enabled (START button clicked)
|
||||
if (!trackingEnabled) {
|
||||
// Cycles are happening but we're not tracking them yet
|
||||
return [null, stateMsg, null];
|
||||
}
|
||||
|
||||
// only count rising edges (0 -> 1) for production totals
|
||||
if (prev === 1 || current !== 1) {
|
||||
return [null, stateMsg, null];
|
||||
}
|
||||
|
||||
let cycles = Number(global.get("cycleCount") || 0) + 1;
|
||||
global.set("cycleCount", cycles);
|
||||
|
||||
// Calculate good parts: total produced minus accumulated scrap
|
||||
const scrapTotal = Number(activeOrder.scrap) || 0;
|
||||
const totalProduced = cycles * cavities;
|
||||
const produced = totalProduced - scrapTotal;
|
||||
const target = Number(activeOrder.target) || 0;
|
||||
const progress = target > 0 ? Math.min(100, Math.round((produced / target) * 100)) : 0;
|
||||
|
||||
activeOrder.good = produced;
|
||||
activeOrder.progressPercent = progress;
|
||||
activeOrder.lastUpdateIso = new Date().toISOString();
|
||||
global.set("activeWorkOrder", activeOrder);
|
||||
|
||||
const promptIssued = global.get("scrapPromptIssuedFor") || null;
|
||||
|
||||
if (!promptIssued && target > 0 && produced >= target) {
|
||||
node.warn(`[DEBUG] TRIGGERING PROMPT!`);
|
||||
global.set("scrapPromptIssuedFor", activeOrder.id);
|
||||
msg._mode = "scrap-prompt";
|
||||
msg.scrapPrompt = {
|
||||
id: activeOrder.id,
|
||||
sku: activeOrder.sku || "",
|
||||
target,
|
||||
produced
|
||||
};
|
||||
return [null, msg, null]; // bypass the DB update on this cycle
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SECTION 3: DATABASE UPDATE MESSAGE (EXISTING)
|
||||
// ============================================================================
|
||||
const dbMsg = {
|
||||
_mode: "cycle",
|
||||
cycle: {
|
||||
id: activeOrder.id,
|
||||
sku: activeOrder.sku || "",
|
||||
target,
|
||||
good: produced,
|
||||
scrap: Number(activeOrder.scrap) || 0,
|
||||
cycleTime: Number(activeOrder.cycleTime || activeOrder.theoreticalCycleTime || 0),
|
||||
progressPercent: progress,
|
||||
lastUpdateIso: activeOrder.lastUpdateIso,
|
||||
machineOnline: true,
|
||||
productionStarted: productionRunning
|
||||
},
|
||||
topic: `
|
||||
UPDATE work_orders
|
||||
SET
|
||||
good_parts = ${produced},
|
||||
progress_percent = ${progress},
|
||||
updated_at = NOW()
|
||||
WHERE work_order_id = '${activeOrder.id}';
|
||||
`
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SECTION 4: STATE BACKUP TO DATABASE (NEW - Every 10th cycle to reduce load)
|
||||
// ============================================================================
|
||||
let cyclesSinceBackup = flow.get("cyclesSinceBackup") || 0;
|
||||
cyclesSinceBackup++;
|
||||
flow.set("cyclesSinceBackup", cyclesSinceBackup);
|
||||
|
||||
let stateBackupMsg = null;
|
||||
if (cyclesSinceBackup >= 10) {
|
||||
// Reset counter
|
||||
flow.set("cyclesSinceBackup", 0);
|
||||
|
||||
// Backup current state to database
|
||||
const productionStartTime = global.get("productionStartTime") || null;
|
||||
const operatingTime = global.get("operatingTime") || 0;
|
||||
const downtime = global.get("downtime") || 0;
|
||||
const lastUpdateTime = global.get("lastUpdateTime") || null;
|
||||
const scrapPromptIssuedFor = global.get("scrapPromptIssuedFor") || null;
|
||||
|
||||
stateBackupMsg = {
|
||||
_mode: "state-backup",
|
||||
topic: `
|
||||
UPDATE session_state
|
||||
SET
|
||||
work_order_id = '${activeOrder.id}',
|
||||
cycle_count = ${cycles},
|
||||
production_start_time = ${productionStartTime},
|
||||
operating_time = ${operatingTime.toFixed(2)},
|
||||
downtime = ${downtime.toFixed(2)},
|
||||
last_update_time = ${lastUpdateTime},
|
||||
tracking_enabled = ${trackingEnabled ? 1 : 0},
|
||||
machine_state = ${current},
|
||||
scrap_prompt_issued_for = ${scrapPromptIssuedFor ? `'${scrapPromptIssuedFor}'` : 'NULL'}
|
||||
WHERE session_key = 'current_session';
|
||||
`
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OUTPUTS:
|
||||
// Output 1: Database update for work_orders table (existing)
|
||||
// Output 2: State message to UI (existing)
|
||||
// Output 3: State backup to session_state table (NEW)
|
||||
// ============================================================================
|
||||
return [dbMsg, stateMsg, stateBackupMsg];
|
||||
Reference in New Issue
Block a user