오타점수/부분합 엑셀 시트에 적용
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
_old_excel_files/
|
||||||
output/
|
output/
|
||||||
input/
|
input/
|
||||||
*.xlsx
|
*.xlsx
|
||||||
|
|||||||
@@ -1532,3 +1532,11 @@
|
|||||||
2025-01-18 17:33:15,690 - INFO - 변환 성공: 워드(한글)-005686-홍유하.hwp -> 워드(한글)-005686-홍유하.hml
|
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:15,916 - INFO - 변환 성공: 워드(한글)-005687-강태원.hwp -> 워드(한글)-005687-강태원.hml
|
||||||
2025-01-18 17:33:16,364 - INFO - 변환 성공: 워드(한글)-005688-정지예.hwp -> 워드(한글)-005688-정지예.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
|
||||||
|
|||||||
63
score5.py
63
score5.py
@@ -15,6 +15,12 @@ class XMLScorer:
|
|||||||
def __init__(self, scoring_criteria_path):
|
def __init__(self, scoring_criteria_path):
|
||||||
# 채점 기준 로드
|
# 채점 기준 로드
|
||||||
self.scoring_criteria = self._load_scoring_criteria(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 파일)
|
# 채점 기준파일 로드(JSON 파일)
|
||||||
def _load_scoring_criteria(self, file_path):
|
def _load_scoring_criteria(self, file_path):
|
||||||
@@ -37,9 +43,7 @@ class XMLScorer:
|
|||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
# result = root.xpath(second_xpath)
|
|
||||||
# print(f'result : {result}')
|
|
||||||
# return result
|
|
||||||
except ET.XPathEvalError as e:
|
except ET.XPathEvalError as e:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
@@ -141,6 +145,7 @@ class XMLScorer:
|
|||||||
|
|
||||||
previous_first_digit = first_digit
|
previous_first_digit = first_digit
|
||||||
|
|
||||||
|
id = criterion_id
|
||||||
xpath = criterion['path']
|
xpath = criterion['path']
|
||||||
xpath2 = criterion['path2']
|
xpath2 = criterion['path2']
|
||||||
search_value = criterion['searchValue']
|
search_value = criterion['searchValue']
|
||||||
@@ -190,16 +195,19 @@ class XMLScorer:
|
|||||||
actual_answer = int(result[0])
|
actual_answer = int(result[0])
|
||||||
else:
|
else:
|
||||||
actual_answer = result[0]
|
actual_answer = result[0]
|
||||||
|
|
||||||
|
if "오타감점" in category:
|
||||||
|
points = self.get_typo_score()
|
||||||
|
|
||||||
scoring = {
|
scoring = {
|
||||||
|
'id': id,
|
||||||
'category': category, # 채점 분류
|
'category': category, # 채점 분류
|
||||||
'item': item, # 채점 항목
|
'item': item, # 채점 항목
|
||||||
'right_answer': right_answer, # 정답
|
'right_answer': right_answer, # 정답
|
||||||
'actual_answer': actual_answer, # 실제 작성 답안
|
'actual_answer': actual_answer, # 실제 작성 답안
|
||||||
'points': 0,
|
'points': points,
|
||||||
'deductions': [] # 각 기준별 감점 내역
|
'deductions': [] # 각 기준별 감점 내역
|
||||||
}
|
}
|
||||||
scoring['points'] = points
|
|
||||||
|
|
||||||
# 점수 차감 조건
|
# 점수 차감 조건
|
||||||
# 1. 정답이 실수형으로 반환받은 경우는 채점항목의 부분점수 합산 결과이므로
|
# 1. 정답이 실수형으로 반환받은 경우는 채점항목의 부분점수 합산 결과이므로
|
||||||
@@ -210,8 +218,8 @@ class XMLScorer:
|
|||||||
scoring['points'] = actual_answer
|
scoring['points'] = actual_answer
|
||||||
|
|
||||||
elif type(actual_answer) is int:
|
elif type(actual_answer) is int:
|
||||||
# 오차범위 5 이상이면 감점
|
# 오차범위 3 이상이면 감점
|
||||||
if abs(actual_answer - right_answer) > 5:
|
if abs(actual_answer - right_answer) > 3:
|
||||||
scoring['points'] -= points
|
scoring['points'] -= points
|
||||||
else:
|
else:
|
||||||
# right_answer(JSON파일 내 valuer값) null일 경우 점수감점 없이 진행
|
# right_answer(JSON파일 내 valuer값) null일 경우 점수감점 없이 진행
|
||||||
@@ -244,8 +252,6 @@ class XMLScorer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def binary_to_chartxml(self, xml_path):
|
def binary_to_chartxml(self, xml_path):
|
||||||
|
|
||||||
print(f'binary_to_chartxml {xml_path}')
|
|
||||||
tree = ET.parse(xml_path)
|
tree = ET.parse(xml_path)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
|
|
||||||
@@ -339,24 +345,25 @@ class XMLScorer:
|
|||||||
|
|
||||||
# result_diff 배열의 길이를 맨 앞에 저장
|
# result_diff 배열의 길이를 맨 앞에 저장
|
||||||
temp = 40 - min(len(result_diff)*2, 40)
|
temp = 40 - min(len(result_diff)*2, 40)
|
||||||
|
self.set_typo_score(temp)
|
||||||
|
|
||||||
result_diff.insert(0, temp)
|
result_diff.insert(0, temp)
|
||||||
return result_diff
|
return result_diff
|
||||||
|
|
||||||
# XML 파일 채점
|
# XML 파일 채점
|
||||||
def score_directory(self, xml_directory, answer_path):
|
def score_directory(self, xml_directory, answer_path):
|
||||||
|
|
||||||
# xml 파일 불러오기
|
# xml 파일 불러오기
|
||||||
xml_files = Path(xml_directory).glob('*.hml')
|
xml_files = Path(xml_directory).glob('*.hml')
|
||||||
|
|
||||||
# 결과 저장할 리스트
|
# 결과 저장할 리스트
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
|
|
||||||
for xml_file in xml_files:
|
for xml_file in xml_files:
|
||||||
result = {}
|
result = {}
|
||||||
chart_xml = self.binary_to_chartxml(xml_file)
|
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['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)
|
results.append(result)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@@ -368,6 +375,7 @@ class XMLScorer:
|
|||||||
number = match.group(1)
|
number = match.group(1)
|
||||||
name = match.group(2)
|
name = match.group(2)
|
||||||
return number, name
|
return number, name
|
||||||
|
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
def export_to_excel(self, results, output_path=None):
|
def export_to_excel(self, results, output_path=None):
|
||||||
@@ -400,29 +408,46 @@ class XMLScorer:
|
|||||||
else:
|
else:
|
||||||
detail_row = {'채점항목':f"{number}-{name}"}
|
detail_row = {'채점항목':f"{number}-{name}"}
|
||||||
|
|
||||||
for i, scoring in enumerate(result['score_results']):
|
section_num = None
|
||||||
# detail_row[scoring['item']] = scoring['points']
|
partial_idx = 0
|
||||||
detail_row[f'{i+1}'] = scoring['points']
|
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_row['총점'] = result.get('total_score', 0)
|
||||||
detail_data.append(detail_row)
|
detail_data.append(detail_row)
|
||||||
|
|
||||||
summary_df = pd.DataFrame(summary_data)
|
summary_df = pd.DataFrame(summary_data)
|
||||||
detail_df = pd.DataFrame(detail_data).transpose()
|
detail_df = pd.DataFrame(detail_data).transpose()
|
||||||
|
# detail_df = pd.DataFrame(detail_data)
|
||||||
|
|
||||||
for temp in results:
|
for temp in results:
|
||||||
result = temp['typo']
|
result = temp['typo']
|
||||||
typo_data.append(result)
|
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 = pd.DataFrame(detail_data)
|
||||||
|
|
||||||
|
# detail_df.iloc[3] = typo_df.iloc[0]
|
||||||
|
|
||||||
# ExcelWriter 객체 생성
|
# ExcelWriter 객체 생성
|
||||||
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
|
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
|
||||||
summary_df.to_excel(writer, sheet_name='채점결과요약', index=False)
|
summary_df.to_excel(writer, sheet_name='채점결과요약', index=False)
|
||||||
detail_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:
|
# for sheet_name in writer.sheets:
|
||||||
|
|||||||
@@ -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')])",
|
"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,
|
"path2": null,
|
||||||
"searchValue": null,
|
"searchValue": null,
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"category": "용지설정",
|
"category": "용지설정",
|
||||||
"item": "A4용지, 왼쪽/오른쪽/위쪽/아래쪽 (각20mm), 머리말/꼬리말 (10mm), 제본(0mm)"
|
"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')",
|
"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,
|
"path2": null,
|
||||||
"searchValue": null,
|
"searchValue": null,
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"category": "기본설정",
|
"category": "기본설정",
|
||||||
"item": "글꼴 (바탕, 10pt), 양쪽정렬, 줄간격 (160%)"
|
"item": "글꼴 (바탕, 10pt), 양쪽정렬, 줄간격 (160%)"
|
||||||
},
|
},
|
||||||
"0-3":{
|
"0-03":{
|
||||||
"path":"",
|
"path":"",
|
||||||
"path2": null,
|
"path2": null,
|
||||||
"searchValue": null,
|
"searchValue": null,
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"category": "오타감점",
|
"category": "오타감점",
|
||||||
"item": "오타 1개 -2점"
|
"item": "오타 1개 -2점"
|
||||||
},
|
},
|
||||||
"1-1":{
|
"1-01":{
|
||||||
"path": "//TEXTART[@Text='{searchValue}']/TEXTARTSHAPE/@FontName",
|
"path": "//TEXTART[@Text='{searchValue}']/TEXTARTSHAPE/@FontName",
|
||||||
"path2": null,
|
"path2": null,
|
||||||
"searchValue": "전통주페어링특강안내",
|
"searchValue": "전통주페어링특강안내",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"category": "글맵시",
|
"category": "글맵시",
|
||||||
"item":"문구 (전통주페어링특강안내)/① 글씨체 : 휴먼옛체"
|
"item":"문구 (전통주페어링특강안내)/① 글씨체 : 휴먼옛체"
|
||||||
},
|
},
|
||||||
"1-2": {
|
"1-02": {
|
||||||
"path": "//TEXTART[@Text='{searchValue}']/descendant::WINDOWBRUSH/@FaceColor",
|
"path": "//TEXTART[@Text='{searchValue}']/descendant::WINDOWBRUSH/@FaceColor",
|
||||||
"path2": null,
|
"path2": null,
|
||||||
"searchValue": "전통주페어링특강안내",
|
"searchValue": "전통주페어링특강안내",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"category": "글맵시",
|
"category": "글맵시",
|
||||||
"item":"문구 (전통주페어링특강안내)/② 채우기 : 색상(RGB:28,145,110)"
|
"item":"문구 (전통주페어링특강안내)/② 채우기 : 색상(RGB:28,145,110)"
|
||||||
},
|
},
|
||||||
"1-3": {
|
"1-03": {
|
||||||
"path": "//TEXTART[@Text='{searchValue}']/SHAPEOBJECT/SIZE/@Width",
|
"path": "//TEXTART[@Text='{searchValue}']/SHAPEOBJECT/SIZE/@Width",
|
||||||
"path2": null,
|
"path2": null,
|
||||||
"searchValue": "전통주페어링특강안내",
|
"searchValue": "전통주페어링특강안내",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"category": "글맵시",
|
"category": "글맵시",
|
||||||
"item":"문구 (전통주페어링특강안내)/③ 크기 : 너비(80mm)"
|
"item":"문구 (전통주페어링특강안내)/③ 크기 : 너비(80mm)"
|
||||||
},
|
},
|
||||||
"1-4": {
|
"1-04": {
|
||||||
"path": "//TEXTART[@Text='{searchValue}']/SHAPEOBJECT/SIZE/@Height",
|
"path": "//TEXTART[@Text='{searchValue}']/SHAPEOBJECT/SIZE/@Height",
|
||||||
"path2": null,
|
"path2": null,
|
||||||
"searchValue": "전통주페어링특강안내",
|
"searchValue": "전통주페어링특강안내",
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
"category": "글맵시",
|
"category": "글맵시",
|
||||||
"item":"문구 (전통주페어링특강안내)/④ 크기 : 높이(20mm)"
|
"item":"문구 (전통주페어링특강안내)/④ 크기 : 높이(20mm)"
|
||||||
},
|
},
|
||||||
"1-5": {
|
"1-05": {
|
||||||
"path": "//TEXTART[@Text='{searchValue}']/SHAPEOBJECT/POSITION/@TreatAsChar",
|
"path": "//TEXTART[@Text='{searchValue}']/SHAPEOBJECT/POSITION/@TreatAsChar",
|
||||||
"path2": null,
|
"path2": null,
|
||||||
"searchValue": "전통주페어링특강안내",
|
"searchValue": "전통주페어링특강안내",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"category": "글맵시",
|
"category": "글맵시",
|
||||||
"item":"문구 (전통주페어링특강안내)/⑤ 위치 (글자처럼 취급)"
|
"item":"문구 (전통주페어링특강안내)/⑤ 위치 (글자처럼 취급)"
|
||||||
},
|
},
|
||||||
"1-6": {
|
"1-06": {
|
||||||
"path": "//PARASHAPE[@Id=//TEXTART[@Text='{searchValue}']/ancestor::P/@ParaShape]/@Align",
|
"path": "//PARASHAPE[@Id=//TEXTART[@Text='{searchValue}']/ancestor::P/@ParaShape]/@Align",
|
||||||
"path2": null,
|
"path2": null,
|
||||||
"searchValue": "전통주페어링특강안내",
|
"searchValue": "전통주페어링특강안내",
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
"category": "글맵시",
|
"category": "글맵시",
|
||||||
"item":"문구 (전통주페어링특강안내)/⑥ 정렬 (가운데 정렬)"
|
"item":"문구 (전통주페어링특강안내)/⑥ 정렬 (가운데 정렬)"
|
||||||
},
|
},
|
||||||
"1-7":{
|
"1-07":{
|
||||||
"path": "",
|
"path": "",
|
||||||
"path2": null,
|
"path2": null,
|
||||||
"searchValue": "전통주페어링특강안내",
|
"searchValue": "전통주페어링특강안내",
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
"category": "글맵시",
|
"category": "글맵시",
|
||||||
"item":"문구 (전통주페어링특강안내)/⑦ 글맵시모양 (육안확인)"
|
"item":"문구 (전통주페어링특강안내)/⑦ 글맵시모양 (육안확인)"
|
||||||
},
|
},
|
||||||
"1-8": {
|
"1-08": {
|
||||||
"path": "boolean(//CHARSHAPE[@Id=//CHAR[contains(text()[1],'{searchValue}')]/parent::TEXT/@CharShape][BOLD])",
|
"path": "boolean(//CHARSHAPE[@Id=//CHAR[contains(text()[1],'{searchValue}')]/parent::TEXT/@CharShape][BOLD])",
|
||||||
"path2": null,
|
"path2": null,
|
||||||
"searchValue": "혼술, 홈술, 집술",
|
"searchValue": "혼술, 홈술, 집술",
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
"category": "글꼴속성",
|
"category": "글꼴속성",
|
||||||
"item":"문구 (혼술, 홈술, 집술)/진하게"
|
"item":"문구 (혼술, 홈술, 집술)/진하게"
|
||||||
},
|
},
|
||||||
"1-9": {
|
"1-09": {
|
||||||
"path": "boolean(//CHARSHAPE[@Id=//CHAR[contains(text()[1],'{searchValue}')]/parent::TEXT/@CharShape][UNDERLINE])",
|
"path": "boolean(//CHARSHAPE[@Id=//CHAR[contains(text()[1],'{searchValue}')]/parent::TEXT/@CharShape][UNDERLINE])",
|
||||||
"path2": null,
|
"path2": null,
|
||||||
"searchValue": "혼술, 홈술, 집술",
|
"searchValue": "혼술, 홈술, 집술",
|
||||||
|
|||||||
Reference in New Issue
Block a user