fix(13-01): upgrade to gpt-4o, remove checkboxes, clamp AI coords to page bounds

- gpt-4o-mini replaced with gpt-4o for better placement accuracy
- checkbox removed from schema enum and filtered in loop (positions are input-dependent)
- y coordinate clamped to [0, pageHeight - fieldHeight] to prevent fields rendering
  outside the PDF canvas when AI returns yPct near 100%

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chandler Copeland
2026-03-21 17:29:49 -06:00
parent 72f7d20bac
commit c67d56dc48

View File

@@ -43,7 +43,10 @@ export function aiCoordsToPagePdfSpace(
const x = screenX; const x = screenX;
// PDF y = distance from BOTTOM. screenY is from top, so flip: // PDF y = distance from BOTTOM. screenY is from top, so flip:
// pdfY = pageHeight - screenY - fieldHeight (bottom edge of field) // pdfY = pageHeight - screenY - fieldHeight (bottom edge of field)
const y = pageHeight - screenY - fieldHeight; // Clamp to [0, pageHeight - fieldHeight] so AI coords near page edges
// don't produce negative y values that render outside the canvas.
const rawY = pageHeight - screenY - fieldHeight;
const y = Math.max(0, Math.min(rawY, pageHeight - fieldHeight));
return { x, y, width: fieldWidth, height: fieldHeight }; return { x, y, width: fieldWidth, height: fieldHeight };
} }
@@ -60,7 +63,7 @@ const FIELD_PLACEMENT_SCHEMA = {
type: 'object', type: 'object',
properties: { properties: {
page: { type: 'integer' }, page: { type: 'integer' },
fieldType: { type: 'string', enum: ['text', 'checkbox', 'initials', 'date', 'client-signature', 'agent-signature', 'agent-initials'] }, fieldType: { type: 'string', enum: ['text', 'initials', 'date', 'client-signature', 'agent-signature', 'agent-initials'] },
xPct: { type: 'number' }, xPct: { type: 'number' },
yPct: { type: 'number' }, // % from page TOP (AI top-left origin) yPct: { type: 'number' }, // % from page TOP (AI top-left origin)
widthPct: { type: 'number' }, widthPct: { type: 'number' },
@@ -102,16 +105,18 @@ export async function classifyFieldsWithAI(
.join('\n\n'); .join('\n\n');
const response = await openai.chat.completions.create({ const response = await openai.chat.completions.create({
model: 'gpt-4o-mini', model: 'gpt-4o',
messages: [ messages: [
{ {
role: 'system', role: 'system',
content: `You are a real estate document form field extractor. content: `You are a real estate document form field extractor.
Given extracted text from a PDF page (with context about page number and dimensions), Given extracted text from a PDF page (with context about page number and dimensions),
identify where signature, text, checkbox, initials, and date fields should be placed. identify where signature, initials, text, and date fields should be placed.
Return fields as percentage positions (0-100) from the TOP-LEFT of the page. Return fields as percentage positions (0-100) from the TOP-LEFT of the page.
Use these field types: text (for typed values), checkbox, initials, date, client-signature, agent-signature, agent-initials. Use these field types: text (for typed values), initials, date, client-signature, agent-signature, agent-initials.
For text fields that match the client profile, set prefillValue to the known value. Otherwise use empty string.`, Only place fields where the document clearly requires them — prefer fewer, high-confidence placements.
For text fields that match the client name or property address, set prefillValue to the known value. Otherwise use empty string.
Do NOT place checkbox fields.`,
}, },
{ {
role: 'user', role: 'user',
@@ -135,16 +140,17 @@ For text fields that match the client profile, set prefillValue to the known val
const textFillData: Record<string, string> = {}; const textFillData: Record<string, string> = {};
for (const aiField of raw.fields) { for (const aiField of raw.fields) {
// Never place checkboxes — positions depend on user input and can't be AI-determined
if (aiField.fieldType === 'checkbox') continue;
const pageInfo = pageTexts.find((p) => p.page === aiField.page); const pageInfo = pageTexts.find((p) => p.page === aiField.page);
const pageWidth = pageInfo?.width ?? 612; // fallback: US Letter const pageWidth = pageInfo?.width ?? 612; // fallback: US Letter
const pageHeight = pageInfo?.height ?? 792; const pageHeight = pageInfo?.height ?? 792;
const { x, y } = aiCoordsToPagePdfSpace(aiField, pageWidth, pageHeight); const { x, y } = aiCoordsToPagePdfSpace(aiField, pageWidth, pageHeight);
// Use standard sizes regardless of AI width/height — consistent with FieldPlacer defaults const width = 144; // pts: 2 inches
const isCheckbox = aiField.fieldType === 'checkbox'; const height = 36; // pts: 0.5 inches
const width = isCheckbox ? 24 : 144; // pts: checkbox=24x24, others=144x36
const height = isCheckbox ? 24 : 36;
const id = crypto.randomUUID(); const id = crypto.randomUUID();