# Zero PII Customer Intelligence - Deel 1.1: Sample Data (en afbeeldingen) Lokaal genereren

<!--category-- Product, Privacy, LLM, ComfyUI, C# -->
<datetime class="hidden">2025-12-24T20:00</datetime>

In [Deel 1](/blog/zero-pii-customer-intelligence-part1) We bespraken de filosofie: transparante segmentatie zonder PII. [Deel 2](/blog/zero-pii-customer-intelligence-part2) omvat sessieprofielen, signalen en segmentdefinities.

Maar eerst: **hoe valideer je dit zonder ooit echte klantgegevens aan te raken?**

Voor deze serie maak ik lokaal een complete synthetische ecommerce dataset:

- Productcatalogus (namen, beschrijvingen, tags, prijzen)
- Anonieme uitstrijkjes / persona's (interesses + signalen)
- Productafbeeldingen (en profielportretten) via ComfyUI
- Optionele DB-import zodat de echte app er tegenaan kan draaien

Dit is een van die ..het voelt als vals spelen workflows: je krijgt realistische input, je kunt ze regenereren op elk moment, en je nooit PII in te voeren in uw dev-omgeving.

## Waarom lokale synthetische gegevens zo krachtig zijn

Als je segmentatie / personalisatie bouwt, heb je datasets nodig die:

