# 답안 입력·채점

2026-10-03 기준 구현과 검증 범위. 회차의 **기록 → 답안 입력·채점**에서
**직접 누르기 / 사진으로 읽기**를 고른다. [체험 화면](design/omr/index.html)은
앱과 같은 모듈을 사용하지만 기록·초안을 저장하지 않는다.

## 양식과 표시

검색한 범위에서는 공식 공개 전국연합학력평가 빈 답안지 원본을 찾지 못했다.
사용자 지정에 따라 [경기도교육청의 2026학년도 수능 답안지 견본 공개 자료](https://www.goe.go.kr/goe/na/ntt/selectNttInfo.do?mi=10961&nttSn=2330309)를 사용한다.
사진 판독은 국어·수학·영어·한국사·탐구·제2외국어/한문 여섯 수능 견본을
1800×1406 lossless WebP로 렌더링해 사용한다. 원본의 견본 표시를 유지한다.

직접 입력은 추가로 찾은 [경기도교육청 소속 학교의 공식 공개 실시요강](https://gcja-h.goeay.kr/gcja-h/na/ntt/selectNttInfo.do?bbsId=2506&mi=5763&nttSn=1329024)에
들어 있는 **2026년 시행(2027학년도) 9월 모평** 답안지 앞면을 사용한다.
HWP 안의 3420×2683 JPEG(탐구는 3447×2683)를 그대로 추출해 lossless WebP로
변환했다. ‘견본’ 표시가 없는 실제 공개 양식이며, 기존 786×619 원본보다
가로 해상도가 약 4.35배다. 확대 보간으로 새 세부 정보를 만든 것이 아니다.
[추출 위치·지문](../omr/templates/README.md)을 기록했다. 여섯 앞면의 전체 용량은
약 15MiB이며 서비스 워커가 오프라인용으로 보관한다.
교시·영역은 원본에 인쇄된 표시를 그대로 사용한다. 시험명은 수능·6월/9월 모평·
월별 학평의 **미리 만든 SVG 윤곽 프리셋**과 숫자 프리셋을 찍는다. 실행 중 텍스트·
웹폰트로 제목을 다시 그리지 않는다. 모평 제목은
`0000학년도 대학수학능력시험 N월 모의 평가 답안지` 형식이며, 글자 크기는
원본 제목의 실제 인쇄 높이에 맞춘다. 탐구의 사회·과학·직업 구분만 별도 영역
프리셋을 적용한다. 직접 입력에서 상단 여백의 작은 중복 영역 표시는 가리고
실제 교시·영역 표시와 기계 판독용 표식은 유지한다.
제목·탐구 영역의 교체 범위는 각 원본의 픽셀 좌표로 지정하며, 교시 원·등록 표식·
인적사항·답란을 가리지 않는다. 사진 확인용 양식은 다른 좌표를 사용한다.
학습용 표시 레이어이며 원본 이미지와 사진 판독 기준은 변경하지 않는다.
수학 직접 입력의 세로 열은 표시한 원본 이미지의 상단 검정 기계 표식 45개에서
중심을 읽는다. 문항별 열은 표식 번호에 연결하며, 열 사이의 간격을 동일하다고
가정하지 않는다. 작은 띠만 읽어 해상도에 맞춰 환산하고, 표식 개수·양끝 위치를
검증한 뒤 클릭을 허용한다. 표식이 누락되면 답안 입력과 채점을 막고 재선택을
안내한다. 사진 판독 좌표에는 이 직접 입력용 표식 연결을 적용하지 않는다.
직접 입력과 사진 판독은 답란 좌표가 다른 양식이므로 혼용하지 않는다. 사진에서
읽은 답을 확인하거나 해당 초안을 이어갈 때는 사진과 동일한 수능 견본을 표시한다.

화면 폭 768px 미만에서는 확대된 답란과 문항 이동을 사용한다. 768px 이상에서는
전체 화면에 OMR 한 장을 가용 폭·높이에 맞춰 펼치고 바로 마킹할 수 있다.
960px 이상에서는 조작·채점 버튼을 옆에 모아 답안지 공간을 확보한다.
작은 마킹 칸을 대신할 현재 문항의 44px 이상 답 버튼도 제공한다.
회전·창 크기 변경 시 표시를 다시 맞추며 답·정답표·초안을 보존한다.
평가원 회차는 시행 연도 + 1 학년도, 교육청 학평은 시행 연도로 표시한다.

현재 지원 범위는 2021년 시행 이후 고3·N수 과목이다. 이전 수학·국어 배치나
고1·고2 배치를 검증 없이 같은 양식으로 읽지 않는다. 국어 45문항/1교시,
수학 30문항/2교시, 영어 45문항/3교시, 한국사 20문항/4교시,
탐구 20문항/4교시, 제2외국어·한문 30문항/5교시를 사용한다.
탐구 사진은 제1선택·제2선택 답란 중 선택한다. 직접 입력은 제1선택 답란을
기본으로 사용한다. 수학 서답형(단답형)은 0~999 정수이며 백·십·일의 자리별
OMR 칸을 답안지 위에서 직접 누른다. 같은 자리의 다른 숫자는 기존 선택을 바꾸고,
선택한 숫자를 다시 누르면 지운다. 빈칸과 0을 구분하며 1→백·십 자리 빈칸,
100→백 1·십 0·일 0처럼 입력한다. 중간 자리가 빠지거나 일의 자리가 없는 마킹은
미완성으로 남겨 채점을 막는다. 초안에 부분 마킹도 보존한다. 작은 답란 대신
44px 이상 숫자 버튼을 쓰는 ‘숫자 답란 확대’도 제공한다.

## 사진 처리

Android 10.2부터 [Google ML Kit 문서 스캐너](https://developers.google.com/ml-kit/vision/doc-scanner/android)를
사용한다. 16.0.0 SDK, 한 페이지 JPEG, 자르기·회전만 하는 BASE 모드다.
마킹을 지울 수 있는 얼룩 제거 모드는 사용하지 않는다. 앱 자체의 카메라 권한은
추가하지 않는다. Google Play 서비스가 필요하며 최초 실행에는 구성 요소
다운로드가 필요할 수 있다. 실패하면 JPEG/PNG 파일 선택이나 직접 입력으로 전환한다.
옛 앱에서는 새 스캔 메서드를 요구하지 않고 업데이트·직접 입력을 안내한다.
브라우저는 기기의 파일·촬영 선택 기능을 이용한다.

문서의 밝은 영역으로 모서리를 제안한 뒤 사용자가 네 모서리·회전을 확인한다.
직접 만든 JS 원근 보정과 원본 좌표 기반 마킹 판독을 기기 안에서 수행한다.
[OpenCV 원근 변환](https://docs.opencv.org/4.x/da/d6e/tutorial_py_geometric_transformations.html)과
[OMRChecker의 템플릿·답란 방식](https://github.com/Udayraj123/OMRChecker)을 참고했다.
Python 런타임이나 해당 프로젝트 코드를 앱에 복사한 구현은 아니다.

**선택한 견본과 동일한 배치만 지원**한다. 임의의 교육청 답안지·촬영된 정답표를
범용 OCR로 읽는 기능은 아니다. 중복·흐린 마킹·미응답·숫자 자리 오류는 확인
항목으로 남긴다. 전체 답안을 원본과 비교했다는 확인 없이는 사진 채점을 진행하지
않는다. 사진 인식은 시험 적용 단계이며 **실물 촬영 정확도는 아직 현장 검증 전**이다.
문서 모서리와 답란이 정확히 맞아야 하므로 자동 검출을 그대로 신뢰하지 않는다.

웹 파일은 JPEG/PNG 20MB·40MP 제한과 디코딩 시 축소를 적용한다. 네이티브도
40MP 제한·샘플링을 적용하며 웹에 전달하는 JPEG는 2MB 이하이다. 처리 이미지의
긴 변은 1800px로 제한한다. 사진은 서버·동기화·백업·초안에
넣지 않고 화면을 닫을 때 폐기한다. 늦게 도착한 이전 스캔 결과는 요청 ID로 거른다.

## 채점과 보관

자동으로 준비된 정답표는 **2026년 9월 시행(2027학년도) 평가원 모평
생명과학Ⅰ** 한 회차이며 문제·정답·해설과 배점을 수동 대조했다.
다른 회차의 정답은 직접 확인해 입력한다. 배점은 아래 검증된 등록 범위에서는
미리 채워 주되, 시험지와 일치하는지 확인한 뒤 적용한다.
모든 정답 이미지에 대한 자동 OCR은 아직 제공하지 않는다. 정답 수, 선택형 1~5,
수학 단답형 0~999, 배점 합계 50/100점을 확인한 뒤 적용한다.
정답표·초안 열쇠는 과목·시행일·원본 제목·정답 URL을 포함해 홀짝형을 구분한다.

채점 후 사용자가 기록을 눌러야 기존 점수·틀린 번호에 반영된다. 미응답도 틀린
번호에 포함하며 결과 화면에서는 오답과 미응답을 나눠 보여 준다. 기존 푼 날과
풀이시간은 보존하고, 푼 날이 없으면 오늘로 표시한다. 점수·틀린 번호는 기존
백업·계정 동기화 경로를 따른다. `gijul.omr.drafts.v1`의 답안·사용자 입력 정답표는
이 기기에만 최근 20개 보관하며 기기 간 동기화·파일 백업 대상이 아니다.
초안 저장 실패·기록 저장 실패를 성공으로 표시하지 않는다.

## 문항별 배점 등록·수집

**수학**: 2021~2026년 시행 고3 확률과 통계·미적분·기하의 공통·선택 형식에
번호별 배점을 등록했다. 공통 1~22번 74점, 선택 23~30번 26점, 합계 100점이다.
2021년 시행 6월 모평·수능, 2025년 시행 6월 모평·수능, 2026년 시행 7월 학평·
9월 모평의 세 선택과목 **18개 문제지**에서 같은 배열을 대조했다. 다른 학년·
이전 수학 가/나형·2027년 시행 이후 개편 형식에 자동 적용하지 않는다.

**국어·영어·한국사·탐구**: 총점은 같아도 번호별 3점 문항이 회차마다 달랐다.
2025년 시행 6월 모평·수능 홀수형과 2026년 시행 9월 모평의 화법과 작문·영어·
한국사·생명과학Ⅰ **12개 회차**를 원본 문제지에서 대조해 개별 등록했다.
[원본 URL·SHA-256·번호별 배열](../omr/points-reviewed.json)을 공개한다.
과목 ID·시행일·원본 제목·정답 URL·문제 URL이 모두 일치할 때만 미리 채운다.
제2외국어도 독일어Ⅰ·일본어Ⅰ의 같은 세 회차를 추가 대조했다. 1점 문항 위치가
달라 **6개 회차**를 개별 등록했다. 짝수형이나 같은 날 다른 선택과목에 복사하지 않는다. 정답 자체는 배점 수집과 별개다.

재현용 수집 도구 [`collect_omr_points.py`](../tools/collect_omr_points.py)는
Poppler `pdftotext -raw`의 텍스트에서 문항 번호와 `[1점]`·`[2점]`·`[3점]`·`[4점]`을 읽는다.
번호가 1~마지막까지 정확히 한 번씩 순서대로 있는지, 문항당 표시가 하나인지,
총점이 50/100점인지 검증한다. 표시 누락을 임의로 2점 처리하지 않는다.
수학은 모든 배점을 명시해야 하며, 다른 영역은 검토자가 공식 배점 구조·원본을
확인했을 때만 `--unmarked-two`로 표시 없는 2점 처리를 허용한다.

```bash
python3 tools/collect_omr_points.py 원본.pdf --count 30 --maximum 100 \
  --source '공식 문제지 HTTPS URL' --subject '미적분' --output 검토후보.json
```

출력은 `reviewed:false`인 **검토 후보**이며 앱에 자동 등록하지 않는다. PDF 파일명으로
과목을 판정하지 않고 렌더링한 첫 페이지에서 시험·과목·유형을 확인한다. 원본의
모든 문항 배점을 대조한 뒤 등록한다. 스캔 PDF·텍스트가 깨진 옛 문제지·비정상
읽기 순서는 거절하며, OCR을 추가하려면 문항 좌표와 원본의 사람 대조가 필요하다.
정답 PDF/이미지를 읽는 별도 수집기로 확대할 수 있지만 현재 범용 답지 OCR은 없다.

## 검증

`tests/test_omr.py`가 실제 견본 좌표, 태블릿 전체 표시·마킹·창 크기 전환, 직접 입력→42점/오답 3·15·19 기록,
기존 날짜·시간 보존, 여섯 직접 입력 양식의 인쇄된 답란 1,266곳과 좌표 대조,
수학 상단 기계 표식 45개 기반 열 정렬·20·21·30번 실제 마킹·해상도 변경·좌표 오염과 표식 누락 거절, 시험명 프리셋·연도 구분·제목 영역 침범 방지, 자릿수 직접 마킹·미완성 초안 복원·채점 차단, 배점 출처 분리, 회차·홀짝형별 정답 분리, 원본에 합성한
마킹 판독, 중복·흐린 답·여섯 양식 간 교차 판독 거절, 사진 확인 단계와 늦은 응답을 검증한다.
합성 이미지 검증은 실물 촬영 성공률을 의미하지 않는다. ML Kit 실제 카메라
촬영과 다양한 종이·조명·기기·필기구는 추가 현장 검증이 필요하다.

Android 10.3에 태블릿 전체 표시와 직접 입력 양식을 반영했다.
Android 단위시험·lint·디버그 빌드와 API 36 에뮬레이터의 실제 WebView 입력·
스캔 브리지·뒤로가기·늦은 응답을 확인했다. WebKit은 로컬 실행 라이브러리가
부족해 생략하며 배포 CI의 실제 실행 결과를 따로 확인한다.

## 회차 목록 시안

[다른 버전 시안](design/versions/index.html)은 홀수형·짝수형을 같은 줄에서 전환한다.
‘다른 버전 ↔’은 유형명 바로 옆에 있으며 글자+기호/기호만 표시를 비교할 수 있다.
실제 정적 API의 문제·정답·해설과 보내기 목록, OMR의 회차·유형·정답 URL을 함께
바꾼다. 짝수형 해설이 없는 회차는 비활성 표시한다. 푼 표시·풀이시간·점수·틀린 번호는
시안 안에서 회차에 함께 표시하며 localStorage·동기화·앱 기록에는 저장하지 않는다.
실제 회차 목록의 두 줄 표시는 이 시안 배포만으로 변경되지 않는다.
