파일 삭제 및 새로운 채점 기준 추가
This commit is contained in:
110
score5.py
110
score5.py
@@ -1,13 +1,15 @@
|
||||
from datetime import datetime
|
||||
import json
|
||||
import glob
|
||||
from pathlib import Path
|
||||
import os
|
||||
from lxml import etree as ET
|
||||
import re
|
||||
from difflib import SequenceMatcher
|
||||
import pandas as pd
|
||||
# from xpathSearch import XMLPathHandler
|
||||
|
||||
from binaryToChartxml import binaryToChartxml
|
||||
|
||||
|
||||
class XMLScorer:
|
||||
# 채점 기준 경로 초기화
|
||||
@@ -21,21 +23,38 @@ class XMLScorer:
|
||||
return json.load(f)
|
||||
|
||||
# XML 파일에서 element의 값을 찾아 반환
|
||||
def query_xml(self, root, query):
|
||||
try:
|
||||
result = root.xpath(query)
|
||||
if type(result) is list and len(result) == 0:
|
||||
def query_xml(self, root, *args):
|
||||
points = args[2]
|
||||
if args[1] is not None:
|
||||
try:
|
||||
result = root.xpath(args[0])
|
||||
if type(result) is list and len(result) == 0:
|
||||
return None
|
||||
elif result < points:
|
||||
result = root.xpath(args[1])
|
||||
return result
|
||||
else:
|
||||
return result
|
||||
# result = root.xpath(args[1])
|
||||
# print(f'result : {result}')
|
||||
# return result
|
||||
except ET.XPathEvalError as e:
|
||||
return None
|
||||
else:
|
||||
try:
|
||||
result = root.xpath(args[0])
|
||||
if type(result) is list and len(result) == 0:
|
||||
return None
|
||||
|
||||
return result
|
||||
except ET.XPathEvalError as e:
|
||||
return None
|
||||
|
||||
return result
|
||||
except ET.XPathEvalError as e:
|
||||
return None
|
||||
|
||||
# 유사한 텍스트 찾기
|
||||
def find_similar_text(self, root, target_text, threshold=0.3):
|
||||
def find_similar_text(self, root, target_text, threshold=0.5):
|
||||
"""
|
||||
전체 문서에서 유사한 텍스트를 찾아 반환
|
||||
|
||||
|
||||
Args:
|
||||
root (_type_): xml root element 객체
|
||||
target_text (_type_): 찾을 텍스트
|
||||
@@ -47,6 +66,7 @@ class XMLScorer:
|
||||
# 전체 텍스트 추출
|
||||
# all_text = root.xpath(f"//CHAR/text()")
|
||||
# all_text.append(root.xpath(f"//TEXTART/@text"))
|
||||
|
||||
all_text = root.xpath(f"//CHAR/text() | //TEXTART/@Text")
|
||||
|
||||
# 유사도 비교
|
||||
@@ -64,7 +84,7 @@ class XMLScorer:
|
||||
return similar_text
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
# 하나의 XML 파일 채점
|
||||
def _score_xml_file(self, xml_path):
|
||||
try:
|
||||
@@ -84,24 +104,24 @@ class XMLScorer:
|
||||
|
||||
for criterion_id, criterion in self.scoring_criteria.items():
|
||||
xpath = criterion['path']
|
||||
xpath2 = criterion['path2']
|
||||
search_value = criterion['searchValue']
|
||||
right_answer = criterion['value']
|
||||
points = criterion['points']
|
||||
category = criterion['category']
|
||||
item = criterion['item']
|
||||
|
||||
simliar_text = None
|
||||
|
||||
# searchValue가 있을 경우 유사한 텍스트 찾기
|
||||
if search_value is not None:
|
||||
simliar_text = self.find_similar_text(root, search_value)
|
||||
if simliar_text is None:
|
||||
xpath = xpath.replace('{searchValue}', '')
|
||||
xpath = xpath.replace('{searchValue}', search_value)
|
||||
else:
|
||||
xpath = xpath.replace('{searchValue}', simliar_text)
|
||||
|
||||
# xpath로 실제 작성 답안 찾기
|
||||
result = self.query_xml(root, xpath)
|
||||
result = self.query_xml(root, xpath, xpath2, points)
|
||||
|
||||
# [ boolean 타입 ]
|
||||
# 1. 이텔릭체, 굵게, 밑줄 등 효과가 적용 여부에 따라
|
||||
@@ -112,18 +132,16 @@ class XMLScorer:
|
||||
# [ float 타입 ]
|
||||
# 1. 부분점수의 합산으로 반환되는 경우 float 타입으로 반환
|
||||
if type(result) is not list:
|
||||
actual_answer = result
|
||||
|
||||
# 표 같이 여러 조건을 동시에 검사 해야하는 경우우
|
||||
# elif type(result) is list and len(result) > 1:
|
||||
# xpath2 = criterion['path2']
|
||||
# for i in result:
|
||||
# xpath2 = xpath2.replace('{path_result_list}', str(i))
|
||||
# print(f"xpath2: {xpath2}")
|
||||
|
||||
if type(result) is float and (result > points):
|
||||
actual_answer = float(points)
|
||||
else:
|
||||
actual_answer = result
|
||||
else:
|
||||
actual_answer = result[0]
|
||||
|
||||
if type(right_answer) is int:
|
||||
actual_answer = int(result[0])
|
||||
else:
|
||||
actual_answer = result[0]
|
||||
|
||||
scoring = {
|
||||
'category': category, # 채점 분류
|
||||
'item': item, # 채점 항목
|
||||
@@ -132,26 +150,31 @@ class XMLScorer:
|
||||
'points': 0,
|
||||
'deductions': [] # 각 기준별 감점 내역
|
||||
}
|
||||
|
||||
scoring['points'] = points
|
||||
|
||||
# 점수 차감 조건
|
||||
# 1. 정답이 실수형으로 반환받은 경우는 채점항목의 부분점수 합산 결과이므로
|
||||
# 반환받은 값 그대로를 점수로 사용
|
||||
# 2. 그 외의 경우 정답과 실제 작성 답안이 다른 경우 점수 차감
|
||||
# 2. 정답이 정수형(사이즈 비교)의 경우 오차범위를 넘는다면 감점
|
||||
# 3. 그 외의 경우 정답과 실제 작성 답안이 다른 경우 점수 차감
|
||||
if type(actual_answer) is float:
|
||||
scoring['points'] = actual_answer
|
||||
scoring['points'] = actual_answer
|
||||
|
||||
elif type(actual_answer) is int:
|
||||
# 오차범위 5 이상이면 감점
|
||||
if abs(actual_answer - right_answer) > 5:
|
||||
scoring['points'] -= points
|
||||
else:
|
||||
# right_answer(JSON파일 내 valuer값) null일 경우 점수감점 없이 진행
|
||||
if right_answer != actual_answer:
|
||||
scoring['points'] -= points
|
||||
|
||||
|
||||
# 점수 차감 이유 작성 (개발중)
|
||||
|
||||
results['score_results'].append(scoring)
|
||||
total_score += scoring['points']
|
||||
|
||||
if scoring['points'] > 0:
|
||||
print(f'scoring: {scoring}')
|
||||
print(f'scoring: {scoring}')
|
||||
|
||||
results['total_score'] = total_score
|
||||
return results
|
||||
@@ -162,6 +185,7 @@ class XMLScorer:
|
||||
'error': f"XML 파싱 오류: {str(e)}",
|
||||
'total_score': 0
|
||||
}
|
||||
# def binary_to_chartxml(self, xml_path):
|
||||
|
||||
# XML 파일 채점
|
||||
def score_directory(self, xml_directory):
|
||||
@@ -173,11 +197,21 @@ class XMLScorer:
|
||||
results = []
|
||||
|
||||
for xml_file in xml_files:
|
||||
self.binary_to_chartxml(xml_file)
|
||||
result = self._score_xml_file(xml_file)
|
||||
results.append(result)
|
||||
|
||||
results.append(result)
|
||||
return results
|
||||
|
||||
def parse_filename(self, filename):
|
||||
if isinstance(filename, dict):
|
||||
filename = filename.get('파일명', '')
|
||||
match = re.match(r'.*-(\d+)-(.+)\.hml', filename)
|
||||
if match:
|
||||
number = match.group(1)
|
||||
name = match.group(2)
|
||||
return number, name
|
||||
return None, None
|
||||
|
||||
def export_to_excel(self, results, output_path=None):
|
||||
if output_path is None:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
@@ -185,7 +219,6 @@ class XMLScorer:
|
||||
|
||||
summary_data = []
|
||||
detail_data = []
|
||||
header_added = False
|
||||
|
||||
for result in results:
|
||||
# 요약 정보
|
||||
@@ -200,7 +233,10 @@ class XMLScorer:
|
||||
|
||||
# 상세 정보
|
||||
if 'score_results' in result:
|
||||
detail_row = {'파일명': result['filename']}
|
||||
filename = {'파일명': result['filename']}
|
||||
number, name = self.parse_filename(filename)
|
||||
detail_row = {'수험자':f"{number}-{name}"}
|
||||
|
||||
for i, scoring in enumerate(result['score_results']):
|
||||
detail_row[f'점수_{i+1}'] = scoring['points']
|
||||
|
||||
@@ -208,7 +244,7 @@ class XMLScorer:
|
||||
detail_data.append(detail_row)
|
||||
|
||||
summary_df = pd.DataFrame(summary_data)
|
||||
detail_df = pd.DataFrame(detail_data)
|
||||
detail_df = pd.DataFrame(detail_data).transpose()
|
||||
# detail_df = pd.DataFrame(detail_data)
|
||||
|
||||
# ExcelWriter 객체 생성
|
||||
|
||||
Reference in New Issue
Block a user