# TASK 3.3: Create MonitorSpec Seed Data Examples

## Summary

Seed example `MonitorSpec` records using EF Core 9's `UseSeeding` / `UseAsyncSeeding` API. All seeding code lives in the `Kds.Ai.Data` project alongside the DbContext - no separate seeder service or repository is needed. The `ExamplePrompts` class provides example system prompts as constants for development and testing.

## Dependencies

- TASK 1.1 (MonitorSpec entity)

## Design Principles

- **EF9 `UseSeeding` / `UseAsyncSeeding`**: The recommended Microsoft approach (introduced in EF Core 9). Seeding callbacks are registered on `DbContextOptionsBuilder` and run automatically after `EnsureCreated`, `Migrate`, or `dotnet ef database update` - even if no migrations were applied. The seeding code is protected by the migration locking mechanism to prevent concurrency issues.
- **Both sync and async**: Microsoft requires implementing both `UseSeeding` (sync) and `UseAsyncSeeding` (async). EF Core tooling relies on the synchronous version; runtime code uses the async version. To avoid duplicating logic, the sync `Seed` method delegates to `SeedAsync` via `.GetAwaiter().GetResult()`.
- **Idempotent**: The callbacks check whether a monitor with the given name already exists before inserting.
- **All in `Kds.Ai.Data`**: Since seeds are for initial testing/development, they live in the Data project next to the DbContext. No cross-project wiring needed.
- **Example prompts in code**: We include a few example system prompts as constants for development and testing. In production, prompts are authored by humans and stored in the database.

## Reference: EF9 UseSeeding pattern

