한빛앤
경력근거: LMS 및 디지털 콘텐츠 플랫폼의 백엔드 시스템 설계·개발·운영
※ 실제 산출물이 아닌 AI 예측 시안입니다.
한빛앤은 한빛미디어의 30년 IT/AI 콘텐츠를 기반으로 디지털 학습 플랫폼 한빛+ 를 만드는 회사이고, 이 자리는 그 LMS 백엔드를 초기 단계부터 설계·개발·운영합니다. 학습자가 어느 개념에서 막혔는지 짚고 다음 강의를 근거와 함께 추천하려면 한빛+ 고유의 강의 커리큘럼, 개념 사이 선수관계, 퀴즈 문항-개념 태깅이 있어야 하고, 이 진단→추천 사슬은 다른 회사 콘텐츠로는 그대로 만들 수 없습니다. 다만 이 포트폴리오는 선수 개념 그래프 탐색·오답 매핑·결정론 추천이라는 백엔드 기술을 보여주며, 학습 진단의 교육학적 전문성은 주장하지 않습니다. 실제 개념 선수관계와 문항 난이도의 타당성은 한빛앤 강사·콘텐츠팀의 검수가 필요하고, 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
한빛+ 서비스를 아이디어 구상부터 출시·개선까지 함께합니다. 기술 영역을 한정하지 않고 사용자의 문제를 정의해 제품 중심으로 푸는 자리라, 데이터에서 인사이트, 추천, 화면까지 한 사람이 닫는 최소 단위가 어필 지점입니다.
플랫폼 사업부 LMS 개발팀의 백엔드 시스템 설계·개발·운영이 주요업무입니다. 스택이 Kotlin/Java Spring Boot·PostgreSQL·AWS 로 명시돼, 이 위에 실제로 도는 서비스를 얹는 것이 기술 확인의 핵심입니다.
전자책·강의를 보여주는 뷰어가 아니라 학습 효과를 극대화하는 서비스를 지향합니다. 학습자가 어디서 막히는지 데이터로 짚어 다음 콘텐츠를 근거와 함께 제시하는 것이 '더 나은 학습 경험' 을 제품으로 옮기는 각도입니다.
학습자의 퀴즈 응시 기록과 진도를 한곳에 모아, 오답이 몰린 개념을 선수 개념 그래프에서 위로 거슬러 올라가며 정말 막힌 뿌리 개념을 결정론으로 짚습니다. 그 개념의 미이수 강의를 난이도 순으로 3건 추천하고, 각 추천이 어떤 오답 문항을 근거로 나왔는지 함께 보여줍니다. 막힌 개념 판정과 추천 후보 선정은 그래프 탐색·이수 여부라는 규칙으로 정해지고, LLM 은 '왜 이 개념부터 다시 보라는지' 를 학습자 친화적인 문장으로 바꾸는 데만 씁니다. 1주차만으로 백엔드 API 와 대시보드가 도는 완성품이고, 근거는 채용공고 정렬이고, 데이터는 작성자가 만든 더미입니다.
한빛앤 LMS 개발팀 PE. 학습자가 자꾸 특정 챕터에서 이탈하는데 '어느 선행 개념이 안 잡혀서 그런지' 를 매번 손으로 로그를 뒤져 추측하던 상황을 끝내고, 다음에 뭘 보라고 할지까지 근거를 붙여 자동으로 내놓고 싶은 사람입니다.
퀴즈 문항과 강의 콘텐츠에 개념 태그를 달고 개념 사이 선수관계를 그래프로 두면, 학습자 오답에서 (a) 오답이 몰린 개념과 그 선수 경로를 따라 정말 막힌 뿌리 개념을 규칙으로 짚고, (b) 그 개념의 미이수 강의를 근거 문항과 함께 3건 추천하며, (c) 추천 강의를 들은 뒤 다음 퀴즈 정답률이 실제로 오르는지로 추천이 맞았는지 확인할 수 있습니다.
퀴즈 오답·진도 기록을 넣으면 선수 개념 그래프를 탐색해 막힌 뿌리 개념을 짚고 미이수 강의 3건을 근거 문항과 함께 추천하는, Spring Boot 백엔드 API + Next.js 대시보드입니다. 기본 배포로 바로 도는 완성품입니다.
2주차부터 AWS 로 배포·운영하며, 추천 강의를 이수한 뒤 다음 퀴즈 정답률이 올랐는지 추적하고 강사 검수자 2명이 개념 매핑이 타당한지 라벨링해 신뢰도를 보여주는 대시보드입니다.
새 문항이 들어오면 LLM 이 개념 태그 후보를 제안하고 사람이 확정하는 반자동 태깅과, 1주차 가정값 대신 실제 이수-정답률 분포로 추천 확신도를 다시 맞추는 단계입니다.
학습자별 오답 시나리오 10개를 넣으면, 막힌 뿌리 개념과 근거 붙은 미이수 강의 3건이 몇 초 안에 나오는 걸 시연합니다
같은 입력에는 항상 같은 진단·추천이 나오는 걸 재현 테스트로 보여줍니다. 막힌 개념 판정과 추천 후보 선정이 그래프 탐색·이수 여부라는 규칙으로 정해지기 때문입니다
일부러 만든 악성·결측 입력 30종(미등록 개념 태그·미응시 결측·선수관계 끊긴 개념·별칭 표기 흔들림)을 넣어도 출력 형식(JSON Schema)이 한 번도 안 깨지고, 미매핑 태그는 추측 없이 '미매핑' 으로 표시합니다
내가 만든 정답 30건 중 막힌 뿌리 개념이 일치한 게 25건(83%)입니다. 단, 정답과 데이터를 모두 작성자가 만든 합성 기준이라 '성능 검증' 이 아니라 '데모 조건에서의 동작' 임을 함께 명시합니다
위 수치는 전부 작성자가 만든 더미데이터 자가채점이며, 실제 진단 정확도와 추천 이수 전환율은 한빛+ 실 학습 로그와 실제 강사 검수, 실제 커리큘럼 선수관계가 있어야 측정 가능합니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다
이건 고객·실데이터 없이 혼자 만든 포트폴리오입니다. 모든 수치는 합성 데이터 자가채점이라 '성능 증명' 이 아닙니다. 면접에서는 솔직히 '데모 조건 동작 + 실검증 준비 상태' 로 설명합니다
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 미등록 개념 태그·미응시 결측·선수관계가 끊긴 개념·별칭 표기 흔들림 같은 경계 케이스를 더미에 일부러 섞어, 쉬운 문제만 푸는 게 아님을 보입니다
오답이 곧 그 개념을 모른다는 뜻은 아닙니다(찍어서 맞거나 실수로 틀립니다). 오답률과 표본 수를 함께 보고, 표본이 적으면 확신도를 낮추며 '~때문에 막혔다' 는 단정 표현을 가드로 눌러 상관을 인과로 비약하지 않게 합니다
이 포트폴리오는 선수 개념 그래프 탐색·오답 매핑·결정론 추천이라는 백엔드 기술을 보여주며, 학습 진단의 교육학적 전문성은 주장하지 않습니다. 실제 개념 선수관계와 문항 난이도의 타당성은 한빛앤 강사·콘텐츠팀의 검수가 필요하고, 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다
실제 도입 시 달라지는 것을 명시합니다. 실 학습 로그 분포는 더미와 다르므로 확신도 기준을 다시 맞춰야 하고, 개념 선수관계는 임의로 그린 그래프가 아니라 한빛+ 실제 커리큘럼으로 교체해야 하며, 강사 검수도 실제 인력으로 바꿔야 합니다(도입 단계 과제로 분리)
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
학습자별 퀴즈 응시 로그. 진단 API 가 가장 먼저 질의하는 테이블입니다. 운영 현실대로 미등록 개념 태그(orphan)·미응시 결측·별칭 표기 흔들림·진도 경계값을 일부러 섞어, 쉬운 데이터만 푸는 게 아님을 보입니다.
| learner_id | quiz_item_id | concept_raw | concept_id | is_correct | answered_at | progress_pct |
|---|---|---|---|---|---|---|
| l_001 | q_301 | 리스트 | c_list | false | 2026-06-10T09:00:00 | 45 |
| l_001 | q_305 | 배열 | c_list | false | 2026-06-10T09:04:00 | 45 |
| l_001 | q_309 | 리스트 | c_list | true | 2026-06-10T09:08:00 | 45 |
| l_001 | q_312 | 딕셔너리 | c_dict | false | 2026-06-10T09:12:00 | 40 |
| l_001 | q_315 | 해시맵 | c_dict | false | 2026-06-10T09:16:00 | 40 |
아래 JSON Schema 에 맞는 IT 학습 플랫폼 퀴즈 응시 로그를 학습자 8명 × 문항 여러 개 JSON 배열로 생성하세요.
{"type":"object","properties":{"learner_id":{"type":"string"},"quiz_item_id":{"type":"string"},"concept_raw":{"type":"string"},"concept_id":{"type":["string","null"]},"is_correct":{"type":["boolean","null"]},"answered_at":{"type":["string","null"]},"progress_pct":{"type":["number","null"]}},"required":["learner_id","quiz_item_id","concept_raw","concept_id","is_correct"]}
쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라(쉬운 문제만 푸는 함정 회피): (1) 같은 개념의 별칭 표기 혼입(concept_raw '배열'→concept_id c_list, '해시맵'→c_dict, '클래스'→c_oop 처럼 concept_catalog 로 정규화돼야 함), (2) 미등록 개념 태그(concept_id=null) 2~3건(concept_catalog 에 없는 신규 개념), (3) 미응시·스킵 결측(is_correct/answered_at/progress_pct 가 null 인 행 1~2건), (4) 진도 경계값(progress_pct 0.0 과 100.0 각 1건), (5) 같은 개념에 정답·오답이 섞여 오답률이 0/1 이 아닌 중간값이 나오게. concept_id 는 c_var·c_cond·c_loop·c_func·c_list·c_dict·c_oop 등 concept_catalog 의 id 와 매칭. JSON 배열만.{"type":"object","properties":{"learner_id":{"type":"string"},"quiz_item_id":{"type":"string"},"concept_raw":{"type":"string"},"concept_id":{"type":["string","null"]},"is_correct":{"type":["boolean","null"]},"answered_at":{"type":["string","null"]},"progress_pct":{"type":["number","null"]}},"required":["learner_id","quiz_item_id","concept_raw","concept_id","is_correct"]}개념 지도. 개념별 표준 id·별칭·선수 개념·연결 강의·난이도를 담습니다. 그래프 탐색이 막힌 뿌리 개념을 짚고 추천 강의를 뽑는 근거 테이블입니다. 선수관계가 끊긴 orphan·강의 미제작 개념·별칭을 일부러 섞어, 그래프가 깨진 상황에서도 진단이 안 무너지는지 보입니다.
| concept_id | canonical_name | aliases | prerequisite_ids | content_ids | difficulty |
|---|---|---|---|---|---|
| c_var | 변수와 자료형 | ["변수","자료형"] | [] | ["ct_101"] | intro |
| c_cond | 조건문 | ["조건문","if문"] | ["c_var"] | ["ct_108"] | basic |
| c_loop | 반복문 | ["반복문","루프","for문"] | ["c_cond"] | ["ct_110"] | basic |
| c_func | 함수 | ["함수"] | ["c_var"] | ["ct_120"] | basic |
| c_list | 리스트 | ["리스트","배열"] | ["c_loop"] | ["ct_130"] | inter |
아래 JSON Schema 에 맞는 IT 학습 개념 지도 30행 JSON 배열로 생성하세요. 프로그래밍 입문~중급 개념을 선수관계로 연결하세요.
{"type":"object","properties":{"concept_id":{"type":"string"},"canonical_name":{"type":"string"},"aliases":{"type":"array","items":{"type":"string"}},"prerequisite_ids":{"type":"array","items":{"type":"string"}},"content_ids":{"type":"array","items":{"type":"string"}},"difficulty":{"type":"string","enum":["intro","basic","inter","advanced"]}},"required":["concept_id","canonical_name","aliases","prerequisite_ids","content_ids","difficulty"]}
쉬운 데이터만 만들지 말 것. 하드 케이스 의도적 혼입: (1) 뿌리 개념(prerequisite_ids=[]) 2~3건, (2) 선수 id 가 이 목록에 없는 orphan(예: c_zzz_missing) 1건(그래프 끊김 → 미매핑으로 알려야 함), (3) 강의 미제작 개념(content_ids=[]) 1~2건, (4) 같은 개념의 별칭 1~3개(aliases). difficulty 분포 자연스럽게, 선수관계가 순환하지 않게(위상 정렬 가능하게). JSON 배열만.{"type":"object","properties":{"concept_id":{"type":"string"},"canonical_name":{"type":"string"},"aliases":{"type":"array","items":{"type":"string"}},"prerequisite_ids":{"type":"array","items":{"type":"string"}},"content_ids":{"type":"array","items":{"type":"string"}},"difficulty":{"type":"string","enum":["intro","basic","inter","advanced"]}},"required":["concept_id","canonical_name","aliases","prerequisite_ids","content_ids","difficulty"]}| Kotlin | Spring Boot 기반 진단·추천 API 의 주 구현 언어. 선수 개념 그래프 탐색기(위상 정렬 + 미이수 선수 탐색)와 오답→개념 매핑 서비스를 담당합니다 |
| Java | 기존 LMS 연동부(퀴즈 응시 로그 수집 배치)를 Java 로 붙여, Kotlin 신규 모듈과 Java 레거시가 한 Spring 컨텍스트에서 공존하는 걸 보입니다 |
| Spring Boot | 진단 API(`POST /diagnose`. learner_id·기간 입력 → output_schema 반환)와 추천 결과 조회 API, 강사 검수 라벨 수집 엔드포인트 |
| PostgreSQL | quiz_attempts / concept_catalog 단일 진실원 + 개념 이수·오답률 집계 뷰(`v_learner_concept_stat`). 선수관계는 재귀 CTE 로 경로 조회 |
| JPA | concept_catalog·quiz_attempts 엔티티 매핑과 집계 쿼리. 선수관계 재귀 조회는 native query 로 분리 |
| Next.js | 막힌 개념 카드 + 추천 강의 리스트 + 근거 문항 하이라이트 대시보드(React·TypeScript·zustand·Tailwind) |
| TypeScript | 프런트 추천 카드 렌더링 + 응답 JSON Schema 런타임 검증 |
| AWS | 2주차부터 ECS/RDS(PostgreSQL) 로 배포·운영. 응시 로그 수집 배치는 EventBridge 스케줄 |
| Github Actions | 테스트(그래프 탐색 결정론 재현 테스트 포함) + 빌드 + AWS 배포 파이프라인 |
| Grafana | 일일 진단 요청 수·미매핑 개념 태그 비율·추천 이수 전환율 대시보드 |
| Sentry | 진단 API 예외·스키마 검증 실패 추적 |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 2주차(AWS 배포 + 추천이 실제로 도움됐는지 확인)로 진행합니다.
✓ pass → 여기서 멈춰도 완결입니다. 더 키우면 3주차(개념 태깅 반자동화 + 추천 확신도 보정)로 진행합니다.
1) 오답이 몰린 개념과 그 선수 경로를 따라 짚은 막힌 뿌리 개념(근거 오답 문항 포함), 2) 그 개념의 미이수 강의 3건 추천(각 추천이 어떤 오답에서 나왔는지 evidence 로 추적), 3) 학습자 친화 설명 문구. 진단→추천을 한 스키마에 담습니다.
{"type":"object","properties":{"learner_id":{"type":"string"},"period":{"type":"string"},"stuck_concepts":{"type":"array","items":{"type":"object","properties":{"concept_id":{"type":"string"},"name":{"type":"string"},"wrong_rate":{"type":"number"},"wrong_item_ids":{"type":"array","items":{"type":"string"}},"prerequisite_path":{"type":"array","items":{"type":"string"}}},"required":["concept_id","name","wrong_rate","wrong_item_ids","prerequisite_path"]}},"recommendations":{"type":"array","minItems":1,"maxItems":3,"items":{"type":"object","properties":{"content_id":{"type":"string"},"target_concept_id":{"type":"string"},"reason":{"type":"string"},"difficulty":{"type":"string"},"evidence_item_ids":{"type":"array","items":{"type":"string"}},"confidence":{"type":"string","enum":["low","mid","high"]}},"required":["content_id","target_concept_id","reason","difficulty","evidence_item_ids"]}},"unmapped_tags":{"type":"array","items":{"type":"string"}},"explanation":{"type":["string","null"]}},"required":["learner_id","period","stuck_concepts","recommendations","unmapped_tags"]}[
{
"input_ref": "l_001 · 2026-06-08~06-10 (리스트·딕셔너리 오답 집중)",
"output": {
"learner_id": "l_001",
"period": "2026-06-08~2026-06-10",
"stuck_concepts": [
{
"concept_id": "c_list",
"name": "리스트",
"wrong_rate": 0.67,
"wrong_item_ids": [
"q_301",
"q_305"
],
"prerequisite_path": [
"c_var",
"c_cond",
"c_loop",
"c_list"
]
},
{
"concept_id": "c_dict",
"name": "딕셔너리",
"wrong_rate": 1,
"wrong_item_ids": [
"q_312",
"q_315"
],
"prerequisite_path": [
"c_var",
"c_cond",
"c_loop",
"c_list",
"c_dict"
]
}
],
"recommendations": [
{
"content_id": "ct_110",
"target_concept_id": "c_loop",
"reason": "리스트·딕셔너리 오답의 공통 선수 개념인 반복문이 미이수 상태입니다. 변수·조건문은 정답률이 높아 넘어가고, 아직 다지지 않은 가장 기초 개념인 반복문부터 다시 보도록 추천합니다.",
"difficulty": "basic",
"evidence_item_ids": [
"q_301",
"q_312"
],
"confidence": "high"
},
{
"content_id": "ct_130",
"target_concept_id": "c_list",
"reason": "반복문을 마친 뒤 리스트 강의를 재학습하도록 추천합니다. 오답 2건이 리스트 개념에 몰려 있습니다.",
"difficulty": "inter",
"evidence_item_ids": [
"q_305"
],
"confidence": "mid"
},
{
"content_id": "ct_140",
"target_concept_id": "c_dict",
"reason": "리스트를 다진 뒤 딕셔너리로 이어가도록 추천합니다. 딕셔너리는 리스트를 선수로 하므로 순서상 마지막입니다.",
"difficulty": "inter",
"evidence_item_ids": [
"q_315"
],
"confidence": "mid"
}
],
"unmapped_tags": [],
"explanation": "리스트와 딕셔너리 문제에서 자주 막히셨는데, 두 개념이 공통으로 딛고 있는 반복문이 아직 덜 잡힌 것으로 보입니다. 반복문 강의부터 다시 보시면 그다음이 한결 수월해집니다. 다만 문항 수가 많지 않아 확정은 아니고, 반복문을 보신 뒤 정답률로 확인해 보시길 권합니다."
}
},
{
"input_ref": "l_002 · 2026-06-11 (미등록 개념 태그 혼입)",
"output": {
"learner_id": "l_002",
"period": "2026-06-11~2026-06-11",
"stuck_concepts": [
{
"concept_id": "c_func",
"name": "함수",
"wrong_rate": 1,
"wrong_item_ids": [
"q_120",
"q_121"
],
"prerequisite_path": [
"c_var",
"c_func"
]
}
],
"recommendations": [
{
"content_id": "ct_120",
"target_concept_id": "c_func",
"reason": "함수 문항 2건을 모두 틀리셨고 선수 개념인 변수는 잡혀 있어, 함수 강의를 바로 재학습하도록 추천합니다.",
"difficulty": "basic",
"evidence_item_ids": [
"q_120",
"q_121"
],
"confidence": "high"
}
],
"unmapped_tags": [
"q_401(정규표현식)",
"q_402(머신러닝 입문)"
],
"explanation": "함수 개념에서 막히신 것으로 보여 함수 강의를 추천합니다. 정규표현식·머신러닝 입문 문항은 아직 개념 지도에 등록되지 않아 진단에서 제외했고, 추측으로 끼워 맞추지 않았습니다. 이 두 태그는 개념 지도에 편입되면 다음 진단부터 반영됩니다."
}
},
{
"input_ref": "l_003 · 2026-06-11~06-12 (전부 정답, 다음 심화 추천)",
"output": {
"learner_id": "l_003",
"period": "2026-06-11~2026-06-12",
"stuck_concepts": [],
"recommendations": [
{
"content_id": "ct_180",
"target_concept_id": "c_recur",
"reason": "최근 문항을 모두 맞히셨고 객체지향까지 이수하셨습니다. 함수·조건문을 선수로 하는 재귀가 아직 미이수라 다음 심화 단계로 추천합니다.",
"difficulty": "advanced",
"evidence_item_ids": [],
"confidence": "mid"
}
],
"unmapped_tags": [],
"explanation": "최근 푸신 문제는 모두 맞히셨습니다. 막힌 개념은 보이지 않아, 이수하신 함수·객체지향 위에서 다음 단계인 재귀 강의로 넘어가시길 추천합니다. 근거가 되는 오답이 없어 확신도는 보통으로 표시했습니다."
}
}
][자동 검증] 1. JSON Schema parse 통과(형식·필수 필드·enum 위반 걸러냄). 2. 막힌 뿌리 개념 재현성: 같은 입력에 항상 같은 stuck_concepts·recommendations 가 나오는지 그래프 탐색 재현 테스트(판정이 LLM 이 아니라 위상 정렬 규칙이라 결정론). 3. evidence 정합성: 각 recommendation 의 evidence_item_ids 가 그 학습자의 실제 오답 문항 id 인지, target_concept_id 가 stuck_concepts 의 선수 경로 위에 있는지 확인. 4. 추천 유효성: 추천 content_id 가 concept_catalog 의 미이수 강의이고 난이도 순서가 선수관계와 어긋나지 않는지 확인. 5. 미매핑 처리: concept_id=null 인 문항이 진단에서 빠지고 unmapped_tags 로만 노출되는지(추측 삽입 금지), prerequisite_ids 에 orphan 이 섞인 개념은 경로 조회에서 격리되는지. [수동 검증] 1. 강사 검수자 2명이 개념 매핑·추천 타당성을 비동기 라벨링, 검수자 일치도 Cohen's κ(두 사람 답이 우연 이상으로 일치하는 정도, 0~1) 하한이 기준 이상인지. 2. 설명 문구가 오답을 곧 실력 부족으로 단정하지 않는지 사람 표본 점검.
포트폴리오가 “무엇을 만들지”라면, 시나리오 테스트는 “현장에서 어떻게 판단·소통할지” 입니다. 실제 이 채용공고 맥락의 대본을 읽고 무엇을 물어야 할지 직접 써봅니다.