# Null PII Kunden Intelligence - Del | 2: | Profiler |, | Signaler | МSK3 | Segment

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

I [Del 1](/blog/zero-pii-customer-intelligence-part1) vi bevakade filosofin och serien överblicken. I [Del 1.1](/blog/zero-pii-customer-intelligence-part1-1) vi byggde en modelldatagenerator

Låt oss nu bygga ett kärnsystem

1. **Profilstrukturen Zero-PII** - Otydliga inspelningar mot persisterande profil
2. **Signaler och vikter** - Vad vi följer och hur det samlas in
3. **Segmentdefinitioner** - Fuzzy membership with weighted rules the main eventM SK2
4. **Ett exempel på utbox-mönster** - Tillförlitlig händelsepublicering | ( | detaljerad i del |3)

Det här är **exempelprojekt** (`Mostlylucid.SegmentCommerce`), -, en liten e-commerce-demon som visar kärnans mönster, ., det är medvetet förenklad för att visa koncept tydligt, ,, men det inkluderar också verkliga infrastruktursmönster.

Tänk på detta som """, "" produktionsmönster i en enda "" -, "" app-formfaktorn "", ." "". Samma kodstruktur fungerar oavsett om du "'" kör en process eller distribuerar mellan tjänster.

[TOC]

## Technologiska val

Vi valde **enkel, kohäsiv stapel** för att hålla proveren tydlig när man visar produktionsmönster

### PostgreSQL + pgvector : En databas

Istället för separata vektordatabaser **PostgreSQL**:

```mermaid
flowchart LR
    App[ASP.NET Core] --> PG[(PostgreSQL)]
    
    PG --> JSONB[JSONB<br/>interests]
    PG --> Vector[pgvector<br/>embeddings]
    PG --> Queue[Queue tables<br/>jobs, outbox]
    PG --> FTS[Full-text<br/>tsvector]
    
    style PG stroke:#2f9e44,stroke-width:3px
    style JSONB stroke:#1971c2,stroke-width:2px
    style Vector stroke:#1971c2,stroke-width:2px
    style Queue stroke:#1971c2,stroke-width:2px
    style FTS stroke:#1971c2,stroke-width:2px
```

- **JSONB** för flexibela schema ( inga separata NoSQLs
- **pgvector** för inbäddar (o Qdrant/PineconeM SK2
- **LISTEN/NOTIFY** för jobb (no RedisM SK1RabbitMQ)
- **Tsvector** för fulltext-text M SK1 inga elastiska sökningar )

Ett anslutningspool. Ett reservsystem . Ett utplacering

### HTMX + Alpine

```html
<!-- Instant search (no page reload) -->
<input 
    hx-get="/api/search" 
    hx-trigger="keyup changed delay:300ms" 
    hx-target="#results" />
```

- **HTMX**: Server renderar delar ( inga JSONs ♫ ♫ → ♫ templering ♫
- **Alpine.js**: Reaktivitet utan byggande steg
- **Fortskridande förbättring**: Fungerar bättre utan JS

SPA-lik UX med server

### ASP.NET Core: Distributable Patterns

Enkel app , men mönster skala:

- **Utbox** (DB händelser) → utbyter med meddelandebussen
- **Jobbörns kö** (LISTEN/NOTIFY
- **Sammlararen** → utbyte för separat API

Starta enkelt. Distribuera när det behövs.

### Vad vi undvek

- Inget separat vektordatabas (pgvector är lagradM SK1
- Inget JS ramverk (HTMX + Alpine är enklare
- Det finns inga mikrotjänster än (mönster fungerar i monolith eller fördelade )
- Ingen Docker Komponera utbredning ( en DB , en app)

## Den Zero- -MSK- -PII-utmaningen

Traditionella användarspårning förvarar identifierande information.

**Vi lagrar beteendemönster**

```mermaid
flowchart LR
    subgraph "Traditional Approach"
        User1[User: john@email.com] --> Behavior1[Bought headphones]
        Behavior1 --> PII1[(PII Database)]
    end
    
    subgraph "Zero-PII Approach"
        User2[Anonymous Session] --> Behavior2[Bought headphones]
        Behavior2 --> Pattern[(Category: tech<br/>Signal weight: 1.0)]
    end
    
    style PII1 stroke:#c92a2a,stroke-width:3px
    style Pattern stroke:#2f9e44,stroke-width:3px
```

Den viktigaste insikten **du behöver inte veta vem någon är för att veta vad de är intresserade av**.

## Arkitektur överblick

Systemet har tre kärnlaggar:

```mermaid
flowchart TB
    Browser[Browser] --> Session[Session Collector]
    Session --> Profile[Persistent Profile]
    Profile --> Segments[Segment Service]
    
    Session -->|in-memory only| Cache[(IMemoryCache)]
    Profile -->|elevated signals| DB[(PostgreSQL + JSONB)]
    Segments -->|memberships| UI[Segment Explorer UI]
    
    style Session stroke:#1971c2,stroke-width:3px
    style Profile stroke:#2f9e44,stroke-width:3px
    style Segments stroke:#fab005,stroke-width:3px
    style Cache stroke:#e64980,stroke-width:2px
```

1. **Sammlararen** - Captures behavioral signals **i- endast minne**
2. **Persistent Profil** - Förhöjer höga -värdesignaler | , | beräkningsintressen | **databank-beskyddat**
3. **Segmenttjänster** - Bedömer profiler mot reglerna , utser medlemmar

**Kritisk skillnad**: Inspelningar är efemeriska . Profiler är persisterande . Det här är inte en implementerings-detail

## Sektionsprofiler: Strict In-Memory M SK2LFU Cache )

Börjarna är **strikt i minnet**. De bor i `IMemoryCache` med skiftande utgång och **aldrig röra vid databasen**.

Det här är en svår arkitektonisk begränsning.

### Sessionprofil: In- -Memory-modellen

```csharp
// Mostlylucid.SegmentCommerce/Models/SessionProfile.cs
public class SessionProfile
{
    public string SessionKey { get; set; } = string.Empty;

    // Category interest scores: { "tech": 0.75, "fashion": 0.25 }
    public Dictionary<string, double> Interests { get; set; } = new();

    // Detailed signal counts: { "tech": { "product_view": 5, "add_to_cart": 1 } }
    public Dictionary<string, Dictionary<string, int>> Signals { get; set; } = new();

    // Products viewed this session
    public List<int> ViewedProducts { get; set; } = new();

    // Session context (device, referrer domain, time-of-day)
    public SessionContext? Context { get; set; }

    // Aggregates
    public double TotalWeight { get; set; }
    public int SignalCount { get; set; }
    public int PageViews { get; set; }
    public int ProductViews { get; set; }
    public int CartAdds { get; set; }

    // Timestamps
    public DateTime StartedAt { get; set; } = DateTime.UtcNow;
    public DateTime LastActivityAt { get; set; } = DateTime.UtcNow;

    // Link to persistent profile (if fingerprint resolved)
    public Guid? PersistentProfileId { get; set; }
}
```

### Bevarad i IMemoryCache ( eller IDistributedCache) med slitningsperiod

```csharp
// Mostlylucid.SegmentCommerce/Services/Profiles/SessionCollector.cs
_cache.Set(sessionKey, sessionProfile, new MemoryCacheEntryOptions
{
    SlidingExpiration = TimeSpan.FromMinutes(30),
    Priority = CacheItemPriority.Normal, // LFU eviction under memory pressure
    
    // CRITICAL: Eviction callback decides whether to elevate to persistent profile
    PostEvictionCallbacks =
    {
        new PostEvictionCallbackRegistration
        {
            EvictionCallback = async (key, value, reason, state) =>
            {
                if (value is SessionProfile session && ShouldElevate(session))
                {
                    // Only NOW do we write to database (persistent profile)
                    await ElevateToProfileAsync(session);
                }
                // Otherwise: session is gone forever
            }
        }
    }
});
```

**hårda begränsningar:**

- **Noll oavsiktlig uthållighet**: Live-sessioner **bara** i kass (IMemoryCache eller IDistributedCache som Redis
- **LFU: s utflåning**: lågt - användarsessioner löser först under minnestryck
- **Sliding utgång**: 30 minuter från sista aktiviteten
- **Eviction callback**: Den sista möjligheten att höja höga signaler innan de
- **Ingen återhämtning**: App återstartar = alla inspelningar förloras ♫ ( ♫ om inte använda IDistributedCache ♫

### Förhöjelsestillstånd (Vom utfånganing)

```csharp
private bool ShouldElevate(SessionProfile session)
{
    // Elevate if:
    // - User added to cart (high intent)
    // - User completed purchase (conversion)
    // - Fingerprint was resolved (identity established)
    // - Session weight exceeds threshold (engaged visitor)
    
    return session.CartAdds > 0 
        || session.TotalWeight > 5.0 
        || session.PersistentProfileId.HasValue;
}
```

**Varför ska utfångalingen tillbaka??**

Det här är **bara** säkerhetspunkt för att bestämma överlägsenhet. När kasken avlägsnar rummet

1. Vi känner till den fulla sammansättningshistorien
2. Vi kan bedöma
3. Vi undviker att lagra låga valuessessioner.
4. Vi garanterar att inspelningar inte fortsätts oavsiktligt

Om vi inte höjer upp ' during eviction, ,, så är sändningen **för alltid**. Det här är designen

### Sessionkontext: Vad vi följer (SafelyM SK2

```csharp
public class SessionContext
{
    public string? DeviceType { get; set; }        // "mobile", "desktop"
    public string? EntryPath { get; set; }         // "/products/tech" (no query params)
    public string? ReferrerDomain { get; set; }    // "google.com" (domain only, not full URL)
    public string? TimeOfDay { get; set; }         // "morning", "afternoon"
    public string? DayType { get; set; }           // "weekday", "weekend"
}
```

Titta på vad **inte** här: IP-Adressen , användaragenter **kontextmuster**, inte identifierad information

## Vad är signaler?

A **signal** är ett beteendemässigt faktum som fångas under en användaraktion.

Det här konceptet kommer från [efemerala signaler](/blog/ephemeral-signals)-, kort, -, levande fakta som existerar i ett begränsat fönster och åldras naturligt. `"api.rate_limited"` eller `"gateway.slow"` att koordinera beteendet utan hårt sammankoppling.

Här applicerar vi samma mönster till användarbeteenden:

- **Produktuppfattning** → `product_view` signal (vikt
- **Lägg till cart** → `add_to_cart` signal (vikt
- **Förköp** → `purchase` signal (vikt

Signaler är **efemeral** (dröjer med talet **0-PII** (, ingen identitet **i vikt** (

### Klientens fingeravtryck (ZeroM SK1Cookie-identifisering)

För att länka samman händelser utan att spåra cookies, använder vi **klient-sidan av fingeravtryck**. Browsern beräknar en hash från signalerna | | ( | tidzonen | , | skärmenupplösning | МSK3 | WebGL-referrer |, | canvas fingerprint | **bara haschen** till `/api/fingerprint`.

Servern **HMACs som har** med en hemlig nyckel

Den här koden är adapterad från min [bot-detektorprojekt](/blog/botdetection-introduction), där det

```javascript
// Mostlylucid.SegmentCommerce/ClientFingerprint/fingerprint.js
// Collect signals (browser capabilities, not PII)
var signals = [
    Intl.DateTimeFormat().resolvedOptions().timeZone,
    navigator.language,
    screen.width + 'x' + screen.height,
    // ... (see full code)
];

// Hash locally
var hash = hash(signals.join('|'));

// Send only the hash via sendBeacon
navigator.sendBeacon('/api/fingerprint', JSON.stringify({ h: hash }));
```

**Server-sida:**

```csharp
// Server HMACs the client hash with a secret key
var profileKey = HMACSHA256(clientHash + secretKey);
```

Nu har vi en stabil identifisator utan cookies eller localStorage [full fingerprint.js källa](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid.SegmentCommerce/ClientFingerprint/fingerprint.js) (adapterad från mer eller mindre självklara

## Signaltyper och vikter

Skillnade handlingar har olika syftesnivåer. Vi modellerar detta med **grundvikt**.

### Signaltyper : Weighthierarkin

```csharp
// Mostlylucid.SegmentCommerce/Data/Entities/Profiles/SignalEntity.cs
public static class SignalTypes
{
    // Passive signals (low intent)
    public const string PageView = "page_view";              // 0.01
    public const string CategoryBrowse = "category_browse";  // 0.03
    public const string ProductImpression = "product_impression"; // 0.02

    // Active signals (medium intent)
    public const string ProductView = "product_view";        // 0.10
    public const string ProductClick = "product_click";      // 0.08
    public const string Search = "search";                   // 0.05

    // High-intent signals
    public const string AddToCart = "add_to_cart";           // 0.35
    public const string AddToWishlist = "add_to_wishlist";   // 0.25
    public const string ViewCart = "view_cart";              // 0.15
    public const string BeginCheckout = "begin_checkout";    // 0.40

    // Conversion signals (highest intent)
    public const string Purchase = "purchase";               // 1.00
    public const string Review = "review";                   // 0.60
    public const string Share = "share";                     // 0.50

    public static readonly Dictionary<string, double> BaseWeights = new()
    {
        { PageView, 0.01 },
        { ProductView, 0.10 },
        { AddToCart, 0.35 },
        { Purchase, 1.00 },
        // ... (see full code for complete list)
    };

    public static double GetBaseWeight(string signalType)
    {
        return BaseWeights.GetValueOrDefault(signalType, 0.05);
    }
}
```

**Varför är detta viktigt:**

- En enkel sida-uppfattning (`0.01`) vann ' inte dominerade signalen
- Läggande till cart (`0.35`) är en stark intentionssignal
- köpa (`1.00`) är den starkaste signalen

Den här hierarkin förhindrar att "drive- genom att bläddra " ska förorena profilen

## SessionCollector: Inspelningssignaler (Cache-OmnärM SK3

```csharp
// Mostlylucid.SegmentCommerce/Services/Profiles/SessionCollector.cs
public async Task<SessionProfile> RecordSignalAsync(
    SessionSignalInput input, CancellationToken ct = default)
{
    var sessionKey = input.SessionKey;
    
    // Get or create session FROM CACHE (never DB)
    var session = _cache.Get<SessionProfile>(sessionKey);
    
    if (session == null)
    {
        session = new SessionProfile
        {
            SessionKey = sessionKey,
            StartedAt = DateTime.UtcNow
        };
    }

    session.LastActivityAt = DateTime.UtcNow;

    var weight = input.Weight ?? SignalTypes.GetBaseWeight(input.SignalType);

    // Update in-memory aggregates
    session.TotalWeight += weight;
    session.SignalCount++;

    if (!string.IsNullOrEmpty(input.Category))
    {
        session.Interests.TryGetValue(input.Category, out var currentScore);
        session.Interests[input.Category] = currentScore + weight;
    }

    if (input.SignalType == SignalTypes.AddToCart)
    {
        session.CartAdds++;
    }

    // Put back in cache with sliding expiration
    _cache.Set(sessionKey, session, new MemoryCacheEntryOptions
    {
        SlidingExpiration = TimeSpan.FromMinutes(30),
        Priority = CacheItemPriority.Normal,
        PostEvictionCallbacks = { /* elevation callback */ }
    });

    return session;
}
```

**snabb eftersom:**

- Rent i-memory | ( | inga DB-skrivare
- Ingen serialisering överhuvudtaget (IMemoryCache)
- Ingen nätverkssändning (lokal cache)

## Permanenta Profiler : Upphöjda signaler

När en sessie visar hög uppmärkelse (kart lägger till , inköp | ), | lyfter vi signalerna till **bestående profil**.

### PersistentProfileEntity: Den långa -Termprofilen

```csharp
// Mostlylucid.SegmentCommerce/Data/Entities/Profiles/PersistentProfileEntity.cs
[Table("persistent_profiles")]
public class PersistentProfileEntity
{
    [Key]
    public Guid Id { get; set; } = Guid.NewGuid();

    [Required]
    [MaxLength(256)]
    public string ProfileKey { get; set; } = string.Empty;

    // How this profile is identified (Fingerprint, Cookie, Identity)
    public ProfileIdentificationMode IdentificationMode { get; set; }

    // Behavioral data (all JSONB)
    [Column("interests", TypeName = "jsonb")]
    public Dictionary<string, double> Interests { get; set; } = new();

    [Column("affinities", TypeName = "jsonb")]
    public Dictionary<string, double> Affinities { get; set; } = new();

    [Column("brand_affinities", TypeName = "jsonb")]
    public Dictionary<string, double> BrandAffinities { get; set; } = new();

    [Column("price_preferences", TypeName = "jsonb")]
    public PricePreferences? PricePreferences { get; set; }

    [Column("traits", TypeName = "jsonb")]
    public Dictionary<string, bool> Traits { get; set; } = new();

    // Computed segments
    public ProfileSegments Segments { get; set; } = ProfileSegments.None;

    [Column("llm_segments", TypeName = "jsonb")]
    public Dictionary<string, double>? LlmSegments { get; set; }

    // Vector embedding for similarity matching
    [Column("embedding", TypeName = "vector(384)")]
    public Vector? Embedding { get; set; }

    // Statistics
    public int TotalSessions { get; set; }
    public int TotalSignals { get; set; }
    public int TotalPurchases { get; set; }
    public int TotalCartAdds { get; set; }

    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    public DateTime LastSeenAt { get; set; } = DateTime.UtcNow;
    public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
```

**Ändå noll PII**:

- `ProfileKey` är en HMAC hash (oåtvändbarM SK1
- `IdentificationMode` berättar för oss hur den identifierades. (fingerprint/cookieM SK2login
- Alla data är **beteendesignaler**, inte personlig information

### Elevation: Session M SK1 Profil

```csharp
public async Task ElevateToProfileAsync(
    SessionProfileEntity session, 
    PersistentProfileEntity profile, 
    CancellationToken ct = default)
{
    if (session.IsElevated)
        return;

    // Merge interests (use higher value)
    foreach (var (category, score) in session.Interests)
    {
        if (!profile.Interests.ContainsKey(category) || 
            profile.Interests[category] < score)
        {
            profile.Interests[category] = score;
        }
    }

    // Update stats
    profile.TotalSessions++;
    profile.TotalSignals += session.SignalCount;
    profile.TotalCartAdds += session.CartAdds;
    profile.LastSeenAt = DateTime.UtcNow;
    profile.UpdatedAt = DateTime.UtcNow;

    // Mark session as elevated
    session.IsElevated = true;
    session.PersistentProfileId = profile.Id;

    // Clear segment cache (will be recomputed)
    profile.SegmentsComputedAt = null;
    profile.EmbeddingComputedAt = null;

    await _db.SaveChangesAsync(ct);
}
```

**När höjd uppstår:**

- Efter en cart lägga till (high intent
- Efter en köp (konversion)
- När användaren väljer att använda persistens (fingerprint/cookieM SK2login )

## Segmentdefinitioner : Belöningen

Vi har tvätsat segment för två delar, .. Låt oss nu leverera **aktionsbar utgång** av alla signalinsamlingar, svarade de: ", vilken sorts shopper är den här?

### Varför Fuzzy membership?

Den traditionella segmenteringen är binär.

```mermaid
flowchart LR
    subgraph "Binary Segmentation"
        Profile1[3 purchases] -->|"Threshold: 5"| Out[NOT High-Value]
        Profile2[5 purchases] -->|"Threshold: 5"| In[High-Value]
    end
```

En kund med "4" -köp behandlas på samma sätt som en med .

**Fuzzy segmentering** ger varje profil en poäng (0-1):

| Köprningar | Binär | | | Fuzzy Score ||
|-----------|--------|-------------|
| 0 ♫ ♫ | Nej ♫
| 2 ♫ ♫ | Nej ♫
| 4 ♫ ♫ | Nej ♫
|  5+

Nu kan du personalisera proportionellt.

### Segmentdefinitionsstrukturen

```csharp
// Mostlylucid.SegmentCommerce/Services/Segments/SegmentDefinition.cs
public class SegmentDefinition
{
    public string Id { get; set; }           // "tech-enthusiast"
    public string Name { get; set; }          // "Tech Enthusiasts"
    public string Description { get; set; }   // "Users with strong interest in technology"
    public string Icon { get; set; }          // "🔧"
    public string Color { get; set; }         // "#3b82f6"

    public List<SegmentRule> Rules { get; set; } = [];

    // How rules combine: All (AND), Any (OR), Weighted (sum)
    public RuleCombination Combination { get; set; } = RuleCombination.Weighted;

    // Minimum score to be "a member" (0-1)
    public double MembershipThreshold { get; set; } = 0.3;

    public List<string> Tags { get; set; } = [];  // For filtering/grouping
}
```

### Regeltyper : Vad du kan kolla

Varje regel bedömer en dimension av profilen:

```csharp
public enum RuleType
{
    CategoryInterest,  // Check interests.tech, interests.fashion, etc.
    BrandAffinity,     // Check brandAffinities.Sony, brandAffinities.Nike, etc.
    PriceRange,        // Check price preferences (budget vs luxury)
    Trait,             // Check boolean traits (prefersDeals, browsesExtensively)
    Statistic,         // Check totalPurchases, totalSessions, totalCartAdds
    TagAffinity,       // Check affinities.gadgets, affinities.organic, etc.
    Recency,           // Check days since last activity
    Expression         // Custom expressions (advanced)
}
```

### Regleroperatörer

```csharp
public enum RuleOperator
{
    GreaterThan,       // value > threshold
    GreaterOrEqual,    // value >= threshold
    LessThan,          // value < threshold
    LessOrEqual,       // value <= threshold
    Equal,             // value == threshold
    NotEqual,          // value != threshold
    Contains,          // for array/string checks
    Between,           // for ranges
    In, NotIn          // for set membership
}
```

### Rule Combination Methods

Hur många regler kombineras till en slutlig poäng

```csharp
public enum RuleCombination
{
    All,      // AND logic: Score = min(all rule scores). All rules must pass.
    Any,      // OR logic: Score = max(all rule scores). Any rule can pass.
    Weighted  // Weighted sum: Score = Σ(rule.weight × rule.score) / Σ(rule.weight)
}
```

**I vikt** är det vanligaste

### Exemplationer för riktiga segment

Här är de standardsegmenten i exemplet projekt:

#### 1. Teknikentusiaster

```csharp
new SegmentDefinition
{
    Id = "tech-enthusiast",
    Name = "Tech Enthusiasts",
    Description = "Users with strong interest in technology products",
    Icon = "🔧",
    Color = "#3b82f6",
    MembershipThreshold = 0.35,
    Rules =
    [
        new() { 
            Type = RuleType.CategoryInterest, 
            Field = "interests.tech", 
            Operator = RuleOperator.GreaterOrEqual, 
            Value = 0.4, 
            Weight = 0.6,  // 60% of score
            Description = "Tech interest > 40%" 
        },
        new() { 
            Type = RuleType.TagAffinity, 
            Field = "affinities.gadgets", 
            Operator = RuleOperator.GreaterOrEqual, 
            Value = 0.2, 
            Weight = 0.2,  // 20% of score
            Description = "Likes gadgets" 
        },
        new() { 
            Type = RuleType.TagAffinity, 
            Field = "affinities.electronics", 
            Operator = RuleOperator.GreaterOrEqual, 
            Value = 0.2, 
            Weight = 0.2,  // 20% of score
            Description = "Likes electronics" 
        }
    ]
}
```

**Utvärderingsexemplet:**

- Profilet har `interests.tech = 0.72`, `affinities.gadgets = 0.31`, `affinities.electronics = 0.15`
- Regler 1: 0.72 ♫ ♫ >= ♫
- Regler 2: 0.31 ♫ ♫ >= ♫
- Regler 3: 0.15 ♫ ♫ < ♫
- Final :  (1.0×0.6 **0.95**
- 0.95 >= ♫ ♫ 0.35 ♫ tröskel ♫ **Membres med 95% tillit**

#### 2. Körkortsmissbrukare

```csharp
new SegmentDefinition
{
    Id = "cart-abandoner",
    Name = "Cart Abandoners",
    Description = "Users who add items to cart but don't complete purchase",
    Icon = "🛒",
    Color = "#ef4444",
    MembershipThreshold = 0.4,
    Rules =
    [
        new() { 
            Type = RuleType.Statistic, 
            Field = "totalCartAdds", 
            Operator = RuleOperator.GreaterOrEqual, 
            Value = 3, 
            Weight = 0.5, 
            Description = "3+ cart adds" 
        },
        new() { 
            Type = RuleType.Statistic, 
            Field = "totalPurchases", 
            Operator = RuleOperator.LessThan, 
            Value = 2, 
            Weight = 0.5, 
            Description = "Few purchases" 
        }
    ]
}
```

Det fångar den " som går tillbaks men inte räddar den' som köper patternen, " mönster, M SK3 som är perfekt för återhämtningskampanjer.

#### 3. Höga -Value Kunden

```csharp
new SegmentDefinition
{
    Id = "high-value",
    Name = "High-Value Customers",
    Description = "Customers who make frequent purchases and spend above average",
    Icon = "💎",
    Color = "#8b5cf6",
    MembershipThreshold = 0.4,
    Rules =
    [
        new() { 
            Type = RuleType.Statistic, 
            Field = "totalPurchases", 
            Operator = RuleOperator.GreaterOrEqual, 
            Value = 3, 
            Weight = 0.4, 
            Description = "3+ purchases" 
        },
        new() { 
            Type = RuleType.PriceRange, 
            Field = "priceRange", 
            Value = "100-10000",  // High-end shoppers
            Weight = 0.3, 
            Description = "High price range" 
        },
        new() { 
            Type = RuleType.Recency, 
            Field = "lastSeen", 
            Operator = RuleOperator.LessThan, 
            Value = 30, 
            Weight = 0.3, 
            Description = "Active in last 30 days" 
        }
    ]
}
```

#### 4. Makthandlare

```csharp
new SegmentDefinition
{
    Id = "bargain-hunter",
    Name = "Bargain Hunters",
    Description = "Price-sensitive shoppers who love deals and discounts",
    Icon = "🏷️",
    Color = "#22c55e",
    MembershipThreshold = 0.3,
    Rules =
    [
        new() { 
            Type = RuleType.PriceRange, 
            Field = "priceRange", 
            Value = "0-75",  // Budget shoppers
            Weight = 0.5, 
            Description = "Low price preference" 
        },
        new() { 
            Type = RuleType.Trait, 
            Field = "traits.prefersDeals", 
            Value = true, 
            Weight = 0.3, 
            Description = "Prefers deals" 
        },
        new() { 
            Type = RuleType.Statistic, 
            Field = "totalCartAdds", 
            Operator = RuleOperator.GreaterThan, 
            Value = 5, 
            Weight = 0.2, 
            Description = "Shops around" 
        }
    ]
}
```

### Alla standardsektioner

Sampleset inkluderar segment 10 som täcker vanliga e-commerce-mönster

| Segment
|---------|------|-----------|----------|
| HögaM SK1Value Kunden | | | 💎 ♫ ♫ | ♫
| Teknikbegeistrare | 🔧 | | | Teknikintressen | , | prylarffinitet
| Fashion Forward | | | 👗 | МSK3 | Fashion interest |, | flera besök ♫ | | Style recommendations |
| Makthandlare | | | 🏷 ️ | | | låg prissegment
| Nya besökare ♫ | ♫ ♫👋 ♫
| Körkortslöseri | | | 🛒 ♫ ♫ | ♫
| hemmabegeistrade | 🏠 | | | hemmaintressen | , | senaste aktiviteten || | hemmaproduktcross |
| Fitnessaktivt | | | 🏃 | 3 | Sportintressen | , | hälsodrag | МSK5 | Fokus på hälsoprodukter |
| Loyal Customers ♫ ♫ | ♫
| Forskare ♫ | ♫ ♫🔍 ♫

## SegmentService: Beräkningsmedlemmar

Den `SegmentService` bedömer profiler mot alla segmentregler:

```csharp
// Mostlylucid.SegmentCommerce/Services/Segments/SegmentService.cs
public SegmentMembership EvaluateSegment(ProfileData profile, SegmentDefinition segment)
{
    var ruleScores = new List<RuleScore>();
    
    foreach (var rule in segment.Rules)
    {
        var (score, actualValue) = EvaluateRule(profile, rule);
        ruleScores.Add(new RuleScore
        {
            RuleDescription = rule.Description,
            Score = score,
            Weight = rule.Weight,
            ActualValue = actualValue  // For transparency
        });
    }

    // Combine based on segment's combination method
    double finalScore = segment.Combination switch
    {
        RuleCombination.All => ruleScores.Min(r => r.Score),
        RuleCombination.Any => ruleScores.Max(r => r.Score),
        RuleCombination.Weighted => ComputeWeightedScore(ruleScores),
        _ => 0
    };

    return new SegmentMembership
    {
        SegmentId = segment.Id,
        SegmentName = segment.Name,
        Score = Math.Round(finalScore, 3),
        IsMember = finalScore >= segment.MembershipThreshold,
        RuleScores = ruleScores,
        Confidence = score switch  // Human-readable
        {
            >= 0.8 => "Very High",
            >= 0.6 => "High",
            >= 0.4 => "Medium",
            >= 0.2 => "Low",
            _ => "Very Low"
        }
    };
}
```

### Förklaring Built-In

Varje valresultat inkluderar **faktiska värden** som ledde till poängen

```csharp
// What the UI receives:
{
    "segmentId": "tech-enthusiast",
    "segmentName": "Tech Enthusiasts",
    "score": 0.95,
    "isMember": true,
    "confidence": "Very High",
    "ruleScores": [
        { "description": "Tech interest > 40%", "score": 1.0, "actualValue": "0.72" },
        { "description": "Likes gadgets", "score": 1.0, "actualValue": "0.31" },
        { "description": "Likes electronics", "score": 0.75, "actualValue": "0.15" }
    ]
}
```

Användare kan **se exakt varför** de' är i ett segment . Detta är viktigt för transparens och GDPR-observation

## Signalflödet

Här' är hur en produktuppfattning blir en segmentaffiliering

```mermaid
sequenceDiagram
    participant Browser
    participant Cache as SessionCache
    participant Outbox as Outbox
    participant Segment as SegmentService

    Browser->>Cache: Product view (category: "tech")
    Cache->>Cache: Update in-memory session
    Note over Cache: interests.tech += 0.10
    
    Browser->>Cache: Add to cart (high intent)
    Cache->>Outbox: Publish elevation event
    Outbox->>Outbox: Write to outbox table
    
    Note over Outbox: Background worker processes
    Outbox->>Segment: Elevate to PersistentProfile
    
    Segment->>Segment: Evaluate segment rules
    Note over Segment: interests.tech: 0.72 >= 0.40 ✓<br/>Score: 0.95, IsMember: true
    Segment-->>Browser: Segment memberships + explanations
```

## Det utbox-mönstret (Preview)

Alla betydelsefulla handlingar körs genom **utbox-mönster**- vår primära orkestreringsmekanism

```mermaid
flowchart LR
    Action[Cart Add] --> TX[Single Transaction]
    TX --> DB[(Save + Outbox)]
    DB --> Worker[Background Worker]
    Worker --> Route[Route to Handlers]
    
    style TX stroke:#2f9e44,stroke-width:3px
```

**Varför?** Företagsdata och händelser är skrivna i en enda transaktion.

```csharp
// Every action publishes to outbox in the same transaction
await using var transaction = await _db.Database.BeginTransactionAsync(ct);

cart.Items.Add(new CartItem { ProductId = productId });
await _db.SaveChangesAsync(ct);

_outbox.Publish(OutboxEventTypes.ProductAddedToCart, new { ProductId = productId });
await _db.SaveChangesAsync(ct);

await transaction.CommitAsync(ct);
// Event is now guaranteed to be processed
```

**Del 3** täcker fulla utboximplementeringar: arbetslinjet , LISTEN

## Vad' är nästa

Den här delen täckte

- **Profilstrukturen Zero-PII** (otydliga inspelningar mot persisterande profilerM SK1
- **Segmentdefinitioner** med otydliga medlemmar och viktade regler
- **Segmenttjänsterna** med inbyggda "-" -belägbarhet

**Del 3** går djupare in i

- **Implementering av utboxmönster** - tillförlitlig händelsepublicering och en enda router
- **Jobbörns kö** med PostgreSQL `SKIP LOCKED` för fördelad bearbetning
- **LISTEN/NOTIFY** för att få ett jobb på en gång ( inga röster )
- **Interfejs och transparens** - dess dashboard och segmentforskare

---


*Segment är den verkliga utgången av det här systemet.. De svarar: ", vilken sorts shopper är detta? ?", med felaktiga poäng.*