diff --git a/.gitignore b/.gitignore index fbdda19..dcb6b35 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,12 @@ .venv/ +# 기본적으로 output 디렉토리 전체 무시 output/ + +# 예외 처리: 2단계 하위 폴더의 .xlsx 파일은 추적 +!output/*/ +!output/*/*.xlsx + sample/ .DS_Store diff --git a/01_copyFiles.py b/01_copyFiles.py index b3f0655..414ef35 100644 --- a/01_copyFiles.py +++ b/01_copyFiles.py @@ -44,8 +44,8 @@ def copy_ent_files(source_root, target_root): print(f"Copied {source_file_path} to {target_file_path}") # 사용법 -source_directory = r"D:\project\data\CAS_제2506회 정기\답안파일\제2506회 코딩활용능력 2급 답안파일" # 원본 디렉토리 경로 -target_directory = r"./ent/2506_CAS_2_A" +source_directory = r"D:\project\data\CAT_제2507회 정기\채점의뢰" # 원본 디렉토리 경로 +target_directory = r"./ent/2507_CAT_3_A" target_directory_a = r"./output/A" # '1교시'의 타겟 경로 target_directory_b = r"./output/B" # '2교시'의 타겟 경로 target_directory_c = r"./output/C" # '3교시'의 타겟 경로 diff --git a/02_extract_project_json.py b/02_extract_project_json.py index 192e83a..944ef30 100644 --- a/02_extract_project_json.py +++ b/02_extract_project_json.py @@ -50,7 +50,7 @@ def process_ent_files(ent_dir, output_dir): # 실행 예 if __name__ == "__main__": - test_name = "2506_CAS_2_A" + test_name = "2507_CAT_3_A" ent_dir = f".\\ent\\{test_name}" output_dir = f".\\output\\{test_name}" process_ent_files(ent_dir, output_dir) diff --git a/__pycache__/logging_config.cpython-312.pyc b/__pycache__/logging_config.cpython-312.pyc new file mode 100644 index 0000000..086644a Binary files /dev/null and b/__pycache__/logging_config.cpython-312.pyc differ diff --git a/correct/2507_CAT_3_A.json b/correct/2507_CAT_3_A.json new file mode 100644 index 0000000..5c8df3c --- /dev/null +++ b/correct/2507_CAT_3_A.json @@ -0,0 +1,362 @@ +{ + "1-1": { + "type": "scene", + "ele": "$..objects[?(@.name=='실험실')]", + "points": 2, + "desc": "문제 1/교실 뒤(3)/[배경] 이름 설정/이름을 '실험실'으로 변경하기" + }, + "1-2": { + "type": "scene", + "ele": "$..objects[?(@.name=='새로운 약')]", + "points": 2, + "desc": "문제 1/물약(빨강)/[개체] 이름 설정 1/이름을 '새로운 약'으로 변경하기" + }, + "1-4": { + "type": "scene", + "ele": "$..objects[?(@.name=='불')]", + "points": 2, + "desc": "문제 1/불(2)/[개체] 이름 설정 2/이름을 '불'로 변경하기" + }, + "1-5": { + "type": "scene", + "ele": "$..objects[?(@.name=='마법사')]", + "points": 2, + "desc": "문제 1/꼬마 마법사/[개체] 이름 설정 3/이름을 '마법사'로 변경하기" + }, + "1-6": { + "type": "scene", + "ele": "$..objects[?(@.name=='마법의 약')]", + "points": 2, + "desc": "문제 1/마법의 약/[개체] 이름 설정 4/이름 변경 없음" + }, + "2-0": { + "type": "script", + "ele": "$.objects[?(@.name=~'새로운 약|물약')].script", + "blocks": [ + { + "ele": "$[0][0].type", + "answer": "when_run_button_click", + "points": 2.43, + "desc": "문제 2/새로운 약/시작/시작하기 버튼을 클릭했을 때" + }, + { + "ele": [ + "$[0][1].type", + "$[0][1].params[0].params[0]", + "$[0][1].params[1].params[0]" + ], + "answer": ["locate_xy", "50", "5"], + "points": 2.43, + "desc": "문제 2/새로운 약/[시작]의 세부 동작 1/x: '50' y: '5' 위치로 이동하기", + "type": "list" + }, + { + "ele": "$[0][2].params[0].params[0]", + "answer": "60", + "points": 2.43, + "desc": "문제 2/새로운 약/[시작]의 세부 동작 2/크기를 '60' 으로 정하기" + }, + { + "ele": "$[1][0].type", + "answer": "when_object_click", + "points": 2.43, + "desc": "문제 2/새로운 약/오브젝트/오브젝트를 클릭했을 때" + }, + { + "ele": ["$[1][1].type", "$[1][1].params[0]"], + "answer": ["repeat_inf", null], + "points": 2.43, + "desc": "문제 2/새로운 약/반복/계속 반복하기", + "type": "list" + }, + { + "ele": "$[1][1].statements[0][0].type", + "answer": "locate", + "points": 2.43, + "desc": "문제 2/새로운 약/[반복]의 세부 동작/'마우스 포인터' 위치로 이동하기" + } + ] + }, + "3-0": { + "type": "script", + "ele": "$.objects[?(@.name=~'불')].script", + "blocks": [ + { + "ele": "$[0][0].type", + "answer": "when_run_button_click", + "points": 2.43, + "desc": "문제 2/불/시작/시작하기 버튼을 클릭했을 때" + }, + { + "ele": [ + "$[0][1].type", + "$[0][1].params[0].params[0]", + "$[0][1].params[1].params[0]" + ], + "answer": ["locate_xy", "-150", "-30"], + "points": 2.43, + "desc": "문제 2/불/[시작]의 세부 동작 1/x: '-150' y: '-30' 위치로 이동하기", + "type": "list" + }, + { + "ele": "$[0][2].params[0].params[0]", + "answer": "90", + "points": 2.43, + "desc": "문제 2/불/[시작]의 세부 동작 2/크기를 '90' 으로 정하기" + }, + { + "ele": ["$[0][3].type", "$[0][3].params[0]"], + "answer": ["repeat_inf", null], + "points": 2.43, + "desc": "문제 2/불/반복/계속 반복하기", + "type": "list" + }, + { + "ele": [ + "$[0][3].statements[0][0].type", + "$[0][3].statements[0][0].params[0].params[1].params[0]", + "$[0][3].statements[0][0].params[0].params[3].params[0]", + "$[0][3].statements[0][0].params[1].params[1].params[0]", + "$[0][3].statements[0][0].params[1].params[3].params[0]" + ], + "answer": ["locate_xy", "-150", "0", "-10", "40"], + "points": 2.43, + "desc": "문제 2/불/[반복]의 세부 동작 1/x: '-150 부터 0 사이의 무작위 수' y: '-10 부터 40 사이의 무작위 수' 위치로 이동하기", + "type": "list" + }, + { + "ele": "$[0][3].statements[0][1].type", + "answer": "hide", + "points": 2.43, + "desc": "문제 2/불/[반복]의 세부 동작 2/모양 숨기기" + }, + { + "ele": [ + "$[0][3].statements[0][2].type", + "$[0][3].statements[0][2].params[0].type", + "$[0][3].statements[0][2].params[0].params[1].params[0]", + "$[0][3].statements[0][2].params[0].params[3].params[0]" + ], + "answer": ["wait_second", "calc_rand", "1", "2"], + "points": 2.43, + "desc": "문제 2/불/[반복]의 세부 동작 3/'1 부터 2 사이의 무작위 수' 초 기다리기", + "type": "list" + }, + { + "ele": "$[0][3].statements[0][3].type", + "answer": "show", + "points": 2.43, + "desc": "문제 2/불/[반복]의 세부 동작 4/모양 보이기" + }, + { + "ele": [ + "$[0][3].statements[0][4].type", + "$[0][3].statements[0][4].params[0].params[0]" + ], + "answer": ["wait_second", "0.5"], + "points": 2.43, + "desc": "문제 2/불/[반복]의 세부 동작 5/'0.5' 초 기다리기", + "type": "list" + }, + { + "ele": [ + "$[0][3].statements[0][5].type", + "$[0][3].statements[0][5].params[0].type" + ], + "answer": ["_if", "reach_something"], + "points": 2.43, + "desc": "문제 2/불/만일/만일 '새로운 약' 에 닿았는가? 라면", + "type": "list" + }, + { + "ele": "$[0][3].statements[0][5].statements[0][0].params[0].params[0]", + "answer": "300", + "points": 2.43, + "desc": "문제 2/불/[만일]의 세부 동작 1/크기를 '300' 으로 정하기" + }, + { + "ele": [ + "$[0][3].statements[0][5].statements[0][1].type", + "$[0][3].statements[0][5].statements[0][1].params[0].params[0]", + "$[0][3].statements[0][5].statements[0][1].params[1]" + ], + "answer": ["dialog", "마법 실패", "speak"], + "points": 2.43, + "desc": "문제 2/불/[만일]의 세부 동작 2/'마법 실패' 를 '말하기'", + "type": "list" + }, + { + "ele": [ + "$[0][3].statements[0][5].statements[0][2].type", + "$[0][3].statements[0][5].statements[0][2].params[0]" + ], + "answer": ["stop_object", "all"], + "points": 2.43, + "desc": "문제 2/불/[만일]의 세부 동작 3/'모든' 코드 멈추기", + "type": "list" + } + ] + }, + "4-0": { + "type": "script", + "ele": "$.objects[?(@.name=~'마법사')].script", + "blocks": [ + { + "ele": "$[0][0].type", + "answer": "when_run_button_click", + "points": 2.43, + "desc": "문제 2/마법사/시작/시작하기 버튼을 클릭했을 때" + }, + { + "ele": [ + "$[0][1].type", + "$[0][1].params[0].params[0]", + "$[0][1].params[1].params[0]" + ], + "answer": ["locate_xy", "135", "-20"], + "points": 2.43, + "desc": "문제 2/마법사/[시작]의 세부 동작 1/x: '135' y: '-20' 위치로 이동하기", + "type": "list" + }, + { + "ele": "$[0][2].params[0].params[0]", + "answer": "180", + "points": 2.43, + "desc": "문제 2/마법사/[시작]의 세부 동작 2/크기를 '180' 으로 정하기" + }, + { + "ele": "$[0][3].params[*].params", + "answer": ["마법 실험을 해봐야지!", "2"], + "points": 2.43, + "desc": "문제 2/마법사/[시작]의 세부 동작 3/'마법 실험을 해봐야지!' 를 '2' 초 동안 '말하기'" + } + ] + }, + "5-0": { + "type": "script", + "ele": "$.objects[?(@.name=~'마법의 약')].script", + "blocks": [ + { + "ele": "$[0][0].type", + "answer": "when_run_button_click", + "points": 2.43, + "desc": "문제 2/마법의 약/시작/시작하기 버튼을 클릭했을 때" + }, + { + "ele": [ + "$[0][1].type", + "$[0][1].params[0].params[0]", + "$[0][1].params[1].params[0]" + ], + "answer": ["locate_xy", "-40", "10"], + "points": 2.43, + "desc": "문제 2/마법의 약/[시작]의 세부 동작 1/x: '-40' y: '10' 위치로 이동하기", + "type": "list" + }, + { + "ele": "$[0][2].params[0].params[0]", + "answer": "50", + "points": 2.43, + "desc": "문제 2/마법의 약/[시작]의 세부 동작 2/크기를 '50' 으로 정하기" + }, + { + "ele": ["$[0][3].type", "$[0][3].params[0]"], + "answer": ["repeat_inf", null], + "points": 2.43, + "desc": "문제 2/마법의 약/반복/계속 반복하기", + "type": "list" + }, + { + "ele": "$[0][3].statements[0][0].type", + "answer": "hide", + "points": 2.43, + "desc": "문제 2/마법의 약/[반복]의 세부 동작 1/모양 숨기기" + }, + { + "ele": [ + "$[0][3].statements[0][1].type", + "$[0][3].statements[0][1].params[0].params[1].params[0]", + "$[0][3].statements[0][1].params[0].params[3].params[0]" + ], + "answer": ["locate_x", "-160", "60"], + "points": 2.43, + "desc": "문제 2/마법의 약/[반복]의 세부 동작 2/x: '-160 부터 60 사이의 무작위 수' 위치로 이동하기", + "type": "list" + }, + { + "ele": [ + "$[0][3].statements[0][2].type", + "$[0][3].statements[0][2].params[0].params[0]" + ], + "answer": ["wait_second", "2"], + "points": 2.43, + "desc": "문제 2/마법의 약/[반복]의 세부 동작 3/ '2' 초 기다리기", + "type": "list" + }, + { + "ele": "$[0][3].statements[0][3].type", + "answer": "show", + "points": 2.43, + "desc": "문제 2/마법의 약/[반복]의 세부 동작 4/모양 보이기" + }, + { + "ele": [ + "$[0][3].statements[0][4].type", + "$[0][3].statements[0][4].params[0].params[0]" + ], + "answer": ["wait_second", "0.5"], + "points": 2.43, + "desc": "문제 2/마법의 약/[반복]의 세부 동작 5/'0.5' 초 기다리기", + "type": "list" + }, + { + "ele": [ + "$[0][3].statements[0][5].type", + "$[0][3].statements[0][5].params[0].type" + ], + "answer": ["_if", "reach_something"], + "points": 2.43, + "desc": "문제 2/마법의 약/만일/만일 '새로운 약' 에 닿았는가? 라면", + "type": "list" + }, + { + "ele": "$[0][3].statements[0][5].statements[0][0].params[0].params[0]", + "answer": "100", + "points": 2.43, + "desc": "문제 2/마법의 약/[만일]의 세부 동작 1/크기를 '100' 으로 정하기" + }, + { + "ele": [ + "$[0][3].statements[0][5].statements[0][1].type", + "$[0][3].statements[0][5].statements[0][1].params[0]", + "$[0][3].statements[0][5].statements[0][1].params[1].params[0]" + ], + "answer": ["add_effect_amount", "color", "70"], + "points": 2.43, + "desc": "문제 2/마법의 약/[만일]의 세부 동작 2/'색깔' 효과를 '70' 만큼 주기", + "type": "list" + }, + { + "ele": [ + "$[0][3].statements[0][5].statements[0][2].type", + "$[0][3].statements[0][5].statements[0][2].params[0].params[0]", + "$[0][3].statements[0][5].statements[0][2].params[1]" + ], + "answer": ["dialog", "마법 성공", "speak"], + "points": 2.43, + "desc": "문제 2/마법의 약/[만일]의 세부 동작 3/'마법 성공' 을 '말하기'", + "type": "list" + }, + { + "ele": [ + "$[0][3].statements[0][5].statements[0][3].type", + "$[0][3].statements[0][5].statements[0][3].params[0]" + ], + "answer": ["stop_object", "all"], + "points": 2.43, + "desc": "문제 2/마법의 약/[만일]의 세부 동작 4/'모든' 코드 멈추기", + "type": "list" + } + ] + } +} diff --git a/logging_config.py b/logging_config.py new file mode 100644 index 0000000..138e382 --- /dev/null +++ b/logging_config.py @@ -0,0 +1,24 @@ +import logging +import os + +def setup_logging(): + log_dir = "logs" + os.makedirs(log_dir, exist_ok=True) + log_file = os.path.join(log_dir, "cat.log") + + log_format = "[%(asctime)s] [%(levelname)s] [%(module)s:%(lineno)d] %(message)s" + date_format = "%Y-%m-%d %H:%M:%S" + + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.DEBUG) + console_handler.setFormatter(logging.Formatter(log_format, date_format)) + + file_handler = logging.FileHandler(log_file, encoding="utf-8") + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(logging.Formatter(log_format, date_format)) + + logger.addHandler(console_handler) + logger.addHandler(file_handler) \ No newline at end of file diff --git a/logs/cat.log b/logs/cat.log new file mode 100644 index 0000000..0bb2e61 --- /dev/null +++ b/logs/cat.log @@ -0,0 +1,12 @@ +[2025-07-31 15:50:20] [ERROR] [main:381] Error processing ./output/2507_CAT_3_A/2507회코딩활용능력3급A형정답\project.json: cannot access local variable 'student_id' where it is not associated with a value +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 375, in main + points.insert(0, student_id) + ^^^^^^^^^^ +UnboundLocalError: cannot access local variable 'student_id' where it is not associated with a value +[2025-07-31 15:52:50] [ERROR] [main:380] 🚫Error processing ./output/2507_CAT_3_A/2507회코딩활용능력3급A형정답\project.json: cannot access local variable 'student_id' where it is not associated with a value +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 375, in main + points.insert(0, student_id) + ^^^^^^^^^^ +UnboundLocalError: cannot access local variable 'student_id' where it is not associated with a value diff --git a/main.py b/main.py index 973adf1..2221e45 100644 --- a/main.py +++ b/main.py @@ -6,6 +6,11 @@ import pandas as pd # 추가된 import import unicodedata # 상단에 import 추가 import re # 상단에 추가 from datetime import datetime +import logging +from logging_config import setup_logging # logging 설정을 위한 import +import traceback + +setup_logging() # logging 설정 호출 # JSON 파일 읽기 def read_json(file_path): @@ -262,14 +267,11 @@ def process_project(project_data, scoring_data): script_data_1 = json.loads(script_raw) if script_raw else None script_data = swap_script(script_data_1) if script_data_1 else None - if script_data != script_data_1: - print(f"⬜ Script data 순서 변경 ") - block_index = 1 for block in block_list: block_type = block.get('type') block_path = block.get('ele') - block_expected_answer = block.get('answer', None) + block_answer = block.get('answer', None) block_points = block.get('points') if script_data is None: @@ -285,23 +287,23 @@ def process_project(project_data, scoring_data): block_elements = find_element(script_data, block_path) # 결과값 정리 - if block_elements and isinstance(block_expected_answer, list): + if block_elements and isinstance(block_answer, list): found_values = [convert_to_str(x) for x in chain.from_iterable(block_elements)] else: found_values = convert_to_str(block_elements[0]) if block_elements else None - expected_str = convert_to_str(block_expected_answer) if block_expected_answer is not None else None + expected_str = convert_to_str(block_answer) if block_answer is not None else None # 비교 및 점수 처리 if block_elements: - if block_expected_answer is not None and expected_str != found_values: + if block_answer is not None and expected_str != found_values: print(f"{question_key}-{block_index}: ❌ {expected_str} != {found_values}") score_list.append(0) - elif block_expected_answer is not None and expected_str == found_values: + elif block_answer is not None and expected_str == found_values: print(f"{question_key}-{block_index}: ✅ {expected_str} == {found_values}") total_points += block_points score_list.append(block_points) - elif block_expected_answer is None: + elif block_answer is None: total_points += block_points score_list.append(block_points) print(f"{question_key}-{block_index}: Element Exists") @@ -311,6 +313,7 @@ def process_project(project_data, scoring_data): block_index += 1 + # total_points = round(total_points, ndigits=0) # 총점 반올림 score_list.append(total_points) return score_list @@ -321,9 +324,9 @@ def normalize_path(path): def main(): # 파일 경로 설정 - # project_json_path = './output/2506_CAS_2_A/' - project_json_path = './output/00_test/' - scoring_json_path = './correct/2506_CAS_2_A.json' + project_json_path = './output/2507_CAT_3_A/' + # project_json_path = './output/00_test/' + scoring_json_path = './correct/2507_CAT_3_A.json' scoring_data = read_json(scoring_json_path) student_score_list = [] @@ -363,9 +366,14 @@ def main(): # 디렉토리 패스 내에 학생 이름만 뽑아서 엑셀 컬럼 명으로 추가 # output/cas-000040-이지원/temp/project.json # student_id = normalize_path(full_path.split('/')[3]) - match = re.search(r'(\d{6}-[^\\/]+)[\\/]', full_path) + match = re.search(r'(\d{6}[-_][^\\/]+)[\\/]', full_path) if match: student_id = match.group(1) + else: + if '정답' in full_path: + student_id = '정답' + else: + student_id = '000000' # project.json 파일 내용 project_data = read_json(full_path) @@ -374,7 +382,8 @@ def main(): student_score_list.append(points) print(f"Total Points for {points}") except Exception as e: - print(f"Error processing {full_path}: {str(e)}") + # print(traceback.format_exc()) + logging.exception(f"🚫Error processing {full_path}: {str(e)}") continue # DataFrame 생성 및 엑셀 저장 diff --git a/시험자료/2507/2507회 코딩활용능력 3급 A형 문제.pdf b/시험자료/2507/2507회 코딩활용능력 3급 A형 문제.pdf new file mode 100644 index 0000000..e78c7b2 Binary files /dev/null and b/시험자료/2507/2507회 코딩활용능력 3급 A형 문제.pdf differ diff --git a/시험자료/2507/2507회 코딩활용능력 3급 A형 정답.ent b/시험자료/2507/2507회 코딩활용능력 3급 A형 정답.ent new file mode 100644 index 0000000..147672c Binary files /dev/null and b/시험자료/2507/2507회 코딩활용능력 3급 A형 정답.ent differ diff --git a/시험자료/2507/2507회 코딩활용능력 3급 A형 채점기준표.xlsx b/시험자료/2507/2507회 코딩활용능력 3급 A형 채점기준표.xlsx new file mode 100644 index 0000000..1cda9df Binary files /dev/null and b/시험자료/2507/2507회 코딩활용능력 3급 A형 채점기준표.xlsx differ