Skip to main content
Glama
by frap129
entity_mapper.py6.48 kB
"""Entity type mapper for OrcBrew to LoreKeeper entity types.""" import logging from typing import Any from pydantic import ValidationError from lorekeeper_mcp.models.orcbrew import OrcBrewCreature, OrcBrewSpell logger = logging.getLogger(__name__) # Mapping of OrcBrew entity type keys to LoreKeeper entity types ORCBREW_TO_LOREKEEPER: dict[str, str | None] = { "orcpub.dnd.e5/spells": "spells", "orcpub.dnd.e5/monsters": "creatures", # Note: using 'creatures' not 'monsters' "orcpub.dnd.e5/classes": "classes", "orcpub.dnd.e5/subclasses": "subclasses", "orcpub.dnd.e5/races": "species", # Map to 'species' for consistency "orcpub.dnd.e5/subraces": "subraces", "orcpub.dnd.e5/backgrounds": "backgrounds", "orcpub.dnd.e5/feats": "feats", "orcpub.dnd.e5/languages": "languages", "orcpub.dnd.e5/weapons": "weapons", "orcpub.dnd.e5/armor": "armor", "orcpub.dnd.e5/magic-items": "magicitems", # Unsupported types (return None to skip) "orcpub.dnd.e5/invocations": None, "orcpub.dnd.e5/selections": None, } def map_entity_type(orcbrew_type: str) -> str | None: """Map OrcBrew entity type to LoreKeeper entity type. Args: orcbrew_type: OrcBrew entity type key (e.g., "orcpub.dnd.e5/spells") Returns: LoreKeeper entity type (e.g., "spells") or None if unsupported """ return ORCBREW_TO_LOREKEEPER.get(orcbrew_type) def normalize_entity( entity: dict[str, Any], orcbrew_type: str, ) -> dict[str, Any]: """Normalize OrcBrew entity to LoreKeeper format. Args: entity: OrcBrew entity dictionary orcbrew_type: OrcBrew entity type key Returns: Normalized entity dictionary with LoreKeeper schema Raises: ValueError: If entity is missing required fields Note: Validates entities through OrcBrew Pydantic models after normalization. Validation errors are logged as warnings since OrcBrew data is often incomplete but still usable. """ # Extract or generate slug slug = entity.get("key") if not slug: # Try to generate from name name = entity.get("name", "") if not name: raise ValueError("Entity missing both 'key' and 'name' fields") slug = name.lower().replace(" ", "-").replace("'", "") # Extract name name = entity.get("name", slug.replace("-", " ").title()) # Extract source book source = entity.get("_source_book", "Unknown") if "option-pack" in entity: source = entity["option-pack"] # Extract document name (book name is the document) document = entity.get("option-pack") or entity.get("_source_book", "Unknown") # Build base normalized entity with ALL fields from OrcBrew (not just indexed) normalized: dict[str, Any] = { "slug": slug, "name": name, "source": source, "source_api": "orcbrew", "document": document, "data": {k: v for k, v in entity.items() if not k.startswith("_")}, } # Extract ALL OrcBrew fields to top level (comprehensive extraction) # This ensures fields like concentration, ritual, components are available for key, value in entity.items(): if not key.startswith("_") and key not in normalized: # Convert kebab-case to snake_case snake_key = key.replace("-", "_") normalized[snake_key] = value # Copy indexed fields to top level for filtering (may override some above) lorekeeper_type = map_entity_type(orcbrew_type) if lorekeeper_type: normalized.update(_extract_indexed_fields(entity, lorekeeper_type)) # Validate through OrcBrew Pydantic models _validate_through_model(normalized, lorekeeper_type) return normalized def _validate_through_model(normalized: dict[str, Any], entity_type: str | None) -> None: """Validate normalized entity through appropriate OrcBrew Pydantic model. Args: normalized: Normalized entity data entity_type: LoreKeeper entity type Note: Validation errors are logged as warnings rather than raised, since OrcBrew data is often incomplete but still usable. """ try: if entity_type == "spells": OrcBrewSpell.model_validate(normalized) elif entity_type == "creatures": OrcBrewCreature.model_validate(normalized) # Other types don't have OrcBrew models yet - pass through except ValidationError as e: logger.warning( "OrcBrew entity '%s' failed validation: %s", normalized.get("name", normalized.get("slug", "unknown")), e, ) def _extract_indexed_fields( entity: dict[str, Any], entity_type: str, ) -> dict[str, Any]: """Extract indexed fields for an entity type. Args: entity: OrcBrew entity data entity_type: LoreKeeper entity type Returns: Dictionary of indexed field values """ indexed: dict[str, Any] = {} if entity_type == "spells": if "level" in entity: indexed["level"] = entity["level"] if "school" in entity: indexed["school"] = entity["school"] if "concentration" in entity: indexed["concentration"] = entity["concentration"] if "ritual" in entity: indexed["ritual"] = entity["ritual"] elif entity_type == "creatures": # Note: 'challenge' is NOT copied here - let OrcBrewCreature model handle # the conversion from challenge -> challenge_rating if "type" in entity: indexed["type"] = entity["type"] if "size" in entity: indexed["size"] = entity["size"] elif entity_type == "weapons": if "category" in entity: indexed["category"] = entity["category"] if "damage-type" in entity: indexed["damage_type"] = entity["damage-type"] elif entity_type == "armor": if "category" in entity: indexed["category"] = entity["category"] if "armor-class" in entity: indexed["armor_class"] = entity["armor-class"] elif entity_type == "magicitems": if "type" in entity: indexed["type"] = entity["type"] if "rarity" in entity: indexed["rarity"] = entity["rarity"] if "requires-attunement" in entity: indexed["requires_attunement"] = entity["requires-attunement"] return indexed

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/frap129/lorekeeper-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server