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,
},
});
};