diff --git a/src/lib/ai-ocr.ts b/src/lib/ai-ocr.ts index 8945eaf..6c3f4bd 100644 --- a/src/lib/ai-ocr.ts +++ b/src/lib/ai-ocr.ts @@ -59,19 +59,21 @@ const responseCardSchema = z.object({ const surveySchema = z.object({ messageTopics: z.array(z.string()).describe( - "Checked topics from: Stress, Marriage, Revival, Addiction, Parenting, Miracles, Forgiveness, Finances, My Identity, Conflict Resolution, The Holy Spirit, Understanding The Bible, Spiritual Warfare, Sharing My Faith, Anxiety, Heaven, Spiritual Gifts" + "ONLY items whose checkbox is physically marked (X, checkmark, or filled). Options: Stress, Marriage, Revival, Addiction, Parenting, Miracles, Forgiveness, Finances, My Identity, Conflict Resolution, The Holy Spirit, Understanding The Bible, Spiritual Warfare, Sharing My Faith, Anxiety, Heaven, Spiritual Gifts. Return empty array if none are marked." ), messageTopicsOther: z.string().nullable().describe("Value if Other is filled in"), - nextStep: z.array(z.string()).describe("Checked items from: Baptism, Next Steps"), + nextStep: z.array(z.string()).describe( + "This question has exactly 2 checkboxes. Include ONLY the ones with a visible mark (X, checkmark, filled). 'Baptism' = the checkbox for 'Expressing my faith in Jesus / baptized'. 'Next Steps' = the checkbox for 'Learning more about becoming a partner / attend Next Steps'. Return empty array if neither checkbox is marked." + ), attendanceDuration: z.string().nullable().describe( - "Less than 6 months, 6 Months - 1 Year, 1-3 Years, 4-6 Years, or 7+ Years" + "The single checked radio option. One of: Less than 6 months, 6 Months - 1 Year, 1-3 Years, 4-6 Years, or 7+ Years. null if none is marked." ), campusPreference: z.array(z.string()).describe( - "Checked locations from: Beulah, Pace/Milton, Gulf Breeze, Warrington" + "ONLY locations whose checkbox is physically marked. Options: Beulah, Pace/Milton, Gulf Breeze, Warrington. Return empty array if none are marked." ), campusPreferenceOther: z.string().nullable().describe("Value if Other is filled in"), howHeard: z.array(z.string()).describe( - "Checked items from: This is my church home, Regular Attender, Drove by, Social Media, Google, Personal Invite" + "ONLY items whose checkbox is physically marked. Options: This is my church home, Regular Attender, Drove by, Social Media, Google, Personal Invite. Return empty array if none are marked." ), howHeardOther: z.string().nullable().describe("Value if Other is filled in"), serviceAttended: z.string().nullable().describe("Service letter: A, B, C, or D"), @@ -80,15 +82,26 @@ const surveySchema = z.object({ const RESPONSE_SYSTEM_PROMPT = `You are analyzing a scanned church response card. This is the PERSONAL INFORMATION side. -Extract ALL of the following fields from the image. For checkboxes, determine if they are checked or unchecked. -For handwritten text, read it as accurately as possible. +Extract ALL of the following fields from the image. +CHECKBOX RULES - be very strict: +- A checkbox is CHECKED only if it has a visible mark inside it: an X, a checkmark, a filled square, or pen/pencil marks inside the box. +- An EMPTY box (no marks inside) means UNCHECKED, even if text appears next to it. +- When in doubt, treat a checkbox as UNCHECKED. + +For handwritten text, read it as accurately as possible. Be precise: return null for fields you cannot read. Set prayerForTeam and prayerConfidential to false if the checkboxes are not clearly marked.`; const SURVEY_SYSTEM_PROMPT = `You are analyzing a scanned church Easter survey form. This is the SURVEY side. -Extract ALL of the following fields. For checkboxes, determine if they are checked (filled/marked) or unchecked (empty). -Return empty arrays for checkbox groups where nothing is checked. +Extract ALL of the following fields from the image. + +CHECKBOX RULES - be very strict: +- A checkbox is CHECKED only if it has a visible mark inside it: an X, a checkmark, a filled square, or pen/pencil marks inside the box. +- An EMPTY box (no marks inside) means UNCHECKED, even if text appears next to it. +- When in doubt, treat a checkbox as UNCHECKED. +- For array fields (messageTopics, nextStep, campusPreference, howHeard): ONLY include items whose checkbox is physically marked. Return empty arrays when no checkboxes in that group are marked. +- Do NOT confuse section headings or question titles with checked answers. Be precise: return null for fields you cannot read.`; diff --git a/src/lib/ollama.ts b/src/lib/ollama.ts index 45ae6b6..1bb35c5 100644 --- a/src/lib/ollama.ts +++ b/src/lib/ollama.ts @@ -21,8 +21,15 @@ async function getModel() { const RESPONSE_CARD_PROMPT = `You are analyzing a scanned church response card. This is the PERSONAL INFORMATION side. -Extract ALL of the following fields from the image. For checkboxes, determine if they are checked or unchecked. +Extract ALL of the following fields from the image. + +CHECKBOX RULES - be very strict: +- A checkbox is CHECKED only if it has a visible mark inside it: an X, a checkmark, a filled square, or pen/pencil marks inside the box. +- An EMPTY box (no marks inside) means UNCHECKED, even if text appears next to it. +- When in doubt, treat a checkbox as UNCHECKED. + For handwritten text, read it as accurately as possible. +Set prayerForTeam and prayerConfidential to false if the checkboxes are not clearly marked. Return ONLY valid JSON with this exact structure (no markdown, no code fences): { @@ -48,17 +55,29 @@ Return ONLY valid JSON with this exact structure (no markdown, no code fences): const SURVEY_PROMPT = `You are analyzing a scanned church Easter survey form. This is the SURVEY side. -Extract ALL of the following fields. For checkboxes, determine if they are checked (filled/marked) or unchecked (empty). +Extract ALL of the following fields. + +CHECKBOX RULES - be very strict: +- A checkbox is CHECKED only if it has a visible mark inside it: an X, a checkmark, a filled square, or pen/pencil marks inside the box. +- An EMPTY box (no marks inside) means UNCHECKED, even if text appears next to it. +- When in doubt, treat a checkbox as UNCHECKED. +- For array fields: ONLY include items whose checkbox is physically marked. Return empty arrays when no checkboxes in that group are marked. +- Do NOT confuse section headings or question titles with checked answers. + +IMPORTANT for nextStep: There are exactly 2 checkboxes. +- "Baptism" = the checkbox for "Expressing my faith in Jesus / I want to be baptized..." +- "Next Steps" = the checkbox for "Learning more about becoming a partner / attend Next Steps..." +Only include the one(s) whose box is physically marked. Return ONLY valid JSON with this exact structure (no markdown, no code fences): { - "messageTopics": ["array of checked topics from: Stress, Marriage, Revival, Addiction, Parenting, Miracles, Forgiveness, Finances, My Identity, Conflict Resolution, The Holy Spirit, Understanding The Bible, Spiritual Warfare, Sharing My Faith, Anxiety, Heaven, Spiritual Gifts"], + "messageTopics": ["ONLY topics whose checkbox is marked, from: Stress, Marriage, Revival, Addiction, Parenting, Miracles, Forgiveness, Finances, My Identity, Conflict Resolution, The Holy Spirit, Understanding The Bible, Spiritual Warfare, Sharing My Faith, Anxiety, Heaven, Spiritual Gifts"], "messageTopicsOther": "string if Other is filled in, else null", - "nextStep": ["array of checked items from: Baptism, Next Steps"], + "nextStep": ["ONLY items whose checkbox is marked, from: Baptism, Next Steps"], "attendanceDuration": "Less than 6 months" or "6 Months - 1 Year" or "1-3 Years" or "4-6 Years" or "7+ Years" or null, - "campusPreference": ["array of checked locations from: Beulah, Pace/Milton, Gulf Breeze, Warrington"], + "campusPreference": ["ONLY locations whose checkbox is marked, from: Beulah, Pace/Milton, Gulf Breeze, Warrington"], "campusPreferenceOther": "string if Other is filled in, else null", - "howHeard": ["array of checked items from: This is my church home, Regular Attender, Drove by, Social Media, Google, Personal Invite"], + "howHeard": ["ONLY items whose checkbox is marked, from: This is my church home, Regular Attender, Drove by, Social Media, Google, Personal Invite"], "howHeardOther": "string if Other is filled in, else null", "serviceAttended": "A" or "B" or "C" or "D" or null, "confidence": 0-100