from jsonpath_ng.ext import parse import json from itertools import chain import os import pandas as pd # 추가된 import import unicodedata # 상단에 import 추가 import re # 상단에 추가 # 파일 경로 설정 project_json_path = './output/2506_CAS_2_A/' scoring_json_path = './correct/2506_CAS_2_A.json' # JSON 파일 읽기 def read_json(file_path): with open(file_path, 'r', encoding='utf-8') as file: return json.load(file) # 요소 탐색 함수 def find_element(project_data, jsonpath_expr): jsonpath_expr = parse(jsonpath_expr) return [match.value for match in jsonpath_expr.find(project_data)] # 요소 탐색 함수 def find_script_element(project_data, jsonpath_expr): jsonpath_expr = parse(jsonpath_expr) match = jsonpath_expr.find(project_data) if not match: return None return match[0].value # jsonpath_expr_list 로 넘어온 jsonpath들을 하나씩 parse 해주고 결과를 result 리스트로 반환 def find_list_element(data, jsonpath_expr_list): result = [] for jsonpath_expr in jsonpath_expr_list: jsonpath_expr = parse(jsonpath_expr) result.append([match.value for match in jsonpath_expr.find(data)]) return result # 스크립트 채점 진행 전 스크립트 블럭 순서가 when_run_button_click 1번째, when_clone_start 2번째 배열에 없으면 # 리스트 순서 스왑해서 각각 0, 1번 순서로 배치될 수 있도록 함 # when_scene_start 1번째, when_message_cast 2번째 동일한 순서로 리스트 순서 스왑해서 정렬 def swap_script(origin): """스크립트 블록 순서 정렬 함수""" if not origin or len(origin) == 0: return origin # 스크립트 블록 분류를 위한 임시 저장소 run_button_blocks = [] key_press_blocks = [] clone_start_block = None message_cast_block = [] other_blocks = [] # 각 블록을 타입별로 분류 for i, block in enumerate(origin): if not block or len(block) == 0: continue block_type = block[0].get("type") # # 기존 로직: run_button과 scene_start 처리 # if (block_type == "when_run_button_click" or block_type == "when_scene_start") and i > 0: # print(f"swap script run button or scene start") # swap = origin[0] # origin[0] = origin[i] # origin[i] = swap # # 기존 로직: clone_start와 message_cast 처리 # elif (block_type == "when_clone_start" or block_type == "when_message_cast") and i > 0: # print(f"swap script clone start or msg cast") # swap = origin[1] # origin[1] = origin[i] # origin[i] = swap # 새로운 로직: 키 이벤트 처리 if block_type == "when_run_button_click": run_button_blocks.append(block) elif block_type == "when_some_key_pressed": # params[1]에서 키 값 확인 (49, 50, 51) 아스키코드 값이므로 필요하면 추가 가능 key_value = block[0].get("params")[1] if key_value in ["49", "50", "51"]: key_press_blocks.append((key_value, block)) elif block_type == "when_clone_start": clone_start_block = block elif block_type == "when_message_cast": message_cast_block.append(block) else: other_blocks.append(block) # 결과 배열 재구성 result = [] # 1. when_run_button_click 블록을 첫 번째로 추가 if run_button_blocks: result.extend(run_button_blocks) # 2. when_some_key_pressed 블록들을 키 값 순서대로 정렬하여 추가 key_press_blocks.sort(key=lambda x: int(x[0]) if x[0] else 0) result.extend([block for _, block in key_press_blocks]) # 3. clone_start와 message_cast 블록 추가 if clone_start_block: result.append(clone_start_block) if message_cast_block: result.extend(message_cast_block) # 4. 나머지 블록 추가 result.extend(other_blocks) # 결과가 비어있으면 원본 반환 return result if result else origin def clean_string(text): """문자열 끝의 . 또는 ! 제거""" if isinstance(text, str): # 문자열 끝의 . 또는 ! 제거 return re.sub(r'', '', text.strip()) return text def convert_to_str(value): """값을 문자열로 변환""" if isinstance(value, (list, tuple)): return [convert_to_str(v) for v in value] return str(value) def process_project(project_data, scoring_data): total_points = 0 points = [] for key, value in scoring_data.items(): ele = value.get('ele') type = value.get('type') blocks = value.get('blocks') answer = value.get('answer') print(f"example: {key}") if type == "scene": exists = find_element(project_data, ele) if exists: print(f"elements found for {ele}") # scene type의 경우 문자열 변환 exists = [convert_to_str(x) for x in exists] if exists == answer: total_points += value.get('points') points.append(value.get('points')) elif exists and answer == None: total_points += value.get('points') points.append(value.get('points')) print(f"{ele} found "); else: points.append(0) print(f"{exists} not found"); else: print(f"Element '{ele}' not found") points.append(0) if type == "script": exists = find_script_element(project_data, ele) if exists == None: temp = None else: temp = json.loads(exists) temp = swap_script(temp) innerKey = 1 for block in blocks: innerType = block.get('type') if temp == None: print(f"{key}-{innerKey}: Script Not exist") points.append("확인 필요") continue elif innerType == "list": block_exists = find_list_element(temp, block.get('ele')) else: block_exists = find_element(temp, block.get('ele')) answer = block.get('answer', None) if block_exists and isinstance(answer, list): # 리스트의 모든 요소를 문자열로 변환 flat_matches = [convert_to_str(x) for x in list(chain.from_iterable(block_exists))] else: if not block_exists: flat_matches = None else: # 단일 값을 문자열로 변환 flat_matches = convert_to_str(block_exists[0]) if block_exists: # answer도 문자열로 변환하여 비교 str_answer = convert_to_str(answer) if answer is not None else None if answer is not None and str_answer != flat_matches: print(f"{key}-{innerKey}: {str_answer} != {flat_matches}") points.append(0) elif answer is not None and str_answer == flat_matches: print(f"{key}-{innerKey}: {str_answer} == {flat_matches}") total_points += block.get('points') points.append(block.get('points')) elif answer is None and block_exists: total_points += block.get('points') points.append(block.get('points')) print(f"{key}-{innerKey}: exist ele: {block_exists}") else: print(f"No elements found for {block.get('ele')}") points.append(0) innerKey = innerKey + 1 points.append(total_points) return points def normalize_path(path): """한글 경로명을 NFC 방식으로 정규화""" return unicodedata.normalize('NFC', path) def main(): scoring_data = read_json(scoring_json_path) student_score_list = [] # 컬럼명 생성 columns = ['학생명'] idx = 1 for key in scoring_data.keys(): if scoring_data[key].get('type') == 'scene': columns.append(f'{idx}') idx = idx + 1 elif scoring_data[key].get('type') == 'script': for i in range(len(scoring_data[key].get('blocks', []))): columns.append(f'{idx}') idx = idx + 1 columns.append('총점') # os.walk 결과를 리스트로 변환하고 정렬 walk_results = [] for root, dirs, files in os.walk(project_json_path): # 디렉토리명 정규화 normalized_root = normalize_path(root) normalized_dirs = [normalize_path(d) for d in dirs] normalized_files = [normalize_path(f) for f in files] normalized_dirs.sort() # 정규화된 디렉토리 정렬 walk_results.append((normalized_root, normalized_dirs, normalized_files)) # 정렬된 결과를 바탕으로 처리 for root, dirs, files in sorted(walk_results): for file in sorted(files): # 파일도 정렬 if file == 'project.json': full_path = os.path.join(root, file) print(f"\nProcessing: {full_path}") try: # 디렉토리 패스 내에 학생 이름만 뽑아서 엑셀 컬럼 명으로 추가 # output/cas-000040-이지원/temp/project.json student_id = normalize_path(full_path.split('/')[3]) project_data = read_json(full_path) points = process_project(project_data, scoring_data) points.insert(0, student_id) student_score_list.append(points) print(f"Total Points for {points}") except Exception as e: print(f"Error processing {full_path}: {str(e)}") continue # DataFrame 생성 및 엑셀 저장 df = pd.DataFrame(student_score_list, columns=columns).transpose() df.columns = df.iloc[0] df = df[1:] excel_path = project_json_path + '/' + 'results.xlsx' df.to_excel(excel_path, index=False) print(f"\nResults saved to {excel_path}") if __name__ == "__main__": main()