From e3a418d603a54df66fdc5e3441441702fba5273d Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 16 Mar 2026 14:41:28 -0400 Subject: [PATCH] Add Machine Data seed (tables, stored procedures, sample data) and fix SA password for shell compatibility --- infra/README.md | 10 +- infra/docker-compose.yml | 3 +- infra/mssql/machinedata_seed.sql | 330 +++++++++++++++++++++++++++++++ infra/mssql/setup.sql | 2 +- infra/tools/mssql_tool.py | 2 +- test_infra.md | 13 +- test_infra_db.md | 56 +++++- 7 files changed, 397 insertions(+), 19 deletions(-) create mode 100644 infra/mssql/machinedata_seed.sql diff --git a/infra/README.md b/infra/README.md index 67e6619..4f67e4d 100644 --- a/infra/README.md +++ b/infra/README.md @@ -24,11 +24,17 @@ The MS SQL container does not auto-run init scripts. After the first `docker com ```bash docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U sa -P 'ScadaLink_Dev1!' -C \ + -S localhost -U sa -P 'ScadaLink_Dev1#' -C \ -i /docker-entrypoint-initdb.d/setup.sql ``` -This creates the `ScadaLinkConfig` and `ScadaLinkMachineData` databases and the `scadalink_app` login. +This creates the `ScadaLinkConfig` and `ScadaLinkMachineData` databases and the `scadalink_app` login. Then seed the Machine Data database: + +```bash +docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U sa -P 'ScadaLink_Dev1#' -C \ + -i /docker-entrypoint-initdb.d/machinedata_seed.sql +``` ## Stopping & Teardown diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 6fbe00e..52629cb 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -34,11 +34,12 @@ services: - "1433:1433" environment: ACCEPT_EULA: "Y" - MSSQL_SA_PASSWORD: "ScadaLink_Dev1!" + MSSQL_SA_PASSWORD: "ScadaLink_Dev1#" MSSQL_PID: "Developer" volumes: - scadalink-mssql-data:/var/opt/mssql - ./mssql/setup.sql:/docker-entrypoint-initdb.d/setup.sql:ro + - ./mssql/machinedata_seed.sql:/docker-entrypoint-initdb.d/machinedata_seed.sql:ro restart: unless-stopped smtp: diff --git a/infra/mssql/machinedata_seed.sql b/infra/mssql/machinedata_seed.sql new file mode 100644 index 0000000..5fade6d --- /dev/null +++ b/infra/mssql/machinedata_seed.sql @@ -0,0 +1,330 @@ +-- ScadaLink Machine Data Database seed script +-- Populates ScadaLinkMachineData with realistic SCADA/MES tables, +-- sample data, and stored procedures for development and testing. +-- +-- Run after setup.sql: +-- docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \ +-- -S localhost -U sa -P 'ScadaLink_Dev1#' -C \ +-- -i /docker-entrypoint-initdb.d/machinedata_seed.sql + +USE ScadaLinkMachineData; +GO + +-- ========================================================================= +-- Tables +-- ========================================================================= + +-- Tag history: time-series data collected from OPC UA / custom protocols +IF OBJECT_ID('dbo.TagHistory', 'U') IS NULL +CREATE TABLE dbo.TagHistory ( + Id BIGINT IDENTITY(1,1) PRIMARY KEY, + TagPath NVARCHAR(256) NOT NULL, + Timestamp DATETIME2(3) NOT NULL, + Value FLOAT NULL, + StringValue NVARCHAR(512) NULL, + Quality TINYINT NOT NULL DEFAULT 192, -- 192 = Good + SiteId NVARCHAR(64) NOT NULL, + INDEX IX_TagHistory_TagPath_Timestamp (TagPath, Timestamp DESC), + INDEX IX_TagHistory_SiteId_Timestamp (SiteId, Timestamp DESC) +); +GO + +-- Production counts: shift/line production totals +IF OBJECT_ID('dbo.ProductionCounts', 'U') IS NULL +CREATE TABLE dbo.ProductionCounts ( + Id BIGINT IDENTITY(1,1) PRIMARY KEY, + SiteId NVARCHAR(64) NOT NULL, + LineName NVARCHAR(128) NOT NULL, + ShiftDate DATE NOT NULL, + ShiftNumber TINYINT NOT NULL, + GoodCount INT NOT NULL DEFAULT 0, + RejectCount INT NOT NULL DEFAULT 0, + Efficiency DECIMAL(5,2) NULL, + RecordedAt DATETIME2(3) NOT NULL DEFAULT SYSUTCDATETIME(), + INDEX IX_ProductionCounts_Site_Date (SiteId, ShiftDate DESC) +); +GO + +-- Equipment events: state changes, faults, maintenance +IF OBJECT_ID('dbo.EquipmentEvents', 'U') IS NULL +CREATE TABLE dbo.EquipmentEvents ( + Id BIGINT IDENTITY(1,1) PRIMARY KEY, + SiteId NVARCHAR(64) NOT NULL, + EquipmentId NVARCHAR(128) NOT NULL, + EventType NVARCHAR(32) NOT NULL, -- 'StateChange', 'Fault', 'Maintenance', 'Alarm' + PreviousState NVARCHAR(64) NULL, + NewState NVARCHAR(64) NOT NULL, + Description NVARCHAR(512) NULL, + Timestamp DATETIME2(3) NOT NULL, + INDEX IX_EquipmentEvents_Equipment_Time (EquipmentId, Timestamp DESC), + INDEX IX_EquipmentEvents_Site_Type (SiteId, EventType, Timestamp DESC) +); +GO + +-- Batch records: production batch tracking +IF OBJECT_ID('dbo.BatchRecords', 'U') IS NULL +CREATE TABLE dbo.BatchRecords ( + Id BIGINT IDENTITY(1,1) PRIMARY KEY, + BatchId NVARCHAR(64) NOT NULL UNIQUE, + SiteId NVARCHAR(64) NOT NULL, + RecipeId NVARCHAR(64) NOT NULL, + Status NVARCHAR(32) NOT NULL DEFAULT 'InProgress', -- 'InProgress', 'Complete', 'Aborted' + StartTime DATETIME2(3) NOT NULL, + EndTime DATETIME2(3) NULL, + TotalQuantity DECIMAL(10,2) NULL, + Operator NVARCHAR(128) NULL, + Notes NVARCHAR(1024) NULL, + INDEX IX_BatchRecords_Site_Status (SiteId, Status), + INDEX IX_BatchRecords_StartTime (StartTime DESC) +); +GO + +-- Alarm history: historical alarm events +IF OBJECT_ID('dbo.AlarmHistory', 'U') IS NULL +CREATE TABLE dbo.AlarmHistory ( + Id BIGINT IDENTITY(1,1) PRIMARY KEY, + SiteId NVARCHAR(64) NOT NULL, + AlarmName NVARCHAR(256) NOT NULL, + Severity TINYINT NOT NULL, -- 1=Low, 2=Medium, 3=High, 4=Critical + State NVARCHAR(32) NOT NULL, -- 'Active', 'Acknowledged', 'Cleared' + ActivatedAt DATETIME2(3) NOT NULL, + AcknowledgedAt DATETIME2(3) NULL, + ClearedAt DATETIME2(3) NULL, + AcknowledgedBy NVARCHAR(128) NULL, + Message NVARCHAR(512) NULL, + INDEX IX_AlarmHistory_Site_Active (SiteId, State, ActivatedAt DESC), + INDEX IX_AlarmHistory_Severity (Severity, ActivatedAt DESC) +); +GO + +-- ========================================================================= +-- Stored Procedures +-- ========================================================================= + +-- Get tag history for a tag path within a date range +IF OBJECT_ID('dbo.usp_GetTagHistory', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_GetTagHistory; +GO +CREATE PROCEDURE dbo.usp_GetTagHistory + @TagPath NVARCHAR(256), + @StartTime DATETIME2(3), + @EndTime DATETIME2(3), + @MaxRows INT = 10000 +AS +BEGIN + SET NOCOUNT ON; + SELECT TOP (@MaxRows) + TagPath, Timestamp, Value, StringValue, Quality, SiteId + FROM dbo.TagHistory + WHERE TagPath = @TagPath + AND Timestamp >= @StartTime + AND Timestamp <= @EndTime + ORDER BY Timestamp DESC; +END; +GO + +-- Get production summary for a site over a date range +IF OBJECT_ID('dbo.usp_GetProductionSummary', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_GetProductionSummary; +GO +CREATE PROCEDURE dbo.usp_GetProductionSummary + @SiteId NVARCHAR(64), + @StartDate DATE, + @EndDate DATE +AS +BEGIN + SET NOCOUNT ON; + SELECT + LineName, + SUM(GoodCount) AS TotalGood, + SUM(RejectCount) AS TotalReject, + SUM(GoodCount) + SUM(RejectCount) AS TotalProduced, + CASE + WHEN SUM(GoodCount) + SUM(RejectCount) > 0 + THEN CAST(SUM(GoodCount) * 100.0 / (SUM(GoodCount) + SUM(RejectCount)) AS DECIMAL(5,2)) + ELSE 0 + END AS YieldPercent, + AVG(Efficiency) AS AvgEfficiency, + COUNT(DISTINCT ShiftDate) AS DaysReported + FROM dbo.ProductionCounts + WHERE SiteId = @SiteId + AND ShiftDate >= @StartDate + AND ShiftDate <= @EndDate + GROUP BY LineName + ORDER BY LineName; +END; +GO + +-- Insert a batch record (used by CachedWrite from scripts) +IF OBJECT_ID('dbo.usp_InsertBatchRecord', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_InsertBatchRecord; +GO +CREATE PROCEDURE dbo.usp_InsertBatchRecord + @BatchId NVARCHAR(64), + @SiteId NVARCHAR(64), + @RecipeId NVARCHAR(64), + @TotalQuantity DECIMAL(10,2) = NULL, + @Operator NVARCHAR(128) = NULL, + @Notes NVARCHAR(1024) = NULL +AS +BEGIN + SET NOCOUNT ON; + INSERT INTO dbo.BatchRecords (BatchId, SiteId, RecipeId, Status, StartTime, TotalQuantity, Operator, Notes) + VALUES (@BatchId, @SiteId, @RecipeId, 'InProgress', SYSUTCDATETIME(), @TotalQuantity, @Operator, @Notes); + + SELECT SCOPE_IDENTITY() AS Id, @BatchId AS BatchId; +END; +GO + +-- Complete or abort a batch +IF OBJECT_ID('dbo.usp_CompleteBatch', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_CompleteBatch; +GO +CREATE PROCEDURE dbo.usp_CompleteBatch + @BatchId NVARCHAR(64), + @Status NVARCHAR(32), -- 'Complete' or 'Aborted' + @TotalQuantity DECIMAL(10,2) = NULL, + @Notes NVARCHAR(1024) = NULL +AS +BEGIN + SET NOCOUNT ON; + UPDATE dbo.BatchRecords + SET Status = @Status, + EndTime = SYSUTCDATETIME(), + TotalQuantity = COALESCE(@TotalQuantity, TotalQuantity), + Notes = COALESCE(@Notes, Notes) + WHERE BatchId = @BatchId + AND Status = 'InProgress'; + + IF @@ROWCOUNT = 0 + THROW 50001, 'Batch not found or not in progress.', 1; + + SELECT BatchId, Status, StartTime, EndTime, TotalQuantity + FROM dbo.BatchRecords + WHERE BatchId = @BatchId; +END; +GO + +-- Get recent equipment events +IF OBJECT_ID('dbo.usp_GetEquipmentEvents', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_GetEquipmentEvents; +GO +CREATE PROCEDURE dbo.usp_GetEquipmentEvents + @SiteId NVARCHAR(64), + @EquipmentId NVARCHAR(128) = NULL, + @EventType NVARCHAR(32) = NULL, + @Hours INT = 24 +AS +BEGIN + SET NOCOUNT ON; + DECLARE @Since DATETIME2(3) = DATEADD(HOUR, -@Hours, SYSUTCDATETIME()); + + SELECT + EquipmentId, EventType, PreviousState, NewState, Description, Timestamp + FROM dbo.EquipmentEvents + WHERE SiteId = @SiteId + AND Timestamp >= @Since + AND (@EquipmentId IS NULL OR EquipmentId = @EquipmentId) + AND (@EventType IS NULL OR EventType = @EventType) + ORDER BY Timestamp DESC; +END; +GO + +-- Get active alarms for a site +IF OBJECT_ID('dbo.usp_GetActiveAlarms', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_GetActiveAlarms; +GO +CREATE PROCEDURE dbo.usp_GetActiveAlarms + @SiteId NVARCHAR(64), + @MinSeverity TINYINT = 1 +AS +BEGIN + SET NOCOUNT ON; + SELECT + AlarmName, Severity, State, ActivatedAt, AcknowledgedAt, + AcknowledgedBy, Message + FROM dbo.AlarmHistory + WHERE SiteId = @SiteId + AND State IN ('Active', 'Acknowledged') + AND Severity >= @MinSeverity + ORDER BY Severity DESC, ActivatedAt DESC; +END; +GO + +-- ========================================================================= +-- Sample Data +-- ========================================================================= + +-- Tag history (last few hours of data for two sites) +DECLARE @now DATETIME2(3) = SYSUTCDATETIME(); + +INSERT INTO dbo.TagHistory (TagPath, Timestamp, Value, Quality, SiteId) VALUES +('SiteA/Pump-001/Pressure', DATEADD(MINUTE, -120, @now), 45.2, 192, 'SiteA'), +('SiteA/Pump-001/Pressure', DATEADD(MINUTE, -110, @now), 46.1, 192, 'SiteA'), +('SiteA/Pump-001/Pressure', DATEADD(MINUTE, -100, @now), 44.8, 192, 'SiteA'), +('SiteA/Pump-001/Pressure', DATEADD(MINUTE, -90, @now), 47.3, 192, 'SiteA'), +('SiteA/Pump-001/Pressure', DATEADD(MINUTE, -80, @now), 45.9, 192, 'SiteA'), +('SiteA/Pump-001/Flow', DATEADD(MINUTE, -120, @now), 120.5, 192, 'SiteA'), +('SiteA/Pump-001/Flow', DATEADD(MINUTE, -110, @now), 121.2, 192, 'SiteA'), +('SiteA/Pump-001/Flow', DATEADD(MINUTE, -100, @now), 119.8, 192, 'SiteA'), +('SiteA/Tank-001/Level', DATEADD(MINUTE, -120, @now), 72.0, 192, 'SiteA'), +('SiteA/Tank-001/Level', DATEADD(MINUTE, -110, @now), 73.5, 192, 'SiteA'), +('SiteA/Tank-001/Level', DATEADD(MINUTE, -100, @now), 75.1, 192, 'SiteA'), +('SiteA/Tank-001/Level', DATEADD(MINUTE, -90, @now), 76.8, 192, 'SiteA'), +('SiteA/Tank-001/Temperature', DATEADD(MINUTE, -120, @now), 65.3, 192, 'SiteA'), +('SiteA/Tank-001/Temperature', DATEADD(MINUTE, -110, @now), 65.5, 192, 'SiteA'), +('SiteA/Conv-001/Speed', DATEADD(MINUTE, -120, @now), 2.4, 192, 'SiteA'), +('SiteA/Conv-001/Speed', DATEADD(MINUTE, -110, @now), 0.0, 0, 'SiteA'), -- Bad quality (stopped) +('SiteA/Conv-001/Speed', DATEADD(MINUTE, -100, @now), 2.3, 192, 'SiteA'), +('SiteB/Mixer-001/RPM', DATEADD(MINUTE, -120, @now), 450.0, 192, 'SiteB'), +('SiteB/Mixer-001/RPM', DATEADD(MINUTE, -110, @now), 452.0, 192, 'SiteB'), +('SiteB/Mixer-001/RPM', DATEADD(MINUTE, -100, @now), 448.0, 192, 'SiteB'), +('SiteB/Mixer-001/Temperature',DATEADD(MINUTE, -120, @now), 82.1, 192, 'SiteB'), +('SiteB/Mixer-001/Temperature',DATEADD(MINUTE, -110, @now), 83.0, 192, 'SiteB'); + +-- Production counts (last 3 days, 2 shifts per day) +DECLARE @today DATE = CAST(SYSUTCDATETIME() AS DATE); + +INSERT INTO dbo.ProductionCounts (SiteId, LineName, ShiftDate, ShiftNumber, GoodCount, RejectCount, Efficiency) VALUES +('SiteA', 'Line-1', DATEADD(DAY, -2, @today), 1, 4100, 82, 92.5), +('SiteA', 'Line-1', DATEADD(DAY, -2, @today), 2, 3900, 95, 91.2), +('SiteA', 'Line-2', DATEADD(DAY, -2, @today), 1, 3050, 120, 88.1), +('SiteA', 'Line-2', DATEADD(DAY, -2, @today), 2, 2900, 105, 87.5), +('SiteA', 'Line-1', DATEADD(DAY, -1, @today), 1, 4200, 75, 93.1), +('SiteA', 'Line-1', DATEADD(DAY, -1, @today), 2, 4050, 88, 92.0), +('SiteA', 'Line-2', DATEADD(DAY, -1, @today), 1, 3100, 98, 89.2), +('SiteA', 'Line-2', DATEADD(DAY, -1, @today), 2, 3000, 110, 88.0), +('SiteA', 'Line-1', @today, 1, 2100, 40, 93.5), +('SiteA', 'Line-2', @today, 1, 1550, 65, 88.8), +('SiteB', 'Mixing', DATEADD(DAY, -1, @today), 1, 850, 12, 95.0), +('SiteB', 'Mixing', DATEADD(DAY, -1, @today), 2, 820, 15, 94.2), +('SiteB', 'Packing', DATEADD(DAY, -1, @today), 1, 1600, 45, 90.5), +('SiteB', 'Packing', DATEADD(DAY, -1, @today), 2, 1550, 50, 89.8); + +-- Equipment events +INSERT INTO dbo.EquipmentEvents (SiteId, EquipmentId, EventType, PreviousState, NewState, Description, Timestamp) VALUES +('SiteA', 'PUMP-001', 'StateChange', 'Idle', 'Running', 'Shift start', DATEADD(HOUR, -8, @now)), +('SiteA', 'PUMP-001', 'Fault', 'Running', 'Faulted', 'Overcurrent detected', DATEADD(HOUR, -5, @now)), +('SiteA', 'PUMP-001', 'StateChange', 'Faulted', 'Running', 'Manual reset by operator', DATEADD(HOUR, -4, @now)), +('SiteA', 'TANK-001', 'Alarm', NULL, 'HighLevel','Tank level exceeded 90%', DATEADD(HOUR, -3, @now)), +('SiteA', 'TANK-001', 'Alarm', NULL, 'Normal', 'Tank level returned to normal',DATEADD(HOUR, -2, @now)), +('SiteA', 'CONV-001', 'Maintenance', 'Running', 'Maintenance', 'Scheduled belt inspection', DATEADD(HOUR, -6, @now)), +('SiteA', 'CONV-001', 'StateChange', 'Maintenance', 'Running', 'Maintenance complete', DATEADD(HOUR, -5, @now)), +('SiteB', 'MIXER-001','StateChange', 'Idle', 'Running', 'Batch R-100 started', DATEADD(HOUR, -7, @now)), +('SiteB', 'MIXER-001','StateChange', 'Running', 'Idle', 'Batch R-100 complete', DATEADD(HOUR, -3, @now)); + +-- Batch records +INSERT INTO dbo.BatchRecords (BatchId, SiteId, RecipeId, Status, StartTime, EndTime, TotalQuantity, Operator, Notes) VALUES +('BATCH-20260314-001', 'SiteA', 'R-100', 'Complete', DATEADD(HOUR, -26, @now), DATEADD(HOUR, -22, @now), 450.00, 'jsmith', 'Normal run'), +('BATCH-20260314-002', 'SiteA', 'R-200', 'Complete', DATEADD(HOUR, -20, @now), DATEADD(HOUR, -16, @now), 380.50, 'jsmith', 'Slight yield loss on Material-B'), +('BATCH-20260315-001', 'SiteB', 'R-100', 'Complete', DATEADD(HOUR, -10, @now), DATEADD(HOUR, -6, @now), 445.00, 'mdoe', NULL), +('BATCH-20260315-002', 'SiteA', 'R-150', 'Aborted', DATEADD(HOUR, -8, @now), DATEADD(HOUR, -7, @now), NULL, 'jsmith', 'Material-A out of spec, aborted early'), +('BATCH-20260316-001', 'SiteA', 'R-100', 'InProgress', DATEADD(HOUR, -2, @now), NULL, NULL, 'bwilson', 'Current batch'); + +-- Alarm history +INSERT INTO dbo.AlarmHistory (SiteId, AlarmName, Severity, State, ActivatedAt, AcknowledgedAt, ClearedAt, AcknowledgedBy, Message) VALUES +('SiteA', 'Tank-001 High Level', 3, 'Cleared', DATEADD(HOUR, -3, @now), DATEADD(HOUR, -3, @now), DATEADD(HOUR, -2, @now), 'jsmith', 'Level exceeded 90% setpoint'), +('SiteA', 'Pump-001 Overcurrent', 4, 'Cleared', DATEADD(HOUR, -5, @now), DATEADD(HOUR, -5, @now), DATEADD(HOUR, -4, @now), 'jsmith', 'Current draw 15.2A (limit 12A)'), +('SiteA', 'Conv-001 Belt Slip', 2, 'Active', DATEADD(HOUR, -1, @now), NULL, NULL, NULL, 'Belt speed deviation >5%'), +('SiteA', 'Tank-001 Temperature High', 3, 'Acknowledged', DATEADD(MINUTE, -30, @now), DATEADD(MINUTE, -25, @now), NULL, 'bwilson', 'Temperature 68.2C (limit 65C)'), +('SiteB', 'Mixer-001 Vibration', 2, 'Active', DATEADD(HOUR, -2, @now), NULL, NULL, NULL, 'Vibration level elevated'); +GO + +PRINT 'ScadaLinkMachineData seed complete.'; +PRINT 'Tables: TagHistory, ProductionCounts, EquipmentEvents, BatchRecords, AlarmHistory'; +PRINT 'Stored Procedures: usp_GetTagHistory, usp_GetProductionSummary, usp_InsertBatchRecord, usp_CompleteBatch, usp_GetEquipmentEvents, usp_GetActiveAlarms'; +GO diff --git a/infra/mssql/setup.sql b/infra/mssql/setup.sql index aa43c1b..b780201 100644 --- a/infra/mssql/setup.sql +++ b/infra/mssql/setup.sql @@ -14,7 +14,7 @@ GO -- Create application login IF NOT EXISTS (SELECT name FROM sys.server_principals WHERE name = 'scadalink_app') - CREATE LOGIN scadalink_app WITH PASSWORD = 'ScadaLink_Dev1!', DEFAULT_DATABASE = ScadaLinkConfig; + CREATE LOGIN scadalink_app WITH PASSWORD = 'ScadaLink_Dev1#', DEFAULT_DATABASE = ScadaLinkConfig; GO -- Grant db_owner on ScadaLinkConfig diff --git a/infra/tools/mssql_tool.py b/infra/tools/mssql_tool.py index 2cd1b36..00a7c90 100644 --- a/infra/tools/mssql_tool.py +++ b/infra/tools/mssql_tool.py @@ -10,7 +10,7 @@ import pymssql DEFAULT_HOST = "localhost" DEFAULT_PORT = 1433 DEFAULT_USER = "sa" -DEFAULT_PASSWORD = "ScadaLink_Dev1!" +DEFAULT_PASSWORD = "ScadaLink_Dev1#" EXPECTED_DBS = ["ScadaLinkConfig", "ScadaLinkMachineData"] diff --git a/test_infra.md b/test_infra.md index 6dcfcb4..2ab4ca0 100644 --- a/test_infra.md +++ b/test_infra.md @@ -19,12 +19,16 @@ cd infra docker compose up -d ``` -After the first startup, run the SQL setup script to create databases and the application user: +After the first startup, run the SQL setup and seed scripts: ```bash docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U sa -P 'ScadaLink_Dev1!' -C \ + -S localhost -U sa -P 'ScadaLink_Dev1#' -C \ -i /docker-entrypoint-initdb.d/setup.sql + +docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U sa -P 'ScadaLink_Dev1#' -C \ + -i /docker-entrypoint-initdb.d/machinedata_seed.sql ``` ## Per-Service Documentation @@ -44,8 +48,8 @@ For use in `appsettings.Development.json`: ```json { "ConnectionStrings": { - "ScadaLinkConfig": "Server=localhost,1433;Database=ScadaLinkConfig;User Id=scadalink_app;Password=ScadaLink_Dev1!;TrustServerCertificate=true", - "ScadaLinkMachineData": "Server=localhost,1433;Database=ScadaLinkMachineData;User Id=scadalink_app;Password=ScadaLink_Dev1!;TrustServerCertificate=true" + "ScadaLinkConfig": "Server=localhost,1433;Database=ScadaLinkConfig;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true", + "ScadaLinkMachineData": "Server=localhost,1433;Database=ScadaLinkMachineData;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true" }, "Ldap": { "Server": "localhost", @@ -99,6 +103,7 @@ infra/ teardown.sh # Teardown script (volumes, images, venv) glauth/config.toml # LDAP users and groups mssql/setup.sql # Database and user creation + mssql/machinedata_seed.sql # Machine Data tables, stored procedures, sample data opcua/nodes.json # Custom OPC UA tag definitions restapi/app.py # Flask REST API server restapi/Dockerfile # REST API container build diff --git a/test_infra_db.md b/test_infra_db.md index 448137e..78d028d 100644 --- a/test_infra_db.md +++ b/test_infra_db.md @@ -14,8 +14,8 @@ The test database uses Microsoft SQL Server 2022 Developer Edition running in Do | Account | Username | Password | Purpose | |---------|----------|----------|---------| -| SA | `sa` | `ScadaLink_Dev1!` | Server admin (setup only) | -| App | `scadalink_app` | `ScadaLink_Dev1!` | Application login (db_owner on both databases) | +| SA | `sa` | `ScadaLink_Dev1#` | Server admin (setup only) | +| App | `scadalink_app` | `ScadaLink_Dev1#` | Application login (db_owner on both databases) | ## Databases @@ -24,7 +24,7 @@ The test database uses Microsoft SQL Server 2022 Developer Edition running in Do | `ScadaLinkConfig` | Configuration Database component — templates, deployments, users, audit log | | `ScadaLinkMachineData` | Machine/operational data storage | -Both databases are created empty by `infra/mssql/setup.sql`. EF Core migrations populate the schema. +Both databases are created by `infra/mssql/setup.sql`. EF Core migrations populate the `ScadaLinkConfig` schema. The `ScadaLinkMachineData` database is seeded with sample tables and stored procedures by `infra/mssql/machinedata_seed.sql`. ## Data Persistence @@ -42,22 +42,30 @@ After the first `docker compose up -d`, run the setup script: ```bash docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U sa -P 'ScadaLink_Dev1!' -C \ + -S localhost -U sa -P 'ScadaLink_Dev1#' -C \ -i /docker-entrypoint-initdb.d/setup.sql ``` -This creates the databases and the `scadalink_app` login. You only need to run this once (or again after deleting the volume). +This creates the databases and the `scadalink_app` login. Then seed the Machine Data database with sample tables, stored procedures, and data: + +```bash +docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U sa -P 'ScadaLink_Dev1#' -C \ + -i /docker-entrypoint-initdb.d/machinedata_seed.sql +``` + +You only need to run these once (or again after deleting the volume). ## Connection Strings For `appsettings.Development.json`: ``` -Server=localhost,1433;Database=ScadaLinkConfig;User Id=scadalink_app;Password=ScadaLink_Dev1!;TrustServerCertificate=true +Server=localhost,1433;Database=ScadaLinkConfig;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true ``` ``` -Server=localhost,1433;Database=ScadaLinkMachineData;User Id=scadalink_app;Password=ScadaLink_Dev1!;TrustServerCertificate=true +Server=localhost,1433;Database=ScadaLinkMachineData;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true ``` ## Verification @@ -72,7 +80,7 @@ docker ps --filter name=scadalink-mssql ```bash docker exec -it scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U sa -P 'ScadaLink_Dev1!' -C \ + -S localhost -U sa -P 'ScadaLink_Dev1#' -C \ -Q "SELECT name FROM sys.databases" ``` @@ -80,7 +88,7 @@ docker exec -it scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \ ```bash docker exec -it scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U scadalink_app -P 'ScadaLink_Dev1!' -C \ + -S localhost -U scadalink_app -P 'ScadaLink_Dev1#' -C \ -d ScadaLinkConfig \ -Q "SELECT DB_NAME()" ``` @@ -110,7 +118,32 @@ python infra/tools/mssql_tool.py tables --database ScadaLinkConfig python infra/tools/mssql_tool.py query --database ScadaLinkConfig --sql "SELECT name FROM sys.tables" ``` -Use `--host`, `--port`, `--user`, `--password` to override defaults (localhost:1433, sa, ScadaLink_Dev1!). Run with `--help` for full usage. +Use `--host`, `--port`, `--user`, `--password` to override defaults (localhost:1433, sa, ScadaLink_Dev1#). Run with `--help` for full usage. + +## Machine Data Tables & Stored Procedures + +The `machinedata_seed.sql` script creates the following in `ScadaLinkMachineData`: + +**Tables**: + +| Table | Description | Sample Data | +|-------|-------------|-------------| +| `TagHistory` | Time-series tag values from OPC UA / custom protocols | Pressure, flow, level, temperature, speed readings for SiteA/SiteB | +| `ProductionCounts` | Shift/line production totals (good, reject, efficiency) | 3 days of 2-shift data across 2 sites | +| `EquipmentEvents` | State changes, faults, maintenance, alarm events | Pump faults, belt inspections, batch starts | +| `BatchRecords` | Production batch tracking (start, complete, abort) | 5 batches including one in-progress | +| `AlarmHistory` | Historical alarm activations, acks, and clears | Active, acknowledged, and cleared alarms | + +**Stored Procedures**: + +| Procedure | Description | +|-----------|-------------| +| `usp_GetTagHistory` | Get tag values for a tag path within a date range | +| `usp_GetProductionSummary` | Aggregate production by line over a date range | +| `usp_InsertBatchRecord` | Insert a new batch (for `CachedWrite` testing) | +| `usp_CompleteBatch` | Complete or abort a batch | +| `usp_GetEquipmentEvents` | Get recent equipment events with optional filters | +| `usp_GetActiveAlarms` | Get active/acknowledged alarms by severity | ## Relevance to ScadaLink Components @@ -118,6 +151,9 @@ Use `--host`, `--port`, `--user`, `--password` to override defaults (localhost:1 - **Deployment Manager** — reads/writes deployment records in `ScadaLinkConfig`. - **Template Engine** — reads/writes template definitions in `ScadaLinkConfig`. - **Security & Auth** — user/role data stored in `ScadaLinkConfig`. +- **External System Gateway** — scripts use `Database.Connection("machineDataConnection")` to query `ScadaLinkMachineData` tables and stored procedures. +- **Site Runtime** — scripts call stored procedures via `Database.Connection()` and `Database.CachedWrite()` for batch recording and data queries. +- **Inbound API** — methods can query machine data via named database connections. ## Notes