diff --git a/.bashrc.local b/.bashrc.local new file mode 100644 index 0000000..910d1bf --- /dev/null +++ b/.bashrc.local @@ -0,0 +1,4 @@ +# 프로젝트 루트 폴더/.bashrc.local +if [ -f .venv/Scripts/activate ]; then + source .venv/Scripts/activate +fi \ No newline at end of file diff --git a/.gitignore b/.gitignore index d551d13..fbdda19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,20 @@ +.venv/ + output/ sample/ .DS_Store + +# 시험자료와 그 하위 회차 폴더는 포함 +!시험자료/ +!시험자료/*/ +# 기본적으로 모든 것을 무시 +시험자료/*/* +# 하지만 회차 폴더 안의 파일은 다시 포함 +!시험자료/*/*.* +# 모든 회차 폴더 안의 하위 폴더는 무시 +시험자료/*/*/ + +# ent파일 +ent/ + diff --git a/copyFiles.py b/01_copyFiles.py similarity index 84% rename from copyFiles.py rename to 01_copyFiles.py index 2c0d084..b3f0655 100644 --- a/copyFiles.py +++ b/01_copyFiles.py @@ -34,16 +34,18 @@ def copy_ent_files(source_root, target_root): for root, dirs, files in os.walk(source_root): for file in files: if file.endswith('.ent'): + space_remove_file = file.replace(" ","") + source_file_path = os.path.join(root, file) - target_file_path = os.path.join(target_root, file) + target_file_path = os.path.join(target_root, space_remove_file) # 파일 복사 shutil.copy2(source_file_path, target_file_path) print(f"Copied {source_file_path} to {target_file_path}") # 사용법 -source_directory = r"/Users/waterdrw/Downloads/제2505회 코딩활용능력 3급 답안파일" # 원본 디렉토리 경로 -target_directory = r"./output/2505CAT3A" +source_directory = r"D:\project\data\CAS_제2506회 정기\답안파일\제2506회 코딩활용능력 2급 답안파일" # 원본 디렉토리 경로 +target_directory = r"./ent/2506_CAS_2_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 new file mode 100644 index 0000000..192e83a --- /dev/null +++ b/02_extract_project_json.py @@ -0,0 +1,56 @@ +import os +import gzip +import tarfile +import json +from io import BytesIO + +def extract_project_json_from_gz(filepath, output_folder): + # 1. .gz 압축 해제 → tar 형식 데이터 얻기 + with gzip.open(filepath, 'rb') as gz_file: + tar_data = gz_file.read() + + # 2. tar 데이터를 메모리에서 처리 + tar_bytes = BytesIO(tar_data) + + with tarfile.open(fileobj=tar_bytes, mode='r:') as tar: + try: + target = tar.getmember("temp/project.json") + except KeyError: + print(f"❌ {os.path.basename(filepath)}: temp/project.json 없음") + return + + # 압축파일 내부 temp/project.json 파일의 파일 오브젝트 추출 + extracted = tar.extractfile(target) + if not extracted: + print(f"❌ {os.path.basename(filepath)}: 파일 추출 실패") + return + + # 파일 오브젝트를 JSON 객체로 로드 + project_data = json.load(extracted) + + # 해당 경로에 project.json 파일로 저장 + os.makedirs(output_folder, exist_ok=True) + output_json_path = os.path.join(output_folder, "project.json") + with open(output_json_path, "w", encoding="utf-8") as out_file: + json.dump(project_data, out_file, ensure_ascii=False, indent=2) + + print(f"✅ 처리 완료: {filepath} → {output_json_path}") + +def process_ent_files(ent_dir, output_dir): + for filename in os.listdir(ent_dir): + if filename.lower().endswith((".ent")): + filename = filename.replace(" ","") + filepath = os.path.join(ent_dir, filename) + output_root = output_dir + output_folder = os.path.join(output_root, os.path.splitext(filename)[0]) + try: + extract_project_json_from_gz(filepath, output_folder) + except Exception as e: + print(f"❌ 오류 발생 ({filename}): {e}") + +# 실행 예 +if __name__ == "__main__": + test_name = "2506_CAS_2_A" + ent_dir = f".\\ent\\{test_name}" + output_dir = f".\\output\\{test_name}" + process_ent_files(ent_dir, output_dir) diff --git a/Entry-Scoring.code-workspace b/Entry-Scoring.code-workspace new file mode 100644 index 0000000..876a149 --- /dev/null +++ b/Entry-Scoring.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/scoring.json b/_scoring.json similarity index 100% rename from scoring.json rename to _scoring.json diff --git a/scripts.json b/_scripts.json similarity index 100% rename from scripts.json rename to _scripts.json diff --git a/correct/2506_CAS_2_A.json b/correct/2506_CAS_2_A.json index ea9332e..0e44a46 100644 --- a/correct/2506_CAS_2_A.json +++ b/correct/2506_CAS_2_A.json @@ -42,10 +42,10 @@ "sort": 16 }, "1-7": { + "type": "scene", "ele": "$..variables[?(@.name=='물고기 수')]", "points": 1, "desc": "문제 2/빨간 물고기/변수/'물고기 수' 변수 만들기", - "type": "scene", "sort": 101 }, "2-0": { diff --git a/main.py b/main.py index 4d94754..973adf1 100644 --- a/main.py +++ b/main.py @@ -5,10 +5,7 @@ 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' +from datetime import datetime # JSON 파일 읽기 def read_json(file_path): @@ -18,7 +15,11 @@ def read_json(file_path): # 요소 탐색 함수 def find_element(project_data, jsonpath_expr): jsonpath_expr = parse(jsonpath_expr) - return [match.value for match in jsonpath_expr.find(project_data)] + result = [] + for match in jsonpath_expr.find(project_data): + value = match.value + result.append(value) + return result # 요소 탐색 함수 def find_script_element(project_data, jsonpath_expr): @@ -43,6 +44,8 @@ def find_list_element(data, jsonpath_expr_list): # 스크립트 채점 진행 전 스크립트 블럭 순서가 when_run_button_click 1번째, when_clone_start 2번째 배열에 없으면 # 리스트 순서 스왑해서 각각 0, 1번 순서로 배치될 수 있도록 함 # when_scene_start 1번째, when_message_cast 2번째 동일한 순서로 리스트 순서 스왑해서 정렬 +# when_scene_start = [장면이 시작되었을 때] +# when_message_cast = ["성공"신호를 받았을 때] def swap_script(origin): """스크립트 블록 순서 정렬 함수""" if not origin or len(origin) == 0: @@ -127,101 +130,201 @@ def convert_to_str(value): return [convert_to_str(v) for v in value] return str(value) -def process_project(project_data, scoring_data): +# def process_project(project_data, scoring_data): total_points = 0 - points = [] + point_list = [] - for key, value in scoring_data.items(): - ele = value.get('ele') - type = value.get('type') - blocks = value.get('blocks') - answer = value.get('answer') + for key, question_info in scoring_data.items(): + element_path = question_info.get('ele') + type = question_info.get('type') + block_list = question_info.get('blocks') + answer = question_info.get('answer') + points = question_info.get('points') print(f"example: {key}") if type == "scene": - exists = find_element(project_data, ele) - if exists: - print(f"elements found for {ele}") + found_elements = find_element(project_data, element_path) + if found_elements: + print(f"🟨 Elements found for {element_path}") # scene type의 경우 문자열 변환 - exists = [convert_to_str(x) for x in exists] - if exists == answer: - total_points += value.get('points') - points.append(value.get('points')) + found_elements = [convert_to_str(x) for x in found_elements] + if found_elements == answer: + total_points += points + point_list.append(points) - elif exists and answer == None: - total_points += value.get('points') - points.append(value.get('points')) - print(f"{ele} found "); + elif found_elements and answer == None: + total_points += points + point_list.append(points) + print(f"{element_path} found ") else: - points.append(0) - print(f"{exists} not found"); + point_list.append(0) + print(f"{found_elements} not found") else: - print(f"Element '{ele}' not found") - points.append(0) + print(f"🟥 Element '{element_path}' not found") + point_list.append(0) + # 스크립트 타입의 경우 + # 스크립트 블록을 찾고, 블록 내의 요소를 확인 + # 블록 내의 요소를 확인하고, 정답과 비교하여 점수 계산 if type == "script": - exists = find_script_element(project_data, ele) + script_raw = find_script_element(project_data, element_path) - if exists == None: - temp = None + if script_raw == None: + script_data = None else: - temp = json.loads(exists) - temp = swap_script(temp) + script_data = json.loads(script_raw) + script_data = swap_script(script_data) - innerKey = 1 - for block in blocks: - innerType = block.get('type') + block_index = 1 + for block in block_list: + block_type = block.get('type') - if temp == None: - print(f"{key}-{innerKey}: Script Not exist") - points.append("확인 필요") + if script_data == None: + print(f"{key}-{block_index}: Script Not exist") + point_list.append("확인 필요") continue - elif innerType == "list": - block_exists = find_list_element(temp, block.get('ele')) + elif block_type == "list": + block_elements = find_list_element(script_data, block.get('ele')) else: - block_exists = find_element(temp, block.get('ele')) + block_elements = find_element(script_data, block.get('ele')) answer = block.get('answer', None) - if block_exists and isinstance(answer, list): + if block_elements and isinstance(answer, list): # 리스트의 모든 요소를 문자열로 변환 - flat_matches = [convert_to_str(x) for x in list(chain.from_iterable(block_exists))] + flat_matches = [convert_to_str(x) for x in list(chain.from_iterable(block_elements))] else: - if not block_exists: + if not block_elements: flat_matches = None else: # 단일 값을 문자열로 변환 - flat_matches = convert_to_str(block_exists[0]) + flat_matches = convert_to_str(block_elements[0]) - if block_exists: + if block_elements: # 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) + print(f"{key}-{block_index}: {str_answer} != {flat_matches}") + point_list.append(0) elif answer is not None and str_answer == flat_matches: - print(f"{key}-{innerKey}: {str_answer} == {flat_matches}") + print(f"{key}-{block_index}: {str_answer} == {flat_matches}") total_points += block.get('points') - points.append(block.get('points')) - elif answer is None and block_exists: + point_list.append(block.get('points')) + elif answer is None and block_elements: total_points += block.get('points') - points.append(block.get('points')) - print(f"{key}-{innerKey}: exist ele: {block_exists}") + point_list.append(block.get('points')) + print(f"{key}-{block_index}: exist ele: {block_elements}") else: - print(f"No elements found for {block.get('ele')}") - points.append(0) + print(f"No elements found for {block.get('ele')}") + point_list.append(0) - innerKey = innerKey + 1 + block_index = block_index + 1 - points.append(total_points) - return points + point_list.append(total_points) + return point_list + +def process_project(project_data, scoring_data): + total_points = 0 + score_list = [] + + for question_key, question_info in scoring_data.items(): + element_path = question_info.get('ele') + question_type = question_info.get('type') + block_list = question_info.get('blocks') + expected_answer = question_info.get('answer') + question_points = question_info.get('points') + + print(f"▶ Processing question: {question_key}") + + # ✅ SCENE TYPE 처리 + if question_type == "scene": + scene_elements = find_element(project_data, element_path) + + if scene_elements: + print(f"🟨 Found elements for '{element_path}'") + scene_elements = [convert_to_str(x) for x in scene_elements] + + if scene_elements == expected_answer or (expected_answer is None and scene_elements): + total_points += question_points + score_list.append(question_points) + # print(f"✅ Matched: {scene_elements}") + else: + score_list.append(0) + print(f"❌ Mismatch: {scene_elements} vs {expected_answer}") + else: + print(f"🟥 Element '{element_path}' not found") + score_list.append(0) + + # ✅ SCRIPT TYPE 처리 + elif question_type == "script": + script_raw = find_script_element(project_data, element_path) + 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_points = block.get('points') + + if script_data is None: + print(f"{question_key}-{block_index}: Script Not Found") + score_list.append("확인 필요") + block_index += 1 + continue + + # 블록 요소 검색 + if block_type == "list": + block_elements = find_list_element(script_data, block_path) + else: + block_elements = find_element(script_data, block_path) + + # 결과값 정리 + if block_elements and isinstance(block_expected_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 + + # 비교 및 점수 처리 + if block_elements: + if block_expected_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: + 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: + total_points += block_points + score_list.append(block_points) + print(f"{question_key}-{block_index}: Element Exists") + else: + print(f"{question_key}-{block_index}: No elements found for {block_path}") + score_list.append(0) + + block_index += 1 + + score_list.append(total_points) + return score_list def normalize_path(path): """한글 경로명을 NFC 방식으로 정규화""" + return unicodedata.normalize('NFC', 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' + scoring_data = read_json(scoring_json_path) student_score_list = [] @@ -259,7 +362,12 @@ def main(): try: # 디렉토리 패스 내에 학생 이름만 뽑아서 엑셀 컬럼 명으로 추가 # output/cas-000040-이지원/temp/project.json - student_id = normalize_path(full_path.split('/')[3]) + # student_id = normalize_path(full_path.split('/')[3]) + match = re.search(r'(\d{6}-[^\\/]+)[\\/]', full_path) + if match: + student_id = match.group(1) + + # project.json 파일 내용 project_data = read_json(full_path) points = process_project(project_data, scoring_data) points.insert(0, student_id) @@ -274,7 +382,9 @@ def main(): df.columns = df.iloc[0] df = df[1:] - excel_path = project_json_path + '/' + 'results.xlsx' + timestamp = datetime.now().strftime("%y%m%d") + + excel_path = f'{project_json_path}/{timestamp}_results.xlsx' df.to_excel(excel_path, index=False) print(f"\nResults saved to {excel_path}") diff --git a/scorer.py b/scorer.py index 78cc745..2f94bb0 100644 --- a/scorer.py +++ b/scorer.py @@ -41,7 +41,7 @@ def calculate_points(project_data, scoring_data): return total_points def main(): - project_data = load_json('sample/제2410회 코딩활용능력 2급 B형 정답/project.json') + project_data = load_json(r'D:\project\Entry\Entry-Scoring\sample\제2506회 코딩활용능력 2급 A형 정답\temp\project.json') scoring_data = load_json('scoring.json') total_points = calculate_points(project_data, scoring_data) diff --git a/시험자료/2506/제2506회 코딩활용능력 2급 A형 문제.hwp b/시험자료/2506/제2506회 코딩활용능력 2급 A형 문제.hwp new file mode 100644 index 0000000..7447903 Binary files /dev/null and b/시험자료/2506/제2506회 코딩활용능력 2급 A형 문제.hwp differ diff --git a/시험자료/2506/제2506회 코딩활용능력 2급 A형 정답.ent b/시험자료/2506/제2506회 코딩활용능력 2급 A형 정답.ent new file mode 100644 index 0000000..f2267a9 Binary files /dev/null and b/시험자료/2506/제2506회 코딩활용능력 2급 A형 정답.ent differ diff --git a/시험자료/2506/제2506회 코딩활용능력 2급 A형 채점기준표.xlsx b/시험자료/2506/제2506회 코딩활용능력 2급 A형 채점기준표.xlsx new file mode 100644 index 0000000..2ff5f01 Binary files /dev/null and b/시험자료/2506/제2506회 코딩활용능력 2급 A형 채점기준표.xlsx differ