세타원코리아
경력근거: 딥테크 TIPS 기반의 업계 유일의 4축 쉐도잉 피드백(음소·강세·속도·리듬)을 제공하며
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고는 랭플릭스를 음소·강세·속도·리듬 네 축으로 쉐도잉을 되짚는 앱이라 밝히고, 영상 주소만 넣으면 관심사에 맞춘 커리큘럼이 생긴다고 적습니다. 네 축의 값과 표현별 복습 목표가 한 계정 안에 함께 쌓이는 곳이라 어느 단어를 왜 다시 읽어야 하는지가 이어집니다. 점수만 내는 발음 앱은 오늘 무엇을 다시 읽으라는 말을 못 합니다. 다만 이 포트폴리오는 네 축의 값을 규칙으로 견주고 앱 화면으로 세우는 설계를 보여줍니다. 음성 인식 모델을 직접 만들거나 음운론을 다루는 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
공고는 랭플릭스를 음소·강세·속도·리듬 네 축으로 쉐도잉을 되짚어 주는 앱이라 소개하고, 유튜브 영상 주소만 넣으면 관심사에 맞춘 커리큘럼이 만들어진다고 적습니다. 이 네 축이 제품의 값이 나오는 자리입니다.
자격 요건에 React 기반 SPA·SSR 설계 경험과 React Native(Expo) 개발 경험이 나란히 있고, 기능 하나를 기획부터 배포까지 책임지고 출시해 본 사람을 찾습니다. 앱 화면 하나를 끝까지 내는 자리입니다.
자막 번역 테이블이 이미 3,250만 행이고 매달 두 자릿수로 커진다고 밝히면서, 인덱스·파티셔닝·복제본 분리로 데이터가 세 배가 되어도 응답 시간을 지킨다고 적습니다. 화면을 그리는 사람도 조회량을 함께 보는 자리입니다.
학습자가 오늘 쉐도잉한 녹음을 열면 단어마다 무엇이 어긋났는지가 붙습니다. 판정은 음소 기호를 짝지어 대조하고 강세 시각과 말 빠르기, 쉼 길이를 원본과 견주는 계산 규칙이 정하며, 모델은 그 단어를 내일 어떻게 다시 소리 낼지 학습자가 따라 할 연습 지시를 씁니다.
랭플릭스로 매일 유튜브 영상 한 편씩 쉐도잉하는 영어 학습자 — 점수는 나오는데 그 점수가 어느 단어의 무엇 때문인지가 안 보여서, 내일 무엇을 다시 연습할지를 감으로 고른다
쉐도잉이 늘지 않는 이유는 연습량이 모자라서가 아니라 어긋난 자리가 단어 단위로 남지 않기 때문입니다. 원본 대사의 음소와 시간 값을 단어마다 옆에 두면 학습자는 내일 다시 읽을 단어를 화면에서 그대로 고릅니다.
학습자가 쓰는 앱 화면 하나가 돕니다. 녹음 분석 결과와 원본 대사, 표현별 복습 목표를 넣으면 단어마다 판정이 붙고 기준별 묶음과 근거 값이 한 화면에 뜹니다. 1주차는 음성 인식을 직접 돌리지 않고 음소·시간 값이 이미 뽑혀 나온 분석 결과를 받는 것으로 시작합니다.
원어민 강사가 손으로 표시한 60개 단어로 판정 일치율을 자동으로 내고, 어긋난 단어가 음소 대조에서 갈라졌는지 시간 기준에서 갈라졌는지 나눠 리포트로 냅니다. 단어마다 연습 지시를 모델이 쓰고, 인용한 컬럼이 입력에 없으면 그 응답을 버립니다.
표현별 통과 횟수와 마지막 통과일을 보고 기한이 지난 표현을 골라, 관심사 태그가 같은 클립을 내일 순서로 붙이는 화면까지 돕니다. 학습자가 앱을 열면 어제 못 채운 표현이 첫 화면에 먼저 뜹니다.
학습자가 오늘 녹음을 열면 다시 읽을 단어가 기준별로 뜨고, 원본과 내 값이 얼마나 벌어졌는지 그 자리에서 펼쳐집니다.
말 빠르기가 안 잡힌 단어, 원본에 쉼 기준이 없는 자리, 아직 한 번도 통과하지 못한 표현을 섞은 입력 30종을 넣어도 화면이 죽지 않고 그 단어가 왜 판정 없이 남았는지 읽힙니다.
음소 기호가 빠지거나 시간 값이 비거나 인코딩이 깨진 분석 결과 12종을 넣어도 목록 대신 어느 단어가 왜 걸렸는지 이슈로 뜨고 나머지 단어는 그대로 판정됩니다.
원어민 강사가 표시한 60개 단어로 판정 일치율을 냅니다. 단, 녹음 값과 정답을 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 더미데이터 자가채점이며 실제 적중률은 랭플릭스에 쌓인 실 녹음과 강사 표시가 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
판정 일치율은 제가 만든 녹음 값에 제가 정답을 붙여 낸 숫자라 성능 증명이 아닙니다. 면접에서는 실제 학습자 녹음이 들어왔을 때 음소 대조부터 어떻게 다시 재야 하는지로 이야기하겠습니다.
채점표를 만든 사람이 녹음 값도 만들었으니 잘 맞는 것은 당연합니다. 말 빠르기가 안 잡힌 단어, 원본 쉼 기준이 비어 있는 자리, 한 번도 통과한 적 없는 표현, 원본 속도의 딱 0.80배로 읽은 단어를 더미에 일부러 섞어 쉬운 단어만 골라 푸는 함정을 피합니다.
음소 기호가 다르다는 사실이 곧 못 알아듣는 발음이라는 뜻은 아닙니다. 같은 단어도 지역과 말투에 따라 기호가 갈리므로, 판정을 결론으로 읽지 않게 원본 기호와 내 기호를 나란히 보여줍니다.
강세와 쉼의 밀리초 기준은 이 포트폴리오가 정한 값입니다. 실제 학습 효과가 그 경계에서 갈리는지는 학습자 기록이 있어야 알 수 있어, 기준을 화면에 적어 두고 바꿀 수 있게 두었습니다.
실제 도입에서는 녹음 품질이 기기와 주변 소리에 따라 크게 흔들리고, 영상마다 원본 대사의 시간 값을 뽑는 정확도가 다릅니다. 소리를 다루는 앞단은 1주차 범위 밖이라 이미 뽑힌 값을 받는 것으로 두고, 그 앞단은 도입 단계 과제로 분리합니다.
발음 교정 자체를 대신해 주지는 않습니다. 여기서는 어느 단어를 어떤 기준에서 다시 읽을지 정한 목록을 내는 데까지만 다룹니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
학습자가 오늘 쉐도잉한 녹음을 단어 단위로 쪼갠 분석 결과입니다. 판정 대상이며, 발화가 짧아 말 빠르기가 안 잡힌 단어와 쉼이 0밀리초로 붙어 버린 단어를 일부러 섞었습니다.
| take_id | clip_id | user_id | word_index | word_text | spoken_phoneme | syllable_per_sec | stress_peak_ms | pause_ms | recorded_at |
|---|---|---|---|---|---|---|---|---|---|
| TK-20260908-011 | CLP-7781 | U-3092 | 3 | actually | ˈʌk.tʃu.ə.li | 3.5 | 290 | 0 | 2026-09-08T21:04:00 |
| TK-20260908-012 | CLP-7781 | U-3092 | 5 | rather | ˈræð.ɚ | 3.3 | 620 | 0 | 2026-09-08T21:04:00 |
| TK-20260908-013 | CLP-7781 | U-3092 | 8 | go | ɡoʊ | 2.9 | 220 | 250 | 2026-09-08T21:04:00 |
| TK-20260908-014 | CLP-7781 | U-3092 | 11 | you | juː | 4.2 | 185 | 0 | 2026-09-08T21:05:00 |
| TK-20260908-015 | CLP-7781 | U-3092 | 12 | know | noʊ | 3.8 | 210 | 0 | 2026-09-08T21:05:00 |
아래 JSON Schema 에 맞는 녹음 분석 행을 더 만들어 주세요. {"type":"object","properties":{"take_id":{"type":"string"},"clip_id":{"type":"string"},"user_id":{"type":"string"},"word_index":{"type":"integer","minimum":1},"word_text":{"type":"string"},"spoken_phoneme":{"type":["string","null"]},"syllable_per_sec":{"type":["number","null"],"minimum":0},"stress_peak_ms":{"type":["integer","null"],"minimum":0},"pause_ms":{"type":["integer","null"],"minimum":0},"recorded_at":{"type":"string"}},"required":["take_id","clip_id","user_id","word_index","word_text","spoken_phoneme","syllable_per_sec","stress_peak_ms","pause_ms","recorded_at"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) syllable_per_sec 가 null 인 단어(발화가 짧아 못 잼), (2) pause_ms 가 0 인 단어와 500밀리초를 넘는 단어, (3) 원본 속도의 0.80배·1.20배에 정확히 닿은 단어, (4) 같은 단어를 하루에 두 번 다시 읽어 take_id 만 다른 행. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"take_id":{"type":"string"},"clip_id":{"type":"string"},"user_id":{"type":"string"},"word_index":{"type":"integer","minimum":1},"word_text":{"type":"string"},"spoken_phoneme":{"type":["string","null"]},"syllable_per_sec":{"type":["number","null"],"minimum":0},"stress_peak_ms":{"type":["integer","null"],"minimum":0},"pause_ms":{"type":["integer","null"],"minimum":0},"recorded_at":{"type":"string"}},"required":["take_id","clip_id","user_id","word_index","word_text","spoken_phoneme","syllable_per_sec","stress_peak_ms","pause_ms","recorded_at"]}영상 클립의 원본 대사를 단어 단위로 뽑아 둔 기준값입니다. 대조의 다른 쪽 축이며, 원본이 쉬지 않아 쉼 기준이 비어 있는 자리를 담았습니다.
| clip_id | word_index | word_text | target_phoneme | target_syllable_per_sec | stress_peak_ms | pause_ms | expression_id |
|---|---|---|---|---|---|---|---|
| CLP-7781 | 3 | actually | ˈæk.tʃu.ə.li | 3.6 | 280 | 0 | EXP-2041 |
| CLP-7781 | 5 | rather | ˈræð.ɚ | 3.4 | 320 | 0 | EXP-2044 |
| CLP-7781 | 8 | go | ɡoʊ | 2.8 | 210 | 240 | EXP-2044 |
| CLP-7781 | 11 | you | juː | 4.1 | 180 | 0 | EXP-2057 |
| CLP-7781 | 12 | know | noʊ | 3.9 | 200 | 0 | EXP-2057 |
아래 JSON Schema 에 맞는 원본 대사 행을 더 만들어 주세요. {"type":"object","properties":{"clip_id":{"type":"string"},"word_index":{"type":"integer","minimum":1},"word_text":{"type":"string"},"target_phoneme":{"type":"string"},"target_syllable_per_sec":{"type":"number","minimum":0},"stress_peak_ms":{"type":"integer","minimum":0},"pause_ms":{"type":["integer","null"],"minimum":0},"expression_id":{"type":"string"}},"required":["clip_id","word_index","word_text","target_phoneme","target_syllable_per_sec","stress_peak_ms","pause_ms","expression_id"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) pause_ms 가 null 인 단어(원본에 쉼 기준이 없음), (2) stress_peak_ms 가 0 에 닿은 단어, (3) target_syllable_per_sec 가 5를 넘는 빠른 구간, (4) 한 표현(expression_id)에 단어가 넷 이상 묶인 클립. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"clip_id":{"type":"string"},"word_index":{"type":"integer","minimum":1},"word_text":{"type":"string"},"target_phoneme":{"type":"string"},"target_syllable_per_sec":{"type":"number","minimum":0},"stress_peak_ms":{"type":"integer","minimum":0},"pause_ms":{"type":["integer","null"],"minimum":0},"expression_id":{"type":"string"}},"required":["clip_id","word_index","word_text","target_phoneme","target_syllable_per_sec","stress_peak_ms","pause_ms","expression_id"]}학습자가 표현마다 몇 번 통과하기로 했는지와 마지막으로 통과한 날입니다. 소리 기준을 다 넘긴 단어에서 복습 차례를 정하는 자리이며, 아직 한 번도 통과하지 못해 통과일이 비어 있는 표현을 담았습니다.
| user_id | expression_id | expression_text | target_pass_count | passed_count | last_passed_at | interest_tag |
|---|---|---|---|---|---|---|
| U-3092 | EXP-2041 | actually | 3 | 2 | 2026-09-07 | 축구 |
| U-3092 | EXP-2044 | I'd rather | 3 | 2 | 2026-09-07 | 축구 |
| U-3092 | EXP-2057 | you know what | 3 | 3 | 2026-09-06 | 여행 |
| U-3092 | EXP-2063 | take it easy | 3 | 1 | 2026-09-02 | 축구 |
| U-3092 | EXP-2071 | probably | 3 | 3 | 2026-09-08 | 여행 |
아래 JSON Schema 에 맞는 복습 목표 행을 더 만들어 주세요. {"type":"object","properties":{"user_id":{"type":"string"},"expression_id":{"type":"string"},"expression_text":{"type":"string"},"target_pass_count":{"type":"integer","minimum":1},"passed_count":{"type":"integer","minimum":0},"last_passed_at":{"type":["string","null"]},"interest_tag":{"type":"string","enum":["축구","여행","요리","영화","비즈니스"]}},"required":["user_id","expression_id","expression_text","target_pass_count","passed_count","last_passed_at","interest_tag"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) last_passed_at 이 null 이고 passed_count 가 0 인 표현, (2) 마지막 통과가 정확히 사흘 전인 표현, (3) 날수는 지났지만 passed_count 가 target_pass_count 를 채운 표현, (4) 관심사 태그가 오늘 클립과 다른 표현. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"user_id":{"type":"string"},"expression_id":{"type":"string"},"expression_text":{"type":"string"},"target_pass_count":{"type":"integer","minimum":1},"passed_count":{"type":"integer","minimum":0},"last_passed_at":{"type":["string","null"]},"interest_tag":{"type":"string","enum":["축구","여행","요리","영화","비즈니스"]}},"required":["user_id","expression_id","expression_text","target_pass_count","passed_count","last_passed_at","interest_tag"]}| TypeScript | 판정 규칙부터 화면까지 한 언어로 씁니다. 밀리초와 음절 수, 배수가 섞이지 않도록 비교 함수마다 단위를 타입으로 박습니다. |
| React Native | 학습자가 보는 앱 화면입니다. 단어 목록과 근거 값을 하단 탭 세 개로 나눠 둡니다. |
| Expo | 앱을 실제 기기에 올려 녹음 재생과 목록 스크롤을 확인하는 데 씁니다. |
| React | 웹에서 같은 판정 화면을 열어 보는 관리용 페이지에 씁니다. |
| Next.js | 판정 결과를 서버에서 미리 그려 첫 화면이 빨리 뜨게 합니다. |
| Drizzle ORM | 녹음·원본 대사·복습 목표 세 표의 스키마와 조회를 다룹니다. 단어 단위 조회라 clip_id 와 word_index 에 복합 인덱스를 겁니다. |
| PostgreSQL | 단어 단위 행이 빠르게 늘어나는 저장소입니다. 사용자별 최근 녹음만 읽도록 조회 범위를 날짜로 자릅니다. |
| i18n | 화면 문구를 한국어와 영어로 나눠 두고, 학습자가 쓰는 말과 배우는 말을 갈라 표시합니다. |
| LLM | 판정이 정해진 뒤 그 단어를 내일 어떻게 소리 낼지 적은 연습 지시를 씁니다. 여섯 판정 값에는 손대지 못합니다. |
| Claude | 연습 지시를 쓰는 기본 모델입니다. 응답을 흘려보내며 화면에 한 글자씩 채웁니다. |
| Codex | 판정 규칙 시험과 더미데이터 증강 스크립트를 쓰는 데 씁니다. 생성한 코드는 시험을 돌려 확인한 뒤 넣습니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 강사가 표시한 60개 단어와 판정을 맞춰 보는 단계로 넘어갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 복습 기한이 지난 표현으로 내일 클립을 이어 붙이는 단계로 확장합니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · take_id + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
통과통과음소가 다름소리 다름강세 자리 어긋남강세 어긋남속도 기준 초과너무 빠름쉼 자리 어긋남쉼 어긋남복습 기한 지남복습 차례단어 하나를 여섯 갈래 중 하나로 판정하고 그 판정을 만든 값을 남기는 형식입니다. 판정은 수치 비교 규칙이 정하고 연습 지시만 모델이 쓰며, 표현 단위 갈래는 expression_id 로 한 줄에 접습니다.
{"type":"object","properties":{"take_id":{"type":"string"},"clip_id":{"type":"string"},"word_index":{"type":"integer","minimum":1},"word_text":{"type":"string"},"expression_id":{"type":"string","description":"그 단어가 속한 clip_reference.expression_id 입니다"},"expression_text":{"type":["string","null"],"description":"curriculum_goal.expression_text 를 그대로 옮긴 표현 전체입니다. 화면이 복습 갈래를 표현 한 줄로 접을 때 쓰는 값입니다"},"verdict":{"type":"string","enum":["통과","음소가 다름","강세 자리 어긋남","속도 기준 초과","쉼 자리 어긋남","복습 기한 지남"]},"stress_gap_ms":{"type":["integer","null"],"minimum":0,"description":"shadow_take.stress_peak_ms 와 clip_reference.stress_peak_ms 의 차를 절댓값으로 적습니다. 어느 한쪽이 비어 있으면 비웁니다"},"speed_ratio":{"type":["number","null"],"minimum":0,"description":"shadow_take.syllable_per_sec ÷ clip_reference.target_syllable_per_sec 를 소수 둘째 자리로 반올림한 값입니다"},"days_since_pass":{"type":["integer","null"],"minimum":0,"description":"curriculum_goal.last_passed_at 부터 recorded_at 까지의 날수입니다. last_passed_at 이 비어 있으면 비웁니다"},"coach_line":{"type":["string","null"],"description":"내일 그 단어를 어떻게 다시 소리 낼지 적은 연습 지시입니다"},"evidence":{"type":"array","items":{"type":"object","properties":{"dataset":{"type":"string"},"field":{"type":"string"},"agg":{"type":["string","null"]},"value":{"type":["string","number","null"]}},"required":["dataset","field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["take_id","clip_id","word_index","word_text","verdict","evidence","summary"]}[
{
"take_id": "TK-20260908-013",
"clip_id": "CLP-7781",
"word_index": 8,
"word_text": "go",
"expression_id": "EXP-2044",
"expression_text": "I'd rather",
"verdict": "통과",
"stress_gap_ms": 10,
"speed_ratio": 1.04,
"days_since_pass": 1,
"coach_line": null,
"evidence": [
{
"dataset": "shadow_take",
"field": "spoken_phoneme",
"value": "ɡoʊ"
},
{
"dataset": "clip_reference",
"field": "target_phoneme",
"value": "ɡoʊ"
},
{
"dataset": "shadow_take",
"field": "stress_peak_ms",
"value": 220
},
{
"dataset": "clip_reference",
"field": "stress_peak_ms",
"value": 210
},
{
"dataset": "shadow_take",
"field": "syllable_per_sec",
"value": 2.9
},
{
"dataset": "clip_reference",
"field": "target_syllable_per_sec",
"value": 2.8
},
{
"dataset": "shadow_take",
"field": "pause_ms",
"value": 250
},
{
"dataset": "clip_reference",
"field": "pause_ms",
"value": 240
},
{
"dataset": "curriculum_goal",
"field": "last_passed_at",
"value": "2026-09-07"
}
],
"summary": "음소가 같고 강세는 10밀리초, 쉼은 10밀리초 안에서 붙었습니다. 어제 통과한 표현이라 복습 기한도 남아 있어 이 단어는 넘어갑니다.",
"hedge": null
},
{
"take_id": "TK-20260908-021",
"clip_id": "CLP-7815",
"word_index": 6,
"word_text": "little",
"expression_id": "EXP-2088",
"expression_text": "a little bit",
"verdict": "통과",
"stress_gap_ms": 10,
"speed_ratio": 0.97,
"days_since_pass": 3,
"coach_line": null,
"evidence": [
{
"dataset": "shadow_take",
"field": "spoken_phoneme",
"value": "ˈlɪt̬.əl"
},
{
"dataset": "clip_reference",
"field": "target_phoneme",
"value": "ˈlɪt̬.əl"
},
{
"dataset": "shadow_take",
"field": "stress_peak_ms",
"value": 280
},
{
"dataset": "clip_reference",
"field": "stress_peak_ms",
"value": 270
},
{
"dataset": "clip_reference",
"field": "pause_ms",
"value": null
},
{
"dataset": "curriculum_goal",
"field": "last_passed_at",
"value": "2026-09-05"
}
],
"summary": "원본에 쉼 기준이 없는 자리라 쉼은 재지 않고 넘어갑니다. 사흘 전에 통과한 표현이라 기한도 아직 지나지 않았습니다.",
"hedge": null
},
{
"take_id": "TK-20260908-011",
"clip_id": "CLP-7781",
"word_index": 3,
"word_text": "actually",
"expression_id": "EXP-2041",
"expression_text": "actually",
"verdict": "음소가 다름",
"stress_gap_ms": 10,
"speed_ratio": 0.97,
"days_since_pass": 1,
"coach_line": "첫 음절을 '어'가 아니라 입을 옆으로 벌린 '애'로 시작해 다시 읽습니다.",
"evidence": [
{
"dataset": "shadow_take",
"field": "spoken_phoneme",
"value": "ˈʌk.tʃu.ə.li"
},
{
"dataset": "clip_reference",
"field": "target_phoneme",
"value": "ˈæk.tʃu.ə.li"
},
{
"dataset": "shadow_take",
"field": "stress_peak_ms",
"value": 290
},
{
"dataset": "clip_reference",
"field": "stress_peak_ms",
"value": 280
}
],
"summary": "첫 음절이 원본의 /æ/ 대신 /ʌ/로 나왔습니다. 강세와 속도는 기준 안이라 소리 하나만 다시 잡으면 됩니다.",
"hedge": null
},
{
"take_id": "TK-20260908-012",
"clip_id": "CLP-7781",
"word_index": 5,
"word_text": "rather",
"expression_id": "EXP-2044",
"expression_text": "I'd rather",
"verdict": "강세 자리 어긋남",
"stress_gap_ms": 300,
"speed_ratio": 0.97,
"days_since_pass": 1,
"coach_line": "'RA-ther' 로 앞 음절을 먼저 세게 치고 뒤를 흘립니다.",
"evidence": [
{
"dataset": "shadow_take",
"field": "stress_peak_ms",
"value": 620
},
{
"dataset": "clip_reference",
"field": "stress_peak_ms",
"value": 320
},
{
"dataset": "shadow_take",
"field": "spoken_phoneme",
"value": "ˈræð.ɚ"
},
{
"dataset": "clip_reference",
"field": "target_phoneme",
"value": "ˈræð.ɚ"
}
],
"summary": "소리는 맞는데 세게 누른 자리가 원본보다 300밀리초 뒤입니다. 뒤 음절에 힘이 실려 다른 단어처럼 들립니다.",
"hedge": null
},
{
"take_id": "TK-20260908-016",
"clip_id": "CLP-7781",
"word_index": 13,
"word_text": "what",
"expression_id": "EXP-2057",
"expression_text": "you know what",
"verdict": "속도 기준 초과",
"stress_gap_ms": 10,
"speed_ratio": 1.44,
"days_since_pass": 2,
"coach_line": "'you know what' 세 단어를 한 호흡에 몰지 말고 what 앞에서 한 박 쉽니다.",
"evidence": [
{
"dataset": "shadow_take",
"field": "syllable_per_sec",
"value": 4.9
},
{
"dataset": "clip_reference",
"field": "target_syllable_per_sec",
"value": 3.4
},
{
"dataset": "shadow_take",
"field": "stress_peak_ms",
"value": 240
},
{
"dataset": "clip_reference",
"field": "stress_peak_ms",
"value": 230
}
],
"summary": "원본보다 1.44배 빠르게 지나갔습니다. 음소와 강세는 맞아서 속도만 늦추면 붙는 단어입니다.",
"hedge": "말 빠르기는 녹음 구간을 어디서 끊었는지에 따라 달라져서, 같은 문장을 다시 녹음하면 비율이 조금 움직입니다."
},
{
"take_id": "TK-20260908-019",
"clip_id": "CLP-7802",
"word_index": 4,
"word_text": "easy",
"expression_id": "EXP-2063",
"expression_text": "take it easy",
"verdict": "쉼 자리 어긋남",
"stress_gap_ms": 10,
"speed_ratio": 1.03,
"days_since_pass": 6,
"coach_line": "easy 를 끝내고 260밀리초쯤 멈춘 뒤 다음 문장으로 넘어갑니다.",
"evidence": [
{
"dataset": "shadow_take",
"field": "pause_ms",
"value": 0
},
{
"dataset": "clip_reference",
"field": "pause_ms",
"value": 260
},
{
"dataset": "shadow_take",
"field": "stress_peak_ms",
"value": 250
},
{
"dataset": "clip_reference",
"field": "stress_peak_ms",
"value": 240
},
{
"dataset": "shadow_take",
"field": "spoken_phoneme",
"value": "ˈiː.zi"
},
{
"dataset": "clip_reference",
"field": "target_phoneme",
"value": "ˈiː.zi"
}
],
"summary": "원본이 260밀리초 쉬는 자리에서 쉬지 않고 이어 갔습니다. 문장이 끊기지 않아 뒤 단어까지 뭉쳐 들립니다.",
"hedge": null
},
{
"take_id": "TK-20260908-017",
"clip_id": "CLP-7802",
"word_index": 2,
"word_text": "take",
"expression_id": "EXP-2063",
"expression_text": "take it easy",
"verdict": "복습 기한 지남",
"stress_gap_ms": 10,
"speed_ratio": 0.97,
"days_since_pass": 6,
"coach_line": "'take it easy' 를 오늘 두 번 더 읽어 세 번을 채웁니다.",
"evidence": [
{
"dataset": "curriculum_goal",
"field": "last_passed_at",
"value": "2026-09-02"
},
{
"dataset": "curriculum_goal",
"field": "passed_count",
"value": 1
},
{
"dataset": "curriculum_goal",
"field": "target_pass_count",
"value": 3
},
{
"dataset": "curriculum_goal",
"field": "interest_tag",
"value": "축구"
},
{
"dataset": "shadow_take",
"field": "stress_peak_ms",
"value": 250
},
{
"dataset": "clip_reference",
"field": "stress_peak_ms",
"value": 260
},
{
"dataset": "shadow_take",
"field": "recorded_at",
"value": "2026-09-08T21:12:00"
}
],
"summary": "네 가지 소리 기준은 다 넘겼지만 'take it easy' 는 엿새 전에 통과한 뒤 세 번 중 한 번밖에 못 채웠습니다. 관심사인 축구 영상에서 같은 표현이 나오는 클립을 다음 순서로 붙입니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. verdict 가 enum 여섯 값(통과·음소가 다름·강세 자리 어긋남·속도 기준 초과·쉼 자리 어긋남·복습 기한 지남) 중 하나인지 확인합니다. 판정은 수치 비교 규칙이 정하므로 모델이 다른 값을 쓰면 그 응답을 버립니다. 2. take_id·clip_id·word_index·word_text·verdict·evidence·summary 일곱 필드가 모두 있는지 확인합니다. 3. stress_gap_ms 와 speed_ratio, days_since_pass 가 0 이상인지 범위를 확인합니다. 4. stress_gap_ms 를 shadow_take.stress_peak_ms 와 clip_reference.stress_peak_ms 의 차로 다시 계산해 값이 같은지 확인합니다. 5. speed_ratio 를 shadow_take.syllable_per_sec ÷ clip_reference.target_syllable_per_sec 로, days_since_pass 를 curriculum_goal.last_passed_at 부터 recorded_at 까지의 날수로 다시 계산해 값이 같은지 확인합니다. 6. evidence 의 field 가 shadow_take·clip_reference·curriculum_goal 에 실재하는 컬럼 이름인지 대조합니다. 없는 이름을 인용하면 연습 지시가 지어낸 문장이라는 뜻이라 규칙이 만든 기본 문장으로 되돌립니다. 7. agg 가 없는 evidence 값이 그 컬럼에 실제로 수록된 값과 같은지 대조합니다. 여러 단어에서 센 수치에는 agg 를 함께 적습니다. 8. summary 와 coach_line 이 존댓말로 끝나는 한국어 문장인지 확인합니다. 9. clip_reference.pause_ms 가 비어 있으면 그 단어의 쉼 판정을 건너뜁니다. CLP-7815 의 여섯째 단어가 조건이 성립한 경우이고, 같은 클립의 일곱째 단어는 원본 쉼이 180밀리초로 적혀 있어 성립하지 않은 경우입니다. 10. shadow_take.syllable_per_sec 가 비어 있으면 speed_ratio 를 비우고 속도 판정을 건너뜁니다. TK-20260908-022 가 조건이 성립한 경우이고, TK-20260908-016 은 4.9음절이 잡혀 성립하지 않은 경우입니다. 11. curriculum_goal.last_passed_at 이 비어 있으면 days_since_pass 를 비우고 복습 기한 판정을 건너뜁니다. 아직 한 번도 통과하지 못한 EXP-2094 가 조건이 성립한 경우이고, EXP-2063 은 9월 2일 통과 기록이 있어 성립하지 않은 경우입니다. 12. 판정 순서는 음소가 다름, 강세 자리 어긋남, 속도 기준 초과, 쉼 자리 어긋남, 복습 기한 지남, 통과 순으로 앞에서 걸리면 뒤를 보지 않습니다. 소리 자체가 다른 쪽을 먼저 고치는 순서입니다. 13. 기준값은 강세 40밀리초, 속도 0.8배에서 1.2배 사이, 쉼 200밀리초, 복습 사흘입니다. 경계에 정확히 닿은 값은 걸리지 않습니다. 원본 속도의 0.80배로 읽은 TK-20260908-023 이 그 경계 사례이고, 마지막 통과가 사흘 전인 EXP-2088 도 기한 안입니다. 14. 복습 기한 지남은 앞의 네 기준을 모두 넘긴 단어에서만 나옵니다. passed_count 가 target_pass_count 이상이면 날수가 지났어도 기한 판정을 하지 않습니다. 나흘이 지났지만 세 번을 다 채운 EXP-2102 가 그 경우입니다. 15. 복습 기한 지남은 단어가 아니라 표현에 붙는 조건입니다. 같은 expression_id 에서 이 갈래로 떨어진 단어가 여럿이면 word_index 가 가장 작은 단어 한 줄만 목록에 남기고 나머지는 그 줄 안으로 접습니다. 'take it easy' 의 take 와 it 이 그 경우로, take 한 줄만 뜨고 it 은 줄을 펼쳐야 보입니다. easy 는 쉼 자리 어긋남이 먼저 걸린 단어라 소리 갈래에 따로 남습니다. 접는 것은 목록에 뜨는 줄뿐이고 판정 자체는 단어마다 그대로 남겨 2주차 일치율은 단어 단위로 셉니다. 16. expression_id 를 clip_reference 에서, expression_text 를 curriculum_goal 에서 그대로 옮겼는지 대조합니다. 두 표의 표현이 서로 다르면 화면이 엉뚱한 줄을 접게 되므로 그 단어는 접지 않고 그대로 둡니다. [평가 러너 · 2주차] 1. 원어민 강사가 손으로 표시한 60개 단어로 판정 일치율을 냅니다. 어긋난 단어는 음소 대조에서 갈라졌는지 시간 기준에서 갈라졌는지 나눠 리포트에 답니다. [수동 검증 · 사람이 봅니다] 1. 강세 자리 어긋남으로 뜬 단어를 학습자가 다시 읽었을 때 실제로 붙는지 표본 20건을 들어 봅니다. 2. 관심사 태그로 붙인 다음 클립을 학습자가 실제로 이어서 하는지 확인하고 붙이는 기준을 조정할지 정합니다.
근거: 딥테크 TIPS 기반의 업계 유일의 4축 쉐도잉 피드백(음소·강세·속도·리듬)을 제공하며