Zero PII Kundenintelligenz Teil 2:® Profile Signale Segmente
Im Teil wir deckten die Philosophie und die Serie Übersicht Teil Wir bauten die Probendatengenerator .. .
Jetzt lassen Sie let-' s bauen das Kernsystem. Dieses Teil konzentriert sich auf...
- Zero---PII-Profilarchitektur Ephemerale Sitzungen gegen persistente Profile
- Signale und Gewichte Was wir verfolgen und wie es sich ansammelt
- Segmentdefinitionen Fuzzy-Mitgliedschaft mit gewichteten Regeln Das Hauptereignis
- Voransicht des Outbox-Musters Verlässliche Veröffentlichung von Ereignissen
Das ist die Beispielprojekt (Mostlylucid.SegmentCommerceEine kleine E-Commerce-Demo, die das Kernmuster zeigt, ., es wird absichtlich vereinfacht, um Konzepte klar zu demonstrieren ,, aber es enthält auch reale Infrastruktur-Muster (outbox .- based Message Handling . , Hintergrund Job Processing . ,, JSONB Indexierung .), dass Sie ' skalieren horizontal in der Produktion .
Betrachten Sie dies als "Produktionsmuster in einem einzigen -app-Formfaktor. Die gleiche Codestruktur funktioniert, ob Sie einen Prozess ausführen oder über Dienstleistungen verteilen möchten.
Technologiewahl
Wir wählten eine Einfacher, zusammenhängender Stack um die Probe klar zu halten, während sie Produktionsmuster zeigt
PostgreSQL Eine Datenbank
Anstelle von separaten Vektordatenbanken verwenden wir Nachrichtenqueues und Caching Layers. PostgreSQL:
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 flexible Schemata
- pgvector für die Einbettung von (,no Qdrant-/,Pinecone-),
- LEBENSMITTEL für Jobs .( .no Redis . / .RabbitMQ .) .
- tsvector für Volltext---Text - Elastische Suche - )-
Eine Verbindung Pool-Verbindungspool-Verbindung 1 Backup-Verbindung
HTMX-Datenverarbeitungsgeräte für Alpine-., :, Server, UI
<!-- Instant search (no page reload) -->
<input
hx-get="/api/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#results" />
- HTMXServer gibt Teilwerte wieder ,( ,no JSON ,→ ,templatierend ,)
- Alpen-.-jsReaktivität ohne Bauschritt
- Fortschrittliche VerbesserungFunktioniert ohne JS.,, besser mit ihm
SPA---ähnliche UX mit Server---Rendering Einfachheit
ASP-.-NET-Kern ,:-Verteilbare Muster
Einzel-App, aber Musterskala, :
- Auslauf Auswechseln für Nachrichtenbus
- Job-Warteschlange ](-LISTEN-/-NOTIFY-)-Swap für Arbeitnehmerpool
- Sitzungssammler Swap für separate API
Einfach starten - .-Verteilen, wenn erforderlich -.-
Was wir vermieden haben
- Keine separate Vektordatenbank ist kolodiert.
- Kein JS-Framework (-HTMX +-Alpine ist einfacher
- Noch keine Mikroservices arbeiten in Monolith oder verteilt
- Keine Docker-Kompositausbreitung
Die Zero---PII-Herausforderung
Traditionelles Benutzer-Tracking speichert identifizierbare Informationen.:-Namen.,-E-Mails.MS K2-Benutzer-IDs, die mit dem Verhalten verbunden sind..-DSGVO und Datenschutzbestimmungen machen diese zunehmend problematischer.
Wir speichern Verhaltensmuster... nicht Identitäten...
flowchart LR
subgraph "Traditional Approach"
User1[User: [email protected]] --> 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
Die wichtigsten Erkenntnisse du musst nicht wissen, wer jemand ist zu wissen, WAS sie interessiert?.
Architektur im Überblick
Das System hat drei Kernschichten.
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
- Sitzungssammler --Erfasst Verhaltenssignale (-Ansichten,-Klicks,-Warenkorb fügt hinzu)---Aufnahmen Nur in Erinnerung
- Persistentes Profil -, erhöht hohe Wertesignale ,, berechnet Interessen Datenbank ,- , unterstützt
- Segmentdienst Bewertet Profile gegen RegelnBewertet Mitgliedschaften
Kritische UnterscheidungDie Sitzungen sind ephemer, .. Profile sind persistent,.. Dies ist kein Implementierungs-Detail.-.it' ist eine Entscheidung für die Privatsphäre-Architektur.
Sitzungsprofile Strict In--Memory-Cache von (LFU)
Sitzungen sind ausschließlich in der --SpeicherSie leben in IMemoryCache mit gleitendem Ablauf und nie die Datenbank berühren.
Dies ist ein harter architektonischer Constraint.:-Session-Daten können nicht persistent werden..-Es sammelt Signale während eines Besuchs und elikt nach Minuten der Inaktivität über LFU.MSC3-Least Häufig verwendete Cache-Richtlinien unter Speicherdruck.
Sitzungsprofil - Das In-Memory-Modell
// 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; }
}
Gespeichert in IMemoryCache oder IDistributedCache mit Sliding Expiration
// 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
}
}
}
});
Starke Einschränkungen
- Keine versehentliche PersistenzLive-Sitzungen Nur im Cache -(-IMemoryCache oder IDistributedCache wie Redis-)-
- LFU-VertreibungUnter dem Druck des Gedächtnisses zuerst die Sitzungen evakuieren
- Ablauf des GleitvorgangsMinuten vor der letzten Aktivität
- RäumungsaufrufLetzte Chance, hohe - Wertesignale zu erhöhen bevor sie verloren gehen
- Keine WiedereinziehungDie App startet alle Sitzungen neu, es sei denn, sie verwendet IDistributedCache, ,, aber immer noch ephemeral.
Elevationsentscheidung über die Räumung
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;
}
Warum die Räumung zurückruft
Das ist die Nur Sicherer Punkt, um Persistenz zu entscheiden.
- Wir kennen den gesamten Sitzungsverlauf
- Wir können das Gesamtengagement bewerten
- Wir vermeiden die Speicherung von Low---Wert-Sitzungen
- Wir garantieren, dass die Sitzungen nicht versehentlich fortbestehen
Wenn wir nicht während der Räumung erhöhen, ist die Sitzung fort für immerDies ist das Design.
SitzungKontext, was wir verfolgen
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"
}
Beachten Sie was, ' nicht Hier werden die IP-Adressen der , user agents, volle URLs, Messpixel der, tracking erfasst KontextmusterNicht identifizierbare Informationen
Was sind Signale?
A Signal ist ein Verhalten Tatsache während einer Benutzer-Action erfasst, statt Tracking-"hessen, die ,"h wir verfolgen, was passierte, ."h
Dieses Konzept kommt von ephemere Signale- Lived Fakten, die in einem begrenzten Fenster existieren und aus natürlichem Alter .. . In diesem System ., Operationen senden Signale wie "api.rate_limited" oder "gateway.slow" Verhalten ohne enge Kopplung zu koordinieren
Hier wenden wir das gleiche Muster auf das Benutzerverhalten an.
- Produktansicht →
product_viewSignal Gewicht : - In den Warenkorb legen →
add_to_cartSignal Gewicht : - Kauf →
purchaseSignal Gewicht :
Signale sind Vergänglich Auslöschen mit der Sitzung Null---PII Nichtzutreffendes streichen. gewichtet (-Intent--Bewusst
Kunden-Fingerabdruck-Methoden zur Identifizierung von Cookies
Um Sitzungen zu verlinken, ohne Cookies zu verfolgen, verwenden wir client--'side FingerabdruckDer Browser berechnet einen Hash aus Signalen , ( timezone ,, , Bildschirmauflösung , , , WebGL Renderer, , , Canvas-Fingerabdruck , und sendet nur der Hash zu /api/fingerprint.
Der Server dann HMACs, die Hasch mit einem geheimen Schlüssel, der es an anderer Stelle unbrauchbar macht
Dieser Code ist von meinem Bot-Detektionsprojekt,, wo es verwendet wird, um Schaber zu identifizieren; ., gleiche Technik; ,, verschiedene Zwecke; MSC4,
// 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---Seite:-
// Server HMACs the client hash with a secret key
var profileKey = HMACSHA256(clientHash + secretKey);
Jetzt haben wir einen stabilen usersite-Bezeichner ohne Cookies oder lokaleStorage. Vollständige Fingerabdruckquelle Angepasst aus meistluzider ., Botdetektion, ).
Signaltypen und Gewichte
Verschiedene Aktionen haben unterschiedliche Intent-Levels. Grundgewichte.
SignalTypen: Die Gewichtshierarchie
// 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);
}
}
Warum das so wichtig ist
- Eine einzelne Seitenansicht
0.01Das Signal wird beherrscht - In den Warenkorb legen
0.35Ein starkes Intent-Signal - Kauf von (
1.00Das Signal ist das stärkste.
Diese Hierarchie verhindert, dass das Profil verschmutzt wird.
Sitzungssammler: Recording-Signale ( Editieren- Nur )
// 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;
}
Schnell, weil
- Rein in: -, Memory, keine DB schreibt:),
- Kein Serialisierungsüberkopf
- Keine Netzwerk-Aufrufe Lokaler Cache
Persistente Profile, erhöhte Signale
Wenn eine Sitzung zeigt, hohe Intent-(-cart fügt hinzu: ,, Käufe, ),, wir erhöhen Signale zu einem Persistentes Profil.
PersistentProfileEntity-ProfileDas lange Profil von -
// 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;
}
Immer noch Null PII:
ProfileKeyist ein HMAC-Hash, nicht reversibelIdentificationModeerklärt uns, wie es identifiziert wurde.- Alle Daten sind VerhaltenssignaleNicht personenbezogene Daten
Erhöhte Anzahl der Sitzungen (MSK)
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);
}
Wenn eine Erhöhung eintritt
- Nach einem Warenkorb fügen Sie hinzu:
- Nach einem Kauf
- Wenn Benutzer sich für Persistenz entscheidet
Segmentdefinitionen – Die Ausschüttung
Die Segmente für zwei Teile sind die Durchführbare Ausgabe von allen, die Signalsammlung----sie antworten, was für eine Art Shopper ist diese-MSC2-
Warum Fuzzy Mitgliedschaft?
Traditionelle Segmentierung ist binär, :-you-'-yout entweder im Segment oder youou-'-iout nicht.-iOut Dies erzeugt Probleme
flowchart LR
subgraph "Binary Segmentation"
Profile1[3 purchases] -->|"Threshold: 5"| Out[NOT High-Value]
Profile2[5 purchases] -->|"Threshold: 5"| In[High-Value]
end
Ein Kunde mit Käufen von 4 wird identisch mit einem mit 0. behandelt, dass ' falsch ist.
Fuzzy Segmentierung gibt jedem Profil eine Punktzahl
Käufe Binärer |, Fuzzy Score |-----------|--------|-------------| Nicht verfügbar bei: Nicht verfügbar bei: Nicht verfügbar bei: ,| , 5+, |, Ja ,| , 1.0 , | ,
Jetzt können Sie proportional personalisieren ,: , , " , fast hoch , - , Wert , " , Kunden erhalten etwas andere Behandlung als ," , Nirgendwo in der Nähe
Die Segmentdefinitionsstruktur
// 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
}
Regel-Typen, die Sie überprüfen können
Jede Regel wertet eine Dimension des Profils aus
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)
}
Regeloperatoren
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
}
Regelkombinationsmethoden
Wie sich mehrere Regeln zu einem Endergebnis zusammensetzen
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)
}
Gewichtet ist am häufigsten, lässt Sie sagen, " category interest matters 60%, recency mattings 30%, brand affinity matters
Reale Segmentbeispiele
Hier sind die Standard-Segmente im Beispielprojekt.
Tech-Enthusiasten
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"
}
]
}
Beispiel für die Bewertung
- Profil hat
interests.tech = 0.72,affinities.gadgets = 0.31,affinities.electronics = 0.15 - Regel 1:0.72>= -0.4- →Score 1.0- - (Überschreitungsschwelle
- Regel 2:0.31>= -0.2- →Score 1.0- - (Überschreitungsschwelle
- Regel-3: 0.15 <- - 0.2 , →-Score 0.75, (-partial-:
- Schlussbemerkung 0.95
- MSK-Schwellenwert Mitglied mit Vertrauen
Abtrünnige Wagen
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"
}
]
}
Dieses fängt die -"...adds zum Warenkorb, aber tut es nicht kaufen -"... pattern -mesK3... perfekt für Wiederherstellungskampagnen...
Kunden mit hohem Wert
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"
}
]
}
Jäger von Schnäppchen
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"
}
]
}
Alle Standardsegmente
Die Stichprobe umfasst Segmente, die allgemeine e-- commerce-Muster abdecken.
Abschnitt „|" Symbol Hauptregeln Anwendungsfall |---------|------|-----------|----------| Kunden mit hohem Wert , | , 💎 ,| , 3+ , Käufe von , , hohe Ausgaben , vor kurzem ,| , VIP-Behandlung , CRMK9 , Treueprogramme , | , Technologische Enthusiasten – | – MSC2 – MSV3 – MKK4 – Gadget-Affinität – MKS5 – Technische Produktempfehlungen – MSR6 – Mode nach vorn – | – 👗 – | – Mode-Interessen –, – mehrfache Besuche – | – Style-Empfehlungen – | – Verhandeln Jäger Vergünstigungen Vergünstigung Angebote Vergünstigungs-Meldungen Verbilligte Preisspanne Neue Besucher |, neue Besucher |, neue Mitglieder ≤2, keine Einkäufe |, Onboarding ,, erste Angebote -, Kauf Warenkorb Abtrünnige Waren , | , 🛒 ,| , 3+ , Warenkorb fügt hinzu ,, , wenige Einkäufe , E| , Wiederherstellung von E-Mails , Home-Enthusiasten Home-Begeisterte Home-Produkt cross--, verkaufen Sportliches Interesse an der Gesundheit von ,e Eigenschaften des Fitness-Produktfokus Loyal Customers Loyale Kunden – |, ⭐, |, MSC4, Käufe ,, 10+, Sitzungen – Retention – MSC8, Belohnungen |, Hochsignale , surft extensive Vergleichswerkzeuge , Detaillierte Informationen |
SegmentService®:-Computing Mitgliedschaften
Das SegmentService wertet Profile gegen alle Segmentregeln aus
// 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"
}
};
}
Erklärbarkeit Built---In
Jedes Mitgliedsergebnis beinhaltet die tatsächliche Werte die zum Score geführt haben
// 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" }
]
}
Benutzer können sehen Sie genau, warum there-'-Re in einem Segment.Dies ist entscheidend für Transparenz und GDPR-Compliance.
Signalfluss (MSK)
Hier wird eine Produktansicht zu einer Segmentmitgliedschaft.
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
Das Outbox-Muster
Alle wichtigen Aktionen fließen durch die outbox-MusterUnser primärer Orchestrierungsmechanismus
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
Warum? Geschäftsdaten und Ereignisse werden in einer einzigen Transaktion geschrieben..-Events können verloren gehen!.-Fälle versuchen automatisch mit exponentiellem Backoff
// 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
Teil deckt die volle Outbox-Implementierung ab ,: , die Job Warteschlange , , , LISTEN , / , NOTIFY für sofortige Pickup , , , retry Logik und Skalierung Muster
Was kommt als nächstes?
Dieser Teil umfasste ,:'
- Zero---PII-Profilarchitektur Vergängliche Sitzungen gegen persistente Profile
- Segmentdefinitionen mit fuzzy Mitgliedschaft und gewichteten Regeln
- Der SegmentService mit eingebauter ,-'in Erklärbarkeit
Teil geht tiefer ins :
- Implementierung des Outbox-Musters Zuverlässiges Event Publishing und der einzelne Router
- Job-Warteschlange mit PostgreSQL
SKIP LOCKEDfür die verteilte Verarbeitung - LEBENSMITTEL für sofortigen Job Pickup
- Benutzeroberfläche und Transparenz Das Armaturenbrett und der Segment-Explorer
Segmente sind die durchführbare Ausgabe dieses Systems..-Sie antworten "-Welche Art von Shopper ist das??"- mit fuzzy Scores.,-nicht binären Eimern..-Und jeder Benutzer kann genau sehen, warum sie sich in einem Segment befinden.