UNPKG

chat-bet-parse

Version:

TypeScript package for parsing sports betting contract text into structured data types compatible with SQL Server stored procedures

610 lines (593 loc) 24.2 kB
import { ParseOptions, ParseResult, ParseResultStraight, Period, ContestantType, KnownLeague, Sport, League } from './types-only.js'; export { Bet, BetType, ChatFillResult, ChatOrderResult, ChatType, Contract, ContractSportCompetitionMatch, ContractSportCompetitionMatchBase, ContractSportCompetitionMatchHandicapContestantLine, ContractSportCompetitionMatchHandicapContestantML, ContractSportCompetitionMatchPropOU, ContractSportCompetitionMatchPropYN, ContractSportCompetitionMatchTotalPoints, ContractSportCompetitionMatchTotalPointsContestant, ContractSportCompetitionMatchType, ContractSportCompetitionSeries, ContractType, ContractWritein, Match, ParseResultBase, ParseResultParlay, ParseResultRoundRobin, PeriodTypeCode, isFill, isHandicapLine, isHandicapML, isOrder, isParlay, isPropOU, isPropYN, isRoundRobin, isSeries, isStraight, isTotalPoints, isTotalPointsContestant, isWritein, knownLeagues, knownSports, leagueSportMap } from './types-only.js'; /** * Main parsing engine for chat-bet-parse * Implements the EBNF grammar from README.md */ /** * Parse a chat order (IW message) */ declare function parseChatOrder(message: string, options?: ParseOptions): ParseResultStraight; /** * Parse a chat fill (YG message) */ declare function parseChatFill(message: string, options?: ParseOptions): ParseResultStraight; declare function parseChat(message: string, options?: ParseOptions): ParseResult; /** * Error classes for chat-bet-parse * Provides descriptive error messages for parsing failures */ declare class ChatBetParseError extends Error { readonly rawInput: string; position?: number; constructor(message: string, rawInput: string, position?: number); } declare class InvalidChatFormatError extends ChatBetParseError { constructor(rawInput: string, reason: string); } declare class InvalidContractTypeError extends ChatBetParseError { constructor(rawInput: string, contractPortion: string); } declare class InvalidPriceFormatError extends ChatBetParseError { constructor(rawInput: string, priceStr: string); } declare class InvalidSizeFormatError extends ChatBetParseError { constructor(rawInput: string, sizeStr: string, expectedFormat: string); } declare class InvalidLineValueError extends ChatBetParseError { constructor(rawInput: string, line: number); } declare class InvalidTeamFormatError extends ChatBetParseError { constructor(rawInput: string, teamStr: string, reason: string); } declare class InvalidPeriodFormatError extends ChatBetParseError { constructor(rawInput: string, periodStr: string); } declare class InvalidGameNumberError extends ChatBetParseError { constructor(rawInput: string, gameStr: string); } declare class InvalidRotationNumberError extends ChatBetParseError { constructor(rawInput: string, rotationStr: string); } declare class InvalidPropFormatError extends ChatBetParseError { constructor(rawInput: string, propStr: string, availableProps: string[]); } declare class InvalidSeriesLengthError extends ChatBetParseError { constructor(rawInput: string, lengthStr: string); } declare class MissingSizeForFillError extends ChatBetParseError { constructor(rawInput: string); } declare class UnrecognizedChatPrefixError extends ChatBetParseError { constructor(rawInput: string, prefix: string); } declare class AmbiguousContractError extends ChatBetParseError { constructor(rawInput: string, possibleTypes: string[]); } declare class InvalidDateError extends ChatBetParseError { constructor(rawInput: string, dateStr: string, reason: string); } declare class InvalidWriteinDateError extends ChatBetParseError { constructor(rawInput: string, dateStr: string, reason: string); } declare class InvalidWriteinDescriptionError extends ChatBetParseError { constructor(rawInput: string, description: string, reason: string); } declare class InvalidWriteinFormatError extends ChatBetParseError { constructor(rawInput: string, reason: string); } declare class InvalidKeywordSyntaxError extends ChatBetParseError { constructor(rawInput: string, _keyword: string, message: string); } declare class InvalidKeywordValueError extends ChatBetParseError { constructor(rawInput: string, _keyword: string, _value: string, message: string); } declare class UnknownKeywordError extends ChatBetParseError { constructor(rawInput: string, keyword: string); } declare class InvalidParlayStructureError extends ChatBetParseError { constructor(rawInput: string, message: string); } declare class InvalidParlayLegError extends ChatBetParseError { constructor(rawInput: string, legNumber: number, message: string); } declare class InvalidParlayToWinError extends ChatBetParseError { constructor(rawInput: string, message: string); } declare class MissingNcrNotationError extends ChatBetParseError { constructor(rawInput: string, message?: string); } declare class LegCountMismatchError extends ChatBetParseError { constructor(rawInput: string, expected: number, actual: number); } declare class MissingRiskTypeError extends ChatBetParseError { constructor(rawInput: string); } declare class InvalidRiskTypeError extends ChatBetParseError { constructor(rawInput: string, value: string); } declare class InvalidRoundRobinLegError extends ChatBetParseError { constructor(rawInput: string, legNumber: number, message: string); } declare class InvalidRoundRobinToWinError extends ChatBetParseError { constructor(rawInput: string, message: string); } declare function createPositionError(ErrorClass: new (rawInput: string, ...args: any[]) => ChatBetParseError, rawInput: string, position: number, ...args: any[]): ChatBetParseError; /** * Parses USA odds format: +150, -110, -115.5, ev, even */ declare function parsePrice(priceStr: string, rawInput: string): number; /** * Calculate risk and toWin amounts from American odds price and size * * American odds work as follows: * - Positive odds (e.g., +150): Size is risk amount → risk = size, toWin = size * odds / 100 * - Negative odds (e.g., -150): Size is to-win amount → risk = size * abs(odds) / 100, toWin = size * * @param price - American odds price (must be >= 100 or <= -100) * @param size - The bet size/stake amount (interpreted based on odds sign) * @returns Object containing risk and toWin amounts, both rounded to 2 decimal places */ declare function calculateRiskAndToWin(price: number, size: number): { risk: number; toWin: number; }; /** * Calculate toWin from risk amount and price */ declare function calculateToWinFromRisk(price: number, risk: number): number; /** * Calculate risk from toWin amount and price */ declare function calculateRiskFromToWin(price: number, toWin: number): number; type SizeFormat = 'unit' | 'decimal_thousands' | 'k_notation' | 'dollar' | 'plain_number'; interface ParsedSize { value: number; format: SizeFormat; } /** * Shared size parsing implementation */ type SizeInterpretation = 'unit' | 'decimal_thousands'; /** * Parse size for chat orders (unit interpretation) */ declare function parseOrderSize(sizeStr: string, rawInput: string): ParsedSize; /** * Parse size for chat fills (thousands interpretation for decimals) */ declare function parseFillSize(sizeStr: string, rawInput: string): ParsedSize; interface ParsedStraightSize { risk?: number; toWin?: number; size?: number; } /** * Parse straight bet size specification with support for extended syntax * Formats: * Simple: "= 2.5" or "= $100" (sets size, caller infers risk/toWin from price) * Risk + ToWin: "= $110 tw $100" or "= $110 to win $100" * Risk + ToPay: "= $120 tp $220" or "= $120 to pay $220" (calculates toWin = toPay - risk) * Risk only: "= risk $110" (caller calculates toWin from price) * ToWin only: "= towin $150" (caller calculates risk from price) */ declare function parseStraightSize(sizeText: string, rawInput: string, interpretation: SizeInterpretation): ParsedStraightSize; /** * Parse betting line - must be divisible by 0.5 */ declare function parseLine(lineStr: string, rawInput: string): number; /** * Parse period according to EBNF grammar */ declare function parsePeriod(periodStr: string, rawInput: string): Period; /** * Parse game number: G2, GM1, #2, G 2, GM 1, # 2, etc. */ declare function parseGameNumber(gameStr: string, rawInput: string): number; /** * Parse rotation number (must appear immediately after YG/IW) */ declare function parseRotationNumber(rotationStr: string, rawInput: string): number; /** * Clean and validate team name, with detection for individual contestants */ declare function parseTeam(teamStr: string, rawInput: string): string; /** * Detect if a contestant name is an individual (follows pattern like "B. Falter") */ declare function detectContestantType(contestant: string): ContestantType | undefined; /** * Parse teams string: "Team1/Team2" or just "Team1" */ declare function parseTeams(teamsStr: string, rawInput: string): { team1: string; team2?: string; }; /** * Infer sport and league from context (rotation number, teams, etc.) * This is a simplified version - in practice, you might use rotation number ranges */ declare function inferSportAndLeague(rotationNumber?: number, explicitLeague?: KnownLeague, explicitSport?: Sport): { sport?: Sport; league?: League; }; /** * Case-insensitive text matching */ declare function matchesIgnoreCase(text: string, pattern: string): boolean; /** * Extract player name and optional team affiliation * Pattern: "Cooper Flagg (DAL)" or "B. Falter" * * @param text - Player text potentially containing team in parentheses * @returns Object with player name and optional team */ declare function parsePlayerWithTeam(text: string): { player: string; team?: string; }; /** * Extract over/under from text: "o4.5" -> { isOver: true, line: 4.5 } * Also handles attached prices: "u2.5-125" -> { isOver: false, line: 2.5, attachedPrice: -125 } */ declare function parseOverUnder(ouStr: string, rawInput: string): { isOver: boolean; line: number; attachedPrice?: number; }; type PropCategory = 'PropOU' | 'PropYN'; interface PropTypeInfo { standardName: string; category: PropCategory; contestantType?: 'Individual' | 'TeamLeague'; } /** * Detect prop type from text and return standardized info * Note: Matches longest phrases first to avoid partial matches */ declare function detectPropType(propText: string): PropTypeInfo | null; /** * Extract contestant name and prop keyword from text * Handles multi-word names like "Cooper Flagg pts" or "Team123 passing yards" * * @param text - Text containing contestant and prop (e.g., "Cooper Flagg pts") * @returns Object with contestant name and prop keyword */ declare function extractContestantAndProp(text: string): { contestant: string; propText: string; } | null; /** * Validate prop format based on category */ declare function validatePropFormat(propText: string, hasLine: boolean, rawInput: string): void; /** * Parse writein date with multiple format support and smart year inference */ declare function parseWriteinDate(dateString: string, rawInput: string, isWritein?: boolean, referenceDate?: Date): Date; /** * Validate writein description */ declare function validateWriteinDescription(description: string, rawInput: string): string; interface ParsedKeywords { date?: string; league?: string; freebet?: boolean; cleanedText: string; } declare function parseKeywords(text: string, rawInput: string, allowedKeys: string[]): ParsedKeywords; interface ParsedParlayKeywords { pusheslose?: boolean; tieslose?: boolean; freebet?: boolean; cleanedText: string; } /** * Parse parlay-specific keywords (appear on first line only) * Format: key:value (no spaces around colon) * Returns parsed keywords and text with keywords removed */ declare function parseParlayKeywords(text: string, rawInput: string, allowedKeys: string[]): ParsedParlayKeywords; interface ParsedParlaySize { risk: number; toWin?: number; useFair: boolean; } interface ParsedRoundRobinSize { risk: number; toWin?: number; useFair: boolean; riskType: 'perSelection' | 'total'; } /** * Parse parlay size specification with optional to-win override * Format: "= $100" or "= $100 tw $500" */ declare function parseParlaySize(sizeText: string, rawInput: string): ParsedParlaySize; /** * Parse round robin size with risk type specification * Format: "= $100 per" or "= $600 total" or "= $100 per tw $500" */ declare function parseRoundRobinSize(sizeText: string, rawInput: string): ParsedRoundRobinSize; /** * Calculate the combination C(n, r) = n! / (r! * (n-r)!) * This represents "n choose r" - the number of ways to choose r items from n items */ declare function calculateCombination(n: number, r: number): number; /** * Calculate the total number of parlays for a round robin bet * For exact notation (e.g., 4c2): returns C(n, r) * For at-most notation (e.g., 4c3-): returns sum of C(n, 2) + C(n, 3) + ... + C(n, r) */ declare function calculateTotalParlays(totalLegs: number, parlaySize: number, isAtMost: boolean): number; /** * Convert American odds to decimal odds * Positive odds (e.g., +120): decimal = 1 + (odds / 100) → 2.20 * Negative odds (e.g., -110): decimal = 1 + (100 / abs(odds)) → 1.909090... */ declare function americanToDecimalOdds(americanOdds: number): number; /** * Calculate fair ToWin for a parlay given leg prices and risk amount * Formula: ToWin = Risk × (product of all decimal odds - 1) */ declare function calculateParlayFairToWin(legPrices: number[], risk: number): number; /** * Calculate fair ToWin for a round robin given leg prices, risk, and configuration * For per-selection: risk is per-parlay amount * For total: risk is total, need to divide by number of parlays */ declare function calculateRoundRobinFairToWin(legPrices: number[], totalRisk: number, riskType: 'perSelection' | 'total', parlaySize: number, isAtMost: boolean): number; /** * Type definitions for chat-bet-parse grading functionality */ /** * The result of grading a contract * W = Win, L = Loss, P = Push (tie), ? = Unable to grade (missing data) */ type GradeResult = 'W' | 'L' | 'P' | '?'; /** * SQL Server connection configuration * Uses the mssql package connection interface */ interface GradingClientConfig { /** SQL Server connection string */ connectionString: string; /** Connection pool configuration (optional) */ pool?: { max?: number; min?: number; idleTimeoutMillis?: number; acquireTimeoutMillis?: number; }; /** Request timeout in milliseconds (default: 30000) */ requestTimeout?: number; /** Connection timeout in milliseconds (default: 15000) */ connectionTimeout?: number; } /** * Interface for grading client */ interface IGradingClient { /** * Grade a parsed chat result * @param result The ParseResult from parseChat() * @param options Optional grading options * @returns Promise resolving to grade ('W', 'L', 'P', or '?') */ grade(result: ParseResult, options?: GradingOptions): Promise<GradeResult>; /** * Test the database connection * @throws Error if connection fails */ testConnection(): Promise<void>; /** * Close the database connection and clean up resources */ close(): Promise<void>; /** * Get connection status */ isConnected(): boolean; } /** * SQL parameters for the universal grading function */ interface GradingSqlParameters { MatchScheduledDate: Date; Contestant1?: string; Contestant2?: string; DaySequence?: number; MatchContestantType?: ContestantType; PeriodTypeCode: string; PeriodNumber: number; ContractType: string; Line?: number; IsOver?: boolean; SelectedContestant?: string; TiesLose?: boolean; Prop?: string; PropContestantType?: ContestantType; IsYes?: boolean; SeriesLength?: number; EventDate?: Date; WriteInDescription?: string; } /** * Base class for grading errors */ declare class GradingError extends Error { readonly originalError?: Error | undefined; constructor(message: string, originalError?: Error | undefined); } /** * Error thrown when database connection fails */ declare class GradingConnectionError extends GradingError { constructor(message: string, originalError?: Error); } /** * Error thrown when SQL query fails */ declare class GradingQueryError extends GradingError { constructor(message: string, originalError?: Error); } /** * Error thrown when contract data is insufficient for grading */ declare class GradingDataError extends GradingError { constructor(message: string); } /** * Options for grading a contract */ interface GradingOptions { /** The scheduled date of the match (defaults to contract date or today) */ matchScheduledDate?: Date; } /** * SQL Server grading client with connection pooling */ /** * SQL Server client for grading parsed chat bets * Supports connection pooling and comprehensive error handling */ declare class ChatBetGradingClient implements IGradingClient { private pool; private isPoolConnected; private readonly connectionString; constructor(config: string | GradingClientConfig); /** * Test the database connection * This will be called automatically on first use, but can be called explicitly */ testConnection(): Promise<void>; /** * Get connection status */ isConnected(): boolean; /** * Grade a parsed chat result */ grade(result: ParseResult, options?: GradingOptions): Promise<GradeResult>; /** * Close the database connection and clean up resources */ close(): Promise<void>; /** * Execute the SQL Server grading function with parameters */ private executeGradingFunction; } /** * Create a new grading client with a connection string */ declare function createGradingClient(connectionString: string): ChatBetGradingClient; /** * Create a new grading client with full configuration */ declare function createGradingClientWithConfig(config: GradingClientConfig): ChatBetGradingClient; /** * @deprecated This module is deprecated. Use the tracking mapper instead: * import { mapParseResultToContractLegSpec } from 'chat-bet-parse/tracking' * * This module now wraps the tracking mapper for backward compatibility. * It will be removed in a future major version. */ /** * @deprecated Use mapParseResultToContractLegSpec from tracking module instead * * Convert a ParseResult to SQL Server function parameters * For straight bets, returns a single GradingSqlParameters object * For parlays/round robins, returns an array of GradingSqlParameters (one per leg) * * This function now wraps the tracking mapper and transforms field names for backward compatibility. */ declare function mapParseResultToSqlParameters(result: ParseResult, options?: GradingOptions): GradingSqlParameters | GradingSqlParameters[]; /** * @deprecated Use validateContractLegSpec from tracking module instead * * Validate that required parameters are present for grading */ declare function validateGradingParameters(params: GradingSqlParameters): void; /** * Type definitions for tracking module * Matches SQL Server ContractLegSpecTableType for ticket tracking */ /** * Contract leg specification matching the SQL Server ContractLegSpecTableType * Used for tracking and storing bet tickets */ interface ContractLegSpec { /** Leg sequence number (1-based) */ LegSequence: number; /** Contract type */ ContractType: string; /** Event date for the contract */ EventDate: Date; /** First contestant raw name */ Contestant1_RawName?: string; /** Second contestant raw name */ Contestant2_RawName?: string; /** League code */ League?: string; /** Day sequence for multiple games */ DaySequence?: number; /** Contestant type */ ContestantType?: string; /** Sport */ Sport?: string; /** Period type code */ PeriodTypeCode?: string; /** Period number */ PeriodNumber?: number; /** Line value */ Line?: number; /** Is over/under */ IsOver?: boolean; /** Selected contestant raw name */ SelectedContestant_RawName?: string; /** Ties lose flag */ TiesLose: boolean; /** Prop name */ Prop?: string; /** Prop contestant type */ PropContestantType?: string; /** Is yes/no */ IsYes?: boolean; /** Series length */ SeriesLength?: number; /** Write-in description */ WriteInDescription?: string; /** Price in American odds format (null for straight bets) */ Price?: number | null; } /** * Options for mapping ParseResult to ContractLegSpec */ interface ContractMappingOptions { /** Event date to use for the contract (optional - will infer from ExecutionDtm if not provided) */ eventDate?: Date; /** League to use for the contract (optional - will infer from contract if not provided) */ league?: string; } /** * Error class for contract mapping failures */ declare class ContractMappingError extends ChatBetParseError { readonly contractType?: string; constructor(message: string, contractType?: string); } /** * Mappers to convert ParseResult to ContractLegSpec for SQL Server tracking */ /** * Convert a ParseResult to ContractLegSpec for SQL Server * For straight bets, returns a single ContractLegSpec object * For parlays/round robins, returns an array of ContractLegSpec (one per leg) */ declare function mapParseResultToContractLegSpec(result: ParseResult, options?: ContractMappingOptions): ContractLegSpec | ContractLegSpec[]; /** * Validate that required parameters are present for contract mapping */ declare function validateContractLegSpec(spec: Partial<ContractLegSpec>): void; export { AmbiguousContractError, ChatBetGradingClient, ChatBetParseError, ContestantType, type ContractLegSpec, ContractMappingError, type ContractMappingOptions, type GradeResult, type GradingClientConfig, GradingConnectionError, GradingDataError, GradingError, GradingQueryError, type GradingSqlParameters, type IGradingClient, InvalidChatFormatError, InvalidContractTypeError, InvalidDateError, InvalidGameNumberError, InvalidKeywordSyntaxError, InvalidKeywordValueError, InvalidLineValueError, InvalidParlayLegError, InvalidParlayStructureError, InvalidParlayToWinError, InvalidPeriodFormatError, InvalidPriceFormatError, InvalidPropFormatError, InvalidRiskTypeError, InvalidRotationNumberError, InvalidRoundRobinLegError, InvalidRoundRobinToWinError, InvalidSeriesLengthError, InvalidSizeFormatError, InvalidTeamFormatError, InvalidWriteinDateError, InvalidWriteinDescriptionError, InvalidWriteinFormatError, KnownLeague, League, LegCountMismatchError, MissingNcrNotationError, MissingRiskTypeError, MissingSizeForFillError, ParseOptions, ParseResult, ParseResultStraight, type ParsedKeywords, type ParsedParlayKeywords, type ParsedParlaySize, type ParsedRoundRobinSize, type ParsedSize, type ParsedStraightSize, Period, type PropCategory, type PropTypeInfo, type SizeFormat, Sport, UnknownKeywordError, UnrecognizedChatPrefixError, americanToDecimalOdds, calculateCombination, calculateParlayFairToWin, calculateRiskAndToWin, calculateRiskFromToWin, calculateRoundRobinFairToWin, calculateToWinFromRisk, calculateTotalParlays, createGradingClient, createGradingClientWithConfig, createPositionError, parseChat as default, detectContestantType, detectPropType, extractContestantAndProp, inferSportAndLeague, mapParseResultToContractLegSpec, mapParseResultToSqlParameters, matchesIgnoreCase, parseChat, parseChatFill, parseChatOrder, parseFillSize, parseGameNumber, parseKeywords, parseLine, parseOrderSize, parseOverUnder, parseParlayKeywords, parseParlaySize, parsePeriod, parsePlayerWithTeam, parsePrice, parseRotationNumber, parseRoundRobinSize, parseStraightSize, parseTeam, parseTeams, parseWriteinDate, validateContractLegSpec, validateGradingParameters, validatePropFormat, validateWriteinDescription };