/* API CONFIGURATION */
const workspaceID = "0191a3ed-097f-7dfb-9043-956d54236ce7"; // Replace with your workspace ID
const 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"]
*/
const ADDITIONAL_TRAITS = ["cloud"];
/*
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"]
*/
const KNOWN_POSITIVE_FACTORS = [
"activity:30_days:User Action",
"activity:total:User Action",
"activity:30_days:Add Tenant",
"activity:total:Add Tenant",
"activity:30_days:Enabled Monitoring",
"activity:total:Enabled Monitoring",
"activity:30_days:Enabled Logging",
"activity:total:Enabled Logging",
"activity:30_days:Started Onboarding",
"activity:total:Started Onboarding",
"property:percentage_cost_increase",
"property:api_usage_30_days",
"property:count_of_environments",
"activity:30_days:Created Account",
"activity:30_days:Conversation Created",
"activity:30_days:Completed Implementation",
"activity:30_days:Infra Created",
"activity:total:Meeting Occurred",
"activity:30_days:Meeting Occurred",
"activity:total:Moved to Support",
"property:non_duplo_users"
];
/*
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"]
*/
const KNOWN_NEGATIVE_FACTORS = ["activity:30_days:Created Account", "activity:total:Canceled Project", "activity:30_days:Canceled Project"];
/*
exclude_factors:
An array of strings representing factors to exclude from training.
Example: ["property:last_month_impressions", "activity:6_mo_cutoff:Created User"]
*/
const EXCLUDE_FACTORS = [
"activity:total:Created User",
"activity:30_days:Created User",
"property:node_limit",
"property:arr_all_time",
"property:avg_EC2_compute_cost",
"property:avg_EC2_other_cost",
"property:avg_ECR_cost",
"property:avg_ELB_cost",
"property:avg_IoT_cost",
"property:avg_KMS_cost",
"property:avg_RDS_cost",
"property:avg_RDS_cost",
"property:avg_SQS_cost",
"property:avg_VPC_cost",
"property:avg_cloudwatch_cost",
"property:avg_elasticache_cost",
"property:avg_inspector_cost",
"property:avg_route_53_cost",
"property:avg_secrets_manager_cost",
];
/*
Create safe fallbacks so that if any of the above are missing or not arrays,
they default to an empty array.
*/
const safeAdditionalTraits = Array.isArray(ADDITIONAL_TRAITS)
? ADDITIONAL_TRAITS
: [];
const safeKnownPositiveFactors = Array.isArray(KNOWN_POSITIVE_FACTORS)
? KNOWN_POSITIVE_FACTORS
: [];
const safeKnownNegativeFactors = Array.isArray(KNOWN_NEGATIVE_FACTORS)
? KNOWN_NEGATIVE_FACTORS
: [];
const safeExcludeFactors = Array.isArray(EXCLUDE_FACTORS)
? EXCLUDE_FACTORS
: [];
/* MAIN SCRIPT */
const trainWithAudiences = async (type) => {
// Fetch audiences (including hidden ones)
const response = await fetch(
`${BASE_API_URL}/audiences?include_hidden=true`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"fs-workspace-id": workspaceID,
},
}
);
const audiencesData = await response.json();
// Helper: Combine considered IDs from Churn and Retention audiences
const getCombinedConsideredIds = (audiences, type) => {
const churnAudience = audiences.find((aud) => aud.name === "Churn");
const retentionAudience = audiences.find((aud) => aud.name === "Retention");
if (type === "user") {
const churnUserIds = churnAudience
? churnAudience.data.matching_fs_user_ids
: [];
const retentionUserIds = retentionAudience
? retentionAudience.data.matching_fs_user_ids
: [];
return Array.from(new Set([...churnUserIds, ...retentionUserIds]));
} else {
const churnAccountIds = churnAudience
? churnAudience.data.matching_account_ids
: [];
const 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
const trainModel = async (aud, model, consideredIds) => {
const url =
type === "user"
? `${BASE_API_URL}/internal/prediction/models/user_${model}/train`
: `${BASE_API_URL}/internal/prediction/models/${model}/train`;
const targetIds =
type === "user"
? { target_user_fsids: aud.data.matching_fs_user_ids }
: { target_account_ids: aud.data.matching_account_ids };
const consideredKey =
type === "user" ? "considered_user_fsids" : "considered_account_ids";
// Training parameters
const params = {
num_trees: 25,
tree_depth: 100,
learning_rate: 0.1,
iterations: 100,
lambda: 0.01,
regularizer: "ridge",
ensemble_weights: { lr: 0.3, rf: 0.7 },
};
// Build the POST body including the extra configuration fields
const 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",
};
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)
const audiencesArray = audiencesData.response.audiences;
const consideredIds = getCombinedConsideredIds(audiencesArray, type);
// Filter to include only hidden 'Churn' and 'Retention' audiences
const trainingAudiences = audiencesArray.filter(
(aud) => aud.hidden && (aud.name === "Churn" || aud.name === "Retention")
);
// Start training concurrently for each selected audience
const trainPromises = trainingAudiences.map((aud) => {
const model = aud.name.toLowerCase(); // Either "churn" or "retention"
return trainModel(aud, model, consideredIds);
});
await Promise.all(trainPromises);
// Update the scores after training
const 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,
},
});
};