카테고리 없음

토익 파싱 whisper , json

자무카 2025. 7. 8.

아후 개 생노가다.

근데, 단어 단위로 인식하기 때문에, 재처리를 위해서는 캐쉬 시스템을 구현해서 처리하는게 좋겠다.

#!/usr/bin/env python3
"""
Faster-Whisper를 사용한 TOEIC 오디오 전사 스크립트
키워드 기반 세그먼테이션을 위한 단어별 타임스탬프 포함
"""

import argparse
import json
import sys
import logging
from pathlib import Path
import re

try:
    from faster_whisper import WhisperModel
except ImportError:
    print("faster-whisper가 설치되지 않았습니다. 설치해주세요:")
    print("pip install faster-whisper")
    sys.exit(1)

# 로깅 설정
logging.basicConfig(
    level=logging.INFO,
    format='%(levelname)s:%(name)s:%(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout)  # stdout으로 출력
    ]
)
logger = logging.getLogger(__name__)

# faster-whisper 로그 레벨 조정
logging.getLogger('faster_whisper').setLevel(logging.WARNING)

class ToeicWhisperTranscriber:
    def __init__(self, model_size="base", device="cpu"):
        """
        TOEIC 전문 Whisper 전사기
        
        Args:
            model_size: whisper 모델 크기 (tiny, base, small, medium, large)
            device: 실행 디바이스 (cpu, cuda)
        """
        self.model_size = model_size
        self.device = device
        self.model = None
        
        # TOEIC 특화 키워드
        self.toeic_keywords = [
            "listening", "test", "part", "one", "two", "three", "four",
            "directions", "photograph", "picture", "conversation", "talk",
            "question", "response", "number", "now", "listen"
        ]
    
    def load_model(self):
        """Whisper 모델 로드"""
        try:
            logger.info(f"Whisper 모델 로드 중... (크기: {self.model_size}, 디바이스: {self.device})")
            self.model = WhisperModel(
                self.model_size, 
                device=self.device,
                compute_type="float16" if self.device == "cuda" else "int8"
            )
            logger.info("모델 로드 완료")
        except Exception as e:
            logger.error(f"모델 로드 실패: {e}")
            raise
    
    def transcribe_audio(self, audio_path, language="en"):
        """
        오디오 파일 전사
        
        Args:
            audio_path: 오디오 파일 경로
            language: 언어 코드 (기본: en)
            
        Returns:
            전사 결과 딕셔너리
        """
        if not self.model:
            self.load_model()
        
        try:
            logger.info(f"오디오 전사 시작: {audio_path}")
            
            # Whisper 전사 실행
            logger.info(f"오디오 파일 처리 시작: {audio_path}")
            segments, info = self.model.transcribe(
                audio_path,
                language=language,
                word_timestamps=True,
                vad_filter=True,  # Voice Activity Detection
                vad_parameters=dict(
                    min_silence_duration_ms=800,   # 더 긴 무음으로 완전한 문장 단위 분할
                    max_speech_duration_s=30,      # 더 긴 음성 구간으로 완전한 문장 단위 분할
                    speech_pad_ms=200              # 음성 패딩 추가
                ),
                initial_prompt="This is a TOEIC listening test. Transcribe complete sentences, not fragments. Keep sentences together as complete thoughts. Separate at question numbers like 'Number 7' and answer choices like 'A', 'B', 'C', 'D'.",
                without_timestamps=False,
                temperature=0.0,                   # 더 일관된 결과를 위해
                beam_size=5,                       # 더 나은 품질
                best_of=5                          # 여러 후보 중 최고 선택
            )
            
            logger.info(f"전사 완료 - 언어: {info.language}, 확률: {info.language_probability:.2f}")
            
            # 결과 정리
            result = {
                "language": info.language,
                "language_probability": info.language_probability,
                "duration": info.duration,
                "segments": [],
                "word_segments": [],
                "text": ""
            }
            
            full_text = []
            
            for segment in segments:
                # 세그먼트 정보
                segment_data = {
                    "id": segment.id,
                    "start": segment.start,
                    "end": segment.end,
                    "text": segment.text.strip(),
                    "avg_logprob": segment.avg_logprob,
                    "no_speech_prob": segment.no_speech_prob,
                    "words": []
                }
                
                # 단어별 타임스탬프
                if segment.words:
                    for word in segment.words:
                        word_data = {
                            "word": word.word,
                            "start": word.start,
                            "end": word.end,
                            "probability": word.probability
                        }
                        segment_data["words"].append(word_data)
                        result["word_segments"].append(word_data)
                
                result["segments"].append(segment_data)
                full_text.append(segment.text.strip())
            
            result["text"] = " ".join(full_text)
            
            # TOEIC 키워드 매칭 점수 계산
            result["toeic_score"] = self.calculate_toeic_score(result["text"])
            
            logger.info(f"세그먼트 수: {len(result['segments'])}, TOEIC 점수: {result['toeic_score']:.2f}")
            
            return result
            
        except Exception as e:
            logger.error(f"전사 실패: {e}")
            raise
    
    def calculate_toeic_score(self, text):
        """TOEIC 관련 키워드 매칭 점수 계산"""
        text_lower = text.lower()
        matches = sum(1 for keyword in self.toeic_keywords if keyword in text_lower)
        return matches / len(self.toeic_keywords)
    
    def enhance_segments_for_toeic(self, result):
        """TOEIC 특화 세그먼트 후처리"""
        enhanced = result.copy()
        
        # 파트별 구분자 감지
        part_markers = []
        for segment in result["segments"]:
            text = segment["text"].lower()
            
            if "part one" in text or "part 1" in text:
                part_markers.append({"part": 1, "start": segment["start"], "text": segment["text"]})
            elif "part two" in text or "part 2" in text:
                part_markers.append({"part": 2, "start": segment["start"], "text": segment["text"]})
            elif "part three" in text or "part 3" in text:
                part_markers.append({"part": 3, "start": segment["start"], "text": segment["text"]})
            elif "part four" in text or "part 4" in text:
                part_markers.append({"part": 4, "start": segment["start"], "text": segment["text"]})
        
        enhanced["part_markers"] = part_markers
        
        # 질문 번호 감지
        question_markers = []
        for segment in result["segments"]:
            text = segment["text"].lower()
            
            # "number X" 패턴 찾기
            import re
            numbers = re.findall(r'number\s+(\d+)', text)
            for num in numbers:
                question_markers.append({
                    "question": int(num),
                    "start": segment["start"],
                    "text": segment["text"]
                })
        
        enhanced["question_markers"] = question_markers
        
        return enhanced

