Switch library hierarchy to GW webshop taxonomy

- warhammer_data.py: GW naming throughout - Space Marine chapters are
  their own factions under a 'Space Marines' group, systems match the
  shop (Age of Sigmar, The Horus Heresy, Other Games, The Old World,
  Middle-Earth), classes are GW shop categories (Battleline, Infantry,
  Vehicle, Walker, Flyer, Monster, Mounted, Hero, Titanic), group map
  extended to AoS grand alliances / Other Games / Old World / Middle-Earth
- gwshop.py: hierarchy remap - GW depth-4 paths are system/group/faction/
  class (the shop's last level is the class layer), depth-5 sources keep
  a subfaction before it; catalog snapshot regenerated accordingly
- sorter.py: catalog entry is hierarchy-authoritative (group, class, and
  faction when GW moved a unit or keyword tables are stale); generic GW
  branches ('Unit Type') and a Battleline default never degrade a
  confident keyword classification; whole-catalog retry when the faction
  pool has no confident match (handles units GW moved between factions);
  whole-catalog matching also runs when keyword classification found no
  faction; invalid (HTML) items never fuzzy-match; default catalog is
  now data/gwshop_catalog.json
- catalogue.py: --apply moves include the group layer, same structure
  as the sorter
- docs/SORTING.md: hierarchy rewritten to GW naming with examples
- tests: expectations updated to GW naming (chapters, unit types)
- verified live: 4/4 real files in the test batch now catalog-match,
  e.g. The Twin Lance -> 40k/Xenos Armies/T'au Empire/Epic Hero (GW's
  current placement) instead of the stale Drukhari keyword
