尚未检测到更新。GitHub 发布版本后会显示在这里。
Ncs 능력 테스트
翻译准备中
正在加载完整 README。 以下为已保存的摘录。
Vite + TypeScript 정적 사이트와 Vercel 함수(api/)로 이루어져 있습니다. DB는 없습니다. 문제 생성·채점·리포트 계산은 서버 함수에서 하고, 브라우저에는 문제 문구·보기·도표만 내려갑니다(정답·해설은 채점 후 응답으로만). 4개 영역(기초연산·기초통계·도표분석·도표작성)에서 3문항씩 12문항을 풀고, 영역별 리포트를 받습니다. 모든 문제는 직접 만든 템플릿으로 매번 새로 생성하며, 외부 기출·문제집 문장을 쓰지 않습니다. validate 옵션: PER_TEMPLATE(기본 3000), SETS(기본 5000), SEED_OFFSET(다른 시드 범위 탐색), VERBOSE. Production 빌드(VERCEL_ENV=production)는 npm run check:production이 결과물에 example.com, [입력, [운영자 이름], %VITE_가 남아 있으면 실패합니다. 로컬·Preview 빌드는 건너뜁니다. 서버 전용 비밀 값은 .env에 넣지 않습니다(.env는 커밋됨). 목록은 .env.example에 있습니다.查看完整 README ↗
Vite + TypeScript 정적 사이트와 Vercel 함수(api/)로 이루어져 있습니다. DB는 없습니다.
문제 생성·채점·리포트 계산은 서버 함수에서 하고, 브라우저에는 문제 문구·보기·도표만 내려갑니다(정답·해설은 채점 후 응답으로만).
4개 영역(기초연산·기초통계·도표분석·도표작성)에서 3문항씩 12문항을 풀고, 영역별 리포트를 받습니다.
모든 문제는 직접 만든 템플릿으로 매번 새로 생성하며, 외부 기출·문제집 문장을 쓰지 않습니다.
npm install
npm run dev # 개발 서버 (/api/*도 같은 핸들러로 함께 동작, .env.local의 REPORT_TOKEN_SECRET 필요)
npm run validate # 문제 생성기 검증 (실패 시 exit 1)
npm run build # validate → 타입 검사 → vite build (검증 실패 시 빌드 중단)
npm run preview # 빌드 결과 미리보기 (/api/*도 동작)
npm run test:golden # 서버 출력 = 골든 스냅샷(서버 이전 전 출력)인지 비교
npm run test:api # 서버 입력 검증·토큰·응답 내용 테스트
npm run test:gating # 기능 스위치·로그인·이용권에 따른 응답 나누기 테스트 (모의 Supabase)
npm run check:bundle # dist에 서버 전용 문구·서비스 키·소스맵이 없는지 검사 (build 뒤에 실행)
npx tsx scripts/sample.ts chartRead 2 # 템플릿 id 접두어별 예시 문제 출력
validate 옵션: PER_TEMPLATE(기본 3000), SETS(기본 5000), SEED_OFFSET(다른 시드 범위 탐색), VERBOSE.
.env)| 변수 | 용도 |
|---|---|
VITE_SITE_URL | canonical·OG·sitemap 절대 주소 (현재 https://example.com 자리표시자) |
VITE_CONTACT_EMAIL | 꼬리말·안내 페이지 문의 이메일 (현재 [email protected] 자리표시자) |
VITE_LAST_UPDATED | 꼬리말·안내 페이지의 최종 업데이트 날짜, sitemap lastmod |
VITE_ADSENSE_ACCOUNT | 애드센스 사이트 소유권 확인 <meta name="google-adsense-account"> 값. 형식 ca-pub-숫자(틀리면 빌드 실패). Production에만 설정, 비워 두면 태그 없음. 광고 스크립트는 넣지 않음 |
VITE_OPERATOR_NAME | 개인정보처리방침·소개 페이지의 운영자 이름 (저장소에는 [운영자 이름] 자리표시자만, 실제 값은 Vercel 환경 변수) |
Production 빌드(VERCEL_ENV=production)는 npm run check:production이 결과물에 example.com, [입력, [운영자 이름], %VITE_가 남아 있으면 실패합니다. 로컬·Preview 빌드는 건너뜁니다.
서버 전용 비밀 값은 .env에 넣지 않습니다(.env는 커밋됨). 목록은 .env.example에 있습니다.
| 변수 | 어디에 | 용도 |
|---|---|---|
REPORT_TOKEN_SECRET | 로컬: .env.local(커밋 제외) / 배포: Vercel 프로젝트 환경 변수 | 세션 토큰 HMAC 서명 키, 32자 이상. 없으면 /api/*가 500 server_misconfigured로 실패 |
MONETIZATION_ENABLED(서버)와 VITE_MONETIZATION_ENABLED(빌드)가 모두 "true"가 아니면 이전과 똑같이 동작합니다.
꺼진 빌드에는 로그인 코드와 @supabase/supabase-js가 들어가지 않고 HTML도 같습니다.
| 구분 | 무료(게스트·이용권 없음) | 이용권 |
|---|---|---|
| 진단 | 1회 (브라우저 저장소 표시로 제한) | 반복 |
| 결과 | 영역별 요약 + 1·2번 해설 | 전체(영역별 상세, 12문항 해설) |
VITE_KAKAO_LOGIN_ENABLED=true로 빌드할 때만 버튼과 안내 문구가 들어가고 로그인 시작이 허용됩니다(기본 꺼짐). 이미 로그인한 계정의 방식 표시("카카오")는 그대로 둡니다. 결제는 아직 없고 로그인 뒤 "결제 준비 중"을 보여 줍니다.Authorization: Bearer <Supabase 액세스 토큰>을 Supabase 인증 서버로 검증하고 entitlements를 서비스 키로 조회합니다. 토큰 문제 401, 일시 장애 503. 무료 응답은 server/diagnosis.ts의 composeReportResponse에서 잘라 잠긴 내용이 응답 본문에 들어가지 않습니다. 응답의 gated는 스위치가 켜졌을 때만 들어갑니다.supabase/migrations/의 SQL을 Supabase SQL Editor에서 실행합니다. 개발용 이용권 수동 부여는 supabase/dev/grant-entitlement.example.sql(운영 금지).<!--#if monetization-->켜짐<!--#else-->꺼짐<!--#endif--> 블록을 빌드 때 고릅니다.npm run test:gating(모의 Supabase scripts/mock-supabase.ts로 실제 supabase-js 경로 검증)| 경로 | 요청 | 응답 |
|---|---|---|
POST /api/session | 본문 없음 | { token, expiresAt, questions[12] } — 문항은 area, areaName, text, figure?, choices[{label, chart?}]만 |
POST /api/report | { token, answers[12], secs[12] } (+ 선택: Authorization) | { meta, summary, areaDetails, explanations } (+ 스위치 켜짐: gated) |
POST /api/account-delete | Authorization: Bearer <토큰> | { deleted: true } — 이용권 행과 Supabase 계정 삭제 |
base64url({v, s, iat}).base64url(HMAC-SHA256), 유효 6시간. 실제 문제 생성 시드는 HMAC(키, s)로 만들어 키 없이는 재현할 수 없습니다.server/diagnosis.ts의 composeReportResponse 한 곳에서 만듭니다(이후 무료/유료 구분 시 여기서 자름).index.html 정적 랜딩 (meta/OG/JSON-LD)
diagnosis/index.html 진단 앱 셸
method/index.html 문제 생성·검증 방식, 판정 기준, 개인정보, 유의사항, 문의
api/ Vercel 함수 진입점 (session.ts, report.ts)
server/ 서버 전용 (브라우저 번들에 들어가지 않음)
handlers.ts HTTP 처리·입력 검증 (dev 서버와 Vercel 함수가 함께 씀)
token.ts 세션 토큰 서명·검증
diagnosis.ts 문항 생성, 화면용 문항 변환, 채점, 응답 구역 구성
engine/
rng.ts 시드 난수 (mulberry32)
choices.ts 보기 5개 구성: 흔한 실수 오답 우선, 부족할 때만 근접값("계산 실수")
set.ts 세트 구성: 영역별 서로 다른 유형 3개, 가능하면 난이도 1·2·3
mistakes.ts 실수 태그 → 리포트의 "틀린 패턴" 문장
frac.ts, format.ts 기약분수·조합, 숫자 표기
types.ts Template / Generated / Problem
templates/<영역>/<유형>.ts 24개 유형 (기초연산 8, 기초통계 5, 도표분석 6, 도표작성 5)
registry.ts 템플릿 목록
areas.ts 영역 설명, 학습 순서, 권장 시간
report/analyze.ts 영역별 정답률·시간·수준·틀린 패턴·학습 순서 (순수 함수)
shared/ 서버와 브라우저가 함께 씀
api.ts 요청·응답 타입
charts/render.ts 막대·꺾은선·원·점그래프·표를 SVG/HTML 문자열로 (DOM 없이 동작)
format.ts 숫자 표기
src/ 브라우저
ui/ 풀이 화면, 결과 리포트, API 호출
styles/ tokens / base / components / chart / report / landing / print
partials/ 공통 머리말·꼬리말 (빌드 시 삽입)
scripts/validate.ts 생성기 검증
scripts/golden.ts 골든 스냅샷 생성 (서버 이전 전 한 번 만든 기준값, tests/golden/)
scripts/golden-compare.ts, api-test.ts, check-bundle.ts 검증
Node ESM으로 함수가 파일 단위로 실행되므로 server/, shared/, api/의 상대 import에는 .js 확장자를 붙입니다.
export const myType: Template = {
id: 'arith.myType', area: 'arith', subtype: '유형 이름', difficulty: 2,
generate(rng) {
// 1) 정답을 먼저 정하고 숫자를 역산한다
// 2) 오답은 흔한 실수를 계산식으로 재현하고 mistakeTag를 붙인다 (mistakes.ts에 있는 태그만 가능)
// 3) 문장 틀은 3가지 이상
return { text, answer, wrongs: [{ value, mistakeTag: '기준량 혼동' }, ...], steps, format, near };
},
};
registry.ts에 추가하고 areas.ts의 studyOrder에 유형 이름을 넣은 뒤 npm run validate로 확인합니다.
결과 화면 하단의 .ad-slot[data-ad-slot="result-bottom"]만 광고 자리로 비워 두었습니다(풀이 중에는 없음).
광고를 넣을 때는 method/index.html의 개인정보 안내를 먼저 갱신하세요.