feat: classify gear and weapon mods
Validate and deploy production / validate (push) Successful in 24s
Validate and deploy staging / validate (push) Successful in 44s
Validate and deploy production / deploy (push) Failing after 29s
Validate and deploy staging / deploy (push) Failing after 6s

This commit is contained in:
jb
2026-07-31 21:54:47 -04:00
parent 1bd9facd3f
commit f48cc878aa
21 changed files with 595 additions and 39 deletions
@@ -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}"
)