chat-bet-parse
Version:
TypeScript package for parsing sports betting contract text into structured data types compatible with SQL Server stored procedures
1,416 lines (1,407 loc) • 51.1 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,
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,
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,
validateGradingParameters: () => validateGradingParameters,
validatePropFormat: () => validatePropFormat
});
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";
}
};
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 === "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)?(\d+)|#(\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) {
return {
team1: parseTeam(parts[0], rawInput),
team2: parseTeam(parts[1], rawInput)
};
} 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 line = parseLine(match[2], 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`
);
}
}
// src/parsers/index.ts
function tokenizeChat(message) {
const rawInput = message;
const parts = message.trim().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);
}
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");
}
const contractText = parts.slice(currentIndex, contractEndIndex).join(" ");
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,
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]/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+)?/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+)?/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+)?(\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+)?/i.test(
contractText
)) {
return "TotalPoints";
}
if (!text.includes("/") && !text.includes("o") && !text.includes("u") || /[a-zA-Z]+\s*[+-]0(?:\s|$)/i.test(contractText)) {
return "HandicapContestantML";
}
throw new InvalidContractTypeError(rawInput, contractText);
}
function parseGameTotal(contractText, rawInput, sport, league) {
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+)?(\s+runs)?/i, "").trim();
const { period, match } = parseMatchInfo(withoutOU, rawInput, sport, league);
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) {
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+)?(\s+runs)?/i, "").replace(/\s*tt\s*/i, " ").trim();
const { teams, period, match } = parseMatchInfo(withoutOU, rawInput, finalSport, league);
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) {
const cleanedContractText = contractText.replace(/\s*[+-]0(?:\s|$)/i, "").trim();
const { teams, period, match } = parseMatchInfo(cleanedContractText, rawInput, sport, league);
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) {
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);
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) {
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+)?(\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
},
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) {
const parts = contractText.trim().split(/\s+/);
if (parts.length < 2) {
throw new InvalidContractTypeError(rawInput, contractText);
}
const team = parts[0];
const propText = parts.slice(1).join(" ").toLowerCase();
const propInfo = detectPropType(propText);
if (!propInfo || propInfo.category !== "PropYN") {
throw new InvalidContractTypeError(rawInput, `Invalid PropYN type: ${propText}`);
}
const contestantType = detectContestantType(team);
let isYes;
if (propInfo.standardName === "FirstToScore") {
isYes = true;
} else if (propInfo.standardName === "LastToScore") {
isYes = true;
} else {
isYes = true;
}
return {
Sport: sport,
League: league,
Match: {
Team1: team
},
Period: { PeriodTypeCode: "M", PeriodNumber: 0 },
HasContestant: true,
HasLine: false,
ContractSportCompetitionMatchType: "Prop",
ContestantType: contestantType,
Prop: propInfo.standardName,
Contestant: team,
IsYes: isYes
};
}
function parseSeries(contractText, rawInput, sport, league) {
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
},
SeriesLength: seriesLength,
Contestant: team
};
}
function parseMatchInfo(text, rawInput, _sport, _league) {
let workingText = text.trim();
let daySequence;
const gameMatch = workingText.match(/\s+(g(?:m)?\d+|#\d+)\s*/i);
if (gameMatch) {
daySequence = parseGameNumber(gameMatch[1], rawInput);
workingText = workingText.replace(gameMatch[0], " ").trim();
} else {
const invalidGameMatch = workingText.match(/\s+(g(?:m)?[a-zA-Z]+|#[a-zA-Z]+|g(?:m)?$|#$)\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|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");
}
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);
break;
case "TotalPointsContestant":
contract = parseTeamTotal(tokens.contractText, tokens.rawInput, sport, league);
break;
case "HandicapContestantML":
contract = parseMoneyline(tokens.contractText, tokens.rawInput, sport, league);
break;
case "HandicapContestantLine":
contract = parseSpread(tokens.contractText, tokens.rawInput, sport, league);
break;
case "PropOU":
contract = parsePropOU(tokens.contractText, tokens.rawInput, sport, league);
break;
case "PropYN":
contract = parsePropYN(tokens.contractText, tokens.rawInput, sport, league);
break;
case "Series":
contract = parseSeries(tokens.contractText, tokens.rawInput, sport, league);
break;
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");
}
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);
break;
case "TotalPointsContestant":
contract = parseTeamTotal(tokens.contractText, tokens.rawInput, sport, league);
break;
case "HandicapContestantML":
contract = parseMoneyline(tokens.contractText, tokens.rawInput, sport, league);
break;
case "HandicapContestantLine":
contract = parseSpread(tokens.contractText, tokens.rawInput, sport, league);
break;
case "PropOU":
contract = parsePropOU(tokens.contractText, tokens.rawInput, sport, league);
break;
case "PropYN":
contract = parsePropYN(tokens.contractText, tokens.rawInput, sport, league);
break;
case "Series":
contract = parseSeries(tokens.contractText, tokens.rawInput, sport, league);
break;
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();
if (trimmed.toUpperCase().startsWith("IW")) {
return parseChatOrder(message);
} else if (trimmed.toUpperCase().startsWith("YG")) {
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;
}
// 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 matchInfo = extractMatchInfo(contract);
const periodInfo = extractPeriodInfo(contract);
const baseParams = {
MatchScheduledDate: matchScheduledDate,
Contestant1: matchInfo.Contestant1,
Contestant2: matchInfo.Contestant2,
DaySequence: matchInfo.DaySequence,
PeriodTypeCode: periodInfo.PeriodTypeCode,
PeriodNumber: periodInfo.PeriodNumber,
TiesLose: false
// Default assumption
};
const { contractType } = result;
let contractParams = {};
switch (contractType) {
case "TotalPoints":
contractParams = mapTotalPoints(contract);
break;
case "TotalPointsContestant":
contractParams = mapTotalPointsContestant(contract);
break;
case "HandicapContestantML":
contractParams = mapHandicapML(contract);
break;
case "HandicapContestantLine":
contractParams = mapHandicapLine(contract);
break;
case "PropOU":
contractParams = mapPropOU(contract);
break;
case "PropYN":
contractParams = mapPropYN(contract);
break;
case "Series":
contractParams = mapSeries(contract);
break;
default:
throw new GradingDataError(`Unsupported contract type: ${contractType}`);
}
return {
...baseParams,
ContractType: contractType,
...contractParams
};
}
function extractMatchInfo(contract) {
return {
Contestant1: contract.Match.Team1,
Contestant2: contract.Match.Team2,
DaySequence: contract.Match.DaySequence || void 0
};
}
function extractPeriodInfo(contract) {
if ("SeriesLength" in contract) {
return {
PeriodTypeCode: "M",
// Default to match
PeriodNumber: 0
};
}
if ("Period" in contract) {
const matchContract = contract;
return {
PeriodTypeCode: matchContract.Period.PeriodTypeCode,
PeriodNumber: matchContract.Period.PeriodNumber
};
}
return {
PeriodTypeCode: "FG",
PeriodNumber: 1
};
}
function mapTotalPoints(contract) {
if (!("ContractSportCompetitionMatchType" in contract) || contract.ContractSportCompetitionMatchType !== "TotalPoints" || contract.HasContestant !== false) {
throw new GradingDataError("Invalid TotalPoints contract structure");
}
return {
Line: contract.Line,
IsOver: contract.IsOver
};
}
function mapTotalPointsContestant(contract) {
if (!("ContractSportCompetitionMatchType" in contract) || contract.ContractSportCompetitionMatchType !== "TotalPoints" || contract.HasContestant !== true) {
throw new GradingDataError("Invalid TotalPointsContestant contract structure");
}
return {
Line: contract.Line,
IsOver: contract.IsOver,
SelectedContestant: contract.Contestant
};
}
function mapHandicapML(contract) {
if (!("ContractSportCompetitionMatchType" in contract) || contract.ContractSportCompetitionMatchType !== "Handicap" || contract.HasContestant !== true || contract.HasLine !== false) {
throw new GradingDataError("Invalid HandicapContestantML contract structure");
}
return {
SelectedContestant: contract.Contestant,
TiesLose: contract.TiesLose
};
}
function mapHandicapLine(contract) {
if (!("ContractSportCompetitionMatchType" in contract) || contract.ContractSportCompetitionMatchType !== "Handicap" || contract.HasContestant !== true || contract.HasLine !== true) {
throw new GradingDataError("Invalid HandicapContestantLine contract structure");
}
return {
SelectedContestant: contract.Contestant,
Line: contract.Line
};
}
function mapPropOU(contract) {
if (!("ContractSportCompetitionMatchType" in contract) || contract.ContractSportCompetitionMatchType !== "Prop" || contract.HasContestant !== true || contract.HasLine !== true) {
throw new GradingDataError("Invalid PropOU contract structure");
}
return {
SelectedContestant: contract.Contestant,
Line: contract.Line,
IsOver: contract.IsOver,
Prop: contract.Prop,
PropContestantType: contract.ContestantType
};
}
function mapPropYN(contract) {
if (!("ContractSportCompetitionMatchType" in contract) || contract.ContractSportCompetitionMatchType !== "Prop" || contract.HasContestant !== true || contract.HasLine !== false) {
throw new GradingDataError("Invalid PropYN contract structure");
}
return {
SelectedContestant: contract.Contestant,
IsYes: contract.IsYes,
Prop: contract.Prop,
PropContestantType: contract.ContestantType
};
}
function mapSeries(contract) {
if (!("SeriesLength" in contract)) {
throw new GradingDataError("Invalid Series contract structure");
}
const seriesContract = contract;
return {
SeriesLength: seriesContract.SeriesLength,
SelectedContestant: seriesContract.Contestant
};
}
function validateGradingParameters(params) {
if (!params.MatchScheduledDate) {
throw new GradingDataError("MatchScheduledDate is required");
}
if (!params.Contestant1) {
throw new GradingDataError("Contestant1 is required");
}
if (!params.ContractType) {
throw new GradingDataError("ContractType is required");
}
switch (params.ContractType) {
case "TotalPoints":
case "TotalPointsContestant":
if (params.Line === void 0 || params.IsOver === void 0) {
throw new GradingDataError(`${params.ContractType} requires Line and IsOver`);
}
if (params.ContractType === "TotalPointsContestant" && !params.SelectedContestant) {
throw new GradingDataError("TotalPointsContestant requires SelectedContestant");
}
break;
case "HandicapContestantML":
if (!params.SelectedContestant) {
throw new GradingDataError("HandicapContestantML requires SelectedContestant");
}
break;
case "HandicapContestantLine":
if (!params.SelectedContestant || params.Line === void 0) {
throw new GradingDataError("HandicapContestantLine requires SelectedContestant and Line");
}
break;
case "PropOU":
if (!params.SelectedContestant || params.Line === void 0 || params.IsOver === void 0 || !params.Prop) {
throw new GradingDataError("PropOU requires SelectedContestant, Line, IsOver, and Prop");
}
break;
case "PropYN":
if (!params.SelectedContestant || params.IsYes === void 0 || !params.Prop) {
throw new GradingDataError("PropYN requires SelectedContestant, IsYes, and Prop");
}
break;
case "Series":
if (!params.SelectedContestant || !params.SeriesLength) {
throw new GradingDataError("Series requires SelectedContestant and SeriesLength");
}
break;
default:
throw new GradingDataError(`Unknown contract type: ${params.ContractType}`);
}
}
// src/grading/client.ts
var ChatBetGradingClient = class {
constructor(config) {
this.pool = null;
this.isPoolConnected = false;
if (typeof config === "string") {
this.connectionString = config;
} else {
this.connectionString = config.connectionString;
}
}
/**
* Test the database connection
* This will be called automatically on first use, but can be called explicitly
*/
async testConnection() {
try {
if (!this.pool) {
this.pool = new sql.ConnectionPool(this.connectionString);
}
if (!this.isPoolConnected) {
await this.pool.connect();
this.isPoolConnected = true;
}
const request = this.pool.request();
await request.query("SELECT 1 as test");
} catch (error) {
this.isPoolConnected = false;
const message = error instanceof Error ? error.message : "Unknown connection error";
throw new GradingConnectionError(
`Failed to connect to SQL Server: ${message}`,
error instanceof Error ? error : void 0
);
}
}
/**
* Get connection status
*/
isConnected() {
return this.isPoolConnected && this.pool?.connected === true;
}
/**
* Grade a parsed chat result
*/
async grade(result, options) {
try {
if (!this.isConnected()) {
await this.testConnection();
}
const sqlParams = mapParseResultToSqlParameters(result, options);
validateGradingParameters(sqlParams);
const grade = await this.executeGradingFunction(sqlParams);
return grade;
} catch (error) {
if (error instanceof GradingError) {
throw error;
}
const message = error instanceof Error ? error.message : "Unknown grading error";
throw new GradingQueryError(
`Failed to grade contract: ${message}`,
error instanceof Error ? error : void 0
);
}
}
/**
* Close the database connection and clean up resources
*/
async close() {
try {
if (this.pool && this.isPoolConnected) {
await this.pool.close();
this.isPoolConnected = false;
}
} catch (error) {
console.warn("Warning: Error closing database connection:", error);
}
}
// ==============================================================================
// PRIVATE METHODS
// ==============================================================================
/**
* Execute the SQL Server grading function with parameters
*/
async executeGradingFunction(params) {
if (!this.pool) {
throw new GradingConnectionError("Database connection not established");
}
try {
const request = this.pool.request();
request.input("MatchScheduledDate", sql.Date, params.MatchScheduledDate);
request.input("Contestant1", sql.Char(50), params.Contestant1);
request.input("Contestant2", sql.Char(50), params.Contestant2 ?? null);
request.input("DaySequence", sql.TinyInt, params.DaySequence ?? null);
request.input("MatchContestantType", sql.Char(10), params.MatchContestantType ?? null);
request.input("PeriodTypeCode", sql.Char(2), params.PeriodTypeCode);
request.input("PeriodNumber", sql.TinyInt, params.PeriodNumber);
request.input("ContractType", sql.VarChar(30), params.ContractType);
request.input("Line", sql.Decimal(5, 2), params.Line ?? null);
request.input("IsOver", sql.Bit, params.IsOver ?? null);
request.input("SelectedContestant", sql.Char(50), params.SelectedContestant ?? null);
request.input("TiesLose", sql.Bit, params.TiesLose ?? false);
request.input("Prop", sql.VarChar(20), params.Prop ?? null);
request.input("PropContestantType", sql.Char(10), params.PropContestantType ?? null);
request.input("IsYes", sql.Bit, params.IsYes ?? null);
request.input("SeriesLength", sql.TinyInt, params.SeriesLength ?? null);
const result = await request.query(`
SELECT dbo.Contract_CALCULATE_Grade_fn(
@MatchScheduledDate,
@Contestant1,
@Contestant2,
@DaySequence,
@MatchContestantType,
@PeriodTypeCode,
@PeriodNumber,
@ContractType,
@Line,
@IsOver,
@SelectedContestant,
@TiesLose,
@Prop,
@PropContestantType,
@IsYes,
@SeriesLength
) as Grade
`);
if (!result.recordset || result.recordset.length === 0) {
throw new GradingQueryError("No result returned from grading function");
}
const grade = result.recordset[0].Grade;
if (!["W", "L", "P", "?"].includes(grade)) {
throw new GradingQueryError(`Invalid grade result: ${grade}`);
}
return grade;
} catch (error) {
if (error instanceof GradingError) {
throw error;
}
let message = error instanceof Error ? error.message : `Unknown SQL execution error: ${error}`;
const conversionErrorPattern = /^Conversion failed when converting the (?:nvarchar|varchar) value '(.+?)' to data type tinyint\.?$/;
const match = message.match(conversionErrorPattern);
if (match) {
message = match[1].trim();
}
throw new GradingQueryError(
`SQL execution failed: ${message}`,
error instanceof Error ? error : void 0
);
}
}
};
function createGradingClient(connectionString) {
return new ChatBetGradingClient(connectionString);
}
function createGradingClientWithConfig(config) {
return new ChatBetGradingClient(config);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AmbiguousContractError,
ChatBetGradingCli