팀스파르타
경력근거: PO(프로덕트오너) , 솔루션 , 백엔드/서버개발
※ 실제 산출물이 아닌 AI 예측 시안입니다.
팀스파르타는 코딩 교육을 튜터가 붙어 돌리는 곳이라 수강생이 낸 과제를 누가 어떤 기준으로 통과시키는지가 서비스 품질을 그대로 결정하고, 이 화면은 그 판정을 제품 안으로 들여와 튜터의 선택 하나가 수강생의 다음 단계를 여는 형태로 만든 축소판입니다. 채용 페이지에 튜터와 프로덕트엔지니어링과 백엔드가 함께 올라 있어 사람이 많이 붙는 운영을 제품으로 줄이는 일이 이 자리의 몫이라고 보고, 판정을 없애는 자동화가 아니라 판정 횟수를 실패 유형 수로 줄이는 방향을 골랐습니다. 다만 이 공고는 수집된 본문이 비어 있어 주요업무와 자격요건과 우대사항을 확인하지 못했고, 그래서 회사가 하는 일에서 확실한 것만 골조로 두고 특정 기술 스택이나 우대 조건을 추측해 박지 않았습니다. 기술 스택 항목도 공고에 목록이 없어 자사 서비스 운영 도구에서 흔한 구성으로 잡은 것입니다. 이 포트폴리오는 채점 결과를 도메인 모델로 다루고 사람의 판정이 상태 전이를 여는 내부 도구를 데이터부터 화면까지 혼자 만드는 풀스택 기술을 보여주며, 교육학이나 학습 설계 도메인 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
튜터 직군과 프로덕트엔지니어링 직군이 함께 열려 있어 튜터 손이 많이 드는 운영이 서비스의 축인 곳이라, 튜터 한 사람이 하루에 처리하는 판정 수를 줄이는 화면을 제품으로 잡았습니다.
프로덕트오너와 솔루션 직군이 같은 채용에 묶여 있어 기능 구현만이 아니라 운영 흐름을 정하는 일까지 함께 도는 자리라, 무엇을 자동으로 넘기고 무엇을 사람이 정할지의 경계를 제품 안에 그렸습니다.
백엔드와 서버개발이 함께 열려 있어 채점 실행과 화면이 분리된 구조를 다루게 되는 자리라, 채점기가 낸 결과를 입력으로 받아 판정과 상태 전이를 맡는 층을 따로 세웠습니다.
과제 채점기가 낸 제출별 실행 결과와 과제의 테스트 케이스 정의를 함께 받아, 전부 통과한 제출과 실행 자체가 깨진 제출은 규칙으로 나눠 놓고, 일부만 통과했거나 제출 파일이 빠졌거나 앞 제출과 코드가 그대로이거나 채점이 시간 초과로 끊긴 제출은 사유 코드와 함께 튜터 판정 대기로 세우는 1주 포트폴리오입니다. 대기 목록은 실패한 테스트 케이스 조합이 같은 것끼리 묶여 뜨고, 튜터는 한 건을 열어 통과 확정과 재제출 요청과 사유를 적은 예외 통과 가운데 하나를 고르며, 재제출 요청을 고르면 어느 테스트 케이스를 근거로 보여 줄지도 함께 지정합니다. 튜터가 고르기 전까지 그 제출은 수강생 화면에 결과가 뜨지 않고 다음 회차 과제도 열리지 않으며, 같은 묶음에 일괄로 적용할지 한 건씩 볼지도 튜터가 정합니다.
밤사이 쌓인 과제 제출 수백 건의 채점기 로그를 스프레드시트에 붙여 놓고 통과와 실패를 눈으로 훑다가, 애매한 제출만 따로 메모해 두었다가 다시 찾아 결과를 손으로 바꿔 주던 튜터 운영 담당자입니다. 뻔한 통과와 뻔한 실패는 도구가 나눠 주되, 반만 통과한 제출을 통과로 볼지 다시 시킬지는 본인이 정하고 싶은 사람입니다.
테스트 케이스 결과와 과제 정의가 데이터로 있으면 자동으로 넘겨도 되는 제출과 사람이 봐야 하는 제출은 규칙으로 정확히 갈리고 실패 유형이 같은 제출은 한 번에 묶이므로, 튜터가 하루에 내리는 판정 횟수가 제출 건수가 아니라 실패 유형 수로 줄어든다는 가정입니다.
채점기 결과와 과제 정의를 넣으면 자동 통과와 자동 재제출과 튜터 판정 대기가 사유 코드와 함께 갈리고, 대기 목록이 실패 케이스 조합별로 묶여 뜨며, 튜터가 통과 확정과 재제출 요청과 예외 통과 가운데 하나를 골라야 그 제출의 결과가 수강생 화면에 뜨고 다음 회차가 열리는 웹 화면이 배포까지 끝난 완결품입니다. 만드는 덩어리는 판정 규칙 엔진과 튜터 판정 화면 둘이며 평일 5일에 혼자 끝나는 범위입니다.
재제출된 제출을 앞선 판정에서 근거로 지정한 테스트 케이스와 대조해 그 케이스가 여전히 실패인지 통과로 바뀌었는지 판정해 두고, 지적한 곳이 그대로인 재제출을 대기 목록 위쪽에 세워 튜터가 같은 지적을 두 번 쓰지 않게 하는 재제출 추적 화면입니다.
쌓인 판정 이력을 과제와 회차와 테스트 케이스 단위로 집계해 어느 회차의 어느 케이스에서 재제출이 몰리는지 보여 주고, 튜터가 그 가운데 콘텐츠를 고쳐야 한다고 표시한 항목만 주간 리포트로 내보내는 화면입니다.
하루치 채점기 결과를 넣으면 자동으로 갈린 건과 판정 대기 묶음이 뜨고, 튜터가 한 묶음을 판정하면 그 수강생들의 결과와 다음 회차가 열리는 흐름을 처음부터 끝까지 시연합니다.
일부만 통과한 제출과 필수 파일이 빠진 제출과 변경 없는 재제출과 시간 초과 제출을 심어 둔 로그에서 그 네 유형이 모두 사유 코드와 함께 판정 대기로 서고, 실패 케이스 조합이 같은 건이 한 묶음으로 묶이는 것을 보입니다. 다만 정답과 데이터를 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
케이스 정의에 없는 case_id 와 결측 회차와 깨진 러너 메시지와 마감 이후 시각 표기가 섞인 로그 30종을 넣어도 화면이 죽지 않고 행 단위로 적재 거절 사유를 남기는 것을 보입니다.
위 결과는 더미데이터 자가채점이며, 튜터의 판정 시간이 실제로 줄어드는지는 팀스파르타의 실제 제출 로그와 튜터의 실제 사용이 있어야 측정 가능합니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다.
결과는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모가 도는 상태와 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 일부만 통과한 제출과 필수 파일이 빠진 제출과 코드가 그대로인 재제출과 정의에 없는 케이스 결과를 더미에 일부러 섞어 두었습니다.
이 공고는 수집된 본문이 비어 있어 주요업무와 자격요건과 우대사항을 확인하지 못했습니다. 회사가 하는 일에서 확실한 것만 골조로 두고 기술 스택과 우대 조건은 추측해 넣지 않았으며, 실제 공고 본문을 확인하면 스택 항목부터 다시 맞춰야 합니다.
이 포트폴리오는 판정 규칙과 상태 전이를 다루는 내부 도구 개발 기술을 보여주며 교육학이나 학습 설계 도메인 전문성은 주장하지 않습니다. 어떤 수준이면 통과인지의 기준 자체는 팀스파르타의 튜터와 커리큘럼 담당자가 정하는 것이고, 이 제품은 그 기준을 규칙으로 옮겨 담는 자리까지만 맡습니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
과제 채점기가 제출 한 건을 돌리고 남긴 실행 결과를 흉내 낸 더미입니다. 자동으로 넘길 제출과 튜터가 봐야 하는 제출을 가르는 입력입니다.
| submission_id | student_id | assignment_id | attempt_no | submitted_at_raw | runner_status | case_results | submitted_files | lint_flags | code_hash | memo |
|---|---|---|---|---|---|---|---|---|---|---|
| SB-1001 | ST-301 | AS-웹개발-04 | 1 | 2026-05-18 22:14 | completed | [{"case_id":"TC-1","result":"pass","message_raw":null},{"case_id":"TC-2","result":"pass","message_raw":null},{"case_id":"TC-3","result":"pass","message_raw":null},{"case_id":"TC-4","result":"pass","message_raw":null}] | ["app.py","README.md"] | [] | 9af31c | — |
| SB-1002 | ST-302 | AS-웹개발-04 | 1 | 2026-05-18 23:47 | error | [] | ["app.py"] | [] | 1b0e77 | 실행 자체가 깨져 케이스 결과가 하나도 없는 제출입니다 |
| SB-1003 | ST-303 | AS-웹개발-04 | 1 | 2026-05-18 21:02 | completed | [{"case_id":"TC-1","result":"pass","message_raw":null},{"case_id":"TC-2","result":"pass","message_raw":null},{"case_id":"TC-3","result":"fail","message_raw":"AssertionError: expected 3 items, got 2"},{"case_id":"TC-4","result":"fail","message_raw":"KeyError: 'total'"}] | ["app.py","README.md"] | [] | 77c2aa | 일부만 통과라 튜터 판정 대상입니다 |
| SB-1004 | ST-304 | AS-데이터-02 | 1 | 2026-05-18 20:31 | timeout | [{"case_id":"TC-1","result":"pass","message_raw":null},{"case_id":"TC-2","result":"skip","message_raw":"runner killed after 60s"}] | ["solution.py"] | [] | 44de10 | 채점이 시간 초과로 끊겨 뒤 케이스를 건너뛴 제출입니다 |
| SB-1005 | ST-305 | AS-웹개발-04 | 1 | 2026-05-18 19:55 | completed | [{"case_id":"TC-1","result":"pass","message_raw":null},{"case_id":"TC-2","result":"pass","message_raw":null},{"case_id":"TC-3","result":"pass","message_raw":null},{"case_id":"TC-4","result":"pass","message_raw":null}] | ["app.py"] | [] | 22bb90 | 전부 통과했지만 필수 제출 파일인 README.md 가 빠진 제출입니다 |
아래 JSON Schema에 맞는 과제 채점기 실행 결과 행을 30건 더 만들어 주세요. {"type":"object","properties":{"submission_id":{"type":"string"},"student_id":{"type":"string"},"assignment_id":{"type":"string"},"attempt_no":{"type":["integer","null"]},"submitted_at_raw":{"type":"string"},"runner_status":{"type":"string","enum":["completed","error","timeout"]},"case_results":{"type":"array","items":{"type":"object","properties":{"case_id":{"type":"string"},"result":{"type":"string","enum":["pass","fail","skip"]},"message_raw":{"type":["string","null"]}}}},"submitted_files":{"type":"array","items":{"type":"string"}},"lint_flags":{"type":"array","items":{"type":"string"}},"code_hash":{"type":["string","null"]},"memo":{"type":["string","null"]}},"required":["submission_id","student_id","assignment_id","submitted_at_raw","runner_status","case_results","submitted_files"]} 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라. 첫째 케이스가 일부만 통과했거나 앞 케이스 실패로 뒤가 skip 인 행, 둘째 runner_status 가 error 나 timeout 이라 결과가 비었거나 끊긴 행, 셋째 attempt_no 가 2 이상인데 code_hash 가 앞 제출과 같은 행과 attempt_no 나 code_hash 가 null 인 행, 넷째 submitted_files 에서 필수 파일이 빠졌거나 lint_flags 에 금지 라이브러리 표시가 붙은 행, 다섯째 submitted_at_raw 가 마감 이후이거나 한글 시간 표기이거나 연도가 빠진 행, 여섯째 과제 정의에 없는 case_id 가 섞인 행. 실패 케이스 조합이 같은 행도 여러 개 두어야 묶음 판정이 화면에서 드러납니다. 형식을 깨뜨리는 시도는 3건까지 별도로 만들어 주세요.{"type":"object","properties":{"submission_id":{"type":"string"},"student_id":{"type":"string"},"assignment_id":{"type":"string"},"attempt_no":{"type":["integer","null"]},"submitted_at_raw":{"type":"string"},"runner_status":{"type":"string","enum":["completed","error","timeout"]},"case_results":{"type":"array","items":{"type":"object","properties":{"case_id":{"type":"string"},"result":{"type":"string","enum":["pass","fail","skip"]},"message_raw":{"type":["string","null"]}}}},"submitted_files":{"type":"array","items":{"type":"string"}},"lint_flags":{"type":"array","items":{"type":"string"}},"code_hash":{"type":["string","null"]},"memo":{"type":["string","null"]}},"required":["submission_id","student_id","assignment_id","submitted_at_raw","runner_status","case_results","submitted_files"]}과제마다 어떤 테스트 케이스가 있고 무엇이 필수인지를 적어 둔 정의 더미입니다. 채점기 결과를 판정 규칙과 대조하고 튜터에게 보여 줄 근거 문구를 고르는 기준입니다.
| assignment_id | course | week_no | case_id | case_title | required | weight | hint_text | required_files | forbidden_imports | due_at_raw |
|---|---|---|---|---|---|---|---|---|---|---|
| AS-웹개발-04 | 웹개발 | 4 | TC-1 | 서버가 켜지고 루트 경로가 응답합니다 | true | 25 | 포트 설정과 실행 명령을 다시 확인해 보세요. | ["app.py","README.md"] | ["requests"] | 2026-05-18 23:59 |
| AS-웹개발-04 | 웹개발 | 4 | TC-2 | 목록 조회가 저장된 항목을 모두 돌려줍니다 | true | 25 | 조회 결과를 정렬하기 전에 개수를 먼저 맞춰 보세요. | ["app.py","README.md"] | ["requests"] | 2026-05-18 23:59 |
| AS-웹개발-04 | 웹개발 | 4 | TC-3 | 삭제한 항목이 목록에서 빠집니다 | true | 25 | — | ["app.py","README.md"] | ["requests"] | 2026-05-18 23:59 |
| AS-웹개발-04 | 웹개발 | 4 | TC-4 | 합계 값이 응답에 포함됩니다 | false | 30 | 응답 본문의 키 이름을 과제 안내와 맞춰 보세요. | ["app.py","README.md"] | ["requests"] | 2026-05-18 23:59 |
| AS-데이터-02 | 데이터분석 | 2 | TC-1 | 파일을 읽어 행 수를 맞춥니다 | true | 40 | 구분자와 인코딩을 먼저 확인해 보세요. | ["solution.py"] | [] | 2026-05-19 23:59 |
아래 JSON Schema에 맞는 과제 테스트 케이스 정의 행을 30건 더 만들어 주세요. {"type":"object","properties":{"assignment_id":{"type":"string"},"course":{"type":"string"},"week_no":{"type":["integer","null"]},"case_id":{"type":"string"},"case_title":{"type":"string"},"required":{"type":"boolean"},"weight":{"type":["number","null"]},"hint_text":{"type":["string","null"]},"required_files":{"type":"array","items":{"type":"string"}},"forbidden_imports":{"type":"array","items":{"type":"string"}},"due_at_raw":{"type":["string","null"]}},"required":["assignment_id","course","case_id","case_title","required","required_files"]} 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라. 첫째 한 과제의 weight 합이 100 이 아니거나 weight 가 null 인 행, 둘째 required 가 true 인데 hint_text 가 null 인 행, 셋째 week_no 나 due_at_raw 가 null 이거나 연도 없는 날짜 표기인 행, 넷째 같은 과제 안에서 case_title 이 똑같은 행, 다섯째 채점기 결과에는 나오지 않는 케이스만 정의된 과제. 정의와 채점기 결과가 서로 어긋나는 행이 있어야 적재 거절 사유가 화면에 드러납니다. 형식을 깨뜨리는 시도는 3건까지 별도로 만들어 주세요.{"type":"object","properties":{"assignment_id":{"type":"string"},"course":{"type":"string"},"week_no":{"type":["integer","null"]},"case_id":{"type":"string"},"case_title":{"type":"string"},"required":{"type":"boolean"},"weight":{"type":["number","null"]},"hint_text":{"type":["string","null"]},"required_files":{"type":"array","items":{"type":"string"}},"forbidden_imports":{"type":"array","items":{"type":"string"}},"due_at_raw":{"type":["string","null"]}},"required":["assignment_id","course","case_id","case_title","required","required_files"]}| Python | 채점기 결과 로그와 과제 정의를 읽어 형식이 흔들리는 행을 적재 형태로 정규화하고 판정 규칙을 계산하는 층을 만듭니다. 공고에 기술 스택 목록이 없어 자사 서비스 운영 도구에서 흔한 구성으로 잡았습니다. |
| TypeScript | 판정 결과와 화면을 같은 타입으로 묶고 채점기에서 들어오는 결과를 Zod 로 런타임에 검증해 형식이 어긋난 행이 화면까지 흘러가지 않게 막습니다. |
| React | 묶음 목록과 제출 상세와 판정 선택과 근거 케이스 지정이 한 화면에서 맞물리는 튜터 판정 화면을 만들고, 판정이 바뀔 때마다 묶음과 대기 수를 다시 받아 옵니다. |
| Node.js | 판정 저장과 상태 전이와 묶음 재계산을 각각의 엔드포인트로 나눠 화면과 규칙 엔진의 경계를 분명히 둡니다. |
| PostgreSQL | 제출과 케이스 결과와 튜터 판정 이력을 저장하고, 어느 회차의 어느 케이스에서 재제출이 몰리는지를 쿼리로 직접 집계합니다. |
| LLM API (Claude) | 튜터가 근거로 지정한 실패 케이스를 수강생이 읽을 문장으로 옮긴 코멘트 초안만 만들고, 판정 자체와 대기 분류에는 쓰지 않습니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 2주차에서 재제출이 지적한 케이스를 실제로 고쳤는지 대조하는 화면을 붙입니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 3주차에서 회차별 집계와 주간 리포트 화면을 만듭니다.
제출 한 건에 대한 판정 상태입니다. 규칙이 정한 분류와 사유와 묶음 열쇠를 담고, 튜터가 고른 판정과 근거 케이스와 코멘트 초안을 그 위에 얹어 수강생의 다음 단계를 엽니다.
{"type":"object","properties":{"submission_id":{"type":"string"},"auto_class":{"type":"string","enum":["auto_pass","auto_resubmit","tutor_required","ingest_rejected"]},"reason_codes":{"type":"array","items":{"type":"string"}},"batch_key":{"type":["string","null"]},"tutor_action":{"enum":["pass","resubmit","exception_pass",null]},"evidence_case_ids":{"type":"array","items":{"type":"string"}},"exception_note":{"type":["string","null"]},"comment_draft":{"type":["string","null"]},"unlocks_next":{"type":"boolean"},"hedge":{"type":["string","null"]}},"required":["submission_id","auto_class","reason_codes","evidence_case_ids","unlocks_next"]}[
{
"submission_id": "SB-1001",
"auto_class": "auto_pass",
"reason_codes": [
"all_cases_passed",
"required_files_present"
],
"batch_key": null,
"tutor_action": null,
"evidence_case_ids": [],
"exception_note": null,
"comment_draft": null,
"unlocks_next": true,
"hedge": null
},
{
"submission_id": "SB-1003",
"auto_class": "tutor_required",
"reason_codes": [
"partial_pass"
],
"batch_key": "AS-웹개발-04|TC-3,TC-4",
"tutor_action": "resubmit",
"evidence_case_ids": [
"TC-3"
],
"exception_note": null,
"comment_draft": "삭제한 항목이 목록에서 빠집니다 케이스에서 항목이 하나 남아 있습니다. 삭제 후 목록을 다시 조회해 개수를 확인하고 올려 주세요.",
"unlocks_next": false,
"hedge": "코멘트 초안은 튜터가 지정한 케이스 하나만 놓고 쓴 문장이라 다른 실패 원인은 담고 있지 않습니다."
},
{
"submission_id": "SB-1005",
"auto_class": "tutor_required",
"reason_codes": [
"required_file_missing:README.md"
],
"batch_key": "AS-웹개발-04|files_missing",
"tutor_action": "exception_pass",
"evidence_case_ids": [],
"exception_note": "케이스는 전부 통과했고 안내 문서만 빠져 이번 회차는 통과로 넘기고 다음 제출에서 받기로 했습니다.",
"comment_draft": null,
"unlocks_next": true,
"hedge": null
},
{
"submission_id": "SB-1006",
"auto_class": "tutor_required",
"reason_codes": [
"partial_pass",
"same_code_as_previous_attempt"
],
"batch_key": "AS-웹개발-04|TC-3,TC-4",
"tutor_action": null,
"evidence_case_ids": [],
"exception_note": null,
"comment_draft": null,
"unlocks_next": false,
"hedge": null
},
{
"submission_id": "SB-1008",
"auto_class": "ingest_rejected",
"reason_codes": [
"unknown_case_id:TC-99"
],
"batch_key": null,
"tutor_action": null,
"evidence_case_ids": [],
"exception_note": null,
"comment_draft": null,
"unlocks_next": false,
"hedge": null
}
][자동 검증] 1. 판정 JSON 이 위 스키마를 통과하는지 확인합니다. auto_class 가 네 값 밖이면 그 행을 버립니다. 2. auto_class 를 케이스 결과와 제출 파일 목록과 과제 정의로 다시 계산해 같은 값이 나오는지 대조합니다. 규칙이 정하는 값이라 재계산 결과가 다르면 그 자체가 결함입니다. 3. unlocks_next 가 true 이면 auto_class 가 auto_pass 이거나 tutor_action 이 pass 또는 exception_pass 여야 합니다. 사람이 고르지 않은 판정 대기 건에서 다음 회차가 열리는 일을 막기 위해서입니다. 4. tutor_action 이 resubmit 이면 evidence_case_ids 가 비어 있지 않아야 하고, 그 케이스는 실제로 실패한 케이스여야 하며, comment_draft 에 담긴 케이스 제목과 과제 이름이 원본 정의와 글자 그대로 일치해야 합니다. 5. tutor_action 이 exception_pass 이면 exception_note 가 있어야 합니다. 6. batch_key 가 같은 건들은 실패 케이스 조합도 같아야 합니다. [수동 검증] 1. 판정 대기 묶음 20건을 뽑아 같은 묶음의 제출들이 사람 눈으로도 같은 문제로 보이는지 확인합니다. 2. 코멘트 초안 20건을 수강생 시선으로 읽어 과제 밖의 내용을 지어내지 않았는지 확인합니다.
근거: PO(프로덕트오너) , 솔루션 , 백엔드/서버개발