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:
@@ -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();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user