Appendix: Future Usage Tracking

The next phase can include:

  • Full event tracking (logins + features) •
  • Custom analytics queries
  • HubSpot sync of feature usage
  • HubSpot dashboards for real adoption
  • Extendable architecture for any future event
  • Zero ongoing cost (self-hosted analytics pipeline)

We will track “Feature Usage Events”, meaning anytime the user performs a meaningful action inside the app. Examples:

These will be logged as events in the analytics_events table — the same system we built for logins

RECOMMENDED STRUCTURE:

We encode the full event in one string: event: “vessel_create”

Including optional metadata:

metadata: {

    vesselId: 123,

    screen: “VesselEditor”,

    source: “web”

}

FEATURE TRACKING UTILITY

/lib/analytics/featureTracker.js

 import { EventLogger } from “./eventLogger”;

export const FeatureTracker = {

    async track(userId, companyId, featureName, metadata = {}) {

        return EventLogger.track({

            userId,

            companyId,

            event: `feature_${featureName}`,

            metadata

        });

    }
};

Usage: FeatureTracker.track(user.id, user.companyId, “vessel_create”, { vesselId });

FRONTEND / APP INTEGRATION

When a vessel is created example

Inside the create vessel handler:

import { FeatureTracker } from “@/lib/analytics/featureTracker”;

await FeatureTracker.track(

    user.id,

    user.companyId,

    “vessel_create”,

    { method: “manual” }

);

NEW ANALYTIC QUERIES

We would then add an API to measure feature usage at company level. /lib/analytics/featureQueries.js

import db from “../db”;

export async function getFeatureUsage(companyId) {

  const { rows } = await db.query(

        `SELECT event, COUNT(*) AS count

        FROM analytics_events

        WHERE company_id = $1

          AND event LIKE ‘feature_%’

        GROUP BY event

        ORDER BY count DESC`,

        [companyId]

  );

  return rows;

}

 

export async function getFeatureUsageLast30(companyId) {

    const { rows } = await db.query(

        `SELECT event, COUNT(*) AS count

        FROM analytics_events

        WHERE company_id = $1

          AND event LIKE ‘feature_%’

          AND timestamp > NOW() – INTERVAL ’30 days’

        GROUP BY event

        ORDER BY count DESC`,

        [companyId]

  );

  return rows;

}

HUBSPOT SYNC OF FEATURE METRICS

We won’t store every feature in HubSpot — that would be too many properties. Instead, we send a compressed view into a single property. Something like:

HubSpot Property: vv_top_features_last_30d (type: text)

Which would contain something like:

Added to the nightly sync:

import { getFeatureUsageLast30 } from “@/lib/analytics/featureQueries”;

const features = await getFeatureUsageLast30(companyId);

const formatted = features

  • map(f =>`${f.event.replace(‘feature_’, ”)}: ${f.count}`)
  • join(“\n”);

await updateHubSpotCompany(hubspotId, {

        vv_top_features_last_30d: formatted

});

HEALTH SCORE INCLUDING FEATURES

Feature Adoption (Core Features + Weighted Importance)

So now the formula includes:

Category
Why it matters

We would define Core Features 

Tier 1 (Critical)

(40% weight total)

Tier 2 (Important)

(20% weight total)

Tier 3 (Secondary)

(10% weight total)

Weighted Health Score Formula: Final health score = 0–100.
health_score =

How Each Score Is Calculated

Core Feature Adoption Score (0–100)

  • Based on usage in the last 30 days:
  • Important Feature Score (0–100)
  • Secondary Feature Score (0–100)
  • Login Activity Score (0–100). Same as v1 but normalized:
  • Recency Score (0–100) – days_since_last_login:
  • Vessel Count Score (0–100)
  • Combined Score. The final score is:
  • Implementation Code: /lib/analytics/healthScoreV2.js

export function computeHealthScoreV2({

    coreUsage,

    importantUsage,

    secondaryUsage,

    logins30,

    lastLogin,

    vesselCount

 }) {

    // Normalize feature usage

    const coreFeatureScore = Math.min(coreUsage / 10, 1) * 100;

    const importantFeatureScore = Math.min(importantUsage / 5, 1) * 100;

    const secondaryFeatureScore = Math.min(secondaryUsage / 8, 1) * 100;

    // Login score

    const loginScore = Math.min(logins30 / 30, 1) * 100;

    // Recency score

    const now = Date.now();

    const daysSinceLast = lastLogin

          ? (now – new Date(lastLogin).getTime()) / 86400000

          : 999;

    let recencyScore = 0;

    if (daysSinceLast < 7) recencyScore = 100;

    else if (daysSinceLast < 14) recencyScore = 70;

    else if (daysSinceLast < 30) recencyScore = 40;

    // Vessel count score

    const vesselScore = Math.min(vesselCount / 10, 1) * 100;

    // Weighted score

    const finalScore =

          coreFeatureScore * 0.40 +

          importantFeatureScore * 0.20 +

secondaryFeatureScore * 0.10 +

 loginScore * 0.15 +

recencyScore * 0.10 +

vesselScore * 0.05;

    return Math.round(finalScore);
}

Could also add Churn Prediction in future

