"""Seed test data for the OPC Gateway e2e validation session (SIENTIAPDE-1972). Populates: 1. Mongo `connectors` + `connector_tags` (each tag with `metadata: {}` — required for documentation, per the feature owner). 2. Redis slot v1 (`slot:opc_tags:1`) so the legacy subscription path collects them. 3. Postgres test predictions table (with the `write_status` column) + one row the NATS write test can UPDATE (correlated by timestamp + model_id). Idempotent: connectors/tags are upserted, the slot is overwritten, the table uses CREATE IF NOT EXISTS + ON CONFLICT DO NOTHING. Usage (from the repo root, sientia-dataops conda env): python scripts/seed_e2e.py --print-only # dry-run: show docs/SQL, touch nothing python scripts/seed_e2e.py # seed Mongo + Redis + Postgres Env (same names the gateway/app use): MONGODB_URL/MONGODB_USERNAME/MONGODB_PASSWORD/MONGODB_DATABASE REDIS_HOST/REDIS_PORT POSTGRES_HOST/PORT/USER/PASSWORD/DBNAME OPC_SERVER_URL (default opc.tcp://localhost:49320 — KepServer default port) # ponytail: plain sync pymongo/redis/sqlalchemy, no sientia_do async plumbing — # this is a test fixture, not production code. """ import argparse import json import os import sys OPC_URL = os.getenv('OPC_SERVER_URL', 'opc.tcp://localhost:49320') CONNECTOR = { 'connector_id': 9001, 'type': 'opc_ua', 'name': 'e2e-kepserver', 'url': OPC_URL, 'server_uri': 'urn:e2e:gateway:test', 'subscription_period_ms': 1000, 'use_security': False, # round 1; flip after certs are in sientia-dataops 'status': 'Active', 'description': 'OPC Gateway e2e validation connector (SIENTIAPDE-1972)', } # metadata: {} on every tag — feature owner asked test tags to carry the field. TAGS = [ {'tag_id': 9101, 'connector_id': 9001, 'address': 'ns=2;s=Simulation Examples.Functions.Ramp1', 'tag_name': 'e2e_ramp', 'category': 'Read', 'data_type': 'float', 'unit': '-', 'status': 'Active', 'description': 'e2e read tag', 'metadata': {}}, {'tag_id': 9102, 'connector_id': 9001, 'address': 'ns=2;s=Simulation Examples.Functions.User1', 'tag_name': 'e2e_write_target', 'category': 'Write', 'data_type': 'float', 'unit': '-', 'status': 'Active', 'description': 'e2e write tag', 'metadata': {}}, ] # Slot v1 (legacy path the gateway still runs until SIENTIAPDE-1974 delivers slot v2). SLOT_V1 = { 'e2e-kepserver': { 'name': 'e2e-kepserver', 'server_id': '9001', 'url': OPC_URL, 'server_uri': 'urn:e2e:gateway:test', 'subscription_period_ms': 1000, 'tags': { TAGS[0]['address']: { 'tag_name': TAGS[0]['tag_name'], 'frequency': '5000', 'topics': ['raw_e2e-gateway-test'], } }, } } PREDICTIONS_SQL = """ CREATE SCHEMA IF NOT EXISTS sientia_data; CREATE TABLE IF NOT EXISTS sientia_data.e2e_predictions ( model_id text NOT NULL, prediction double precision, prediction_confidence double precision, timestamp text NOT NULL, write_status text, PRIMARY KEY (model_id, timestamp) ); INSERT INTO sientia_data.e2e_predictions (model_id, prediction, prediction_confidence, timestamp) VALUES ('9001', 42.5, 0.97, '2026-07-15T12:00:00Z') ON CONFLICT DO NOTHING; """ # The matching NATS write command for the row above (paste into `nats pub opc.write '...'`). NATS_CMD = { 'msg_id': 'e2e-0001', 'server_id': '9001', 'addr': TAGS[1]['address'], 'value': 42.5, 'data_type': 'float', 'tag_type': 'prediction', 'model_id': '9001', 'model_name': 'e2e-model', 'schema': 'sientia_data', 'table_name': 'e2e_predictions', 'timestamp': '2026-07-15T12:00:00Z', 'url': OPC_URL, 'use_security': False, 'server_uri': 'urn:e2e:gateway:test', } def seed_mongo(): from pymongo import MongoClient url = os.getenv('MONGODB_URL', 'localhost:27017') user, pwd = os.getenv('MONGODB_USERNAME', 'sientia'), os.getenv('MONGODB_PASSWORD', 'sientia') db = MongoClient(f'mongodb://{user}:{pwd}@{url}')[os.getenv('MONGODB_DATABASE', 'sientia')] db['connectors'].replace_one({'connector_id': CONNECTOR['connector_id']}, CONNECTOR, upsert=True) for tag in TAGS: db['connector_tags'].replace_one({'tag_id': tag['tag_id']}, tag, upsert=True) print(f'mongo: upserted 1 connector + {len(TAGS)} tags (metadata: {{}})') def seed_redis(): import redis r = redis.Redis(host=os.getenv('REDIS_HOST', 'localhost'), port=int(os.getenv('REDIS_PORT', '6379'))) r.set('slot:opc_tags:1', json.dumps(SLOT_V1)) print('redis: wrote slot:opc_tags:1 (v1)') def seed_postgres(): from sqlalchemy import create_engine, text host, port = os.getenv('POSTGRES_HOST', 'localhost'), os.getenv('POSTGRES_PORT', '5432') user, pwd = os.getenv('POSTGRES_USER', 'sientia'), os.getenv('POSTGRES_PASSWORD', 'sientia') dbname = os.getenv('POSTGRES_DBNAME', 'sientia') engine = create_engine(f'postgresql://{user}:{pwd}@{host}:{port}/{dbname}') with engine.begin() as conn: for stmt in filter(str.strip, PREDICTIONS_SQL.split(';')): conn.execute(text(stmt)) print('postgres: sientia_data.e2e_predictions ready (write_status column + 1 row)') def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--print-only', action='store_true', help='show docs/SQL, touch nothing') args = parser.parse_args() if args.print_only: print('connector:', json.dumps(CONNECTOR, indent=2)) print('tags:', json.dumps(TAGS, indent=2)) print('slot v1:', json.dumps(SLOT_V1, indent=2)) print('sql:', PREDICTIONS_SQL) print('nats write cmd:', json.dumps(NATS_CMD, indent=2)) return 0 seed_mongo() seed_redis() seed_postgres() print('\nNATS write command for the session:') print(f"nats pub opc.write '{json.dumps(NATS_CMD)}'") return 0 if __name__ == '__main__': sys.exit(main())