›
byrcsc/laravel-customer-health · 1.x
Features group related events while milestones preserve the first durable customer achievement.
Use features to ask how a customer uses part of the product. Use milestones when the first occurrence should remain true after raw activity expires.
Each event declares one feature:
final class WorkflowCreated extends ProductEvent
{
public static string $feature = 'workflows';
public static bool $milestone = true;
}
final class WorkflowRunCompleted extends ProductEvent
{
public static string $feature = 'workflows';
}CustomerHealth::features() returns the registered event classes grouped by
feature name:
$features = CustomerHealth::features();
// [
// 'workflows' => [
// WorkflowCreated::class,
// WorkflowRunCompleted::class,
// ],
// ]The package does not keep a separate feature registry. A misspelled feature name therefore produces an empty event set rather than a configuration error.
Set $milestone = true when the first event means the subject has crossed a
durable boundary:
public static bool $milestone = true;The package records one Milestone per subject and event name. Later
occurrences remain in raw history but do not replace the original milestone
time or actor.
This split supports different retention needs. Raw events can be pruned while milestones continue to answer adoption and onboarding questions.
A feature is adopted when at least one registered milestone event for that feature exists for the subject:
$adopted = CustomerHealth::hasAdopted($team, 'workflows');
$sameResult = $team->hasAdopted('workflows');Non-milestone activity does not count as adoption. A feature containing no
milestone declarations always returns false from hasAdopted().
Feature usage reads raw events for all registered declarations in the feature:
$usage = CustomerHealth::featureUsage('workflows')->for($team);
$usage->firstUsedAt;
$usage->lastUsedAt;
$usage->eventCount();
$usage->eventCount(days: 30);
$usage->distinctActors();
$usage->distinctActors(days: 30);firstUsedAt and lastUsedAt are nullable UTC CarbonImmutable values. Day
windows are inclusive at the cutoff and use the current UTC time.
Actor counts include only rows with both actor type and actor ID. System events
with no actor still contribute to eventCount().
Adoption reads permanent milestone rows. Usage, recency, and actor counts read raw event rows.
After raw-event pruning, hasAdopted() can remain true while event counts
and usage dates no longer include the deleted history. Set retention long
enough for every activity window used by your application and score signals.