UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

981 lines (980 loc) 40.2 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /** * # Summarizer Evaluator * * This class evaluates a summarizer definition against data to produce * natural language phrases describing the data. * * ## Usage * * ```typescript * const evaluator = new SummarizerEvaluator(); * const result = evaluator.evaluate(summarizer, data, options); * * console.log(result.phrases); * // ["has extremely high health (500 HP)", "can fly and teleport"] * * console.log(result.asCompleteSentence); * // "This entity has extremely high health (500 HP), can fly and teleport." * ``` */ const Log_1 = __importDefault(require("../core/Log")); const Utilities_1 = __importDefault(require("../core/Utilities")); const ICondition_1 = require("./ICondition"); const ISummarizerToken_1 = require("./ISummarizerToken"); /** * Evaluates summarizer definitions against data objects. * * ## Design Philosophy * * Summarizers are **authored**, not dynamically generated. When creating a * summarizer (by AI or human), the author looks at form samples and bakes in * meaningful comparisons as literal text (e.g., "stronger than an Iron Golem"). * * This evaluator focuses on: * - Evaluating conditions to select which phrases/tokens to display * - Formatting values with units and humanification * - Combining phrases with proper grammar * * It does NOT maintain a catalog of reference values - comparisons are * baked into the summarizer definition as literal text. */ class SummarizerEvaluator { formDefinition; currentData; options; /** * Create a new SummarizerEvaluator. */ constructor() { this.options = {}; } /** * Evaluate a summarizer against data to produce natural language phrases. * * @param summarizer The summarizer definition * @param data The data object to summarize * @param formDefinition Optional form definition for sample lookup * @param options Evaluation options * @returns Result containing phrases and formatted output * * @example * const result = evaluator.evaluate( * healthSummarizer, * { max: 500, value: 500 }, * healthForm * ); * // result.phrases = ["has god-tier health (500 HP)"] * // result.asCompleteSentence = "This entity has god-tier health (500 HP)." */ evaluate(summarizer, data, formDefinition, options) { this.formDefinition = formDefinition; this.currentData = data; this.options = options || {}; const debug = this.options.debug ? { includedPhrases: [], excludedPhrases: [], truncatedPhrases: [], } : undefined; // Evaluate all phrases const evaluatedPhrases = []; for (const phrase of summarizer.phrases) { const phraseId = phrase.id || `phrase_${evaluatedPhrases.length}`; // Check category filters if (this.options.includeCategories && phrase.category) { if (!this.options.includeCategories.includes(phrase.category)) { debug?.excludedPhrases.push(phraseId); continue; } } if (this.options.excludeCategories && phrase.category) { if (this.options.excludeCategories.includes(phrase.category)) { debug?.excludedPhrases.push(phraseId); continue; } } // Check visibility conditions if (phrase.visibility && !this.checkConditions(phrase.visibility, data)) { debug?.excludedPhrases.push(phraseId); continue; } // Evaluate the phrase tokens const text = this.evaluateTokens(phrase.tokens, data); if (text.trim().length > 0) { const priority = phrase.priority ?? 2; // Check priority filter if (this.options.maxPriority !== undefined && priority > this.options.maxPriority) { debug?.excludedPhrases.push(phraseId); continue; } evaluatedPhrases.push({ text: text.trim(), priority, id: phraseId, }); debug?.includedPhrases.push(phraseId); } else { debug?.excludedPhrases.push(phraseId); } } // Sort by priority (lower = more important) evaluatedPhrases.sort((a, b) => a.priority - b.priority); // Apply max phrases limit let phrases = evaluatedPhrases.map((p) => p.text); if (this.options.maxPhrases !== undefined && phrases.length > this.options.maxPhrases) { const truncated = evaluatedPhrases.slice(this.options.maxPhrases); debug?.truncatedPhrases.push(...truncated.map((p) => p.id || "unknown")); phrases = phrases.slice(0, this.options.maxPhrases); } // Build result const asSentence = this.joinPhrases(phrases); const asCompleteSentence = phrases.length > 0 ? `This entity ${asSentence}.` : ""; return { phrases, asSentence, asCompleteSentence, debug, }; } /** * Evaluate a summarizer against data with structured token output for rich rendering. * * This method is similar to evaluate() but returns structured data that preserves * token boundaries and effects, allowing the UI to render tokens with styling. * * @param summarizer The summarizer definition * @param data The data object to summarize * @param formDefinition Optional form definition for sample lookup * @param options Evaluation options * @returns Result containing structured tokens with effects * * @example * const result = evaluator.evaluateWithEffects( * healthSummarizer, * { max: 500, value: 500 }, * healthForm * ); * // result.evaluatedPhrases[0].tokens = [ * // { text: "has ", effects: undefined }, * // { text: "god-tier health", effects: { emphasis: "strong", sentiment: "positive" } }, * // { text: " (", effects: { emphasis: "subtle" } }, * // { text: "500 HP", effects: { emphasis: "strong", role: "value" } }, * // { text: ")", effects: { emphasis: "subtle" } } * // ] */ evaluateWithEffects(summarizer, data, formDefinition, options) { this.formDefinition = formDefinition; this.currentData = data; this.options = options || {}; const debug = this.options.debug ? { includedPhrases: [], excludedPhrases: [], truncatedPhrases: [], } : undefined; // Evaluate all phrases with structured output const evaluatedPhrases = []; for (const phrase of summarizer.phrases) { const phraseId = phrase.id || `phrase_${evaluatedPhrases.length}`; // Check category filters if (this.options.includeCategories && phrase.category) { if (!this.options.includeCategories.includes(phrase.category)) { debug?.excludedPhrases.push(phraseId); continue; } } if (this.options.excludeCategories && phrase.category) { if (this.options.excludeCategories.includes(phrase.category)) { debug?.excludedPhrases.push(phraseId); continue; } } // Check visibility conditions if (phrase.visibility && !this.checkConditions(phrase.visibility, data)) { debug?.excludedPhrases.push(phraseId); continue; } // Check priority filter const priority = phrase.priority ?? 2; if (this.options.maxPriority !== undefined && priority > this.options.maxPriority) { debug?.excludedPhrases.push(phraseId); continue; } // Evaluate the phrase tokens with effects const tokens = this.evaluateTokensWithEffects(phrase.tokens, data); const plainText = tokens.map((t) => t.text).join(""); if (plainText.trim().length > 0) { evaluatedPhrases.push({ id: phraseId, category: phrase.category, priority, tokens, plainText: plainText.trim(), }); debug?.includedPhrases.push(phraseId); } else { debug?.excludedPhrases.push(phraseId); } } // Sort by priority (lower = more important) evaluatedPhrases.sort((a, b) => a.priority - b.priority); // Apply max phrases limit let resultPhrases = evaluatedPhrases; if (this.options.maxPhrases !== undefined && resultPhrases.length > this.options.maxPhrases) { const truncated = resultPhrases.slice(this.options.maxPhrases); debug?.truncatedPhrases.push(...truncated.map((p) => p.id || "unknown")); resultPhrases = resultPhrases.slice(0, this.options.maxPhrases); } // Build result const phrases = resultPhrases.map((p) => p.plainText); const asSentence = this.joinPhrases(phrases); const asCompleteSentence = phrases.length > 0 ? `This entity ${asSentence}.` : ""; return { evaluatedPhrases: resultPhrases, phrases, asSentence, asCompleteSentence, debug, }; } /** * Join phrases into a grammatically correct sentence. */ joinPhrases(phrases) { if (phrases.length === 0) { return ""; } if (phrases.length === 1) { return phrases[0]; } if (phrases.length === 2) { return `${phrases[0]} and ${phrases[1]}`; } const allButLast = phrases.slice(0, -1).join(", "); return `${allButLast}, and ${phrases[phrases.length - 1]}`; } /** * Evaluate an array of tokens and return structured output with effects. * * This method preserves token boundaries and effects for rich rendering. */ evaluateTokensWithEffects(tokens, data) { const result = []; for (const token of tokens) { // Check token visibility if (token.visibility && !this.checkConditions(token.visibility, data)) { continue; } const evaluatedTokens = this.evaluateTokenWithEffects(token, data); result.push(...evaluatedTokens); } return result; } /** * Evaluate a single token and return structured output with effects. * * Some token types (like switch, list, template) recursively evaluate * child tokens and inherit effects from the parent. */ evaluateTokenWithEffects(token, data) { const baseEffects = token.effects; switch (token.type) { case ISummarizerToken_1.SummarizerTokenType.literal: case "literal": { const text = token.text; if (text) { return [{ text, effects: baseEffects }]; } return []; } case ISummarizerToken_1.SummarizerTokenType.value: case "value": { const text = this.evaluateValueToken(token, data); if (text) { // Value tokens default to "value" role if no effects specified const effects = baseEffects || { role: "value" }; return [{ text, effects }]; } return []; } case ISummarizerToken_1.SummarizerTokenType.switch: case "switch": { const switchToken = token; for (const switchCase of switchToken.cases) { if (this.checkConditions(switchCase.conditions, data)) { const childTokens = this.evaluateTokensWithEffects(switchCase.tokens, data); // Apply parent effects to children that don't have their own return this.applyEffectsToChildren(childTokens, baseEffects); } } if (switchToken.default) { const childTokens = this.evaluateTokensWithEffects(switchToken.default, data); return this.applyEffectsToChildren(childTokens, baseEffects); } return []; } case ISummarizerToken_1.SummarizerTokenType.list: case "list": { const listToken = token; const visibleItems = []; for (const item of listToken.items) { if (item.visibility && !this.checkConditions(item.visibility, data)) { continue; } const itemTokens = this.evaluateTokensWithEffects(item.tokens, data); const itemText = itemTokens.map((t) => t.text).join(""); if (itemText.trim().length > 0) { visibleItems.push(itemTokens); } } if (visibleItems.length === 0) { if (listToken.emptyText) { return [{ text: listToken.emptyText, effects: baseEffects }]; } return []; } // Build the list with separators const result = []; // Add prefix if (listToken.prefix) { result.push(...this.evaluateTokensWithEffects(listToken.prefix, data)); } for (let i = 0; i < visibleItems.length; i++) { result.push(...visibleItems[i]); if (i < visibleItems.length - 1) { let sep; if (visibleItems.length === 2) { sep = listToken.twoItemSeparator ?? " and "; } else if (i === visibleItems.length - 2) { sep = listToken.finalSeparator ?? ", and "; } else { sep = listToken.separator ?? ", "; } result.push({ text: sep, effects: { emphasis: "subtle" } }); } } // Add suffix if (listToken.suffix) { result.push(...this.evaluateTokensWithEffects(listToken.suffix, data)); } return this.applyEffectsToChildren(result, baseEffects); } case ISummarizerToken_1.SummarizerTokenType.template: case "template": { const templateToken = token; // For templates, we need to parse the template and insert evaluated values const result = []; const template = templateToken.template; const parts = template.split(/(\{[^}]+\})/); for (const part of parts) { if (part.startsWith("{") && part.endsWith("}")) { const key = part.slice(1, -1); const valueTokens = templateToken.values[key]; if (valueTokens) { result.push(...this.evaluateTokensWithEffects(valueTokens, data)); } } else if (part.length > 0) { result.push({ text: part, effects: undefined }); } } return this.applyEffectsToChildren(result, baseEffects); } case ISummarizerToken_1.SummarizerTokenType.plural: case "plural": { const text = this.evaluatePluralToken(token, data); if (text) { return [{ text, effects: baseEffects }]; } return []; } case ISummarizerToken_1.SummarizerTokenType.sample: case "sample": { const text = this.evaluateSampleToken(token, data); if (text) { return [{ text, effects: baseEffects }]; } return []; } case ISummarizerToken_1.SummarizerTokenType.unit: case "unit": { const text = this.evaluateUnitToken(token, data); if (text) { // Unit tokens get "value" role for the number and "unit" role overall const effects = baseEffects || { role: "value" }; return [{ text, effects }]; } return []; } case ISummarizerToken_1.SummarizerTokenType.exists: case "exists": { const existsToken = token; const value = this.getFieldValue(existsToken.field, data); let isDefined = value !== undefined && value !== null; if (isDefined && existsToken.treatEmptyAsUndefined) { if (typeof value === "string" && value.length === 0) { isDefined = false; } else if (Array.isArray(value) && value.length === 0) { isDefined = false; } else if (typeof value === "object" && Object.keys(value).length === 0) { isDefined = false; } } if (isDefined) { const childTokens = this.evaluateTokensWithEffects(existsToken.whenDefined, data); return this.applyEffectsToChildren(childTokens, baseEffects); } else if (existsToken.whenUndefined) { const childTokens = this.evaluateTokensWithEffects(existsToken.whenUndefined, data); return this.applyEffectsToChildren(childTokens, baseEffects); } return []; } case ISummarizerToken_1.SummarizerTokenType.group: case "group": { const childTokens = this.evaluateTokensWithEffects(token.tokens, data); return this.applyEffectsToChildren(childTokens, baseEffects); } case ISummarizerToken_1.SummarizerTokenType.conjunction: case "conjunction": { const conjunctionToken = token; const visibleItems = []; for (const item of conjunctionToken.items) { if (item.visibility && !this.checkConditions(item.visibility, data)) { continue; } const itemTokens = this.evaluateTokensWithEffects(item.tokens, data); const itemText = itemTokens.map((t) => t.text).join(""); if (itemText.trim().length > 0) { visibleItems.push(itemTokens); } } if (visibleItems.length === 0) { return []; } const result = []; for (let i = 0; i < visibleItems.length; i++) { result.push(...visibleItems[i]); if (i < visibleItems.length - 1) { result.push({ text: ` ${conjunctionToken.conjunction} `, effects: { emphasis: "subtle" }, }); } } return this.applyEffectsToChildren(result, baseEffects); } default: Log_1.default.debug(`Unknown summarizer token type: ${token.type}`); return []; } } /** * Apply parent effects to child tokens that don't have their own effects. * * Child effects take precedence. This allows a parent switch token to * set a sentiment (e.g., "positive" for high health) that applies to * all its children unless they override it. */ applyEffectsToChildren(children, parentEffects) { if (!parentEffects) { return children; } return children.map((child) => { if (!child.effects) { return { ...child, effects: parentEffects }; } // Merge: child effects take precedence return { ...child, effects: { ...parentEffects, ...child.effects }, }; }); } /** * Evaluate an array of tokens and concatenate their output. */ evaluateTokens(tokens, data) { const parts = []; for (const token of tokens) { // Check token visibility if (token.visibility && !this.checkConditions(token.visibility, data)) { continue; } const text = this.evaluateToken(token, data); if (text !== undefined && text !== null) { parts.push(text); } } return parts.join(""); } /** * Evaluate a single token and return its string output. */ evaluateToken(token, data) { switch (token.type) { case ISummarizerToken_1.SummarizerTokenType.literal: case "literal": return token.text; case ISummarizerToken_1.SummarizerTokenType.value: case "value": return this.evaluateValueToken(token, data); case ISummarizerToken_1.SummarizerTokenType.switch: case "switch": return this.evaluateSwitchToken(token, data); case ISummarizerToken_1.SummarizerTokenType.list: case "list": return this.evaluateListToken(token, data); case ISummarizerToken_1.SummarizerTokenType.template: case "template": return this.evaluateTemplateToken(token, data); case ISummarizerToken_1.SummarizerTokenType.plural: case "plural": return this.evaluatePluralToken(token, data); case ISummarizerToken_1.SummarizerTokenType.sample: case "sample": return this.evaluateSampleToken(token, data); case ISummarizerToken_1.SummarizerTokenType.unit: case "unit": return this.evaluateUnitToken(token, data); case ISummarizerToken_1.SummarizerTokenType.exists: case "exists": return this.evaluateExistsToken(token, data); case ISummarizerToken_1.SummarizerTokenType.group: case "group": return this.evaluateTokens(token.tokens, data); case ISummarizerToken_1.SummarizerTokenType.conjunction: case "conjunction": return this.evaluateConjunctionToken(token, data); default: Log_1.default.debug(`Unknown summarizer token type: ${token.type}`); return undefined; } } /** * Evaluate a value token: insert a field value from the data. */ evaluateValueToken(token, data) { const value = this.getFieldValue(token.field, data); if (value === undefined || value === null) { return token.fallback ?? ""; } let result; // Format the value if (token.format) { result = this.formatValue(value, token.format); } else { result = String(value); } // Apply humanification if (token.humanify && token.humanify !== ISummarizerToken_1.SummarizerHumanifyType.none && token.humanify !== "none") { result = this.humanifyValue(result, token.humanify); } return result; } /** * Evaluate a switch token: select tokens based on conditions. */ evaluateSwitchToken(token, data) { for (const switchCase of token.cases) { if (this.checkConditions(switchCase.conditions, data)) { return this.evaluateTokens(switchCase.tokens, data); } } if (token.default) { return this.evaluateTokens(token.default, data); } return undefined; } /** * Evaluate a list token: render items as a grammatically correct list. */ evaluateListToken(token, data) { const visibleItems = []; for (const item of token.items) { // Check item visibility if (item.visibility && !this.checkConditions(item.visibility, data)) { continue; } const itemText = this.evaluateTokens(item.tokens, data); if (itemText.trim().length > 0) { visibleItems.push(itemText.trim()); } } if (visibleItems.length === 0) { return token.emptyText ?? ""; } // Build the list with proper grammar let listText; if (visibleItems.length === 1) { listText = visibleItems[0]; } else if (visibleItems.length === 2) { const sep = token.twoItemSeparator ?? " and "; listText = `${visibleItems[0]}${sep}${visibleItems[1]}`; } else { const separator = token.separator ?? ", "; const finalSep = token.finalSeparator ?? ", and "; const allButLast = visibleItems.slice(0, -1).join(separator); listText = `${allButLast}${finalSep}${visibleItems[visibleItems.length - 1]}`; } // Add prefix and suffix let result = listText; if (token.prefix) { result = this.evaluateTokens(token.prefix, data) + result; } if (token.suffix) { result = result + this.evaluateTokens(token.suffix, data); } return result; } /** * Evaluate a template token: string interpolation. */ evaluateTemplateToken(token, data) { let result = token.template; for (const key in token.values) { const valueTokens = token.values[key]; const valueText = this.evaluateTokens(valueTokens, data); result = result.replace(new RegExp(`\\{${key}\\}`, "g"), valueText); } return result; } /** * Evaluate a plural token: handle singular/plural forms. */ evaluatePluralToken(token, data) { const count = this.getFieldValue(token.countField, data); const numCount = typeof count === "number" ? count : parseInt(String(count), 10) || 0; let formTokens; if (numCount === 0 && token.zero) { formTokens = token.zero; } else if (numCount === 1) { formTokens = token.singular; } else { formTokens = token.plural; } const formText = this.evaluateTokens(formTokens, data); const includeCount = token.includeCount !== false; if (includeCount) { return `${numCount} ${formText}`; } return formText; } /** * Evaluate a sample token: pull a sample value from the form definition. */ evaluateSampleToken(token, data) { if (!this.formDefinition) { Log_1.default.debug("Sample token used but no form definition provided"); return undefined; } // Look for the sample in the form definition // Samples are stored in fields as: samples: { "path": [{ content: ... }] } let sampleValue = undefined; // Try to find the sample in the form's fields for (const field of this.formDefinition.fields) { if (field.samples) { const sample = field.samples[token.samplePath]; if (sample && sample.length > 0) { const sampleContent = sample[0].content; if (typeof sampleContent === "object" && sampleContent !== null) { sampleValue = sampleContent[token.field]; } else if (field.id === token.field) { sampleValue = sampleContent; } } } } if (sampleValue === undefined) { return undefined; } // Evaluate the template with sample value available const augmentedData = { ...data, __sampleValue: sampleValue, __samplePath: token.samplePath, __sampleName: this.humanifyValue(token.samplePath.split("/").pop() || token.samplePath, token.humanifySampleName || ISummarizerToken_1.SummarizerHumanifyType.minecraft), }; return this.evaluateTokens(token.template, augmentedData); } /** * Evaluate a unit token: format value with units. */ evaluateUnitToken(token, data) { const value = this.getFieldValue(token.field, data); const numValue = typeof value === "number" ? value : parseFloat(String(value)); if (isNaN(numValue)) { return ""; } // Get the unit string with optional pluralization let unitStr = token.unit; if (token.pluralize && numValue !== 1) { unitStr = token.unitPlural ?? token.unit + "s"; } let result = `${numValue} ${unitStr}`; // Add conversion if specified if (token.conversion) { const convertedValue = numValue * token.conversion.factor; const decimals = token.conversion.decimals ?? 1; const formattedConverted = convertedValue.toFixed(decimals).replace(/\.?0+$/, ""); let targetUnit = token.conversion.targetUnit; if (token.pluralize && convertedValue !== 1) { targetUnit = token.conversion.targetUnitPlural ?? token.conversion.targetUnit + "s"; } if (token.showBoth !== false) { const format = token.bothFormat ?? "({value} {unit})"; const parenthetical = format.replace("{value}", formattedConverted).replace("{unit}", targetUnit); result = `${result} ${parenthetical}`; } else { result = `${formattedConverted} ${targetUnit}`; } } return result; } /** * Evaluate an exists token: check if a field is defined. */ evaluateExistsToken(token, data) { const value = this.getFieldValue(token.field, data); let isDefined = value !== undefined && value !== null; // Optionally treat empty values as undefined if (isDefined && token.treatEmptyAsUndefined) { if (typeof value === "string" && value.length === 0) { isDefined = false; } else if (Array.isArray(value) && value.length === 0) { isDefined = false; } else if (typeof value === "object" && Object.keys(value).length === 0) { isDefined = false; } } if (isDefined) { return this.evaluateTokens(token.whenDefined, data); } else if (token.whenUndefined) { return this.evaluateTokens(token.whenUndefined, data); } return ""; } /** * Evaluate a conjunction token: join items with a conjunction. */ evaluateConjunctionToken(token, data) { const visibleItems = []; for (const item of token.items) { if (item.visibility && !this.checkConditions(item.visibility, data)) { continue; } const itemText = this.evaluateTokens(item.tokens, data); if (itemText.trim().length > 0) { visibleItems.push(itemText.trim()); } } if (visibleItems.length === 0) { return ""; } if (visibleItems.length === 1) { return visibleItems[0]; } return visibleItems.join(` ${token.conjunction} `); } /** * Get a field value from the data object only (literal value). * Does NOT fall back to default values from the form definition. * Use this when you need to check if a value is explicitly set. * Supports dot notation for nested fields: "damage.min" */ getLiteralFieldValue(fieldPath, data) { const parts = fieldPath.split("."); let current = data; for (const part of parts) { if (current === undefined || current === null) { return undefined; } current = current[part]; } return current; } /** * Get the default value for a field from the form definition. */ getFieldDefaultValue(fieldPath) { if (!this.formDefinition || !this.formDefinition.fields) { return undefined; } // For simple field paths, look up directly // For nested paths like "damage.min", we'd need to traverse subforms const parts = fieldPath.split("."); const fieldId = parts[0]; for (const field of this.formDefinition.fields) { if (field.id === fieldId) { if (parts.length === 1) { return field.defaultValue; } // KNOWN LIMITATION: Nested subform default value lookup not implemented. // For paths like "damage.min", we would need to: // 1. Load the subForm referenced by field.subFormId // 2. Look up the remaining path parts in that subForm's fields // This is a rare edge case as most summarizers don't reference nested defaults. return undefined; } } return undefined; } /** * Get a field value from the data object, falling back to the * form definition's default value if not explicitly set. * * This is the "effective" value - what the user would see in the form. * Supports dot notation for nested fields: "damage.min" */ getFieldValue(fieldPath, data) { // First try to get the literal value from the data const literalValue = this.getLiteralFieldValue(fieldPath, data); // If it's explicitly set (not undefined), use it if (literalValue !== undefined) { return literalValue; } // Fall back to the default value from the form definition return this.getFieldDefaultValue(fieldPath); } /** * Check if all conditions are met. */ checkConditions(conditions, data) { for (const condition of conditions) { if (!this.checkCondition(condition, data)) { return false; } } return true; } /** * Check a single condition against data. * Uses effective values (data + defaults) unless checking for literallyDefined. */ checkCondition(condition, data) { const comparison = condition.comparison.toLowerCase(); // For literallyDefined, we check only the actual data, not defaults if (comparison === ICondition_1.ComparisonType.isLiterallyDefined || comparison === "literallydefined") { const literalValue = condition.field ? this.getLiteralFieldValue(condition.field, data) : undefined; return literalValue !== undefined && literalValue !== null; } // For all other comparisons, use effective value (data + defaults) const fieldValue = condition.field ? this.getFieldValue(condition.field, data) : undefined; switch (comparison) { case ICondition_1.ComparisonType.equals: case "=": case "==": case "equals": if (condition.value !== undefined) { return fieldValue === condition.value; } if (condition.anyValues !== undefined) { return condition.anyValues.includes(fieldValue); } return false; case ICondition_1.ComparisonType.lessThan: case "<": return typeof fieldValue === "number" && fieldValue < condition.value; case ICondition_1.ComparisonType.lessThanOrEqualTo: case "<=": return typeof fieldValue === "number" && fieldValue <= condition.value; case ICondition_1.ComparisonType.greaterThan: case ">": return typeof fieldValue === "number" && fieldValue > condition.value; case ICondition_1.ComparisonType.greaterThanOrEqualTo: case ">=": return typeof fieldValue === "number" && fieldValue >= condition.value; case ICondition_1.ComparisonType.isDefined: case "defined": return fieldValue !== undefined && fieldValue !== null; case ICondition_1.ComparisonType.isNonEmpty: case "nonempty": if (fieldValue === undefined || fieldValue === null) { return false; } if (typeof fieldValue === "string") { return fieldValue.length > 0; } if (Array.isArray(fieldValue)) { return fieldValue.length > 0; } return true; case "!=": case "notequals": return fieldValue !== condition.value; default: Log_1.default.debug(`Unknown comparison type: ${condition.comparison}`); return false; } } /** * Format a value according to a format string. */ formatValue(value, format) { const locale = this.options.locale ?? "en-US"; if (format === "number") { const num = typeof value === "number" ? value : parseFloat(String(value)); if (!isNaN(num)) { return num.toLocaleString(locale); } } if (format === "percent") { const num = typeof value === "number" ? value : parseFloat(String(value)); if (!isNaN(num)) { return `${(num * 100).toFixed(0)}%`; } } if (format.startsWith("decimal:")) { const decimals = parseInt(format.substring(8), 10); const num = typeof value === "number" ? value : parseFloat(String(value)); if (!isNaN(num)) { return num.toFixed(decimals); } } return String(value); } /** * Apply humanification to a value. */ humanifyValue(value, humanifyType) { switch (humanifyType) { case ISummarizerToken_1.SummarizerHumanifyType.minecraft: case "minecraft": return Utilities_1.default.humanifyMinecraftName(value); case ISummarizerToken_1.SummarizerHumanifyType.general: case "general": return Utilities_1.default.humanifyJsName(value); case ISummarizerToken_1.SummarizerHumanifyType.sentence: case "sentence": return value.charAt(0).toUpperCase() + value.slice(1); default: return value; } } } exports.default = SummarizerEvaluator;