book-genre-v5-title-author / genre_policy.py
Mitchins's picture
Initial V5 title-author book genre classifier
e40a651 verified
Raw
History Blame Contribute Delete
2.93 kB
import json
import os
class GenreDecisionPolicy:
def __init__(self, policy_path="decision_policy.json"):
with open(policy_path, "r") as f:
self.policy = json.load(f)
self.distance_matrix = self.policy.get("distance_matrix", {})
self.exact_rules = self.policy.get("exact_auto_label", {})
self.ambig_rules = self.policy.get("ambiguous_bucket", {})
def get_distance(self, g1, g2):
if g1 == g2: return 0.0
if g1 in self.distance_matrix and g2 in self.distance_matrix[g1]:
return self.distance_matrix[g1][g2]
if g2 in self.distance_matrix and g1 in self.distance_matrix[g2]:
return self.distance_matrix[g2][g1]
return 0.75
def decide(self, top_predictions):
if not top_predictions or len(top_predictions) < 2:
return {
"decision": "abstain",
"final_label": None,
"ambiguity_bucket": None,
"reason": "not_enough_predictions"
}
top1 = top_predictions[0][0]
top1_conf = top_predictions[0][1]
top2 = top_predictions[1][0]
top2_conf = top_predictions[1][1]
margin = top1_conf - top2_conf
dist = self.get_distance(top1, top2)
decision = "abstain"
final_label = None
ambiguity_bucket = None
reason = "low_confidence"
# A. Exact Auto Label
if top1_conf >= self.exact_rules.get("min_top1_confidence", 0.80) and margin >= self.exact_rules.get("min_margin", 0.15):
decision = "exact_auto_label"
final_label = top1
reason = "high_confidence_clear_margin"
# B. Ambiguous Bucket
elif (top1_conf >= self.ambig_rules.get("min_top1_confidence", 0.50) and
top1_conf < self.ambig_rules.get("max_top1_confidence", 0.80) and
margin < self.ambig_rules.get("max_margin", 0.20) and
dist <= self.ambig_rules.get("max_top1_top2_distance", 0.25)):
decision = "ambiguous_bucket"
pair = sorted([top1, top2])
s1 = pair[0].replace(" / ", "/").replace("Fiction", "Fic")
s2 = pair[1].replace(" / ", "/").replace("Fiction", "Fic")
ambiguity_bucket = f"{s1} / {s2} Ambiguous"
final_label = ambiguity_bucket
reason = "semantic_proximity_low_margin"
# C. Abstain Reasons
elif top1_conf < self.ambig_rules.get("min_top1_confidence", 0.50):
reason = "low_signal"
elif margin < self.exact_rules.get("min_margin", 0.15) and dist >= 0.60:
reason = "semantic_conflict"
else:
reason = "insufficient_certainty"
return {
"decision": decision,
"final_label": final_label,
"ambiguity_bucket": ambiguity_bucket,
"reason": reason
}