From the [official Microsoft documentation](https://learn.microsoft.com/en-us/ef/core/modeling/data-seeding):

```csharp
// UseSeeding and UseAsyncSeeding are set up in the options configuration step:
options
    .UseSqlServer(connectionString)
    .UseSeeding((context, _) =>
    {
        var testBlog = context.Set<Blog>().FirstOrDefault(b => b.Url == "http://test.com");
        if (testBlog == null)
        {
            context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
            context.SaveChanges();
        }
    })
    .UseAsyncSeeding(async (context, _, cancellationToken) =>
    {
        var testBlog = await context.Set<Blog>().FirstOrDefaultAsync(
            b => b.Url == "http://test.com", cancellationToken);
        if (testBlog == null)
        {
            context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
            await context.SaveChangesAsync(cancellationToken);
        }
    });
```

Key points from the docs:
- The `bool` argument of the seed delegate indicates whether any store management operation was performed.
- The methods are called as part of `EnsureCreated`, `Migrate`, and `dotnet ef database update`, even if there are no model changes.
- The code is protected by the migration locking mechanism to prevent concurrency issues.

## Files to Create

### 1. `backend/Kds.Ai.Data/Seeding/ExamplePrompts.cs`

```csharp
namespace Kds.Ai.Data.Seeding;

/// <summary>
/// Example system prompts for development and testing.
/// In production, prompts are authored by humans via the UI/CLI.
/// </summary>
public static class ExamplePrompts
{
    /// <summary>
    /// Example prompt for prep-time anomaly detection on KDSDev orders data.
    /// This is NOT compiled into the agent - it is stored as plain text in MonitorSpec.SystemPrompt.
    /// </summary>
    public const string PrepTimeAnomaly = """
        You are a restaurant operations anomaly detector. Your job is to analyze order preparation times and detect anomalies.

        ## Database
        You have access to a MySQL database via the `mysql_query` tool. The key table is `orders` in the KDSDev database:
        - `preparation_time` (int, seconds, nullable) - how long the order took to prepare
        - `create_time` (int, unix timestamp) - when the order was created
        - `store_guid` (varchar) - which store/restaurant
        - `items_count` (int) - number of items in the order
        - `order_type` (varchar) - type of order (dine-in, takeout, delivery)
        - `destination` (varchar) - kitchen station
        - `done` (tinyint) - whether the order is complete
        - `is_deleted` (tinyint) - soft delete flag

        ## Analysis Approach
        1. Query the last 24 hours of completed orders (done=1, is_deleted=0, preparation_time IS NOT NULL)
        2. Aggregate by hour to get hourly average prep times
        3. Compare against the historical baseline (same hour of day over the past 28 days)
        4. Flag hours where the average prep time deviates significantly (>2 standard deviations)
        5. Optionally break down by order_type or destination to find more specific patterns

        ## Output
        When done analyzing, call the `emit_conclusion` tool with your findings.
        - Set `anomalyDetected` to true if you found significant deviations
        - Set severity: "Low" for minor (2-3σ), "Medium" for moderate (3-4σ), "High" for severe (>4σ)
        - Include a clear summary and detailed markdown analysis
        - List each anomalous finding with the affected time period and evidence
        """;
}
```

### 2. `backend/Kds.Ai.Data/Seeding/MonitorSpecSeeding.cs`

Static helper class with the seeding logic. The async method contains the real implementation; the sync version (required by EF Core tooling) delegates to it via `GetAwaiter().GetResult()`:

```csharp
using Kds.Ai.Data.Entities;
using Kds.Ai.Data.Enums;
using Microsoft.EntityFrameworkCore;

namespace Kds.Ai.Data.Seeding;

/// <summary>
/// Seeding logic for MonitorSpec records.
/// Called from UseSeeding/UseAsyncSeeding callbacks on DbContextOptionsBuilder.
/// </summary>
public static class MonitorSpecSeeding
{
    private static readonly (string Name, string SystemPrompt)[] SeedMonitors =
    [
        ("Prep Time Monitor - Downtown", ExamplePrompts.PrepTimeAnomaly)
    ];

    /// <summary>
    /// Synchronous seeding callback for UseSeeding.
    /// Required by EF Core tooling (dotnet ef database update).
    /// Delegates to the async implementation.
    /// </summary>
    public static void Seed(KdsAiDbContext context)
    {
        SeedAsync(context, CancellationToken.None).GetAwaiter().GetResult();
    }

    /// <summary>
    /// Asynchronous seeding callback for UseAsyncSeeding.
    /// Contains the actual seeding logic.
    /// </summary>
    public static async Task SeedAsync(KdsAiDbContext context, CancellationToken cancellationToken)
    {
        foreach (var (name, systemPrompt) in SeedMonitors)
        {
            var exists = await context.MonitorSpecs
                .AnyAsync(m => m.Name == name, cancellationToken);
            if (!exists)
            {
                context.MonitorSpecs.Add(new MonitorSpec
                {
                    Name = name,
                    SystemPrompt = systemPrompt,
                    State = MonitorState.Draft
                });
            }
        }

        await context.SaveChangesAsync(cancellationToken);
    }
}
```

## Files to Modify

### 3. `backend/Kds.Ai.Data/DependencyInjectionExtensions.cs`

Wire up `UseSeeding` / `UseAsyncSeeding` directly in the `AddDataModule` method:

```csharp
using Kds.Ai.Data.Seeding;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Kds.Ai.Data;

public static class DependencyInjectionExtensions
{
    public static IHostApplicationBuilder AddDataModule(this IHostApplicationBuilder builder, string connectionName = "kdsaidb")
    {
        builder.Services.AddDbContext<KdsAiDbContext>(options =>
        {
            // we are using default connection string for schema migrations only
            var connectionString = builder.Configuration.GetConnectionString(connectionName) ?? "Server=localhost;Database=dummy;";
            options.UseMySQL(connectionString);

            options
                .UseSeeding((context, _) =>
                {
                    if (context is KdsAiDbContext db)
                    {
                        MonitorSpecSeeding.Seed(db);
                    }
                })
                .UseAsyncSeeding(async (context, _, cancellationToken) =>
                {
                    if (context is KdsAiDbContext db)
                    {
                        await MonitorSpecSeeding.SeedAsync(db, cancellationToken);
                    }
                });
        });

        return builder;
    }
}
```

## How It Works

1. When the application starts, `Migrate()` / `MigrateAsync()` is called (or `EnsureCreated`).
2. EF Core runs the `UseSeeding` (sync) or `UseAsyncSeeding` (async) callback automatically.
3. The callback checks if each named monitor already exists via `Any()` / `AnyAsync()`.
4. Missing monitors are added with `Draft` state and saved.
5. The migration locking mechanism prevents concurrent seeding across multiple instances.

## Acceptance Criteria

- [ ] `ExamplePrompts` class created in `Kds.Ai.Data/Seeding/`
- [ ] At least one example prompt constant
- [ ] `MonitorSpecSeeding` static class with `SeedAsync` (real logic) and `Seed` (delegates via `.GetAwaiter().GetResult()`)
- [ ] Duplicates prevented by checking name existence before inserting
- [ ] `UseSeeding` / `UseAsyncSeeding` wired in `AddDataModule`
- [ ] No separate seeder service class, no repository dependency for seeding
- [ ] No domain-specific code (no `PrepTimeMonitorSpecFactory`, no `PrepTimeMonitorConfig`)
- [ ] No references to `StoreGuid` or `MonitorType`
- [ ] Monitors created in `Draft` state by default
- [ ] Solution builds without errors

## Notes

- The old `PrepTimeMonitorSpecFactory` is completely replaced by this generic approach
- `ExamplePrompts` is for dev convenience only - prompts are plain text, not typed models
- No `PrepTimeMonitorConfig` - configuration is optional free-form JSON, not a typed class
- The seeder uses name-based deduplication since MonitorSpec no longer has StoreGuid+MonitorType as a composite key
- EF Core tooling (`dotnet ef`) relies on the synchronous `UseSeeding` - the sync `Seed` delegates to `SeedAsync` via `.GetAwaiter().GetResult()` to avoid duplicating logic
- The seeding code is concurrency-safe via EF Core's migration locking mechanism
