# ExperionCrawler — Agent Instructions ## Build / Run / Test | Action | Command | Working Dir | |--------|---------|-------------| | Build | `dotnet build src/Web/ExperionCrawler.csproj` | repo root | | Run (dev) | `dotnet run` | `src/Web/` | | Publish | `dotnet publish -c Release -o /opt/ExperionCrawler` | `src/Web/` | | Tests | `dotnet test` | repo root | Single project: `src/Web/ExperionCrawler.csproj`. Core and Infrastructure are included via `` globs — there are no separate projects to build. Runtime target is `linux-arm64`. ## Architecture ``` src/ ├── Core/ — Interfaces (IExperionServices.cs), Domain entities, DTOs, Application Services ├── Infrastructure/ — OpcUa/, Database/, Certificates/, Csv/, Mcp/ └── Web/ — Program.cs, Controllers/, wwwroot/ (SPA) ``` All controllers are in `src/Web/Controllers/ExperionControllers.cs` (single file). All interfaces are in `src/Core/Application/Interfaces/IExperionServices.cs` (single file). ## Database **PostgreSQL** (NOT SQLite — README is stale). Connection strings in `src/Web/appsettings.json`. TimescaleDB extension may be enabled on `history_table` via DDL only; no app code changes needed. ## Critical Convention — JSON camelCase `PropertyNamingPolicy = null` in Program.cs means C# PascalCase becomes JSON keys. **Frontend expects camelCase**. Every controller `Ok(...)` response MUST use explicit anonymous objects with camelCase keys: ```csharp // ✅ Correct return Ok(new { id = x.Id, tagName = x.TagName }); // ❌ Broken — JS gets undefined return Ok(new { x.Id, x.TagName }); return Ok(myDto); // typed object ``` See `CODING_CONVENTIONS.md` for full details and checklist. ## Background Services (HostedServices in Program.cs) - `ExperionRealtimeService` — OPC UA subscription, 500ms batch flush to DB - `ExperionHistoryService` — periodic snapshot (60s) from realtime_table → history_table - `ExperionOpcServerService` — exposes realtime data as OPC UA server (port 4841) - `McpServerHostedService` — Python MCP server bridge - `ExperionFastService` — high-frequency data capture sessions - `ExperionFastCleanupService` — expired session cleanup All registered as Singleton + HostedService. RealtimeService and OpcServerService share autostart flag files (`realtime_autostart.json`, `opcserver_autostart.json`) in the working directory. ## Frontend Vanilla JS SPA. `wwwroot/index.html` + `js/app.js` + `css/style.css`. No build step. Tab-based navigation; tabs do NOT auto-fire API calls on entry (performance fix). ## OPC UA SDK Gotchas - SDK v1.5.378.134 — `Session.Create()` is `[Obsolete]`; use `DefaultSessionFactory.CreateAsync()` - `Subscription.Create()` / `Delete()` / `ApplyChanges()` also deprecated → async variants preferred - Certificate validation must be attached AFTER `OpcUaConfigProvider.GetConfigAsync()` returns the config - TCP connect timeout: wrap `SelectEndpointAsync` with a 10s `CancellationTokenSource` (OS default is 127s) ## Deploy `sudo bash deploy.sh` — publishes to `/opt/ExperionCrawler`, creates systemd service `experioncrawler`, sets up PKI dirs. Service runs as `www-data`.