This version goes beyond “health score rules” into actual predictive modeling — detecting accounts that are likely to churn before they churn, based on historical behavior patterns. We would not need a full ML infrastructure but we would need to run this with:

  • A small Python script (cron job)
  • A lightweight model file saved locally (pickle)
  • PostgreSQL / existing analytics DB
  • HubSpot as the visualization layer

The goal would be to build a machine-learning model that outputs churn_risk_score which would be a value between 0–100 representing probability of churn in the next 30–90 days. We could then sync this directly into HubSpot Companies so CS + Sales could see:

  • High-risk accounts
  • Declining feature usage
  • At-risk companies needing outreach
  • Renewal timing actions

Machine Learning Approach

Either Logistic Regression (FAST + stable + interpretable) or Random Forest (more accurate but slightly heavier)

Both can run on a cron job easily. The ML model is trained on historical user & account activity, predicting:
Did this account churn? (yes/no) We use your historical data on:

  • Accounts that canceled
  • Accounts that stopped using the product
  • Accounts that downgraded
  • Accounts that stopped paying
  • Support tickets (optional)

Required features for the Model that could produce extremely strong churn prediction signals:

Usage Behavior (all from our v2 system)

  • logins_30_days
  • monthly_active_users
  • days_since_last_login
  • number_vessels
  • core_feature_usage_last_30d
  • important_feature_usage_last_30d
  • secondary_feature_usage_last_30d

Velocity / Trend Features

Comparing to previous 30-day periods:

  • % change in logins
  • % change in core feature usage
  • % change in total activity
  • number of days active this month vs last month

Engagement Breadth

  • number of unique features used in last 30 days
  • number of new features adopted

Account Metadata

  • plan type (free/pro/enterprise)
  • account age (in months)
  • number of users in the account
  • industry type (optional)

Support Signals (optional)

  • number of open tickets
  • days since last support interaction

DATA MODEL: TRAINING SET

We create a dataset using historical accounts.

  • Each row is 1 company

Columns are all features above

  • Label = 1 if churned, 0 if retained

Churn definition:

  • No usage for 60+ days
  • OR account canceled
  • OR account downgraded
  • OR overdue invoices > 60 days

THE TRAINING PIPELINE (PYTHON)

/ml/train_churn_model.py

import pandas as pd

from sklearn.ensemble import RandomForestClassifier

from sklearn.model_selection import train_test_split

import pickle

# Load data from DB export

df = pd.read_csv(“training_data.csv”)

# Separate inputs + label

X = df.drop(columns=[“churn_flag”])

y = df[“churn_flag”]

# Train/validation split

X_train, X_val, y_train, y_val = train_test_split(

          X, y, test_size=0.2, random_state=42

)

# Train model

model = RandomForestClassifier(

n_estimators=200,

max_depth=12,

class_weight=”balanced”

)

model.fit(X_train, y_train)

# Save the model

with open(“churn_model.pkl”, “wb”) as f:

pickle.dump(model, f)

Daily Churn Prediction Pipeline

Cron job loads:

  • Each active company’s metrics
  • The ML model (churn_model.pkl)

Predicts churn risk:

Syncs to HubSpot – HubSpot Property: vv_churn_risk_score (0–100)

Also helpful: vv_churn_risk_label = “High” / “Medium” / “Low”

Rules:

Implementation Code (Sample Prediction Script)

/ml/run_churn_prediction.py

import pickle

import pandas as pd

from hubspot_sync import update_hubspot_company

from queries import fetch_company_features

# Load ML model

model = pickle.load(open(“churn_model.pkl”, “rb”))

companies = fetch_company_features() # returns rows of features

for company in companies:

company_id = company[“company_id”]

hubspot_id = company[“hubspot_id”]

# Convert to model input

feature_vector = pd.DataFrame([company])

# Predict churn probability

prob = model.predict_proba(feature_vector)[0][1]

churn_score = int(prob * 100)

# Sync to HubSpot

update_hubspot_company(hubspot_id, {

“vv_churn_risk_score”: churn_score,

“vv_churn_risk_label”(

“High” if churn_score >= 70

else “Medium” if churn_score >= 40

else “Low”

)

})

print(f”Updated HubSpot churn score for {company_id}: {churn_score}”)

TEAM ACTIONS TRIGGERED AUTOMATICALLY

Using HubSpot workflows:

HIGH RISK (≥70)

  • Notification immediately
  • Create “Churn Prevention Task”
  • Trigger automated email “We noticed you haven’t logged in recently…”
  • Show them onboarding videos / feature tips

MEDIUM RISK (40–69)

  • Add to “monitor” list
  • Suggest check-in call
  • Analyze decline in feature usage

LOW RISK (<40)

  • Healthy; no action

Could also add Adoption Score in future

HubSpot Property: vv_feature_adoption_score (0–100)

Algorithm example:

This shows if a client is adopting core features.

Could also add Real-Time Feature Alerts

We can use HubSpot Workflows to notify Customer Service if:

  • “Client hasn’t created a vessel in 60 days”
  • “Client has never opened Engines page”
  • “Client actively using feature engine_add in last 7 days”
And finally one day…
Full multi-model blending (login decay model + feature regression + ML ensemble)