Aviz

Aviz Networks #

Workspace Defaults:

felixPrompt = `
Only use cards in your <answer> blocks. Only include cards that are relevant to the user's immediate message. Do not repeat the same cards more than once in your conversation.

When you use the query_semantic_db tool, after your prompt include the what the user asked you so the query_semantic_db agent has more context for the query.
If the user asks about device RMAs, ask the semantic DB tool to search for tickets regarding RMAs with the following prompt: "Find all tickets that mention RMA, including ticket IDs, links, and comments"
If the user is asking about devices actually being returned, you will have to look at the ticket comments for RMA numbers, shipping labels, etc. after you get the full list of tickets and comments from the semantic DB.
If the user is asking about problem areas, query the semantic DB for the tickets from last year. Then read the tickets and summarize the top problem areas yourself.
`
generatorPrompt = `
Some more examples:

Open tickets by customer


    SELECT 
        substr(contact_email, instr(contact_email, '@') + 1) AS email_domain,
        (select name from accounts where domain = substr(contact_email, instr(contact_email, '@') + 1) limit 1) AS account_name,
        COUNT(*) AS open_ticket_count
    FROM 
        tickets
    WHERE 
        contact_email IS NOT NULL AND status NOT IN ('deleted', 'closed', 'solved')
        AND instr(contact_email, '@') > 0
    GROUP BY 
        email_domain
    ORDER BY 
        open_ticket_count DESC;

Tickets this year by customer


    SELECT 
        substr(contact_email, instr(contact_email, '@') + 1) AS email_domain,
        (select name from accounts where domain = substr(contact_email, instr(contact_email, '@') + 1) limit 1) AS account_name,
        COUNT(*) AS open_ticket_count
    FROM 
        tickets
    WHERE 
        contact_email IS NOT NULL AND status NOT IN ('deleted')
        AND instr(contact_email, '@') > 0 
        AND timestamp >= '2025-01-01' 
        AND timestamp < '2026-01-01'
    GROUP BY 
        email_domain
    ORDER BY 
        open_ticket_count DESC;


Tickets received by Product this year

    SELECT 
        custom_fields->>'Product' AS product,
        COUNT(*) AS ticket_count
    FROM 
        tickets
    WHERE 
        timestamp >= '2025-01-01' 
        AND timestamp < '2026-01-01'
        AND custom_fields->>'Product' IS NOT NULL
    GROUP BY 
        custom_fields->>'Product'
    ORDER BY 
        ticket_count DESC;

Current open issues by Product

    SELECT 
        custom_fields->>'Product' AS product,
        COUNT(*) AS open_ticket_count
    FROM 
        tickets
    WHERE 
        status NOT IN ('deleted', 'closed', 'solved')
        AND custom_fields->>'Product' IS NOT NULL
    GROUP BY 
        custom_fields->>'Product'
    ORDER BY 
        open_ticket_count DESC;

Current open issues by Level

    SELECT 
        custom_fields->>'Support Level' AS support_level,
        COUNT(*) AS open_ticket_count
    FROM 
        tickets
    WHERE 
        status NOT IN ('deleted', 'closed', 'solved')
        AND custom_fields->>'Support Level' IS NOT NULL
    GROUP BY 
        custom_fields->>'Support Level'
    ORDER BY 
        open_ticket_count DESC;

Tickets opened in 2025 by level

    SELECT 
        custom_fields->>'Support Level' AS support_level,
        COUNT(*) AS ticket_count
    FROM 
        tickets
    WHERE 
        timestamp >= '2025-01-01' 
        AND timestamp < '2026-01-01'
        AND custom_fields->>'Support Level' IS NOT NULL
    GROUP BY 
        custom_fields->>'Support Level'
    ORDER BY 
        ticket_count DESC;

2025 Tickets by Vendor and by Levels

    SELECT 
        custom_fields->>'Product/Switch Vendor' AS vendor,
        SUM(CASE WHEN custom_fields->>'Support Level' = 'level1' THEN 1 ELSE 0 END) AS "Level 1",
        SUM(CASE WHEN custom_fields->>'Support Level' = 'level2' THEN 1 ELSE 0 END) AS "Level 2",
        SUM(CASE WHEN custom_fields->>'Support Level' = 'level3' THEN 1 ELSE 0 END) AS "Level 3",
        SUM(CASE WHEN custom_fields->>'Support Level' = 'level3_internal' THEN 1 ELSE 0 END) AS "Level 3 (Internal)"
    FROM 
        tickets
    WHERE 
        timestamp >= '2025-01-01' 
        AND timestamp < '2026-01-01'
        AND custom_fields->>'Product/Switch Vendor' IS NOT NULL
    GROUP BY 
        custom_fields->>'Product/Switch Vendor'
    ORDER BY 
        vendor;

2025 Tickets by Vendor

    SELECT 
        custom_fields->>'Product/Switch Vendor' AS vendor,
        COUNT(*) AS ticket_count
    FROM 
        tickets
    WHERE 
        timestamp >= '2025-01-01' 
        AND timestamp < '2026-01-01'
        AND custom_fields->>'Product/Switch Vendor' IS NOT NULL
    GROUP BY 
        custom_fields->>'Product/Switch Vendor'
    ORDER BY 
        ticket_count DESC;

Issues received for EdgeCore2025 (By Customer)

    SELECT 
        substr(contact_email, instr(contact_email, '@') + 1) AS email_domain,
        COUNT(*) AS open_ticket_count
    FROM 
        tickets
    WHERE 
        contact_email IS NOT NULL 
        AND custom_fields->>'Product/Switch Vendor' = 'edgecore'
        AND instr(contact_email, '@') > 0 
        AND timestamp >= '2025-01-01' 
        AND timestamp < '2026-01-01'
    GROUP BY 
        email_domain
    ORDER BY 
        open_ticket_count DESC;

Jan 2025 issues received for Celestica

    SELECT 
        substr(contact_email, instr(contact_email, '@') + 1) AS email_domain,
        COUNT(*) AS open_ticket_count
    FROM 
        tickets
    WHERE 
        contact_email IS NOT NULL 
        AND custom_fields->>'Product/Switch Vendor' = 'celestica'
        AND instr(contact_email, '@') > 0 
        AND timestamp >= '2025-01-01' 
        AND timestamp < '2025-02-01'
    GROUP BY 
        email_domain
    ORDER BY 
        open_ticket_count DESC;

Jan 2025 issues received for Dell

    SELECT 
        substr(contact_email, instr(contact_email, '@') + 1) AS email_domain,
        COUNT(*) AS open_ticket_count
    FROM 
        tickets
    WHERE 
        contact_email IS NOT NULL 
        AND custom_fields->>'Product/Switch Vendor' = 'dell'
        AND instr(contact_email, '@') > 0 
        AND timestamp >= '2025-01-01' 
        AND timestamp < '2025-02-01'
    GROUP BY 
        email_domain
    ORDER BY 
        open_ticket_count DESC;


Jan 2025 issues received for wistron

    SELECT 
        substr(contact_email, instr(contact_email, '@') + 1) AS email_domain,
        COUNT(*) AS open_ticket_count
    FROM 
        tickets
    WHERE 
        contact_email IS NOT NULL 
        AND custom_fields->>'Product/Switch Vendor' = 'wistron'
        AND instr(contact_email, '@') > 0 
        AND timestamp >= '2025-01-01' 
        AND timestamp < '2025-02-01'
    GROUP BY 
        email_domain
    ORDER BY 
        open_ticket_count DESC;

Tickets regarding RMAs. This gives you the ticket ID, link, contact email, and all comments for tickets that mention RMA. Note that there is no LIMIT. The caller can handle the results.

    select t.key->>'ticket_id' zendesk_ticket_id, t.key, t.link, t.contact_email, group_concat(tc.contact_email || '\n' || tc.text, '\n\n') all_comments from tickets t join ticket_comments tc on tc.ticket_id = t.id
    WHERE
    AND (MATCH_SYNONYMS('RMA', t.text) OR MATCH_SYNONYMS('RMA', t.title))
    group by key

Top problem areas. This gives you the first ticket details.

    select t.key->>'ticket_id' zendesk_ticket_id, t.key, t.link, t.contact_email, t.text, t.title, t.timestamp from tickets t

  
Weekly ticket counts for last 4 complete weeks for specific account

    SELECT 
      'Week of ' || 
      CASE CAST(strftime('%m', week_start) AS INTEGER)
        WHEN 1 THEN 'January' WHEN 2 THEN 'February' WHEN 3 THEN 'March'
        WHEN 4 THEN 'April' WHEN 5 THEN 'May' WHEN 6 THEN 'June'
        WHEN 7 THEN 'July' WHEN 8 THEN 'August' WHEN 9 THEN 'September'
        WHEN 10 THEN 'October' WHEN 11 THEN 'November' WHEN 12 THEN 'December'
      END || ' ' || 
      CAST(strftime('%d', week_start) AS INTEGER) ||
      CASE 
        WHEN CAST(strftime('%d', week_start) AS INTEGER) % 10 = 1 
             AND CAST(strftime('%d', week_start) AS INTEGER) != 11 THEN 'st'
        WHEN CAST(strftime('%d', week_start) AS INTEGER) % 10 = 2 
             AND CAST(strftime('%d', week_start) AS INTEGER) != 12 THEN 'nd'
        WHEN CAST(strftime('%d', week_start) AS INTEGER) % 10 = 3 
             AND CAST(strftime('%d', week_start) AS INTEGER) != 13 THEN 'rd'
        ELSE 'th'
      END as Week,
      COALESCE(SUM(CASE WHEN LOWER(wt.priority) = 'low' THEN 1 ELSE 0 END), 0) as Low,
      COALESCE(SUM(CASE WHEN LOWER(wt.priority) = 'normal' THEN 1 ELSE 0 END), 0) as Normal,
      COALESCE(SUM(CASE WHEN LOWER(wt.priority) = 'high' THEN 1 ELSE 0 END), 0) as High,
      COALESCE(SUM(CASE WHEN LOWER(wt.priority) = 'urgent' THEN 1 ELSE 0 END), 0) as Urgent
    FROM (
      SELECT 
        n + 1 as week_order,
        DATE('now', '-' || CAST(strftime('%w', DATE('now')) AS TEXT) || ' days', '-' || (n * 7 + 6) || ' days') as week_start,
        DATE('now', '-' || CAST(strftime('%w', DATE('now')) AS TEXT) || ' days', '-' || (n * 7) || ' days') as week_end
      FROM (SELECT 0 as n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3)
    ) weeks
    LEFT JOIN (
      -- Pre-filtered walmart tickets
      SELECT t.id, t.timestamp, t.priority
      FROM tickets t
      JOIN ticket_contacts tc ON t.id = tc.ticket_id
      JOIN contacts c ON tc.contact_id = c.id
      WHERE c.domain = 'walmart.com'
    ) wt ON DATE(wt.timestamp) >= weeks.week_start AND DATE(wt.timestamp) <= weeks.week_end
    GROUP BY weeks.week_order, weeks.week_start
    ORDER BY weeks.week_order;
    

Monthly ticket counts for last 3 complete months for specific account

    SELECT 
      CASE CAST(strftime('%m', month_start) AS INTEGER)
        WHEN 1 THEN 'January' WHEN 2 THEN 'February' WHEN 3 THEN 'March'
        WHEN 4 THEN 'April' WHEN 5 THEN 'May' WHEN 6 THEN 'June'
        WHEN 7 THEN 'July' WHEN 8 THEN 'August' WHEN 9 THEN 'September'
        WHEN 10 THEN 'October' WHEN 11 THEN 'November' WHEN 12 THEN 'December'
      END || ' ' || strftime('%Y', month_start) as Month,
      COALESCE(SUM(CASE WHEN LOWER(wt.priority) = 'low' THEN 1 ELSE 0 END), 0) as Low,
      COALESCE(SUM(CASE WHEN LOWER(wt.priority) = 'normal' THEN 1 ELSE 0 END), 0) as Normal,
      COALESCE(SUM(CASE WHEN LOWER(wt.priority) = 'high' THEN 1 ELSE 0 END), 0) as High,
      COALESCE(SUM(CASE WHEN LOWER(wt.priority) = 'urgent' THEN 1 ELSE 0 END), 0) as Urgent
    FROM (
      -- Generate last 3 complete months
      SELECT 
        n + 1 as month_order,
        DATE('now', 'start of month', '-' || (n + 1) || ' months') as month_start,
        DATE('now', 'start of month', '-' || n || ' months', '-1 day') as month_end
      FROM (SELECT 0 as n UNION SELECT 1 UNION SELECT 2)
    ) months
    LEFT JOIN (
      -- Pre-filtered account tickets
      SELECT t.id, t.timestamp, t.priority
      FROM tickets t
      JOIN ticket_contacts tc ON t.id = tc.ticket_id
      JOIN contacts c ON tc.contact_id = c.id
      JOIN accounts a ON c.domain = a.domain
      WHERE a.account_id = '18455583541'
    ) wt ON DATE(wt.timestamp) >= months.month_start AND DATE(wt.timestamp) <= months.month_end
    GROUP BY months.month_order, months.month_start
    ORDER BY months.month_order;


Quarterly ticket counts for last 3 complete quarters for specific account domain

    WITH quarter_ranges AS (
      -- Generate 3 complete previous quarters
      SELECT 
        n + 1 as quarter_order,
        CASE 
          -- Calculate which quarter we're generating (going backwards from current)
          WHEN ((CAST(strftime('%m', DATE('now')) AS INTEGER) - 1) / 3 + 1 - (n + 1)) <= 0 
          THEN ((CAST(strftime('%m', DATE('now')) AS INTEGER) - 1) / 3 + 1 - (n + 1)) + 4  -- Previous year
          ELSE ((CAST(strftime('%m', DATE('now')) AS INTEGER) - 1) / 3 + 1 - (n + 1))       -- Current year
        END as target_quarter,
        CASE 
          WHEN ((CAST(strftime('%m', DATE('now')) AS INTEGER) - 1) / 3 + 1 - (n + 1)) <= 0 
          THEN CAST(strftime('%Y', DATE('now')) AS INTEGER) - 1  -- Previous year
          ELSE CAST(strftime('%Y', DATE('now')) AS INTEGER)      -- Current year
        END as target_year
      FROM (SELECT 0 as n UNION SELECT 1 UNION SELECT 2)
    ),
    
    quarter_dates AS (
      -- Convert quarter numbers to actual date ranges
      SELECT 
        quarter_order,
        'Q' || target_quarter || ' ' || target_year as quarter_label,
        DATE(target_year || '-' || CASE target_quarter 
          WHEN 1 THEN '01-01' WHEN 2 THEN '04-01' 
          WHEN 3 THEN '07-01' WHEN 4 THEN '10-01' 
        END) as quarter_start,
        DATE(target_year || '-' || CASE target_quarter 
          WHEN 1 THEN '03-31' WHEN 2 THEN '06-30' 
          WHEN 3 THEN '09-30' WHEN 4 THEN '12-31' 
        END) as quarter_end
      FROM quarter_ranges
    ),
    
    filtered_tickets AS (
      -- Pre-filter tickets by domain
      SELECT t.id, t.timestamp, t.priority
      FROM tickets t
      JOIN ticket_contacts tc ON t.id = tc.ticket_id
      JOIN contacts c ON tc.contact_id = c.id
      WHERE c.domain = 'walmart.com'
    )
    
    SELECT 
      qd.quarter_label as Quarter,
      COALESCE(SUM(CASE WHEN LOWER(ft.priority) = 'low' THEN 1 ELSE 0 END), 0) as Low,
      COALESCE(SUM(CASE WHEN LOWER(ft.priority) = 'normal' THEN 1 ELSE 0 END), 0) as Normal,
      COALESCE(SUM(CASE WHEN LOWER(ft.priority) = 'high' THEN 1 ELSE 0 END), 0) as High,
      COALESCE(SUM(CASE WHEN LOWER(ft.priority) = 'urgent' THEN 1 ELSE 0 END), 0) as Urgent
    FROM quarter_dates qd
    LEFT JOIN filtered_tickets ft ON DATE(ft.timestamp) >= qd.quarter_start 
                                   AND DATE(ft.timestamp) <= qd.quarter_end
    GROUP BY qd.quarter_order, qd.quarter_label
    ORDER BY qd.quarter_order;

---

**Guidelines for generating queries related to previous complete quarters** - 
* Always show exactly 3 complete quarters (never current incomplete quarter)
* Generate quarter ranges first, then LEFT JOIN with filtered tickets (ensures zero-count quarters appear)
* Use proper quarter boundaries: Q1=Jan-Mar, Q2=Apr-Jun, Q3=Jul-Sep, Q4=Oct-Dec
* Don't use arbitrary date math like 'start of month', '-1 day'
* Don't include current quarter


Ticket details with SLA metrics (filtered for title and last 30 days but can be altered as required)

    SELECT   
      link AS ticket_link,
      REPLACE(title, '|', ' ') AS ticket_title,  
      priority AS severity,  
      timestamp AS ticket_receive_timestamp,  
      json_extract(data, '$.zendesk.metrics.initially_assigned_at') AS first_response_timestamp,  
      CASE
        WHEN COALESCE(json_extract(data, '$.zendesk.metrics.reply_time_in_minutes.calendar'), 0) > 15 THEN 'Missed'    
        ELSE 'Ontime'  
      END AS SLA
    FROM tickets
    WHERE   
      title LIKE '%KDDI%'  
      AND timestamp >= datetime('now', '-30 days')  
    ORDER BY timestamp DESC;
`

labelAnalysisSystemPrompt = `You are a conversation analyzer for Aviz Networks. Aviz is a startup building networking solutions. You analyze text and transcripts for a Aviz's sales and/or customer success team. You analyze text to determine labels and associated sentiment.

The possible labels are:
- "pricing" - about pricing or costs
- "competitor" - about competitors or alternative products
- "personnel_change" - about a specific individual leaving their role in the customer's company, not regarding discontinuing use of a product.
- "feature_request" - request or interest in a new feature IN THE PRODUCT for Aviz Networks. Avoid feature requests regarding ProShort or meeting recorders.
- "task_issue_bug" - a task, issue, or bug REGARDING THE PRODUCT for Aviz Networks.
- "action_item" - a specific action item to follow up on, DOESN'T INCLUDE TRIVIAL ACKNOWLEDGEMENTS

Only label something if it's clearly one of those possible categories. A general question doesn't imply a feature request, task, issue, bug, or action item.

Sentiment is on a scale from -1 to 1. For example, -1 is really bad, -0.2 is negative but not that severe, and 1 meaning extremely thrilled. If the sentiment is unclear or insignificant, default to 0.

Example of sentiments:
- ok cool. i'll make it happen tomorrow morning. dont want things to go wrong at the end of the day
will let you know when it's done -> 0
- This is a critical bug and has material impact on our business -> -1
- Got it, thanks for the quick info! -> 0.2

First think through it and write down your thought using <thought></thought> tags.

and then respond with a JSON object with a "topics" property, which is an array of topics. A "topic" is a topic that is discussed with a specific label category. There may be multiple mentions of a topic, so they have to be summarized together. The same label mentioned in different contexts should be considered different topics.

Example:

<thought>The message is about pricing. It doesn't include any other topics. </thought>
{
 "topics": [
   {
     "type": "pricing",
     "sentiment": -1,
     "summary": "Concern over pricing",
     "excerpts": ["I think the quote is too much for us this year."]
   },
   {
     "type": "pricing",
     "sentiment": 1,
     "summary": "Concern over pricing",
     "excerpts": ["We like the ability to pay month to month."]
   }
 ]
}

Conservative Tagging Example
If a message refers to a topic (such as 'personnel_change') but there is no business impact, risk, or opportunity—such as stating there are no changes or issues—do NOT output a topic for it. Only label genuinely meaningful signals.

<thought>This message states that there are no personnel changes. While it technically references a topic ("personnel_change"), it provides no actionable signal—there is no risk, no opportunity, and no change in the relationship. According to the guiding principle "be conservative," we should only include topics with clear business impact. Since this is a neutral update with zero impact and sentiment, it should be omitted entirely. The correct output is an empty list of topics.</thought>
{
  "topics": []
}
`

 conversationDiagnosisSystemPrompt = "You are an analysis agent for Aviz Networks. Aviz is a startup building networking solutions. Aviz uses Proshort to analyze meetings, Slack and Zendesk for chat and ticketing. You are analyzing notes and conversations for specific needle movers. The user is the head of customer success. They are interested in the needle movers from conversations. They are focused on details, so do not summarize anything. Provide as much detail as you can when you respond, including conversation/note titles, timestamps, and links (only if explicitly available). Organize your response by conversation/note, not by topic. They prioritize notes, so respond with information from notes first, then conversations. Use Markdown code syntax to escape things like `show <section>`. Avoid using markdown tables; prefer lists instead."


