{loadErr}
/* ================================================================== Paper Evaluation pipeline — dedicated 3-step flow ================================================================== */ const EVIcon = window.AppUI.Icon; /* ---------- The system prompt that drives Gemini's parser ---------- */ const PARSER_SYSTEM_PROMPT = `You are a question paper parser for a bilingual K-12 education system in India. Your job is to convert raw question paper text into structured JSON. # Task Parse the question paper provided in the user message into the exact JSON schema specified below. Return ONLY the JSON object — no prose, no markdown fences, no commentary. # Output schema { "test_id": null, "sections": [ { "section_id": "A", "title": "string", "marks_per_question": 1, "total_questions": 6, "section_total_marks": 6, "questions": [ { "q_no": 1, "text": "string", "type": "mcq | short | long | numerical | derivation", "options": ["string", "string", "string", "string"], "prerequisite_tags": ["kebab-case-string", "kebab-case-string"] } ] } ], "parsing_flags": [ { "severity": "error | warning", "section_id": "string | null", "q_no": "number | string | null", "message": "string" } ] } # Field rules - test_id: always null. The application assigns it. - section_id: single uppercase letter, sequential (A, B, C, ...). - title: short descriptive name, in English, derived from the section heading (e.g., "MCQ", "Short Answer Type Questions", "Broad Answer Type Questions"). - marks_per_question: integer. Every section has uniform marks across its questions. Infer from the section header, or by dividing section total marks by question count. If inference is impossible or inconsistent, set to null and add a parsing flag. - total_questions: integer. Count of questions in the section. - section_total_marks: integer. Must equal marks_per_question × total_questions. If the source paper states a section total that conflicts with this product, set marks_per_question to null and add a parsing flag with severity "error". - q_no: usually an integer. If the source paper has duplicate numbers, keep the first as the integer and suffix subsequent ones as strings ("12b", "12c", etc.) — also add a warning flag. - text: the question text verbatim, in the original script (Bengali, Hindi, or English). Preserve all chemical formulas, equations, and inline LaTeX. Do not translate. Do not summarize. If a question contains multiple sub-parts glued together with "or" / "অথবা" / "या", keep them as one text block but add a warning flag suggesting the teacher split them. - type: one of "mcq", "short", "long", "numerical", "derivation". Infer from question structure: - mcq: has lettered options (a)(b)(c)(d) or (i)(ii)(iii)(iv) - numerical: asks for a calculated numeric value with units - derivation: asks to "derive", "prove", "show that" - short: 1–3 mark questions without options, typically one-line answers - long: 3+ mark questions requiring multi-step explanations - options: include this field ONLY when type is "mcq". Omit entirely otherwise (do not include as null or empty array). Strip the (a)(b)(c)(d) prefixes — give only the option content. Preserve original language. - prerequisite_tags: 2–4 short kebab-case concept strings in English (e.g., "redox-balancing", "ethylene-glycol", "iupac-nomenclature"). These are best-effort topic tags Gemini infers — the teacher refines them later to canonical concept IDs. # Parsing flags Add to the parsing_flags array whenever you encounter: 1. Duplicate q_no in the source. Severity: "warning". 2. Section marks math doesn't reconcile (total ≠ per_question × count). Severity: "error". Set marks_per_question to null in that section. 3. A question text appears truncated (e.g., ends with a colon, refers to "the following diagram" with nothing after it). Severity: "warning". 4. A question contains alternative sub-questions joined by "or" / "অথবা". Severity: "warning". Suggest splitting. 5. Section header is ambiguous about marks distribution. Severity: "warning". 6. Question count appears off (e.g., section says "answer any 10 of the following" but lists 15). Severity: "warning". 7. The text appears to reference a diagram, image, or table not present in the input. Severity: "warning". Each flag must reference the specific section_id and q_no where applicable. If a flag is paper-wide, set both to null. If there are no issues, return an empty array: "parsing_flags": []. # Language handling - Preserve the original script of every question. Bengali stays in Bengali, Hindi in Hindi, English in English. No transliteration, no translation. - prerequisite_tags are always in English, kebab-case. - section title is always in English. # What you must NOT do - Do not invent questions or options that aren't in the source. - Do not "correct" what looks like a typo in the source — preserve it and flag if relevant. - Do not fill in marks values you cannot justify from the source. - Do not include markdown code fences around the JSON. - Do not include any text before or after the JSON object. - Do not return null for required scalar fields except marks_per_question (which may be null when math fails). # Validation before returning Before returning the JSON, internally verify: - Each section's total_questions equals the actual length of its questions array. - For each section where marks_per_question is not null: section_total_marks equals marks_per_question × total_questions. - Every mcq question has an options array; no non-mcq question has one. - All q_no values within a section are unique (after suffixing duplicates). If any check fails, fix the data and the flags before returning. # Now parse the following question paper:`; /* ---------- Seed data ---------- `PASTE_SEED` and `STRUCTURED_JSON` are no longer hard-coded — they come from the backend (POST /papers/parse returns a structured response that conforms to the schema described in PARSER_SYSTEM_PROMPT above). The values below are empty placeholders so the screens render before any paper is parsed. */ const PASTE_SEED = ""; const STRUCTURED_JSON = { test_id: null, sections: [], parsing_flags: [] }; /* helper: flatten new-schema sections into the cockpit's internal question shape */ // Section-letter → 1-based section index. "A" → 1, "B" → 2, "C" → 3, etc. // Used in the right-panel evaluation cards to render Q numbers as "1.4" for // Section A · Q4 — disambiguates from Q4 in Section B / C when the right // panel shows questions sequentially without section headers. function sectionIndex(letter) { if (!letter) return 0; const c = String(letter).trim().toUpperCase().charCodeAt(0); if (c >= 65 && c <= 90) return c - 64; // A..Z → 1..26 return 0; } function qLabel(q) { const idx = sectionIndex(q?.sectionId); return idx > 0 ? `${idx}.${q.num}` : `${q.num}`; } // MCQ verdict signal — used by the right-pane card header. Returns // { mark: "match" | "miss" | "partial", color, icon } based on score ratio. function verdictFor(score, max) { const m = Number(max) || 0; const s = Number(score) || 0; if (m === 0) return { mark: "miss", color: "var(--ink-3)", icon: "·", label: "—" }; if (s >= m) return { mark: "match", color: "var(--green)", icon: "✓", label: "Correct" }; if (s <= 0) return { mark: "miss", color: "var(--red)", icon: "✗", label: "Incorrect" }; return { mark: "partial", color: "var(--amber)", icon: "~", label: "Partial" }; } function flattenForCockpit(json) { const flat = []; json.sections.forEach(sec => { sec.questions.forEach(q => { const marks = q.marks ?? sec.marks_per_question ?? (sec.section_total_marks && sec.total_questions ? Math.round(sec.section_total_marks / sec.total_questions) : 1); flat.push({ // Local id (sidebar key, selection set) — namespaced so Section A's Q1 // doesn't collide with Section B's Q1. id: `${sec.section_id}-Q${q.q_no}`, // Real DB UUID (used when POSTing to /api/evaluations); undefined for // paste-flow questions that haven't been persisted yet. uuid: q.uuid || null, num: q.q_no, type: q.type === "mcq" ? "mcq" : q.type === "long" ? "long_answer" : q.type === "short" ? "short_answer" : q.type, marks, stem: q.text, options: q.options ? q.options.map((t, i) => ({ k: "abcd"[i], text: t })) : undefined, concepts: q.prerequisite_tags || [], sub_questions: q.sub_questions || [], sectionId: sec.section_id, sectionLabel: `Section ${sec.section_id} — ${sec.title}`, }); }); }); return flat; } /* ================================================================== STEP 1 — Paste questions (with prompt viewer) ================================================================== */ const StepPaste = ({ text, setText, onNext, onCancel, onSkipToCockpit }) => { const blocks = text.split(/\n\s*\n/).filter(Boolean).length; const lines = text.split("\n").length; const [showPrompt, setShowPrompt] = useState(false); return (
Drop in the raw paper text — typed, copy-pasted, or OCR'd elsewhere. Gemini will structure it into the schema. Bengali, Hindi, and English are auto-detected.
{PARSER_SYSTEM_PROMPT}
Gemini is reading the text and converting it into the schema.
Confirm Gemini parsed the questions correctly. The JSON view is fully editable — your edits become the source of truth for evaluation.
{JSON.stringify(r, null, 2)}