def main():
    parser = argparse.ArgumentParser(description="TOEIC 오디오 전사")
    parser.add_argument("--audio", required=True, help="입력 오디오 파일")
    parser.add_argument("--output", help="출력 JSON 파일 (선택사항)")
    parser.add_argument("--model", default="base", help="Whisper 모델 크기")
    parser.add_argument("--language", default="en", help="언어 코드")
    parser.add_argument("--device", default="cpu", help="실행 디바이스")
    parser.add_argument("--output-format", default="file", choices=["file", "json", "srt"], help="출력 형식")
    parser.add_argument("--output-dir", default=".", help="출력 디렉토리")
    
    args = parser.parse_args()
    
    # 입력 파일 확인
    input_path = Path(args.audio)
    if not input_path.exists():
        logger.error(f"입력 파일이 없습니다: {input_path}")
        sys.exit(1)
    
    try:
        # 전사기 생성
        transcriber = ToeicWhisperTranscriber(
            model_size=args.model,
            device=args.device
        )
        
        # 전사 실행
        result = transcriber.transcribe_audio(
            str(input_path),
            language=args.language
        )
        
        # TOEIC 후처리
        enhanced_result = transcriber.enhance_segments_for_toeic(result)
        
        if args.output_format == "json":
            # JSON 형식으로 stdout에 출력 (API용)
            print(json.dumps(enhanced_result, ensure_ascii=False))
        elif args.output_format == "srt":
            # SRT 파일만 생성
            output_dir = Path(args.output_dir)
            output_dir.mkdir(parents=True, exist_ok=True)
            
            srt_path = output_dir / f"{input_path.stem}.srt"
            generate_srt_file(enhanced_result, srt_path)
            
            logger.info(f"SRT 파일 생성 완료: {srt_path}")
            print(f"SRT 파일 생성: {srt_path}")  # stdout 출력으로 Node.js에서 확인 가능
        else:
            # 파일 저장 모드 (JSON + SRT)
            if not args.output:
                output_path = input_path.with_suffix('.json')
            else:
                output_path = Path(args.output)
            
            output_path.parent.mkdir(parents=True, exist_ok=True)
            
            with open(output_path, 'w', encoding='utf-8') as f:
                json.dump(enhanced_result, f, ensure_ascii=False, indent=2)
            
            # SRT 파일도 자동 생성
            srt_path = output_path.with_suffix('.srt')
            generate_srt_file(enhanced_result, srt_path)
            
            logger.info(f"전사 완료: {output_path}")
            logger.info(f"SRT 파일 생성: {srt_path}")
        
    except Exception as e:
        error_msg = {
            "status": "error",
            "error_type": type(e).__name__,
            "error_message": str(e)
        }
        if args.output_format == "json":
            print(json.dumps(error_msg, ensure_ascii=False))
        else:
            logger.error(f"전사 실패: {e}")
        sys.exit(1)

def split_into_sentences(text):
    """텍스트를 문장 단위로 분할"""
    import re
    
    # 문장 끝 패턴: 마침표, 물음표, 느낌표 + 공백 또는 끝
    sentence_endings = re.compile(r'[.!?]+\s+|[.!?]+$')
    
    # 약어나 숫자를 고려한 더 정교한 패턴
    sentences = []
    current_pos = 0
    
    for match in sentence_endings.finditer(text):
        sentence = text[current_pos:match.end()].strip()
        if sentence:
            sentences.append(sentence)
        current_pos = match.end()
    
    # 마지막 남은 텍스트
    if current_pos < len(text):
        remaining = text[current_pos:].strip()
        if remaining:
            sentences.append(remaining)
    
    return sentences

def generate_srt_file(transcript_data, srt_path, max_words_per_subtitle=15):
    """전사 데이터를 문장 단위로 SRT 파일로 변환"""
    try:
        srt_entries = []
        entry_id = 1
        
        for segment in transcript_data.get('segments', []):
            text = segment.get('text', '').strip()
            if not text:
                continue
                
            start_time = segment.get('start', 0)
            end_time = segment.get('end', start_time + 3)
            
            # 먼저 문장 단위로 분할 시도
            sentences = split_into_sentences(text)
            
            if len(sentences) <= 1:
                # 문장이 하나이거나 분할되지 않은 경우, 단어 수로 분할
                words = text.split()
                chunks = []
                
                for i in range(0, len(words), max_words_per_subtitle):
                    chunk = ' '.join(words[i:i + max_words_per_subtitle])
                    chunks.append(chunk)
                
                sentences = chunks
            
            # 각 문장/청크별로 SRT 엔트리 생성
            sentence_duration = (end_time - start_time) / len(sentences) if sentences else 1
            
            for i, sentence in enumerate(sentences):
                sentence_start = start_time + (i * sentence_duration)
                sentence_end = start_time + ((i + 1) * sentence_duration)
                
                # 마지막 문장은 원래 세그먼트 끝시간으로
                if i == len(sentences) - 1:
                    sentence_end = end_time
                
                srt_entries.append({
                    'id': entry_id,
                    'start': sentence_start,
                    'end': sentence_end,
                    'text': sentence.strip()
                })
                entry_id += 1
        
        # SRT 파일 작성
        with open(srt_path, 'w', encoding='utf-8') as f:
            for entry in srt_entries:
                f.write(f"{entry['id']}\n")
                f.write(f"{format_srt_time(entry['start'])} --> {format_srt_time(entry['end'])}\n")
                f.write(f"{entry['text']}\n\n")
        
        logger.info(f"SRT 파일 생성 완료: {len(srt_entries)}개 엔트리")
        
    except Exception as e:
        logger.error(f"SRT 파일 생성 오류: {e}")