- Heb genoeg variatie om naïeve heuristiek te breken
- Zijn veilig om te delen (in code, testen, demo's)
- Zijn reproduceerbaar (zodat je modelwijzigingen kunt vergelijken)

Echte gegevens zijn het tegenovergestelde: gevoelig, rommelig, moeilijk te verplaatsen, en vol historische vooroordelen.

Synthetische data geeft je drie superkrachten:

1. **Snelle iteratie**: de segmentatielogica veranderen, regenereren, opnieuw uitvoeren.
2. **Verharding**: simuleer vreemde lange-staart gedrag dat je niet zult zien in een kleine dev dataset.
3. **Uitlegbaarheidstests**: you can *inspecteer elk gegenereerd veld* en bevestig dat je me laat zien waarom de UI waarheidsgetrouw is.

```mermaid
flowchart LR
    Taxonomy[gadget-taxonomy.json] --> Gen[SampleData generator]
    Gen --> Products[products.json]
    Gen --> Profiles[profiles.json]
    Gen --> Images[images/*]
    Products --> Import[Import into Postgres]
    Profiles --> Import

    style Taxonomy stroke:#1971c2,stroke-width:3px
    style Gen stroke:#1971c2,stroke-width:3px
    style Import stroke:#2f9e44,stroke-width:3px
```

## The Tooling (What Actually Runs)

De generator leeft in `Mostlylucid.SegmentCommerce.SampleData`.

Het is opzettelijk geen kader; het is een pragmatische CLI die praat met:

- **Ollama** (lokale LLM) voor gestructureerde JSON-generatie.
- **ComfyUI** (lokale Stable Diffusion) voor afbeeldingen in productfotografiestijl.
- A **taxonomie JSON** dat de outputs coherent houdt (categorieën, types, varianten, prijsklassen).

Configuratie is bekabeld zodat je het kunt rijden via omgevingsvariabelen:

```csharp
// Mostlylucid.SegmentCommerce.SampleData/Program.cs
var configuration = new ConfigurationBuilder()
    .SetBasePath(AppContext.BaseDirectory)
    .AddJsonFile("appsettings.json", optional: true)
    .AddEnvironmentVariables("SAMPLEDATA_")
    .Build();
```

## Snelstart

Je zult meestal draaien drie lokale diensten naast de generator:

```mermaid
flowchart LR
    CLI[SampleData CLI] --> Ollama["Ollama
    http://localhost:11434"]
    CLI --> Comfy["ComfyUI
    http://localhost:8188"]
    CLI -. optional .-> DB[(Postgres)]

    style CLI stroke:#1971c2,stroke-width:3px
    style Ollama stroke:#1971c2,stroke-width:3px
    style Comfy stroke:#2f9e44,stroke-width:3px
    style DB stroke:#fab005,stroke-width:3px
```

Start de generator van de repo-root:

```bash
dotnet run --project Mostlylucid.SegmentCommerce.SampleData -- status
```

Genereer dan een dataset:

```bash
# v1 generator: taxonomy + optional Ollama + optional ComfyUI
# Writes ./Output/products.json, ./Output/profiles.json, ./Output/images/...
dotnet run --project Mostlylucid.SegmentCommerce.SampleData -- generate --count 20
```

Nuttige schakelaars:

```bash
# No LLM calls, taxonomy only
dotnet run --project Mostlylucid.SegmentCommerce.SampleData -- generate --no-ollama

# No ComfyUI images
dotnet run --project Mostlylucid.SegmentCommerce.SampleData -- generate --no-images

# Write into Postgres (uses configured connection string)
dotnet run --project Mostlylucid.SegmentCommerce.SampleData -- generate --db
```

Opmerking: als ComfyUI niet beschikbaar is, valt de v1 generator terug naar plaatshouder afbeeldingen (het maakt gebruik van `picsum.photos`, zodat dat pad is niet volledig offline. Als u wilt strikt-lokaal, lopen met `--no-images`.

## v2 Generator: Verkopers → Producten → Klanten → Bestellingen → Inbeddingen

Er is ook een nieuwer generator commando (`gen`) dat bouwt een meer complete marktplaats-geribbelde settle:

```bash
# v2 generator
# Writes dataset.json plus sellers/products/customers/orders split files
dotnet run --project Mostlylucid.SegmentCommerce.SampleData -- gen --sellers 50 --products 20 --customers 1000
```

Het wordt georkestreerd als een multi-fase pijpleiding:

```mermaid
flowchart LR
    A[Sellers] --> B[Products]
    B --> C[Customers]
    C --> D[Orders]
    D --> E[Embeddings]

    B -. optional .-> I[ComfyUI images]

    style A stroke:#1971c2,stroke-width:3px
    style B stroke:#1971c2,stroke-width:3px
    style C stroke:#1971c2,stroke-width:3px
    style D stroke:#1971c2,stroke-width:3px
    style E stroke:#2f9e44,stroke-width:3px
    style I stroke:#2f9e44,stroke-width:3px
```

En je kunt die fasen direct in code zien:

```csharp
// Mostlylucid.SegmentCommerce.SampleData/Services/DataGenerator.cs
// 1. Generate Sellers
// 2. Generate Products for each seller
// 3. Generate Customers
// 4. Generate Orders (with fake checkout data via Bogus)
// 5. Generate embeddings for all entities
```

### Inbeddingen (Lokale ONNX, gekacheld na de eerste run)

De v2 generator kan inbeddingen berekenen met behulp van een ONNX model (standaard: `all-MiniLM-L6-v2`De eerste keer dat je het uitvoert, downloadt het model en vocab in je uitvoermap.

Dat betekent:

- Eerste run heeft netwerktoegang nodig (modeldownload)
- Volgende runs zijn lokaal en snel
- Als u wilt dat er geen netwerk ooit `--no-embeddings`

```csharp
// Mostlylucid.SegmentCommerce.SampleData/Services/EmbeddingService.cs
private const string ModelUrl = "https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/onnx/model.onnx";
private const string VocabUrl = "https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/vocab.txt";

// Download model if not exists
if (!File.Exists(_config.ModelPath))
{
    await DownloadFileAsync(ModelUrl, _config.ModelPath, ct);
}
```

## Hoe genereren we producten (alleen JSON)

De prompt voor het genereren van producten is bewust streng: de LLM moet alleen JSON retourneren, met een ingebed JSON schema voorbeeld.

```csharp
// Mostlylucid.SegmentCommerce.SampleData/Services/OllamaProductGenerator.cs
return $"""
    You are a product catalog generator for an e-commerce store. Generate {count} unique, realistic product listings for the \"{category.DisplayName}\" category.

    Category description: {category.Description}
    Example products in this category: {category.ExampleProducts}
    Price range: £{category.PriceRange.Min:F2} - £{category.PriceRange.Max:F2}

    For each product, provide:
    1. A compelling product name (realistic brand-style naming)
    2. A detailed description (2-3 sentences, highlighting key features and benefits)
    3. A realistic price within the range
    4. Optional original price if on sale (20-40% higher than current price)
    5. 3-5 relevant tags
    6. Whether it's trending (about 20% should be trending)
    7. Whether it's featured (about 15% should be featured)
    8. An image prompt for AI image generation (detailed, product photography style)
    9. 2-3 colour variants for the product

    IMPORTANT: Respond with ONLY valid JSON, no markdown formatting, no code blocks, no explanations.

    Generate {count} diverse products now:
    """;
```

Dit is belangrijk omdat downstream systemen (image generation + import + inbeddingen) gestructureerde gegevens willen. *ingangen van een pijpleiding*, niet het schrijven van proza.

### LLM-responsen: Parse JSON, of Terugvallen

LLM's zijn geen compilers. Zelfs met alleen JSON heb je nog steeds defensieve ontleden en sierlijke terugval nodig.

De v2 generator doet dat via een kleine helper (`LlmService`) dat de eerste `{...}` blok en deserialiseert het:

```csharp
// Mostlylucid.SegmentCommerce.SampleData/Services/LlmService.cs
var jsonStart = response.IndexOf('{');
var jsonEnd = response.LastIndexOf('}');

if (jsonStart >= 0 && jsonEnd > jsonStart)
{
    var jsonStr = response.Substring(jsonStart, jsonEnd - jsonStart + 1);
    return JsonSerializer.Deserialize<T>(jsonStr, new JsonSerializerOptions
    {
        PropertyNameCaseInsensitive = true
    });
}
```

En de algemene generatie pijplijn controleert expliciet beschikbaarheid en downgrades naar deterministische templates wanneer dat nodig is:

```mermaid
flowchart LR
    A[EnableLlm=true?] -->|no| F[Fallback templates]
    A -->|yes| B[GET /api/tags]
    B -->|model present| C[LLM JSON prompts]
    B -->|not available| F

    style A stroke:#1971c2,stroke-width:3px
    style C stroke:#2f9e44,stroke-width:3px
    style F stroke:#fab005,stroke-width:3px
```

### Voorbeeld: Customer Personas (v2)

De persona prompt voor klanten is kort en gestructureerd zodat het snel kan worden gegenereerd met een klein lokaal model:

```csharp
// Mostlylucid.SegmentCommerce.SampleData/Services/DataGenerator.cs
var prompt = $$"""
    Generate a shopper persona interested in: {{string.Join(", ", categoryNames)}}.

    Return JSON only:
    {
      "persona": "Brief persona description (e.g. 'Tech enthusiast who values quality')",
      "name": "Realistic first name",
      "bio": "One sentence about their shopping habits",
      "age": 25,
      "shopping_style": "budget|value|premium|luxury",
      "preferred_categories": ["category1", "category2"]
    }
    """;
```

## ComfyUI: Product Fotografie via API

ComfyUI is geweldig omdat het geeft u een controleerbare pijplijn (workflows) in plaats van een enkele zwarte doos afbeelding eindpunt.

De generator:

1. Laadt een workflow-sjabloon (`ComfyUI/workflows/product_image.json`)
2. De prompt invoegen `CLIPTextEncode`
3. Wachtrijen `/prompt`
4. Enquêtes `/history/{promptId}`
5. Downloadt de afbeelding via `/view?...`

```mermaid
sequenceDiagram
    participant Gen as SampleData
    participant Comfy as ComfyUI

    Gen->>Comfy: POST /prompt (workflow + prompt)
    Comfy-->>Gen: prompt_id
    loop poll
        Gen->>Comfy: GET /history/{prompt_id}
        Comfy-->>Gen: outputs / images
    end
    Gen->>Comfy: GET /view?filename=...&type=output
    Comfy-->>Gen: PNG bytes
```

ComfyUI modelselectie wordt ook gepatcht in de workflow op runtime (dus je kunt checkpoints ruilen zonder de JSON te bewerken):

```csharp
// Mostlylucid.SegmentCommerce.SampleData/Services/ComfyUIImageGenerator.cs
TryPatchCheckpoint(workflow, _config.ComfyUICheckpointName ?? "sd_xl_base_1.0.safetensors");
TryPatchRefiner(workflow, _config.ComfyUIRefinerName ?? "sd_xl_refiner_1.0.safetensors");
```

En de workflow patching is opzettelijk eenvoudig en robuust:

```csharp
// Mostlylucid.SegmentCommerce.SampleData/Services/ComfyUIImageGenerator.cs
// Update CLIPTextEncode nodes with our prompt
if (classType == "CLIPTextEncode")
{
    var inputs = nodeObj["inputs"]?.AsObject();
    if (inputs != null && inputs.ContainsKey("text"))
    {
        var currentText = inputs["text"]?.GetValue<string>() ?? "";
        if (!currentText.Contains("bad") && !currentText.Contains("ugly") && !currentText.Contains("deformed"))
        {
            inputs["text"] = prompt;
        }
    }
}

// Update image dimensions
if (classType == "EmptyLatentImage")
{
    var inputs = nodeObj["inputs"]?.AsObject();
    if (inputs != null)
    {
        inputs["width"] = _config.ImageWidth;
        inputs["height"] = _config.ImageHeight;
    }
}
```

## Profielen en personen (Nuttig, niet griezelig)

De v1 generator maakt anonieme profielen en verrijkt ze vervolgens met een persona (nog steeds geen PII). U eindigt met realistische mensen-geribbelde testgegevens zonder e-mails, adressen, of iets dat je ooit per ongeluk zou kunnen verzenden.

In v1 worden profielen getoetst met behulp van een one-way hash, dus er is niets om te herstellen:

```csharp
// Mostlylucid.SegmentCommerce.SampleData/Services/ProfileGenerator.cs
var profileKey = Hash($"fp-{Guid.NewGuid():N}");

private static string Hash(string input)
{
    using var sha = SHA256.Create();
    var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(input));
    return Convert.ToHexString(bytes).ToLowerInvariant();
}
```

Dat is precies de mindset van de hele serie: je kunt niet lekken wat je nooit opgeslagen.

```mermaid
flowchart TB
    Signals["Generated signals
    views/cart/purchase weights"] --> Persona["Persona enrichment
    Ollama JSON-only"]
    Persona --> Portrait[Portrait prompt]
    Portrait --> Comfy[ComfyUI]

    style Signals stroke:#1971c2,stroke-width:3px
    style Persona stroke:#1971c2,stroke-width:3px
    style Comfy stroke:#2f9e44,stroke-width:3px
```

## Validatie: Wat dit laat je bewijzen

Deze lokale pijpleiding is krachtig omdat het ondersteunt *modelvalidatie*Niet alleen demo's.

- **Segmentatie gezond verstand**: zijn de segmenten die je berekent coherent wanneer je productnamen/tags en gegenereerde persona's inspecteert?
- **Uitlegbaarheid van de aanbeveling**: Laat me zien waarom je een echt signaal aanwijst dat je kunt verifiëren?
- **Koude start**: Als je de dataset veegt en regenereert, gedraagt het systeem zich dan voorspelbaar?
- **Regressietests**: regenereren van dezelfde vorm van gegevens en ervoor zorgen dat uw veranderingen niet breken rangschikking, scoren, of UI.

De belangrijke subtiliteit: door het genereren van zowel de *tekst* en de *afbeeldingen*, kunt u de gehele productervaring te valideren, niet alleen back-end wiskunde.

## Wat is het volgende?

**[Deel 2](/blog/zero-pii-customer-intelligence-part2)**: Sessieprofielen, signalen en segmentdefinities.

**Deel 3** (coming): Outbox patroon, taak wachtrij, en de transparantie UI.

---


*Generatorcode: `Mostlylucid.SegmentCommerce.SampleData/`*