# 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 `<Compile Include>` 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`.
