하이퍼링크 수정, 차트 타입에 따른 x축 y축 채점요소 변경적용
This commit is contained in:
145
diwScoring2.py
145
diwScoring2.py
@@ -94,15 +94,22 @@ class XMLScorer:
|
||||
|
||||
is_correct = False
|
||||
|
||||
# 일치 여부 확인
|
||||
if method == "equal":
|
||||
is_correct = (user_answer == right_answer)
|
||||
|
||||
# 정답이 오차범위가 필요한 경우
|
||||
elif method == "tolerance":
|
||||
if isinstance(user_answer, dict) and isinstance(right_answer, dict):
|
||||
is_correct = all(abs(user_answer[k] - right_answer[k]) <= tolerance for k in right_answer)
|
||||
else:
|
||||
is_correct = abs(user_answer - right_answer) <= tolerance
|
||||
|
||||
# 정답이 포함되어 있는 경우
|
||||
elif method == "in":
|
||||
is_correct = user_answer in right_answer
|
||||
|
||||
# 정답을 부분점수로 계산(특수문자, 한자)
|
||||
elif method == "partial_score":
|
||||
# 부분 점수 계산
|
||||
is_correct = isinstance(user_answer, (int, float)) and user_answer <= right_answer
|
||||
@@ -176,7 +183,8 @@ class XMLScorer:
|
||||
xpath = xpath.replace('{option}', option) if xpath else ""
|
||||
xpath2 = xpath2.replace('{option}', option) if xpath2 else ""
|
||||
chart_xpath = chart_xpath.replace('{option}', option) if chart_xpath else ""
|
||||
|
||||
|
||||
|
||||
# 문항 별 채점 결과 저장
|
||||
scoring = {
|
||||
'section': section_id,
|
||||
@@ -281,33 +289,72 @@ class XMLScorer:
|
||||
self.partial_score += points
|
||||
scoring['points'] = points
|
||||
|
||||
# 정답이 하나 또는 테이블의 모든 값이 정답인 경우
|
||||
elif (category or "") in ["OneAnswer", "TableOneAnswer"]:
|
||||
# 테이블의 경우 모든 셀에 요구사항이 적용되어야 정답처리
|
||||
elif (category or "") == "TableAnswer":
|
||||
items = root.xpath(xpath) if xpath else []
|
||||
items2 = root.xpath(xpath2) if xpath2 else []
|
||||
|
||||
def is_all_match(item_list):
|
||||
return item_list and all(item == right_answer for item in item_list)
|
||||
## 위 코드와 동일한 기능(풀어서 설명)
|
||||
# 리스트가 비어 있으면 False 반환
|
||||
# if not item_list:
|
||||
# return False
|
||||
|
||||
# # 리스트의 모든 항목이 right_answer와 같은지 검사
|
||||
# for item in item_list:
|
||||
# if item != right_answer:
|
||||
# return False # 하나라도 다르면 False 반환
|
||||
|
||||
# return True # 전부 일치하면 True 반환
|
||||
|
||||
if is_all_match(items):
|
||||
user_answer = right_answer
|
||||
elif is_all_match(items2):
|
||||
user_answer = right_answer
|
||||
else:
|
||||
user_answer = ""
|
||||
points = 0
|
||||
|
||||
self.evaluate_answer(scoring, user_answer, right_answer, points)
|
||||
|
||||
|
||||
# 정답이 하나인 경우
|
||||
# elif (category or "") == "OneAnswer":
|
||||
elif (category or "") in ["OneAnswer", "ChartOneAnswer"]:
|
||||
items = root.xpath(xpath) if xpath else []
|
||||
items2 = root.xpath(xpath2) if xpath2 else []
|
||||
|
||||
# 차트 XML에서 정답을 찾는 경우
|
||||
# 차트 종류가
|
||||
# 세로막대형이면 x축이 카테고리(catAx) y축이 값(valAx)
|
||||
# 가로막대형이면 x축이 값(valAx) y축이 카테고리(catAx)
|
||||
if category == "ChartOneAnswer":
|
||||
# 하드코딩이라 [2-45문항] 변경시 수정 필요
|
||||
# chart_type = self.scoring_criteria["2"]["45"]["chart_type"].replace(" ","")
|
||||
|
||||
# chart_type 변수의 경우 45번 문항을 먼저 채점하므로
|
||||
# xy축의 변경이 필요한 53~58번 문항 채점시에 chart_type변수에 차트모양의 정보는 입력 되어있음
|
||||
|
||||
# 가로 차트일 경우에만 x축과 y축을 바꿔줌
|
||||
# 세로, 꺾은선, 원형 차트의 경우 그대로 사용
|
||||
if "가로" in chart_type:
|
||||
chart_xpath = chart_xpath.replace("catAx", "valAx")
|
||||
chart_xpath = chart_xpath.replace("valAx", "catAx")
|
||||
|
||||
|
||||
|
||||
chart_items = chart_tree.xpath(chart_xpath, namespaces=namespaces) if chart_xpath else []
|
||||
|
||||
require_all_match = (category == "TableOneAnswer")
|
||||
any_match = False
|
||||
all_match = True
|
||||
|
||||
|
||||
for item in chain(items, items2, chart_items):
|
||||
user_answer = item
|
||||
|
||||
if user_answer == right_answer:
|
||||
any_match = True
|
||||
else:
|
||||
all_match = False
|
||||
if require_all_match:
|
||||
break # 하나라도 다르면 바로 탈출
|
||||
|
||||
if require_all_match:
|
||||
score = points if all_match else 0
|
||||
else:
|
||||
score = points if any_match else 0
|
||||
|
||||
self.evaluate_answer(scoring, user_answer, right_answer, score)
|
||||
self.evaluate_answer(scoring, user_answer, right_answer, points)
|
||||
|
||||
if scoring['points'] > 0:
|
||||
break
|
||||
|
||||
# 정답이 두개인 경우
|
||||
elif (category or "") == "DoubleAnswer":
|
||||
items1 = root.xpath(xpath) if xpath else []
|
||||
items2 = root.xpath(xpath2) if xpath else []
|
||||
@@ -326,7 +373,7 @@ class XMLScorer:
|
||||
items = root.xpath(xpath)
|
||||
|
||||
# 오차범위 설정
|
||||
# 한글 프로그램 내부에서 드물게 mm단위는 0mm이지만 pt단위는 1pt로 저장되는 경우가 있음
|
||||
# 한글 프로그램 내부에서 드물게 0mm이지만 1pt로 저장되는 경우가 있음
|
||||
#
|
||||
# XML파일의 요소 옵션값은 내부적으로 1=0.01pt
|
||||
# 이 경우를 대비하여 tolerance를 10으로 설정 (1pt=약0.04mm 만큼의 오차 혀용)
|
||||
@@ -419,6 +466,7 @@ class XMLScorer:
|
||||
require_all_match = (category == "TableFontName")
|
||||
any_match = False
|
||||
all_match = True
|
||||
matched_user_answer = None # 일치하는 user_answer를 기억
|
||||
|
||||
for charshape_id in charshape_list:
|
||||
font_id = root.xpath(f"//CHARSHAPE[@Id='{charshape_id}']/FONTID/@Hangul")
|
||||
@@ -439,6 +487,7 @@ class XMLScorer:
|
||||
|
||||
if user_answer == right_answer:
|
||||
any_match = True
|
||||
matched_user_answer = user_answer
|
||||
else:
|
||||
all_match = False
|
||||
if require_all_match:
|
||||
@@ -446,19 +495,19 @@ class XMLScorer:
|
||||
|
||||
if require_all_match:
|
||||
score = points if all_match else 0
|
||||
self.evaluate_answer(scoring, user_answer, right_answer, score)
|
||||
else:
|
||||
score = points if any_match else 0
|
||||
|
||||
self.evaluate_answer(scoring, user_answer, right_answer, score, method="equal")
|
||||
self.evaluate_answer(scoring, matched_user_answer if any_match else "", right_answer, score)
|
||||
|
||||
# 폰트 속성
|
||||
elif (category or "") == "FontAttribute":
|
||||
# 하이퍼링크 처리
|
||||
hyperlink_ptag = criterion.get('hyperlink_ptag', None)
|
||||
has_ptag = root.xpath(hyperlink_ptag) if hyperlink_ptag else False
|
||||
has_hyperlink_ptag = root.xpath(hyperlink_ptag) if hyperlink_ptag else False
|
||||
|
||||
# hyperlink가 아닌 경우(일반적인 텍스트 일 경우)
|
||||
if not has_ptag:
|
||||
if not has_hyperlink_ptag:
|
||||
charshape = root.xpath(xpath)
|
||||
if not charshape:
|
||||
charshape = None
|
||||
@@ -473,12 +522,20 @@ class XMLScorer:
|
||||
self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal")
|
||||
|
||||
# 하이퍼링크인 경우
|
||||
elif has_ptag:
|
||||
elif has_hyperlink_ptag:
|
||||
hyperlink_text = search_value.replace(" ", "")
|
||||
|
||||
p_elements = has_ptag
|
||||
p_elements = has_hyperlink_ptag
|
||||
|
||||
for p in p_elements:
|
||||
# 수험자가 입력한 텍스트 중 하이퍼링크가 들어간 문단의 모든 텍스트를 가져와
|
||||
# 채점하고자 하는 (정답) 하이퍼링크 텍스트와 시작 위치를 비교
|
||||
# (예시)
|
||||
# [수험자입력] 1. 사전등록 : 서울 국제 도서 박람회 흠페이지(http://www.ind.or.kr) 참조
|
||||
# [정답] 서울 국제 도서 박람회 흠페이지(http://www.ind.or.kr) 참조
|
||||
# 수험자 텍스트의 "1. 사전등록" 부분을 제외하고 난 뒤
|
||||
# 남은 "서울 국제 도서 박람회 흠페이지(http://www.ind.or.kr) 참조"의 정답 부분과 유사도를 비교
|
||||
|
||||
text_list = p.xpath(".//CHAR/text()")
|
||||
full_text = ''.join(text_list).replace(" ", "")
|
||||
# print("full_text: ", full_text)
|
||||
@@ -623,13 +680,12 @@ class XMLScorer:
|
||||
self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal")
|
||||
|
||||
# 한자
|
||||
elif (category or "") == "Hanja":
|
||||
word_list = criterion.get('word', [])
|
||||
|
||||
elif (category or "") == "Hanja":
|
||||
# 점수 계산
|
||||
score = 0
|
||||
max_score = points
|
||||
|
||||
word_list = criterion.get('word', [])
|
||||
# 부분점수 (최대점수에서 한자 갯수만큼 나눈 몫)
|
||||
score_per_pair = max_score // len(word_list)
|
||||
|
||||
@@ -652,15 +708,13 @@ class XMLScorer:
|
||||
elif (category or "") == "ChartType":
|
||||
chart_type_list = {
|
||||
'꺾은선형': "//c:lineChart[c:grouping[@val='standard']]",
|
||||
'가로막대형': "//c:barChart[c:barDir[@val='bar'] and c:grouping[@val='clustered']]",
|
||||
'세로막대형': "//c:barChart[c:barDir[@val='col'] and c:grouping[@val='clustered']]",
|
||||
'묶은가로막대형': "//c:barChart[c:barDir[@val='bar'] and c:grouping[@val='clustered']]",
|
||||
'묶은세로막대형': "//c:barChart[c:barDir[@val='col'] and c:grouping[@val='clustered']]",
|
||||
'원형': "//c:pieChart",
|
||||
'분산형': "//c:scatterChart"
|
||||
}
|
||||
chart_type = criterion.get('chart_type').replace(" ","")
|
||||
if "묶은" in chart_type:
|
||||
chart_type = chart_type.replace("묶은", "")
|
||||
|
||||
|
||||
# 입력한 chart_type에 해당하는 xpath를 가져옴
|
||||
chart_xpath = chart_type_list[chart_type]
|
||||
|
||||
@@ -723,10 +777,8 @@ class XMLScorer:
|
||||
return xml_data
|
||||
|
||||
def typo_check(self, correct_answer_file, user_answer_file, chart_xml):
|
||||
user_answer_tree = ET.parse(user_answer_file)
|
||||
user_answer_root = user_answer_tree.getroot()
|
||||
correct_answer_tree = ET.parse(correct_answer_file)
|
||||
correct_answer_root = correct_answer_tree.getroot()
|
||||
user_answer_root = ET.parse(user_answer_file).getroot()
|
||||
correct_answer_root = ET.parse(correct_answer_file).getroot()
|
||||
|
||||
# xpath로 바이너리 부분추출
|
||||
user_input_text = user_answer_root.xpath('//CHAR//text()[not(ancestor::HEADER) and not(ancestor::TABLE)]')
|
||||
@@ -762,8 +814,6 @@ class XMLScorer:
|
||||
correct_input_text = [re.sub(r'\d+\.\s*|-', '', text) for text in correct_input_text]
|
||||
|
||||
try :
|
||||
# xpath = self.scoring_criteria["2"]["29"]['path'].split("'")[1]
|
||||
# ignore_word = xpath.split("'")[1]
|
||||
ignore_word = self.scoring_criteria["2"]["29"]["ignoreWord"]
|
||||
# 특정 단어 제거
|
||||
# 오타와 누락의 경우만 판단하면 정상작동하지만
|
||||
@@ -975,19 +1025,22 @@ def main():
|
||||
|
||||
# 시험회차 및 유형
|
||||
exam_round = '2504'
|
||||
|
||||
# 채점하고자 하는 유형은 주석 해제
|
||||
exam_types = [
|
||||
# 'A',
|
||||
# 'B',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
]
|
||||
|
||||
test_mode = False
|
||||
# test_mode = True
|
||||
# test_mode = True #/TEST 폴더 채점시
|
||||
|
||||
output_excel_paths = []
|
||||
for exam_type in exam_types:
|
||||
# JSON 채점기준표 파일 (예시:DIW_2503A.json)
|
||||
# scoring_criteria_path = f'./DIW_{exam_round}.json'
|
||||
scoring_criteria_path = f'./DIW_{exam_round}{exam_type}_new.json'
|
||||
scoring_criteria_path = f'./DIW_{exam_round}{exam_type}.json'
|
||||
|
||||
# xml(hml)파일 디렉토리 경로 (예시:./output/A/DIW)
|
||||
# xml_directory = f'./output/{exam_type}/{"TEST" if test_mode else "DIW"}'
|
||||
|
||||
Reference in New Issue
Block a user