diff --git a/01_copyFiles.py b/01_copyFiles.py index 5cfff00..f5e2892 100644 --- a/01_copyFiles.py +++ b/01_copyFiles.py @@ -47,8 +47,8 @@ def copy_ent_files(source_root, target_root): # 사용법 -source_directory = r"D:\project\Entry\Entry-Scoring\시험자료\2509\A" # 원본 디렉토리 경로 -target_directory = r".\ent\2509_CAT_3_A" +source_directory = r"D:\project\data\CAS_제2510회 정기\제2510회 코딩활용능력 2급 정기 답안파일" # 원본 디렉토리 경로 +target_directory = r".\ent\2510_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 index 201daef..5b78f66 100644 --- a/02_extract_project_json.py +++ b/02_extract_project_json.py @@ -50,8 +50,9 @@ def process_ent_files(ent_dir, output_dir): # 실행 예 if __name__ == "__main__": - test_names = ["2509_CAT_3_A"] + # test_names = ["2509_CAT_3_A"] # test_names = ["2508_CAS_2_A","2508_CAS_2_B"] + test_names = ["2510_CAS_2_A"] for test_name in test_names: ent_dir = f".\\ent\\{test_name}" output_dir = f".\\output\\{test_name}" diff --git a/251002_2509_CAT_3_A_채점결과.xlsx b/251002_2509_CAT_3_A_채점결과.xlsx new file mode 100644 index 0000000..8d782af Binary files /dev/null and b/251002_2509_CAT_3_A_채점결과.xlsx differ diff --git a/251030_2510_CAS_2_A_채점결과.xlsx b/251030_2510_CAS_2_A_채점결과.xlsx new file mode 100644 index 0000000..9017284 Binary files /dev/null and b/251030_2510_CAS_2_A_채점결과.xlsx differ diff --git a/251031_2510_CAS_2_A_채점결과.xlsx b/251031_2510_CAS_2_A_채점결과.xlsx new file mode 100644 index 0000000..ffea7a3 Binary files /dev/null and b/251031_2510_CAS_2_A_채점결과.xlsx differ diff --git a/251103_2510_CAS_2_A_채점결과.xlsx b/251103_2510_CAS_2_A_채점결과.xlsx new file mode 100644 index 0000000..051a67f Binary files /dev/null and b/251103_2510_CAS_2_A_채점결과.xlsx differ diff --git a/251104_2510_CAS_2_A_채점결과.xlsx b/251104_2510_CAS_2_A_채점결과.xlsx new file mode 100644 index 0000000..8472535 Binary files /dev/null and b/251104_2510_CAS_2_A_채점결과.xlsx differ diff --git a/251105_2510_CAS_2_A_TEST.xlsx b/251105_2510_CAS_2_A_TEST.xlsx new file mode 100644 index 0000000..ad026c1 Binary files /dev/null and b/251105_2510_CAS_2_A_TEST.xlsx differ diff --git a/251105_2510_CAS_2_A_채점결과.xlsx b/251105_2510_CAS_2_A_채점결과.xlsx new file mode 100644 index 0000000..c22c57b Binary files /dev/null and b/251105_2510_CAS_2_A_채점결과.xlsx differ diff --git a/correct/2510_CAS_2_A.json b/correct/2510_CAS_2_A.json new file mode 100644 index 0000000..0107be5 --- /dev/null +++ b/correct/2510_CAS_2_A.json @@ -0,0 +1,1008 @@ +{ + "1-1": { + "type": "scene", + "ele": "$..objects[?(@.name=='별이 빛나는 숲')]", + "point": 1.5, + "desc": "문제 1/장면 1/[배경] 이름 설정 1/이름 변경 없음", + "sort": 11 + }, + "1-2": { + "type": "scene", + "ele": "$..objects[?(@.name=='우주')]", + "point": 1.7, + "desc": "문제 1/장면 2/[배경] 이름 설정 2/이름을 '우주'로 변경하기", + "sort": 12 + }, + "1-3": { + "type": "scene", + "ele": "$..objects[?(@.name=='번개')]", + "point": 1.7, + "desc": "문제 1/번개(1)/[개체] 이름 설정 1/이름을 '번개'로 변경하기", + "sort": 13 + }, + "1-4": { + "type": "scene", + "ele": "$..objects[?(@.name=='별')]", + "point": 1.7, + "desc": "문제 1/큰별(빨강)/[개체] 이름 설정 2/이름을 '별'로 변경하기", + "sort": 14 + }, + "1-5": { + "type": "scene", + "ele": "$..objects[?(@.name=='공')]", + "point": 1.7, + "desc": "문제 1/신호/[개체] 이름 설정 3/이름을 '공'으로 변경하기", + "sort": 15 + }, + "1-6": { + "type": "scene", + "ele": "$..objects[?(@.name=='막대')]", + "point": 1.7, + "desc": "문제 1/진행 상태 바/[개체] 이름 설정 4/이름을 '막대'로 변경하기", + "sort": 16 + }, + "1-0": { + "ele": "$.messages[?(@.name=='게임시작')]", + "point": 0.95, + "desc": "문제 2/번개/신호/'게임시작' 신호 만들기", + "type": "scene", + "sort": 101 + }, + "2-0": { + "ele": "$.messages[?(@.name=='미션성공')]", + "point": 0.95, + "desc": "문제 2/번개/신호/'미션성공' 신호 만들기", + "type": "scene", + "sort": 102 + }, + "3-0": { + "ele": "$.variables[?(@.name=='점수')]", + "point": 0.95, + "desc": "문제 2/번개/변수/'점수' 변수 만들기", + "type": "scene", + "sort": 102 + }, + "4-0": { + "type": "script", + "ele": "$.objects[?(@.name=~'번개')].script", + "blocks": [ + { + "ele": "$[0][0].type", + "answer": "when_run_button_click", + "point": 0.95, + "desc": "문제 2/번개/시작/시작하기 버튼을 클릭했을 때" + }, + { + "ele": [ + "$[0][1].type", + "$[0][1].params[0].params[0]" + ], + "answer": [ + "set_scale_size", + 30 + ], + "point": 0.95, + "desc": "문제 2/번개/[시작]의 세부 동작 1/크기를 '30' 으로 정하기", + "type": "list" + }, + { + "ele": [ + "$[0][2].type", + "$[0][2].params[0].params[0]", + "$[0][2].params[1].params[0]" + ], + "answer": [ + "locate_xy", + "-205", + 105 + ], + "point": 0.95, + "desc": "문제 2/번개/[시작]의 세부 동작 2/x: '-205' y: '105' 위치로 이동하기", + "type": "list" + }, + { + "ele": "$[0][3].type", + "answer": "hide", + "point": 0.95, + "desc": "문제 2/번개/[시작]의 세부 동작 3/모양 숨기기" + }, + { + "ele": "$[1][0].type", + "answer": "when_message_cast", + "point": 0.95, + "desc": "문제 2/번개/신호/'게임시작' 신호를 받았을 때" + }, + { + "ele": [ + "$[1][1].type", + "$[1][1].params[0]" + ], + "answer": [ + "repeat_inf", + null + ], + "point": 0.95, + "desc": "문제 2/번개/반복/계속 반복하기", + "type": "list" + }, + { + "ele": [ + "$[1][1].statements[0][0].type", + "$[1][1].statements[0][0].params[0].params[0]" + ], + "answer": [ + "repeat_basic", + 3 + ], + "point": 0.95, + "desc": "문제 2/번개/반복/'3' 번 반복하기", + "type": "list" + }, + { + "ele": [ + "$[1][1].statements[0][0].statements[0][0].type", + "$[1][1].statements[0][0].statements[0][0].params[0]" + ], + "answer": [ + "create_clone", + "self" + ], + "point": 0.95, + "desc": "문제 2/번개/[반복]의 세부 동작 1/'자신' 의 복제본 만들기", + "type": "list" + }, + { + "ele": [ + "$[1][1].statements[0][1].type", + "$[1][1].statements[0][1].params[0].params[0]" + ], + "answer": [ + "wait_second", + 3 + ], + "point": 0.95, + "desc": "문제 2/번개/반복/'3' 초 기다리기", + "type": "list" + }, + { + "ele": "$[2][0].type", + "answer": "when_clone_start", + "point": 0.95, + "desc": "문제 2/번개/복제본/복제본이 처음 생성되었을 때" + }, + { + "ele": [ + "$[2][1].type", + "$[2][1].params[0].params[1].params[0]", + "$[2][1].params[0].params[3].params[0]", + "$[2][1].params[1].params[0]" + ], + "answer": [ + "locate_xy", + "-220", + 220, + 150 + ], + "point": 0.95, + "desc": "문제 2/번개/[복제본]의 세부 동작1/x: '-220 부터 220 사이의 무작위 수' y: '150' 위치로 이동하기", + "type": "list" + }, + { + "ele": "$[2][2].type", + "answer": "show", + "point": 0.95, + "desc": "문제 2/번개/[복제본]의 세부 동작2/모양 보이기" + }, + { + "ele": [ + "$[2][3].type", + "$[2][3].params[0]" + ], + "answer": [ + "repeat_inf", + null + ], + "point": 0.95, + "desc": "문제 2/번개/반복/계속 반복하기", + "type": "list" + }, + { + "ele": [ + "$[2][3].statements[0][0].type", + "$[2][3].statements[0][0].params[0].params[1].params[0]", + "$[2][3].statements[0][0].params[0].params[3].params[0]" + ], + "answer": [ + "move_y", + "-1", + "-2" + ], + "point": 0.95, + "desc": "문제 2/번개/[반복]의 세부 동작 1/y 좌표를 '-1 부터 -2 사이의 무작위 수' 만큼 바꾸기", + "type": "list" + }, + { + "ele": [ + "$[2][3].statements[0][1].type", + "$[2][3].statements[0][1].params[0].type" + ], + "answer": [ + "_if", + "reach_something" + ], + "point": 0.95, + "desc": "문제 2/번개/만일/만일 '공' 에 닿았는가? 라면", + "type": "list" + }, + { + "ele": [ + "$[2][3].statements[0][1].statements[0][0].type", + "$[2][3].statements[0][1].statements[0][0].params[1].params[0]" + ], + "answer": [ + "change_variable", + "-2" + ], + "point": 0.95, + "desc": "문제 2/번개/[만일]의 세부 동작 1/'점수' 에 '-2' 만큼 더하기", + "type": "list" + }, + { + "ele": [ + "$[2][3].statements[0][1].statements[0][1].type", + "$[2][3].statements[0][1].statements[0][1].params[0].params[0]" + ], + "answer": [ + "wait_second", + 0.1 + ], + "point": 0.95, + "desc": "문제 2/번개/[만일]의 세부 동작 2/'0.1' 초 기다리기", + "type": "list" + }, + { + "ele": "$[2][3].statements[0][1].statements[0][2].type", + "answer": "delete_clone", + "point": 0.95, + "desc": "문제 2/번개/[만일]의 세부 동작 3/이 복제본 삭제하기" + }, + { + "ele": "$[3][0].type", + "answer": "when_message_cast", + "point": 0.95, + "desc": "문제 2/번개/신호/'미션성공' 신호를 받았을 때" + }, + { + "ele": [ + "$[3][1].type", + "$[3][1].params[0]" + ], + "answer": [ + "stop_object", + "otherThread" + ], + "point": 0.95, + "desc": "문제 2/번개/[신호]의 세부 동작 1/'자신의 다른' 코드 멈추기", + "type": "list" + } + ], + "sort": 106 + }, + "5-0": { + "type": "script", + "ele": "$.objects[?(@.name=~'별')].script", + "blocks": [ + { + "ele": "$[0][0].type", + "answer": "when_run_button_click", + "point": 0.95, + "desc": "문제 2/별/시작/시작하기 버튼을 클릭했을 때" + }, + { + "ele": [ + "$[0][1].type", + "$[0][1].params[0].params[0]" + ], + "answer": [ + "set_scale_size", + "10" + ], + "point": 0.95, + "desc": "문제 2/별/[시작]의 세부 동작 1/크기를 '10' 으로 정하기", + "type": "list" + }, + { + "ele": [ + "$[0][2].type", + "$[0][2].params[0].params[0]", + "$[0][2].params[1].params[0]" + ], + "answer": [ + "locate_xy", + "-205", + "105" + ], + "point": 0.95, + "desc": "문제 2/별/[시작]의 세부 동작 2/x: '-205' y: '105' 위치로 이동하기", + "type": "list" + }, + { + "ele": "$[0][3].type", + "answer": "hide", + "point": 0.95, + "desc": "문제 2/별/[시작]의 세부 동작 3/모양 숨기기" + }, + { + "ele": "$[1][0].type", + "answer": "when_message_cast", + "point": 0.95, + "desc": "문제 2/별/신호/'게임시작' 신호를 받았을 때" + }, + { + "ele": [ + "$[1][1].type", + "$[1][1].params[0]" + ], + "answer": [ + "repeat_inf", + null + ], + "point": 0.95, + "desc": "문제 2/별/반복/계속 반복하기", + "type": "list" + }, + { + "ele": [ + "$[1][1].statements[0][0].type", + "$[1][1].statements[0][0].params[0].params[0]" + ], + "answer": [ + "repeat_basic", + "3" + ], + "point": 0.95, + "desc": "문제 2/별/반복/'3' 번 반복하기", + "type": "list" + }, + { + "ele": [ + "$[1][1].statements[0][0].statements[0][0].type", + "$[1][1].statements[0][0].statements[0][0].params[0]" + ], + "answer": [ + "create_clone", + "self" + ], + "point": 0.95, + "desc": "문제 2/별/[반복]의 세부 동작 1/'자신' 의 복제본 만들기", + "type": "list" + }, + { + "ele": [ + "$[1][1].statements[0][1].type", + "$[1][1].statements[0][1].params[0].params[0]" + ], + "answer": [ + "wait_second", + "3" + ], + "point": 0.95, + "desc": "문제 2/별/반복/'3' 초 기다리기", + "type": "list" + }, + { + "ele": "$[2][0].type", + "answer": "when_clone_start", + "point": 0.95, + "desc": "문제 2/별/복제본/복제본이 처음 생성되었을 때" + }, + { + "ele": [ + "$[2][1].type", + "$[2][1].params[0].params[1].params[0]", + "$[2][1].params[0].params[3].params[0]" + ], + "answer": [ + "set_scale_size", + "10", + "40" + ], + "point": 0.95, + "desc": "문제 2/별/[복제본]의 세부 동작1/크기를 '10 부터 40 사이의 무작위 수' 로 정하기 ", + "type": "list" + }, + { + "ele": [ + "$[2][2].type", + "$[2][2].params[0].params[1].params[0]", + "$[2][2].params[0].params[3].params[0]", + "$[2][2].params[1].params[0]" + ], + "answer": [ + "locate_xy", + "-220", + "220", + "150" + ], + "point": 0.95, + "desc": "문제 2/별/[복제본]의 세부 동작2/x: '-220부터 220 사이의 무작위 수' y: '150' 위치로 이동하기", + "type": "list" + }, + { + "ele": "$[2][3].type", + "answer": "show", + "point": 0.95, + "desc": "문제 2/별/[복제본]의 세부 동작3/모양 보이기" + }, + { + "ele": [ + "$[2][4].type", + "$[2][4].params[0]" + ], + "answer": [ + "repeat_inf", + null + ], + "point": 0.95, + "desc": "문제 2/별/반복/계속 반복하기", + "type": "list" + }, + { + "ele": [ + "$[2][4].statements[0][0].type", + "$[2][4].statements[0][0].params[0].params[1].params[0]", + "$[2][4].statements[0][0].params[0].params[3].params[0]" + ], + "answer": [ + "move_y", + "-1", + "-2" + ], + "point": 0.95, + "desc": "문제 2/별/[반복]의 세부 동작 1/y 좌표를 '-1 부터 -2 사이의 무작위 수' 만큼 바꾸기", + "type": "list" + }, + { + "ele": [ + "$[2][4].statements[0][1].type", + "$[2][4].statements[0][1].params[0].type" + ], + "answer": [ + "_if", + "reach_something" + ], + "point": 0.95, + "desc": "문제 2/별/만일/만일 '공' 에 닿았는가? 라면", + "type": "list" + }, + { + "ele": [ + "$[2][4].statements[0][1].statements[0][0].type", + "$[2][4].statements[0][1].statements[0][0].params[1].params[0]" + ], + "answer": [ + "change_variable", + "2" + ], + "point": 0.95, + "desc": "문제 2/별/[만일]의 세부 동작 1/'점수' 에 '2' 만큼 더하기", + "type": "list" + }, + { + "ele": [ + "$[2][4].statements[0][1].statements[0][1].type", + "$[2][4].statements[0][1].statements[0][1].params[0].params[0]" + ], + "answer": [ + "wait_second", + "0.1" + ], + "point": 0.95, + "desc": "문제 2/별/[만일]의 세부 동작 2/'0.1' 초 기다리기", + "type": "list" + }, + { + "ele": "$[2][4].statements[0][1].statements[0][2].type", + "answer": "delete_clone", + "point": 0.95, + "desc": "문제 2/별/[만일]의 세부 동작 3/이 복제본 삭제하기" + }, + { + "ele": [ + "$[2][4].statements[0][2].type", + "$[2][4].statements[0][2].params[0].type", + "$[2][4].statements[0][2].params[0].params[0].type", + "$[2][4].statements[0][2].params[0].params[1]", + "$[2][4].statements[0][2].params[0].params[2].params[0]" + ], + "answer": [ + "_if", + "boolean_basic_operator", + "get_variable", + "GREATER_OR_EQUAL", + "10" + ], + "point": 0.95, + "desc": "문제 2/별/만일/만일 '점수' 값 ≥ '10' 이라면", + "type": "list" + }, + { + "ele": "$[2][4].statements[0][2].statements[0][0].type", + "answer": "message_cast", + "point": 0.95, + "desc": "문제 2/별/신호/'미션성공' 신호 보내기" + }, + { + "ele": "$[3][0].type", + "answer": "when_message_cast", + "point": 0.95, + "desc": "문제 2/별/신호/'미션성공' 신호를 받았을 때" + }, + { + "ele": [ + "$[3][1].type", + "$[3][1].params[0]" + ], + "answer": [ + "stop_object", + "otherThread" + ], + "point": 0.95, + "desc": "문제 2/별/[신호]의 세부 동작 1/'자신의 다른' 코드 멈추기", + "type": "list" + } + ], + "sort": 125 + }, + "6-0": { + "type": "script", + "ele": "$.objects[?(@.name=~'공|신')].script", + "blocks": [ + { + "ele": "$[0][0].type", + "answer": "when_run_button_click", + "point": 0.95, + "desc": "문제 2/공/시작/시작하기 버튼을 클릭했을 때" + }, + { + "ele": [ + "$[0][1].type", + "$[0][1].params[0].params[0]" + ], + "answer": [ + "set_scale_size", + "15" + ], + "point": 0.95, + "desc": "문제 2/공/[시작]의 세부 동작 1/크기를 '15' 로 정하기", + "type": "list" + }, + { + "ele": [ + "$[0][2].type", + "$[0][2].params[0].params[0]", + "$[0][2].params[1].params[0]" + ], + "answer": [ + "locate_xy", + "0", + "0" + ], + "point": 0.95, + "desc": "문제 2/공/[시작]의 세부 동작 2/x: '0' y: '0' 위치로 이동하기", + "type": "list" + }, + { + "ele": "$[0][3].type", + "answer": "hide", + "point": 0.95, + "desc": "문제 2/공/[시작]의 세부 동작 3/모양 숨기기" + }, + { + "ele": "$[1][0].type", + "answer": "when_message_cast", + "point": 0.95, + "desc": "문제 2/공/신호/'게임시작' 신호를 받았을 때" + }, + { + "ele": [ + "$[1][1].type", + "$[1][1].params[0]" + ], + "answer": [ + "repeat_inf", + null + ], + "point": 0.95, + "desc": "문제 2/공/반복/계속 반복하기", + "type": "list" + }, + { + "ele": [ + "$[1][1].statements[0][0].type", + "$[1][1].statements[0][0].params[0].type", + "$[1][1].statements[0][0].params[0].params[0]" + ], + "answer": [ + "_if", + "is_press_some_key", + "32" + ], + "point": 0.95, + "desc": "문제 2/공/만일/만일 '스페이스' 키가 눌러져 있는가? 라면", + "type": "list" + }, + { + "ele": [ + "$[1][1].statements[0][0].statements[0][0].type", + "$[1][1].statements[0][0].statements[0][0].params[0]" + ], + "answer": [ + "create_clone", + "self" + ], + "point": 0.95, + "desc": "문제 2/공/[만일]의 세부 동작 1/'자신' 의 복제본 만들기", + "type": "list" + }, + { + "ele": [ + "$[1][1].statements[0][0].statements[0][1].type", + "$[1][1].statements[0][0].statements[0][1].params[0]", + "$[1][1].statements[0][0].statements[0][1].params[1].params[0]" + ], + "answer": [ + "add_effect_amount", + "color", + "10" + ], + "point": 0.95, + "desc": "문제 2/공/[만일]의 세부 동작 2/'색깔' 효과를 '10' 만큼 주기", + "type": "list" + }, + { + "ele": [ + "$[1][1].statements[0][0].statements[0][2].type", + "$[1][1].statements[0][0].statements[0][2].params[0].params[0]" + ], + "answer": [ + "wait_second", + "0.1" + ], + "point": 0.95, + "desc": "문제 2/공/[만일]의 세부 동작 3/'0.1' 초 기다리기", + "type": "list" + }, + { + "ele": "$[2][0].type", + "answer": "when_clone_start", + "point": 0.95, + "desc": "문제 2/공/복제본/복제본이 처음 생성되었을 때" + }, + { + "ele": "$[2][1].type", + "answer": "locate", + "point": 0.95, + "desc": "문제 2/공/[복제본]의 세부 동작1/'막대' 위치로 이동하기" + }, + { + "ele": "$[2][2].type", + "answer": "show", + "point": 0.95, + "desc": "문제 2/공/[복제본]의 세부 동작2/모양 보이기" + }, + { + "ele": [ + "$[2][3].type", + "$[2][3].params[0].params[0].params[3]", + "$[2][3].params[0].params[1]", + "$[2][3].params[0].params[2].params[0]" + ], + "answer": [ + "repeat_while_true", + null, + "wall_up", + null + ], + "point": 0.95, + "desc": "문제 2/공/반복/'위쪽 벽' 에 닿았는가? '이 될 때까지' 반복하기", + "type": "list" + }, + { + "ele": [ + "$[2][3].statements[0][0].type", + "$[2][3].statements[0][0].params[0].params[0]" + ], + "answer": [ + "move_y", + 10 + ], + "point": 0.95, + "desc": "문제 2/공/[반복]의 세부 동작 1/y 좌표를 '10' 만큼 바꾸기", + "type": "list" + }, + { + "ele": [ + "$[2][3].statements[0][1].type", + "$[2][3].statements[0][1].params[0].type" + ], + "answer": [ + "_if", + "reach_something" + ], + "point": 0.95, + "desc": "문제 2/공/만일/만일 '별' 에 닿았는가? 라면", + "type": "list" + }, + { + "ele": [ + "$[2][3].statements[0][1].statements[0][0].type", + "$[2][3].statements[0][1].statements[0][0].params[0].params[0]" + ], + "answer": [ + "wait_second", + "0.1" + ], + "point": 0.95, + "desc": "문제 2/공/[만일]의 세부 동작 1/'0.1' 초 기다리기", + "type": "list" + }, + { + "ele": "$[2][3].statements[0][1].statements[0][1].type", + "answer": "delete_clone", + "point": 0.95, + "desc": "문제 2/공/[만일]의 세부 동작 2/이 복제본 삭제하기" + }, + { + "ele": "$[2][4].type", + "answer": "delete_clone", + "point": 0.95, + "desc": "문제 2/공/복제본/이 복제본 삭제하기" + }, + { + "ele": "$[3][0].type", + "answer": "when_message_cast", + "point": 0.95, + "desc": "문제 2/공/신호/'미션성공' 신호를 받았을 때" + }, + { + "ele": [ + "$[3][1].type", + "$[3][1].params[0]" + ], + "answer": [ + "stop_object", + "otherThread" + ], + "point": 0.95, + "desc": "문제 2/공/[신호]의 세부 동작/'자신의 다른' 코드 멈추기", + "type": "list" + } + ], + "sort": 148 + }, + "7-0": { + "type": "script", + "ele": "$.objects[?(@.name=~'막대|진행 상태 ')].script", + "blocks": [ + { + "ele": "$[0][0].type", + "answer": "when_run_button_click", + "point": 0.95, + "desc": "문제 2/막대/시작/시작하기 버튼을 클릭했을 때" + }, + { + "ele": [ + "$[0][1].type", + "$[0][1].params[0].params[0]" + ], + "answer": [ + "set_scale_size", + "60" + ], + "point": 0.95, + "desc": "문제 2/막대/[시작]의 세부 동작 1/크기를 'h60' 으로 정하기", + "type": "list" + }, + { + "ele": [ + "$[0][2].type", + "$[0][2].params[0].params[0]", + "$[0][2].params[1].params[0]" + ], + "answer": [ + "locate_xy", + "0", + "-110" + ], + "point": 0.95, + "desc": "문제 2/막대/[시작]의 세부 동작 2/x: '0' y: '-110' 위치로 이동하기", + "type": "list" + }, + { + "ele": "$[0][3].type", + "answer": "message_cast", + "point": 0.95, + "desc": "문제 2/막대/신호/'게임시작' 신호 보내기" + }, + { + "ele": "$[1][0].type", + "answer": "when_message_cast", + "point": 0.95, + "desc": "문제 2/막대/신호/'게임시작' 신호를 받았을 때" + }, + { + "ele": [ + "$[1][1].type", + "$[1][1].params[0].params[0]" + ], + "answer": [ + "wait_second", + "0.3" + ], + "point": 0.95, + "desc": "문제 2/막대/[신호]의 세부 동작 1/'0.3' 초 기다리기", + "type": "list" + }, + { + "ele": [ + "$[1][2].type", + "$[1][2].params[0]" + ], + "answer": [ + "repeat_inf", + null + ], + "point": 0.95, + "desc": "문제 2/막대/반복/계속 반복하기", + "type": "list" + }, + { + "ele": [ + "$[1][2].statements[0][0].type", + "$[1][2].statements[0][0].params[0].type", + "$[1][2].statements[0][0].params[0].params[0]" + ], + "answer": [ + "_if", + "is_press_some_key", + "37" + ], + "point": 0.95, + "desc": "문제 2/막대/만일/만일 '왼쪽 화살표' 키가 눌러져 있는가? 라면", + "type": "list" + }, + { + "ele": [ + "$[1][2].statements[0][0].statements[0][0].type", + "$[1][2].statements[0][0].statements[0][0].params[0].params[0]" + ], + "answer": [ + "move_x", + "-10" + ], + "point": 0.95, + "desc": "문제 2/막대/[만일]의 세부 동작 1/x 좌표를 '-10' 만큼 바꾸기", + "type": "list" + }, + { + "ele": [ + "$[1][2].statements[0][1].type", + "$[1][2].statements[0][1].params[0].type", + "$[1][2].statements[0][1].params[0].params[0]" + ], + "answer": [ + "_if", + "is_press_some_key", + "39" + ], + "point": 0.95, + "desc": "문제 2/막대/만일/만일 '오른쪽 화살표' 키가 눌러져 있는가? 라면", + "type": "list" + }, + { + "ele": [ + "$[1][2].statements[0][1].statements[0][0].type", + "$[1][2].statements[0][1].statements[0][0].params[0].params[0]" + ], + "answer": [ + "move_x", + "10" + ], + "point": 0.95, + "desc": "문제 2/막대/[만일2]의 세부 동작 1/x 좌표를 '10' 만큼 바꾸기", + "type": "list" + }, + { + "ele": "$[2][0].type", + "answer": "when_message_cast", + "point": 0.95, + "desc": "문제 2/막대/신호/'미션성공' 신호를 받았을 때" + }, + { + "ele": [ + "$[2][1].type", + "$[2][1].params[0]" + ], + "answer": [ + "stop_object", + "otherThread" + ], + "point": 0.95, + "desc": "문제 2/막대/[신호]의 세부 동작 1/'자신의 다른' 코드 멈추기", + "type": "list" + }, + { + "ele": "$[2][2].params[*].params", + "answer": [ + "미션성공!", + "2" + ], + "point": 0.95, + "desc": "문제 2/막대/[신호]의 세부 동작 1/'미션성공!' 을 '2' 초 동안 '말하기'" + }, + { + "ele": "$[2][3].type", + "answer": "start_neighbor_scene", + "point": 0.95, + "desc": "문제 2/막대/[신호]의 세부 동작 1/'다음' 장면 시작하기" + } + ], + "sort": 168 + }, + "8-0": { + "type": "script", + "ele": "$.objects[?(@.name=~'우주|장면 ')].script", + "blocks": [ + { + "ele": "$[0][0].type", + "answer": "when_scene_start", + "point": 0.95, + "desc": "문제 2/우주/장면 2/장면이 시작되었을 때" + }, + { + "ele": "$[0][1].type", + "answer": "hide_variable", + "point": 0.95, + "desc": "문제 2/우주/[장면 2]의 세부 동작 1/변수 '점수' 숨기기" + }, + { + "ele": "$[1][0].type", + "answer": "when_object_click", + "point": 2.5, + "desc": "문제 3/우주/오브젝트/오브젝트를 클릭했을 때" + }, + { + "ele": [ + "$[1][1].type", + "$[1][1].params[0]", + "$[1][1].params[1].params[0]" + ], + "answer": [ + "add_effect_amount", + "color", + 30 + ], + "point": 2.5, + "desc": "문제 3/우주/[오브젝트]의 세부 동작 1/'색깔' 효과를 '30' 만큼 주기", + "type": "list" + }, + { + "ele": "$[1][2].params[*].params", + "answer": [ + "다시시작!", + 2 + ], + "point": 2.5, + "desc": "문제 3/우주/[오브젝트]의 세부 동작 2/'다시시작!' 을 '2' 초 동안 '말하기'" + }, + { + "ele": "$[1][3].type", + "answer": "restart_project", + "point": 2.5, + "desc": "문제 3/우주/[오브젝트]의 세부 동작 3/처음부터 다시 실행하기" + } + ], + "sort": 183 + } +} \ No newline at end of file diff --git a/logs/cat.log b/logs/cat.log index ba67d25..dbccd48 100644 --- a/logs/cat.log +++ b/logs/cat.log @@ -34,3 +34,226 @@ Traceback (most recent call last): File "D:\project\Entry\Entry-Scoring\main.py", line 162, in process_project total_points += question_points TypeError: unsupported operand type(s) for +=: 'int' and 'NoneType' +[2025-10-30 16:24:04] [ERROR] [main:301] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: unsupported operand type(s) for +=: 'int' and 'NoneType' +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 296, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 162, in process_project + total_points += question_points +TypeError: unsupported operand type(s) for +=: 'int' and 'NoneType' +[2025-10-30 16:26:32] [ERROR] [main:301] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: unsupported operand type(s) for +=: 'float' and 'NoneType' +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 296, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 212, in process_project + total_points += block_points +TypeError: unsupported operand type(s) for +=: 'float' and 'NoneType' +[2025-10-31 17:08:00] [ERROR] [main:322] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: 'NoneType' object is not iterable +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 317, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 167, in process_project + for block in block_list + ^^^^^^^^^^ +TypeError: 'NoneType' object is not iterable +[2025-10-31 17:14:04] [ERROR] [main:324] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: unhashable type: 'list' +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 319, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 168, in process_project + if block.get("answer") in target_event_answers + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: unhashable type: 'list' +[2025-10-31 17:17:42] [ERROR] [main:327] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: unhashable type: 'list' +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 322, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 171, in process_project + if answer in target_event_answers: # 조건에 맞는지 확인 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: unhashable type: 'list' +[2025-10-31 17:51:14] [ERROR] [main:360] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: 'list' object has no attribute 'get' +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 355, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 234, in process_project + script_data = reorder_script_by_event_order(script_json, block_event_order) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 149, in reorder_script_by_event_order + if script.get("type") == event_type: + ^^^^^^^^^^ +AttributeError: 'list' object has no attribute 'get' +[2025-10-31 17:56:20] [ERROR] [main:360] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: 'list' object has no attribute 'get' +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 355, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 234, in process_project + script_data = reorder_script_by_event_order(script_json, block_event_order) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 149, in reorder_script_by_event_order + if script.get("type") == event_type: + ^^^^^^^^^^ +AttributeError: 'list' object has no attribute 'get' +[2025-10-31 17:56:40] [ERROR] [main:360] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: 'list' object has no attribute 'get' +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 355, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 234, in process_project + script_data = reorder_script_by_event_order(script_json, block_event_order) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 149, in reorder_script_by_event_order + if script.get("type") == event_type: + ^^^^^^^^^^ +AttributeError: 'list' object has no attribute 'get' +[2025-10-31 17:58:18] [ERROR] [main:361] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: 'list' object has no attribute 'get' +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 356, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 235, in process_project + script_data = reorder_script_by_event_order(script_json, block_event_order) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 150, in reorder_script_by_event_order + if script.get("type") == event_type: + ^^^^^^^^^^ +AttributeError: 'list' object has no attribute 'get' +[2025-11-03 16:09:35] [ERROR] [main:399] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: list indices must be integers or slices, not str +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 394, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 270, in process_project + script_data = reorder_script_all_cases(script_json, block_event_order) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 167, in reorder_script_all_cases + type_map.setdefault(s["type"], []).append(s) + ~^^^^^^^^ +TypeError: list indices must be integers or slices, not str +[2025-11-03 17:15:12] [ERROR] [main:433] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: 0 +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 428, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 310, in process_project + block_elements = find_element(single_script, block_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 33, in find_element + for match in jsonpath_expr.find(item): + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 268, in find + for subdata in self.left.find(datum) + ^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 270, in find + for submatch in self.right.find(subdata)] + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 680, in find + return self._find_base(datum, create=False) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 692, in _find_base + return [DatumInContext(datum.value[self.index], path=self, context=datum)] + ~~~~~~~~~~~^^^^^^^^^^^^ +KeyError: 0 +[2025-11-03 17:20:20] [ERROR] [main:417] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: 0 +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 412, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 300, in process_project + block_elements = find_element(data, block_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 33, in find_element + for match in jsonpath_expr.find(item): + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 268, in find + for subdata in self.left.find(datum) + ^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 270, in find + for submatch in self.right.find(subdata)] + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 680, in find + return self._find_base(datum, create=False) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 692, in _find_base + return [DatumInContext(datum.value[self.index], path=self, context=datum)] + ~~~~~~~~~~~^^^^^^^^^^^^ +KeyError: 0 +[2025-11-03 17:21:39] [ERROR] [main:407] 🚫Error processing ./output/2510_CAS_2_A/2510_CAS_2_A(정답)\project.json: 1 +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 402, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 288, in process_project + block_elements = find_list_element(data, block_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 48, in find_list_element + result.append([match.value for match in jsonpath_expr.find(data)]) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 268, in find + for subdata in self.left.find(datum) + ^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 270, in find + for submatch in self.right.find(subdata)] + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 680, in find + return self._find_base(datum, create=False) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "d:\project\Entry\Entry-Scoring\.venv\Lib\site-packages\jsonpath_ng\jsonpath.py", line 692, in _find_base + return [DatumInContext(datum.value[self.index], path=self, context=datum)] + ~~~~~~~~~~~^^^^^^^^^^^^ +KeyError: 1 +[2025-11-04 15:36:56] [ERROR] [main:423] 🚫Error processing ./output/2510_CAS_2_A/코딩활용능력2급(엔트리)-000139-성지환\project.json: 'NoneType' object is not iterable +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 418, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 268, in process_project + script_data = reorder_script_all_cases(script_json, block_event_order) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 167, in reorder_script_all_cases + for s in script_json: + ^^^^^^^^^^^ +TypeError: 'NoneType' object is not iterable +[2025-11-04 16:20:44] [ERROR] [main:423] 🚫Error processing ./output/2510_CAS_2_A/코딩활용능력2급(엔트리)-000139-성지환\project.json: 'NoneType' object is not iterable +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 418, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 268, in process_project + script_data = reorder_script_all_cases(script_json, block_event_order) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 167, in reorder_script_all_cases + for s in script_json: + ^^^^^^^^^^^ +TypeError: 'NoneType' object is not iterable +[2025-11-04 17:11:33] [ERROR] [main:423] 🚫Error processing ./output/2510_CAS_2_A/코딩활용능력2급(엔트리)-000139-성지환\project.json: 'NoneType' object is not iterable +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 418, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 268, in process_project + script_data = reorder_script_all_cases(script_json, block_event_order) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 167, in reorder_script_all_cases + for s in script_json: + ^^^^^^^^^^^ +TypeError: 'NoneType' object is not iterable +[2025-11-05 16:02:42] [ERROR] [main:423] 🚫Error processing ./output/00_test/코딩활용능력2급(엔트리)-000139-성지환\project.json: 'NoneType' object is not iterable +Traceback (most recent call last): + File "D:\project\Entry\Entry-Scoring\main.py", line 418, in main + points = process_project(project_data, scoring_data) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 268, in process_project + script_data = reorder_script_all_cases(script_json, block_event_order) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\project\Entry\Entry-Scoring\main.py", line 167, in reorder_script_all_cases + for s in script_json: + ^^^^^^^^^^^ +TypeError: 'NoneType' object is not iterable diff --git a/main.py b/main.py index 1564411..a3c49e6 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,5 @@ from jsonpath_ng.ext import parse import json -from itertools import chain import os import pandas as pd # 추가된 import import unicodedata # 상단에 import 추가 @@ -9,6 +8,8 @@ from datetime import datetime import logging from logging_config import setup_logging # logging 설정을 위한 import import traceback +import itertools +import copy from script_utils import extract_and_format_scripts # 스크립트 추출 함수 import @@ -21,13 +22,13 @@ def read_json(file_path): # 요소 탐색 함수 def find_element(project_data, jsonpath_expr): - jsonpath_expr = parse(jsonpath_expr) - result = [] - for match in jsonpath_expr.find(project_data): - value = match.value - result.append(value) - return result - + """ + 주어진 데이터(project_data)에서 jsonpath 표현식에 일치하는 모든 값들을 찾아 + 리스트로 반환합니다. + """ + parsed_expr = parse(jsonpath_expr) + return [match.value for match in parsed_expr.find(project_data)] + # 요소 탐색 함수 def find_script_element(project_data, jsonpath_expr): jsonpath_expr = parse(jsonpath_expr) @@ -40,13 +41,14 @@ def find_script_element(project_data, jsonpath_expr): # 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 + """ + 주어진 데이터(data)에서 여러 jsonpath 표현식들에 일치하는 값들을 찾아 + 결과를 리스트의 리스트 형태로 반환합니다. + """ + return [ + [match.value for match in parse(expr).find(data)] + for expr in jsonpath_expr_list + ] # 스크립트 채점 진행 전 스크립트 블럭 순서가 when_run_button_click 1번째, when_clone_start 2번째 배열에 없으면 # 리스트 순서 스왑해서 각각 0, 1번 순서로 배치될 수 있도록 함 @@ -113,10 +115,11 @@ def swap_script(origin): 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) + if clone_start_block: + result.append(clone_start_block) + # 4. 나머지 블록 추가 result.extend(other_blocks) @@ -124,6 +127,49 @@ def swap_script(origin): # 결과가 비어있으면 원본 반환 return result if result else origin +def reorder_script_all_cases(script_json, block_event_order): + """ + script_json의 'type' 순서를 block_event_order 순서에 맞게 재정렬합니다. + + Args: + script_json (list[dict]): 각 요소가 {'type': '...', ...} 형태의 리스트 + block_event_order (list[str]): 원하는 type 순서 예: ['when_run_button_click', 'when_message_cast'] + + Returns: + list[dict]: block_event_order 순서대로 재정렬된 리스트 + """ + # 타입별로 스크립트를 분류 + type_map = {} + for s in script_json: + type_map.setdefault(s[0]["type"], []).append(s) + + results = [] + + def backtrack(order_idx, used_counts, current): + # ✅ 모든 이벤트 순서를 처리했으면 결과에 추가 + if order_idx == len(block_event_order): + results.append(copy.deepcopy(current)) + return + + event_type = block_event_order[order_idx] + available_scripts = type_map.get(event_type, []) + + # 아직 사용하지 않은 script만 선택 + for i, script in enumerate(available_scripts): + if used_counts[event_type][i]: + continue + used_counts[event_type][i] = True + current.append(script) + backtrack(order_idx + 1, used_counts, current) + current.pop() + used_counts[event_type][i] = False + + # 사용 여부 초기화 + used_counts = {t: [False] * len(lst) for t, lst in type_map.items()} + backtrack(0, used_counts, []) + + return results + def clean_string(text): """문자열 끝의 . 또는 ! 제거""" if isinstance(text, str): @@ -140,14 +186,36 @@ def convert_to_str(value): def process_project(project_data, scoring_data): total_points = 0 score_list = [] + + # - 시작하기 버튼을 클릭했을 때 : when_run_button_click + # - (특정) 신호를 받았을 때 : when_message_cast + # - 복제본이 생성 되었을 때 : when_clone_start + # - 장면이 시작 되었을 때 : when_scene_start + # - 오브젝트를 클릭 했을 때 : when_object_click + # 이벤트 블록이 존재하는지 여부 확인용 변수 + target_event_type = { + "when_run_button_click", + "when_message_cast", + "when_clone_start", + "when_scene_start", + "when_object_click" + } 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') + question_points = question_info.get('point') + + block_event_order = [] # 결과를 저장할 리스트 생성 + if isinstance(block_list, list) and block_list: + for block in block_list: + answer = block.get("answer") # answer 키값을 안전하게 가져오기 + if isinstance(answer, str) and answer in target_event_type: + block_event_order.append(answer) # 리스트에 추가 + print(f"▶ Processing question: {question_key}") # ✅ SCENE TYPE 처리 @@ -173,50 +241,82 @@ def process_project(project_data, scoring_data): elif question_type == "script": script_raw = find_script_element(project_data, element_path) script_json = json.loads(script_raw) if script_raw else None - script_data = swap_script(script_json) if script_json else None + + # 스크립트 블록 순서 재정렬 + script_data = reorder_script_all_cases(script_json, block_event_order) if script_json else None block_index = 1 for block in block_list: block_type = block.get('type') block_path = block.get('ele') block_answer = block.get('answer', None) - block_points = block.get('points') + block_points = block.get('point') if script_data is None: print(f"{question_key}-{block_index}: Script Not Found") score_list.append("확인 필요") block_index += 1 continue + + # 1. 현재 블록(문제)이 정답인지 판별하는 플래그 + is_block_correct = False + + # 2. 로깅을 위해 마지막으로 찾은 값을 저장할 변수 + last_found_values = None + + # 3. script_data가 단일 객체여도 처리 가능하도록 리스트로 통일 + scripts_to_check = script_data if isinstance(script_data, list) else [script_data] - # 블록 요소 검색 - if block_type == "list": - block_elements = find_list_element(script_data, block_path) - else: - block_elements = find_element(script_data, block_path) + # 4. 여러 스크립트 뭉치를 순회하며 정답이 하나라도 있는지 확인 + for single_script in scripts_to_check: + # 단일 스크립트 객체(single_script)에 대해 블록 요소 검색 + if block_type == "list": + block_elements = find_list_element(single_script, block_path) + else: + block_elements = find_element(single_script, block_path) + + # 결과값 정리 + if block_elements and isinstance(block_answer, list): + # 1. 비어있는 sublist를 ['None']으로 먼저 치환합니다. + # - sublist가 비어있지 않으면(if sublist) sublist를 그대로 사용하고, + # - 비어있으면 ['None']으로 대체합니다. + processed_elements = [sublist if sublist else ['None'] for sublist in block_elements] + found_values = [convert_to_str(x) for x in itertools.chain.from_iterable(processed_elements)] + else: + found_values = convert_to_str(block_elements[0]) if block_elements else None + + # 실패 시 출력할 값을 위해 마지막으로 찾은 값을 저장 + last_found_values = found_values + expected_str = convert_to_str(block_answer) if block_answer is not None else None - # 결과값 정리 - 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 + # 정답 조건 확인 + if block_elements: + # 5-1. 정답(block_answer)이 없고, 요소만 찾으면 되는 경우 + if block_answer is None: + is_block_correct = True + # 5-2. 정답이 있고, 찾은 값과 일치하는 경우 + elif expected_str == found_values: + is_block_correct = True + + # 6. 정답을 찾았으면, 더 이상 다른 스크립트를 확인할 필요 없이 내부 반복문 탈출 + if is_block_correct: + break # for single_script in scripts_to_check: 루프를 중단 - expected_str = convert_to_str(block_answer) if block_answer is not None else None - - # 비교 및 점수 처리 - if block_elements: - 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_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_answer is None: - total_points += block_points - score_list.append(block_points) + # 7. 내부 반복문 종료 후, 플래그를 기반으로 최종 점수 처리 + if is_block_correct: + # 정답을 맞힌 경우 + if block_answer is not None: + print(f"{question_key}-{block_index}: ✅ {expected_str} == {last_found_values}") + else: print(f"{question_key}-{block_index}: Element Exists") + total_points += block_points + score_list.append(block_points) else: - print(f"{question_key}-{block_index}: No elements found for {block_path}") + # 모든 스크립트를 확인했지만 정답이 없는 경우 + if last_found_values is not None: # 요소는 찾았으나 값이 틀린 경우 + print(f"{question_key}-{block_index}: ❌ {expected_str} != {last_found_values}") + else: # 요소를 전혀 찾지 못한 경우 + print(f"{question_key}-{block_index}: No elements found for {block_path}") score_list.append(0) block_index += 1 @@ -234,8 +334,9 @@ def main(): timestamp = datetime.now().strftime("%y%m%d") test_mode = False # 테스트 모드 설정 # test_mode = True # 테스트 모드 설정 - exam_round = "2509" - exam_names = ["CAT_3_A"] # 여러 시험명을 리스트로 설정 + exam_round = "2510" + # exam_names = ["CAT_3_A"] # 여러 시험명을 리스트로 설정 + exam_names = ["CAS_2_A"] # 여러 시험명을 리스트로 설정 excel_list = [] for exam_name in exam_names: