@aglyn/plugins-ai 1.0.0-beta.287 → 1.0.0-beta.288
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/jobs/ai-business-profile-prefill.js +6 -3
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.js +5 -3
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.js +2 -1
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +97 -4
- package/src/lib/jobs/ai-layout-stock-photos.js +635 -154
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.d.ts +120 -0
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js +410 -0
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.d.ts +26 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js +714 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-pictures.d.ts +9 -1
- package/src/lib/layout-language/ai-layout-pictures.js +14 -5
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-plan-said-sections.d.ts +12 -3
- package/src/lib/model/ai-plan-said-sections.js +22 -5
- package/src/lib/model/ai-plan-said-sections.js.map +1 -1
- package/src/lib/model/ai-site-context.d.ts +6 -2
- package/src/lib/model/ai-site-context.js +8 -2
- package/src/lib/model/ai-site-context.js.map +1 -1
- package/src/lib/runtime/ai-dataset-generation.d.ts +26 -5
- package/src/lib/runtime/ai-dataset-generation.js +110 -9
- package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.js +2 -1
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +5 -4
- package/src/lib/runtime/ai-doctrine.js +2 -1
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/server/assist-build.d.ts +15 -1
- package/src/lib/server/assist-build.js +29 -3
- package/src/lib/server/assist-build.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-context.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BUSINESS_TONE_LABELS,\n type ResolvedBusinessProfile,\n} from '@aglyn/aglyn/app-utils/business-profile'\nimport type { AiSystemBlock } from '../providers/contract'\nimport type { AiHostPublishContext } from './ai-host-publish-context'\nimport type { AiSiteInventory } from './ai-site-inventory'\n\n/**\n * What every AI job knows about the site it works on (AGL-3661), as ONE\n * compact block: the business profile, what the site already has and whether\n * it is live, and what the owner's edits taught it.\n *\n * ── Why it exists ─────────────────────────────────────────────────────────\n *\n * Before it, a job saw its brief, the guided start's answers and the site's\n * name. It named a Free site \"Austin Paws Grooming\" that was called nothing\n * of the kind, and nothing stopped it inventing a phone number. The block\n * gives it the facts the owner entered and a standing rule for the ones they\n * did not: never invent a name, a contact detail, a price or a review.\n *\n * ── Where it sits, and what that costs ────────────────────────────────────\n *\n * {@link aiSiteContextSystemBlock} marks it a `cacheBreakpoint`, placed AFTER\n * every platform-wide cached block and before the volatile ones. The\n * platform's prefix is still shared by every workspace; this block extends it\n * with one cache entry per site, which every request of a job — each re-ask,\n * each section of a page — reads at the cache rate. It changes only when the\n * owner edits the profile, a page is published, or an edit is remembered.\n *\n * ── Held to a ceiling ─────────────────────────────────────────────────────\n *\n * {@link AI_SITE_CONTEXT_MAX_CHARS}: 400 tokens at the runtime's four\n * characters a token. Lines are dropped from the least useful up — the site\n * index, the profiles, then the preferences — and the closing rule is never\n * dropped, because it is the line that stops an invented phone number.\n */\n\n/** The block's ceiling: 400 tokens at four characters a token. */\nexport const AI_SITE_CONTEXT_MAX_CHARS = 1_600\n\n/** Pages, forms, components and layouts listed by name in the site index, each. */\nexport const AI_SITE_CONTEXT_INDEX_PER_KIND = 8\n\n/** The rule every block closes with, whatever else it says. */\nexport const AI_SITE_CONTEXT_RULE =\n 'Never invent a business name, a person’s name, an email address, a phone number, a street address, opening hours, prices, reviews or testimonials. Use only the facts above; where a page needs one that is missing, write around it or leave a bracketed placeholder such as [phone number] for the owner to fill.'\n\nexport interface AiSiteContextInput {\n /** The resolved business profile; `null` when the site has none to read. */\n profile: ResolvedBusinessProfile | null\n /**\n * The site's inventory, for a caller that does NOT already send it. A job\n * whose prompt carries the inventory block (the plan step and the writers\n * built on the doctrine) leaves this out: the block would only repeat it.\n */\n inventory?: AiSiteInventory | null\n /** Whether the site is live and where. */\n publish?: AiHostPublishContext | null\n /** What the owner's edits taught, strongest first. */\n preferences?: readonly string[]\n /**\n * Whether the block may carry contact details. An answer that must name\n * nobody and print no address (the insight rules) leaves them out.\n */\n contact?: boolean\n}\n\nconst clip = (value: string, max: number) => (value.length > max ? `${value.slice(0, max - 1)}…` : value)\n\n/** One labelled line, or nothing for an empty value. */\nconst line = (label: string, value: string | null | undefined, max = 240): string | null =>\n value && value.trim() ? `${label}: ${clip(value.trim(), max)}` : null\n\nconst pathOf = (slug: string) => `/${slug.trim().replace(/^\\/+|\\/+$/g, '')}`\n\n/**\n * The site's index (AGL-3661): what it already has, by name and id, so a job\n * reuses before it builds. Pages say whether they are live; a form says how\n * many fields it asks for. Pure, so a caller holding an inventory pays one\n * render and no read.\n */\nexport function aiSiteInventoryIndex(\n inventory: AiSiteInventory | null | undefined,\n publish?: AiHostPublishContext | null,\n): string[] {\n if (!inventory) return []\n const live = new Set((publish?.publishedPages ?? []).map((path) => pathOf(path)))\n const cap = AI_SITE_CONTEXT_INDEX_PER_KIND\n const more = (total: number) => (total > cap ? `, and ${total - cap} more` : '')\n const lines: string[] = []\n const pages = inventory.screens.filter((screen) => !screen.template)\n if (pages.length) {\n const rows = pages.slice(0, cap).map((page) => {\n const path = pathOf(page.slug)\n return `${page.name} ${path} (${live.has(path) ? 'live' : 'draft'})`\n })\n lines.push(`Pages: ${rows.join('; ')}${more(pages.length)}`)\n }\n if (inventory.forms.length) {\n const rows = inventory.forms.slice(0, cap).map((form) => `${form.name} [${form.id}, ${form.fields.length} fields]`)\n lines.push(`Forms: ${rows.join('; ')}${more(inventory.forms.length)}`)\n }\n if (inventory.components.length) {\n const rows = inventory.components.slice(0, cap).map((component) => `${component.name} [${component.id}]`)\n lines.push(`Components: ${rows.join('; ')}${more(inventory.components.length)}`)\n }\n if (inventory.layouts.length) {\n const rows = inventory.layouts.slice(0, cap).map((layout) => `${layout.name} [${layout.id}]`)\n lines.push(`Layouts: ${rows.join('; ')}${more(inventory.layouts.length)}`)\n }\n return lines\n}\n\n/** The block's lines in priority order; each group is dropped whole when it does not fit. */\nfunction contextGroups(input: AiSiteContextInput): string[][] {\n const { profile, publish, preferences } = input\n const business: string[] = []\n if (profile) {\n const toneLabel = profile.tone ? BUSINESS_TONE_LABELS[profile.tone.value] : null\n const toneNotes = profile.toneNotes?.value ?? ''\n business.push(\n ...[\n line('Business name', profile.name?.value, 120),\n line('What it does', profile.whatYouDo?.value),\n line('Business type', profile.businessType, 80),\n line('Services', profile.services?.value.join('; ')),\n line('Area served', profile.serviceArea?.value, 160),\n line('Audience', profile.audience?.value, 160),\n line('Tone of voice', [toneLabel, toneNotes].filter(Boolean).join(' — ') || null),\n ].filter((entry): entry is string => Boolean(entry)),\n )\n }\n const contact: string[] = []\n if (profile && input.contact !== false) {\n const parts = [\n profile.contact.email ? `email ${profile.contact.email}` : null,\n profile.contact.phone ? `phone ${profile.contact.phone}` : null,\n profile.contact.address ? `address ${profile.contact.address}` : null,\n profile.contact.hours ? `hours ${profile.contact.hours}` : null,\n ].filter(Boolean)\n contact.push(\n parts.length\n ? `Contact details the owner entered (use exactly as written): ${clip(parts.join('; '), 400)}`\n : 'Contact details: none entered yet.',\n )\n }\n const status: string[] = []\n if (publish) {\n status.push(\n publish.published\n ? `Site: live at ${publish.liveUrl ?? 'its address'}; live pages ${clip(publish.publishedPages.join(', '), 200)}`\n : `Site: not published yet${publish.liveUrl ? `; it will be at ${publish.liveUrl}` : ''}`,\n )\n }\n const remembered = preferences?.length\n ? [`What the owner's past edits showed: ${clip(preferences.join('; '), 360)}.`]\n : []\n const profiles =\n profile && input.contact !== false && profile.profiles.length\n ? [`Social profiles: ${clip(profile.profiles.join(', '), 300)}`]\n : []\n const index = aiSiteInventoryIndex(input.inventory, publish)\n // Priority order: what the business is, how to reach it, whether it is\n // live, what the owner taught, its profiles, then what the site holds.\n return [business, contact, status, remembered, profiles, index]\n}\n\n/**\n * The block as text (AGL-3661), never over {@link AI_SITE_CONTEXT_MAX_CHARS}.\n * `''` when there is nothing to say, so a caller sends no block at all.\n */\nexport function aiSiteContextBlock(input: AiSiteContextInput): string {\n const groups = contextGroups(input).filter((group) => group.length)\n if (!groups.length) return ''\n const head = 'About this site, from its owner’s settings. Write for this business and do not contradict these facts:'\n const render = (kept: string[][]) => [head, ...kept.flat(), AI_SITE_CONTEXT_RULE].join('\\n')\n const kept = [...groups]\n let text = render(kept)\n while (text.length > AI_SITE_CONTEXT_MAX_CHARS && kept.length > 1) {\n kept.pop()\n text = render(kept)\n }\n if (text.length <= AI_SITE_CONTEXT_MAX_CHARS) return text\n // One group still too long (a very long profile): its lines are cut, the\n // closing rule is kept whole.\n const room = AI_SITE_CONTEXT_MAX_CHARS - head.length - AI_SITE_CONTEXT_RULE.length - 2\n return [head, clip(kept.flat().join('\\n'), Math.max(0, room)), AI_SITE_CONTEXT_RULE].join('\\n')\n}\n\n/**\n * The block as the system block a door appends after its own cached blocks\n * (AGL-3661): a `cacheBreakpoint` of its own, so the platform's prefix ahead\n * of it is still shared and this one is cached per site. `[]` when there is\n * nothing to say.\n *\n * A door already carrying the runtime's limit of breakpoints passes\n * `cache: false`, and the block rides as a volatile block after them instead.\n */\nexport function aiSiteContextSystemBlock(\n input: AiSiteContextInput | null | undefined,\n options: { cache?: boolean } = {},\n): AiSystemBlock[] {\n const text = input ? aiSiteContextBlock(input) : ''\n if (!text) return []\n return [options.cache === false ? { text, volatile: true, site: true } : { text, cacheBreakpoint: true, site: true }]\n}\n\n/**\n * A door's own instructions with the site context after them (AGL-3661) —\n * THE call a generator makes to give its model the site:\n *\n * instructions: aiInstructionsWithSiteContext(MY_INSTRUCTIONS, context)\n *\n * The door's last instruction block is marked a breakpoint, so the\n * platform-wide prefix (the doctrine and the door's rules) stays one cache\n * entry for every workspace, and the context block after it is the per-site\n * entry. Without a context it hands the instructions back unchanged, so a\n * door's prompt is byte-for-byte what it was for a job that has no site.\n */\nexport function aiInstructionsWithSiteContext(\n instructions: readonly AiSystemBlock[],\n context: AiSiteContextInput | null | undefined,\n): readonly AiSystemBlock[] {\n const site = aiSiteContextSystemBlock(context)\n if (!site.length) return instructions\n const blocks = [...instructions]\n const last = blocks.length - 1\n if (last >= 0 && !blocks[last].volatile && !blocks.some((block) => block.cacheBreakpoint)) {\n blocks[last] = { ...blocks[last], cacheBreakpoint: true }\n }\n return [...blocks, ...site]\n}\n"],"names":["BUSINESS_TONE_LABELS","AI_SITE_CONTEXT_MAX_CHARS","AI_SITE_CONTEXT_INDEX_PER_KIND","AI_SITE_CONTEXT_RULE","clip","value","max","length","slice","line","label","trim","pathOf","slug","replace","aiSiteInventoryIndex","inventory","publish","live","Set","publishedPages","map","path","cap","more","total","lines","pages","screens","filter","screen","template","rows","page","name","has","push","join","forms","form","id","fields","components","component","layouts","layout","contextGroups","input","profile","preferences","business","toneLabel","tone","toneNotes","whatYouDo","businessType","services","serviceArea","audience","Boolean","entry","contact","parts","email","phone","address","hours","status","published","liveUrl","remembered","profiles","index","aiSiteContextBlock","groups","group","head","render","kept","flat","text","pop","room","Math","aiSiteContextSystemBlock","options","cache","volatile","site","cacheBreakpoint","aiInstructionsWithSiteContext","instructions","context","blocks","last","some","block"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-context.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BUSINESS_TONE_LABELS,\n type ResolvedBusinessProfile,\n} from '@aglyn/aglyn/app-utils/business-profile'\nimport type { AiSystemBlock } from '../providers/contract'\nimport type { AiHostPublishContext } from './ai-host-publish-context'\nimport type { AiSiteInventory } from './ai-site-inventory'\n\n/**\n * What every AI job knows about the site it works on (AGL-3661), as ONE\n * compact block: the business profile, what the site already has and whether\n * it is live, and what the owner's edits taught it.\n *\n * ── Why it exists ─────────────────────────────────────────────────────────\n *\n * Before it, a job saw its brief, the guided start's answers and the site's\n * name. It named a Free site \"Austin Paws Grooming\" that was called nothing\n * of the kind, and nothing stopped it inventing a phone number. The block\n * gives it the facts the owner entered and a standing rule for the ones they\n * did not: never invent a name, a contact detail, a price or a review.\n *\n * ── Where it sits, and what that costs ────────────────────────────────────\n *\n * {@link aiSiteContextSystemBlock} marks it a `cacheBreakpoint`, placed AFTER\n * every platform-wide cached block and before the volatile ones. The\n * platform's prefix is still shared by every workspace; this block extends it\n * with one cache entry per site, which every request of a job — each re-ask,\n * each section of a page — reads at the cache rate. It changes only when the\n * owner edits the profile, a page is published, or an edit is remembered.\n *\n * ── Held to a ceiling ─────────────────────────────────────────────────────\n *\n * {@link AI_SITE_CONTEXT_MAX_CHARS}: 400 tokens at the runtime's four\n * characters a token. Lines are dropped from the least useful up — the site\n * index, the profiles, then the preferences — and the closing rule is never\n * dropped, because it is the line that stops an invented phone number.\n */\n\n/** The block's ceiling: 400 tokens at four characters a token. */\nexport const AI_SITE_CONTEXT_MAX_CHARS = 1_600\n\n/** Pages, forms, components and layouts listed by name in the site index, each. */\nexport const AI_SITE_CONTEXT_INDEX_PER_KIND = 8\n\n/**\n * The rule every block closes with, whatever else it says. A missing fact is\n * left out, never a gap for the owner (AGL-4092): \"[phone number]\" here put\n * \"[email]\" and \"[phone]\" in an Assist build's plan.\n */\nexport const AI_SITE_CONTEXT_RULE =\n 'Never invent a business name, a person’s name, an email address, a phone number, a street address, opening hours, prices, reviews or testimonials. Use only the facts above; where a page needs one that is missing, leave it out and write the sentence without it, never a placeholder for the owner to fill.'\n\nexport interface AiSiteContextInput {\n /** The resolved business profile; `null` when the site has none to read. */\n profile: ResolvedBusinessProfile | null\n /**\n * The site's inventory, for a caller that does NOT already send it. A job\n * whose prompt carries the inventory block (the plan step and the writers\n * built on the doctrine) leaves this out: the block would only repeat it.\n */\n inventory?: AiSiteInventory | null\n /** Whether the site is live and where. */\n publish?: AiHostPublishContext | null\n /** What the owner's edits taught, strongest first. */\n preferences?: readonly string[]\n /**\n * Whether the block may carry contact details. An answer that must name\n * nobody and print no address (the insight rules) leaves them out.\n */\n contact?: boolean\n}\n\nconst clip = (value: string, max: number) => (value.length > max ? `${value.slice(0, max - 1)}…` : value)\n\n/** One labelled line, or nothing for an empty value. */\nconst line = (label: string, value: string | null | undefined, max = 240): string | null =>\n value && value.trim() ? `${label}: ${clip(value.trim(), max)}` : null\n\nconst pathOf = (slug: string) => `/${slug.trim().replace(/^\\/+|\\/+$/g, '')}`\n\n/**\n * The site's index (AGL-3661): what it already has, by name and id, so a job\n * reuses before it builds. Pages say whether they are live; a form says how\n * many fields it asks for. Pure, so a caller holding an inventory pays one\n * render and no read.\n */\nexport function aiSiteInventoryIndex(\n inventory: AiSiteInventory | null | undefined,\n publish?: AiHostPublishContext | null,\n): string[] {\n if (!inventory) return []\n const live = new Set((publish?.publishedPages ?? []).map((path) => pathOf(path)))\n const cap = AI_SITE_CONTEXT_INDEX_PER_KIND\n const more = (total: number) => (total > cap ? `, and ${total - cap} more` : '')\n const lines: string[] = []\n const pages = inventory.screens.filter((screen) => !screen.template)\n if (pages.length) {\n const rows = pages.slice(0, cap).map((page) => {\n const path = pathOf(page.slug)\n return `${page.name} ${path} (${live.has(path) ? 'live' : 'draft'})`\n })\n lines.push(`Pages: ${rows.join('; ')}${more(pages.length)}`)\n }\n if (inventory.forms.length) {\n const rows = inventory.forms.slice(0, cap).map((form) => `${form.name} [${form.id}, ${form.fields.length} fields]`)\n lines.push(`Forms: ${rows.join('; ')}${more(inventory.forms.length)}`)\n }\n if (inventory.components.length) {\n const rows = inventory.components.slice(0, cap).map((component) => `${component.name} [${component.id}]`)\n lines.push(`Components: ${rows.join('; ')}${more(inventory.components.length)}`)\n }\n if (inventory.layouts.length) {\n const rows = inventory.layouts.slice(0, cap).map((layout) => `${layout.name} [${layout.id}]`)\n lines.push(`Layouts: ${rows.join('; ')}${more(inventory.layouts.length)}`)\n }\n return lines\n}\n\n/** The block's lines in priority order; each group is dropped whole when it does not fit. */\nfunction contextGroups(input: AiSiteContextInput): string[][] {\n const { profile, publish, preferences } = input\n const business: string[] = []\n if (profile) {\n const toneLabel = profile.tone ? BUSINESS_TONE_LABELS[profile.tone.value] : null\n const toneNotes = profile.toneNotes?.value ?? ''\n business.push(\n ...[\n line('Business name', profile.name?.value, 120),\n line('What it does', profile.whatYouDo?.value),\n line('Business type', profile.businessType, 80),\n // The year the business started (AGL-4092), which a plan once marked \"[year founded]\".\n line('Founded', profile.founded?.value, 12),\n line('Services', profile.services?.value.join('; ')),\n line('Area served', profile.serviceArea?.value, 160),\n line('Audience', profile.audience?.value, 160),\n line('Tone of voice', [toneLabel, toneNotes].filter(Boolean).join(' — ') || null),\n ].filter((entry): entry is string => Boolean(entry)),\n )\n }\n const contact: string[] = []\n if (profile && input.contact !== false) {\n const parts = [\n profile.contact.email ? `email ${profile.contact.email}` : null,\n profile.contact.phone ? `phone ${profile.contact.phone}` : null,\n profile.contact.address ? `address ${profile.contact.address}` : null,\n profile.contact.hours ? `hours ${profile.contact.hours}` : null,\n ].filter(Boolean)\n contact.push(\n parts.length\n ? `Contact details the owner entered (use exactly as written): ${clip(parts.join('; '), 400)}`\n : 'Contact details: none entered yet.',\n )\n }\n const status: string[] = []\n if (publish) {\n status.push(\n publish.published\n ? `Site: live at ${publish.liveUrl ?? 'its address'}; live pages ${clip(publish.publishedPages.join(', '), 200)}`\n : `Site: not published yet${publish.liveUrl ? `; it will be at ${publish.liveUrl}` : ''}`,\n )\n }\n const remembered = preferences?.length\n ? [`What the owner's past edits showed: ${clip(preferences.join('; '), 360)}.`]\n : []\n const profiles =\n profile && input.contact !== false && profile.profiles.length\n ? [`Social profiles: ${clip(profile.profiles.join(', '), 300)}`]\n : []\n const index = aiSiteInventoryIndex(input.inventory, publish)\n // Priority order: what the business is, how to reach it, whether it is\n // live, what the owner taught, its profiles, then what the site holds.\n return [business, contact, status, remembered, profiles, index]\n}\n\n/**\n * The block as text (AGL-3661), never over {@link AI_SITE_CONTEXT_MAX_CHARS}.\n * `''` when there is nothing to say, so a caller sends no block at all.\n */\nexport function aiSiteContextBlock(input: AiSiteContextInput): string {\n const groups = contextGroups(input).filter((group) => group.length)\n if (!groups.length) return ''\n const head = 'About this site, from its owner’s settings. Write for this business and do not contradict these facts:'\n const render = (kept: string[][]) => [head, ...kept.flat(), AI_SITE_CONTEXT_RULE].join('\\n')\n const kept = [...groups]\n let text = render(kept)\n while (text.length > AI_SITE_CONTEXT_MAX_CHARS && kept.length > 1) {\n kept.pop()\n text = render(kept)\n }\n if (text.length <= AI_SITE_CONTEXT_MAX_CHARS) return text\n // One group still too long (a very long profile): its lines are cut, the\n // closing rule is kept whole.\n const room = AI_SITE_CONTEXT_MAX_CHARS - head.length - AI_SITE_CONTEXT_RULE.length - 2\n return [head, clip(kept.flat().join('\\n'), Math.max(0, room)), AI_SITE_CONTEXT_RULE].join('\\n')\n}\n\n/**\n * The block as the system block a door appends after its own cached blocks\n * (AGL-3661): a `cacheBreakpoint` of its own, so the platform's prefix ahead\n * of it is still shared and this one is cached per site. `[]` when there is\n * nothing to say.\n *\n * A door already carrying the runtime's limit of breakpoints passes\n * `cache: false`, and the block rides as a volatile block after them instead.\n */\nexport function aiSiteContextSystemBlock(\n input: AiSiteContextInput | null | undefined,\n options: { cache?: boolean } = {},\n): AiSystemBlock[] {\n const text = input ? aiSiteContextBlock(input) : ''\n if (!text) return []\n return [options.cache === false ? { text, volatile: true, site: true } : { text, cacheBreakpoint: true, site: true }]\n}\n\n/**\n * A door's own instructions with the site context after them (AGL-3661) —\n * THE call a generator makes to give its model the site:\n *\n * instructions: aiInstructionsWithSiteContext(MY_INSTRUCTIONS, context)\n *\n * The door's last instruction block is marked a breakpoint, so the\n * platform-wide prefix (the doctrine and the door's rules) stays one cache\n * entry for every workspace, and the context block after it is the per-site\n * entry. Without a context it hands the instructions back unchanged, so a\n * door's prompt is byte-for-byte what it was for a job that has no site.\n */\nexport function aiInstructionsWithSiteContext(\n instructions: readonly AiSystemBlock[],\n context: AiSiteContextInput | null | undefined,\n): readonly AiSystemBlock[] {\n const site = aiSiteContextSystemBlock(context)\n if (!site.length) return instructions\n const blocks = [...instructions]\n const last = blocks.length - 1\n if (last >= 0 && !blocks[last].volatile && !blocks.some((block) => block.cacheBreakpoint)) {\n blocks[last] = { ...blocks[last], cacheBreakpoint: true }\n }\n return [...blocks, ...site]\n}\n"],"names":["BUSINESS_TONE_LABELS","AI_SITE_CONTEXT_MAX_CHARS","AI_SITE_CONTEXT_INDEX_PER_KIND","AI_SITE_CONTEXT_RULE","clip","value","max","length","slice","line","label","trim","pathOf","slug","replace","aiSiteInventoryIndex","inventory","publish","live","Set","publishedPages","map","path","cap","more","total","lines","pages","screens","filter","screen","template","rows","page","name","has","push","join","forms","form","id","fields","components","component","layouts","layout","contextGroups","input","profile","preferences","business","toneLabel","tone","toneNotes","whatYouDo","businessType","founded","services","serviceArea","audience","Boolean","entry","contact","parts","email","phone","address","hours","status","published","liveUrl","remembered","profiles","index","aiSiteContextBlock","groups","group","head","render","kept","flat","text","pop","room","Math","aiSiteContextSystemBlock","options","cache","volatile","site","cacheBreakpoint","aiInstructionsWithSiteContext","instructions","context","blocks","last","some","block"],"mappings":"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"}
|
|
@@ -92,15 +92,27 @@ export declare function aiDatasetPrompt(input: AiDatasetPromptInput): string;
|
|
|
92
92
|
* (AGL-4065): the 2026-10-25 Rut Runners Racing start's "Race events" came
|
|
93
93
|
* back as "Upcoming race 1 (details to be added)" to "Past race 5 (details to
|
|
94
94
|
* be added)", because the rules forbid inventing a date and the first field
|
|
95
|
-
* may not be empty.
|
|
95
|
+
* may not be empty. And (AGL-4092) the 2026-10-28 start's sample riders each
|
|
96
|
+
* ended "Add this rider's story here.": an instruction to the owner is a
|
|
97
|
+
* placeholder too.
|
|
96
98
|
*/
|
|
97
99
|
export declare const AI_DATASET_PLACEHOLDER_REASK: string;
|
|
98
100
|
/** The code a dataset's placeholder records are refused under. */
|
|
99
101
|
export declare const AI_DATASET_PLACEHOLDER_CODE = "placeholder-records";
|
|
102
|
+
/** Whether words, read from their start, are an instruction to the owner rather than words about the record (AGL-4092). */
|
|
103
|
+
export declare function aiDatasetInstructionSentence(words: string): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* A value with every instruction to the owner taken out (AGL-4092): an aside,
|
|
106
|
+
* a sentence, or the turn a sentence ends on; `''` where nothing else was
|
|
107
|
+
* said, and the value unchanged where none was. Pure.
|
|
108
|
+
*/
|
|
109
|
+
export declare function aiDatasetValueWithoutInstructions(value: string): string;
|
|
100
110
|
/**
|
|
101
111
|
* The records of a designed dataset that are placeholders (AGL-4065), by
|
|
102
112
|
* position, which no site shows:
|
|
103
113
|
* - any value that marks a gap: "details to be added", "TBD", "coming soon";
|
|
114
|
+
* - any sentence that tells the owner what to add instead of saying it
|
|
115
|
+
* (AGL-4092): "Add this rider's story here.", "Add finishing position";
|
|
104
116
|
* - a name that is a number after nothing but the list's own words and
|
|
105
117
|
* stand-in words: "Item 1", "Race 2", "Upcoming race 3", "New entry 4".
|
|
106
118
|
* A course's "Level 1" to "Level 3" and a series' "Series Round 1 at
|
|
@@ -131,11 +143,20 @@ export declare function checkAiDatasetRecords(answer: Record<string, unknown>, c
|
|
|
131
143
|
*/
|
|
132
144
|
export declare function aiDatasetWithoutPlaceholders(answer: Record<string, unknown> | null | undefined, context: AiDatasetCheckContext): AiDataset | null;
|
|
133
145
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
146
|
+
* An answer with every instruction to the owner taken out of its records'
|
|
147
|
+
* values (AGL-4092), for the salvage after its one re-ask: "Add this rider's
|
|
148
|
+
* story here." leaves the bio's other sentences, "Add finishing position"
|
|
149
|
+
* leaves the result "", and a record with no name left is dropped. The
|
|
150
|
+
* answer unchanged where no value instructed. Pure.
|
|
151
|
+
*/
|
|
152
|
+
export declare function aiDatasetWithoutInstructions<A extends Record<string, unknown> | null | undefined>(answer: A): A;
|
|
153
|
+
/**
|
|
154
|
+
* A designed dataset with any instruction to the owner (AGL-4092) and any
|
|
155
|
+
* placeholder record taken out (AGL-4065), and where the list starts with no
|
|
156
|
+
* samples (`AI_DATASET_LIST_WITHOUT_ENTRIES` is `empty`), every sample too.
|
|
157
|
+
* Pure.
|
|
137
158
|
*/
|
|
138
|
-
export declare function aiDatasetRealRecords(
|
|
159
|
+
export declare function aiDatasetRealRecords(designed: AiDataset, name: string): AiDataset;
|
|
139
160
|
/** The days a sample's date steps by, and how far from the job's day the first falls: a season of weekends. */
|
|
140
161
|
export declare const AI_DATASET_SAMPLE_DATES: {
|
|
141
162
|
readonly stepDays: 21;
|
|
@@ -48,7 +48,8 @@ import { aiTextNamesBrand } from "../model/ai-products.js";
|
|
|
48
48
|
* say the entries are on the way. The rules, the tool's answer and the
|
|
49
49
|
* pages' brief all follow this one switch.
|
|
50
50
|
*/ export const AI_DATASET_LIST_WITHOUT_ENTRIES = 'samples';
|
|
51
|
-
/** What a list the brief gives no entries for is told to hold (AGL-4065). */ const WITHOUT_ENTRIES_RULE = AI_DATASET_LIST_WITHOUT_ENTRIES === 'samples' ? 'Where the brief gives no entries for the list, write sample records the owner replaces, each marked sample: a complete entry of its kind, named in the brief’s own words (such as "Series Round 1 at Mill Creek" for a race team that rides at Mill Creek), its other values typical of it. This is the one place a record may give a value the brief does not, and a sample still gives no price, phone number, email, address, real person’s name or brand. Leave a date "" in a sample, for the platform to set, and name no month or date in it.'
|
|
51
|
+
/** What a list the brief gives no entries for is told to hold (AGL-4065). */ const WITHOUT_ENTRIES_RULE = AI_DATASET_LIST_WITHOUT_ENTRIES === 'samples' ? 'Where the brief gives no entries for the list, write sample records the owner replaces, each marked sample: a complete entry of its kind, named in the brief’s own words (such as "Series Round 1 at Mill Creek" for a race team that rides at Mill Creek), its other values typical of it. This is the one place a record may give a value the brief does not, and a sample still gives no price, phone number, email, address, real person’s name or brand. Leave a date "" in a sample, for the platform to set, and name no month or date in it. ' + // AGL-4092: six sample riders' bios ended "Add this rider's story here.", and six past races' results were "Add finishing position".
|
|
52
|
+
'A sample’s words read as finished copy about it: never an instruction to the owner, such as "Add this rider\'s story here" or "Add finishing position", and never a note that it is a sample, which the console says.' : 'Where the brief gives no entries for the list, give the fields and no records: an empty list is an answer, a numbered placeholder is not.';
|
|
52
53
|
/** The rules for one dataset. Byte-identical on every request. */ export const AI_DATASET_INSTRUCTIONS = [
|
|
53
54
|
{
|
|
54
55
|
text: 'You design one dataset of a new website: a list of like things — a menu, a team, services, portfolio pieces, events, questions and answers — kept as records the owner edits in one place, which the site’s pages list.\n\n' + `Answer by calling ${AI_DATASET_TOOL_NAME} exactly once. A reply in prose cannot be used.\n\n` + 'Rules:\n' + [
|
|
@@ -93,10 +94,55 @@ import { aiTextNamesBrand } from "../model/ai-products.js";
|
|
|
93
94
|
* (AGL-4065): the 2026-10-25 Rut Runners Racing start's "Race events" came
|
|
94
95
|
* back as "Upcoming race 1 (details to be added)" to "Past race 5 (details to
|
|
95
96
|
* be added)", because the rules forbid inventing a date and the first field
|
|
96
|
-
* may not be empty.
|
|
97
|
-
|
|
97
|
+
* may not be empty. And (AGL-4092) the 2026-10-28 start's sample riders each
|
|
98
|
+
* ended "Add this rider's story here.": an instruction to the owner is a
|
|
99
|
+
* placeholder too.
|
|
100
|
+
*/ export const AI_DATASET_PLACEHOLDER_REASK = AI_DATASET_LIST_WITHOUT_ENTRIES === 'samples' ? "Every record names a real thing the brief gives, or is a sample marked sample: a complete entry of its kind in the brief's own words. Never a placeholder such as 'Upcoming race 1 (details to be added)', 'TBD', 'coming soon' or 'Item 1'. " + // AGL-4092: the samples' bios and results told the owner what to add.
|
|
101
|
+
"Never an instruction to the owner either, such as 'Add this rider's story here' or 'Add finishing position': a sample's words read as finished copy about the sample." : "Every record names a real thing the brief gives. Where the brief gives no entries for this list — no dates, no names, no results — answer with the fields and ZERO records; never number placeholders like 'Upcoming race 1 (details to be added)'.";
|
|
98
102
|
/** The code a dataset's placeholder records are refused under. */ export const AI_DATASET_PLACEHOLDER_CODE = 'placeholder-records';
|
|
99
103
|
/** A value that marks a gap for later rather than saying anything: in a name, a date or anywhere else. */ const PLACEHOLDER_TEXT = /\b(?:details?|dates?|info|information|names?)\s+(?:(?:to be|will be)\s+)?(?:added|confirmed|announced|determined|decided|filled)\b|\bto be (?:added|confirmed|announced|determined|decided)\b|\b(?:tbd|tba|t\.b\.[ad])(?![a-z])|\bcoming soon\b|\bplaceholder\b|\blorem\b|\b(?:sample|example|test|dummy) (?:record|entry|row|item|name)s?\b/i;
|
|
104
|
+
/** What a sentence tells the owner to do to the record, in place of saying anything: add, write, replace. */ const OWNER_VERB = '(?:add|insert|write|enter|put|type|paste|replace|swap|fill in|fill out|update|describe|upload|attach)';
|
|
105
|
+
/** How an instruction opens: its own words, imperative, first. */ const INSTRUCTION_START = '^\\s*(?:please\\s+)?';
|
|
106
|
+
/**
|
|
107
|
+
* Words that tell the owner to do something, where the record should say
|
|
108
|
+
* something (AGL-4092): "Add this rider's story here.", "Replace with your
|
|
109
|
+
* own photo.", "Update the results later." Only an imperative is one: "Riders
|
|
110
|
+
* enter the gate early and race later" says something, and a visitor's
|
|
111
|
+
* instruction names no place the owner fills ("Fill in the form on the
|
|
112
|
+
* Contact page"); both are kept.
|
|
113
|
+
*/ const OWNER_INSTRUCTION = new RegExp(`${INSTRUCTION_START}(?:${OWNER_VERB}\\b[^.!?]{0,80}?\\b(?:here|later|soon|in data)\\b|(?:replace|swap)\\b[^.!?]{0,60}?\\byour (?:own|real)\\b)`, 'i');
|
|
114
|
+
/** What a record says of itself, which an instruction names as the thing to add: "Add finishing position". */ const RECORD_PARTS = '(?:position|placing|placement|result|results|score|scores|time|times|finish|date|dates|details?|info|information|name|names|story|bio|biography|description|photo|photos|picture|image|link|price|location|address|phone|email|text|copy|content|notes?|summary|title|caption|quote)';
|
|
115
|
+
/** Words that are nothing but a bare instruction naming a part of the record: "Add finishing position", "Enter result". */ const BARE_INSTRUCTION = new RegExp(`${INSTRUCTION_START}${OWNER_VERB}\\s+(?:(?:the|this|that|a|an|their|its|each)\\s+)?(?:[\\p{L}'’-]+\\s+){0,2}${RECORD_PARTS}\\s*[.!]?\\s*$`, 'iu');
|
|
116
|
+
/** An aside in parentheses: "Junior rider (add details later)". */ const ASIDE = /\s*\(([^()]{1,160})\)/g;
|
|
117
|
+
/** Where a sentence turns to something else: a dash, a colon, a semicolon. */ const TURN = /\s*(?:[—–:;]|\s-)\s*/g;
|
|
118
|
+
/** A value's sentences, each with its closing mark. */ const sentencesOf = (value)=>value.split(/(?<=[.!?])\s+/).map((sentence)=>sentence.trim()).filter(Boolean);
|
|
119
|
+
/** What a visitor's instruction points at, which an owner's never does: "Fill in the form here", "Book on the Contact page". */ const VISITOR_TARGET = /\b(?:form|button|link|page|website|checkout|cart|account|app)s?\b/i;
|
|
120
|
+
/** Whether words, read from their start, are an instruction to the owner rather than words about the record (AGL-4092). */ export function aiDatasetInstructionSentence(words) {
|
|
121
|
+
return OWNER_INSTRUCTION.test(words) && !VISITOR_TARGET.test(words) || BARE_INSTRUCTION.test(words);
|
|
122
|
+
}
|
|
123
|
+
/** One sentence without the instruction it is or ends with: "" for "Add finishing position", "Junior rider" for "Junior rider — add details later". */ function sentenceWithoutInstruction(sentence) {
|
|
124
|
+
if (aiDatasetInstructionSentence(sentence)) return '';
|
|
125
|
+
for (const turn of sentence.matchAll(TURN)){
|
|
126
|
+
var _turn_index;
|
|
127
|
+
const at = (_turn_index = turn.index) != null ? _turn_index : 0;
|
|
128
|
+
if (at > 0 && aiDatasetInstructionSentence(sentence.slice(at + turn[0].length))) {
|
|
129
|
+
const head = sentence.slice(0, at).replace(/[\s,]+$/, '');
|
|
130
|
+
return head && !/[.!?]$/.test(head) ? `${head}.` : head;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return sentence;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A value with every instruction to the owner taken out (AGL-4092): an aside,
|
|
137
|
+
* a sentence, or the turn a sentence ends on; `''` where nothing else was
|
|
138
|
+
* said, and the value unchanged where none was. Pure.
|
|
139
|
+
*/ export function aiDatasetValueWithoutInstructions(value) {
|
|
140
|
+
const unasided = value.replace(ASIDE, (aside, inner)=>aiDatasetInstructionSentence(inner) ? '' : aside);
|
|
141
|
+
const sentences = sentencesOf(unasided);
|
|
142
|
+
const kept = sentences.map(sentenceWithoutInstruction).filter(Boolean);
|
|
143
|
+
return unasided === value && kept.length === sentences.length && kept.every((sentence, index)=>sentence === sentences[index]) ? value : kept.join(' ');
|
|
144
|
+
}
|
|
145
|
+
/** Whether a value tells the owner to do something anywhere in it (AGL-4092). */ const holdsInstruction = (value)=>aiDatasetValueWithoutInstructions(value) !== value;
|
|
100
146
|
/** A name that ends in a number, with any aside after it: "Upcoming race 1 (details to be added)". */ const NUMBERED_NAME = /^(.*?)\s*(?:#|no\.?\s*)?\d+\s*(?:\([^)]*\))?$/i;
|
|
101
147
|
/** Words a numbered placeholder is made of besides the list's own: "Item 1", "Upcoming event 2", "New entry 3". */ const STAND_IN_WORDS = new Set('item entry record row event name product thing option sample example test placeholder untitled new upcoming past next previous future current recent old'.split(' '));
|
|
102
148
|
/** A name's words as a stem compares them: lowercase, a plural's "-s" dropped. */ const stemWords = (text)=>text.toLowerCase().split(RegExp("[^\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376-\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E-\\u066F\\u0671-\\u06D3\\u06D5\\u06E5-\\u06E6\\u06EE-\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4-\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F-\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC-\\u09DD\\u09DF-\\u09E1\\u09F0-\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F-\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32-\\u0A33\\u0A35-\\u0A36\\u0A38-\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2-\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0-\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F-\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32-\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C-\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99-\\u0B9A\\u0B9C\\u0B9E-\\u0B9F\\u0BA3-\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C-\\u0C5D\\u0C60-\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0-\\u0CE1\\u0CF1-\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32-\\u0E33\\u0E40-\\u0E46\\u0E81-\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2-\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065-\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u1711\\u171F-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4C\\u1B83-\\u1BA0\\u1BAE-\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5-\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183-\\u2184\\u2C00-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2-\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3006\\u3031-\\u3035\\u303B-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A-\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7DC\\uA7F1-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD-\\uA8FE\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5-\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40-\\uFB41\\uFB43-\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}-\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{10300}-\\u{1031F}\\u{1032D}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{10400}-\\u{1049D}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057A}\\u{1057C}-\\u{1058A}\\u{1058C}-\\u{10592}\\u{10594}-\\u{10595}\\u{10597}-\\u{105A1}\\u{105A3}-\\u{105B1}\\u{105B3}-\\u{105B9}\\u{105BB}-\\u{105BC}\\u{105C0}-\\u{105F3}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}-\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089E}\\u{108E0}-\\u{108F2}\\u{108F4}-\\u{108F5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10940}-\\u{10959}\\u{10980}-\\u{109B7}\\u{109BE}-\\u{109BF}\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A60}-\\u{10A7C}\\u{10A80}-\\u{10A9C}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B60}-\\u{10B72}\\u{10B80}-\\u{10B91}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10D00}-\\u{10D23}\\u{10D4A}-\\u{10D65}\\u{10D6F}-\\u{10D85}\\u{10E80}-\\u{10EA9}\\u{10EB0}-\\u{10EB1}\\u{10EC2}-\\u{10EC7}\\u{10F00}-\\u{10F1C}\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F70}-\\u{10F81}\\u{10FB0}-\\u{10FC4}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11071}-\\u{11072}\\u{11075}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{11103}-\\u{11126}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111DA}\\u{111DC}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{1123F}-\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{11305}-\\u{1130C}\\u{1130F}-\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}-\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11380}-\\u{11389}\\u{1138B}\\u{1138E}\\u{11390}-\\u{113B5}\\u{113B7}\\u{113D1}\\u{113D3}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{1145F}-\\u{11461}\\u{11480}-\\u{114AF}\\u{114C4}-\\u{114C5}\\u{114C7}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11680}-\\u{116AA}\\u{116B8}\\u{11700}-\\u{1171A}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118DF}\\u{118FF}-\\u{11906}\\u{11909}\\u{1190C}-\\u{11913}\\u{11915}-\\u{11916}\\u{11918}-\\u{1192F}\\u{1193F}\\u{11941}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AB0}-\\u{11AF8}\\u{11BC0}-\\u{11BE0}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}-\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D60}-\\u{11D65}\\u{11D67}-\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DB0}-\\u{11DDB}\\u{11EE0}-\\u{11EF2}\\u{11F02}\\u{11F04}-\\u{11F10}\\u{11F12}-\\u{11F33}\\u{11FB0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12F90}-\\u{12FF0}\\u{13000}-\\u{1342F}\\u{13441}-\\u{13446}\\u{13460}-\\u{143FA}\\u{14400}-\\u{14646}\\u{16100}-\\u{1611D}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A70}-\\u{16ABE}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16D40}-\\u{16D6C}\\u{16E40}-\\u{16E7F}\\u{16EA0}-\\u{16EB8}\\u{16EBB}-\\u{16ED3}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}-\\u{16FE1}\\u{16FE3}\\u{16FF2}-\\u{16FF3}\\u{17000}-\\u{18CD5}\\u{18CFF}-\\u{18D1E}\\u{18D80}-\\u{18DF2}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}-\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}-\\u{1D49F}\\u{1D4A2}\\u{1D4A5}-\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E14E}\\u{1E290}-\\u{1E2AD}\\u{1E2C0}-\\u{1E2EB}\\u{1E4D0}-\\u{1E4EB}\\u{1E5D0}-\\u{1E5ED}\\u{1E5F0}\\u{1E6C0}-\\u{1E6DE}\\u{1E6E0}-\\u{1E6E2}\\u{1E6E4}-\\u{1E6E5}\\u{1E6E7}-\\u{1E6ED}\\u{1E6F0}-\\u{1E6F4}\\u{1E6FE}-\\u{1E6FF}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}-\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1E800}-\\u{1E8C4}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}-\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}-\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}-\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}\\u0030-\\u0039\\u00B2-\\u00B3\\u00B9\\u00BC-\\u00BE\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u09F4-\\u09F9\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0B72-\\u0B77\\u0BE6-\\u0BF2\\u0C66-\\u0C6F\\u0C78-\\u0C7E\\u0CE6-\\u0CEF\\u0D58-\\u0D5E\\u0D66-\\u0D78\\u0DE6-\\u0DEF\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F33\\u1040-\\u1049\\u1090-\\u1099\\u1369-\\u137C\\u16EE-\\u16F0\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19DA\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\u2070\\u2074-\\u2079\\u2080-\\u2089\\u2150-\\u2182\\u2185-\\u2189\\u2460-\\u249B\\u24EA-\\u24FF\\u2776-\\u2793\\u2CFD\\u3007\\u3021-\\u3029\\u3038-\\u303A\\u3192-\\u3195\\u3220-\\u3229\\u3248-\\u324F\\u3251-\\u325F\\u3280-\\u3289\\u32B1-\\u32BF\\uA620-\\uA629\\uA6E6-\\uA6EF\\uA830-\\uA835\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uA9F0-\\uA9F9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19\\u{10107}-\\u{10133}\\u{10140}-\\u{10178}\\u{1018A}-\\u{1018B}\\u{102E1}-\\u{102FB}\\u{10320}-\\u{10323}\\u{10341}\\u{1034A}\\u{103D1}-\\u{103D5}\\u{104A0}-\\u{104A9}\\u{10858}-\\u{1085F}\\u{10879}-\\u{1087F}\\u{108A7}-\\u{108AF}\\u{108FB}-\\u{108FF}\\u{10916}-\\u{1091B}\\u{109BC}-\\u{109BD}\\u{109C0}-\\u{109CF}\\u{109D2}-\\u{109FF}\\u{10A40}-\\u{10A48}\\u{10A7D}-\\u{10A7E}\\u{10A9D}-\\u{10A9F}\\u{10AEB}-\\u{10AEF}\\u{10B58}-\\u{10B5F}\\u{10B78}-\\u{10B7F}\\u{10BA9}-\\u{10BAF}\\u{10CFA}-\\u{10CFF}\\u{10D30}-\\u{10D39}\\u{10D40}-\\u{10D49}\\u{10E60}-\\u{10E7E}\\u{10F1D}-\\u{10F26}\\u{10F51}-\\u{10F54}\\u{10FC5}-\\u{10FCB}\\u{11052}-\\u{1106F}\\u{110F0}-\\u{110F9}\\u{11136}-\\u{1113F}\\u{111D0}-\\u{111D9}\\u{111E1}-\\u{111F4}\\u{112F0}-\\u{112F9}\\u{11450}-\\u{11459}\\u{114D0}-\\u{114D9}\\u{11650}-\\u{11659}\\u{116C0}-\\u{116C9}\\u{116D0}-\\u{116E3}\\u{11730}-\\u{1173B}\\u{118E0}-\\u{118F2}\\u{11950}-\\u{11959}\\u{11BF0}-\\u{11BF9}\\u{11C50}-\\u{11C6C}\\u{11D50}-\\u{11D59}\\u{11DA0}-\\u{11DA9}\\u{11DE0}-\\u{11DE9}\\u{11F50}-\\u{11F59}\\u{11FC0}-\\u{11FD4}\\u{12400}-\\u{1246E}\\u{16130}-\\u{16139}\\u{16A60}-\\u{16A69}\\u{16AC0}-\\u{16AC9}\\u{16B50}-\\u{16B59}\\u{16B5B}-\\u{16B61}\\u{16D70}-\\u{16D79}\\u{16E80}-\\u{16E96}\\u{16FF4}-\\u{16FF6}\\u{1CCF0}-\\u{1CCF9}\\u{1D2C0}-\\u{1D2D3}\\u{1D2E0}-\\u{1D2F3}\\u{1D360}-\\u{1D378}\\u{1D7CE}-\\u{1D7FF}\\u{1E140}-\\u{1E149}\\u{1E2F0}-\\u{1E2F9}\\u{1E4F0}-\\u{1E4F9}\\u{1E5F1}-\\u{1E5FA}\\u{1E8C7}-\\u{1E8CF}\\u{1E950}-\\u{1E959}\\u{1EC71}-\\u{1ECAB}\\u{1ECAD}-\\u{1ECAF}\\u{1ECB1}-\\u{1ECB4}\\u{1ED01}-\\u{1ED2D}\\u{1ED2F}-\\u{1ED3D}\\u{1F100}-\\u{1F10C}\\u{1FBF0}-\\u{1FBF9}]+", "u")).filter(Boolean).map((word)=>word.length > 3 && word.endsWith('s') && !word.endsWith('ss') ? word.slice(0, -1) : word);
|
|
@@ -104,6 +150,8 @@ import { aiTextNamesBrand } from "../model/ai-products.js";
|
|
|
104
150
|
* The records of a designed dataset that are placeholders (AGL-4065), by
|
|
105
151
|
* position, which no site shows:
|
|
106
152
|
* - any value that marks a gap: "details to be added", "TBD", "coming soon";
|
|
153
|
+
* - any sentence that tells the owner what to add instead of saying it
|
|
154
|
+
* (AGL-4092): "Add this rider's story here.", "Add finishing position";
|
|
107
155
|
* - a name that is a number after nothing but the list's own words and
|
|
108
156
|
* stand-in words: "Item 1", "Race 2", "Upcoming race 3", "New entry 4".
|
|
109
157
|
* A course's "Level 1" to "Level 3" and a series' "Series Round 1 at
|
|
@@ -115,7 +163,7 @@ import { aiTextNamesBrand } from "../model/ai-products.js";
|
|
|
115
163
|
]);
|
|
116
164
|
return dataset.records.flatMap((values, index)=>{
|
|
117
165
|
var _values_, _match_;
|
|
118
|
-
if (values.some((value)=>PLACEHOLDER_TEXT.test(value != null ? value : ''))) return [
|
|
166
|
+
if (values.some((value)=>PLACEHOLDER_TEXT.test(value != null ? value : '') || holdsInstruction(value != null ? value : ''))) return [
|
|
119
167
|
index
|
|
120
168
|
];
|
|
121
169
|
const match = NUMBERED_NAME.exec(((_values_ = values[0]) != null ? _values_ : '').replace(/\s+/g, ' ').trim());
|
|
@@ -212,11 +260,63 @@ import { aiTextNamesBrand } from "../model/ai-products.js";
|
|
|
212
260
|
return checked.value && !checked.violations.length ? checked.value : null;
|
|
213
261
|
}
|
|
214
262
|
/**
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
|
|
263
|
+
* An answer with every instruction to the owner taken out of its records'
|
|
264
|
+
* values (AGL-4092), for the salvage after its one re-ask: "Add this rider's
|
|
265
|
+
* story here." leaves the bio's other sentences, "Add finishing position"
|
|
266
|
+
* leaves the result "", and a record with no name left is dropped. The
|
|
267
|
+
* answer unchanged where no value instructed. Pure.
|
|
268
|
+
*/ export function aiDatasetWithoutInstructions(answer) {
|
|
269
|
+
if (!answer || !Array.isArray(answer['records'])) return answer;
|
|
270
|
+
let changed = false;
|
|
271
|
+
const records = answer['records'].flatMap((record)=>{
|
|
272
|
+
const values = record == null ? void 0 : record['values'];
|
|
273
|
+
if (!Array.isArray(values)) return [
|
|
274
|
+
record
|
|
275
|
+
];
|
|
276
|
+
const next = values.map((value)=>typeof value === 'string' ? aiDatasetValueWithoutInstructions(value) : value);
|
|
277
|
+
if (next.every((value, index)=>value === values[index])) return [
|
|
278
|
+
record
|
|
279
|
+
];
|
|
280
|
+
changed = true;
|
|
281
|
+
return typeof next[0] === 'string' && next[0].trim() ? [
|
|
282
|
+
_extends({}, record, {
|
|
283
|
+
values: next
|
|
284
|
+
})
|
|
285
|
+
] : [];
|
|
286
|
+
});
|
|
287
|
+
return changed ? _extends({}, answer, {
|
|
288
|
+
records
|
|
289
|
+
}) : answer;
|
|
290
|
+
}
|
|
291
|
+
/** A designed dataset with every instruction to the owner taken out (AGL-4092), as {@link aiDatasetWithoutInstructions} does an answer. */ function designedWithoutInstructions(dataset) {
|
|
292
|
+
const records = dataset.records.map((values)=>values.map((value)=>aiDatasetValueWithoutInstructions(value != null ? value : '')));
|
|
293
|
+
if (records.every((values, index)=>values.every((value, column)=>{
|
|
294
|
+
var _ref;
|
|
295
|
+
var _dataset_records_index;
|
|
296
|
+
return value === ((_ref = (_dataset_records_index = dataset.records[index]) == null ? void 0 : _dataset_records_index[column]) != null ? _ref : '');
|
|
297
|
+
}))) return dataset;
|
|
298
|
+
const keep = records.map((values)=>{
|
|
299
|
+
var _values_;
|
|
300
|
+
return Boolean((_values_ = values[0]) == null ? void 0 : _values_.trim());
|
|
301
|
+
});
|
|
302
|
+
const { samples: all } = dataset, rest = _object_without_properties_loose(dataset, [
|
|
303
|
+
"samples"
|
|
304
|
+
]);
|
|
305
|
+
const samples = all == null ? void 0 : all.filter((_sample, index)=>keep[index]);
|
|
306
|
+
return _extends({}, rest, {
|
|
307
|
+
records: records.filter((_values, index)=>keep[index])
|
|
308
|
+
}, (samples == null ? void 0 : samples.some(Boolean)) ? {
|
|
309
|
+
samples
|
|
310
|
+
} : {});
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* A designed dataset with any instruction to the owner (AGL-4092) and any
|
|
314
|
+
* placeholder record taken out (AGL-4065), and where the list starts with no
|
|
315
|
+
* samples (`AI_DATASET_LIST_WITHOUT_ENTRIES` is `empty`), every sample too.
|
|
316
|
+
* Pure.
|
|
317
|
+
*/ export function aiDatasetRealRecords(designed, name) {
|
|
219
318
|
var _dataset_samples;
|
|
319
|
+
const dataset = designedWithoutInstructions(designed);
|
|
220
320
|
const drop = new Set(aiDatasetPlaceholderRecords(dataset, name));
|
|
221
321
|
if (AI_DATASET_LIST_WITHOUT_ENTRIES === 'empty') (_dataset_samples = dataset.samples) == null ? void 0 : _dataset_samples.forEach((sample, index)=>sample && drop.add(index));
|
|
222
322
|
if (!drop.size) return dataset;
|
|
@@ -296,7 +396,8 @@ const DAY_MS = 86400000;
|
|
|
296
396
|
const past = options.pastOnly || splitColumn > 0 && PAST_WORDS.test((_values_splitColumn = values[splitColumn]) != null ? _values_splitColumn : '');
|
|
297
397
|
const at = past ? new Date(firstBehind.getTime() - behind++ * stepDays * DAY_MS) : new Date(firstAhead.getTime() + ahead++ * stepDays * DAY_MS);
|
|
298
398
|
const said = format.format(at);
|
|
299
|
-
|
|
399
|
+
// A race still to come has no result, and one behind us never an instruction for one (AGL-4092).
|
|
400
|
+
return values.map((value, column)=>dates.includes(column) ? said : outcomes.includes(column) && (!past || holdsInstruction(value != null ? value : '')) ? '' : value);
|
|
300
401
|
})
|
|
301
402
|
});
|
|
302
403
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-dataset-generation.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiStepKind } from '../providers/catalog'\nimport type { AiProvider } from '../providers/contract'\nimport { AI_ROUTING_TABLE, type AiPluginSettings } from '../providers/routing'\nimport {\n AI_DATASET_LIMITS,\n AI_DATASET_LIST_SEPARATOR,\n AI_DATASET_TOOL,\n AI_DATASET_TOOL_NAME,\n checkAiDataset,\n type AiDataset,\n} from '../tools/ai-dataset-tool'\nimport { runValidatedGeneration, type AiGenerationCheckResult, type AiValidatedGeneration } from './ai-doctrine'\nimport type { AiDoctrineViolation } from './ai-doctrine-validators'\nimport { aiLayoutArticles } from '../layout-language/ai-layout-copy'\nimport { aiTextNamesBrand } from '../model/ai-products'\nimport type { AiSystemBlock } from './ai-runtime'\n\n/**\n * A SITE'S DATASETS (AGL-3616): the typed fields and first records of one\n * dataset a site plan created, through the doctrine's generation call and the\n * strict `submit_dataset` tool. The data plugin's writer stores what it\n * answers; nothing here writes.\n *\n * Routed as `job.products`, the row a store's first products are proposed on\n * (no thinking, a structured list from a brief, held to the storefront claim\n * rules): one dataset of a dozen records is the same work. Its answer is held\n * to a ceiling of its own under that row's. The rules are cached and\n * byte-identical; the site's words, the dataset and the pages that show it\n * ride in the user turn.\n */\n\n/** The step a dataset is routed as. */\nexport const AI_DATASET_STEP: AiStepKind = 'job.products'\n\n/**\n * One dataset's answer ceiling: twelve fields and twelve records of a few\n * sentences each, written as JSON, about 9,000 characters at three a token\n * with room; under the routing row's.\n */\nexport const AI_DATASET_MAX_TOKENS = Math.min(4_000, AI_ROUTING_TABLE[AI_DATASET_STEP].maxTokens)\n\n/** The generation kind a dataset is asked under, which the doctrine scopes. */\nexport const AI_DATASET_KIND = 'dataset'\n\n/**\n * THE Q2 DECISION (AGL-4065): what a list the brief gives no entries for starts\n * with. `samples` (decided 2026-10-28): complete-looking SAMPLE records the\n * owner replaces, each marked a sample in Data, a dated list's dates set by\n * the platform (`aiDatasetSampleDates`), so the site looks complete and no\n * row is a placeholder. `empty`: the fields and no records, and the pages\n * say the entries are on the way. The rules, the tool's answer and the\n * pages' brief all follow this one switch.\n */\nexport const AI_DATASET_LIST_WITHOUT_ENTRIES: 'samples' | 'empty' = 'samples'\n\n/** What a list the brief gives no entries for is told to hold (AGL-4065). */\nconst WITHOUT_ENTRIES_RULE =\n AI_DATASET_LIST_WITHOUT_ENTRIES === 'samples'\n ? 'Where the brief gives no entries for the list, write sample records the owner replaces, each marked sample: a complete entry of its kind, named in the brief’s own words (such as \"Series Round 1 at Mill Creek\" for a race team that rides at Mill Creek), its other values typical of it. This is the one place a record may give a value the brief does not, and a sample still gives no price, phone number, email, address, real person’s name or brand. Leave a date \"\" in a sample, for the platform to set, and name no month or date in it.'\n : 'Where the brief gives no entries for the list, give the fields and no records: an empty list is an answer, a numbered placeholder is not.'\n\n/** The rules for one dataset. Byte-identical on every request. */\nexport const AI_DATASET_INSTRUCTIONS: AiSystemBlock[] = [\n {\n text:\n 'You design one dataset of a new website: a list of like things — a menu, a team, services, portfolio pieces, events, questions and answers — kept as records the owner edits in one place, which the site’s pages list.\\n\\n' +\n `Answer by calling ${AI_DATASET_TOOL_NAME} exactly once. A reply in prose cannot be used.\\n\\n` +\n 'Rules:\\n' +\n [\n 'Keep the planned fields, in order and named exactly as planned; the pages around the dataset already name them. Add a field only where the records clearly need it.',\n 'Type each field: text for words, including a date or a time written the way a visitor reads it; number or integer only for a figure a visitor compares; boolean for yes or no; list for a few short tags. The first field names each record and is text.',\n `Write ${AI_DATASET_LIMITS.recordsMin} to ${AI_DATASET_LIMITS.recordsMax} records, as many as the brief supports: what the business actually offers, in its own words where the brief gives them, otherwise plain and general enough to be true of it.`,\n 'Never invent a fact: no price, date, address, phone number, award, credential, client or number the brief does not give, and no review, testimonial, rating or quote from anyone. Leave a value \"\" rather than make one up.',\n // AGL-4065: a race schedule with no dates came back as ten numbered placeholders.\n WITHOUT_ENTRIES_RULE,\n 'Never give a person a name the brief does not give: a team member the brief does not name is their role, such as \"Head chef\".',\n 'Never say anything cures, treats, heals or prevents anything, and never promise a health, financial or legal result.',\n `Values are plain text with no links, markup, emoji or square brackets, at most ${AI_DATASET_LIMITS.valueMax} characters each; a description is one to three sentences.`,\n 'Write in the language of the brief.',\n ]\n .map((rule) => `- ${rule}`)\n .join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\nexport interface AiDatasetPromptInput {\n /** The site's words: the brief and its answers, as a unit's brief carries them. */\n brief: string\n /** The dataset as the plan named it, and why it has one. */\n name: string\n why: string\n /** The field names the plan gave it. */\n fields: readonly string[]\n /** The sections that list it, by page: \"Menu › Our dishes (12 items)\". */\n shownIn: readonly string[]\n /** Whether each record also gets a page of its own. */\n recordPages: boolean\n /**\n * The sections of one page that each list part of it (AGL-4065), and the\n * value of its planned field each lists: \"Upcoming races\" the records whose\n * Status is \"Upcoming\", \"Past results\" those whose Status is \"Past\".\n */\n split?: AiDatasetSplit\n}\n\n/** A page's lists of one dataset, each the records of one value of one field (AGL-4065). */\nexport interface AiDatasetSplit {\n /** The planned field, by name: \"Status\". */\n field: string\n /** The sections, in page order. */\n sections: string[]\n /** The value each section lists, by position. */\n values: string[]\n}\n\n/** Names as a sentence lists them: “A”, “B” and “C”. */\nfunction listed(names: readonly string[], last: 'and' | 'or'): string {\n const quoted = names.map((name) => `“${name}”`)\n return quoted.length > 1 ? `${quoted.slice(0, -1).join(', ')} ${last} ${quoted[quoted.length - 1]}` : (quoted[0] ?? '')\n}\n\n/** What a dataset is told of the sections that each list part of it (AGL-4065). */\nexport function aiDatasetSplitLine(split: AiDatasetSplit): string {\n return `Sections ${listed(split.sections, 'and')} list this dataset: its “${split.field}” field takes exactly ${listed(split.values, 'or')}.`\n}\n\n/** The user turn: the site's words, the dataset, and where it is shown. */\nexport function aiDatasetPrompt(input: AiDatasetPromptInput): string {\n return [\n 'Brief:',\n input.brief.trim(),\n `Dataset: “${input.name}” — ${input.why.trim() || 'the site’s structured content'}`,\n `Planned fields: ${input.fields.length ? input.fields.map((field) => `“${field}”`).join(', ') : '(none: choose them)'}`,\n input.shownIn.length ? `Listed on: ${input.shownIn.join('; ')}.` : 'Listed on the site’s pages.',\n ...(input.split ? [aiDatasetSplitLine(input.split)] : []),\n input.recordPages\n ? 'Each record also gets a page of its own, so give each one enough to read: a sentence or two describing it.'\n : 'Each record is shown as a card, so keep its values short.',\n ].join('\\n')\n}\n\n// ── No placeholder rows (AGL-4065) ─────────────────────────────────────\n\n/**\n * The re-ask a dataset whose records are placeholders is answered with\n * (AGL-4065): the 2026-10-25 Rut Runners Racing start's \"Race events\" came\n * back as \"Upcoming race 1 (details to be added)\" to \"Past race 5 (details to\n * be added)\", because the rules forbid inventing a date and the first field\n * may not be empty.\n */\nexport const AI_DATASET_PLACEHOLDER_REASK =\n AI_DATASET_LIST_WITHOUT_ENTRIES === 'samples'\n ? \"Every record names a real thing the brief gives, or is a sample marked sample: a complete entry of its kind in the brief's own words. Never a placeholder such as 'Upcoming race 1 (details to be added)', 'TBD', 'coming soon' or 'Item 1'.\"\n : \"Every record names a real thing the brief gives. Where the brief gives no entries for this list — no dates, no names, no results — answer with the fields and ZERO records; never number placeholders like 'Upcoming race 1 (details to be added)'.\"\n\n/** The code a dataset's placeholder records are refused under. */\nexport const AI_DATASET_PLACEHOLDER_CODE = 'placeholder-records'\n\n/** A value that marks a gap for later rather than saying anything: in a name, a date or anywhere else. */\nconst PLACEHOLDER_TEXT =\n /\\b(?:details?|dates?|info|information|names?)\\s+(?:(?:to be|will be)\\s+)?(?:added|confirmed|announced|determined|decided|filled)\\b|\\bto be (?:added|confirmed|announced|determined|decided)\\b|\\b(?:tbd|tba|t\\.b\\.[ad])(?![a-z])|\\bcoming soon\\b|\\bplaceholder\\b|\\blorem\\b|\\b(?:sample|example|test|dummy) (?:record|entry|row|item|name)s?\\b/i\n\n/** A name that ends in a number, with any aside after it: \"Upcoming race 1 (details to be added)\". */\nconst NUMBERED_NAME = /^(.*?)\\s*(?:#|no\\.?\\s*)?\\d+\\s*(?:\\([^)]*\\))?$/i\n\n/** Words a numbered placeholder is made of besides the list's own: \"Item 1\", \"Upcoming event 2\", \"New entry 3\". */\nconst STAND_IN_WORDS = new Set(\n 'item entry record row event name product thing option sample example test placeholder untitled new upcoming past next previous future current recent old'.split(' '),\n)\n\n/** A name's words as a stem compares them: lowercase, a plural's \"-s\" dropped. */\nconst stemWords = (text: string) =>\n text\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean)\n .map((word) => (word.length > 3 && word.endsWith('s') && !word.endsWith('ss') ? word.slice(0, -1) : word))\n\n/**\n * The records of a designed dataset that are placeholders (AGL-4065), by\n * position, which no site shows:\n * - any value that marks a gap: \"details to be added\", \"TBD\", \"coming soon\";\n * - a name that is a number after nothing but the list's own words and\n * stand-in words: \"Item 1\", \"Race 2\", \"Upcoming race 3\", \"New entry 4\".\n * A course's \"Level 1\" to \"Level 3\" and a series' \"Series Round 1 at\n * Lockhart\" name things, and are kept. Pure.\n */\nexport function aiDatasetPlaceholderRecords(\n dataset: { fields: ReadonlyArray<{ name: string }>; records: ReadonlyArray<ReadonlyArray<string | undefined>> },\n name = '',\n): number[] {\n const own = new Set([...stemWords(name), ...dataset.fields.flatMap((field) => stemWords(field.name))])\n return dataset.records.flatMap((values, index) => {\n if (values.some((value) => PLACEHOLDER_TEXT.test(value ?? ''))) return [index]\n const match = NUMBERED_NAME.exec((values[0] ?? '').replace(/\\s+/g, ' ').trim())\n if (!match) return []\n const words = stemWords(match[1] ?? '')\n return words.every((word) => own.has(word) || STAND_IN_WORDS.has(word)) ? [index] : []\n })\n}\n\n/** An answer's fields and records as the placeholder test reads them, whatever else is wrong with it. */\nfunction answerShape(answer: Record<string, unknown> | null | undefined): {\n fields: Array<{ name: string }>\n records: string[][]\n} {\n const text = (value: unknown) => (typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '')\n const fields = (Array.isArray(answer?.['fields']) ? (answer?.['fields'] as unknown[]) : []).map((field) => ({\n name: text((field as Record<string, unknown> | null)?.['name']),\n }))\n const records = (Array.isArray(answer?.['records']) ? (answer?.['records'] as unknown[]) : []).map((record) => {\n const values = (record as Record<string, unknown> | null)?.['values']\n return Array.isArray(values) ? values.map(text) : []\n })\n return { fields, records }\n}\n\n/** Where a dataset is checked: its plan's fields, the words a claim may come from, and its name. */\nexport interface AiDatasetCheckContext {\n planned: readonly string[]\n merchantWords: string\n name: string\n}\n\n/**\n * A dataset's answer held to the tool's check and to naming real things\n * (AGL-4065): one with placeholder records is refused, with\n * {@link AI_DATASET_PLACEHOLDER_REASK} for the one re-ask. Pure.\n */\nexport function checkAiDatasetRecords(answer: Record<string, unknown>, context: AiDatasetCheckContext): AiGenerationCheckResult<AiDataset> {\n const checked = checkAiDataset(answer, context)\n const shape = answerShape(answer)\n const placeholders = aiDatasetPlaceholderRecords(shape, context.name)\n // A sample is of its kind, never of a make the brief does not name (AGL-4065).\n const rawRecords = Array.isArray(answer['records']) ? (answer['records'] as unknown[]) : []\n const branded = shape.records.flatMap((values, index) => {\n const sample = (rawRecords[index] as Record<string, unknown> | null)?.['sample'] === true\n const brand = sample ? values.map((value) => aiTextNamesBrand(value, context.merchantWords)).find(Boolean) : null\n return brand ? [{ index, brand }] : []\n })\n if (!placeholders.length && !branded.length) return checked\n const violations = [...checked.violations]\n const offending: Record<string, unknown> = { ...(checked.offending ?? {}) }\n if (placeholders.length) {\n violations.push({\n rule: null,\n code: AI_DATASET_PLACEHOLDER_CODE,\n message: AI_DATASET_PLACEHOLDER_REASK,\n paths: placeholders.map((index) => `records[${index}]`),\n } satisfies AiDoctrineViolation)\n for (const index of placeholders.slice(0, 3)) offending[`records[${index}]`] = shape.records[index]?.[0] ?? ''\n }\n for (const { index, brand } of branded) {\n violations.push({\n rule: null,\n code: 'sample-brand',\n message: `Sample record ${index + 1} names the make \"${brand}\", which the brief does not: a sample is of its kind, never of a brand.`,\n paths: [`records[${index}]`],\n })\n }\n return { value: null, violations, offending }\n}\n\n/**\n * A dataset answered with placeholders after its one re-ask, kept with the\n * records that name real things (AGL-4065) — however few, none included —\n * where nothing else in it breaks a rule; `null` otherwise. Pure.\n */\nexport function aiDatasetWithoutPlaceholders(answer: Record<string, unknown> | null | undefined, context: AiDatasetCheckContext): AiDataset | null {\n if (!answer) return null\n const drop = new Set(aiDatasetPlaceholderRecords(answerShape(answer), context.name))\n const records = Array.isArray(answer['records']) ? (answer['records'] as unknown[]) : []\n const kept = { ...answer, records: records.filter((_record, index) => !drop.has(index)) }\n // The fewest records a list is asked for is for one the brief fills; what is left of this one is what it gave.\n const checked = checkAiDataset(kept, { ...context, recordsMin: 1 })\n return checked.value && !checked.violations.length ? checked.value : null\n}\n\n/**\n * A designed dataset with any placeholder record taken out (AGL-4065), and\n * where the list starts with no samples (`AI_DATASET_LIST_WITHOUT_ENTRIES`\n * is `empty`), every sample too. Pure.\n */\nexport function aiDatasetRealRecords(dataset: AiDataset, name: string): AiDataset {\n const drop = new Set(aiDatasetPlaceholderRecords(dataset, name))\n if (AI_DATASET_LIST_WITHOUT_ENTRIES === 'empty') dataset.samples?.forEach((sample, index) => sample && drop.add(index))\n if (!drop.size) return dataset\n const keep = (_entry: unknown, index: number) => !drop.has(index)\n const { samples: all, ...rest } = dataset\n const samples = all?.filter(keep)\n return { ...rest, records: dataset.records.filter(keep), ...(samples?.some(Boolean) ? { samples } : {}) }\n}\n\n// ── A sample row's date (AGL-4065) ──────────────────────────────────────\n\n/** A field whose name says it holds when a record happens. */\nconst DATE_FIELD = /\\b(?:date|dates|day|when)\\b/i\n\n/** A field a record has only once it has happened: a race's result, a match's score. */\nconst OUTCOME_FIELD = /\\b(?:results?|finish(?:ed)?|placings?|placed|winners?|scores?|outcome)\\b/i\n\n/** A value or a section's name that says its records are behind us. */\nconst PAST_WORDS = /\\b(?:past|previous|completed|finished|results?|earlier|archived?|last season)\\b/i\n\n/** The days a sample's date steps by, and how far from the job's day the first falls: a season of weekends. */\nexport const AI_DATASET_SAMPLE_DATES = { stepDays: 21, leadDays: 14 } as const\n\nconst DAY_MS = 86_400_000\n\n/** The Saturday on or after (`ahead`) or on or before a day, at noon UTC. */\nfunction saturday(from: Date, ahead: boolean): Date {\n const day = new Date(Date.UTC(from.getUTCFullYear(), from.getUTCMonth(), from.getUTCDate(), 12))\n const shift = ahead ? (6 - day.getUTCDay() + 7) % 7 : -((day.getUTCDay() + 1) % 7)\n return new Date(day.getTime() + shift * DAY_MS)\n}\n\n/**\n * A dated list's SAMPLE records given real dates (AGL-4065): a sample the\n * brief gave no date for falls on a weekend of a season from the job's day —\n * the first Saturday at least two weeks out, then every three weeks — and a\n * sample that is behind us (its split value or the list says past: \"Past\n * results\") on the Saturdays as far back, the latest first, with no result\n * left on one still to come. Written as a visitor reads a date, in the\n * site's language. A record from the brief keeps its own date. Pure.\n */\nexport function aiDatasetSampleDates(\n dataset: AiDataset,\n options: { now: Date; language?: string | null; split?: AiDatasetSplit | null; pastOnly?: boolean },\n): AiDataset {\n const samples = dataset.samples ?? []\n const dates = dataset.fields.flatMap((field, column) => (column > 0 && field.type === 'text' && DATE_FIELD.test(field.name) ? [column] : []))\n if (!samples.some(Boolean) || !dates.length) return dataset\n const splitColumn = options.split\n ? dataset.fields.findIndex((field) => field.name.trim().toLowerCase() === (options.split as AiDatasetSplit).field.trim().toLowerCase())\n : -1\n const outcomes = dataset.fields.flatMap((field, column) => (column > 0 && OUTCOME_FIELD.test(field.name) ? [column] : []))\n let format: Intl.DateTimeFormat\n try {\n format = new Intl.DateTimeFormat(options.language || 'en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' })\n } catch {\n format = new Intl.DateTimeFormat('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' })\n }\n const { stepDays, leadDays } = AI_DATASET_SAMPLE_DATES\n const firstAhead = saturday(new Date(options.now.getTime() + leadDays * DAY_MS), true)\n const firstBehind = saturday(new Date(options.now.getTime() - leadDays * DAY_MS), false)\n let ahead = 0\n let behind = 0\n return {\n ...dataset,\n records: dataset.records.map((values, index) => {\n if (!samples[index]) return [...values]\n const past = options.pastOnly || (splitColumn > 0 && PAST_WORDS.test(values[splitColumn] ?? ''))\n const at = past\n ? new Date(firstBehind.getTime() - behind++ * stepDays * DAY_MS)\n : new Date(firstAhead.getTime() + ahead++ * stepDays * DAY_MS)\n const said = format.format(at)\n return values.map((value, column) => (dates.includes(column) ? said : !past && outcomes.includes(column) ? '' : value))\n }),\n }\n}\n\n/** Whether every section that lists a dataset says its records are behind us (\"Past results\", \"Race results\"). */\nexport function aiDatasetListedAsPast(sections: readonly string[]): boolean {\n return sections.length > 0 && sections.every((section) => PAST_WORDS.test(section))\n}\n\nexport interface GenerateAiDatasetInput extends AiDatasetPromptInput {\n /** What a claim or a price may be quoted from: the brief and the site's answers. */\n merchantWords: string\n settings?: AiPluginSettings\n model?: string\n maxTokens?: number\n signal?: AbortSignal\n provider?: AiProvider\n}\n\n/** Design one dataset. It writes nothing to the site. */\nexport function generateAiDataset(input: GenerateAiDatasetInput): Promise<AiValidatedGeneration<AiDataset>> {\n const row = AI_ROUTING_TABLE[AI_DATASET_STEP]\n return runValidatedGeneration(AI_DATASET_KIND, {\n step: AI_DATASET_STEP,\n ...(input.settings ? { settings: input.settings } : {}),\n ...(input.model ? { model: input.model } : {}),\n instructions: AI_DATASET_INSTRUCTIONS,\n messages: [{ role: 'user', content: aiDatasetPrompt(input) }],\n tool: AI_DATASET_TOOL,\n maxTokens: input.maxTokens ?? AI_DATASET_MAX_TOKENS,\n ...(row.thinking ? { thinking: row.thinking } : {}),\n ...(row.effort ? { effort: row.effort } : {}),\n ...(input.signal ? { signal: input.signal } : {}),\n ...(input.provider ? { provider: input.provider } : {}),\n check: (answer) => checkAiDatasetRecords(answer, { planned: input.fields, merchantWords: input.merchantWords, name: input.name }),\n }) as Promise<AiValidatedGeneration<AiDataset>>\n}\n\n/**\n * A designed dataset as the data plugin's `dataset` writer takes it: values by\n * field name. Its words' \"a\" and \"an\" agree with what follows them in English\n * (`aiLayoutArticles`, AGL-3988), as a page's do: the beta.270 Sugar Hollow\n * packages said \"A easygoing session\" in their records and on every page\n * that listed them.\n */\nexport function aiDatasetDraftContent(\n name: string,\n dataset: AiDataset,\n options: { recordPages: boolean; language?: string | null },\n): Record<string, unknown> {\n const said = (value: string) => aiLayoutArticles(value, options.language)\n const records = dataset.records.map((values) => {\n const record: Record<string, unknown> = {}\n dataset.fields.forEach((field, column) => {\n const value = (values[column] ?? '').trim()\n if (!value) return\n if (field.type === 'list') {\n record[field.name] = value.split(AI_DATASET_LIST_SEPARATOR.trim()).map((part) => said(part.trim())).filter(Boolean)\n } else if (field.type === 'number' || field.type === 'integer') {\n record[field.name] = value.replace(/,/g, '')\n } else if (field.type === 'text') {\n record[field.name] = said(value)\n } else {\n record[field.name] = value\n }\n })\n return record\n })\n return {\n name,\n fields: dataset.fields.map((field) => ({ name: field.name, type: field.type })),\n records,\n // Which records are samples for the owner to replace (AGL-4065), which Data marks.\n ...(dataset.samples?.some(Boolean) ? { samples: dataset.records.map((_values, index) => dataset.samples?.[index] === true) } : {}),\n // A record page shows each record at an address made from its name.\n ...(options.recordPages && dataset.fields[0] ? { pageAddressFrom: dataset.fields[0].name } : {}),\n }\n}\n"],"names":["AI_ROUTING_TABLE","AI_DATASET_LIMITS","AI_DATASET_LIST_SEPARATOR","AI_DATASET_TOOL","AI_DATASET_TOOL_NAME","checkAiDataset","runValidatedGeneration","aiLayoutArticles","aiTextNamesBrand","AI_DATASET_STEP","AI_DATASET_MAX_TOKENS","Math","min","maxTokens","AI_DATASET_KIND","AI_DATASET_LIST_WITHOUT_ENTRIES","WITHOUT_ENTRIES_RULE","AI_DATASET_INSTRUCTIONS","text","recordsMin","recordsMax","valueMax","map","rule","join","cacheBreakpoint","listed","names","last","quoted","name","length","slice","aiDatasetSplitLine","split","sections","field","values","aiDatasetPrompt","input","brief","trim","why","fields","shownIn","recordPages","AI_DATASET_PLACEHOLDER_REASK","AI_DATASET_PLACEHOLDER_CODE","PLACEHOLDER_TEXT","NUMBERED_NAME","STAND_IN_WORDS","Set","stemWords","toLowerCase","filter","Boolean","word","endsWith","aiDatasetPlaceholderRecords","dataset","own","flatMap","records","index","match","some","value","test","exec","replace","words","every","has","answerShape","answer","Array","isArray","record","checkAiDatasetRecords","context","checked","shape","placeholders","rawRecords","branded","sample","brand","merchantWords","find","violations","offending","push","code","message","paths","aiDatasetWithoutPlaceholders","drop","kept","_record","aiDatasetRealRecords","samples","forEach","add","size","keep","_entry","all","rest","DATE_FIELD","OUTCOME_FIELD","PAST_WORDS","AI_DATASET_SAMPLE_DATES","stepDays","leadDays","DAY_MS","saturday","from","ahead","day","Date","UTC","getUTCFullYear","getUTCMonth","getUTCDate","shift","getUTCDay","getTime","aiDatasetSampleDates","options","dates","column","type","splitColumn","findIndex","outcomes","format","Intl","DateTimeFormat","language","weekday","month","year","timeZone","firstAhead","now","firstBehind","behind","past","pastOnly","at","said","includes","aiDatasetListedAsPast","section","generateAiDataset","row","step","settings","model","instructions","messages","role","content","tool","thinking","effort","signal","provider","check","planned","aiDatasetDraftContent","part","_values","pageAddressFrom"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;;AAID,SAASA,gBAAgB,QAA+B,0BAAsB;AAC9E,SACEC,iBAAiB,EACjBC,yBAAyB,EACzBC,eAAe,EACfC,oBAAoB,EACpBC,cAAc,QAET,8BAA0B;AACjC,SAASC,sBAAsB,QAAkE,mBAAe;AAEhH,SAASC,gBAAgB,QAAQ,uCAAmC;AACpE,SAASC,gBAAgB,QAAQ,0BAAsB;AAGvD;;;;;;;;;;;;CAYC,GAED,qCAAqC,GACrC,OAAO,MAAMC,kBAA8B,eAAc;AAEzD;;;;CAIC,GACD,OAAO,MAAMC,wBAAwBC,KAAKC,GAAG,CAAC,MAAOZ,gBAAgB,CAACS,gBAAgB,CAACI,SAAS,EAAC;AAEjG,6EAA6E,GAC7E,OAAO,MAAMC,kBAAkB,UAAS;AAExC;;;;;;;;CAQC,GACD,OAAO,MAAMC,kCAAuD,UAAS;AAE7E,2EAA2E,GAC3E,MAAMC,uBACJD,oCAAoC,YAChC,yhBACA;AAEN,gEAAgE,GAChE,OAAO,MAAME,0BAA2C;IACtD;QACEC,MACE,gOACA,CAAC,kBAAkB,EAAEd,qBAAqB,mDAAmD,CAAC,GAC9F,aACA;YACE;YACA;YACA,CAAC,MAAM,EAAEH,kBAAkBkB,UAAU,CAAC,IAAI,EAAElB,kBAAkBmB,UAAU,CAAC,6KAA6K,CAAC;YACvP;YACA,kFAAkF;YAClFJ;YACA;YACA;YACA,CAAC,+EAA+E,EAAEf,kBAAkBoB,QAAQ,CAAC,0DAA0D,CAAC;YACxK;SACD,CACEC,GAAG,CAAC,CAACC,OAAS,CAAC,EAAE,EAAEA,MAAM,EACzBC,IAAI,CAAC;QACVC,iBAAiB;IACnB;CACD,CAAA;AAgCD,sDAAsD,GACtD,SAASC,OAAOC,KAAwB,EAAEC,IAAkB;QAE6CC;IADvG,MAAMA,SAASF,MAAML,GAAG,CAAC,CAACQ,OAAS,CAAC,CAAC,EAAEA,KAAK,CAAC,CAAC;IAC9C,OAAOD,OAAOE,MAAM,GAAG,IAAI,GAAGF,OAAOG,KAAK,CAAC,GAAG,CAAC,GAAGR,IAAI,CAAC,MAAM,CAAC,EAAEI,KAAK,CAAC,EAAEC,MAAM,CAACA,OAAOE,MAAM,GAAG,EAAE,EAAE,IAAIF,WAAAA,MAAM,CAAC,EAAE,YAATA,WAAa;AACtH;AAEA,iFAAiF,GACjF,OAAO,SAASI,mBAAmBC,KAAqB;IACtD,OAAO,CAAC,SAAS,EAAER,OAAOQ,MAAMC,QAAQ,EAAE,OAAO,yBAAyB,EAAED,MAAME,KAAK,CAAC,sBAAsB,EAAEV,OAAOQ,MAAMG,MAAM,EAAE,MAAM,CAAC,CAAC;AAC/I;AAEA,yEAAyE,GACzE,OAAO,SAASC,gBAAgBC,KAA2B;IACzD,OAAO;QACL;QACAA,MAAMC,KAAK,CAACC,IAAI;QAChB,CAAC,UAAU,EAAEF,MAAMT,IAAI,CAAC,IAAI,EAAES,MAAMG,GAAG,CAACD,IAAI,MAAM,iCAAiC;QACnF,CAAC,gBAAgB,EAAEF,MAAMI,MAAM,CAACZ,MAAM,GAAGQ,MAAMI,MAAM,CAACrB,GAAG,CAAC,CAACc,QAAU,CAAC,CAAC,EAAEA,MAAM,CAAC,CAAC,EAAEZ,IAAI,CAAC,QAAQ,uBAAuB;QACvHe,MAAMK,OAAO,CAACb,MAAM,GAAG,CAAC,WAAW,EAAEQ,MAAMK,OAAO,CAACpB,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG;WAC/De,MAAML,KAAK,GAAG;YAACD,mBAAmBM,MAAML,KAAK;SAAE,GAAG,EAAE;QACxDK,MAAMM,WAAW,GACb,+GACA;KACL,CAACrB,IAAI,CAAC;AACT;AAEA,0EAA0E;AAE1E;;;;;;CAMC,GACD,OAAO,MAAMsB,+BACX/B,oCAAoC,YAChC,iPACA,sPAAqP;AAE3P,gEAAgE,GAChE,OAAO,MAAMgC,8BAA8B,sBAAqB;AAEhE,wGAAwG,GACxG,MAAMC,mBACJ;AAEF,oGAAoG,GACpG,MAAMC,gBAAgB;AAEtB,iHAAiH,GACjH,MAAMC,iBAAiB,IAAIC,IACzB,2JAA2JjB,KAAK,CAAC;AAGnK,gFAAgF,GAChF,MAAMkB,YAAY,CAAClC,OACjBA,KACGmC,WAAW,GACXnB,KAAK,CAAC,8maACNoB,MAAM,CAACC,SACPjC,GAAG,CAAC,CAACkC,OAAUA,KAAKzB,MAAM,GAAG,KAAKyB,KAAKC,QAAQ,CAAC,QAAQ,CAACD,KAAKC,QAAQ,CAAC,QAAQD,KAAKxB,KAAK,CAAC,GAAG,CAAC,KAAKwB;AAExG;;;;;;;;CAQC,GACD,OAAO,SAASE,4BACdC,OAA+G,EAC/G7B,OAAO,EAAE;IAET,MAAM8B,MAAM,IAAIT,IAAI;WAAIC,UAAUtB;WAAU6B,QAAQhB,MAAM,CAACkB,OAAO,CAAC,CAACzB,QAAUgB,UAAUhB,MAAMN,IAAI;KAAG;IACrG,OAAO6B,QAAQG,OAAO,CAACD,OAAO,CAAC,CAACxB,QAAQ0B;YAEJ1B,UAEV2B;QAHxB,IAAI3B,OAAO4B,IAAI,CAAC,CAACC,QAAUlB,iBAAiBmB,IAAI,CAACD,gBAAAA,QAAS,MAAM,OAAO;YAACH;SAAM;QAC9E,MAAMC,QAAQf,cAAcmB,IAAI,CAAC,EAAC/B,WAAAA,MAAM,CAAC,EAAE,YAATA,WAAa,IAAIgC,OAAO,CAAC,QAAQ,KAAK5B,IAAI;QAC5E,IAAI,CAACuB,OAAO,OAAO,EAAE;QACrB,MAAMM,QAAQlB,WAAUY,UAAAA,KAAK,CAAC,EAAE,YAARA,UAAY;QACpC,OAAOM,MAAMC,KAAK,CAAC,CAACf,OAASI,IAAIY,GAAG,CAAChB,SAASN,eAAesB,GAAG,CAAChB,SAAS;YAACO;SAAM,GAAG,EAAE;IACxF;AACF;AAEA,uGAAuG,GACvG,SAASU,YAAYC,MAAkD;IAIrE,MAAMxD,OAAO,CAACgD,QAAoB,OAAOA,UAAU,WAAWA,MAAMG,OAAO,CAAC,QAAQ,KAAK5B,IAAI,KAAK;IAClG,MAAME,SAAS,AAACgC,CAAAA,MAAMC,OAAO,CAACF,0BAAAA,MAAQ,CAAC,SAAS,IAAKA,0BAAAA,MAAQ,CAAC,SAAS,GAAiB,EAAE,AAAD,EAAGpD,GAAG,CAAC,CAACc,QAAW,CAAA;YAC1GN,MAAMZ,KAAMkB,yBAAD,AAACA,KAA0C,CAAC,OAAO;QAChE,CAAA;IACA,MAAM0B,UAAU,AAACa,CAAAA,MAAMC,OAAO,CAACF,0BAAAA,MAAQ,CAAC,UAAU,IAAKA,0BAAAA,MAAQ,CAAC,UAAU,GAAiB,EAAE,AAAD,EAAGpD,GAAG,CAAC,CAACuD;QAClG,MAAMxC,SAAUwC,0BAAD,AAACA,MAA2C,CAAC,SAAS;QACrE,OAAOF,MAAMC,OAAO,CAACvC,UAAUA,OAAOf,GAAG,CAACJ,QAAQ,EAAE;IACtD;IACA,OAAO;QAAEyB;QAAQmB;IAAQ;AAC3B;AASA;;;;CAIC,GACD,OAAO,SAASgB,sBAAsBJ,MAA+B,EAAEK,OAA8B;QAalDC;IAZjD,MAAMA,UAAU3E,eAAeqE,QAAQK;IACvC,MAAME,QAAQR,YAAYC;IAC1B,MAAMQ,eAAexB,4BAA4BuB,OAAOF,QAAQjD,IAAI;IACpE,+EAA+E;IAC/E,MAAMqD,aAAaR,MAAMC,OAAO,CAACF,MAAM,CAAC,UAAU,IAAKA,MAAM,CAAC,UAAU,GAAiB,EAAE;IAC3F,MAAMU,UAAUH,MAAMnB,OAAO,CAACD,OAAO,CAAC,CAACxB,QAAQ0B;YAC7BoB;QAAhB,MAAME,SAAS,EAACF,oBAAAA,UAAU,CAACpB,MAAM,qBAAlB,AAACoB,iBAAsD,CAAC,SAAS,MAAK;QACrF,MAAMG,QAAQD,SAAShD,OAAOf,GAAG,CAAC,CAAC4C,QAAU1D,iBAAiB0D,OAAOa,QAAQQ,aAAa,GAAGC,IAAI,CAACjC,WAAW;QAC7G,OAAO+B,QAAQ;YAAC;gBAAEvB;gBAAOuB;YAAM;SAAE,GAAG,EAAE;IACxC;IACA,IAAI,CAACJ,aAAanD,MAAM,IAAI,CAACqD,QAAQrD,MAAM,EAAE,OAAOiD;IACpD,MAAMS,aAAa;WAAIT,QAAQS,UAAU;KAAC;IAC1C,MAAMC,YAAqC,cAAMV,qBAAAA,QAAQU,SAAS,YAAjBV,qBAAqB,CAAC;IACvE,IAAIE,aAAanD,MAAM,EAAE;;YAOwDkD;QAN/EQ,WAAWE,IAAI,CAAC;YACdpE,MAAM;YACNqE,MAAM7C;YACN8C,SAAS/C;YACTgD,OAAOZ,aAAa5D,GAAG,CAAC,CAACyC,QAAU,CAAC,QAAQ,EAAEA,MAAM,CAAC,CAAC;QACxD;QACA,KAAK,MAAMA,SAASmB,aAAalD,KAAK,CAAC,GAAG,GAAI0D,SAAS,CAAC,CAAC,QAAQ,EAAE3B,MAAM,CAAC,CAAC,CAAC,YAAGkB,uBAAAA,MAAMnB,OAAO,CAACC,MAAM,qBAApBkB,oBAAsB,CAAC,EAAE,mBAAI;IAC9G;IACA,KAAK,MAAM,EAAElB,KAAK,EAAEuB,KAAK,EAAE,IAAIF,QAAS;QACtCK,WAAWE,IAAI,CAAC;YACdpE,MAAM;YACNqE,MAAM;YACNC,SAAS,CAAC,cAAc,EAAE9B,QAAQ,EAAE,iBAAiB,EAAEuB,MAAM,uEAAuE,CAAC;YACrIQ,OAAO;gBAAC,CAAC,QAAQ,EAAE/B,MAAM,CAAC,CAAC;aAAC;QAC9B;IACF;IACA,OAAO;QAAEG,OAAO;QAAMuB;QAAYC;IAAU;AAC9C;AAEA;;;;CAIC,GACD,OAAO,SAASK,6BAA6BrB,MAAkD,EAAEK,OAA8B;IAC7H,IAAI,CAACL,QAAQ,OAAO;IACpB,MAAMsB,OAAO,IAAI7C,IAAIO,4BAA4Be,YAAYC,SAASK,QAAQjD,IAAI;IAClF,MAAMgC,UAAUa,MAAMC,OAAO,CAACF,MAAM,CAAC,UAAU,IAAKA,MAAM,CAAC,UAAU,GAAiB,EAAE;IACxF,MAAMuB,OAAO,aAAKvB;QAAQZ,SAASA,QAAQR,MAAM,CAAC,CAAC4C,SAASnC,QAAU,CAACiC,KAAKxB,GAAG,CAACT;;IAChF,+GAA+G;IAC/G,MAAMiB,UAAU3E,eAAe4F,MAAM,aAAKlB;QAAS5D,YAAY;;IAC/D,OAAO6D,QAAQd,KAAK,IAAI,CAACc,QAAQS,UAAU,CAAC1D,MAAM,GAAGiD,QAAQd,KAAK,GAAG;AACvE;AAEA;;;;CAIC,GACD,OAAO,SAASiC,qBAAqBxC,OAAkB,EAAE7B,IAAY;QAElB6B;IADjD,MAAMqC,OAAO,IAAI7C,IAAIO,4BAA4BC,SAAS7B;IAC1D,IAAIf,oCAAoC,UAAS4C,mBAAAA,QAAQyC,OAAO,qBAAfzC,iBAAiB0C,OAAO,CAAC,CAAChB,QAAQtB,QAAUsB,UAAUW,KAAKM,GAAG,CAACvC;IAChH,IAAI,CAACiC,KAAKO,IAAI,EAAE,OAAO5C;IACvB,MAAM6C,OAAO,CAACC,QAAiB1C,QAAkB,CAACiC,KAAKxB,GAAG,CAACT;IAC3D,MAAM,EAAEqC,SAASM,GAAG,EAAW,GAAG/C,SAATgD,wCAAShD;;;IAClC,MAAMyC,UAAUM,uBAAAA,IAAKpD,MAAM,CAACkD;IAC5B,OAAO,aAAKG;QAAM7C,SAASH,QAAQG,OAAO,CAACR,MAAM,CAACkD;OAAWJ,CAAAA,2BAAAA,QAASnC,IAAI,CAACV,YAAW;QAAE6C;IAAQ,IAAI,CAAC;AACvG;AAEA,2EAA2E;AAE3E,4DAA4D,GAC5D,MAAMQ,aAAa;AAEnB,sFAAsF,GACtF,MAAMC,gBAAgB;AAEtB,qEAAqE,GACrE,MAAMC,aAAa;AAEnB,6GAA6G,GAC7G,OAAO,MAAMC,0BAA0B;IAAEC,UAAU;IAAIC,UAAU;AAAG,EAAU;AAE9E,MAAMC,SAAS;AAEf,2EAA2E,GAC3E,SAASC,SAASC,IAAU,EAAEC,KAAc;IAC1C,MAAMC,MAAM,IAAIC,KAAKA,KAAKC,GAAG,CAACJ,KAAKK,cAAc,IAAIL,KAAKM,WAAW,IAAIN,KAAKO,UAAU,IAAI;IAC5F,MAAMC,QAAQP,QAAQ,AAAC,CAAA,IAAIC,IAAIO,SAAS,KAAK,CAAA,IAAK,IAAI,CAAE,CAAA,AAACP,CAAAA,IAAIO,SAAS,KAAK,CAAA,IAAK,CAAA;IAChF,OAAO,IAAIN,KAAKD,IAAIQ,OAAO,KAAKF,QAAQV;AAC1C;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASa,qBACdpE,OAAkB,EAClBqE,OAAmG;QAEnFrE;IAAhB,MAAMyC,WAAUzC,mBAAAA,QAAQyC,OAAO,YAAfzC,mBAAmB,EAAE;IACrC,MAAMsE,QAAQtE,QAAQhB,MAAM,CAACkB,OAAO,CAAC,CAACzB,OAAO8F,SAAYA,SAAS,KAAK9F,MAAM+F,IAAI,KAAK,UAAUvB,WAAWzC,IAAI,CAAC/B,MAAMN,IAAI,IAAI;YAACoG;SAAO,GAAG,EAAE;IAC3I,IAAI,CAAC9B,QAAQnC,IAAI,CAACV,YAAY,CAAC0E,MAAMlG,MAAM,EAAE,OAAO4B;IACpD,MAAMyE,cAAcJ,QAAQ9F,KAAK,GAC7ByB,QAAQhB,MAAM,CAAC0F,SAAS,CAAC,CAACjG,QAAUA,MAAMN,IAAI,CAACW,IAAI,GAAGY,WAAW,OAAO,AAAC2E,QAAQ9F,KAAK,CAAoBE,KAAK,CAACK,IAAI,GAAGY,WAAW,MAClI,CAAC;IACL,MAAMiF,WAAW3E,QAAQhB,MAAM,CAACkB,OAAO,CAAC,CAACzB,OAAO8F,SAAYA,SAAS,KAAKrB,cAAc1C,IAAI,CAAC/B,MAAMN,IAAI,IAAI;YAACoG;SAAO,GAAG,EAAE;IACxH,IAAIK;IACJ,IAAI;QACFA,SAAS,IAAIC,KAAKC,cAAc,CAACT,QAAQU,QAAQ,IAAI,SAAS;YAAEC,SAAS;YAASC,OAAO;YAAStB,KAAK;YAAWuB,MAAM;YAAWC,UAAU;QAAM;IACrJ,EAAE,eAAM;QACNP,SAAS,IAAIC,KAAKC,cAAc,CAAC,SAAS;YAAEE,SAAS;YAASC,OAAO;YAAStB,KAAK;YAAWuB,MAAM;YAAWC,UAAU;QAAM;IACjI;IACA,MAAM,EAAE9B,QAAQ,EAAEC,QAAQ,EAAE,GAAGF;IAC/B,MAAMgC,aAAa5B,SAAS,IAAII,KAAKS,QAAQgB,GAAG,CAAClB,OAAO,KAAKb,WAAWC,SAAS;IACjF,MAAM+B,cAAc9B,SAAS,IAAII,KAAKS,QAAQgB,GAAG,CAAClB,OAAO,KAAKb,WAAWC,SAAS;IAClF,IAAIG,QAAQ;IACZ,IAAI6B,SAAS;IACb,OAAO,aACFvF;QACHG,SAASH,QAAQG,OAAO,CAACxC,GAAG,CAAC,CAACe,QAAQ0B;gBAEiC1B;YADrE,IAAI,CAAC+D,OAAO,CAACrC,MAAM,EAAE,OAAO;mBAAI1B;aAAO;YACvC,MAAM8G,OAAOnB,QAAQoB,QAAQ,IAAKhB,cAAc,KAAKtB,WAAW3C,IAAI,EAAC9B,sBAAAA,MAAM,CAAC+F,YAAY,YAAnB/F,sBAAuB;YAC5F,MAAMgH,KAAKF,OACP,IAAI5B,KAAK0B,YAAYnB,OAAO,KAAKoB,WAAWlC,WAAWE,UACvD,IAAIK,KAAKwB,WAAWjB,OAAO,KAAKT,UAAUL,WAAWE;YACzD,MAAMoC,OAAOf,OAAOA,MAAM,CAACc;YAC3B,OAAOhH,OAAOf,GAAG,CAAC,CAAC4C,OAAOgE,SAAYD,MAAMsB,QAAQ,CAACrB,UAAUoB,OAAO,CAACH,QAAQb,SAASiB,QAAQ,CAACrB,UAAU,KAAKhE;QAClH;;AAEJ;AAEA,gHAAgH,GAChH,OAAO,SAASsF,sBAAsBrH,QAA2B;IAC/D,OAAOA,SAASJ,MAAM,GAAG,KAAKI,SAASoC,KAAK,CAAC,CAACkF,UAAY3C,WAAW3C,IAAI,CAACsF;AAC5E;AAYA,uDAAuD,GACvD,OAAO,SAASC,kBAAkBnH,KAA6B;QAShDA;IARb,MAAMoH,MAAM3J,gBAAgB,CAACS,gBAAgB;IAC7C,OAAOH,uBAAuBQ,iBAAiB;QAC7C8I,MAAMnJ;OACF8B,MAAMsH,QAAQ,GAAG;QAAEA,UAAUtH,MAAMsH,QAAQ;IAAC,IAAI,CAAC,GACjDtH,MAAMuH,KAAK,GAAG;QAAEA,OAAOvH,MAAMuH,KAAK;IAAC,IAAI,CAAC;QAC5CC,cAAc9I;QACd+I,UAAU;YAAC;gBAAEC,MAAM;gBAAQC,SAAS5H,gBAAgBC;YAAO;SAAE;QAC7D4H,MAAMhK;QACNU,SAAS,GAAE0B,mBAAAA,MAAM1B,SAAS,YAAf0B,mBAAmB7B;OAC1BiJ,IAAIS,QAAQ,GAAG;QAAEA,UAAUT,IAAIS,QAAQ;IAAC,IAAI,CAAC,GAC7CT,IAAIU,MAAM,GAAG;QAAEA,QAAQV,IAAIU,MAAM;IAAC,IAAI,CAAC,GACvC9H,MAAM+H,MAAM,GAAG;QAAEA,QAAQ/H,MAAM+H,MAAM;IAAC,IAAI,CAAC,GAC3C/H,MAAMgI,QAAQ,GAAG;QAAEA,UAAUhI,MAAMgI,QAAQ;IAAC,IAAI,CAAC;QACrDC,OAAO,CAAC9F,SAAWI,sBAAsBJ,QAAQ;gBAAE+F,SAASlI,MAAMI,MAAM;gBAAE4C,eAAehD,MAAMgD,aAAa;gBAAEzD,MAAMS,MAAMT,IAAI;YAAC;;AAEnI;AAEA;;;;;;CAMC,GACD,OAAO,SAAS4I,sBACd5I,IAAY,EACZ6B,OAAkB,EAClBqE,OAA2D;QAyBrDrE;IAvBN,MAAM2F,OAAO,CAACpF,QAAkB3D,iBAAiB2D,OAAO8D,QAAQU,QAAQ;IACxE,MAAM5E,UAAUH,QAAQG,OAAO,CAACxC,GAAG,CAAC,CAACe;QACnC,MAAMwC,SAAkC,CAAC;QACzClB,QAAQhB,MAAM,CAAC0D,OAAO,CAAC,CAACjE,OAAO8F;gBACd7F;YAAf,MAAM6B,QAAQ,EAAC7B,iBAAAA,MAAM,CAAC6F,OAAO,YAAd7F,iBAAkB,IAAII,IAAI;YACzC,IAAI,CAACyB,OAAO;YACZ,IAAI9B,MAAM+F,IAAI,KAAK,QAAQ;gBACzBtD,MAAM,CAACzC,MAAMN,IAAI,CAAC,GAAGoC,MAAMhC,KAAK,CAAChC,0BAA0BuC,IAAI,IAAInB,GAAG,CAAC,CAACqJ,OAASrB,KAAKqB,KAAKlI,IAAI,KAAKa,MAAM,CAACC;YAC7G,OAAO,IAAInB,MAAM+F,IAAI,KAAK,YAAY/F,MAAM+F,IAAI,KAAK,WAAW;gBAC9DtD,MAAM,CAACzC,MAAMN,IAAI,CAAC,GAAGoC,MAAMG,OAAO,CAAC,MAAM;YAC3C,OAAO,IAAIjC,MAAM+F,IAAI,KAAK,QAAQ;gBAChCtD,MAAM,CAACzC,MAAMN,IAAI,CAAC,GAAGwH,KAAKpF;YAC5B,OAAO;gBACLW,MAAM,CAACzC,MAAMN,IAAI,CAAC,GAAGoC;YACvB;QACF;QACA,OAAOW;IACT;IACA,OAAO;QACL/C;QACAa,QAAQgB,QAAQhB,MAAM,CAACrB,GAAG,CAAC,CAACc,QAAW,CAAA;gBAAEN,MAAMM,MAAMN,IAAI;gBAAEqG,MAAM/F,MAAM+F,IAAI;YAAC,CAAA;QAC5ErE;OAEIH,EAAAA,mBAAAA,QAAQyC,OAAO,qBAAfzC,iBAAiBM,IAAI,CAACV,YAAW;QAAE6C,SAASzC,QAAQG,OAAO,CAACxC,GAAG,CAAC,CAACsJ,SAAS7G;gBAAUJ;mBAAAA,EAAAA,mBAAAA,QAAQyC,OAAO,qBAAfzC,gBAAiB,CAACI,MAAM,MAAK;;IAAM,IAAI,CAAC,GAE5HiE,QAAQnF,WAAW,IAAIc,QAAQhB,MAAM,CAAC,EAAE,GAAG;QAAEkI,iBAAiBlH,QAAQhB,MAAM,CAAC,EAAE,CAACb,IAAI;IAAC,IAAI,CAAC;AAElG"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-dataset-generation.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiStepKind } from '../providers/catalog'\nimport type { AiProvider } from '../providers/contract'\nimport { AI_ROUTING_TABLE, type AiPluginSettings } from '../providers/routing'\nimport {\n AI_DATASET_LIMITS,\n AI_DATASET_LIST_SEPARATOR,\n AI_DATASET_TOOL,\n AI_DATASET_TOOL_NAME,\n checkAiDataset,\n type AiDataset,\n} from '../tools/ai-dataset-tool'\nimport { runValidatedGeneration, type AiGenerationCheckResult, type AiValidatedGeneration } from './ai-doctrine'\nimport type { AiDoctrineViolation } from './ai-doctrine-validators'\nimport { aiLayoutArticles } from '../layout-language/ai-layout-copy'\nimport { aiTextNamesBrand } from '../model/ai-products'\nimport type { AiSystemBlock } from './ai-runtime'\n\n/**\n * A SITE'S DATASETS (AGL-3616): the typed fields and first records of one\n * dataset a site plan created, through the doctrine's generation call and the\n * strict `submit_dataset` tool. The data plugin's writer stores what it\n * answers; nothing here writes.\n *\n * Routed as `job.products`, the row a store's first products are proposed on\n * (no thinking, a structured list from a brief, held to the storefront claim\n * rules): one dataset of a dozen records is the same work. Its answer is held\n * to a ceiling of its own under that row's. The rules are cached and\n * byte-identical; the site's words, the dataset and the pages that show it\n * ride in the user turn.\n */\n\n/** The step a dataset is routed as. */\nexport const AI_DATASET_STEP: AiStepKind = 'job.products'\n\n/**\n * One dataset's answer ceiling: twelve fields and twelve records of a few\n * sentences each, written as JSON, about 9,000 characters at three a token\n * with room; under the routing row's.\n */\nexport const AI_DATASET_MAX_TOKENS = Math.min(4_000, AI_ROUTING_TABLE[AI_DATASET_STEP].maxTokens)\n\n/** The generation kind a dataset is asked under, which the doctrine scopes. */\nexport const AI_DATASET_KIND = 'dataset'\n\n/**\n * THE Q2 DECISION (AGL-4065): what a list the brief gives no entries for starts\n * with. `samples` (decided 2026-10-28): complete-looking SAMPLE records the\n * owner replaces, each marked a sample in Data, a dated list's dates set by\n * the platform (`aiDatasetSampleDates`), so the site looks complete and no\n * row is a placeholder. `empty`: the fields and no records, and the pages\n * say the entries are on the way. The rules, the tool's answer and the\n * pages' brief all follow this one switch.\n */\nexport const AI_DATASET_LIST_WITHOUT_ENTRIES: 'samples' | 'empty' = 'samples'\n\n/** What a list the brief gives no entries for is told to hold (AGL-4065). */\nconst WITHOUT_ENTRIES_RULE =\n AI_DATASET_LIST_WITHOUT_ENTRIES === 'samples'\n ? 'Where the brief gives no entries for the list, write sample records the owner replaces, each marked sample: a complete entry of its kind, named in the brief’s own words (such as \"Series Round 1 at Mill Creek\" for a race team that rides at Mill Creek), its other values typical of it. This is the one place a record may give a value the brief does not, and a sample still gives no price, phone number, email, address, real person’s name or brand. Leave a date \"\" in a sample, for the platform to set, and name no month or date in it. ' +\n // AGL-4092: six sample riders' bios ended \"Add this rider's story here.\", and six past races' results were \"Add finishing position\".\n 'A sample’s words read as finished copy about it: never an instruction to the owner, such as \"Add this rider\\'s story here\" or \"Add finishing position\", and never a note that it is a sample, which the console says.'\n : 'Where the brief gives no entries for the list, give the fields and no records: an empty list is an answer, a numbered placeholder is not.'\n\n/** The rules for one dataset. Byte-identical on every request. */\nexport const AI_DATASET_INSTRUCTIONS: AiSystemBlock[] = [\n {\n text:\n 'You design one dataset of a new website: a list of like things — a menu, a team, services, portfolio pieces, events, questions and answers — kept as records the owner edits in one place, which the site’s pages list.\\n\\n' +\n `Answer by calling ${AI_DATASET_TOOL_NAME} exactly once. A reply in prose cannot be used.\\n\\n` +\n 'Rules:\\n' +\n [\n 'Keep the planned fields, in order and named exactly as planned; the pages around the dataset already name them. Add a field only where the records clearly need it.',\n 'Type each field: text for words, including a date or a time written the way a visitor reads it; number or integer only for a figure a visitor compares; boolean for yes or no; list for a few short tags. The first field names each record and is text.',\n `Write ${AI_DATASET_LIMITS.recordsMin} to ${AI_DATASET_LIMITS.recordsMax} records, as many as the brief supports: what the business actually offers, in its own words where the brief gives them, otherwise plain and general enough to be true of it.`,\n 'Never invent a fact: no price, date, address, phone number, award, credential, client or number the brief does not give, and no review, testimonial, rating or quote from anyone. Leave a value \"\" rather than make one up.',\n // AGL-4065: a race schedule with no dates came back as ten numbered placeholders.\n WITHOUT_ENTRIES_RULE,\n 'Never give a person a name the brief does not give: a team member the brief does not name is their role, such as \"Head chef\".',\n 'Never say anything cures, treats, heals or prevents anything, and never promise a health, financial or legal result.',\n `Values are plain text with no links, markup, emoji or square brackets, at most ${AI_DATASET_LIMITS.valueMax} characters each; a description is one to three sentences.`,\n 'Write in the language of the brief.',\n ]\n .map((rule) => `- ${rule}`)\n .join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\nexport interface AiDatasetPromptInput {\n /** The site's words: the brief and its answers, as a unit's brief carries them. */\n brief: string\n /** The dataset as the plan named it, and why it has one. */\n name: string\n why: string\n /** The field names the plan gave it. */\n fields: readonly string[]\n /** The sections that list it, by page: \"Menu › Our dishes (12 items)\". */\n shownIn: readonly string[]\n /** Whether each record also gets a page of its own. */\n recordPages: boolean\n /**\n * The sections of one page that each list part of it (AGL-4065), and the\n * value of its planned field each lists: \"Upcoming races\" the records whose\n * Status is \"Upcoming\", \"Past results\" those whose Status is \"Past\".\n */\n split?: AiDatasetSplit\n}\n\n/** A page's lists of one dataset, each the records of one value of one field (AGL-4065). */\nexport interface AiDatasetSplit {\n /** The planned field, by name: \"Status\". */\n field: string\n /** The sections, in page order. */\n sections: string[]\n /** The value each section lists, by position. */\n values: string[]\n}\n\n/** Names as a sentence lists them: “A”, “B” and “C”. */\nfunction listed(names: readonly string[], last: 'and' | 'or'): string {\n const quoted = names.map((name) => `“${name}”`)\n return quoted.length > 1 ? `${quoted.slice(0, -1).join(', ')} ${last} ${quoted[quoted.length - 1]}` : (quoted[0] ?? '')\n}\n\n/** What a dataset is told of the sections that each list part of it (AGL-4065). */\nexport function aiDatasetSplitLine(split: AiDatasetSplit): string {\n return `Sections ${listed(split.sections, 'and')} list this dataset: its “${split.field}” field takes exactly ${listed(split.values, 'or')}.`\n}\n\n/** The user turn: the site's words, the dataset, and where it is shown. */\nexport function aiDatasetPrompt(input: AiDatasetPromptInput): string {\n return [\n 'Brief:',\n input.brief.trim(),\n `Dataset: “${input.name}” — ${input.why.trim() || 'the site’s structured content'}`,\n `Planned fields: ${input.fields.length ? input.fields.map((field) => `“${field}”`).join(', ') : '(none: choose them)'}`,\n input.shownIn.length ? `Listed on: ${input.shownIn.join('; ')}.` : 'Listed on the site’s pages.',\n ...(input.split ? [aiDatasetSplitLine(input.split)] : []),\n input.recordPages\n ? 'Each record also gets a page of its own, so give each one enough to read: a sentence or two describing it.'\n : 'Each record is shown as a card, so keep its values short.',\n ].join('\\n')\n}\n\n// ── No placeholder rows (AGL-4065) ─────────────────────────────────────\n\n/**\n * The re-ask a dataset whose records are placeholders is answered with\n * (AGL-4065): the 2026-10-25 Rut Runners Racing start's \"Race events\" came\n * back as \"Upcoming race 1 (details to be added)\" to \"Past race 5 (details to\n * be added)\", because the rules forbid inventing a date and the first field\n * may not be empty. And (AGL-4092) the 2026-10-28 start's sample riders each\n * ended \"Add this rider's story here.\": an instruction to the owner is a\n * placeholder too.\n */\nexport const AI_DATASET_PLACEHOLDER_REASK =\n AI_DATASET_LIST_WITHOUT_ENTRIES === 'samples'\n ? \"Every record names a real thing the brief gives, or is a sample marked sample: a complete entry of its kind in the brief's own words. Never a placeholder such as 'Upcoming race 1 (details to be added)', 'TBD', 'coming soon' or 'Item 1'. \" +\n // AGL-4092: the samples' bios and results told the owner what to add.\n \"Never an instruction to the owner either, such as 'Add this rider's story here' or 'Add finishing position': a sample's words read as finished copy about the sample.\"\n : \"Every record names a real thing the brief gives. Where the brief gives no entries for this list — no dates, no names, no results — answer with the fields and ZERO records; never number placeholders like 'Upcoming race 1 (details to be added)'.\"\n\n/** The code a dataset's placeholder records are refused under. */\nexport const AI_DATASET_PLACEHOLDER_CODE = 'placeholder-records'\n\n/** A value that marks a gap for later rather than saying anything: in a name, a date or anywhere else. */\nconst PLACEHOLDER_TEXT =\n /\\b(?:details?|dates?|info|information|names?)\\s+(?:(?:to be|will be)\\s+)?(?:added|confirmed|announced|determined|decided|filled)\\b|\\bto be (?:added|confirmed|announced|determined|decided)\\b|\\b(?:tbd|tba|t\\.b\\.[ad])(?![a-z])|\\bcoming soon\\b|\\bplaceholder\\b|\\blorem\\b|\\b(?:sample|example|test|dummy) (?:record|entry|row|item|name)s?\\b/i\n\n/** What a sentence tells the owner to do to the record, in place of saying anything: add, write, replace. */\nconst OWNER_VERB = '(?:add|insert|write|enter|put|type|paste|replace|swap|fill in|fill out|update|describe|upload|attach)'\n\n/** How an instruction opens: its own words, imperative, first. */\nconst INSTRUCTION_START = '^\\\\s*(?:please\\\\s+)?'\n\n/**\n * Words that tell the owner to do something, where the record should say\n * something (AGL-4092): \"Add this rider's story here.\", \"Replace with your\n * own photo.\", \"Update the results later.\" Only an imperative is one: \"Riders\n * enter the gate early and race later\" says something, and a visitor's\n * instruction names no place the owner fills (\"Fill in the form on the\n * Contact page\"); both are kept.\n */\nconst OWNER_INSTRUCTION = new RegExp(\n `${INSTRUCTION_START}(?:${OWNER_VERB}\\\\b[^.!?]{0,80}?\\\\b(?:here|later|soon|in data)\\\\b|(?:replace|swap)\\\\b[^.!?]{0,60}?\\\\byour (?:own|real)\\\\b)`,\n 'i',\n)\n\n/** What a record says of itself, which an instruction names as the thing to add: \"Add finishing position\". */\nconst RECORD_PARTS =\n '(?:position|placing|placement|result|results|score|scores|time|times|finish|date|dates|details?|info|information|name|names|story|bio|biography|description|photo|photos|picture|image|link|price|location|address|phone|email|text|copy|content|notes?|summary|title|caption|quote)'\n\n/** Words that are nothing but a bare instruction naming a part of the record: \"Add finishing position\", \"Enter result\". */\nconst BARE_INSTRUCTION = new RegExp(\n `${INSTRUCTION_START}${OWNER_VERB}\\\\s+(?:(?:the|this|that|a|an|their|its|each)\\\\s+)?(?:[\\\\p{L}'’-]+\\\\s+){0,2}${RECORD_PARTS}\\\\s*[.!]?\\\\s*$`,\n 'iu',\n)\n\n/** An aside in parentheses: \"Junior rider (add details later)\". */\nconst ASIDE = /\\s*\\(([^()]{1,160})\\)/g\n\n/** Where a sentence turns to something else: a dash, a colon, a semicolon. */\nconst TURN = /\\s*(?:[—–:;]|\\s-)\\s*/g\n\n/** A value's sentences, each with its closing mark. */\nconst sentencesOf = (value: string): string[] =>\n value\n .split(/(?<=[.!?])\\s+/)\n .map((sentence) => sentence.trim())\n .filter(Boolean)\n\n/** What a visitor's instruction points at, which an owner's never does: \"Fill in the form here\", \"Book on the Contact page\". */\nconst VISITOR_TARGET = /\\b(?:form|button|link|page|website|checkout|cart|account|app)s?\\b/i\n\n/** Whether words, read from their start, are an instruction to the owner rather than words about the record (AGL-4092). */\nexport function aiDatasetInstructionSentence(words: string): boolean {\n return (OWNER_INSTRUCTION.test(words) && !VISITOR_TARGET.test(words)) || BARE_INSTRUCTION.test(words)\n}\n\n/** One sentence without the instruction it is or ends with: \"\" for \"Add finishing position\", \"Junior rider\" for \"Junior rider — add details later\". */\nfunction sentenceWithoutInstruction(sentence: string): string {\n if (aiDatasetInstructionSentence(sentence)) return ''\n for (const turn of sentence.matchAll(TURN)) {\n const at = turn.index ?? 0\n if (at > 0 && aiDatasetInstructionSentence(sentence.slice(at + turn[0].length))) {\n const head = sentence.slice(0, at).replace(/[\\s,]+$/, '')\n return head && !/[.!?]$/.test(head) ? `${head}.` : head\n }\n }\n return sentence\n}\n\n/**\n * A value with every instruction to the owner taken out (AGL-4092): an aside,\n * a sentence, or the turn a sentence ends on; `''` where nothing else was\n * said, and the value unchanged where none was. Pure.\n */\nexport function aiDatasetValueWithoutInstructions(value: string): string {\n const unasided = value.replace(ASIDE, (aside, inner: string) => (aiDatasetInstructionSentence(inner) ? '' : aside))\n const sentences = sentencesOf(unasided)\n const kept = sentences.map(sentenceWithoutInstruction).filter(Boolean)\n return unasided === value && kept.length === sentences.length && kept.every((sentence, index) => sentence === sentences[index])\n ? value\n : kept.join(' ')\n}\n\n/** Whether a value tells the owner to do something anywhere in it (AGL-4092). */\nconst holdsInstruction = (value: string): boolean => aiDatasetValueWithoutInstructions(value) !== value\n\n/** A name that ends in a number, with any aside after it: \"Upcoming race 1 (details to be added)\". */\nconst NUMBERED_NAME = /^(.*?)\\s*(?:#|no\\.?\\s*)?\\d+\\s*(?:\\([^)]*\\))?$/i\n\n/** Words a numbered placeholder is made of besides the list's own: \"Item 1\", \"Upcoming event 2\", \"New entry 3\". */\nconst STAND_IN_WORDS = new Set(\n 'item entry record row event name product thing option sample example test placeholder untitled new upcoming past next previous future current recent old'.split(' '),\n)\n\n/** A name's words as a stem compares them: lowercase, a plural's \"-s\" dropped. */\nconst stemWords = (text: string) =>\n text\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean)\n .map((word) => (word.length > 3 && word.endsWith('s') && !word.endsWith('ss') ? word.slice(0, -1) : word))\n\n/**\n * The records of a designed dataset that are placeholders (AGL-4065), by\n * position, which no site shows:\n * - any value that marks a gap: \"details to be added\", \"TBD\", \"coming soon\";\n * - any sentence that tells the owner what to add instead of saying it\n * (AGL-4092): \"Add this rider's story here.\", \"Add finishing position\";\n * - a name that is a number after nothing but the list's own words and\n * stand-in words: \"Item 1\", \"Race 2\", \"Upcoming race 3\", \"New entry 4\".\n * A course's \"Level 1\" to \"Level 3\" and a series' \"Series Round 1 at\n * Lockhart\" name things, and are kept. Pure.\n */\nexport function aiDatasetPlaceholderRecords(\n dataset: { fields: ReadonlyArray<{ name: string }>; records: ReadonlyArray<ReadonlyArray<string | undefined>> },\n name = '',\n): number[] {\n const own = new Set([...stemWords(name), ...dataset.fields.flatMap((field) => stemWords(field.name))])\n return dataset.records.flatMap((values, index) => {\n if (values.some((value) => PLACEHOLDER_TEXT.test(value ?? '') || holdsInstruction(value ?? ''))) return [index]\n const match = NUMBERED_NAME.exec((values[0] ?? '').replace(/\\s+/g, ' ').trim())\n if (!match) return []\n const words = stemWords(match[1] ?? '')\n return words.every((word) => own.has(word) || STAND_IN_WORDS.has(word)) ? [index] : []\n })\n}\n\n/** An answer's fields and records as the placeholder test reads them, whatever else is wrong with it. */\nfunction answerShape(answer: Record<string, unknown> | null | undefined): {\n fields: Array<{ name: string }>\n records: string[][]\n} {\n const text = (value: unknown) => (typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '')\n const fields = (Array.isArray(answer?.['fields']) ? (answer?.['fields'] as unknown[]) : []).map((field) => ({\n name: text((field as Record<string, unknown> | null)?.['name']),\n }))\n const records = (Array.isArray(answer?.['records']) ? (answer?.['records'] as unknown[]) : []).map((record) => {\n const values = (record as Record<string, unknown> | null)?.['values']\n return Array.isArray(values) ? values.map(text) : []\n })\n return { fields, records }\n}\n\n/** Where a dataset is checked: its plan's fields, the words a claim may come from, and its name. */\nexport interface AiDatasetCheckContext {\n planned: readonly string[]\n merchantWords: string\n name: string\n}\n\n/**\n * A dataset's answer held to the tool's check and to naming real things\n * (AGL-4065): one with placeholder records is refused, with\n * {@link AI_DATASET_PLACEHOLDER_REASK} for the one re-ask. Pure.\n */\nexport function checkAiDatasetRecords(answer: Record<string, unknown>, context: AiDatasetCheckContext): AiGenerationCheckResult<AiDataset> {\n const checked = checkAiDataset(answer, context)\n const shape = answerShape(answer)\n const placeholders = aiDatasetPlaceholderRecords(shape, context.name)\n // A sample is of its kind, never of a make the brief does not name (AGL-4065).\n const rawRecords = Array.isArray(answer['records']) ? (answer['records'] as unknown[]) : []\n const branded = shape.records.flatMap((values, index) => {\n const sample = (rawRecords[index] as Record<string, unknown> | null)?.['sample'] === true\n const brand = sample ? values.map((value) => aiTextNamesBrand(value, context.merchantWords)).find(Boolean) : null\n return brand ? [{ index, brand }] : []\n })\n if (!placeholders.length && !branded.length) return checked\n const violations = [...checked.violations]\n const offending: Record<string, unknown> = { ...(checked.offending ?? {}) }\n if (placeholders.length) {\n violations.push({\n rule: null,\n code: AI_DATASET_PLACEHOLDER_CODE,\n message: AI_DATASET_PLACEHOLDER_REASK,\n paths: placeholders.map((index) => `records[${index}]`),\n } satisfies AiDoctrineViolation)\n for (const index of placeholders.slice(0, 3)) offending[`records[${index}]`] = shape.records[index]?.[0] ?? ''\n }\n for (const { index, brand } of branded) {\n violations.push({\n rule: null,\n code: 'sample-brand',\n message: `Sample record ${index + 1} names the make \"${brand}\", which the brief does not: a sample is of its kind, never of a brand.`,\n paths: [`records[${index}]`],\n })\n }\n return { value: null, violations, offending }\n}\n\n/**\n * A dataset answered with placeholders after its one re-ask, kept with the\n * records that name real things (AGL-4065) — however few, none included —\n * where nothing else in it breaks a rule; `null` otherwise. Pure.\n */\nexport function aiDatasetWithoutPlaceholders(answer: Record<string, unknown> | null | undefined, context: AiDatasetCheckContext): AiDataset | null {\n if (!answer) return null\n const drop = new Set(aiDatasetPlaceholderRecords(answerShape(answer), context.name))\n const records = Array.isArray(answer['records']) ? (answer['records'] as unknown[]) : []\n const kept = { ...answer, records: records.filter((_record, index) => !drop.has(index)) }\n // The fewest records a list is asked for is for one the brief fills; what is left of this one is what it gave.\n const checked = checkAiDataset(kept, { ...context, recordsMin: 1 })\n return checked.value && !checked.violations.length ? checked.value : null\n}\n\n/**\n * An answer with every instruction to the owner taken out of its records'\n * values (AGL-4092), for the salvage after its one re-ask: \"Add this rider's\n * story here.\" leaves the bio's other sentences, \"Add finishing position\"\n * leaves the result \"\", and a record with no name left is dropped. The\n * answer unchanged where no value instructed. Pure.\n */\nexport function aiDatasetWithoutInstructions<A extends Record<string, unknown> | null | undefined>(answer: A): A {\n if (!answer || !Array.isArray(answer['records'])) return answer\n let changed = false\n const records = (answer['records'] as unknown[]).flatMap((record) => {\n const values = (record as Record<string, unknown> | null)?.['values']\n if (!Array.isArray(values)) return [record]\n const next = values.map((value: unknown) => (typeof value === 'string' ? aiDatasetValueWithoutInstructions(value) : value))\n if (next.every((value, index) => value === values[index])) return [record]\n changed = true\n return typeof next[0] === 'string' && next[0].trim() ? [{ ...(record as Record<string, unknown>), values: next }] : []\n })\n return changed ? ({ ...answer, records } as A) : answer\n}\n\n/** A designed dataset with every instruction to the owner taken out (AGL-4092), as {@link aiDatasetWithoutInstructions} does an answer. */\nfunction designedWithoutInstructions(dataset: AiDataset): AiDataset {\n const records = dataset.records.map((values) => values.map((value) => aiDatasetValueWithoutInstructions(value ?? '')))\n if (records.every((values, index) => values.every((value, column) => value === (dataset.records[index]?.[column] ?? '')))) return dataset\n const keep = records.map((values) => Boolean(values[0]?.trim()))\n const { samples: all, ...rest } = dataset\n const samples = all?.filter((_sample, index) => keep[index])\n return { ...rest, records: records.filter((_values, index) => keep[index]), ...(samples?.some(Boolean) ? { samples } : {}) }\n}\n\n/**\n * A designed dataset with any instruction to the owner (AGL-4092) and any\n * placeholder record taken out (AGL-4065), and where the list starts with no\n * samples (`AI_DATASET_LIST_WITHOUT_ENTRIES` is `empty`), every sample too.\n * Pure.\n */\nexport function aiDatasetRealRecords(designed: AiDataset, name: string): AiDataset {\n const dataset = designedWithoutInstructions(designed)\n const drop = new Set(aiDatasetPlaceholderRecords(dataset, name))\n if (AI_DATASET_LIST_WITHOUT_ENTRIES === 'empty') dataset.samples?.forEach((sample, index) => sample && drop.add(index))\n if (!drop.size) return dataset\n const keep = (_entry: unknown, index: number) => !drop.has(index)\n const { samples: all, ...rest } = dataset\n const samples = all?.filter(keep)\n return { ...rest, records: dataset.records.filter(keep), ...(samples?.some(Boolean) ? { samples } : {}) }\n}\n\n// ── A sample row's date (AGL-4065) ──────────────────────────────────────\n\n/** A field whose name says it holds when a record happens. */\nconst DATE_FIELD = /\\b(?:date|dates|day|when)\\b/i\n\n/** A field a record has only once it has happened: a race's result, a match's score. */\nconst OUTCOME_FIELD = /\\b(?:results?|finish(?:ed)?|placings?|placed|winners?|scores?|outcome)\\b/i\n\n/** A value or a section's name that says its records are behind us. */\nconst PAST_WORDS = /\\b(?:past|previous|completed|finished|results?|earlier|archived?|last season)\\b/i\n\n/** The days a sample's date steps by, and how far from the job's day the first falls: a season of weekends. */\nexport const AI_DATASET_SAMPLE_DATES = { stepDays: 21, leadDays: 14 } as const\n\nconst DAY_MS = 86_400_000\n\n/** The Saturday on or after (`ahead`) or on or before a day, at noon UTC. */\nfunction saturday(from: Date, ahead: boolean): Date {\n const day = new Date(Date.UTC(from.getUTCFullYear(), from.getUTCMonth(), from.getUTCDate(), 12))\n const shift = ahead ? (6 - day.getUTCDay() + 7) % 7 : -((day.getUTCDay() + 1) % 7)\n return new Date(day.getTime() + shift * DAY_MS)\n}\n\n/**\n * A dated list's SAMPLE records given real dates (AGL-4065): a sample the\n * brief gave no date for falls on a weekend of a season from the job's day —\n * the first Saturday at least two weeks out, then every three weeks — and a\n * sample that is behind us (its split value or the list says past: \"Past\n * results\") on the Saturdays as far back, the latest first, with no result\n * left on one still to come. Written as a visitor reads a date, in the\n * site's language. A record from the brief keeps its own date. Pure.\n */\nexport function aiDatasetSampleDates(\n dataset: AiDataset,\n options: { now: Date; language?: string | null; split?: AiDatasetSplit | null; pastOnly?: boolean },\n): AiDataset {\n const samples = dataset.samples ?? []\n const dates = dataset.fields.flatMap((field, column) => (column > 0 && field.type === 'text' && DATE_FIELD.test(field.name) ? [column] : []))\n if (!samples.some(Boolean) || !dates.length) return dataset\n const splitColumn = options.split\n ? dataset.fields.findIndex((field) => field.name.trim().toLowerCase() === (options.split as AiDatasetSplit).field.trim().toLowerCase())\n : -1\n const outcomes = dataset.fields.flatMap((field, column) => (column > 0 && OUTCOME_FIELD.test(field.name) ? [column] : []))\n let format: Intl.DateTimeFormat\n try {\n format = new Intl.DateTimeFormat(options.language || 'en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' })\n } catch {\n format = new Intl.DateTimeFormat('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' })\n }\n const { stepDays, leadDays } = AI_DATASET_SAMPLE_DATES\n const firstAhead = saturday(new Date(options.now.getTime() + leadDays * DAY_MS), true)\n const firstBehind = saturday(new Date(options.now.getTime() - leadDays * DAY_MS), false)\n let ahead = 0\n let behind = 0\n return {\n ...dataset,\n records: dataset.records.map((values, index) => {\n if (!samples[index]) return [...values]\n const past = options.pastOnly || (splitColumn > 0 && PAST_WORDS.test(values[splitColumn] ?? ''))\n const at = past\n ? new Date(firstBehind.getTime() - behind++ * stepDays * DAY_MS)\n : new Date(firstAhead.getTime() + ahead++ * stepDays * DAY_MS)\n const said = format.format(at)\n // A race still to come has no result, and one behind us never an instruction for one (AGL-4092).\n return values.map((value, column) =>\n dates.includes(column) ? said : outcomes.includes(column) && (!past || holdsInstruction(value ?? '')) ? '' : value,\n )\n }),\n }\n}\n\n/** Whether every section that lists a dataset says its records are behind us (\"Past results\", \"Race results\"). */\nexport function aiDatasetListedAsPast(sections: readonly string[]): boolean {\n return sections.length > 0 && sections.every((section) => PAST_WORDS.test(section))\n}\n\nexport interface GenerateAiDatasetInput extends AiDatasetPromptInput {\n /** What a claim or a price may be quoted from: the brief and the site's answers. */\n merchantWords: string\n settings?: AiPluginSettings\n model?: string\n maxTokens?: number\n signal?: AbortSignal\n provider?: AiProvider\n}\n\n/** Design one dataset. It writes nothing to the site. */\nexport function generateAiDataset(input: GenerateAiDatasetInput): Promise<AiValidatedGeneration<AiDataset>> {\n const row = AI_ROUTING_TABLE[AI_DATASET_STEP]\n return runValidatedGeneration(AI_DATASET_KIND, {\n step: AI_DATASET_STEP,\n ...(input.settings ? { settings: input.settings } : {}),\n ...(input.model ? { model: input.model } : {}),\n instructions: AI_DATASET_INSTRUCTIONS,\n messages: [{ role: 'user', content: aiDatasetPrompt(input) }],\n tool: AI_DATASET_TOOL,\n maxTokens: input.maxTokens ?? AI_DATASET_MAX_TOKENS,\n ...(row.thinking ? { thinking: row.thinking } : {}),\n ...(row.effort ? { effort: row.effort } : {}),\n ...(input.signal ? { signal: input.signal } : {}),\n ...(input.provider ? { provider: input.provider } : {}),\n check: (answer) => checkAiDatasetRecords(answer, { planned: input.fields, merchantWords: input.merchantWords, name: input.name }),\n }) as Promise<AiValidatedGeneration<AiDataset>>\n}\n\n/**\n * A designed dataset as the data plugin's `dataset` writer takes it: values by\n * field name. Its words' \"a\" and \"an\" agree with what follows them in English\n * (`aiLayoutArticles`, AGL-3988), as a page's do: the beta.270 Sugar Hollow\n * packages said \"A easygoing session\" in their records and on every page\n * that listed them.\n */\nexport function aiDatasetDraftContent(\n name: string,\n dataset: AiDataset,\n options: { recordPages: boolean; language?: string | null },\n): Record<string, unknown> {\n const said = (value: string) => aiLayoutArticles(value, options.language)\n const records = dataset.records.map((values) => {\n const record: Record<string, unknown> = {}\n dataset.fields.forEach((field, column) => {\n const value = (values[column] ?? '').trim()\n if (!value) return\n if (field.type === 'list') {\n record[field.name] = value.split(AI_DATASET_LIST_SEPARATOR.trim()).map((part) => said(part.trim())).filter(Boolean)\n } else if (field.type === 'number' || field.type === 'integer') {\n record[field.name] = value.replace(/,/g, '')\n } else if (field.type === 'text') {\n record[field.name] = said(value)\n } else {\n record[field.name] = value\n }\n })\n return record\n })\n return {\n name,\n fields: dataset.fields.map((field) => ({ name: field.name, type: field.type })),\n records,\n // Which records are samples for the owner to replace (AGL-4065), which Data marks.\n ...(dataset.samples?.some(Boolean) ? { samples: dataset.records.map((_values, index) => dataset.samples?.[index] === true) } : {}),\n // A record page shows each record at an address made from its name.\n ...(options.recordPages && dataset.fields[0] ? { pageAddressFrom: dataset.fields[0].name } : {}),\n }\n}\n"],"names":["AI_ROUTING_TABLE","AI_DATASET_LIMITS","AI_DATASET_LIST_SEPARATOR","AI_DATASET_TOOL","AI_DATASET_TOOL_NAME","checkAiDataset","runValidatedGeneration","aiLayoutArticles","aiTextNamesBrand","AI_DATASET_STEP","AI_DATASET_MAX_TOKENS","Math","min","maxTokens","AI_DATASET_KIND","AI_DATASET_LIST_WITHOUT_ENTRIES","WITHOUT_ENTRIES_RULE","AI_DATASET_INSTRUCTIONS","text","recordsMin","recordsMax","valueMax","map","rule","join","cacheBreakpoint","listed","names","last","quoted","name","length","slice","aiDatasetSplitLine","split","sections","field","values","aiDatasetPrompt","input","brief","trim","why","fields","shownIn","recordPages","AI_DATASET_PLACEHOLDER_REASK","AI_DATASET_PLACEHOLDER_CODE","PLACEHOLDER_TEXT","OWNER_VERB","INSTRUCTION_START","OWNER_INSTRUCTION","RegExp","RECORD_PARTS","BARE_INSTRUCTION","ASIDE","TURN","sentencesOf","value","sentence","filter","Boolean","VISITOR_TARGET","aiDatasetInstructionSentence","words","test","sentenceWithoutInstruction","turn","matchAll","at","index","head","replace","aiDatasetValueWithoutInstructions","unasided","aside","inner","sentences","kept","every","holdsInstruction","NUMBERED_NAME","STAND_IN_WORDS","Set","stemWords","toLowerCase","word","endsWith","aiDatasetPlaceholderRecords","dataset","own","flatMap","records","match","some","exec","has","answerShape","answer","Array","isArray","record","checkAiDatasetRecords","context","checked","shape","placeholders","rawRecords","branded","sample","brand","merchantWords","find","violations","offending","push","code","message","paths","aiDatasetWithoutPlaceholders","drop","_record","aiDatasetWithoutInstructions","changed","next","designedWithoutInstructions","column","keep","samples","all","rest","_sample","_values","aiDatasetRealRecords","designed","forEach","add","size","_entry","DATE_FIELD","OUTCOME_FIELD","PAST_WORDS","AI_DATASET_SAMPLE_DATES","stepDays","leadDays","DAY_MS","saturday","from","ahead","day","Date","UTC","getUTCFullYear","getUTCMonth","getUTCDate","shift","getUTCDay","getTime","aiDatasetSampleDates","options","dates","type","splitColumn","findIndex","outcomes","format","Intl","DateTimeFormat","language","weekday","month","year","timeZone","firstAhead","now","firstBehind","behind","past","pastOnly","said","includes","aiDatasetListedAsPast","section","generateAiDataset","row","step","settings","model","instructions","messages","role","content","tool","thinking","effort","signal","provider","check","planned","aiDatasetDraftContent","part","pageAddressFrom"],"mappings":"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"}
|
|
@@ -5513,7 +5513,8 @@ function slugKey(slug) {
|
|
|
5513
5513
|
*/ export const AI_FREE_PAGE_WORST_CASE_CREDITS = {
|
|
5514
5514
|
plan: 83,
|
|
5515
5515
|
layout: 64,
|
|
5516
|
-
|
|
5516
|
+
// 44 until AGL-4092, whose rule 14 every section pass's cached prefix carries.
|
|
5517
|
+
firstSection: 45,
|
|
5517
5518
|
laterSection: 20,
|
|
5518
5519
|
// A page's listing, on the fast tier at the platform's markup since
|
|
5519
5520
|
// AGL-3781 (3 credits at Claude Haiku 4.5's cost before).
|