def format_srt_time(seconds):
    """초 단위를 SRT 시간 형식으로 변환"""
    hours = int(seconds // 3600)
    minutes = int((seconds % 3600) // 60)
    secs = int(seconds % 60)
    milliseconds = int((seconds % 1) * 1000)
    
    return f"{hours:02d}:{minutes:02d}:{secs:02d},{milliseconds:03d}"

if __name__ == "__main__":
    main()
{
  "metadata": {
    "title": "TOEIC Listening Test Structure",
    "totalQuestions": 100,
    "totalSegments": 54,
    "estimatedDuration": 2700,
    "version": "1.0"
  },
  "parts": {
    "part1": {
      "name": "Part 1",
      "type": "photographs",
      "description": "사진 설명",
      "questionRange": [1, 6],
      "questionCount": 6,
      "avgQuestionDuration": 8,
      "directionDuration": 35,
      "color": "rgba(255, 99, 132, 0.3)",
      "icon": "📷",
      "segments": {
        "direction": {
          "id": "part1-direction",
          "label": "Part 1 (Direction)",
          "description": "Part 1 지시문 - 사진 설명",
          "type": "direction"
        },
        "questions": [
          {
            "id": "q1",
            "label": "Q1",
            "description": "문항 1번 - 사진 설명",
            "questionNumber": 1,
            "type": "question"
          },
          {
            "id": "q2",
            "label": "Q2", 
            "description": "문항 2번 - 사진 설명",
            "questionNumber": 2,
            "type": "question"
          },
          {
            "id": "q3",
            "label": "Q3",
            "description": "문항 3번 - 사진 설명", 
            "questionNumber": 3,
            "type": "question"
          },
          {
            "id": "q4",
            "label": "Q4",
            "description": "문항 4번 - 사진 설명",
            "questionNumber": 4,
            "type": "question"
          },
          {
            "id": "q5",
            "label": "Q5",
            "description": "문항 5번 - 사진 설명",
            "questionNumber": 5,
            "type": "question"
          },
          {
            "id": "q6",
            "label": "Q6",
            "description": "문항 6번 - 사진 설명",
            "questionNumber": 6,
            "type": "question"
          }
        ]
      }
    },
    "part2": {
      "name": "Part 2",
      "type": "question_response",
      "description": "질문-응답",
      "questionRange": [7, 31],
      "questionCount": 25,
      "avgQuestionDuration": 6,
      "directionDuration": 12,
      "color": "rgba(54, 162, 235, 0.3)",
      "icon": "🗣️",
      "segments": {
        "direction": {
          "id": "part2-direction",
          "label": "Part 2 (Direction)",
          "description": "Part 2 지시문 - 질문-응답",
          "type": "direction"
        },
        "questions": [
          {"id": "q7", "label": "Q7", "description": "문항 7번 - 질문-응답", "questionNumber": 7, "type": "question"},
          {"id": "q8", "label": "Q8", "description": "문항 8번 - 질문-응답", "questionNumber": 8, "type": "question"},
          {"id": "q9", "label": "Q9", "description": "문항 9번 - 질문-응답", "questionNumber": 9, "type": "question"},
          {"id": "q10", "label": "Q10", "description": "문항 10번 - 질문-응답", "questionNumber": 10, "type": "question"},
          {"id": "q11", "label": "Q11", "description": "문항 11번 - 질문-응답", "questionNumber": 11, "type": "question"},
          {"id": "q12", "label": "Q12", "description": "문항 12번 - 질문-응답", "questionNumber": 12, "type": "question"},
          {"id": "q13", "label": "Q13", "description": "문항 13번 - 질문-응답", "questionNumber": 13, "type": "question"},
          {"id": "q14", "label": "Q14", "description": "문항 14번 - 질문-응답", "questionNumber": 14, "type": "question"},
          {"id": "q15", "label": "Q15", "description": "문항 15번 - 질문-응답", "questionNumber": 15, "type": "question"},
          {"id": "q16", "label": "Q16", "description": "문항 16번 - 질문-응답", "questionNumber": 16, "type": "question"},
          {"id": "q17", "label": "Q17", "description": "문항 17번 - 질문-응답", "questionNumber": 17, "type": "question"},
          {"id": "q18", "label": "Q18", "description": "문항 18번 - 질문-응답", "questionNumber": 18, "type": "question"},
          {"id": "q19", "label": "Q19", "description": "문항 19번 - 질문-응답", "questionNumber": 19, "type": "question"},
          {"id": "q20", "label": "Q20", "description": "문항 20번 - 질문-응답", "questionNumber": 20, "type": "question"},
          {"id": "q21", "label": "Q21", "description": "문항 21번 - 질문-응답", "questionNumber": 21, "type": "question"},
          {"id": "q22", "label": "Q22", "description": "문항 22번 - 질문-응답", "questionNumber": 22, "type": "question"},
          {"id": "q23", "label": "Q23", "description": "문항 23번 - 질문-응답", "questionNumber": 23, "type": "question"},
          {"id": "q24", "label": "Q24", "description": "문항 24번 - 질문-응답", "questionNumber": 24, "type": "question"},
          {"id": "q25", "label": "Q25", "description": "문항 25번 - 질문-응답", "questionNumber": 25, "type": "question"},
          {"id": "q26", "label": "Q26", "description": "문항 26번 - 질문-응답", "questionNumber": 26, "type": "question"},
          {"id": "q27", "label": "Q27", "description": "문항 27번 - 질문-응답", "questionNumber": 27, "type": "question"},
          {"id": "q28", "label": "Q28", "description": "문항 28번 - 질문-응답", "questionNumber": 28, "type": "question"},
          {"id": "q29", "label": "Q29", "description": "문항 29번 - 질문-응답", "questionNumber": 29, "type": "question"},
          {"id": "q30", "label": "Q30", "description": "문항 30번 - 질문-응답", "questionNumber": 30, "type": "question"},
          {"id": "q31", "label": "Q31", "description": "문항 31번 - 질문-응답", "questionNumber": 31, "type": "question"}
        ]
      }
    },
    "part3": {
      "name": "Part 3", 
      "type": "conversations",
      "description": "대화",
      "questionRange": [32, 70],
      "questionCount": 39,
      "groupCount": 13,
      "questionsPerGroup": 3,
      "avgGroupDuration": 35,
      "directionDuration": 15,
      "color": "rgba(255, 206, 86, 0.3)",
      "icon": "💬",
      "categories": ["회의", "은퇴", "교육", "여행", "음식", "음악", "운동", "의료", "정부", "교통"],
      "segments": {
        "direction": {
          "id": "part3-direction",
          "label": "Part 3 (Direction)",
          "description": "Part 3 지시문 - 대화",
          "type": "direction"
        },
        "groups": [
          {"id": "q32-34", "label": "Q32-34", "description": "문항 32-34번 - 대화", "questionNumbers": [32, 33, 34], "type": "passage"},
          {"id": "q32", "label": "Q32", "description": "문항 32번 - 대화", "questionNumbers": [32], "type": "question"},
          {"id": "q33", "label": "Q33", "description": "문항 33번 - 대화", "questionNumbers": [33], "type": "question"},
          {"id": "q34", "label": "Q34", "description": "문항 34번 - 대화", "questionNumbers": [34], "type": "question"},
          
          {"id": "q35-37", "label": "Q35-37", "description": "문항 35-37번 - 대화", "questionNumbers": [35, 36, 37], "type": "passage"},
          {"id": "q35", "label": "Q35", "description": "문항 35번 - 대화", "questionNumbers": [35], "type": "question"},
          {"id": "q36", "label": "Q36", "description": "문항 36번 - 대화", "questionNumbers": [36], "type": "question"},
          {"id": "q37", "label": "Q37", "description": "문항 37번 - 대화", "questionNumbers": [37], "type": "question"},
          
          {"id": "q38-40", "label": "Q38-40", "description": "문항 38-40번 - 대화", "questionNumbers": [38, 39, 40], "type": "passage"},
          {"id": "q38", "label": "Q38", "description": "문항 38번 - 대화", "questionNumbers": [38], "type": "question"},
          {"id": "q39", "label": "Q39", "description": "문항 39번 - 대화", "questionNumbers": [39], "type": "question"},
          {"id": "q40", "label": "Q40", "description": "문항 40번 - 대화", "questionNumbers": [40], "type": "question"},
          
          {"id": "q41-43", "label": "Q41-43", "description": "문항 41-43번 - 대화", "questionNumbers": [41, 42, 43], "type": "passage"},
          {"id": "q41", "label": "Q41", "description": "문항 41번 - 대화", "questionNumbers": [41], "type": "question"},
          {"id": "q42", "label": "Q42", "description": "문항 42번 - 대화", "questionNumbers": [42], "type": "question"},
          {"id": "q43", "label": "Q43", "description": "문항 43번 - 대화", "questionNumbers": [43], "type": "question"},
          
          {"id": "q44-46", "label": "Q44-46", "description": "문항 44-46번 - 대화", "questionNumbers": [44, 45, 46], "type": "passage"},
          {"id": "q44", "label": "Q44", "description": "문항 44번 - 대화", "questionNumbers": [44], "type": "question"},
          {"id": "q45", "label": "Q45", "description": "문항 45번 - 대화", "questionNumbers": [45], "type": "question"},
          {"id": "q46", "label": "Q46", "description": "문항 46번 - 대화", "questionNumbers": [46], "type": "question"},
          
          {"id": "q47-49", "label": "Q47-49", "description": "문항 47-49번 - 대화", "questionNumbers": [47, 48, 49], "type": "passage"},
          {"id": "q47", "label": "Q47", "description": "문항 47번 - 대화", "questionNumbers": [47], "type": "question"},
          {"id": "q48", "label": "Q48", "description": "문항 48번 - 대화", "questionNumbers": [48], "type": "question"},
          {"id": "q49", "label": "Q49", "description": "문항 49번 - 대화", "questionNumbers": [49], "type": "question"},
          
          {"id": "q50-52", "label": "Q50-52", "description": "문항 50-52번 - 대화", "questionNumbers": [50, 51, 52], "type": "passage"},
          {"id": "q50", "label": "Q50", "description": "문항 50번 - 대화", "questionNumbers": [50], "type": "question"},
          {"id": "q51", "label": "Q51", "description": "문항 51번 - 대화", "questionNumbers": [51], "type": "question"},
          {"id": "q52", "label": "Q52", "description": "문항 52번 - 대화", "questionNumbers": [52], "type": "question"},
          
          {"id": "q53-55", "label": "Q53-55", "description": "문항 53-55번 - 대화", "questionNumbers": [53, 54, 55], "type": "passage"},
          {"id": "q53", "label": "Q53", "description": "문항 53번 - 대화", "questionNumbers": [53], "type": "question"},
          {"id": "q54", "label": "Q54", "description": "문항 54번 - 대화", "questionNumbers": [54], "type": "question"},
          {"id": "q55", "label": "Q55", "description": "문항 55번 - 대화", "questionNumbers": [55], "type": "question"},
          
          {"id": "q56-58", "label": "Q56-58", "description": "문항 56-58번 - 대화", "questionNumbers": [56, 57, 58], "type": "passage"},
          {"id": "q56", "label": "Q56", "description": "문항 56번 - 대화", "questionNumbers": [56], "type": "question"},
          {"id": "q57", "label": "Q57", "description": "문항 57번 - 대화", "questionNumbers": [57], "type": "question"},
          {"id": "q58", "label": "Q58", "description": "문항 58번 - 대화", "questionNumbers": [58], "type": "question"},
          
          {"id": "q59-61", "label": "Q59-61", "description": "문항 59-61번 - 대화", "questionNumbers": [59, 60, 61], "type": "passage"},
          {"id": "q59", "label": "Q59", "description": "문항 59번 - 대화", "questionNumbers": [59], "type": "question"},
          {"id": "q60", "label": "Q60", "description": "문항 60번 - 대화", "questionNumbers": [60], "type": "question"},
          {"id": "q61", "label": "Q61", "description": "문항 61번 - 대화", "questionNumbers": [61], "type": "question"},
          
          {"id": "q62-64", "label": "Q62-64", "description": "문항 62-64번 - 대화", "questionNumbers": [62, 63, 64], "type": "passage"},
          {"id": "q62", "label": "Q62", "description": "문항 62번 - 대화", "questionNumbers": [62], "type": "question"},
          {"id": "q63", "label": "Q63", "description": "문항 63번 - 대화", "questionNumbers": [63], "type": "question"},
          {"id": "q64", "label": "Q64", "description": "문항 64번 - 대화", "questionNumbers": [64], "type": "question"},
          
          {"id": "q65-67", "label": "Q65-67", "description": "문항 65-67번 - 대화", "questionNumbers": [65, 66, 67], "type": "passage"},
          {"id": "q65", "label": "Q65", "description": "문항 65번 - 대화", "questionNumbers": [65], "type": "question"},
          {"id": "q66", "label": "Q66", "description": "문항 66번 - 대화", "questionNumbers": [66], "type": "question"},
          {"id": "q67", "label": "Q67", "description": "문항 67번 - 대화", "questionNumbers": [67], "type": "question"},
          
          {"id": "q68-70", "label": "Q68-70", "description": "문항 68-70번 - 대화", "questionNumbers": [68, 69, 70], "type": "passage"},
          {"id": "q68", "label": "Q68", "description": "문항 68번 - 대화", "questionNumbers": [68], "type": "question"},
          {"id": "q69", "label": "Q69", "description": "문항 69번 - 대화", "questionNumbers": [69], "type": "question"},
          {"id": "q70", "label": "Q70", "description": "문항 70번 - 대화", "questionNumbers": [70], "type": "question"}
        ]
      }
    },
    "part4": {
      "name": "Part 4",
      "type": "talks", 
      "description": "토크",
      "questionRange": [71, 100],
      "questionCount": 30,
      "groupCount": 10,
      "questionsPerGroup": 3,
      "avgGroupDuration": 45,
      "directionDuration": 15,
      "color": "rgba(75, 192, 192, 0.3)",
      "icon": "🎤",
      "categories": ["광고", "안내", "박물관", "뉴스", "공지사항", "프레젠테이션"],
      "segments": {
        "direction": {
          "id": "part4-direction", 
          "label": "Part 4 (Direction)",
          "description": "Part 4 지시문 - 토크",
          "type": "direction"
        },
        "groups": [
          {"id": "q71-73", "label": "Q71-73", "description": "문항 71-73번 - 토크", "questionNumbers": [71, 72, 73], "type": "passage"},
          {"id": "q71", "label": "Q71", "description": "문항 71번 - 토크", "questionNumbers": [71], "type": "question"},
          {"id": "q72", "label": "Q72", "description": "문항 72번 - 토크", "questionNumbers": [72], "type": "question"},
          {"id": "q73", "label": "Q73", "description": "문항 73번 - 토크", "questionNumbers": [73], "type": "question"},
          
          {"id": "q74-76", "label": "Q74-76", "description": "문항 74-76번 - 토크", "questionNumbers": [74, 75, 76], "type": "passage"},
          {"id": "q74", "label": "Q74", "description": "문항 74번 - 토크", "questionNumbers": [74], "type": "question"},
          {"id": "q75", "label": "Q75", "description": "문항 75번 - 토크", "questionNumbers": [75], "type": "question"},
          {"id": "q76", "label": "Q76", "description": "문항 76번 - 토크", "questionNumbers": [76], "type": "question"},
          
          {"id": "q77-79", "label": "Q77-79", "description": "문항 77-79번 - 토크", "questionNumbers": [77, 78, 79], "type": "passage"},
          {"id": "q77", "label": "Q77", "description": "문항 77번 - 토크", "questionNumbers": [77], "type": "question"},
          {"id": "q78", "label": "Q78", "description": "문항 78번 - 토크", "questionNumbers": [78], "type": "question"},
          {"id": "q79", "label": "Q79", "description": "문항 79번 - 토크", "questionNumbers": [79], "type": "question"},
          
          {"id": "q80-82", "label": "Q80-82", "description": "문항 80-82번 - 토크", "questionNumbers": [80, 81, 82], "type": "passage"},
          {"id": "q80", "label": "Q80", "description": "문항 80번 - 토크", "questionNumbers": [80], "type": "question"},
          {"id": "q81", "label": "Q81", "description": "문항 81번 - 토크", "questionNumbers": [81], "type": "question"},
          {"id": "q82", "label": "Q82", "description": "문항 82번 - 토크", "questionNumbers": [82], "type": "question"},
          
          {"id": "q83-85", "label": "Q83-85", "description": "문항 83-85번 - 토크", "questionNumbers": [83, 84, 85], "type": "passage"},
          {"id": "q83", "label": "Q83", "description": "문항 83번 - 토크", "questionNumbers": [83], "type": "question"},
          {"id": "q84", "label": "Q84", "description": "문항 84번 - 토크", "questionNumbers": [84], "type": "question"},
          {"id": "q85", "label": "Q85", "description": "문항 85번 - 토크", "questionNumbers": [85], "type": "question"},
          
          {"id": "q86-88", "label": "Q86-88", "description": "문항 86-88번 - 토크", "questionNumbers": [86, 87, 88], "type": "passage"},
          {"id": "q86", "label": "Q86", "description": "문항 86번 - 토크", "questionNumbers": [86], "type": "question"},
          {"id": "q87", "label": "Q87", "description": "문항 87번 - 토크", "questionNumbers": [87], "type": "question"},
          {"id": "q88", "label": "Q88", "description": "문항 88번 - 토크", "questionNumbers": [88], "type": "question"},
          
          {"id": "q89-91", "label": "Q89-91", "description": "문항 89-91번 - 토크", "questionNumbers": [89, 90, 91], "type": "passage"},
          {"id": "q89", "label": "Q89", "description": "문항 89번 - 토크", "questionNumbers": [89], "type": "question"},
          {"id": "q90", "label": "Q90", "description": "문항 90번 - 토크", "questionNumbers": [90], "type": "question"},
          {"id": "q91", "label": "Q91", "description": "문항 91번 - 토크", "questionNumbers": [91], "type": "question"},
          
          {"id": "q92-94", "label": "Q92-94", "description": "문항 92-94번 - 토크", "questionNumbers": [92, 93, 94], "type": "passage"},
          {"id": "q92", "label": "Q92", "description": "문항 92번 - 토크", "questionNumbers": [92], "type": "question"},
          {"id": "q93", "label": "Q93", "description": "문항 93번 - 토크", "questionNumbers": [93], "type": "question"},
          {"id": "q94", "label": "Q94", "description": "문항 94번 - 토크", "questionNumbers": [94], "type": "question"},
          
          {"id": "q95-97", "label": "Q95-97", "description": "문항 95-97번 - 토크", "questionNumbers": [95, 96, 97], "type": "passage"},
          {"id": "q95", "label": "Q95", "description": "문항 95번 - 토크", "questionNumbers": [95], "type": "question"},
          {"id": "q96", "label": "Q96", "description": "문항 96번 - 토크", "questionNumbers": [96], "type": "question"},
          {"id": "q97", "label": "Q97", "description": "문항 97번 - 토크", "questionNumbers": [97], "type": "question"},
          
          {"id": "q98-100", "label": "Q98-100", "description": "문항 98-100번 - 토크", "questionNumbers": [98, 99, 100], "type": "passage"},
          {"id": "q98", "label": "Q98", "description": "문항 98번 - 토크", "questionNumbers": [98], "type": "question"},
          {"id": "q99", "label": "Q99", "description": "문항 99번 - 토크", "questionNumbers": [99], "type": "question"},
          {"id": "q100", "label": "Q100", "description": "문항 100번 - 토크", "questionNumbers": [100], "type": "question"}
        ]
      }
    }
  },
  "segmentOrder": [
    "part1-direction", "q1", "q2", "q3", "q4", "q5", "q6",
    "part2-direction", "q7", "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15", "q16", "q17", "q18", "q19", "q20", "q21", "q22", "q23", "q24", "q25", "q26", "q27", "q28", "q29", "q30", "q31",
    "part3-direction", 
    "q32-34", "q32", "q33", "q34",
    "q35-37", "q35", "q36", "q37", 
    "q38-40", "q38", "q39", "q40",
    "q41-43", "q41", "q42", "q43",
    "q44-46", "q44", "q45", "q46",
    "q47-49", "q47", "q48", "q49",
    "q50-52", "q50", "q51", "q52",
    "q53-55", "q53", "q54", "q55",
    "q56-58", "q56", "q57", "q58",
    "q59-61", "q59", "q60", "q61",
    "q62-64", "q62", "q63", "q64",
    "q65-67", "q65", "q66", "q67",
    "q68-70", "q68", "q69", "q70",
    "part4-direction",
    "q71-73", "q71", "q72", "q73",
    "q74-76", "q74", "q75", "q76",
    "q77-79", "q77", "q78", "q79",
    "q80-82", "q80", "q81", "q82",
    "q83-85", "q83", "q84", "q85",
    "q86-88", "q86", "q87", "q88",
    "q89-91", "q89", "q90", "q91",
    "q92-94", "q92", "q93", "q94",
    "q95-97", "q95", "q96", "q97",
    "q98-100", "q98", "q99", "q100"
  ],
  "timingDefaults": {
    "part1Direction": 35,
    "part1QuestionAvg": 8,
    "part2Direction": 12,
    "part2QuestionAvg": 6,
    "part3Direction": 15,
    "part3GroupAvg": 35,
    "part4Direction": 15,
    "part4GroupAvg": 45
  },
  "segmentKeywords": {
    "part1": {
      "direction": {
        "primary": ["Listening test", "part 1", "part 1."],
        "content": ["look at the picture", "in your test book", "select the one statement", "best describes", "what you see", "photograph"]
      },
      "questions": [
        {"number": 1, "keywords": ["number one", "number 1", "number 1."], "variations": ["question 1", "question one"]},
        {"number": 2, "keywords": ["number two", "number 2", "number 2."], "variations": ["question 2", "question two"]},
        {"number": 3, "keywords": ["number three", "number 3", "number 3."], "variations": ["question 3", "question three"]},
        {"number": 4, "keywords": ["number four", "number 4", "number 4."], "variations": ["question 4", "question four"]},
        {"number": 5, "keywords": ["number five", "number 5", "number 5."], "variations": ["question 5", "question five"]},
        {"number": 6, "keywords": ["number six", "number 6", "number 6."], "variations": ["question 6", "question six"]}
      ]
    },
    "part2": {
      "direction": {
        "primary": ["part two", "part 2", "part 2."],
        "content": ["you will hear a question", "followed by three responses", "select the best response"]
      },
      "questions": [
        {"number": 7, "keywords": ["number seven", "number 7", "number 7."], "variations": ["question 7", "question seven"]},
        {"number": 8, "keywords": ["number 8", "number eight"], "variations": ["question 8", "question eight"]},
        {"number": 9, "keywords": ["number 9", "number nine"], "variations": ["question 9", "question nine"]},
        {"number": 10, "keywords": ["number 10", "number ten"], "variations": ["question 10", "question ten"]},
        {"number": 11, "keywords": ["number 11", "number eleven"], "variations": ["question 11", "question eleven"]},
        {"number": 12, "keywords": ["number 12", "number twelve"], "variations": ["question 12", "question twelve"]},
        {"number": 13, "keywords": ["number 13", "number thirteen"], "variations": ["question 13", "question thirteen"]},
        {"number": 14, "keywords": ["number 14", "number fourteen"], "variations": ["question 14", "question fourteen"]},
        {"number": 15, "keywords": ["number 15", "number fifteen"], "variations": ["question 15", "question fifteen"]},
        {"number": 16, "keywords": ["number 16", "number sixteen"], "variations": ["question 16", "question sixteen"]},
        {"number": 17, "keywords": ["number 17", "number seventeen"], "variations": ["question 17", "question seventeen"]},
        {"number": 18, "keywords": ["number 18", "number eighteen"], "variations": ["question 18", "question eighteen"]},
        {"number": 19, "keywords": ["number 19", "number nineteen"], "variations": ["question 19", "question nineteen"]},
        {"number": 20, "keywords": ["number 20", "number twenty"], "variations": ["question 20", "question twenty"]},
        {"number": 21, "keywords": ["number 21", "number twenty-one"], "variations": ["question 21", "question twenty-one"]},
        {"number": 22, "keywords": ["number 22", "number twenty-two"], "variations": ["question 22", "question twenty-two"]},
        {"number": 23, "keywords": ["number 23", "number twenty-three"], "variations": ["question 23", "question twenty-three"]},
        {"number": 24, "keywords": ["number 24", "number twenty-four"], "variations": ["question 24", "question twenty-four"]},
        {"number": 25, "keywords": ["number 25", "number twenty-five"], "variations": ["question 25", "question twenty-five"]},
        {"number": 26, "keywords": ["number 26", "number twenty-six"], "variations": ["question 26", "question twenty-six"]},
        {"number": 27, "keywords": ["number 27", "number twenty-seven"], "variations": ["question 27", "question twenty-seven"]},
        {"number": 28, "keywords": ["number 28", "number twenty-eight"], "variations": ["question 28", "question twenty-eight"]},
        {"number": 29, "keywords": ["number 29", "number twenty-nine"], "variations": ["question 29", "question twenty-nine"]},
        {"number": 30, "keywords": ["number 30", "number thirty"], "variations": ["question 30", "question thirty"]},
        {"number": 31, "keywords": ["number 31", "number thirty-one"], "variations": ["question 31", "question thirty-one"]}
      ]
    },
    "part3": {
      "direction": {
        "primary": ["part three", "part 3"],
        "content": ["you will hear some conversations", "between two people", "three questions", "conversation"]
      },
      "groups": [
        {"questions": [32,33,34], "keywords": ["question 32 through 34", "questions 32 through 34"], "variations": ["question thirty-two through thirty-four", "questions thirty-two through thirty-four"]},
        {"number": 32, "keywords": ["number thirty-two", "number 32", "number 32."], "variations": ["question 32", "question thirty-two"]},
        {"number": 33, "keywords": ["number thirty-three", "number 33", "number 33."], "variations": ["question 33", "question thirty-three"]},
        {"number": 34, "keywords": ["number thirty-four", "number 34", "number 34."], "variations": ["question 34", "question thirty-four"]},
        
        {"questions": [35,36,37], "keywords": ["question 35 through 37", "questions 35 through 37"], "variations": ["question thirty-five through thirty-seven", "questions thirty-five through thirty-seven"]},
        {"number": 35, "keywords": ["number thirty-five", "number 35", "number 35."], "variations": ["question 35", "question thirty-five"]},
        {"number": 36, "keywords": ["number thirty-six", "number 36", "number 36."], "variations": ["question 36", "question thirty-six"]},
        {"number": 37, "keywords": ["number thirty-seven", "number 37", "number 37."], "variations": ["question 37", "question thirty-seven"]},
        
        {"questions": [38,39,40], "keywords": ["question 38 through 40", "questions 38 through 40"], "variations": ["question thirty-eight through forty", "questions thirty-eight through forty"]},
        {"number": 38, "keywords": ["number thirty-eight", "number 38", "number 38."], "variations": ["question 38", "question thirty-eight"]},
        {"number": 39, "keywords": ["number thirty-nine", "number 39", "number 39."], "variations": ["question 39", "question thirty-nine"]},
        {"number": 40, "keywords": ["number forty", "number 40", "number 40."], "variations": ["question 40", "question forty"]},
        
        {"questions": [41,42,43], "keywords": ["question 41 through 43", "questions 41 through 43"], "variations": ["question forty-one through forty-three", "questions forty-one through forty-three"]},
        {"number": 41, "keywords": ["number forty-one", "number 41", "number 41."], "variations": ["question 41", "question forty-one"]},
        {"number": 42, "keywords": ["number forty-two", "number 42", "number 42."], "variations": ["question 42", "question forty-two"]},
        {"number": 43, "keywords": ["number forty-three", "number 43", "number 43."], "variations": ["question 43", "question forty-three"]},
        
        {"questions": [44,45,46], "keywords": ["question 44 through 46", "questions 44 through 46"], "variations": ["question forty-four through forty-six", "questions forty-four through forty-six"]},
        {"number": 44, "keywords": ["number forty-four", "number 44", "number 44."], "variations": ["question 44", "question forty-four"]},
        {"number": 45, "keywords": ["number forty-five", "number 45", "number 45."], "variations": ["question 45", "question forty-five"]},
        {"number": 46, "keywords": ["number forty-six", "number 46", "number 46."], "variations": ["question 46", "question forty-six"]},
        
        {"questions": [47,48,49], "keywords": ["question 47 through 49", "questions 47 through 49"], "variations": ["question forty-seven through forty-nine", "questions forty-seven through forty-nine"]},
        {"number": 47, "keywords": ["number forty-seven", "number 47", "number 47."], "variations": ["question 47", "question forty-seven"]},
        {"number": 48, "keywords": ["number forty-eight", "number 48", "number 48."], "variations": ["question 48", "question forty-eight"]},
        {"number": 49, "keywords": ["number forty-nine", "number 49", "number 49."], "variations": ["question 49", "question forty-nine"]},
        
        {"questions": [50,51,52], "keywords": ["question 50 through 52", "questions 50 through 52"], "variations": ["question fifty through fifty-two", "questions fifty through fifty-two"]},
        {"number": 50, "keywords": ["number fifty", "number 50", "number 50."], "variations": ["question 50", "question fifty"]},
        {"number": 51, "keywords": ["number fifty-one", "number 51", "number 51."], "variations": ["question 51", "question fifty-one"]},
        {"number": 52, "keywords": ["number fifty-two", "number 52", "number 52."], "variations": ["question 52", "question fifty-two"]},
        
        {"questions": [53,54,55], "keywords": ["question 53 through 55", "questions 53 through 55"], "variations": ["question fifty-three through fifty-five", "questions fifty-three through fifty-five"]},
        {"number": 53, "keywords": ["number fifty-three", "number 53", "number 53."], "variations": ["question 53", "question fifty-three"]},
        {"number": 54, "keywords": ["number fifty-four", "number 54", "number 54."], "variations": ["question 54", "question fifty-four"]},
        {"number": 55, "keywords": ["number fifty-five", "number 55", "number 55."], "variations": ["question 55", "question fifty-five"]},
        
        {"questions": [56,57,58], "keywords": ["question 56 through 58", "questions 56 through 58"], "variations": ["question fifty-six through fifty-eight", "questions fifty-six through fifty-eight"]},
        {"number": 56, "keywords": ["number fifty-six", "number 56", "number 56."], "variations": ["question 56", "question fifty-six"]},
        {"number": 57, "keywords": ["number fifty-seven", "number 57", "number 57."], "variations": ["question 57", "question fifty-seven"]},
        {"number": 58, "keywords": ["number fifty-eight", "number 58", "number 58."], "variations": ["question 58", "question fifty-eight"]},
        
        {"questions": [59,60,61], "keywords": ["question 59 through 61", "questions 59 through 61"], "variations": ["question fifty-nine through sixty-one", "questions fifty-nine through sixty-one"]},
        {"number": 59, "keywords": ["number fifty-nine", "number 59", "number 59."], "variations": ["question 59", "question fifty-nine"]},
        {"number": 60, "keywords": ["number sixty", "number 60", "number 60."], "variations": ["question 60", "question sixty"]},
        {"number": 61, "keywords": ["number sixty-one", "number 61", "number 61."], "variations": ["question 61", "question sixty-one"]},
        
        {"questions": [62,63,64], "keywords": ["question 62 through 64", "questions 62 through 64"], "variations": ["question sixty-two through sixty-four", "questions sixty-two through sixty-four"]},
        {"number": 62, "keywords": ["number sixty-two", "number 62", "number 62."], "variations": ["question 62", "question sixty-two"]},
        {"number": 63, "keywords": ["number sixty-three", "number 63", "number 63."], "variations": ["question 63", "question sixty-three"]},
        {"number": 64, "keywords": ["number sixty-four", "number 64", "number 64."], "variations": ["question 64", "question sixty-four"]},
        
        {"questions": [65,66,67], "keywords": ["question 65 through 67", "questions 65 through 67"], "variations": ["question sixty-five through sixty-seven", "questions sixty-five through sixty-seven"]},
        {"number": 65, "keywords": ["number sixty-five", "number 65", "number 65."], "variations": ["question 65", "question sixty-five"]},
        {"number": 66, "keywords": ["number sixty-six", "number 66", "number 66."], "variations": ["question 66", "question sixty-six"]},
        {"number": 67, "keywords": ["number sixty-seven", "number 67", "number 67."], "variations": ["question 67", "question sixty-seven"]},
        
        {"questions": [68,69,70], "keywords": ["question 68 through 70", "questions 68 through 70"], "variations": ["question sixty-eight through seventy", "questions sixty-eight through seventy"]},
        {"number": 68, "keywords": ["number sixty-eight", "number 68", "number 68."], "variations": ["question 68", "question sixty-eight"]},
        {"number": 69, "keywords": ["number sixty-nine", "number 69", "number 69."], "variations": ["question 69", "question sixty-nine"]},
        {"number": 70, "keywords": ["number seventy", "number 70", "number 70."], "variations": ["question 70", "question seventy"]}
      ]
    },
    "part4": {
      "direction": {
        "primary": ["part four", "part 4", "part 4."],
        "content": ["you will hear some talks", "given by a single speaker", "about each talk"]
      },
      "groups": [
        {"questions": [71,72,73], "keywords": ["question 71 through 73", "questions 71 through 73"], "variations": ["question seventy-one through seventy-three", "questions seventy-one through seventy-three"]},
        {"number": 71, "keywords": ["number seventy-one", "number 71", "number 71."], "variations": ["question 71", "question seventy-one"]},
        {"number": 72, "keywords": ["number seventy-two", "number 72", "number 72."], "variations": ["question 72", "question seventy-two"]},
        {"number": 73, "keywords": ["number seventy-three", "number 73", "number 73."], "variations": ["question 73", "question seventy-three"]},
        
        {"questions": [74,75,76], "keywords": ["question 74 through 76", "questions 74 through 76"], "variations": ["question seventy-four through seventy-six", "questions seventy-four through seventy-six"]},
        {"number": 74, "keywords": ["number seventy-four", "number 74", "number 74."], "variations": ["question 74", "question seventy-four"]},
        {"number": 75, "keywords": ["number seventy-five", "number 75", "number 75."], "variations": ["question 75", "question seventy-five"]},
        {"number": 76, "keywords": ["number seventy-six", "number 76", "number 76."], "variations": ["question 76", "question seventy-six"]},
        
        {"questions": [77,78,79], "keywords": ["question 77 through 79", "questions 77 through 79"], "variations": ["question seventy-seven through seventy-nine", "questions seventy-seven through seventy-nine"]},
        {"number": 77, "keywords": ["number seventy-seven", "number 77", "number 77."], "variations": ["question 77", "question seventy-seven"]},
        {"number": 78, "keywords": ["number seventy-eight", "number 78", "number 78."], "variations": ["question 78", "question seventy-eight"]},
        {"number": 79, "keywords": ["number seventy-nine", "number 79", "number 79."], "variations": ["question 79", "question seventy-nine"]},
        
        {"questions": [80,81,82], "keywords": ["question 80 through 82", "questions 80 through 82"], "variations": ["question eighty through eighty-two", "questions eighty through eighty-two"]},
        {"number": 80, "keywords": ["number eighty", "number 80", "number 80."], "variations": ["question 80", "question eighty"]},
        {"number": 81, "keywords": ["number eighty-one", "number 81", "number 81."], "variations": ["question 81", "question eighty-one"]},
        {"number": 82, "keywords": ["number eighty-two", "number 82", "number 82."], "variations": ["question 82", "question eighty-two"]},
        
        {"questions": [83,84,85], "keywords": ["question 83 through 85", "questions 83 through 85"], "variations": ["question eighty-three through eighty-five", "questions eighty-three through eighty-five"]},
        {"number": 83, "keywords": ["number eighty-three", "number 83", "number 83."], "variations": ["question 83", "question eighty-three"]},
        {"number": 84, "keywords": ["number eighty-four", "number 84", "number 84."], "variations": ["question 84", "question eighty-four"]},
        {"number": 85, "keywords": ["number eighty-five", "number 85", "number 85."], "variations": ["question 85", "question eighty-five"]},
        
        {"questions": [86,87,88], "keywords": ["question 86 through 88", "questions 86 through 88"], "variations": ["question eighty-six through eighty-eight", "questions eighty-six through eighty-eight"]},
        {"number": 86, "keywords": ["number eighty-six", "number 86", "number 86."], "variations": ["question 86", "question eighty-six"]},
        {"number": 87, "keywords": ["number eighty-seven", "number 87", "number 87."], "variations": ["question 87", "question eighty-seven"]},
        {"number": 88, "keywords": ["number eighty-eight", "number 88", "number 88."], "variations": ["question 88", "question eighty-eight"]},
        
        {"questions": [89,90,91], "keywords": ["question 89 through 91", "questions 89 through 91"], "variations": ["question eighty-nine through ninety-one", "questions eighty-nine through ninety-one"]},
        {"number": 89, "keywords": ["number eighty-nine", "number 89", "number 89."], "variations": ["question 89", "question eighty-nine"]},
        {"number": 90, "keywords": ["number ninety", "number 90", "number 90."], "variations": ["question 90", "question ninety"]},
        {"number": 91, "keywords": ["number ninety-one", "number 91", "number 91."], "variations": ["question 91", "question ninety-one"]},
        
        {"questions": [92,93,94], "keywords": ["question 92 through 94", "questions 92 through 94"], "variations": ["question ninety-two through ninety-four", "questions ninety-two through ninety-four"]},
        {"number": 92, "keywords": ["number ninety-two", "number 92", "number 92."], "variations": ["question 92", "question ninety-two"]},
        {"number": 93, "keywords": ["number ninety-three", "number 93", "number 93."], "variations": ["question 93", "question ninety-three"]},
        {"number": 94, "keywords": ["number ninety-four", "number 94", "number 94."], "variations": ["question 94", "question ninety-four"]},
        
        {"questions": [95,96,97], "keywords": ["question 95 through 97", "questions 95 through 97"], "variations": ["question ninety-five through ninety-seven", "questions ninety-five through ninety-seven"]},
        {"number": 95, "keywords": ["number ninety-five", "number 95", "number 95."], "variations": ["question 95", "question ninety-five"]},
        {"number": 96, "keywords": ["number ninety-six", "number 96", "number 96."], "variations": ["question 96", "question ninety-six"]},
        {"number": 97, "keywords": ["number ninety-seven", "number 97", "number 97."], "variations": ["question 97", "question ninety-seven"]},
        
        {"questions": [98,99,100], "keywords": ["question 98 through 100", "questions 98 through 100"], "variations": ["question ninety-eight through one hundred", "questions ninety-eight through one hundred"]},
        {"number": 98, "keywords": ["number ninety-eight", "number 98", "number 98."], "variations": ["question 98", "question ninety-eight"]},
        {"number": 99, "keywords": ["number ninety-nine", "number 99", "number 99."], "variations": ["question 99", "question ninety-nine"]},
        {"number": 100, "keywords": ["number one hundred", "number 100", "number 100."], "variations": ["question 100", "question one hundred"]}
      ]
    }
  }
}

댓글