chat-bet-parse
Version:
TypeScript package for parsing sports betting contract text into structured data types compatible with SQL Server stored procedures
1,485 lines (1,477 loc) • 64.2 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
AmbiguousContractError: () => AmbiguousContractError,
ChatBetGradingClient: () => ChatBetGradingClient,
ChatBetParseError: () => ChatBetParseError,
GradingConnectionError: () => GradingConnectionError,
GradingDataError: () => GradingDataError,
GradingError: () => GradingError,
GradingQueryError: () => GradingQueryError,
InvalidChatFormatError: () => InvalidChatFormatError,
InvalidContractTypeError: () => InvalidContractTypeError,
InvalidGameNumberError: () => InvalidGameNumberError,
InvalidLineValueError: () => InvalidLineValueError,
InvalidPeriodFormatError: () => InvalidPeriodFormatError,
InvalidPriceFormatError: () => InvalidPriceFormatError,
InvalidPropFormatError: () => InvalidPropFormatError,
InvalidRotationNumberError: () => InvalidRotationNumberError,
InvalidSeriesLengthError: () => InvalidSeriesLengthError,
InvalidSizeFormatError: () => InvalidSizeFormatError,
InvalidTeamFormatError: () => InvalidTeamFormatError,
InvalidWriteinDateError: () => InvalidWriteinDateError,
InvalidWriteinDescriptionError: () => InvalidWriteinDescriptionError,
InvalidWriteinFormatError: () => InvalidWriteinFormatError,
MissingSizeForFillError: () => MissingSizeForFillError,
UnrecognizedChatPrefixError: () => UnrecognizedChatPrefixError,
createGradingClient: () => createGradingClient,
createGradingClientWithConfig: () => createGradingClientWithConfig,
createPositionError: () => createPositionError,
default: () => parseChat,
detectContestantType: () => detectContestantType,
detectPropType: () => detectPropType,
inferSportAndLeague: () => inferSportAndLeague,
isFill: () => isFill,
isHandicapLine: () => isHandicapLine,
isHandicapML: () => isHandicapML,
isOrder: () => isOrder,
isPropOU: () => isPropOU,
isPropYN: () => isPropYN,
isSeries: () => isSeries,
isTotalPoints: () => isTotalPoints,
isTotalPointsContestant: () => isTotalPointsContestant,
isWritein: () => isWritein,
mapParseResultToSqlParameters: () => mapParseResultToSqlParameters,
matchesIgnoreCase: () => matchesIgnoreCase,
parseChat: () => parseChat,
parseChatFill: () => parseChatFill,
parseChatOrder: () => parseChatOrder,
parseFillSize: () => parseFillSize,
parseGameNumber: () => parseGameNumber,
parseLine: () => parseLine,
parseOrderSize: () => parseOrderSize,
parseOverUnder: () => parseOverUnder,
parsePeriod: () => parsePeriod,
parsePrice: () => parsePrice,
parseRotationNumber: () => parseRotationNumber,
parseTeam: () => parseTeam,
parseTeams: () => parseTeams,
parseWriteinDate: () => parseWriteinDate,
validateGradingParameters: () => validateGradingParameters,
validatePropFormat: () => validatePropFormat,
validateWriteinDescription: () => validateWriteinDescription
});
module.exports = __toCommonJS(index_exports);
// src/errors/index.ts
var ChatBetParseError = class extends Error {
constructor(message, rawInput, position) {
super(message.trimEnd());
this.name = "ChatBetParseError";
this.rawInput = rawInput;
this.position = position;
}
};
var InvalidChatFormatError = class extends ChatBetParseError {
constructor(rawInput, reason) {
super(`Invalid chat format: ${reason}. Input: "${rawInput}"`, rawInput);
this.name = "InvalidChatFormatError";
}
};
var InvalidContractTypeError = class extends ChatBetParseError {
constructor(rawInput, contractPortion) {
super(
`Unable to determine contract type from: "${contractPortion}". Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidContractTypeError";
}
};
var InvalidPriceFormatError = class extends ChatBetParseError {
constructor(rawInput, priceStr) {
super(
`Invalid USA price format: "${priceStr}". Expected format: +150, -110, -115.5, ev, or even. Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidPriceFormatError";
}
};
var InvalidSizeFormatError = class extends ChatBetParseError {
constructor(rawInput, sizeStr, expectedFormat) {
super(
`Invalid size format: "${sizeStr}". Expected: ${expectedFormat}. Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidSizeFormatError";
}
};
var InvalidLineValueError = class extends ChatBetParseError {
constructor(rawInput, line) {
super(
`Invalid line value: ${line}. Line must be divisible by 0.5. Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidLineValueError";
}
};
var InvalidTeamFormatError = class extends ChatBetParseError {
constructor(rawInput, teamStr, reason) {
super(`Invalid team format: "${teamStr}". ${reason}. Input: "${rawInput}"`, rawInput);
this.name = "InvalidTeamFormatError";
}
};
var InvalidPeriodFormatError = class extends ChatBetParseError {
constructor(rawInput, periodStr) {
super(
`Invalid period format: "${periodStr}". Expected formats: 1st inning, F5, 1H, Q1, etc. Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidPeriodFormatError";
}
};
var InvalidGameNumberError = class extends ChatBetParseError {
constructor(rawInput, gameStr) {
super(
`Invalid game number format: "${gameStr}". Expected formats: G2, GM1, #2, etc. Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidGameNumberError";
}
};
var InvalidRotationNumberError = class extends ChatBetParseError {
constructor(rawInput, rotationStr) {
super(
`Invalid rotation number: "${rotationStr}". Must be a positive integer. Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidRotationNumberError";
}
};
var InvalidPropFormatError = class extends ChatBetParseError {
constructor(rawInput, propStr, availableProps) {
super(
`Invalid prop format: "${propStr}". Available props: ${availableProps.join(", ")}. Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidPropFormatError";
}
};
var InvalidSeriesLengthError = class extends ChatBetParseError {
constructor(rawInput, lengthStr) {
super(
`Invalid series length: "${lengthStr}". Must be a positive integer. Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidSeriesLengthError";
}
};
var MissingSizeForFillError = class extends ChatBetParseError {
constructor(rawInput) {
super(
`Missing size for fill (YG) bet. Fill bets must include size with "=" format. Input: "${rawInput}"`,
rawInput
);
this.name = "MissingSizeForFillError";
}
};
var UnrecognizedChatPrefixError = class extends ChatBetParseError {
constructor(rawInput, prefix) {
super(
`Unrecognized chat prefix: "${prefix}". Chat must be either a chat order (start with "IW" for "i want") or a chat fill (start with "YG", for "you got"). Input: "${rawInput}"`,
rawInput
);
this.name = "UnrecognizedChatPrefixError";
}
};
var AmbiguousContractError = class extends ChatBetParseError {
constructor(rawInput, possibleTypes) {
super(
`Ambiguous contract type. Could be: ${possibleTypes.join(", ")}. Please be more specific. Input: "${rawInput}"`,
rawInput
);
this.name = "AmbiguousContractError";
}
};
var InvalidWriteinDateError = class extends ChatBetParseError {
constructor(rawInput, dateStr, reason) {
super(
`Invalid writein date: "${dateStr}". ${reason}. Expected formats: YYYY-MM-DD, MM/DD/YYYY, YYYY/MM/DD, MM-DD-YYYY, or equivalents without year. Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidWriteinDateError";
}
};
var InvalidWriteinDescriptionError = class extends ChatBetParseError {
constructor(rawInput, description, reason) {
super(
`Invalid writein description: "${description}". ${reason}. Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidWriteinDescriptionError";
}
};
var InvalidWriteinFormatError = class extends ChatBetParseError {
constructor(rawInput, reason) {
super(
`Invalid writein format: ${reason}. Expected format: "IW/YG writein DATE DESCRIPTION [@ price] [= size]". Input: "${rawInput}"`,
rawInput
);
this.name = "InvalidWriteinFormatError";
}
};
function createPositionError(ErrorClass, rawInput, position, ...args) {
const error = new ErrorClass(rawInput, ...args);
error.position = position;
return error;
}
// src/parsers/utils.ts
function parsePrice(priceStr, rawInput) {
const cleaned = priceStr.trim();
if (cleaned.toLowerCase() === "ev" || cleaned.toLowerCase() === "even") {
return 100;
}
const match = cleaned.match(/^([+-])(\d+(?:\.\d+)?)$/);
if (!match) {
throw new InvalidPriceFormatError(rawInput, priceStr);
}
const sign = match[1];
const value = parseFloat(match[2]);
return sign === "+" ? value : -value;
}
function parseOrderSize(sizeStr, rawInput) {
const cleaned = sizeStr.trim();
if (cleaned.startsWith("$")) {
const value2 = parseFloat(cleaned.substring(1));
if (isNaN(value2) || value2 < 0) {
throw new InvalidSizeFormatError(
rawInput,
sizeStr,
"positive dollar amount like $100 or $2.50"
);
}
return { value: value2, format: "dollar" };
}
if (cleaned.toLowerCase().endsWith("k")) {
const value2 = parseFloat(cleaned.slice(0, -1));
if (isNaN(value2) || value2 < 0) {
throw new InvalidSizeFormatError(rawInput, sizeStr, "positive number with k like 4k or 2.5k");
}
return { value: value2 * 1e3, format: "k_notation" };
}
const value = parseFloat(cleaned);
if (isNaN(value) || value < 0) {
throw new InvalidSizeFormatError(rawInput, sizeStr, "positive decimal number like 2.0 or 0.50");
}
return { value, format: "unit" };
}
function parseFillSize(sizeStr, rawInput) {
const cleaned = sizeStr.trim();
if (cleaned.startsWith("$")) {
const value2 = parseFloat(cleaned.substring(1));
if (isNaN(value2) || value2 < 0) {
throw new InvalidSizeFormatError(
rawInput,
sizeStr,
"positive dollar amount like $100 or $2.00"
);
}
return { value: value2, format: "dollar" };
}
if (cleaned.toLowerCase().endsWith("k")) {
const value2 = parseFloat(cleaned.slice(0, -1));
if (isNaN(value2) || value2 < 0) {
throw new InvalidSizeFormatError(rawInput, sizeStr, "positive number with k like 4k or 2.5k");
}
return { value: value2 * 1e3, format: "k_notation" };
}
const value = parseFloat(cleaned);
if (isNaN(value) || value < 0) {
throw new InvalidSizeFormatError(
rawInput,
sizeStr,
"positive decimal number like 2.0 (=$2000) or 0.563 (=$563)"
);
}
return { value: value * 1e3, format: "decimal_thousands" };
}
function parseLine(lineStr, rawInput) {
const value = parseFloat(lineStr);
if (isNaN(value)) {
throw new InvalidLineValueError(rawInput, value);
}
if (value * 2 % 1 !== 0) {
throw new InvalidLineValueError(rawInput, value);
}
return value;
}
function parsePeriod(periodStr, rawInput) {
const cleaned = periodStr.toLowerCase().trim();
if (!cleaned || cleaned === "fg" || cleaned === "full game") {
return { PeriodTypeCode: "M", PeriodNumber: 0 };
}
if (cleaned === "f5" || cleaned === "h1" || cleaned === "1h" || cleaned === "first half" || cleaned === "1st half" || cleaned === "first h" || cleaned === "1st h" || cleaned === "first five" || cleaned === "1st five" || cleaned === "first 5" || cleaned === "1st 5") {
return { PeriodTypeCode: "H", PeriodNumber: 1 };
}
if (cleaned === "f3") {
return { PeriodTypeCode: "H", PeriodNumber: 13 };
}
if (cleaned === "f7") {
return { PeriodTypeCode: "H", PeriodNumber: 17 };
}
if (cleaned === "h2" || cleaned === "2h" || cleaned === "second half" || cleaned === "2nd half" || cleaned === "second h" || cleaned === "2nd h") {
return { PeriodTypeCode: "H", PeriodNumber: 2 };
}
const quarterMatch = cleaned.match(/^(?:(\d+)(?:st|nd|rd|th)?\s*quarter?|q(\d+)|(\d+)q)$/);
if (quarterMatch) {
const qNum = parseInt(quarterMatch[1] || quarterMatch[2] || quarterMatch[3]);
if (qNum >= 1 && qNum <= 4) {
return { PeriodTypeCode: "Q", PeriodNumber: qNum };
}
}
const inningMatch = cleaned.match(/^(?:(\d+)(?:st|nd|rd|th)?\s*inning?|i(\d+)|(\d+)i)$/);
if (inningMatch) {
const iNum = parseInt(inningMatch[1] || inningMatch[2] || inningMatch[3]);
if (iNum >= 1 && iNum <= 15) {
return { PeriodTypeCode: "I", PeriodNumber: iNum };
}
}
const periodMatch = cleaned.match(/^(?:(\d+)(?:st|nd|rd|th)?\s*period?|p(\d+)|(\d+)p)$/);
if (periodMatch) {
const pNum = parseInt(periodMatch[1] || periodMatch[2] || periodMatch[3]);
if (pNum >= 1 && pNum <= 4) {
return { PeriodTypeCode: "P", PeriodNumber: pNum };
}
}
throw new InvalidPeriodFormatError(rawInput, periodStr);
}
function parseGameNumber(gameStr, rawInput) {
const cleaned = gameStr.toLowerCase().trim();
const match = cleaned.match(/^(?:g(?:m)?\s*(\d+)|#\s*(\d+))$/);
if (!match) {
throw new InvalidGameNumberError(rawInput, gameStr);
}
const gameNum = parseInt(match[1] || match[2]);
if (gameNum < 1 || gameNum > 10) {
throw new InvalidGameNumberError(rawInput, gameStr);
}
return gameNum;
}
function parseRotationNumber(rotationStr, rawInput) {
const value = parseInt(rotationStr.trim());
if (isNaN(value) || value < 1 || value > 9999) {
throw new InvalidRotationNumberError(rawInput, rotationStr);
}
return value;
}
function parseTeam(teamStr, rawInput) {
const cleaned = teamStr.trim();
if (!cleaned) {
throw new InvalidTeamFormatError(rawInput, teamStr, "Team name cannot be empty");
}
if (!/^[a-zA-Z0-9\s&\-.']+$/.test(cleaned)) {
throw new InvalidTeamFormatError(rawInput, teamStr, "Team name contains invalid characters");
}
if (cleaned.length > 50) {
throw new InvalidTeamFormatError(rawInput, teamStr, "Team name too long (max 50 characters)");
}
return cleaned;
}
function detectContestantType(contestant) {
if (/^[A-Z]\.\s+[A-Za-z]+/.test(contestant)) {
return "Individual";
}
return void 0;
}
function parseTeams(teamsStr, rawInput) {
const parts = teamsStr.split("/");
if (parts.length === 1) {
return { team1: parseTeam(parts[0], rawInput) };
} else if (parts.length === 2) {
const team1 = parseTeam(parts[0], rawInput);
const team2 = parseTeam(parts[1], rawInput);
if (team1 === team2) {
throw new InvalidTeamFormatError(
rawInput,
teamsStr,
`Team1 and Team2 cannot be the same: "${team1}"`
);
}
return {
team1,
team2
};
} else {
throw new InvalidTeamFormatError(rawInput, teamsStr, 'Too many "/" separators');
}
}
function inferSportAndLeague(rotationNumber) {
if (rotationNumber) {
if (rotationNumber >= 500 && rotationNumber < 600) {
return { sport: "Basketball", league: "NBA" };
}
if (rotationNumber >= 800 && rotationNumber < 900 || rotationNumber >= 9900 && rotationNumber < 1e4) {
return { sport: "Baseball", league: "MLB" };
}
}
return {};
}
function matchesIgnoreCase(text, pattern) {
return text.toLowerCase().includes(pattern.toLowerCase());
}
function parseOverUnder(ouStr, rawInput) {
const match = ouStr.toLowerCase().match(/^([ou])(.+)$/);
if (!match) {
throw new InvalidLineValueError(rawInput, parseFloat(ouStr));
}
const isOver = match[1] === "o";
const lineAndPrice = match[2];
const attachedPriceMatch = lineAndPrice.match(/^(\d+(?:\.\d+)?)([+-]\d+(?:\.\d+)?)$/);
if (attachedPriceMatch) {
const line2 = parseLine(attachedPriceMatch[1], rawInput);
const priceStr = attachedPriceMatch[2];
const attachedPrice = parsePrice(priceStr, rawInput);
return { isOver, line: line2, attachedPrice };
}
const line = parseLine(lineAndPrice, rawInput);
return { isOver, line };
}
var PROP_TYPE_MAP = {
// PropOU (Over/Under) - MUST have line
"passing yards": { standardName: "PassingYards", category: "PropOU" },
passingyards: { standardName: "PassingYards", category: "PropOU" },
rbi: { standardName: "RBI", category: "PropOU" },
rbis: { standardName: "RBI", category: "PropOU" },
rebounds: { standardName: "Rebounds", category: "PropOU" },
rebs: { standardName: "Rebounds", category: "PropOU" },
"receiving yards": { standardName: "ReceivingYards", category: "PropOU" },
receivingyards: { standardName: "ReceivingYards", category: "PropOU" },
ks: { standardName: "Ks", category: "PropOU" },
strikeouts: { standardName: "Ks", category: "PropOU" },
// PropYN (Yes/No) - MAY NOT have line
"first team to score": { standardName: "FirstToScore", category: "PropYN" },
"1st team to score": { standardName: "FirstToScore", category: "PropYN" },
"first to score": { standardName: "FirstToScore", category: "PropYN" },
"to score first": { standardName: "FirstToScore", category: "PropYN" },
"last team to score": { standardName: "LastToScore", category: "PropYN" },
"last to score": { standardName: "LastToScore", category: "PropYN" },
"to score last": { standardName: "LastToScore", category: "PropYN" }
};
function detectPropType(propText) {
const cleanText = propText.toLowerCase().trim();
for (const [keyword, info] of Object.entries(PROP_TYPE_MAP)) {
const regex = new RegExp(`\\b${keyword.replace(/\s+/g, "\\s+")}\\b`);
if (regex.test(cleanText)) {
return info;
}
}
return null;
}
function validatePropFormat(propText, hasLine, rawInput) {
const propInfo = detectPropType(propText);
if (!propInfo) {
throw new InvalidContractTypeError(rawInput, `Unsupported prop type: ${propText}`);
}
if (propInfo.category === "PropOU" && !hasLine) {
throw new InvalidContractTypeError(
rawInput,
`${propInfo.standardName} props require an over/under line (e.g., "o12.5")`
);
}
if (propInfo.category === "PropYN" && hasLine) {
throw new InvalidContractTypeError(
rawInput,
`${propInfo.standardName} props cannot have a line - they are yes/no bets only`
);
}
}
function parseWriteinDate(dateString, rawInput) {
const cleaned = dateString.trim();
if (!cleaned) {
throw new InvalidWriteinDateError(rawInput, dateString, "Date cannot be empty");
}
let parsedDate = null;
const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
const today = /* @__PURE__ */ new Date();
const patterns = [
// Full date patterns with year
/^(\d{4})[/-](\d{1,2})[/-](\d{1,2})$/,
// YYYY/MM/DD or YYYY-MM-DD
/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/,
// MM/DD/YYYY or MM-DD-YYYY
// Date patterns without year
/^(\d{1,2})[/-](\d{1,2})$/
// MM/DD or MM-DD
];
for (const pattern of patterns) {
const match = cleaned.match(pattern);
if (match) {
let year, month, day;
if (match.length === 4) {
if (pattern.source.startsWith("^(\\d{4})")) {
year = parseInt(match[1]);
month = parseInt(match[2]);
day = parseInt(match[3]);
} else {
month = parseInt(match[1]);
day = parseInt(match[2]);
year = parseInt(match[3]);
}
} else {
month = parseInt(match[1]);
day = parseInt(match[2]);
const dateThisYear = new Date(currentYear, month - 1, day);
if (dateThisYear >= today) {
year = currentYear;
} else {
year = currentYear + 1;
}
}
const testDate = new Date(year, month - 1, day);
if (testDate.getFullYear() === year && testDate.getMonth() === month - 1 && testDate.getDate() === day) {
parsedDate = testDate;
break;
} else {
throw new InvalidWriteinDateError(
rawInput,
dateString,
`Invalid calendar date (e.g., February 30th doesn't exist)`
);
}
}
}
if (!parsedDate) {
throw new InvalidWriteinDateError(
rawInput,
dateString,
"Unable to parse date. Supported formats: YYYY-MM-DD, MM/DD/YYYY, YYYY/MM/DD, MM-DD-YYYY, MM/DD, MM-DD"
);
}
return parsedDate;
}
function validateWriteinDescription(description, rawInput) {
const trimmed = description.trim();
if (!trimmed) {
throw new InvalidWriteinDescriptionError(rawInput, description, "Description cannot be empty");
}
if (trimmed.length < 10) {
throw new InvalidWriteinDescriptionError(
rawInput,
description,
`Description must be at least 10 characters long (currently ${trimmed.length})`
);
}
if (trimmed.length > 255) {
throw new InvalidWriteinDescriptionError(
rawInput,
description,
`Description cannot exceed 255 characters (currently ${trimmed.length})`
);
}
if (trimmed.includes("\n") || trimmed.includes("\r")) {
throw new InvalidWriteinDescriptionError(
rawInput,
description,
"Description cannot contain newlines"
);
}
return trimmed;
}
// src/parsers/index.ts
function isWriteinTokens(tokens) {
return "isWritein" in tokens;
}
function tokenizeWritein(parts, chatType, rawInput) {
if (parts.length < 4) {
throw new InvalidWriteinFormatError(
rawInput,
"Writein contracts require at least a date and description"
);
}
if (parts[1].toLowerCase() !== "writein") {
throw new InvalidWriteinFormatError(
rawInput,
"Writein must be separated from date by whitespace"
);
}
let currentIndex = 2;
const dateString = parts[currentIndex];
currentIndex++;
let priceIndex = -1;
let sizeIndex = -1;
for (let i = currentIndex; i < parts.length; i++) {
if (parts[i] === "@" && i + 1 < parts.length) {
priceIndex = i + 1;
}
if (parts[i] === "=" && i + 1 < parts.length) {
sizeIndex = i + 1;
}
}
let descriptionEndIndex = parts.length;
for (let i = currentIndex; i < parts.length; i++) {
if (parts[i] === "@" || parts[i] === "=") {
descriptionEndIndex = i;
break;
}
}
if (descriptionEndIndex <= currentIndex) {
throw new InvalidWriteinFormatError(rawInput, "Writein contracts must include a description");
}
const description = parts.slice(currentIndex, descriptionEndIndex).join(" ");
let price;
if (priceIndex > 0 && priceIndex < parts.length) {
const priceStr = parts[priceIndex];
if (priceStr.toLowerCase().endsWith("k") || priceStr.startsWith("$")) {
price = -110;
if (sizeIndex === -1) {
sizeIndex = priceIndex;
}
} else {
price = parsePrice(priceStr, rawInput);
}
}
let size;
if (sizeIndex > 0 && sizeIndex < parts.length) {
const sizeStr = parts[sizeIndex];
if (chatType === "order") {
const parsed = parseOrderSize(sizeStr, rawInput);
size = parsed.value;
} else {
const parsed = parseFillSize(sizeStr, rawInput);
size = parsed.value;
}
}
if (chatType === "fill" && size === void 0) {
throw new MissingSizeForFillError(rawInput);
}
return {
chatType,
isWritein: true,
dateString,
description,
price: price ?? -110,
// Default price
size,
rawInput
};
}
function tokenizeChat(message) {
const rawInput = message;
let processedMessage = message.trim();
if (processedMessage.toUpperCase().startsWith("IWW ")) {
processedMessage = "IW writein " + processedMessage.substring(4);
} else if (processedMessage.toUpperCase().startsWith("YGW ")) {
processedMessage = "YG writein " + processedMessage.substring(4);
}
processedMessage = processedMessage.replace(/([^=\s])=([^=\s])/g, "$1 = $2");
processedMessage = processedMessage.replace(/([^=\s])=(\s)/g, "$1 = $2");
processedMessage = processedMessage.replace(/(\s)=([^=\s])/g, "$1 = $2");
const parts = processedMessage.split(/\s+/);
if (parts.length < 2) {
throw new InvalidChatFormatError(rawInput, "Message too short");
}
const prefix = parts[0].toUpperCase();
let chatType;
if (prefix === "IW") {
chatType = "order";
} else if (prefix === "YG") {
chatType = "fill";
} else {
throw new UnrecognizedChatPrefixError(rawInput, prefix);
}
if (parts.length >= 2 && parts[1].toLowerCase() === "writein") {
return tokenizeWritein(parts, chatType, rawInput);
}
let currentIndex = 1;
let rotationNumber;
let price;
if (currentIndex < parts.length && /^\d+$/.test(parts[currentIndex])) {
rotationNumber = parseRotationNumber(parts[currentIndex], rawInput);
currentIndex++;
} else if (currentIndex < parts.length && parts[currentIndex] === "abc") {
throw new InvalidRotationNumberError(rawInput, parts[currentIndex]);
}
let priceIndex = -1;
let sizeIndex = -1;
let atSymbolCount = 0;
for (let i = currentIndex; i < parts.length; i++) {
if (parts[i] === "@") {
atSymbolCount++;
if (i + 1 < parts.length) {
priceIndex = i + 1;
}
}
if (parts[i] === "=" && i + 1 < parts.length) {
sizeIndex = i + 1;
}
}
if (atSymbolCount > 1) {
if (chatType === "fill") {
throw new InvalidChatFormatError(
rawInput,
'Expected format for fills is: "YG" [rotation_number] contract ["@" usa_price] "=" fill_size'
);
} else {
throw new InvalidChatFormatError(
rawInput,
'Expected format for orders is: "IW" [rotation_number] contract ["@" usa_price] ["=" unit_size]'
);
}
}
for (let i = currentIndex; i < parts.length; i++) {
if (parts[i] === "@") {
if (i + 1 >= parts.length || parts[i + 1] === "" || parts[i + 1].trim() === "") {
throw new InvalidChatFormatError(rawInput, "No contract details found");
}
}
}
let contractEndIndex = parts.length;
for (let i = currentIndex; i < parts.length; i++) {
if (parts[i] === "@" || parts[i] === "=") {
contractEndIndex = i;
break;
}
}
for (let i = currentIndex; i < contractEndIndex; i++) {
if (/^[+-]\d+(?:\.\d+)?$/.test(parts[i])) {
const value = parseFloat(parts[i].substring(1));
if (value === 0) {
break;
} else if (value > 50 && (value > 100 || value % 1 === 0)) {
contractEndIndex = i;
price = parsePrice(parts[i], rawInput);
break;
}
}
}
if (contractEndIndex <= currentIndex) {
throw new InvalidChatFormatError(rawInput, "No contract details found");
}
let contractText = parts.slice(currentIndex, contractEndIndex).join(" ");
let gameNumber;
const gameNumberAtBeginningMatch = contractText.match(/^(g(?:m)?\s*\d+|#\s*\d+)\s+(.+)$/i);
if (gameNumberAtBeginningMatch) {
const gameNumberStr = gameNumberAtBeginningMatch[1];
const remainingContractText = gameNumberAtBeginningMatch[2];
try {
gameNumber = parseGameNumber(gameNumberStr, rawInput);
contractText = remainingContractText;
} catch (error) {
}
}
if (price === void 0) {
const attachedPriceMatch = contractText.match(/([ou])(\d+(?:\.\d+)?)([+-]\d+(?:\.\d+)?)/i);
if (attachedPriceMatch) {
const attachedPriceStr = attachedPriceMatch[3];
price = parsePrice(attachedPriceStr, rawInput);
contractText = contractText.replace(
attachedPriceMatch[0],
attachedPriceMatch[1] + attachedPriceMatch[2]
);
}
}
if (price === void 0 && priceIndex > 0 && priceIndex < parts.length) {
const priceStr = parts[priceIndex];
if (priceStr.toLowerCase().endsWith("k") || priceStr.startsWith("$")) {
price = -110;
if (sizeIndex === -1) {
sizeIndex = priceIndex;
}
} else {
price = parsePrice(priceStr, rawInput);
}
}
let size;
if (sizeIndex > 0 && sizeIndex < parts.length) {
const sizeStr = parts[sizeIndex];
if (chatType === "order") {
const parsed = parseOrderSize(sizeStr, rawInput);
size = parsed.value;
} else {
const parsed = parseFillSize(sizeStr, rawInput);
size = parsed.value;
}
}
if (chatType === "fill" && size === void 0) {
throw new MissingSizeForFillError(rawInput);
}
return {
chatType,
rotationNumber,
gameNumber,
contractText,
price: price ?? -110,
// Default price
size,
rawInput
};
}
function detectContractType(contractText, rawInput) {
const text = contractText.toLowerCase().trim();
if (text.includes("series")) {
return "Series";
}
if (/\stt\s/i.test(contractText) || /\stt\s*[ou]\d+(?:\.\d+)?(?:[+-]\d+(?:\.\d+)?)?/i.test(contractText) || /^tt\s/i.test(contractText)) {
if (/^tt\s/i.test(contractText.trim())) {
throw new InvalidTeamFormatError(rawInput, "", "Team name cannot be empty");
}
return "TotalPointsContestant";
}
const propInfo = detectPropType(text);
if (propInfo) {
const hasLine = /[ou]\d+(?:\.\d+)?(?:[+-]\d+(?:\.\d+)?)?/i.test(contractText);
validatePropFormat(text, hasLine, rawInput);
return propInfo.category;
}
if (/^[a-zA-Z0-9]+\s+[a-zA-Z\s]+(yards|rbi|rebounds|score|strikeouts|prop)/i.test(contractText)) {
const hasLine = /[ou]\d+(?:\.\d+)?(?:[+-]\d+(?:\.\d+)?)?/i.test(contractText);
validatePropFormat(text, hasLine, rawInput);
}
if (/[a-zA-Z]+(?:\s+[a-zA-Z0-9]+)*\s*[+-]\d+(?:\.\d+)?/i.test(contractText)) {
const spreadMatch = contractText.match(
/([a-zA-Z]+(?:\s+[a-zA-Z0-9]+)*)\s*([+-])(\d+(?:\.\d+)?)/i
);
if (spreadMatch) {
const value = parseFloat(spreadMatch[3]);
if (value === 0) {
return "HandicapContestantML";
}
if (value <= 50 || value % 1 !== 0) {
return "HandicapContestantLine";
} else if (value > 100 && value % 1 === 0) {
return "HandicapContestantML";
} else {
return "HandicapContestantLine";
}
}
}
if (/\//.test(contractText) && /[ou]\d+(?:\.\d+)?(?:[+-]\d+(?:\.\d+)?)?(\s+runs)?/i.test(contractText)) {
return "TotalPoints";
}
if (/^[a-zA-Z\s&.-]+\s+(f5|f3|h1|1h|h2|2h|\d+(?:st|nd|rd|th)?\s*(?:inning|i|quarter|q|period|p))\s+[ou]\d+(?:\.\d+)?(?:[+-]\d+(?:\.\d+)?)?/i.test(
contractText
)) {
return "TotalPoints";
}
if (!contractText.includes("/") && !/\s[ou]\d/i.test(contractText) && !/^[ou]\d/i.test(contractText) || /[a-zA-Z]+\s*[+-]0(?:\s|$)/i.test(contractText) || /\sml\s*$/i.test(contractText) || /\sml\s+/i.test(contractText) || /^[a-zA-Z]+\s+(f5|f3|h1|1h|h2|2h|\d+(?:st|nd|rd|th)?\s*(?:inning|i|quarter|q|period|p))\s*$/i.test(
contractText
)) {
return "HandicapContestantML";
}
throw new InvalidContractTypeError(rawInput, contractText);
}
function parseGameTotal(contractText, rawInput, sport, league, gameNumber) {
const ouMatch = contractText.match(/([ou])(\d+(?:\.\d+)?)(\s+runs)?/i);
if (!ouMatch) {
throw new InvalidContractTypeError(rawInput, contractText);
}
const { isOver, line } = parseOverUnder(ouMatch[1] + ouMatch[2], rawInput);
const hasRunsSuffix = !!ouMatch[3];
const withoutOU = contractText.replace(/\s*[ou]\d+(?:\.\d+)?(?:[+-]\d+(?:\.\d+)?)?(\s+runs)?/i, "").trim();
const { period, match } = parseMatchInfo(withoutOU, rawInput, sport, league, gameNumber);
let finalSport = sport;
if ((hasRunsSuffix || period.PeriodTypeCode === "I") && !sport) {
finalSport = "Baseball";
}
return {
Sport: finalSport,
League: league,
Match: match,
Period: period,
HasContestant: false,
HasLine: true,
ContractSportCompetitionMatchType: "TotalPoints",
Line: line,
IsOver: isOver
};
}
function parseTeamTotal(contractText, rawInput, sport, league, gameNumber) {
const ouMatch = contractText.match(/([ou])(\d+(?:\.\d+)?)(\s+runs)?/i);
if (!ouMatch) {
throw new InvalidContractTypeError(rawInput, contractText);
}
const { isOver, line } = parseOverUnder(ouMatch[1] + ouMatch[2], rawInput);
const hasRunsSuffix = !!ouMatch[3];
let finalSport = sport;
if (hasRunsSuffix && !sport) {
finalSport = "Baseball";
}
const withoutOU = contractText.replace(/\s*[ou]\d+(?:\.\d+)?(?:[+-]\d+(?:\.\d+)?)?(\s+runs)?/i, "").replace(/\s*tt\s*/i, " ").trim();
const { teams, period, match } = parseMatchInfo(
withoutOU,
rawInput,
finalSport,
league,
gameNumber
);
return {
Sport: finalSport,
League: league,
Match: match,
Period: period,
HasContestant: true,
HasLine: true,
ContractSportCompetitionMatchType: "TotalPoints",
Line: line,
IsOver: isOver,
Contestant: teams.team1
};
}
function parseMoneyline(contractText, rawInput, sport, league, gameNumber) {
const cleanedContractText = contractText.replace(/\s*[+-]0(?:\s|$)/i, "").replace(/\s+ml\s*$/i, "").replace(/\s+ml\s+/i, " ").trim();
const { teams, period, match } = parseMatchInfo(
cleanedContractText,
rawInput,
sport,
league,
gameNumber
);
return {
Sport: sport,
League: league,
Match: match,
Period: period,
HasContestant: true,
HasLine: false,
ContractSportCompetitionMatchType: "Handicap",
Contestant: teams.team1,
TiesLose: false
// Default for MLB
};
}
function parseSpread(contractText, rawInput, sport, league, gameNumber) {
const spreadMatch = contractText.match(/^(.*?)\s*([+-]\d+(?:\.\d+)?)$/);
if (!spreadMatch) {
throw new InvalidContractTypeError(rawInput, contractText);
}
const teamPart = spreadMatch[1].trim();
const lineStr = spreadMatch[2];
const sign = lineStr.startsWith("+") ? "+" : "-";
const lineValue = parseFloat(lineStr.substring(1));
const line = sign === "+" ? lineValue : -lineValue;
const { teams, period, match } = parseMatchInfo(teamPart, rawInput, sport, league, gameNumber);
return {
Sport: sport,
League: league,
Match: match,
Period: period,
HasContestant: true,
HasLine: true,
ContractSportCompetitionMatchType: "Handicap",
Contestant: teams.team1,
Line: line
};
}
function parsePropOU(contractText, rawInput, sport, league, gameNumber) {
const ouMatch = contractText.match(/([ou])(\d+(?:\.\d+)?)(\s+runs)?/i);
if (!ouMatch) {
throw new InvalidContractTypeError(rawInput, "PropOU requires an over/under line");
}
const { isOver, line } = parseOverUnder(ouMatch[1] + ouMatch[2], rawInput);
const hasRunsSuffix = !!ouMatch[3];
let finalSport = sport;
if (hasRunsSuffix && !sport) {
finalSport = "Baseball";
}
const withoutOU = contractText.replace(/\s*[ou]\d+(?:\.\d+)?(?:[+-]\d+(?:\.\d+)?)?(\s+runs)?/i, "").trim();
const individualMatch = withoutOU.match(/^([A-Z]\.\s+[A-Za-z]+)\s+(.+)$/);
let contestant;
let propText;
if (individualMatch) {
contestant = individualMatch[1];
propText = individualMatch[2].toLowerCase();
} else {
const parts = withoutOU.trim().split(/\s+/);
if (parts.length < 2) {
throw new InvalidContractTypeError(rawInput, contractText);
}
contestant = parts[0];
propText = parts.slice(1).join(" ").toLowerCase();
}
const propInfo = detectPropType(propText);
if (!propInfo || propInfo.category !== "PropOU") {
throw new InvalidContractTypeError(rawInput, `Invalid PropOU type: ${propText}`);
}
const contestantType = detectContestantType(contestant);
return {
Sport: finalSport,
League: league,
Match: {
Team1: contestant,
DaySequence: gameNumber
},
Period: { PeriodTypeCode: "M", PeriodNumber: 0 },
HasContestant: true,
HasLine: true,
ContractSportCompetitionMatchType: "Prop",
ContestantType: contestantType,
Prop: propInfo.standardName,
Contestant: contestant,
Line: line,
IsOver: isOver
};
}
function parsePropYN(contractText, rawInput, sport, league, gameNumber) {
const propInfo = detectPropType(contractText.toLowerCase());
if (!propInfo || propInfo.category !== "PropYN") {
throw new InvalidContractTypeError(rawInput, `Invalid PropYN type: ${contractText}`);
}
const propPatterns = [
/\s+(1st team to score|first team to score|to score first|first to score)$/i,
/\s+(last team to score|to score last|last to score)$/i
];
let teamAndGameInfo = contractText;
for (const pattern of propPatterns) {
if (pattern.test(contractText)) {
teamAndGameInfo = contractText.replace(pattern, "").trim();
break;
}
}
if (!teamAndGameInfo) {
throw new InvalidContractTypeError(rawInput, contractText);
}
const { teams, match } = parseMatchInfo(teamAndGameInfo, rawInput, sport, league, gameNumber);
const contestantType = detectContestantType(teams.team1);
let isYes;
if (propInfo.standardName === "FirstToScore") {
isYes = true;
} else if (propInfo.standardName === "LastToScore") {
isYes = true;
} else {
isYes = true;
}
return {
Sport: sport,
League: league,
Match: match,
Period: { PeriodTypeCode: "M", PeriodNumber: 0 },
HasContestant: true,
HasLine: false,
ContractSportCompetitionMatchType: "Prop",
ContestantType: contestantType,
Prop: propInfo.standardName,
Contestant: teams.team1,
IsYes: isYes
};
}
function parseSeries(contractText, rawInput, sport, league, gameNumber) {
const seriesSlashMatch = contractText.match(/series\/(\d+)/i);
let seriesLength;
if (seriesSlashMatch) {
seriesLength = parseInt(seriesSlashMatch[1]);
} else {
const outOfMatch = contractText.match(/series\s+out\s+of\s+(\d+)/i);
if (outOfMatch) {
seriesLength = parseInt(outOfMatch[1]);
} else {
const lengthMatch = contractText.match(/(\d+)\s*game\s*series/i);
if (lengthMatch) {
seriesLength = parseInt(lengthMatch[1]);
} else {
const hyphenMatch = contractText.match(/(\d+)-game\s*series/i);
if (hyphenMatch) {
seriesLength = parseInt(hyphenMatch[1]);
} else {
seriesLength = 3;
}
}
}
}
let teamMatch = contractText.match(/([a-zA-Z\s&.]+?)\s*\d+-game\s*series/i);
if (!teamMatch) {
teamMatch = contractText.match(/([a-zA-Z\s&.]+?)\s*series\/\d+/i);
}
if (!teamMatch) {
teamMatch = contractText.match(/([a-zA-Z\s&.]+?)\s*(?:(?:\d+\s*game\s*)?series|series)/i);
}
if (!teamMatch) {
throw new InvalidContractTypeError(rawInput, contractText);
}
const team = teamMatch[1].trim();
return {
Sport: sport,
League: league,
Match: {
Team1: team,
DaySequence: gameNumber
},
SeriesLength: seriesLength,
Contestant: team
};
}
function parseWritein(dateString, description, rawInput) {
const eventDate = parseWriteinDate(dateString, rawInput);
const validatedDescription = validateWriteinDescription(description, rawInput);
return {
EventDate: eventDate,
Description: validatedDescription
};
}
function parseMatchInfo(text, rawInput, _sport, _league, gameNumberFromTokens) {
let workingText = text.trim();
let daySequence = gameNumberFromTokens;
if (daySequence === void 0) {
const gameMatch = workingText.match(/\s+(g(?:m)?\s*\d+|#\s*\d+)\s*/i);
if (gameMatch) {
daySequence = parseGameNumber(gameMatch[1], rawInput);
workingText = workingText.replace(gameMatch[0], " ").trim();
} else {
const invalidGameMatch = workingText.match(
/\s+(g(?:m)?\s*[a-zA-Z]+|#\s*[a-zA-Z]+|g(?:m)?\s*$|#\s*$)\s*/i
);
if (invalidGameMatch) {
throw new InvalidGameNumberError(rawInput, invalidGameMatch[1]);
}
}
}
let period = { PeriodTypeCode: "M", PeriodNumber: 0 };
const periodPatterns = [
/\b(\d+(?:st|nd|rd|th)?\s*(?:inning|i))\b/i,
/\b(f5|f3|f7|h1|1h|h2|2h)\b/i,
/\b(\d+(?:st|nd|rd|th)?\s*(?:quarter|q))\b/i,
/\b(\d+(?:st|nd|rd|th)?\s*(?:period|p))\b/i,
/\b(first\s*(?:half|five|5|inning|i))\b/i,
/\b(second\s*(?:half|h))\b/i
];
for (const pattern of periodPatterns) {
const match2 = workingText.match(pattern);
if (match2) {
period = parsePeriod(match2[1], rawInput);
workingText = workingText.replace(match2[0], " ").trim();
break;
}
}
const teams = parseTeams(workingText, rawInput);
const match = {
Team1: teams.team1,
Team2: teams.team2,
DaySequence: daySequence
};
return { teams, period, match };
}
function parseChatOrder(message) {
const tokens = tokenizeChat(message);
if (tokens.chatType !== "order") {
throw new InvalidChatFormatError(tokens.rawInput, "Expected order (IW) message");
}
if (isWriteinTokens(tokens)) {
const contract2 = parseWritein(tokens.dateString, tokens.description, tokens.rawInput);
return {
chatType: "order",
contractType: "Writein",
contract: contract2,
rotationNumber: void 0,
bet: {
Price: tokens.price,
Size: tokens.size
}
};
}
const contractType = detectContractType(tokens.contractText, tokens.rawInput);
const { sport, league } = inferSportAndLeague(tokens.rotationNumber);
let contract;
switch (contractType) {
case "TotalPoints":
contract = parseGameTotal(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "TotalPointsContestant":
contract = parseTeamTotal(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "HandicapContestantML":
contract = parseMoneyline(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "HandicapContestantLine":
contract = parseSpread(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "PropOU":
contract = parsePropOU(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "PropYN":
contract = parsePropYN(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "Series":
contract = parseSeries(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "Writein":
throw new InvalidContractTypeError(
tokens.rawInput,
"Writein contracts should have been handled earlier"
);
default:
throw new InvalidContractTypeError(tokens.rawInput, tokens.contractText);
}
if (tokens.rotationNumber && "RotationNumber" in contract) {
contract.RotationNumber = tokens.rotationNumber;
}
return {
chatType: "order",
contractType,
contract,
rotationNumber: tokens.rotationNumber,
bet: {
Price: tokens.price,
Size: tokens.size
}
};
}
function parseChatFill(message) {
const tokens = tokenizeChat(message);
if (tokens.chatType !== "fill") {
throw new InvalidChatFormatError(tokens.rawInput, "Expected fill (YG) message");
}
if (isWriteinTokens(tokens)) {
const contract2 = parseWritein(tokens.dateString, tokens.description, tokens.rawInput);
return {
chatType: "fill",
contractType: "Writein",
contract: contract2,
rotationNumber: void 0,
bet: {
ExecutionDtm: /* @__PURE__ */ new Date(),
// Current time for fills
Price: tokens.price,
Size: tokens.size
}
};
}
const contractType = detectContractType(tokens.contractText, tokens.rawInput);
const { sport, league } = inferSportAndLeague(tokens.rotationNumber);
let contract;
switch (contractType) {
case "TotalPoints":
contract = parseGameTotal(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "TotalPointsContestant":
contract = parseTeamTotal(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "HandicapContestantML":
contract = parseMoneyline(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "HandicapContestantLine":
contract = parseSpread(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "PropOU":
contract = parsePropOU(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "PropYN":
contract = parsePropYN(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "Series":
contract = parseSeries(
tokens.contractText,
tokens.rawInput,
sport,
league,
tokens.gameNumber
);
break;
case "Writein":
throw new InvalidContractTypeError(
tokens.rawInput,
"Writein contracts should have been handled earlier"
);
default:
throw new InvalidContractTypeError(tokens.rawInput, tokens.contractText);
}
if (tokens.rotationNumber && "RotationNumber" in contract) {
contract.RotationNumber = tokens.rotationNumber;
}
return {
chatType: "fill",
contractType,
contract,
rotationNumber: tokens.rotationNumber,
bet: {
ExecutionDtm: /* @__PURE__ */ new Date(),
// Current time for fills
Price: tokens.price,
Size: tokens.size
}
};
}
function parseChat(message) {
const trimmed = message.trim();
const upperTrimmed = trimmed.toUpperCase();
if (upperTrimmed.startsWith("IW") || upperTrimmed.startsWith("IWW")) {
return parseChatOrder(message);
} else if (upperTrimmed.startsWith("YG") || upperTrimmed.startsWith("YGW")) {
return parseChatFill(message);
} else {
throw new UnrecognizedChatPrefixError(message, trimmed.split(/\s+/)[0] || "");
}
}
// src/types/index.ts
function isOrder(result) {
return result.chatType === "order";
}
function isFill(result) {
return result.chatType === "fill";
}
function isTotalPoints(contract) {
return "ContractSportCompetitionMatchType" in contract && contract.ContractSportCompetitionMatchType === "TotalPoints" && !contract.HasContestant;
}
function isTotalPointsContestant(contract) {
return "ContractSportCompetitionMatchType" in contract && contract.ContractSportCompetitionMatchType === "TotalPoints" && contract.HasContestant;
}
function isHandicapML(contract) {
return "ContractSportCompetitionMatchType" in contract && contract.ContractSportCompetitionMatchType === "Handicap" && contract.HasContestant && !contract.HasLine;
}
function isHandicapLine(contract) {
return "ContractSportCompetitionMatchType" in contract && contract.ContractSportCompetitionMatchType === "Handicap" && contract.HasContestant && contract.HasLine;
}
function isPropOU(contract) {
return "ContractSportCompetitionMatchType" in contract && contract.ContractSportCompetitionMatchType === "Prop" && contract.HasContestant && contract.HasLine;
}
function isPropYN(contract) {
return "ContractSportCompetitionMatchType" in contract && contract.ContractSportCompetitionMatchType === "Prop" && contract.HasContestant && !contract.HasLine;
}
function isSeries(contract) {
return "SeriesLength" in contract;
}
function isWritein(contract) {
return "EventDate" in contract && "Description" in contract;
}
// src/grading/client.ts
var sql = __toESM(require("mssql"));
// src/grading/types.ts
var GradingError = class extends Error {
constructor(message, originalError) {
super(message);
this.originalError = originalError;
this.name = "GradingError";
}
};
var GradingConnectionError = class extends GradingError {
constructor(message, originalError) {
super(message, originalError);
this.name = "GradingConnectionError";
}
};
var GradingQueryError = class extends GradingError {
constructor(message, originalError) {
super(message, originalError);
this.name = "GradingQueryError";
}
};
var GradingDataError = class extends GradingError {
constructor(message) {
super(message);
this.name = "GradingDataError";
}
};
// src/grading/mappers.ts
function mapParseResultToSqlParameters(result, options) {
const contract = result.contract;
let matchScheduledDate;
if (options?.matchScheduledDate) {
matchScheduledDate = options.matchScheduledDate;
} else {
throw new GradingDataError("MatchScheduledDate must be provided in options when grading");
}
const contractType = result.contractType;
const matchInfo = contractType !== "Writein" ? extractMatchInfo(contract) : null;
const periodInfo = extractPeriodInfo(c