This commit is contained in:
2026-10-04 22:05:31 +02:00
parent 5fd5dacaf0
commit 4bbaefb45a
8 changed files with 4678 additions and 4428 deletions
+302 -113
View File
@@ -1,12 +1,19 @@
"""Warhammer taxonomy and unit keyword tables used by the catalogue.
Naming follows the Games Workshop webshop hierarchy (the same one
dlphelper.gwshop dumps from Algolia):
<System>/<Group>/<Faction>/<Class> e.g.
Warhammer 40,000/Space Marines/Ultramarines/Walker
Two layers:
- SYSTEMS: game system -> faction -> keyword aliases (matched against file
names). Faction detection is the primary signal; unit names double as
faction hints since they are listed under their faction.
- CLASSES: keyword -> force-organization class (40k Force Organization slots:
HQ, Troops, Elites, Fast Attack, Heavy Support, Flyer, Dedicated Transport,
Lords of War, Fortification).
faction hints since they are listed under their faction. Space Marine
chapters are their own GW factions under the "Space Marines" group.
- CLASSES: keyword -> class. With GW as the catalog source the class layer
holds GW shop categories (unit types: Battleline, Infantry, Walker,
Vehicle, Flyer, Monster, Mounted, Hero, Titanic).
The unit table grows with the collection; it does not attempt to enumerate
every unit Games Workshop ever released - unknown files land in the
@@ -21,20 +28,65 @@ from __future__ import annotations
SYSTEMS: dict[str, dict[str, list[str]]] = {
"Warhammer 40,000": {
# -- Imperium -------------------------------------------------------
"Adeptus Astartes": [
"space marine", "astartes", "primaris", "ultramarines", "blood angels",
"dark angels", "space wolves", "black templars", "imperial fists",
"salamanders", "raven guard", "iron hands", "white scars", "grey knights",
"deathwatch", "crimson fists", "flesh tearers", "ravenwing", "deathwing",
"inner circle", "wulfen", "thunderwolf", "intercessor", "reiver", "aggressor", "inceptor",
"hellblaster", "eradicator", "bladeguard", "sternguard", "devastator",
"tactical squad", "terminator squad", "assault marine",
"redemptor", "contemptor", "dreadnought", "invictor", "repulsor",
"land raider", "land speeder", "predator", "vindicator", "whirlwind",
"drop pod", "razorback", "stormraven", "storm talon", "stormhawk",
"caestus", "chaplain", "librarian", "apothecary", "vexilus",
# -- Space Marines (chapters are their own GW factions) --------------
"Space Marines": [
"space marine", "astartes", "primaris", "intercessor", "reiver",
"aggressor", "inceptor", "hellblaster", "eradicator", "bladeguard",
"sternguard", "devastator", "tactical squad", "terminator squad",
"assault marine", "assault intercessor", "outrider", "scout squad",
"vanguard veteran", "invictor", "impulsor", "gladiator",
"land speeder", "drop pod", "inner circle", "honour guard",
],
"Ultramarines": ["ultramarines", "guilliman", "calgar"],
"Blood Angels": [
"blood angel", "sanguinary guard", "sanguinor", "death company",
"baal predator", "mephiston", "dante", "astorath", "flesh tearers",
"lamberto", "furioso",
],
"Dark Angels": [
"dark angel", "ravenwing", "deathwing", "inner circle companions",
"azrael", "asmodai", "belial", "samuel", "lion el'jonson",
"lion el jonson", "nephilim jetfighter",
],
"Space Wolves": [
"space wolf", "space wolves", "wulfen", "thunderwolf", "wolf guard",
"rune priest", "wolf priest", "wolf lord", "logan grimnar",
"ragnar blackmane", "canis wolfborn", "fenrisian wolf",
"stormfang", "murderfang", "lukas the trickster",
],
"Black Templars": [
"black templar", "crusader squad", "high marshal helbrecht",
"grimaldus", "the emperor's champion", "emperors champion",
"castellan", "land raider crusader",
],
"Imperial Fists": [
"imperial fist", "rogal dorn", "lupos", "tor garadon",
"horatius hold", "dorn's arrow",
],
"Raven Guard": [
"raven guard", "kayvaan shrike", "corvus", "sable brand",
"shadow captains",
],
"Iron Hands": [
"iron hand", "iron hands", "ferrus manus", "iron father",
"medusan immortals",
],
"White Scars": ["white scar", "jaghatai khan", "stormseer", "korsos"],
"Salamanders": [
"salamander", "vulkan", "he'stan", "adrax agatone",
"drake-scale", "flamecaster",
],
"Grey Knights": [
"grey knight", "grand master", "brother-captain", "nemesis dreadknight",
"draigo", "castellan crowe", "sternguard knight",
],
"Deathwatch": [
"deathwatch", "watch captain", "watch master", "kill team cassius",
"overkill", "corvus blackstar",
],
"Crimson Fists": ["crimson fist", "kantor", "rynn's world"],
"Flesh Tearers": ["flesh tearer", "gabriel seth", "amissum"],
# -- Imperium --------------------------------------------------------
"Astra Militarum": [
"astra militarum", "imperial guard", "cadian", "catachan", "krieg",
"death korps", "tempestus", "scion", "valhallan", "steel legion",
@@ -72,23 +124,22 @@ SYSTEMS: dict[str, dict[str, list[str]]] = {
"armiger", "warglaive", "helverin", "morialtis", "canis rex",
"cerastus", "atropos", "seneschal",
],
"Inquisition & Officio Assassinorum": [
"Imperial Agents": [
"inquisition", "inquisitor", "astra telepathica", "witch hunter",
"coteaz", "eisenhorn", "ravenor", "kyria draxus",
"officio assassinorum", "culexus", "vindicare", "callidus", "eversor",
"saint celestine", "living saint", "assassin",
],
# -- Chaos ----------------------------------------------------------
# -- Chaos -----------------------------------------------------------
"Chaos Space Marines": [
"chaos space marine", "black legion", "iron warriors", "word bearers",
"night lords", "alpha legion", "red corsairs", "world eaters",
"emperor's children", "chaos terminator", "chosen", "havoc",
"night lords", "alpha legion", "chaos terminator", "chosen", "havoc",
"raptor", "warp talon", "chaos cultist", "cultist", "daemon prince",
"chaos lord", "chaos sorcerer", "abaddon", "huron blackheart",
"kharn", "lucius the eternal", "ahriman", "typhus", "fabius bile",
"vashtorr", "defiler", "forgefiend", "maulerfiend", "helldrake",
"helbrute", "chaos spawn", "obliterator", "chaos rhino",
"chaos vindicator", "spartan assault", "mastodon", "typhon",
"chaos lord", "chaos sorcerer", "abaddon", "kharn", "ahriman",
"typhus", "fabius bile", "vashtorr", "defiler", "forgefiend",
"maulerfiend", "helldrake", "helbrute", "chaos spawn",
"obliterator", "chaos rhino", "chaos vindicator", "spartan assault",
"mastodon", "typhon",
],
"Death Guard": [
"death guard", "nurgle", "plague marine", "deathshroud", "mortarion",
@@ -113,11 +164,21 @@ SYSTEMS: dict[str, dict[str, list[str]]] = {
"skulltaker", "khorne", "slaanesh", "skull lord",
"shalaxi helbane",
],
"World Eaters": [
"world eater", "angron", "eightbound", "exalted eightbound",
"jakhals", "berserker", "skull lord",
],
"Emperor's Children": [
"emperor's children", "emperors children", "fulgrim",
"noise marine", "lucius the eternal", "flawless blades",
],
"Iron Warriors": ["iron warrior", "perturabo"],
"Red Corsairs": ["red corsair", "huron blackheart"],
"Chaos Knights": [
"chaos knight", "knight desecrator", "knight despoiler",
"knight rampager", "kharibdyss", "renegade knight",
],
# -- Xenos ----------------------------------------------------------
# -- Xenos -----------------------------------------------------------
"Orks": [
"ork", "orks", "orkz", "nob", "boyz", "grot", "weirdboy", "painboy",
"warboss", "ghazghkull", "meganob", "deff dread", "killa kan",
@@ -162,7 +223,7 @@ SYSTEMS: dict[str, dict[str, list[str]]] = {
],
"Aeldari": [
"aeldari", "eldar", "craftworld", "ulthwe", "biel-tan", "iyanden",
"alaitoc", "saim-hann", "ynnari", "farseer", "warlock",
"alaitoc", "saim-hann", "farseer", "warlock",
"spiritseer", "autarch", "asurmen", "guardian defender",
"ranger", "wraithguard", "wraithblades", "wraithlord",
"wraithknight", "dire avenger", "howling banshee",
@@ -178,62 +239,124 @@ SYSTEMS: dict[str, dict[str, list[str]]] = {
"kabalite warrior", "raider", "ravager", "venom", "razorwing",
"voidraven", "grotesque", "talos", "cronos",
"beast pack", "khymera", "razorwing flock", "asdrubael vect",
"lilith", "sathonyx", "the twin lance", "luriel",
"lilith", "sathonyx", "luriel",
],
"Harlequins": [
"harlequin", "death jester", "shadowseer", "solitaire",
"troupe", "starweaver", "voidweaver",
],
"Ynnari": [
"ynnari", "ynnead", "visarch", "yncarne", "yvraine",
"Leagues of Votann": [
"leagues of votann", "votann", "hearthkyn", "ironkin",
"brokhyr", "kahl", "cthonian", "transhyperic", "thegn",
],
},
"Other Systems": {
"Age of Sigmar": [
"age of sigmar", "stormcast", "eternals", "orruk", "warclan",
"kruleboyz", "gloomspite", "sylvaneth", "seraphon", "ossiarch",
"nighthaunt", "lumineth", "daughters of khaine", "fyreslayers",
"kharadron", "idoneth", "blades of khorne", "maggotkin",
"gaunt summoner", "soulblight", "freeguild", "cities of sigmar",
"skaven", "chaos warriors", "saurus", "skink", "liberator",
"sequitor", "evocator", "vindictor", "kaldor draigo",
"Age of Sigmar": {
"Stormcast Eternals": [
"stormcast", "eternal", "liberator", "sequitor", "evocator",
"vindictor", "judicators", "retributors", "sacrosanct",
"dracothian", "lord-arcane", "knight-questor", "neave blacktalon",
"stormdrake", "krondys", "karazai",
],
"Horus Heresy": [
"horus heresy", "legiones astartes", "pre-heresy",
"leviathan pattern", "fire raptor", "sicaran", "fellblade",
"kratos", "justaerin", "eisenstein", "veteran tactical",
"seeker squad", "suzerain", "invictarus", "fulgurite",
"Seraphon": ["seraphon", "saurus", "skink", "slann", "carnosaur",
"bastiladon", "stegadon", "razordon", "salamander"],
"Orruk Warclans": ["orruk", "warclan", "kruleboyz", "ironjawz",
"brute", "gorger", "weirdnob", "warchanter"],
"Gloomspite Gitz": ["gloomspite", "gitz", "moonclan", "spiderfang",
"troggoth", "loontouched", "zarbag", "skragrot"],
"Sylvaneth": ["sylvaneth", "treelord", "dryad", "branchwraith",
"alarielle", "kurnothi"],
"Ossiarch Bonereapers": ["ossiarch", "bonereaper", "mortek", "keldrek",
"katakros"],
"Nighthaunt": ["nighthaunt", "grimghast", "bladegheist", "chainghast",
"kurdoss", "lady olynder"],
"Lumineth Realm-lords": ["lumineth", "aelf", "vanari", "teclis",
"light of eltharion"],
"Daughters of Khaine": ["daughters of khaine", "khainite", "witch aelf",
"morgwaeth", "morathi"],
"Fyreslayers": ["fyreslayer", "vulkite", "hearthguard", "auric",
"grimhold"],
"Kharadron Overlords": ["kharadron", "skyfarer", "endofters",
"grundstok", "arcanaut"],
"Idoneth Deepkin": ["idoneth", "deepkin", "namarti", "isharann",
"volturnos"],
"Blades of Khorne": ["blades of khorne", "khorne", "skullgrinder",
"blood warrior", "wrathmonger"],
"Maggotkin of Nurgle": ["maggotkin", "nurgle", "blightking",
"rotmire", "orghotts"],
"Slaves to Darkness": ["slaves to darkness", "chaos warrior",
"chaos knight", "archaon", "gaunt summoner",
"everchosen"],
"Skaven": ["skaven", "clanrat", "stormvermin", "verminlord",
"grey seer", "warplock", "ratling gun"],
"Soulblight Gravelords": ["soulblight", "gravelord", "vampire",
"deathrattle", "koldhunter", "belladamma"],
"Cities of Sigmar": ["cities of sigmar", "freeguild", "windspeaker",
"callis and toll", "hammerers"],
},
"The Horus Heresy": {
"Space Marine Legions": [
"legiones astartes", "pre-heresy", "horus heresy", "justaerin",
"seeker squad", "suzerain", "invictarus", "veteran tactical",
"contemptor", "leviathan pattern", "sicaran", "fellblade",
"kratos", "fire raptor", "eisenstein",
],
"Mechanicum": ["mechanicum", "fulgurite", "magos", "thallax"],
},
"Other Games": {
"Kill Team": [
"kill team", "killteam", "killzone", "operative",
"hand of the archon", "void-dance troupe", "veteran guardsmen",
"spector squad", "wolf scouts", "scout squad kill",
],
"Necromunda": [
"necromunda", "goliath gang", "escher", "orlock", "cawdor",
"van saar", "ash wastes", "underhive",
"van saar", "ash wastes", "underhive", "sumpkrocs",
"forge boss", "sumpkroc", "matriarch", "phyrr cat",
],
"Legions Imperialis / Epic": [
"legions imperialis", "titan legio", "reaver titan",
"Adeptus Titanicus": [
"adepticus titanicus", "titan legio", "reaver titan",
"warhound titan", "warlord titan", "imperator titan",
"warmaster titan",
],
"Warhammer The Old World": [
"the old world", "bretonnia", "greenskins", "tomb kings",
"khemri", "chaos dwarf", "dog of war", "grail knight",
"warmaster titan", "titanicus",
],
"Warhammer Underworlds": ["warhammer underworlds", "underworlds",
"warband", "shadeborn", "embergard"],
"Aeronautica Imperialis": ["aeronautica", "t'au air caste",
"lightning fighter", "thunderbolt"],
},
"The Old World": {
"Kingdom of Bretonnia": ["bretonnia", "grail knight", "peasant",
"questing knight", "perrin"],
"Orc and Goblin Tribes": ["greenskin", "goblin", "night goblin",
"forest goblin", "black orc"],
"Tomb Kings of Khemri": ["tomb king", "khemri", "necrolith",
"skeleton archer"],
"Empire of Man": ["empire of man", "state troop", "greatsword",
"empire knight"],
"Warriors of Chaos": ["chaos dwarf", "dog of war", "chaos marauder"],
"High Elf Realms": ["high elf", "eltharion", "phoenix guard"],
"Wood Elf Realms": ["wood elf", "glade guard", "waywatcher"],
"Beastmen Brayherds": ["beastmen", "brayherd", "ghorgon", "cygor"],
"Dwarfen Mountain Holds": ["dwarf", "ironbreaker", "hammerer"],
},
"Middle-Earth": {
"Rivendell": ["rivendell", "elrond", "elendil", "isildur", "gil-galad",
"beorning", "grimbeorn", "rumil", "orophin"],
"Minas Tirith": ["minas tirith", "gondor", "boromir", "faramir"],
"Mordor": ["mordor", "orc", "orcs", "nazgul", "witch-king"],
"Iron Hills": ["iron hills", "dwarf", "mattock", "dwalin"],
"Moria": ["moria", "cave troll", "balrog"],
},
}
# ---------------------------------------------------------------------------
# Force Organization classes (keyword -> class)
# Classes (keyword -> class). GW shop categories / unit types.
# ---------------------------------------------------------------------------
CLASSES: list[tuple[str, list[str]]] = [
("Lords of War", [
("Titanic", [
"titan", "stompa", "gargantuan", "hierophant", "harridan", "hierodule",
"baneblade", "stormlord", "shadowsword", "hellhammer", "malcador",
"imperator", "warmaster", "tesseract vault", "plaguereaper",
"warhound", "warlord titan",
"warhound", "warlord titan", "ta'unar", "spermato", "titandeath",
]),
("Fortification", ["fortification", "bunker", "bastion", "wall of martyrs"]),
("Flyer", [
@@ -243,64 +366,76 @@ CLASSES: list[tuple[str, list[str]]] = [
"marauder", "razorwing", "voidraven", "crimson hunter",
"hemlock", "doom scythe", "night scythe", "harpy", "crone",
"fire raptor", "barracuda", "sun shark", "razorshark",
"wazbom blasta",
"wazbom blasta", "nephilim jetfighter", "corvus blackstar",
]),
("Dedicated Transport", [
("Vehicle", [
"transport", "rhino", "razorback", "chimera", "taurox", "trukk",
"battlewagon", "devilfish", "wave serpent", "ghost ark",
"land raider", "repulsor", "drop pod", "starweaver", "voidweaver",
"goliath truck", "rockgrinder", "cult limo", "raider", "venom",
"spartan assault",
"spartan assault", "predator", "vindicator", "whirlwind",
"basilisk", "hydra", "wyvern", "manticore", "deathstrike",
"onager dunecrawler", "kastelan", "hammerhead", "broadside",
"stormsurge", "r'varna", "soul grinder", "defiler", "forgefiend",
"maulerfiend", "plagueburst crawler", "annihilation barge",
"doomsday ark", "fire prism", "night spinner", "exorcist",
"immolator", "caladius", "coronus grav", "pallas grav",
"impulsor", "gladiator", "baal predator", "land speeder",
"ironstrider", "sydonian", "sentinel", "taurox", "kataphron",
]),
("Fast Attack", [
"fast attack", "assault marine", "assault intercessor", "inceptor",
"outrider", "bike squad", "scout biker", "thunderwolf", "raptor",
"warp talon", "flesh hound", "screamer", "fiend of slaanesh",
"hellion", "reaver jetbike", "scourge", "mandrake", "swooping hawk",
"shining spears", "windrider", "vyper", "war walker",
"canoptek wraith", "canoptek scarab", "canoptek spyder",
"ravener", "gargoyle", "spore mine", "mawloc", "trygon",
"pyrovore", "khymera", "beast pack", "razorwing flock",
"skyrunner", "zephyrim", "seraphim", "sentinel", "jackal",
"stormboy", "deffkopta", "warbiker", "skorpekh destroyer",
"sicarian", "ironstrider", "sydonian",
("Walker", [
"dreadnought", "redemptor", "contemptor", "leviathan pattern",
"brutalis", "ballistus", "deredeo", "wulfen dreadnought",
"killa kan", "deff dread", "wraithlord", "wraithknight",
"telemon", "achillus", "galatus", "paragon warsuit",
"nemesis dreadknight", "invictor", "talos", "cronos",
]),
("Heavy Support", [
"heavy support", "devastator", "eradicator", "hellblaster", "havoc",
"loota", "burna boy", "carnifex", "exocrine", "haruspex",
"zoanthrope", "venomthrope", "biovore", "hierodule",
"wraithlord", "wraithknight", "fire prism", "night spinner",
"predator", "vindicator", "whirlwind", "basilisk", "hydra",
"wyvern", "manticore", "deathstrike", "onager dunecrawler",
"kastelan", "kataphron", "hammerhead", "broadside", "stormsurge",
"r'varna", "soul grinder", "defiler", "forgefiend", "maulerfiend",
"plagueburst crawler", "bloat-drone", "blight hauler", "mutalith",
"annihilation barge", "doomsday ark", "obront", "dark reaper",
"retributor", "paragon warsuit", "morvenn", "exorcist", "immolator",
"caladius", "coronus grav", "telemon", "achillus", "galatus",
"pallas grav", "skorpekh",
("Monster", [
"monster", "carnifex", "exocrine", "haruspex", "mawloc", "trygon",
"tervigon", "hierodule", "hierophant", "mutalith", "bloat-drone",
"kharibdyss", "gorkanaut", "morkanaut", "magnus", "mortalith",
"fiend of slaanesh", "plague drone", "cave troll", "balrog",
"carnosaur", "stegadon", "bastiladon", "grorgeous",
]),
("Elites", [
"elites", "veteran", "terminator", "sternguard", "bladeguard",
"aggressor", "reiver", "vanguard veteran", "dreadnought",
"redemptor", "contemptor", "leviathan pattern", "wulfen",
"wolf guard", "deathshroud", "plague marine", "blightlord",
"poxwalker", "rubric marine", "rubricae", "scarab occult",
"tzaangor", "chosen", "possessed", "obliterator", "daemonette",
"bloodletter", "plaguebearer", "nurgling", "juggernaut",
"chaos cultist", "cultist", "acolyte hybrid", "aberrant",
("Mounted", [
"mounted", "thunderwolf", "outrider", "bike squad", "scout biker",
"reaver jetbike", "windrider", "skyrunner", "warbiker",
"allarus custodian on", "vertus praetor", "drakeling",
"boarboy", "gruntaz", "khan on", "dracothian guard",
]),
("Battleline", [
"battleline", "troops", "tactical squad", "intercessor",
"incursor", "infiltrator", "infantry squad", "conscript",
"krieg", "catachan", "cadian", "vostroyan", "penal legion",
"ratling", "veteran squad", "fire warrior", "pathfinder team",
"kroot", "termagant", "hormagaunt", "necron warrior",
"necron immortal", "guardian defender", "storm guardian",
"kabalite warrior", "boyz", "gretchin", "scion", "scout squad",
"cultist", "plague marine squad",
]),
("Infantry", [
"infantry", "veteran", "terminator", "sternguard", "bladeguard",
"aggressor", "reiver", "wulfen", "wolf guard", "deathshroud",
"blightlord", "poxwalker", "rubric marine", "rubricae",
"scarab occult", "tzaangor", "chosen", "possessed", "obliterator",
"daemonette", "bloodletter", "plaguebearer", "nurgling",
"juggernaut", "chaos cultist", "acolyte hybrid", "aberrant",
"neophyte", "purestrain", "genestealer", "lictor", "deathmark",
"flayed one", "lychguard", "wraithguard", "wraithblades",
"howling banshee", "striking scorpion", "warp spider",
"fire dragon", "incubi", "wrack", "grotesque", "talos", "cronos",
"troupe", "killa kan", "deff dread", "meganob", "nobz", "nob",
"kommando", "vespid", "sanguinary guard", "sanguinor",
"ancient", "allarus", "vertus praetor", "venatari", "aquilon",
"sagittarum", "witchseeker", "prosecutor", "vigilator",
"vindicare", "callidus", "eversor", "culexus", "assassin",
"fire dragon", "incubi", "wrack", "grotesque", "assault marine",
"assault intercessor", "inceptor", "raptor", "warp talon",
"flesh hound", "screamer", "hellion", "scourge", "mandrake",
"swooping hawk", "shining spears", "canoptek wraith",
"canoptek scarab", "canoptek spyder", "ravener", "gargoyle",
"spore mine", "pyrovore", "khymera", "beast pack",
"razorwing flock", "zephyrim", "seraphim", "stormboy",
"deffkopta", "skorpekh destroyer", "skorpekh", "retributor",
"dark reaper", "sicarian", "ruststalker", "skitarii",
"hormagaunt brood", "termagant brood", "vanguard",
]),
("HQ", [
"hq", "captain", "chapter master", "primarch", "lieutenant",
("Hero", [
"hero", "hq", "captain", "chapter master", "primarch", "lieutenant",
"librarian", "chaplain", "apothecary", "farseer", "warlock",
"spiritseer", "autarch", "warboss", "big mek", "weirdboy",
"painboy", "commander", "ethereal", "hive tyrant", "swarmlord",
@@ -316,18 +451,12 @@ CLASSES: list[tuple[str, list[str]]] = [
"dialogus", "hospitaller", "trajann", "valdor", "inquisitor",
"coteaz", "eisenhorn", "ravenor", "draxus", "celestine",
"commissar", "priest", "enginseer", "dominus", "manipulus",
"archmagos", "magos", "belisarius cawl", "farsight", "shadowsun",
"archmagos", "belisarius cawl", "farsight", "shadowsun",
"aun'va", "darkstrider", "longstrike", "shade runner",
]),
("Troops", [
"troops", "battleline", "tactical squad", "intercessor",
"incursor", "infiltrator", "infantry squad", "conscript",
"krieg", "catachan", "cadian", "vostroyan", "penal legion",
"ratling", "veteran squad", "fire warrior", "pathfinder team",
"kroot", "termagant", "hormagaunt", "necron warrior",
"necron immortal", "guardian defender", "storm guardian",
"kabalite warrior", "boyz", "gretchin",
"scion", "scout squad",
("Rules & Accessories", [
"codex", "rulebook", "rules", "data cards", "datacards",
"warscroll", "index card", "cheat sheet", "reference",
]),
]
@@ -338,3 +467,63 @@ FACTION_ALIASES: dict[str, dict[str, str]] = {
}
UNIT_CLASSES: dict[str, str] = {kw: klass for klass, kws in CLASSES for kw in kws}
# GW group of each faction (matches the GameSystemsRoot.lvl1 values in the
# Algolia index). Used when a keyword-classified file cannot be fuzzy-matched.
GROUP_OF_FACTION: dict[str, str] = {
faction: group
for group, factions in {
"Space Marines": [
"Space Marines", "Ultramarines", "Blood Angels", "Dark Angels",
"Space Wolves", "Black Templars", "Imperial Fists", "Raven Guard",
"Iron Hands", "White Scars", "Salamanders", "Grey Knights",
"Deathwatch", "Crimson Fists", "Flesh Tearers",
],
"Armies of the Imperium": [
"Astra Militarum", "Adeptus Mechanicus", "Adepta Sororitas",
"Adeptus Custodes", "Imperial Knights", "Imperial Agents",
],
"Armies of Chaos": [
"Chaos Space Marines", "Death Guard", "Thousand Sons",
"Chaos Daemons", "Chaos Knights", "World Eaters",
"Emperor's Children", "Iron Warriors", "Red Corsairs",
],
"Xenos Armies": [
"Orks", "T'au Empire", "Tyranids", "Genestealer Cults", "Necrons",
"Aeldari", "Drukhari", "Harlequins", "Leagues of Votann",
],
"Grand Alliance Order": [
"Stormcast Eternals", "Seraphon", "Sylvaneth",
"Lumineth Realm-lords", "Daughters of Khaine", "Fyreslayers",
"Kharadron Overlords", "Idoneth Deepkin", "Cities of Sigmar",
],
"Grand Alliance Destruction": [
"Orruk Warclans", "Gloomspite Gitz",
],
"Grand Alliance Death": [
"Ossiarch Bonereapers", "Nighthaunt", "Soulblight Gravelords",
],
"Grand Alliance Chaos": [
"Slaves to Darkness", "Blades of Khorne", "Maggotkin of Nurgle",
"Skaven",
],
"Space Marine Legions": ["Space Marine Legions", "Mechanicum"],
"Kill Team": ["Kill Team"],
"Necromunda": ["Necromunda"],
"Adeptus Titanicus": ["Adeptus Titanicus"],
"Warhammer Underworlds": ["Warhammer Underworlds"],
"Aeronautica Imperialis": ["Aeronautica Imperialis"],
"Armies of the Old World": [
"Kingdom of Bretonnia", "Orc and Goblin Tribes",
"Tomb Kings of Khemri", "Empire of Man", "Warriors of Chaos",
"High Elf Realms", "Wood Elf Realms", "Beastmen Brayherds",
"Dwarfen Mountain Holds",
],
"Rivendell": ["Rivendell"],
"Minas Tirith": ["Minas Tirith"],
"Mordor": ["Mordor"],
"Iron Hills": ["Iron Hills"],
"Moria": ["Moria"],
}.items()
for faction in factions
}