-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathengine.py
More file actions
169 lines (145 loc) · 5.53 KB
/
engine.py
File metadata and controls
169 lines (145 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import json
import polars as pl
from string import ascii_letters
# Read questionnaire
# file_path = Path(__file__).parent / "www" / "questionnaire.json"
file_path = "www/questionnaire.json"
with open(file_path, encoding="utf-8") as file:
questionnaire: dict[str] = json.load(file)
# Sanity check and answer dictionary creation
answers: dict[dict[str]] = {}
for index, question in enumerate(questionnaire):
if question != f"Q{index + 1}":
raise ValueError("Question number mismatch")
choices = list(questionnaire[question].keys())
if choices.pop(0) != "q":
raise ValueError("Question text not found")
else:
UPPERCASES = list(ascii_letters[26:])
if choices != UPPERCASES[: len(choices)]:
raise ValueError("Choices key mismatch")
answers[f"{index + 1}"] = {choice.lower(): 0 for choice in choices}
# normalise answers to sum to 100
def normalise_answers() -> None:
for question in answers:
total = sum(answers[question].values())
if total == 0:
continue
for choice in answers[question]:
answers[question][choice] = answers[question][choice] / total * 10
# Role score initialisation
file_path = "www/roles.json"
with open(file_path, encoding="utf-8") as file:
role_scores_table: dict[str] = json.load(file)
role_scores: dict[float] = {role: 0 for role in role_scores_table.keys()}
# Role score calculation
def role_score_calculate() -> None:
for role in role_scores:
score = 0
for q in range(1, len(answers) + 1):
choice = role_scores_table[role][str(q)].lower()
score += answers[str(q)][choice]
role_scores[role] = score
# Final score calculation
final_score_table: pl.DataFrame = pl.read_csv("www/final.csv", separator=",")
def final_score_calculator(role: str, raw_score: float) -> int:
if role not in final_score_table.columns:
raise ValueError(f"Role {role} not found in final score table")
role_data = final_score_table[["raw", role]]
# return if raw score is in the table
if raw_score in role_data["raw"]:
processed_score: pl.Series = role_data.filter(
pl.col("raw") == raw_score
).get_column(role)
if len(processed_score) != 1:
raise ValueError(
f"Multiple final scores found for raw score {raw_score} and role {role}"
)
processed_score: int = processed_score[0]
if not isinstance(processed_score, int) or processed_score < 0:
raise ValueError(
f"Invalid final score {processed_score} found for raw score {raw_score} and role {role}"
)
return processed_score
# deal with out of range raw score
if raw_score >= role_data[0, 0]:
return role_data[0, 1]
elif raw_score <= role_data[-1, 0]:
return role_data[-1, 1]
# find interval for decimal raw score
for i in range(role_data.height - 1):
x_high, y_high = role_data[i].rows()[0]
x_low, y_low = role_data[i + 1].rows()[0]
if x_high >= raw_score >= x_low:
break
# 计算插值
slope = (y_low - y_high) / (x_low - x_high)
processed_score = y_high + slope * (raw_score - x_high)
return int(processed_score)
def calculate_final_score() -> None:
for role in role_scores:
raw_score = role_scores[role]
final_score = final_score_calculator(role, raw_score)
role_scores[role] = final_score
# Result table production
file_path = "www/role_names.json"
with open(file_path, encoding="utf-8") as file:
role_names: dict[str] = json.load(file)
if role_names.keys() != role_scores.keys():
raise ValueError("Role names and scores do not match")
results_df: pl.DataFrame = pl.DataFrame()
styles = [] # for highlighting results
def produce_results() -> None:
global results_df
global styles
results_df = pl.DataFrame(
{
"角色": [f"{role_names[role]} ({role})" for role in role_scores.keys()],
"分数": [role_scores[role] for role in role_scores.keys()],
}
)
def role_level(score: int) -> str:
if score >= 70:
return "自然角色"
elif score >= 30:
return "次要角色"
else:
return "避免角色"
results_df = results_df.with_columns(
pl.col("分数").map_elements(role_level, return_dtype=str).alias("角色等级")
)
# calculate styles
styles = []
natural_roles = [i for i, x in enumerate(results_df["角色等级"] == "自然角色") if x]
if len(natural_roles) > 0:
styles.append(
{
"rows": natural_roles,
"style": {"font-weight": "bold", "background-color": "#bfffaf"},
}
)
avoided_roles = [i for i, x in enumerate(results_df["角色等级"] == "避免角色") if x]
if len(avoided_roles) > 0:
styles.append(
{
"rows": avoided_roles,
"style": {"background-color": "#ffbaaf"},
}
)
produce_results()
def get_results() -> pl.DataFrame:
return results_df
def get_styles() -> list:
return styles
# Check for last choice of the last question
def last_choice(question: int, choice: str) -> bool:
try:
questions = list(questionnaire.keys())
if f"Q{question}" != questions[-1]:
return False
choices = list(questionnaire[f"Q{question}"].keys())
if choice.upper() != choices[-1]:
return False
return True
except (ValueError, IndexError):
return False