feat: classify gear and weapon mods
This commit is contained in:
@@ -14,6 +14,39 @@ type MissingReason = typing.Literal[
|
||||
"not_applicable",
|
||||
"redacted",
|
||||
]
|
||||
type ScreenshotFamily = typing.Literal[
|
||||
"weapon",
|
||||
"gear",
|
||||
"skill_chip",
|
||||
"os_protocol",
|
||||
"specialization",
|
||||
"weapon_mod",
|
||||
"gear_mod",
|
||||
"unknown",
|
||||
]
|
||||
type CanonicalRecordType = typing.Literal[
|
||||
"weapon",
|
||||
"weapon_talent",
|
||||
"weapon_augmentation_profile",
|
||||
"gear",
|
||||
"brand_set",
|
||||
"gear_talent",
|
||||
"gear_augmentation_profile",
|
||||
"skill_chip_set",
|
||||
"skill_chip_set_bonus",
|
||||
"skill_chip_roll_pool",
|
||||
"skill_chip_talent",
|
||||
"os_protocol",
|
||||
"os_protocol_secondary_profile",
|
||||
"os_protocol_talent",
|
||||
"specialization",
|
||||
"specialization_focus",
|
||||
"specialization_ability",
|
||||
"specialization_tree_node",
|
||||
"specialization_tree_rank",
|
||||
"weapon_mod",
|
||||
"gear_mod",
|
||||
]
|
||||
|
||||
|
||||
class SourceEngine(typing.TypedDict):
|
||||
@@ -76,14 +109,12 @@ class FieldClaim(typing.TypedDict):
|
||||
|
||||
|
||||
class ScreenshotClassification(typing.TypedDict):
|
||||
family: typing.Literal[
|
||||
"weapon", "gear", "skill_chip", "os_protocol", "specialization", "unknown"
|
||||
]
|
||||
family: ScreenshotFamily
|
||||
view: str
|
||||
gear_slot: (
|
||||
typing.Literal["Mask", "Gloves", "Backpack", "Body Armor", "Holster", "Knee Pads"] | None
|
||||
)
|
||||
record_types: list[str]
|
||||
record_types: list[CanonicalRecordType]
|
||||
confidence: float
|
||||
abstention_reason: str | None
|
||||
|
||||
@@ -99,7 +130,7 @@ class ObservedItemInstance(typing.TypedDict):
|
||||
"""Values shown for one configured item; they are not reusable definitions."""
|
||||
|
||||
instance_id: str
|
||||
family: str
|
||||
family: ScreenshotFamily
|
||||
source_screen_ids: list[str]
|
||||
observed_context: ObservedContext
|
||||
fields: list[FieldClaim]
|
||||
@@ -109,7 +140,7 @@ class CanonicalDefinitionCandidate(typing.TypedDict):
|
||||
"""Evidence-backed reusable claims, kept separate from the observed item."""
|
||||
|
||||
candidate_id: str
|
||||
record_type: str
|
||||
record_type: CanonicalRecordType
|
||||
source_instance_ids: list[str]
|
||||
evidence_refs: list[str]
|
||||
fields: list[FieldClaim]
|
||||
|
||||
@@ -12,6 +12,8 @@ class Family(StrEnum):
|
||||
SKILL_CHIP = "skill_chip"
|
||||
OS_PROTOCOL = "os_protocol"
|
||||
SPECIALIZATION = "specialization"
|
||||
WEAPON_MOD = "weapon_mod"
|
||||
GEAR_MOD = "gear_mod"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
@@ -57,6 +59,8 @@ class RecordType(StrEnum):
|
||||
SPECIALIZATION_ABILITY = "specialization_ability"
|
||||
SPECIALIZATION_TREE_NODE = "specialization_tree_node"
|
||||
SPECIALIZATION_TREE_RANK = "specialization_tree_rank"
|
||||
WEAPON_MOD = "weapon_mod"
|
||||
GEAR_MOD = "gear_mod"
|
||||
|
||||
|
||||
SCREEN_VIEWS: Final[Mapping[Family, tuple[str, ...]]] = MappingProxyType(
|
||||
@@ -80,6 +84,8 @@ SCREEN_VIEWS: Final[Mapping[Family, tuple[str, ...]]] = MappingProxyType(
|
||||
"focus_tree",
|
||||
"rank_details",
|
||||
),
|
||||
Family.WEAPON_MOD: ("detail",),
|
||||
Family.GEAR_MOD: ("detail",),
|
||||
Family.UNKNOWN: ("unknown",),
|
||||
}
|
||||
)
|
||||
@@ -117,6 +123,8 @@ RECORD_TYPES_BY_VIEW: Final[Mapping[tuple[Family, str], tuple[RecordType, ...]]]
|
||||
RecordType.SPECIALIZATION_TREE_NODE,
|
||||
),
|
||||
(Family.SPECIALIZATION, "rank_details"): (RecordType.SPECIALIZATION_TREE_RANK,),
|
||||
(Family.WEAPON_MOD, "detail"): (RecordType.WEAPON_MOD,),
|
||||
(Family.GEAR_MOD, "detail"): (RecordType.GEAR_MOD,),
|
||||
(Family.UNKNOWN, "unknown"): (),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -86,7 +86,13 @@ def validate_classification(classification: Mapping[str, object]) -> None:
|
||||
raise ContractValidationError("record_types must contain strings")
|
||||
if len(record_types) != len(set(record_types)):
|
||||
raise ContractValidationError("record_types must be unique")
|
||||
allowed = {record_type.value for record_type in taxonomy.RECORD_TYPES_BY_VIEW[(family, view)]}
|
||||
if len(record_types) > 8:
|
||||
raise ContractValidationError("record_types cannot contain more than 8 values")
|
||||
try:
|
||||
normalized_record_types = {taxonomy.RecordType(record_type) for record_type in record_types}
|
||||
except ValueError as error:
|
||||
raise ContractValidationError("Unknown canonical record type") from error
|
||||
allowed = set(taxonomy.RECORD_TYPES_BY_VIEW[(family, view)])
|
||||
if family is taxonomy.Family.UNKNOWN:
|
||||
if record_types:
|
||||
raise ContractValidationError("Unknown screens cannot claim canonical record types")
|
||||
@@ -94,7 +100,7 @@ def validate_classification(classification: Mapping[str, object]) -> None:
|
||||
if reason not in taxonomy.ABSTENTION_REASONS:
|
||||
raise ContractValidationError("Unknown screens require an abstention reason")
|
||||
else:
|
||||
if not record_types or not set(record_types).issubset(allowed):
|
||||
if not record_types or not normalized_record_types.issubset(allowed):
|
||||
raise ContractValidationError(
|
||||
f"record_types are incompatible with {family.value}/{view}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user