From ec3a3ba83369654c0501059557525257149d5678 Mon Sep 17 00:00:00 2001 From: gzero-ser7 Date: Tue, 12 Nov 2024 16:29:29 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B1=84=EC=A0=90=EC=9A=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- score2.py | 327 ++++++++++++++++++ score3.py | 296 ++++++++++++++++ scoring_results_20241111_170638.xlsx | Bin 0 -> 6146 bytes scoring_results_20241111_170945.xlsx | Bin 0 -> 6157 bytes scoring_results_20241111_172707.xlsx | Bin 0 -> 6170 bytes scoring_results_20241112_161225.xlsx | Bin 0 -> 6057 bytes ... => ~$scoring_results_20241112_161225.xlsx | Bin 7 files changed, 623 insertions(+) create mode 100644 score2.py create mode 100644 score3.py create mode 100644 scoring_results_20241111_170638.xlsx create mode 100644 scoring_results_20241111_170945.xlsx create mode 100644 scoring_results_20241111_172707.xlsx create mode 100644 scoring_results_20241112_161225.xlsx rename ~$scoring_results_20241107_170915.xlsx => ~$scoring_results_20241112_161225.xlsx (100%) diff --git a/score2.py b/score2.py new file mode 100644 index 0000000..65b82df --- /dev/null +++ b/score2.py @@ -0,0 +1,327 @@ +import json +import xml.etree.ElementTree as ET +import os +from pathlib import Path +import pandas as pd +from datetime import datetime +from Levenshtein import distance as levenshtein_distance + +class XMLScorer: + def __init__(self, scoring_criteria_path): + """ + 채점 기준표 JSON 파일을 로드하여 초기화합니다. + + Args: + scoring_criteria_path (str): 채점 기준표 JSON 파일 경로 + """ + self.scoring_criteria = self._load_scoring_criteria(scoring_criteria_path) + # 오탈자 감점 설정 + self.typo_penalties = { + 'slight': 0.9, # 90% 점수 (약간의 오탈자) + 'moderate': 0.7, # 70% 점수 (중간 정도의 오탈자) + 'severe': 0.0 # 0% 점수 (심각한 오탈자) + } + + def _load_scoring_criteria(self, file_path): + """ + JSON 채점 기준표를 로드합니다. + + Args: + file_path (str): JSON 파일 경로 + + Returns: + dict: 채점 기준표 데이터 + """ + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + + def _calculate_similarity_score(self, str1, str2): + """ + 두 문자열 간의 유사도를 계산합니다. + + Args: + str1 (str): 첫 번째 문자열 + str2 (str): 두 번째 문자열 + + Returns: + float: 유사도 점수 (0.0 ~ 1.0) + """ + if str1 is None or str2 is None: + return 0.0 + + max_len = max(len(str1), len(str2)) + if max_len == 0: + return 1.0 + + distance = levenshtein_distance(str1, str2) + similarity = 1 - (distance / max_len) + return similarity + + def _get_penalty_factor(self, similarity): + """ + 유사도에 따른 감점 계수를 반환합니다. + + Args: + similarity (float): 유사도 점수 + + Returns: + float: 감점 계수 + """ + if similarity >= 0.9: + return self.typo_penalties['slight'] + elif similarity >= 0.7: + return self.typo_penalties['moderate'] + else: + return self.typo_penalties['severe'] + + def _find_best_matching_element(self, root, target_element): + """ + 가장 유사한 요소를 찾습니다. + + Args: + root (Element): XML 루트 요소 + target_element (str): 찾고자 하는 요소 이름 + + Returns: + tuple: (가장 유사한 요소, 유사도 점수) + """ + best_match = None + best_similarity = 0.0 + + for element in root.iter(): + similarity = self._calculate_similarity_score(element.tag, target_element) + if similarity > best_similarity: + best_similarity = similarity + best_match = element + + return best_match, best_similarity + + def _find_element_value(self, root, element_name, attribute_name): + """ + XML에서 특정 요소와 속성값을 찾습니다. 오탈자를 고려합니다. + + Args: + root (Element): XML 루트 요소 + element_name (str): 찾을 요소 이름 + attribute_name (str): 찾을 속성 이름 + + Returns: + tuple: (속성값, 요소 유사도, 속성 유사도) + """ + element, element_similarity = self._find_best_matching_element(root, element_name) + + if element is not None: + # 속성 중 가장 유사한 것을 찾음 + best_attr_value = None + best_attr_similarity = 0.0 + + for attr_name, attr_value in element.attrib.items(): + attr_similarity = self._calculate_similarity_score(attr_name, attribute_name) + if attr_similarity > best_attr_similarity: + best_attr_similarity = attr_similarity + best_attr_value = attr_value + + return best_attr_value, element_similarity, best_attr_similarity + + return None, 0.0, 0.0 + + def score_xml_file(self, xml_path): + """ + 단일 XML 파일을 채점합니다. + + Args: + xml_path (str): XML 파일 경로 + + Returns: + dict: 채점 결과 + """ + try: + tree = ET.parse(xml_path) + root = tree.getroot() + + total_score = 0 + results = { + 'filename': os.path.basename(xml_path), + 'criteria_matches': [], + 'total_score': 0 + } + + # 각 채점 기준에 대해 검사 + for criterion_id, criterion in self.scoring_criteria.items(): + element_name = criterion['ele'] + attribute_name = criterion['arg'] + expected_value = criterion['value'] + points = criterion['points'] + + # 오탈자를 고려하여 값을 찾음 + actual_value, element_similarity, attr_similarity = self._find_element_value( + root, element_name, attribute_name) + + # 값 유사도 계산 + value_similarity = self._calculate_similarity_score(str(actual_value), str(expected_value)) + + # 전체 유사도 계산 (요소, 속성, 값의 유사도를 종합) + total_similarity = (element_similarity + attr_similarity + value_similarity) / 3 + + # 감점 계수 계산 + penalty_factor = self._get_penalty_factor(total_similarity) + + match = { + 'criterion': f"{element_name}.{attribute_name}", + 'expected': expected_value, + 'actual': actual_value, + 'element_similarity': round(element_similarity, 3), + 'attribute_similarity': round(attr_similarity, 3), + 'value_similarity': round(value_similarity, 3), + 'total_similarity': round(total_similarity, 3), + 'penalty_factor': penalty_factor, + 'points': round(points * penalty_factor, 2) + } + + total_score += match['points'] + results['criteria_matches'].append(match) + + results['total_score'] = round(total_score, 2) + return results + + except ET.ParseError as e: + return { + 'filename': os.path.basename(xml_path), + 'error': f"XML 파싱 오류: {str(e)}", + 'total_score': 0 + } + + def score_directory(self, xml_directory): + """ + 디렉토리 내의 모든 XML 파일을 채점합니다. + + Args: + xml_directory (str): XML 파일들이 있는 디렉토리 경로 + + Returns: + list: 모든 파일의 채점 결과 + """ + results = [] + xml_files = Path(xml_directory).glob('*.xml') + + for xml_file in xml_files: + result = self.score_xml_file(str(xml_file)) + results.append(result) + + return results + + def export_to_excel(self, results, output_path=None): + """ + 채점 결과를 엑셀 파일로 저장합니다. + + Args: + results (list): 채점 결과 리스트 + output_path (str, optional): 출력 파일 경로 + """ + if output_path is None: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + output_path = f"scoring_results_{timestamp}.xlsx" + + # 요약 시트용 데이터 준비 + summary_data = [] + detail_data = [] + + for result in results: + # 요약 정보 + summary_row = { + '파일명': result['filename'], + '총점': result.get('total_score', 0) + } + if 'error' in result: + summary_row['오류'] = result['error'] + summary_data.append(summary_row) + + # 상세 정보 + if 'criteria_matches' in result: + for match in result['criteria_matches']: + detail_row = { + '파일명': result['filename'], + '채점항목': match['criterion'], + '기대값': match['expected'], + '실제값': match['actual'], + '요소유사도': match['element_similarity'], + '속성유사도': match['attribute_similarity'], + '값유사도': match['value_similarity'], + '전체유사도': match['total_similarity'], + '감점계수': match['penalty_factor'], + '획득점수': match['points'] + } + detail_data.append(detail_row) + + # DataFrame 생성 + summary_df = pd.DataFrame(summary_data) + detail_df = pd.DataFrame(detail_data) + + # 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) + + # 열 너비 자동 조정 + for sheet_name in writer.sheets: + worksheet = writer.sheets[sheet_name] + for column in worksheet.columns: + max_length = 0 + column = [cell for cell in column] + for cell in column: + try: + if len(str(cell.value)) > max_length: + max_length = len(str(cell.value)) + except: + pass + adjusted_width = (max_length + 2) + worksheet.column_dimensions[column[0].column_letter].width = adjusted_width + + return output_path + +# 메인 함수는 이전과 동일 + +# 사용 예시 +def main(): + # 채점기준표 파일 경로 + scoring_criteria_path = "scoring_criteria.json" + # XML 파일들이 있는 디렉토리 경로 + xml_directory = r"C:\Users\gzero-ser7-win11\Documents\hwpTest\Output" + + # 채점기 초기화 + scorer = XMLScorer(scoring_criteria_path) + + # 디렉토리 내 모든 XML 파일 채점 + results = scorer.score_directory(xml_directory) + + # 결과 출력 + for result in results: + print(f"\n파일: {result['filename']}") + if 'error' in result: + print(f"오류: {result['error']}") + continue + + print(f"총점: {result['total_score']}") + print("\n채점 세부사항:") + for match in result['criteria_matches']: + print(f"기준: {match['criterion']}") + print(f"기대값: {match['expected']}") + print(f"실제값: {match['actual']}") + print(f"획득 점수: {match['points']}") + print("---") + + # 결과를 엑셀 파일로 저장 + excel_path = scorer.export_to_excel(results) + print(f"\n채점 결과가 다음 경로에 저장되었습니다: {excel_path}") + +if __name__ == "__main__": + main() + + + + + + + + + diff --git a/score3.py b/score3.py new file mode 100644 index 0000000..ba5c318 --- /dev/null +++ b/score3.py @@ -0,0 +1,296 @@ +import json +import xml.etree.ElementTree as ET +import os +from pathlib import Path +import pandas as pd +from datetime import datetime +from difflib import SequenceMatcher +import re + +class XMLScorer: + def __init__(self, scoring_criteria_path): + self.scoring_criteria = self._load_scoring_criteria(scoring_criteria_path) + + def _load_scoring_criteria(self, file_path): + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + + def _calculate_string_similarity(self, str1, str2): + """ + 두 문자열 간의 유사도를 계산합니다. + + Args: + str1 (str): 첫 번째 문자열 + str2 (str): 두 번째 문자열 + + Returns: + float: 유사도 (0~1 사이의 값) + """ + return SequenceMatcher(None, str1, str2).ratio() + + def _count_differences(self, str1, str2): + """ + 두 문자열 간의 차이(오탈자, 띄어쓰기)를 계산합니다. + + Args: + str1 (str): 첫 번째 문자열 (기준값) + str2 (str): 두 번째 문자열 (비교값) + + Returns: + tuple: (전체 차이 개수, 띄어쓰기 차이 개수) + """ + # 띄어쓰기 차이 계산 + space_diff = abs(str1.count(' ') - str2.count(' ')) + + # 전체 글자 차이 계산 (Levenshtein 거리 기반) + total_diff = 0 + m, n = len(str1), len(str2) + dp = [[0] * (n + 1) for _ in range(m + 1)] + + for i in range(m + 1): + dp[i][0] = i + for j in range(n + 1): + dp[0][j] = j + + for i in range(1, m + 1): + for j in range(1, n + 1): + if str1[i-1] == str2[j-1]: + dp[i][j] = dp[i-1][j-1] + else: + dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1 + + total_diff = dp[m][n] + + return total_diff, space_diff + + def _find_similar_element(self, root, target_element): + """ + 유사한 요소를 찾습니다. 완전 일치하지 않더라도 비슷한 이름의 요소를 찾습니다. + + Args: + root (Element): XML 루트 요소 + target_element (str): 찾고자 하는 요소 이름 + + Returns: + Element: 가장 유사한 요소 또는 None + """ + best_match = None + best_similarity = 0.7 # 최소 유사도 임계값 + + for element in root.iter(): + similarity = self._calculate_string_similarity(element.tag, target_element) + if similarity > best_similarity: + best_similarity = similarity + best_match = element + + return best_match + + def _find_element_value(self, root, element_name, attribute_name): + """ + XML에서 특정 요소와 속성값을 찾습니다. 유사한 요소도 고려합니다. + + Args: + root (Element): XML 루트 요소 + element_name (str): 찾을 요소 이름 + attribute_name (str): 찾을 속성 이름 + + Returns: + tuple: (찾은 속성값 또는 None, 요소 이름 오탈자 여부) + """ + # 정확한 요소 찾기 + element = root.find(f".//{element_name}") + + # 정확한 요소가 없으면 유사한 요소 찾기 + if element is None: + element = self._find_similar_element(root, element_name) + + if element is not None: + # 속성값 찾기 + value = element.get(attribute_name) + # 요소 이름이 정확히 일치하는지 확인 + has_typo = element.tag != element_name + return value, has_typo + + return None, False + + def score_xml_file(self, xml_path): + try: + tree = ET.parse(xml_path) + root = tree.getroot() + + total_score = 0 + results = { + 'filename': os.path.basename(xml_path), + 'criteria_matches': [], + 'total_score': 0, + 'deductions': [] # 감점 상세 내역 추가 + } + + for criterion_id, criterion in self.scoring_criteria.items(): + element_name = criterion['ele'] + attribute_name = criterion['arg'] + expected_value = criterion['value'] + points = criterion['points'] + + actual_value, has_element_typo = self._find_element_value( + root, element_name, attribute_name) + + match = { + 'criterion': f"{element_name}.{attribute_name}", + 'expected': expected_value, + 'actual': actual_value, + 'points': 0, + 'deductions': [] # 각 기준별 감점 내역 + } + + if actual_value is not None: + # 기본 점수 부여 + match['points'] = points + + # 요소 이름에 오탈자가 있는 경우 + if has_element_typo: + deduction = 1 + match['points'] -= deduction + match['deductions'].append( + f"요소 이름 오탈자 감점: -{deduction}점") + + # 속성값 비교 및 차이 계산 + if actual_value != expected_value: + total_diff, space_diff = self._count_differences( + expected_value, actual_value) + + # 띄어쓰기 차이당 1점 감점 + if space_diff > 0: + match['points'] -= space_diff + match['deductions'].append( + f"띄어쓰기 오류 감점: -{space_diff}점") + + # 나머지 차이(오탈자)당 1점 감점 + char_diff = total_diff - space_diff + if char_diff > 0: + match['points'] -= char_diff + match['deductions'].append( + f"글자 오류 감점: -{char_diff}점") + + # 음수 점수 방지 + match['points'] = max(0, match['points']) + + results['criteria_matches'].append(match) + total_score += match['points'] + + results['total_score'] = total_score + return results + + except ET.ParseError as e: + return { + 'filename': os.path.basename(xml_path), + 'error': f"XML 파싱 오류: {str(e)}", + 'total_score': 0 + } + + def export_to_excel(self, results, output_path=None): + if output_path is None: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + output_path = f"scoring_results_{timestamp}.xlsx" + + summary_data = [] + detail_data = [] + + for result in results: + # 요약 정보 + summary_row = { + '파일명': result['filename'], + '총점': result.get('total_score', 0) + } + if 'error' in result: + summary_row['오류'] = result['error'] + summary_data.append(summary_row) + + # 상세 정보 + if 'criteria_matches' in result: + for match in result['criteria_matches']: + detail_row = { + '파일명': result['filename'], + '채점항목': match['criterion'], + '기대값': match['expected'], + '실제값': match['actual'], + '획득점수': match['points'], + '감점내역': '; '.join(match.get('deductions', [])) + } + detail_data.append(detail_row) + + # DataFrame 생성 + summary_df = pd.DataFrame(summary_data) + detail_df = pd.DataFrame(detail_data) + + # 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) + + # 열 너비 자동 조정 + for sheet_name in writer.sheets: + worksheet = writer.sheets[sheet_name] + for column in worksheet.columns: + max_length = 0 + column = [cell for cell in column] + for cell in column: + try: + if len(str(cell.value)) > max_length: + max_length = len(str(cell.value)) + except: + pass + adjusted_width = (max_length + 2) + worksheet.column_dimensions[column[0].column_letter].width = adjusted_width + + return output_path + + def score_directory(self, xml_directory): + results = [] + xml_files = Path(xml_directory).glob('*.xml') + + for xml_file in xml_files: + result = self.score_xml_file(str(xml_file)) + results.append(result) + + return results + + + +# 사용 예시 +def main(): + # 채점기준표 파일 경로 + scoring_criteria_path = "scoring_criteria.json" + # XML 파일들이 있는 디렉토리 경로 + xml_directory = r"C:\Users\gzero-ser7-win11\Documents\hwpTest\Output" + + # 채점기 초기화 + scorer = XMLScorer(scoring_criteria_path) + + # 디렉토리 내 모든 XML 파일 채점 + results = scorer.score_directory(xml_directory) + + # 결과 출력 + for result in results: + print(f"\n파일: {result['filename']}") + if 'error' in result: + print(f"오류: {result['error']}") + continue + + print(f"총점: {result['total_score']}") + print("\n채점 세부사항:") + for match in result['criteria_matches']: + print(f"기준: {match['criterion']}") + print(f"기대값: {match['expected']}") + print(f"실제값: {match['actual']}") + print(f"획득 점수: {match['points']}") + print("---") + + # 결과를 엑셀 파일로 저장 + excel_path = scorer.export_to_excel(results) + print(f"\n채점 결과가 다음 경로에 저장되었습니다: {excel_path}") + +if __name__ == "__main__": + main() + + diff --git a/scoring_results_20241111_170638.xlsx b/scoring_results_20241111_170638.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0d89223fa2c99556078dec722e14d280e06a1f6f GIT binary patch literal 6146 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 a{Y#3f$fF|kUjP7v_y!`XmL=h@r~d#20=0<% literal 0 HcmV?d00001 diff --git a/scoring_results_20241111_170945.xlsx b/scoring_results_20241111_170945.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..6a3570ef80723ed4ca202c76531c69610e7504ee GIT binary patch literal 6157 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 v`~wz3Dzr%O|0pB3?c7evf9x5vxw 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 J1(5!F`ac1Qx0L_@ literal 0 HcmV?d00001 diff --git a/scoring_results_20241112_161225.xlsx b/scoring_results_20241112_161225.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..902c249f6f01aa3736207df8791a3cb713fc7b26 GIT binary patch literal 6057 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