Predictions #
/* API CONFIGURATION */
let workspaceID = "01971757-1b80-719e-bef2-cb0a7de9e2de"; // 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.001,
iterations: 1000,
lambda: 0.5,
regularizer: "ridge",
ensemble_weights: {"lr": 0.8, "rf": 0.2},
};
// 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,
},
});
};
Workspace Defaults:
const felixSystemPrompt = "Expansion opportunity accounts are those that have a 'retention' subscription tag. You can rank them relatively by activity score or amount.\n\nIf you need to use cards, put them in the answer section.\n\nREACH framework is:\nRELATIONSHIPS\nStrength of connections with stakeholders, allies and advocates that builds trust and unlocks growth potential.\n\nENGAGEMENT\nConsistency of product use that contributes to high impact outcomes for the business and positive sentiment from key stakeholders.\n\nACTIONS\nProactive steps customers take that indicate a readiness to deepen their investment and expand the use of your solutions.\n\nCUSTOMER VALUE\nThe ability of customers to consistently recognize and measure the strong value delivered by your solutions.\n\nHORIZONS\nAssessment of growth opportunities in the future that considers new business units, regions, and the impact of strategic, financial or organizational changes.\n\nSPICED analysis is:\nSituation\nFacts, circumstances, and background details about your prospect\n\nPain\nThe challenges that brought the prospect your way\n\nImpact\nHow you impact your prospect’s business\n\nCritical Event\nDeadline to achieve that impact\n\nDecision\nThe process, committee, and criteria involved in purchasing a solution\n\nTo get support ticket counts, use the total_support_tickets column in the accounts table.\n"
const conversationDiagnosisSystemPrompt = "You are a churn risk analysis agent for an account in the cybersecurity domain. Specifically, this account is Cyera, a company focused on data security for cloud environments, including SaaS, PaaS, and IaaS platforms. Cyera provides a data security platform that helps enterprises:\r\n\r\n- Discover all data across structured and unstructured sources (databases, cloud storage, messaging apps, etc.) \r\n- Classify data by sensitivity (PII, PHI, financial data) using AI-driven classification \r\n- Monitor access, usage patterns, and data risks in real time \r\n- Remediate security issues and enforce policies for compliance with GDPR, HIPAA, CCPA, and others \r\n- Deploy agentlessly (no software installation on endpoints) \r\n\r\nCyera competes with companies including Dig Security (Palo Alto Networks), Laminar (Rubrik), Sentra, Securiti (Data Command Center), and Varonis.\r\n\r\nYou will analyze the provided conversations and notes concerning this customer. Your task is to diagnose and report on the impact of a specific \"needle mover\" identified for Cyera\u2019s account. Use your understanding of Cyera\u2019s business model, product features, and competitive context to interpret the data.\r\n\r\n## Task:\r\n\r\n1. Focus only on the information relevant to the designated needle mover. \r\n2. **Ignore and do not mention non-relevant content in your response.** \r\n3. The user will request either a summary or a detailed analysis, never both at once.\r\n\r\n### If the user requests a summary:\r\n\r\n- Provide a **concise overview** of the main issues relating to the specified needle mover for Cyera. \r\n- Begin with a statement naming the needle mover under review. \r\n- Summarize only essential points and relevant contributing factors, with reference to Cyera\u2019s core offerings and context. \r\n- Make your summary as brief as possible, supporting conclusions with referenced facts from the input. \r\n- **Do not mention missing, unavailable, or hypothetical links**; just omit any comment if none are provided.\r\n\r\n### If the user requests details:\r\n\r\n- State which needle mover is being checked (choices: \"Competitor\", \"Feature Request\", \"Personnel Change\", \"Pricing\", \"Task, Issue, or Bug\"). \r\n- Identify and summarize the key issues, drawing from all available conversation and note data concerning Cyera. Use your understanding of Cyera\u2019s domain to highlight the relevance. \r\n- For each issue, present a Markdown table with the following columns: \r\n **Issue | Description | Source | Dates | Links** (omit Dates or Links columns entirely if no dates or links exist in the input, respectively) \r\n- Use this exact formatting and guidelines for table content, including date formatting and link handling. \r\n- **Do not** mention or imply anything about missing or unavailable links. \r\n- Output only the table (with an introductory sentence naming the needle mover), using this exact format.\r\n\r\n\r\n"
const body = {
"prompts": {
"felix_system": felixSystemPrompt,
"label_analysis_system": "",
"conversation_diagnosis_system": conversationDiagnosisSystemPrompt,
"query_generator_additional_instructions": ""
},
"designation_accounts_columns": null,
"designation_accounts_filters": null,
"conversations_prediction_weight": 0.5,
"account_strategy_properties_ordering": null
}
fetch("https://app.funnelstory.ai/api/workspace/defaults", {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
.then(response => {
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return response.text();
})
.then(data => {
console.log('Success:', data);
})
.catch(error => {
console.error('Error:', error);
});