Training Prediction Models

Basic version:

Determine feature names #

See https://app.funnelstory.ai/api/internal/prediction/models.

Script #

Run the script with trainWithAudiences('account') in the console.

/* API CONFIGURATION */
let workspaceID = "REPLACE_ME"; // Replace with your workspace ID
let BASE_API_URL = "/api";

/* Find feature names from https://app.funnelstory.ai/api/internal/prediction/models */

/* EXTRA CONFIGURATION FOR MODEL TRAINING FIELDS */

/*
  additional_traits:
  An array of strings representing additional traits.
  Example: ["AI_advisor", "AI_answering", "pro", "starter"]
*/
let ADDITIONAL_TRAITS = [];

/*
  known_positive_factors:
  An array of strings representing factors known to contribute positively.
  Example: ["activity:6_mo_cutoff:Had Online Orders", "activity:6_mo_cutoff:Hit 1K Followers"]
*/
let KNOWN_POSITIVE_FACTORS = [];

/*
  known_negative_factors:
  An array of strings representing factors known to have negative effects.
  Example: ["activity:6_mo_cutoff:Negative Reviews", "trait:open_churn_case=true"]
*/
let KNOWN_NEGATIVE_FACTORS = [];

/*
  exclude_factors:
  An array of strings representing factors to exclude from training.
  Example: ["property:last_month_impressions", "activity:6_mo_cutoff:Created User"]
*/
let EXCLUDE_FACTORS = [];

/* 
  Create safe fallbacks so that if any of the above are missing or not arrays,
  they default to an empty array.
*/
let safeAdditionalTraits = Array.isArray(ADDITIONAL_TRAITS)
  ? ADDITIONAL_TRAITS
  : [];
let safeKnownPositiveFactors = Array.isArray(KNOWN_POSITIVE_FACTORS)
  ? KNOWN_POSITIVE_FACTORS
  : [];
let safeKnownNegativeFactors = Array.isArray(KNOWN_NEGATIVE_FACTORS)
  ? KNOWN_NEGATIVE_FACTORS
  : [];
let safeExcludeFactors = Array.isArray(EXCLUDE_FACTORS)
  ? EXCLUDE_FACTORS
  : [];

/* MAIN SCRIPT */
let trainWithAudiences = async (type) => {
  // Fetch audiences (including hidden ones)
  let response = await fetch(`${BASE_API_URL}/audiences?include_hidden=true`, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      "fs-workspace-id": workspaceID,
    },
  });
  let audiencesData = await response.json();

  // Helper: Combine considered IDs from Churn and Retention audiences
  let getCombinedConsideredIds = (audiences, type) => {
    let churnAudience = audiences.find((aud) => aud.name === "Churn");
    let retentionAudience = audiences.find((aud) => aud.name === "Retention");

    if (type === "user") {
      let churnUserIds = churnAudience ? churnAudience.data.matching_fs_user_ids : [];
      let retentionUserIds = retentionAudience ? retentionAudience.data.matching_fs_user_ids : [];
      return Array.from(new Set([...churnUserIds, ...retentionUserIds]));
    } else {
      let churnAccountIds = churnAudience ? churnAudience.data.matching_account_ids : [];
      let retentionAccountIds = retentionAudience ? retentionAudience.data.matching_account_ids : [];
      return Array.from(new Set([...churnAccountIds, ...retentionAccountIds]));
    }
  };

  // For a given audience/model, build the training request body and execute the POST call
  let trainModel = async (aud, model, consideredIds) => {
    let url =
      type === "user"
        ? `${BASE_API_URL}/internal/prediction/models/user_${model}/train`
        : `${BASE_API_URL}/internal/prediction/models/${model}/train`;

    let targetIds =
      type === "user"
        ? { target_user_fsids: aud.data.matching_fs_user_ids }
        : { target_account_ids: aud.data.matching_account_ids };

    let consideredKey = type === "user" ? "considered_user_fsids" : "considered_account_ids";

    // Training parameters
    let params = {
      num_trees: 5,
      tree_depth: 50,
      learning_rate: 0.1,
      iterations: 100,
      lambda: 0.0,
      regularizer: "ridge",
      ensemble_weights: {"lr": 0.5, "rf": 0.5},
    };

    // Build the POST body including the extra configuration fields
    let body = {
      ...targetIds,
      [consideredKey]: consideredIds,
      params,
      additional_traits: safeAdditionalTraits,
      known_positive_factors: safeKnownPositiveFactors,
      known_negative_factors: safeKnownNegativeFactors,
      exclude_factors: safeExcludeFactors,
      // Only ignore target IDs when training the churn model
      // ignore_target_ids: model === "churn", // YOU PROBABLY DON'T WANT THIS.
    };

    await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "fs-workspace-id": workspaceID,
      },
      body: JSON.stringify(body),
    });
  };

  // Get audiences array (adjust based on your API response shape)
  let audiencesArray = audiencesData.response.audiences;
  let consideredIds = getCombinedConsideredIds(audiencesArray, type);

  // Filter to include only hidden 'Churn' and 'Retention' audiences
  let trainingAudiences = audiencesArray.filter(
    (aud) => aud.hidden && (aud.name === "Churn" || aud.name === "Retention")
  );

  // Start training concurrently for each selected audience
  let trainPromises = trainingAudiences.map((aud) => {
    let model = aud.name.toLowerCase(); // Either "churn" or "retention"
    return trainModel(aud, model, consideredIds);
  });
  await Promise.all(trainPromises);

  // Update the scores after training
  let updateUrl =
    type === "user"
      ? `${BASE_API_URL}/internal/prediction/scores/users/update`
      : `${BASE_API_URL}/internal/prediction/scores/accounts/update`;

  await fetch(updateUrl, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "fs-workspace-id": workspaceID,
    },
  });
};