From e59698205fb6fa6743a1349542d3921ec692ebf0 Mon Sep 17 00:00:00 2001 From: devdra9 Date: Mon, 20 Jan 2025 17:56:46 +0900 Subject: [PATCH] =?UTF-8?q?=EC=98=A4=ED=83=80=EC=A0=90=EC=88=98/=EB=B6=80?= =?UTF-8?q?=EB=B6=84=ED=95=A9=20=EC=97=91=EC=85=80=20=EC=8B=9C=ED=8A=B8?= =?UTF-8?q?=EC=97=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + hwp_conversion.log | 8 +++++ score5.py | 63 ++++++++++++++++++++++++++------------ scoring_criteria_2501.json | 24 +++++++-------- 4 files changed, 65 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 4d9a581..39a8442 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +_old_excel_files/ output/ input/ *.xlsx diff --git a/hwp_conversion.log b/hwp_conversion.log index 6ef4591..f21066d 100644 --- a/hwp_conversion.log +++ b/hwp_conversion.log @@ -1532,3 +1532,11 @@ 2025-01-18 17:33:15,690 - INFO - 변환 성공: 워드(한글)-005686-홍유하.hwp -> 워드(한글)-005686-홍유하.hml 2025-01-18 17:33:15,916 - INFO - 변환 성공: 워드(한글)-005687-강태원.hwp -> 워드(한글)-005687-강태원.hml 2025-01-18 17:33:16,364 - INFO - 변환 성공: 워드(한글)-005688-정지예.hwp -> 워드(한글)-005688-정지예.hml +2025-01-20 14:32:03,574 - INFO - 변환 성공: 워드(한글)-005172-김서인.hwp -> 워드(한글)-005172-김서인.hml +2025-01-20 14:32:03,980 - INFO - 변환 성공: 워드(한글)-005174-지율.hwp -> 워드(한글)-005174-지율.hml +2025-01-20 14:32:04,227 - INFO - 변환 성공: 워드(한글)-005175-문지환.hwp -> 워드(한글)-005175-문지환.hml +2025-01-20 14:32:04,561 - INFO - 변환 성공: 워드(한글)-005176-이세영.hwp -> 워드(한글)-005176-이세영.hml +2025-01-20 14:32:04,881 - INFO - 변환 성공: 워드(한글)-005177-김은유.hwp -> 워드(한글)-005177-김은유.hml +2025-01-20 14:32:05,103 - INFO - 변환 성공: 워드(한글)-005179-손민준.hwp -> 워드(한글)-005179-손민준.hml +2025-01-20 14:32:05,346 - INFO - 변환 성공: 워드(한글)-005180-도정원.hwp -> 워드(한글)-005180-도정원.hml +2025-01-20 14:32:07,797 - INFO - 변환 성공: 정답.hwp -> 정답.hml diff --git a/score5.py b/score5.py index 5063b73..8bcd2db 100644 --- a/score5.py +++ b/score5.py @@ -15,6 +15,12 @@ class XMLScorer: def __init__(self, scoring_criteria_path): # 채점 기준 로드 self.scoring_criteria = self._load_scoring_criteria(scoring_criteria_path) + + def set_typo_score(self, score): + self.typo_score = score + + def get_typo_score(self): + return self.typo_score # 채점 기준파일 로드(JSON 파일) def _load_scoring_criteria(self, file_path): @@ -37,9 +43,7 @@ class XMLScorer: return result else: return result - # result = root.xpath(second_xpath) - # print(f'result : {result}') - # return result + except ET.XPathEvalError as e: return None else: @@ -141,6 +145,7 @@ class XMLScorer: previous_first_digit = first_digit + id = criterion_id xpath = criterion['path'] xpath2 = criterion['path2'] search_value = criterion['searchValue'] @@ -190,16 +195,19 @@ class XMLScorer: actual_answer = int(result[0]) else: actual_answer = result[0] - + + if "오타감점" in category: + points = self.get_typo_score() + scoring = { + 'id': id, 'category': category, # 채점 분류 'item': item, # 채점 항목 'right_answer': right_answer, # 정답 'actual_answer': actual_answer, # 실제 작성 답안 - 'points': 0, + 'points': points, 'deductions': [] # 각 기준별 감점 내역 } - scoring['points'] = points # 점수 차감 조건 # 1. 정답이 실수형으로 반환받은 경우는 채점항목의 부분점수 합산 결과이므로 @@ -210,8 +218,8 @@ class XMLScorer: scoring['points'] = actual_answer elif type(actual_answer) is int: - # 오차범위 5 이상이면 감점 - if abs(actual_answer - right_answer) > 5: + # 오차범위 3 이상이면 감점 + if abs(actual_answer - right_answer) > 3: scoring['points'] -= points else: # right_answer(JSON파일 내 valuer값) null일 경우 점수감점 없이 진행 @@ -244,8 +252,6 @@ class XMLScorer: } def binary_to_chartxml(self, xml_path): - - print(f'binary_to_chartxml {xml_path}') tree = ET.parse(xml_path) root = tree.getroot() @@ -339,24 +345,25 @@ class XMLScorer: # result_diff 배열의 길이를 맨 앞에 저장 temp = 40 - min(len(result_diff)*2, 40) + self.set_typo_score(temp) + result_diff.insert(0, temp) return result_diff # XML 파일 채점 def score_directory(self, xml_directory, answer_path): - # xml 파일 불러오기 xml_files = Path(xml_directory).glob('*.hml') # 결과 저장할 리스트 results = [] - for xml_file in xml_files: result = {} chart_xml = self.binary_to_chartxml(xml_file) - result['score'] = self._score_xml_file(xml_file, chart_xml) result['typo'] = self.typo_check(answer_path, xml_file) + result['score'] = self._score_xml_file(xml_file, chart_xml) + # result['score']['score_results'][2]['points'] = result['typo'][0] results.append(result) return results @@ -368,6 +375,7 @@ class XMLScorer: number = match.group(1) name = match.group(2) return number, name + return None, None def export_to_excel(self, results, output_path=None): @@ -400,29 +408,46 @@ class XMLScorer: else: detail_row = {'채점항목':f"{number}-{name}"} - for i, scoring in enumerate(result['score_results']): - # detail_row[scoring['item']] = scoring['points'] - detail_row[f'{i+1}'] = scoring['points'] + section_num = None + partial_idx = 0 + for i, score_result in enumerate(result['score_results']): + current_section = int(score_result['id'].split('-')[0]) + if section_num is None: + section_num = current_section + + if current_section != section_num: + # 이전 섹션의 부분합을 출력 + detail_row[f'[{section_num}]합계'] = result['partial_scores'][partial_idx]['score'] + partial_idx += 1 + section_num = current_section + + detail_row[f'{i+1}'] = score_result['points'] + + # 마지막 섹션의 부분합을 출력 + if section_num is not None and partial_idx < len(result['partial_scores']): + detail_row[f'[{section_num}]합계'] = result['partial_scores'][partial_idx]['score'] detail_row['총점'] = result.get('total_score', 0) detail_data.append(detail_row) summary_df = pd.DataFrame(summary_data) detail_df = pd.DataFrame(detail_data).transpose() - + # detail_df = pd.DataFrame(detail_data) for temp in results: result = temp['typo'] typo_data.append(result) - type_df = pd.DataFrame(typo_data).transpose() + typo_df = pd.DataFrame(typo_data).transpose() # detail_df = pd.DataFrame(detail_data) + detail_df.iloc[3] = typo_df.iloc[0] + # ExcelWriter 객체 생성 with pd.ExcelWriter(output_path, engine='openpyxl') as writer: summary_df.to_excel(writer, sheet_name='채점결과요약', index=False) detail_df.to_excel(writer, sheet_name='채점상세내역', index=False) - type_df.to_excel(writer, sheet_name='오타내역', index=False) + typo_df.to_excel(writer, sheet_name='오타내역', index=False) # 열 너비 자동 조정 # for sheet_name in writer.sheets: diff --git a/scoring_criteria_2501.json b/scoring_criteria_2501.json index 4e7a262..b5f72cb 100644 --- a/scoring_criteria_2501.json +++ b/scoring_criteria_2501.json @@ -1,5 +1,5 @@ { - "0-1":{ + "0-01":{ "path":"boolean(//PAGEMARGIN[(@Bottom='5668'or @Bottom='5669') and (@Footer='2834' or @Footer='2835') and @Gutter='0' and (@Header='2834' or @Header='2835') and (@Left='5668' or @Left='5669') and (@Right='5668' or @Right='5669') and (@Top='5668' or @Top='5669')])", "path2": null, "searchValue": null, @@ -8,7 +8,7 @@ "category": "용지설정", "item": "A4용지, 왼쪽/오른쪽/위쪽/아래쪽 (각20mm), 머리말/꼬리말 (10mm), 제본(0mm)" }, - "0-2":{ + "0-02":{ "path":"boolean(//FONTFACE[@Lang='Hangul']/FONT[@Id=//CHARSHAPE/FONTID/@Hangul]/@Name='바탕' and //CHARSHAPE/@Height='1000' and //PARASHAPE/PARAMARGIN/@LineSpacing='160' and //PARASHAPE/@Align='Justify')", "path2": null, "searchValue": null, @@ -17,7 +17,7 @@ "category": "기본설정", "item": "글꼴 (바탕, 10pt), 양쪽정렬, 줄간격 (160%)" }, - "0-3":{ + "0-03":{ "path":"", "path2": null, "searchValue": null, @@ -26,7 +26,7 @@ "category": "오타감점", "item": "오타 1개 -2점" }, - "1-1":{ + "1-01":{ "path": "//TEXTART[@Text='{searchValue}']/TEXTARTSHAPE/@FontName", "path2": null, "searchValue": "전통주페어링특강안내", @@ -35,7 +35,7 @@ "category": "글맵시", "item":"문구 (전통주페어링특강안내)/① 글씨체 : 휴먼옛체" }, - "1-2": { + "1-02": { "path": "//TEXTART[@Text='{searchValue}']/descendant::WINDOWBRUSH/@FaceColor", "path2": null, "searchValue": "전통주페어링특강안내", @@ -44,7 +44,7 @@ "category": "글맵시", "item":"문구 (전통주페어링특강안내)/② 채우기 : 색상(RGB:28,145,110)" }, - "1-3": { + "1-03": { "path": "//TEXTART[@Text='{searchValue}']/SHAPEOBJECT/SIZE/@Width", "path2": null, "searchValue": "전통주페어링특강안내", @@ -53,7 +53,7 @@ "category": "글맵시", "item":"문구 (전통주페어링특강안내)/③ 크기 : 너비(80mm)" }, - "1-4": { + "1-04": { "path": "//TEXTART[@Text='{searchValue}']/SHAPEOBJECT/SIZE/@Height", "path2": null, "searchValue": "전통주페어링특강안내", @@ -62,7 +62,7 @@ "category": "글맵시", "item":"문구 (전통주페어링특강안내)/④ 크기 : 높이(20mm)" }, - "1-5": { + "1-05": { "path": "//TEXTART[@Text='{searchValue}']/SHAPEOBJECT/POSITION/@TreatAsChar", "path2": null, "searchValue": "전통주페어링특강안내", @@ -71,7 +71,7 @@ "category": "글맵시", "item":"문구 (전통주페어링특강안내)/⑤ 위치 (글자처럼 취급)" }, - "1-6": { + "1-06": { "path": "//PARASHAPE[@Id=//TEXTART[@Text='{searchValue}']/ancestor::P/@ParaShape]/@Align", "path2": null, "searchValue": "전통주페어링특강안내", @@ -80,7 +80,7 @@ "category": "글맵시", "item":"문구 (전통주페어링특강안내)/⑥ 정렬 (가운데 정렬)" }, - "1-7":{ + "1-07":{ "path": "", "path2": null, "searchValue": "전통주페어링특강안내", @@ -89,7 +89,7 @@ "category": "글맵시", "item":"문구 (전통주페어링특강안내)/⑦ 글맵시모양 (육안확인)" }, - "1-8": { + "1-08": { "path": "boolean(//CHARSHAPE[@Id=//CHAR[contains(text()[1],'{searchValue}')]/parent::TEXT/@CharShape][BOLD])", "path2": null, "searchValue": "혼술, 홈술, 집술", @@ -98,7 +98,7 @@ "category": "글꼴속성", "item":"문구 (혼술, 홈술, 집술)/진하게" }, - "1-9": { + "1-09": { "path": "boolean(//CHARSHAPE[@Id=//CHAR[contains(text()[1],'{searchValue}')]/parent::TEXT/@CharShape][UNDERLINE])", "path2": null, "searchValue": "혼술, 홈술, 집술",