import { DataLoader } from '../utils/dataLoader.js';
import { ToolResponse } from '../types/stateData.js';
import {
validateStateName,
handleToolError,
logger
} from '../utils/errorHandler.js';
export const calculateMarketSizeTool = {
name: "calculate_market_size",
description: "Calculate potential market size and revenue projections for a state based on available market data",
inputSchema: {
type: "object",
properties: {
state: {
type: "string",
description: "State jurisdiction for market size calculation"
}
},
required: ["state"]
}
};
export async function handleCalculateMarketSize(
args: { state: string },
dataLoader: DataLoader
): Promise<ToolResponse> {
try {
logger.info('calculate_market_size called', { state: args.state });
validateStateName(args.state);
const stateData = await dataLoader.loadStateData(args.state);
const marketData = stateData.market_data as any;
if (!marketData) {
return {
content: [{
type: "text",
text: JSON.stringify({
state: stateData.state_name,
error: "No market data available for this state",
suggestion: "Market data may be added in future updates"
}, null, 2)
}]
};
}
// Handle different market data structures
const sportsBetting = marketData.sports_betting || marketData.sportsBetting || {};
const casino = marketData.casino || {};
// Check for nested year data (Vermont style)
const firstYearData = sportsBetting.first_year_2024 || sportsBetting.year_2024_data || {};
// Check for specific year fields (New Hampshire style)
const year2024Data = sportsBetting['2024'] || {};
const year2023Data = sportsBetting['2023'] || {};
const year2025Data = sportsBetting['2025_jan_april'] || sportsBetting.jan_march_2024 || {};
// Check for fiscal year data (Oregon style)
const fiscalYear2024 = sportsBetting.fy_2024_oct_2023_sept_2024 || {};
const septemberData = sportsBetting.september_2024 || {};
const marchData = sportsBetting.march_2025 || {};
// Extract handle data (try multiple locations and formats)
const handle2024 = fiscalYear2024.total_handle ||
year2024Data.handle ||
firstYearData.total_handle ||
sportsBetting['2024_handle_jan_oct'] ||
sportsBetting.year_2024_handle ||
sportsBetting['2024_handle'] ||
sportsBetting.current_handle;
const handle2023 = year2023Data.handle ||
sportsBetting['2023_handle'] ||
sportsBetting.first_year_handle ||
sportsBetting.year_2023_handle;
// Extract revenue data (check year-specific and fiscal year fields)
const revenue2024 = fiscalYear2024.gross_revenue ||
year2024Data.gross_revenue ||
firstYearData.gross_revenue ||
sportsBetting['2024_revenue_jan_oct'] ||
sportsBetting.year_2024_revenue ||
sportsBetting['2024_revenue'] ||
sportsBetting.current_revenue;
const revenue2023 = year2023Data.gross_revenue ||
sportsBetting['2023_revenue'] ||
sportsBetting.first_year_revenue ||
sportsBetting.year_2023_revenue;
// Extract tax revenue
const taxRevenue2024 = fiscalYear2024.tax_contributions ||
year2024Data.tax_revenue ||
firstYearData.tax_revenue ||
sportsBetting['2024_tax_revenue'] ||
sportsBetting.tax_revenue;
const taxRevenue2023 = year2023Data.tax_revenue;
// Extract operator data
const operatorName = sportsBetting.operator_name || sportsBetting.exclusive_operator;
const onlineOperators = sportsBetting.online_operators ||
sportsBetting.active_online_sportsbooks ||
marketData.active_online_sportsbooks;
const retailOperators = sportsBetting.retail_operators ||
sportsBetting.retail_operators_tribal ||
sportsBetting.active_retail_sportsbooks;
const operatorNames = sportsBetting.operator_names ||
sportsBetting.major_operators ||
(operatorName ? [operatorName] : []) ||
marketData.major_operators || [];
// Calculate growth rate if we have data
let growthRate = "Cannot calculate";
if (handle2023 && handle2024) {
const growth = ((handle2024 - handle2023) / handle2023) * 100;
growthRate = `${growth.toFixed(1)}%`;
}
// Build comprehensive response
const response: any = {
state: stateData.state_name,
state_code: stateData.state_code,
market_analysis: {
sports_betting: {
launch_date: sportsBetting.launch_date ||
sportsBetting.scoreboard_launch ||
sportsBetting.draftkings_takeover ||
marketData.launch_date,
operators: {
online: onlineOperators,
retail: retailOperators,
names: operatorNames
},
handle: {
"2023": handle2023,
"2024": handle2024
},
revenue: {
"2023": revenue2023,
"2024": revenue2024
},
tax_revenue: {
"2023": taxRevenue2023,
"2024": taxRevenue2024
},
growth_rate: growthRate
}
}
};
// Add fiscal year data if available (Oregon)
if (fiscalYear2024 && Object.keys(fiscalYear2024).length > 0) {
response.market_analysis.sports_betting.fiscal_year_2024 = {
period: "Oct 2023 - Sept 2024",
total_handle: fiscalYear2024.total_handle,
gross_revenue: fiscalYear2024.gross_revenue,
tax_contributions: fiscalYear2024.tax_contributions,
highest_month: fiscalYear2024.highest_month,
highest_month_handle: fiscalYear2024.highest_month_handle
};
}
// Add record month data if available (Oregon September 2024)
if (septemberData && Object.keys(septemberData).length > 0) {
response.market_analysis.sports_betting.record_month = {
month: "September 2024",
handle: septemberData.handle,
gross_revenue: septemberData.gross_revenue,
hold_percentage: septemberData.hold_percentage
};
}
// Add recent month data (Oregon/Vermont style)
if (marchData && Object.keys(marchData).length > 0) {
response.market_analysis.sports_betting.recent_month = {
month: "March 2025",
handle: marchData.handle,
revenue: marchData.gross_revenue,
tax_revenue: marchData.tax_revenue,
hold_percentage: marchData.hold_percentage
};
}
// Add first year detailed data if available (Vermont style)
if (firstYearData && Object.keys(firstYearData).length > 0) {
response.market_analysis.sports_betting.first_year_details = {
total_bets_placed: firstYearData.total_bets_placed,
average_bet_size: firstYearData.average_bet_size,
in_state_handle: firstYearData.in_state_handle,
out_of_state_handle: firstYearData.out_of_state_handle,
monthly_active_users: firstYearData.monthly_active_users_avg,
highest_month: firstYearData.highest_month,
highest_month_handle: firstYearData.highest_month_handle,
lowest_month: firstYearData.lowest_month,
lowest_month_handle: firstYearData.lowest_month_handle,
revenue_share_rate: firstYearData.revenue_share_rate
};
}
// Add 2025 partial year data if available
if (year2025Data && Object.keys(year2025Data).length > 0) {
response.market_analysis.sports_betting.partial_year_2025 = {
handle: year2025Data.handle,
revenue: year2025Data.gross_revenue,
tax_revenue: year2025Data.tax_revenue,
hold_rate: year2025Data.hold_rate,
period: "January-April 2025"
};
}
// Add lifetime totals if available
if (sportsBetting.lifetime_totals) {
response.market_analysis.sports_betting.lifetime_totals = sportsBetting.lifetime_totals;
}
// Add hold rates if available
if (sportsBetting.hold_rates) {
response.market_analysis.sports_betting.hold_rates = sportsBetting.hold_rates;
}
// Add casino data if available
if (casino && Object.keys(casino).length > 0) {
response.market_analysis.casino = {
total_facilities: casino.total_facilities,
net_gaming_revenue: casino.net_gaming_revenue_2022 || casino.annual_revenue,
total_slot_machines: casino.total_slot_machines,
facilities: casino.facilities || []
};
}
// Add population data
if (marketData.population) {
response.market_analysis.population = marketData.population;
response.market_analysis.gambling_age = marketData.gambling_age;
}
// Add unique features if available
if (marketData.unique_features) {
response.market_analysis.unique_features = marketData.unique_features;
}
// Add projections if available
if (sportsBetting.projected_annual_revenue) {
response.market_analysis.sports_betting.projections = sportsBetting.projected_annual_revenue;
} else if (sportsBetting.projections) {
response.market_analysis.sports_betting.projections = sportsBetting.projections;
}
// Extract data extraction date safely
const dataQuality = stateData.data_quality as any;
response.data_extraction_date = dataQuality?.data_extraction_date || (stateData as any).data_extraction_date || "Not available";
return {
content: [{
type: "text",
text: JSON.stringify(response, null, 2)
}]
};
} catch (error) {
return handleToolError(error);
}
}