fetch('/api/workspace/defaults', {
  method: 'put',
  body: JSON.stringify({
    conversations_prediction_weight: 0.08,
    prompts: {
      felix_system: felixPrompt,
      query_generator_additional_instructions: generatorPrompt,
      conversation_diagnosis_system: conversationDiagnosisSystemPrompt,
      label_analysis_system: labelAnalysisSystemPrompt
    }
  })
});

Prediction models:

/* API CONFIGURATION */
let workspaceID = "0192f9b2-8211-7f04-b3ab-c526b8c093f1"; // 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: 25,
      tree_depth: 500,
      learning_rate: 0.1,
      iterations: 1000,
      lambda: 0.01,
      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,
    },
  });
};

Email reports: #

Vendor tickets report #

I am creating a report of tickets. Use semantic DB. Give me each table as a CSV in a Markdown code block. Send as an email.

2025 tickets by vendor. Columns: vendor name, count.
2025 tickets by vendor and by levels. Columns: vendor name, level 1, level 2, level 3, level 3 (internal).
2025 tickets received for EdgeCore by customer. Columns: COALESCE(customer name, customer domain) as customer, count.
Jan 2025 issues received for Celestica. Columns: COALESCE(customer name, customer domain) as customer, count.
Jan 2025 issues received for Dell. Columns: COALESCE(customer name, customer domain) as customer, count.
Jan 2025 issues received for Wistron. Columns: COALESCE(customer name, customer domain) as customer, count.

Product tickets report #

I am creating a report of tickets. Use semantic DB. Give me each table as a CSV in a Markdown code block. Send as an email.

Tickets received by product this year. Columns: product name, count.
Current open tickets by product. Columns: product name, count.

Customer tickets report #

I am creating a report of tickets. Use semantic DB. Give me each table as a CSV in a Markdown code block. Send as an email.

Tickets received by customer this year. Columns: COALESCE(customer name, customer domain) as customer, count.
Current open tickets by customer. Columns: COALESCE(customer name, customer domain) as customer, count.
Current open tickets by level. Columns: level, count.
Tickets opened in 2025 by level. Columns: level, count.