kirby-api 0.2.2__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kirby_api-0.2.2/kirby_api.egg-info → kirby_api-0.2.3}/PKG-INFO +2 -2
- kirby_api-0.2.3/kirby/_migrations/versions/20261005_0002_build_doc_fields.py +43 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/build_doc_store.py +30 -5
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/cli.py +0 -49
- kirby_api-0.2.3/kirby/builds/json_importer.py +117 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/models.py +9 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/power_library_importer.py +69 -98
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/routes.py +6 -6
- {kirby_api-0.2.2 → kirby_api-0.2.3/kirby_api.egg-info}/PKG-INFO +2 -2
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby_api.egg-info/SOURCES.txt +1 -1
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby_api.egg-info/requires.txt +1 -1
- {kirby_api-0.2.2 → kirby_api-0.2.3}/pyproject.toml +2 -2
- kirby_api-0.2.2/kirby/builds/json_exporter.py +0 -277
- kirby_api-0.2.2/kirby/builds/json_importer.py +0 -553
- {kirby_api-0.2.2 → kirby_api-0.2.3}/LICENSE +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/README.md +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/README +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/alembic.ini +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/env.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/script.py.mako +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/1b7affd8668e_initial_schema_users_tenants_characters_.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260424_0001_combat_tables.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260429_0001_add_combatant_jsonb.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260501_0001_codex_targeted_pages.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260501_0002_hd_relational_schema.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260501_0003_definitions_codex_chunk_id.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260503_0001_per_template_base_values.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0001_pr1_relational_combat_schema.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0002_seed_tactics_and_profiles.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0003_backfill_combat_relational.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0004_drop_combat_jsonb_columns.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0005_pr1_index_audit_fills.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0006_pending_phase_modifiers.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0007_combat_session_scene_id.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0008_morale_expires_at_seq.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0009_is_stunned.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0010_tactic_catalog_expansion.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0011_slam_velocity_tactic.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0012_mind_control_state.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0013_mental_illusion_state.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0014_mental_entangle_state.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0015_was_surprised.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0016_catalog_expansion_for_new_actions.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0017_combat_started.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0018_physical_entangle.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0019_stat_drains.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0020_continuous_effects.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0021_lockout_state.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260504_0022_dispelled_powers.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260506_0001_campaign_management_v1.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260506_0002_extend_characters_for_pc.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260506_0003_combatant_control_mode.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260506_0004_brain_embeddings.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260507_0001_knowledge_tier.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260507_0002_normalize_jsonb.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260507_0003_drop_legacy_jsonb.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260507_0004_canon_embeddings.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260508_0001_comic_canon_init.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260511_0001_power_library_init.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260514_0001_combat_session_ai_provider.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260528_0001_coordinate_raw_tables.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260528_0002_coordination_initiator.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260529_0001_character_def_rename.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260529_0002_character_instance.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260529_0003_team_def.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260529_0004_team_member_def.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260529_0005_team_instance.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260529_0006_team_def_real_history.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260529_0007_team_page.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260529_0008_team_narrative.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260529_0009_migrate_org_journal_entries_to_team.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260530_0002_templates_catalog_columns.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260530_0003_reconcile_template_category_vocab.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260531_0001_align_catalog_to_engine_schema.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260601_0001_build_doc_fields.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260605_0001_proposals.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260605_0002_membership_roster.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260605_0003_xp_award.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260606_0001_base_drift_guard.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260607_0001_maneuver_details.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260609_0001_scene_wall_def_value.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260609_0002_combat_session_constructs.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260611_0001_arena_hazard_kind_status_tokens.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260611_0002_tactic_entry_role.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260611_0003_seed_archetype_profiles.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260612_0001_combatant_held_construct.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260613_0001_combatant_is_hidden.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260613_0002_combatant_flashed_sense.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260614_0001_construct_sense_group.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260614_0002_image_decoy.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260618_0001_character_extraction_tables.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260621_0001_combat_session_combatant_slot.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260621_0002_vpp_power.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260623_0001_power_categories.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260726_0001_combat_intent.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260727_0001_intent_checks_since_form.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260728_0001_stunned_at_sequence.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260729_0001_intent_budget_turns.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260730_0001_standable_structures.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260731_0001_climb_difficulty.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260731_0002_widen_character_object_alias.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260802_0001_identity_by_id_not_xmlid.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260803_0001_combatant_opening_state.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260804_0001_combat_random_fight.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260804_0002_presence_attack_effects.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260816_0001_template_level_power.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260831_0001_construct_no_teleport.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260831_0002_physical_entangle_state.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260831_0003_character_object_text.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260902_0001_combatant_in_hero_id.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260902_0002_object_affects_flags.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260917_0001_session_names_a_seat.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260918_0000_drop_combat_tables.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260918_0001_harness_tables.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20260918_0002_narration_speaker_id.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/20261005_0001_knowledge_indexes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/21980c836860_add_asset_id_refs.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/_migrations/versions/ba28e71c955d_codex_init.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/admin/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/auth/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/auth/authz.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/auth/dependencies.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/auth/jwt.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/auth/models.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/auth/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/auth/users.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/category_extraction.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/codex_linker.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/combatant_loader.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/importer.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/power_categories.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/power_library_service.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/recompute.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/builds/template_provider.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/_shared.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/membership_service.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/models.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/routes_acl.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/routes_brain.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/routes_documents.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/routes_knowledge.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/routes_membership.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/routes_relationships.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/routes_roster.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/routes_sessions_events.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/routes_xp.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/services/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/services/brain.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/services/conflicts.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/services/embeddings.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/services/extract.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/services/generation.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/services/legacy_translation.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/services/permissions.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/campaigns/services/recap.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/_shared.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/build_routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/combat_seed.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/corpus_copy.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/models.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/proposals/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/proposals/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/proposals/service.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/services/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/services/character_info_backfill.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/services/effective.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/services/writeup_persistence.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/variants_service.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/characters/xp_service.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/cli.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/cli.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/deps.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/models.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/repositories.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/schemas.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/services/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/services/embed.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/services/extract.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/services/index.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/services/orchestrator.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/services/search.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/services/villain_extractor.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/services/villain_writeup_linker.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/codex/workers.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/choosers.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/create.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/events.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/harness.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/models.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/random_fight/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/random_fight/models.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/random_fight/outcome.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/random_fight/roster.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/random_fight/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/random_fight/runner.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/random_fight/schemas.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/random_fight/session.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/random_fight/worker.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/schemas.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/services/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/services/connection_hub.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/services/publish.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/combat/services/subscriber.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/comic_canon/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/comic_canon/models.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/comic_canon/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/comic_canon/services/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/comic_canon/services/archetypes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/comic_canon/services/embeddings.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/engines/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/engines/cost/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/engines/cost/hdc.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/library/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/main.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/media/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/media/asset_uses.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/media/client.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/media/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/media/schemas.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/media/taxonomy.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/settings.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/shared/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/shared/db.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/teams/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/teams/models.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/teams/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/teams/schemas.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/teams/services/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/teams/services/effective.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/templates/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/tenants/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/tenants/models.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/tenants/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/users/__init__.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/users/routes.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby/version.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby_api.egg-info/dependency_links.txt +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby_api.egg-info/entry_points.txt +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/kirby_api.egg-info/top_level.txt +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/setup.cfg +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/tests/test_knowledge_indexes_migration.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/tests/test_public_vocabulary.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/tests/test_teardown.py +0 -0
- {kirby_api-0.2.2 → kirby_api-0.2.3}/tests/test_wheel_contents.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kirby-api
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Kirby — HERO System VTT backend API (FastAPI mono)
|
|
5
5
|
Author: PeterB
|
|
6
6
|
License: PolyForm Noncommercial License 1.0.0
|
|
@@ -22,7 +22,7 @@ Requires-Dist: cryptography>=44
|
|
|
22
22
|
Requires-Dist: python-jose[cryptography]>=3.3
|
|
23
23
|
Requires-Dist: pyjwt[crypto]>=2.10
|
|
24
24
|
Requires-Dist: redis>=5.2
|
|
25
|
-
Requires-Dist: kirby-cost>=0.6.
|
|
25
|
+
Requires-Dist: kirby-cost>=0.6.2
|
|
26
26
|
Requires-Dist: lxml>=5.3
|
|
27
27
|
Requires-Dist: python-multipart>=0.0.20
|
|
28
28
|
Requires-Dist: email-validator>=2.2
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""columns for the build-doc fields kirby-cost 0.6.2 writes
|
|
2
|
+
|
|
3
|
+
kirby-cost 0.6.2 stops dropping three things from the canonical build doc,
|
|
4
|
+
and a doc stored here must keep them or the database rebuild differs from the
|
|
5
|
+
engine's own load:
|
|
6
|
+
|
|
7
|
+
* ``native_tongue`` on a Language -> ``object_skill_mode.native_tongue``.
|
|
8
|
+
A native tongue is free; without the flag Bokor rebuilds at 279 against
|
|
9
|
+
his file's 276.
|
|
10
|
+
* the Resistant Protection split -> ``character_objects.pd_levels`` /
|
|
11
|
+
``ed_levels`` / ``md_levels`` / ``powd_levels``. Cost is by LEVELS, so the
|
|
12
|
+
split moves no points; it is which half is physical, which a fight needs.
|
|
13
|
+
* the EQUIPMENT section needs no column: it is stored as rows of
|
|
14
|
+
object_type ``equipment``.
|
|
15
|
+
|
|
16
|
+
All nullable and additive: NULL reads back as "not written", which is what
|
|
17
|
+
every row stored before this migration is.
|
|
18
|
+
|
|
19
|
+
Revision ID: 20261005_0002
|
|
20
|
+
Revises: 20261005_0001
|
|
21
|
+
Create Date: 2026-10-05
|
|
22
|
+
"""
|
|
23
|
+
import sqlalchemy as sa
|
|
24
|
+
from alembic import op
|
|
25
|
+
|
|
26
|
+
revision = "20261005_0002"
|
|
27
|
+
down_revision = "20261005_0001"
|
|
28
|
+
branch_labels = None
|
|
29
|
+
depends_on = None
|
|
30
|
+
|
|
31
|
+
_SPLIT = ("pd_levels", "ed_levels", "md_levels", "powd_levels")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def upgrade() -> None:
|
|
35
|
+
for col in _SPLIT:
|
|
36
|
+
op.add_column("character_objects", sa.Column(col, sa.Integer(), nullable=True))
|
|
37
|
+
op.add_column("object_skill_mode", sa.Column("native_tongue", sa.Boolean(), nullable=True))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def downgrade() -> None:
|
|
41
|
+
op.drop_column("object_skill_mode", "native_tongue")
|
|
42
|
+
for col in _SPLIT:
|
|
43
|
+
op.drop_column("character_objects", col)
|
|
@@ -34,7 +34,9 @@ _SCALAR_FIELDS = ("levels", "option_id", "input", "notes", "text", "base_cost",
|
|
|
34
34
|
# therefore means "not stated", i.e. the engine's True.
|
|
35
35
|
"affects_primary", "affects_total",
|
|
36
36
|
"level_cost", "level_value", "min_cost", "max_cost",
|
|
37
|
-
"selected", "required", "private", "group", "sense_active"
|
|
37
|
+
"selected", "required", "private", "group", "sense_active",
|
|
38
|
+
# Resistant Protection's split (kirby-cost >= 0.6.2).
|
|
39
|
+
"pd_levels", "ed_levels", "md_levels", "powd_levels")
|
|
38
40
|
_COL = {"input": "input_value", # build-doc 'input' is stored as input_value
|
|
39
41
|
"text": "text_value", # 'text' collides with the SQL type name
|
|
40
42
|
"group": "sense_group"} # 'group' is a SQL reserved word -> sense_group
|
|
@@ -44,12 +46,15 @@ _COL = {"input": "input_value", # build-doc 'input' is stored as input_value
|
|
|
44
46
|
# min_set/max_set flag is on (value may be exactly 0.0). The DB distinguishes
|
|
45
47
|
# "set to 0.0" (stored 0.0) from "unset" (stored NULL), so a NULL check alone
|
|
46
48
|
# is the right gate for those two.
|
|
47
|
-
_ZERO_SUPPRESSED = {"levels", "base_cost", "level_cost", "level_value"
|
|
49
|
+
_ZERO_SUPPRESSED = {"levels", "base_cost", "level_cost", "level_value",
|
|
50
|
+
"pd_levels", "ed_levels", "md_levels", "powd_levels"}
|
|
48
51
|
_SECTIONS = ("characteristics", "powers", "skills", "perks", "talents", "disadvantages",
|
|
49
|
-
"martial_arts")
|
|
52
|
+
"martial_arts", "equipment")
|
|
53
|
+
# Carried gear is its own section (kirby-cost >= 0.6.2) and its own row type,
|
|
54
|
+
# so it reconstructs into "equipment" rather than joining the innate powers.
|
|
50
55
|
_OTYPE = {"characteristics": "characteristic", "powers": "power", "skills": "skill",
|
|
51
56
|
"perks": "perk", "talents": "talent", "disadvantages": "complication",
|
|
52
|
-
"martial_arts": "maneuver"}
|
|
57
|
+
"martial_arts": "maneuver", "equipment": "equipment"}
|
|
53
58
|
_SECTION_TYPES = frozenset(_OTYPE.values())
|
|
54
59
|
|
|
55
60
|
# ForceWall flat fields (engine emits length_levels/height_levels/body_levels)
|
|
@@ -109,7 +114,8 @@ def _persist_object(db, character_id, o, *, object_type, parent_id, position,
|
|
|
109
114
|
# presence of 'familiarity' marks a skill-mode row.
|
|
110
115
|
if "familiarity" in o:
|
|
111
116
|
db.add(ObjectSkillMode(object_id=row.id,
|
|
112
|
-
**{f: o.get(f) for f in _SKILL_FIELDS}
|
|
117
|
+
**{f: o.get(f) for f in _SKILL_FIELDS},
|
|
118
|
+
native_tongue=o.get("native_tongue")))
|
|
113
119
|
# Maneuver detail row (marker: engine emits "maneuver": True on Maneuver objects).
|
|
114
120
|
# Note: o.get(f) is not None keeps empty-string effect ("") — do NOT change to truthiness.
|
|
115
121
|
if o.get("maneuver"):
|
|
@@ -142,6 +148,21 @@ def _persist_object(db, character_id, o, *, object_type, parent_id, position,
|
|
|
142
148
|
parent_id=row.id, position=0, costs=costs)
|
|
143
149
|
return row
|
|
144
150
|
|
|
151
|
+
def persist_object(db: Session, character_id, o: dict[str, Any], *,
|
|
152
|
+
object_type: str, parent_id: int | None, position: int,
|
|
153
|
+
costs: dict[str, dict[str, float]] | None = None) -> CharacterObject:
|
|
154
|
+
"""Write ONE build-doc object (and its modifiers, adders, sub-powers,
|
|
155
|
+
Endurance Reserve REC and typed child rows) under ``character_id``.
|
|
156
|
+
|
|
157
|
+
The same writer ``persist_build_doc`` uses for a whole character, for a
|
|
158
|
+
caller that stores objects one at a time — the power library catalogues
|
|
159
|
+
single powers under a shared owner. ``costs`` is keyed by doc id, as
|
|
160
|
+
``extract_costs(...).per_object`` is.
|
|
161
|
+
"""
|
|
162
|
+
return _persist_object(db, character_id, o, object_type=object_type,
|
|
163
|
+
parent_id=parent_id, position=position, costs=costs)
|
|
164
|
+
|
|
165
|
+
|
|
145
166
|
def save_build_doc(db: Session, character, doc: dict[str, Any]) -> tuple[dict[str, Any], Any]:
|
|
146
167
|
"""Persist an edited build doc onto a character. Normalizes through the
|
|
147
168
|
engine (build_from_json validates + canonicalizes), then persist_loaded
|
|
@@ -292,6 +313,10 @@ def _row_to_doc_obj(row, rows_by_id, children_by_parent, fw, sm, mv, prov, rowid
|
|
|
292
313
|
o[f] = val
|
|
293
314
|
else:
|
|
294
315
|
o[f] = bool(val)
|
|
316
|
+
# Written by the engine on a Language only, so NULL means "not a
|
|
317
|
+
# Language" (or stored before the column existed) and stays absent.
|
|
318
|
+
if smr.native_tongue is not None:
|
|
319
|
+
o["native_tongue"] = bool(smr.native_tongue)
|
|
295
320
|
if row.id in prov:
|
|
296
321
|
o["provides"] = prov[row.id]
|
|
297
322
|
# Maneuver detail fields (reverse of _persist_object; emission conditions
|
|
@@ -71,28 +71,6 @@ def cmd_import_template(args) -> int:
|
|
|
71
71
|
return 0
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
def cmd_import_corpus(args) -> int:
|
|
75
|
-
from kirby.builds.json_importer import (
|
|
76
|
-
JSONImporter, _resolve_owner, _resolve_tenant,
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
with SessionLocal() as db:
|
|
80
|
-
tenant_id = _resolve_tenant(db, args.tenant)
|
|
81
|
-
owner_id = _resolve_owner(db, args.owner)
|
|
82
|
-
importer = JSONImporter(db, tenant_id=tenant_id, owner_id=owner_id)
|
|
83
|
-
counts = importer.import_directory(
|
|
84
|
-
args.corpus_dir,
|
|
85
|
-
glob=args.glob,
|
|
86
|
-
skip_prefix=tuple(args.skip_prefix),
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
sys.stderr.write("\nImported by source:\n")
|
|
90
|
-
for src in sorted(counts):
|
|
91
|
-
sys.stderr.write(f" {src:12s} {counts[src]:>5d}\n")
|
|
92
|
-
sys.stderr.write(f" {'TOTAL':12s} {sum(counts.values()):>5d}\n")
|
|
93
|
-
return 0
|
|
94
|
-
|
|
95
|
-
|
|
96
74
|
def cmd_backfill_power_categories(args) -> int:
|
|
97
75
|
"""Label stored powers + VPP pools with special-effect categories.
|
|
98
76
|
|
|
@@ -612,33 +590,6 @@ def main() -> int:
|
|
|
612
590
|
)
|
|
613
591
|
p_import.set_defaults(func=cmd_import_template)
|
|
614
592
|
|
|
615
|
-
p_corpus = sub.add_parser(
|
|
616
|
-
"import-corpus",
|
|
617
|
-
help="Bulk-import a JSON corpus directory into characters + character_objects",
|
|
618
|
-
)
|
|
619
|
-
p_corpus.add_argument(
|
|
620
|
-
"corpus_dir",
|
|
621
|
-
help="Directory containing JSON files (e.g. tests/fixtures/oracle/)",
|
|
622
|
-
)
|
|
623
|
-
p_corpus.add_argument(
|
|
624
|
-
"--tenant", default="throwback",
|
|
625
|
-
help="Tenant slug to attribute imported characters to (default: throwback)",
|
|
626
|
-
)
|
|
627
|
-
p_corpus.add_argument(
|
|
628
|
-
"--owner", default="local-bootstrap",
|
|
629
|
-
help="User sub to set as owner (default: local-bootstrap)",
|
|
630
|
-
)
|
|
631
|
-
p_corpus.add_argument(
|
|
632
|
-
"--glob", default="*.json",
|
|
633
|
-
help="Filename glob within corpus_dir (default: *.json)",
|
|
634
|
-
)
|
|
635
|
-
p_corpus.add_argument(
|
|
636
|
-
"--skip-prefix", action="append", default=[],
|
|
637
|
-
help="Skip files starting with this prefix (repeatable). "
|
|
638
|
-
"E.g. --skip-prefix bestiary__ to do villains only.",
|
|
639
|
-
)
|
|
640
|
-
p_corpus.set_defaults(func=cmd_import_corpus)
|
|
641
|
-
|
|
642
593
|
p_reimport = sub.add_parser(
|
|
643
594
|
"reimport-hdc",
|
|
644
595
|
help="Re-import a directory of .hdc files (idempotent; backfills maneuvers)",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Character import — the canonical path only.
|
|
2
|
+
|
|
3
|
+
``JSONImporter.import_hdc_file`` parses an .hdc through the canon engine and
|
|
4
|
+
persists the complete build doc relationally (``persist_loaded``): the engine
|
|
5
|
+
is the single source of structure AND cost.
|
|
6
|
+
|
|
7
|
+
The module used to also read HD-shaped JSON files (the old HDCConverter
|
|
8
|
+
corpus) and write rows by hand, summing ``total_cost`` itself and filling
|
|
9
|
+
child tables no reader used. That path is gone (kirby-api 0.2.3): the corpus
|
|
10
|
+
is the database, and an .hdc enters it once, through ``import_hdc_file``. The
|
|
11
|
+
class keeps its name because the tests and tooling that construct it do.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from uuid import UUID
|
|
18
|
+
|
|
19
|
+
from sqlalchemy.orm import Session
|
|
20
|
+
|
|
21
|
+
from kirby.auth.models import User
|
|
22
|
+
from kirby.characters.models import CharacterDef
|
|
23
|
+
from kirby.tenants.models import Tenant
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class JSONImporter:
|
|
30
|
+
"""Imports .hdc files through the canon engine into kirby's tables."""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self, db: Session, tenant_id: UUID, owner_id: UUID,
|
|
34
|
+
*, category_bearer: str = "",
|
|
35
|
+
) -> None:
|
|
36
|
+
self.db = db
|
|
37
|
+
self.tenant_id = tenant_id
|
|
38
|
+
self.owner_id = owner_id
|
|
39
|
+
# GM Tannhauser JWT used to label powers with special-effect categories
|
|
40
|
+
# via the generation service. Optional: without it, import proceeds and pools stay
|
|
41
|
+
# unrestricted (see _populate_categories).
|
|
42
|
+
self.category_bearer = category_bearer
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------- public
|
|
45
|
+
|
|
46
|
+
def import_hdc_file(
|
|
47
|
+
self, hdc_path: str | Path, *, source: str, is_npc: bool = True,
|
|
48
|
+
) -> CharacterDef:
|
|
49
|
+
"""Parse one .hdc via the canon engine, persist its complete build doc
|
|
50
|
+
relationally (Spec 2/3). The engine is the single source of structure AND
|
|
51
|
+
cost; kirby stores the build doc + a recomputable cost cache."""
|
|
52
|
+
from kirby.engines.cost.hdc import build_loader
|
|
53
|
+
from kirby.builds.build_doc_store import persist_loaded
|
|
54
|
+
|
|
55
|
+
hdc_path = Path(hdc_path)
|
|
56
|
+
# Costs come from kirby's own Main6E catalogue — the same source
|
|
57
|
+
# load_hdc uses. kirby-cost ships no template, so a bare HDCLoader()
|
|
58
|
+
# would depend on a file the deployment may not have.
|
|
59
|
+
loaded = build_loader(self.db).load_file(str(hdc_path))
|
|
60
|
+
name = (getattr(loaded, "name", "") or "").strip() or hdc_path.stem
|
|
61
|
+
|
|
62
|
+
existing = (
|
|
63
|
+
self.db.query(CharacterDef)
|
|
64
|
+
.filter_by(tenant_id=self.tenant_id, source=source,
|
|
65
|
+
hdc_filename=hdc_path.name)
|
|
66
|
+
.first()
|
|
67
|
+
)
|
|
68
|
+
if existing:
|
|
69
|
+
self.db.delete(existing)
|
|
70
|
+
self.db.flush()
|
|
71
|
+
character = CharacterDef(
|
|
72
|
+
tenant_id=self.tenant_id, owner_id=self.owner_id, source=source,
|
|
73
|
+
hdc_filename=hdc_path.name, name=name, is_npc=is_npc,
|
|
74
|
+
)
|
|
75
|
+
self.db.add(character)
|
|
76
|
+
self.db.flush()
|
|
77
|
+
persist_loaded(self.db, character, loaded)
|
|
78
|
+
self._populate_categories(character)
|
|
79
|
+
return character
|
|
80
|
+
|
|
81
|
+
def _populate_categories(self, character: CharacterDef) -> None:
|
|
82
|
+
"""Label the freshly-persisted objects with special-effect categories.
|
|
83
|
+
|
|
84
|
+
Runs after the objects exist so it can read them relationally. Never
|
|
85
|
+
raises: categorisation is a labelling pass layered on top of import, and
|
|
86
|
+
a generation-service outage must not lose a character that already parsed and
|
|
87
|
+
costed correctly. Without a bearer this is a no-op and pools simply stay
|
|
88
|
+
unrestricted.
|
|
89
|
+
"""
|
|
90
|
+
if not self.category_bearer:
|
|
91
|
+
return
|
|
92
|
+
from kirby.builds.power_categories import (
|
|
93
|
+
populate_categories_for_character,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
populate_categories_for_character(
|
|
98
|
+
self.db, character.id, bearer=self.category_bearer)
|
|
99
|
+
except Exception: # noqa: BLE001 — never fail an import over labelling
|
|
100
|
+
logger.warning("category population failed for %s", character.id,
|
|
101
|
+
exc_info=True)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------- bootstrap
|
|
105
|
+
|
|
106
|
+
def _resolve_tenant(db: Session, slug: str) -> UUID:
|
|
107
|
+
t = db.query(Tenant).filter_by(slug=slug).first()
|
|
108
|
+
if t is None:
|
|
109
|
+
raise ValueError(f"Tenant {slug!r} not found in DB")
|
|
110
|
+
return t.id
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _resolve_owner(db: Session, sub: str = "local-bootstrap") -> UUID:
|
|
114
|
+
u = db.query(User).filter_by(sub=sub).first()
|
|
115
|
+
if u is None:
|
|
116
|
+
raise ValueError(f"User with sub={sub!r} not found in DB")
|
|
117
|
+
return u.id
|
|
@@ -113,6 +113,12 @@ class CharacterObject(Base):
|
|
|
113
113
|
private: Mapped[bool | None] = mapped_column(Boolean)
|
|
114
114
|
sense_group: Mapped[str | None] = mapped_column(String(32))
|
|
115
115
|
sense_active: Mapped[bool | None] = mapped_column(Boolean)
|
|
116
|
+
# Resistant Protection's PD/ED/Mental/Power split. Cost is by LEVELS, so
|
|
117
|
+
# these move no points; they say which half is physical.
|
|
118
|
+
pd_levels: Mapped[int | None] = mapped_column(Integer)
|
|
119
|
+
ed_levels: Mapped[int | None] = mapped_column(Integer)
|
|
120
|
+
md_levels: Mapped[int | None] = mapped_column(Integer)
|
|
121
|
+
powd_levels: Mapped[int | None] = mapped_column(Integer)
|
|
116
122
|
|
|
117
123
|
# build doc carried an explicit base_cost (engine emits 0.0 only when BASECOST was
|
|
118
124
|
# explicit in the source XML); reconstruction must re-emit it
|
|
@@ -196,6 +202,9 @@ class ObjectSkillMode(Base):
|
|
|
196
202
|
levels_only: Mapped[bool | None] = mapped_column(Boolean)
|
|
197
203
|
everyman: Mapped[bool | None] = mapped_column(Boolean)
|
|
198
204
|
characteristic: Mapped[str | None] = mapped_column(String(32))
|
|
205
|
+
# A Language's NATIVE_TONGUE (it is free). NULL on every other skill: the
|
|
206
|
+
# engine writes the key on a Language only.
|
|
207
|
+
native_tongue: Mapped[bool | None] = mapped_column(Boolean)
|
|
199
208
|
|
|
200
209
|
|
|
201
210
|
class ObjectManeuver(Base):
|
|
@@ -45,7 +45,6 @@ from kirby.engines.cost.hdc import build_loader
|
|
|
45
45
|
from kirby.auth.models import User
|
|
46
46
|
from kirby.characters.models import CharacterDef
|
|
47
47
|
from kirby.builds.models import (
|
|
48
|
-
CharacterObject,
|
|
49
48
|
PowerLibrary,
|
|
50
49
|
)
|
|
51
50
|
from kirby.shared.db import SessionLocal
|
|
@@ -167,104 +166,80 @@ def ensure_owner_character(
|
|
|
167
166
|
return char
|
|
168
167
|
|
|
169
168
|
|
|
170
|
-
# ──
|
|
169
|
+
# ── the engine's document for a file ───────────────────────────────────────
|
|
171
170
|
|
|
172
171
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# Modifier class hierarchy in kirby_cost: anything that's an advantage
|
|
177
|
-
# or limitation is a Modifier subclass. Specific check by XMLID later.
|
|
178
|
-
if "modifier" in cls_name or "advantage" in cls_name or "limitation" in cls_name:
|
|
179
|
-
return "modifier"
|
|
180
|
-
if "adder" in cls_name:
|
|
181
|
-
return "adder"
|
|
182
|
-
# XMLIDs for canonical modifiers
|
|
183
|
-
return "power"
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
# ── recursive insertion ─────────────────────────────────────────────────────
|
|
187
|
-
|
|
172
|
+
@dataclass
|
|
173
|
+
class FileDoc:
|
|
174
|
+
"""One loaded file's build doc, indexed for cataloguing single powers.
|
|
188
175
|
|
|
189
|
-
|
|
176
|
+
``doc_of`` maps an engine object (by identity) to its doc object, so a
|
|
177
|
+
power picked off ``hero.powers`` finds the document the engine wrote for
|
|
178
|
+
it. ``costs`` is the per-object cost cache keyed by doc id.
|
|
179
|
+
"""
|
|
180
|
+
doc_of: dict[int, dict[str, Any]]
|
|
181
|
+
flat: list[dict[str, Any]]
|
|
182
|
+
costs: dict[str, dict[str, float]]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def file_doc(hero: Any) -> FileDoc:
|
|
186
|
+
"""``to_build_json`` emits one doc object per engine object, in order, so
|
|
187
|
+
the two lists zip. A length mismatch means that stopped being true, and
|
|
188
|
+
cataloguing against a misaligned index would file costs under the wrong
|
|
189
|
+
powers — so it raises instead."""
|
|
190
|
+
from kirby_cost.io import to_build_json
|
|
191
|
+
|
|
192
|
+
doc = to_build_json(hero)
|
|
193
|
+
doc_of: dict[int, dict[str, Any]] = {}
|
|
194
|
+
flat: list[dict[str, Any]] = []
|
|
195
|
+
costs: dict[str, dict[str, float]] = {}
|
|
196
|
+
for section in ("powers", "equipment"):
|
|
197
|
+
objs = list(getattr(hero, section, []) or [])
|
|
198
|
+
docs = doc.get(section) or []
|
|
199
|
+
if len(objs) != len(docs):
|
|
200
|
+
raise RuntimeError(
|
|
201
|
+
f"{section}: {len(objs)} engine objects but {len(docs)} doc objects")
|
|
202
|
+
for o, d in zip(objs, docs):
|
|
203
|
+
doc_of[id(o)] = d
|
|
204
|
+
flat.append(d)
|
|
205
|
+
costs[d["id"]] = {
|
|
206
|
+
"base_cost": float(getattr(o, "base_cost", 0) or 0),
|
|
207
|
+
"active_cost": float(getattr(o, "active_cost", 0) or 0),
|
|
208
|
+
"real_cost": float(getattr(o, "real_cost", 0) or 0),
|
|
209
|
+
}
|
|
210
|
+
return FileDoc(doc_of=doc_of, flat=flat, costs=costs)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# ── insertion ───────────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def insert_power_subtree(
|
|
190
217
|
db: Session,
|
|
191
|
-
|
|
218
|
+
d: dict[str, Any],
|
|
192
219
|
*,
|
|
220
|
+
fdoc: FileDoc,
|
|
193
221
|
character_id: uuid.UUID,
|
|
194
|
-
parent_id: int | None,
|
|
195
|
-
position: int,
|
|
196
|
-
object_type: str | None = None,
|
|
197
|
-
hero_powers: list[Any] | None = None,
|
|
222
|
+
parent_id: int | None = None,
|
|
223
|
+
position: int = 0,
|
|
198
224
|
) -> int:
|
|
199
|
-
"""
|
|
200
|
-
|
|
201
|
-
the inserted row's id so callers can chain it as a parent_id.
|
|
202
|
-
|
|
203
|
-
``hero_powers`` is the full ``hero.powers`` list — required for
|
|
204
|
-
Multipower/VPP/ElementalControl slot resolution, because the loader
|
|
205
|
-
sets ``slot.parent = framework`` but does NOT back-populate
|
|
206
|
-
``framework.objects``. We walk ``hero_powers`` filtering by
|
|
207
|
-
``slot.parent is obj`` to find slots that belong to ``obj``."""
|
|
208
|
-
row = CharacterObject(
|
|
209
|
-
character_id=character_id,
|
|
210
|
-
parent_id=parent_id,
|
|
211
|
-
xmlid=str(obj.xmlid),
|
|
212
|
-
object_type=object_type or classify_object_type(obj),
|
|
213
|
-
name=(getattr(obj, "name", None) or None) or None,
|
|
214
|
-
alias=getattr(obj, "alias", None) or None,
|
|
215
|
-
base_cost=float(getattr(obj, "base_cost", 0.0) or 0.0),
|
|
216
|
-
levels=int(getattr(obj, "levels", 0) or 0),
|
|
217
|
-
active_cost=_safe_float(_safe_attr(obj, "active_cost")),
|
|
218
|
-
real_cost=_safe_float(_safe_attr(obj, "real_cost")),
|
|
219
|
-
position=position,
|
|
220
|
-
input_value=getattr(obj, "input", None) or None,
|
|
221
|
-
option_id=_safe_attr(obj, "selected_option") or None,
|
|
222
|
-
notes=(getattr(obj, "notes", "") or "").strip() or None,
|
|
223
|
-
)
|
|
224
|
-
db.add(row)
|
|
225
|
-
db.flush()
|
|
225
|
+
"""Write one power's doc object through the build-doc writer, then its
|
|
226
|
+
framework slots beneath it. Returns the root row's id.
|
|
226
227
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
# Recurse: assigned_adders → adder children
|
|
236
|
-
for i, add in enumerate(getattr(obj, "assigned_adders", []) or []):
|
|
237
|
-
insert_object_subtree(
|
|
238
|
-
db, add,
|
|
239
|
-
character_id=character_id, parent_id=row.id,
|
|
240
|
-
position=i, object_type="adder",
|
|
241
|
-
)
|
|
228
|
+
Modifiers, adders, compound sub-powers and an Endurance Reserve's REC are
|
|
229
|
+
nested IN the doc object, so the writer handles them. Multipower / VPP /
|
|
230
|
+
Elemental Control slots — and a LIST's members — are flat doc objects
|
|
231
|
+
whose ``parent`` names the framework, so they are attached here.
|
|
232
|
+
"""
|
|
233
|
+
from kirby.builds.build_doc_store import persist_object
|
|
242
234
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
# and filter by `slot.parent is obj`. (Verified 2026-05-12 by
|
|
249
|
-
# instrumenting LoadedHero.)
|
|
250
|
-
if str(obj.xmlid).upper() in FRAMEWORK_XMLIDS:
|
|
251
|
-
nested = getattr(obj, "powers", None) or []
|
|
252
|
-
sibling_slots: list[Any] = []
|
|
253
|
-
if hero_powers is not None:
|
|
254
|
-
sibling_slots = [
|
|
255
|
-
p for p in hero_powers if getattr(p, "parent", None) is obj
|
|
256
|
-
]
|
|
257
|
-
# Prefer parent-linked siblings when present (MP/VPP/EC case);
|
|
258
|
-
# otherwise fall back to nested .powers (CompoundPower case).
|
|
259
|
-
slots = sibling_slots if sibling_slots else nested
|
|
235
|
+
row = persist_object(db, character_id, d, object_type="power",
|
|
236
|
+
parent_id=parent_id, position=position,
|
|
237
|
+
costs=fdoc.costs)
|
|
238
|
+
if str(d["xmlid"]).upper() in FRAMEWORK_XMLIDS:
|
|
239
|
+
slots = [s for s in fdoc.flat if s.get("parent") == d["id"]]
|
|
260
240
|
for i, slot in enumerate(slots):
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
character_id=character_id, parent_id=row.id,
|
|
264
|
-
position=i, object_type="power",
|
|
265
|
-
hero_powers=hero_powers,
|
|
266
|
-
)
|
|
267
|
-
|
|
241
|
+
insert_power_subtree(db, slot, fdoc=fdoc, character_id=character_id,
|
|
242
|
+
parent_id=row.id, position=i)
|
|
268
243
|
return row.id
|
|
269
244
|
|
|
270
245
|
|
|
@@ -324,7 +299,7 @@ def write_library_entry(
|
|
|
324
299
|
sfx_tags: list[str],
|
|
325
300
|
mechanical_tags: list[str],
|
|
326
301
|
embed_fn: Optional[callable] = None,
|
|
327
|
-
|
|
302
|
+
fdoc: FileDoc,
|
|
328
303
|
) -> Optional[PowerLibrary]:
|
|
329
304
|
"""Insert one POWER's tree into character_objects, then a power_library row.
|
|
330
305
|
|
|
@@ -354,13 +329,8 @@ def write_library_entry(
|
|
|
354
329
|
kind = FRAMEWORK_KIND_MAP.get(xmlid, "single")
|
|
355
330
|
|
|
356
331
|
# Insert the character_objects subtree
|
|
357
|
-
root_object_id =
|
|
358
|
-
db, power,
|
|
359
|
-
character_id=owner_character.id,
|
|
360
|
-
parent_id=None,
|
|
361
|
-
position=0,
|
|
362
|
-
object_type="power",
|
|
363
|
-
hero_powers=hero_powers,
|
|
332
|
+
root_object_id = insert_power_subtree(
|
|
333
|
+
db, fdoc.doc_of[id(power)], fdoc=fdoc, character_id=owner_character.id,
|
|
364
334
|
)
|
|
365
335
|
|
|
366
336
|
description_text = (getattr(power, "notes", "") or "").strip() or None
|
|
@@ -448,6 +418,7 @@ def import_file(
|
|
|
448
418
|
# them apart on a Hero does not apply to a catalogue entry.
|
|
449
419
|
hero_powers = list(getattr(hero, "powers", []) or [])
|
|
450
420
|
hero_powers += list(getattr(hero, "equipment", []) or [])
|
|
421
|
+
fdoc = file_doc(hero)
|
|
451
422
|
for power in hero_powers:
|
|
452
423
|
stats.powers_seen += 1
|
|
453
424
|
xmlid = str(_safe_attr(power, "xmlid", "")).upper()
|
|
@@ -483,7 +454,7 @@ def import_file(
|
|
|
483
454
|
sfx_tags=sfx_tags,
|
|
484
455
|
mechanical_tags=mechanical_tags,
|
|
485
456
|
embed_fn=embed_fn,
|
|
486
|
-
|
|
457
|
+
fdoc=fdoc,
|
|
487
458
|
)
|
|
488
459
|
sp.commit()
|
|
489
460
|
except Exception as e:
|
|
@@ -36,7 +36,7 @@ from kirby.campaigns.services.legacy_translation import (
|
|
|
36
36
|
)
|
|
37
37
|
from kirby.characters.models import CharacterDef, CharacterNarrative
|
|
38
38
|
from kirby.characters._shared import load_character_in_tenant
|
|
39
|
-
from kirby.builds.
|
|
39
|
+
from kirby.builds.build_doc_store import build_doc_from_rows
|
|
40
40
|
from kirby.tenants.routes import _ensure_user_row
|
|
41
41
|
|
|
42
42
|
router = APIRouter(prefix="/characters", tags=["characters"])
|
|
@@ -223,10 +223,10 @@ def export_character_json(
|
|
|
223
223
|
db: Session = Depends(get_db),
|
|
224
224
|
download: bool = False,
|
|
225
225
|
):
|
|
226
|
-
"""Export a character as
|
|
226
|
+
"""Export a character as its canonical build doc.
|
|
227
227
|
|
|
228
|
-
The
|
|
229
|
-
so this is the round-trip transport for save / share / backup.
|
|
228
|
+
The same document ``GET /{id}/build`` returns and ``PUT /{id}/build``
|
|
229
|
+
accepts, so this is the round-trip transport for save / share / backup.
|
|
230
230
|
Set ``?download=1`` to get a Content-Disposition: attachment
|
|
231
231
|
header for browser-friendly download.
|
|
232
232
|
|
|
@@ -241,7 +241,7 @@ def export_character_json(
|
|
|
241
241
|
# not-found.
|
|
242
242
|
raise HTTPException(status_code=404, detail="character not found")
|
|
243
243
|
|
|
244
|
-
payload =
|
|
244
|
+
payload = build_doc_from_rows(db, character_id)
|
|
245
245
|
body = json.dumps(payload, indent=2).encode()
|
|
246
246
|
headers: dict[str, str] = {}
|
|
247
247
|
if download:
|
|
@@ -252,7 +252,7 @@ def export_character_json(
|
|
|
252
252
|
for c in (char.name or str(character_id))
|
|
253
253
|
) or str(character_id)
|
|
254
254
|
headers["Content-Disposition"] = (
|
|
255
|
-
f'attachment; filename="{safe}.
|
|
255
|
+
f'attachment; filename="{safe}.build.json"'
|
|
256
256
|
)
|
|
257
257
|
return Response(
|
|
258
258
|
content=body,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kirby-api
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Kirby — HERO System VTT backend API (FastAPI mono)
|
|
5
5
|
Author: PeterB
|
|
6
6
|
License: PolyForm Noncommercial License 1.0.0
|
|
@@ -22,7 +22,7 @@ Requires-Dist: cryptography>=44
|
|
|
22
22
|
Requires-Dist: python-jose[cryptography]>=3.3
|
|
23
23
|
Requires-Dist: pyjwt[crypto]>=2.10
|
|
24
24
|
Requires-Dist: redis>=5.2
|
|
25
|
-
Requires-Dist: kirby-cost>=0.6.
|
|
25
|
+
Requires-Dist: kirby-cost>=0.6.2
|
|
26
26
|
Requires-Dist: lxml>=5.3
|
|
27
27
|
Requires-Dist: python-multipart>=0.0.20
|
|
28
28
|
Requires-Dist: email-validator>=2.2
|
|
@@ -106,6 +106,7 @@ kirby/_migrations/versions/20260918_0000_drop_combat_tables.py
|
|
|
106
106
|
kirby/_migrations/versions/20260918_0001_harness_tables.py
|
|
107
107
|
kirby/_migrations/versions/20260918_0002_narration_speaker_id.py
|
|
108
108
|
kirby/_migrations/versions/20261005_0001_knowledge_indexes.py
|
|
109
|
+
kirby/_migrations/versions/20261005_0002_build_doc_fields.py
|
|
109
110
|
kirby/_migrations/versions/21980c836860_add_asset_id_refs.py
|
|
110
111
|
kirby/_migrations/versions/ba28e71c955d_codex_init.py
|
|
111
112
|
kirby/admin/__init__.py
|
|
@@ -123,7 +124,6 @@ kirby/builds/cli.py
|
|
|
123
124
|
kirby/builds/codex_linker.py
|
|
124
125
|
kirby/builds/combatant_loader.py
|
|
125
126
|
kirby/builds/importer.py
|
|
126
|
-
kirby/builds/json_exporter.py
|
|
127
127
|
kirby/builds/json_importer.py
|
|
128
128
|
kirby/builds/models.py
|
|
129
129
|
kirby/builds/power_categories.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kirby-api"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3"
|
|
4
4
|
description = "Kirby — HERO System VTT backend API (FastAPI mono)"
|
|
5
5
|
# Published to public PyPI. Every runtime dependency below resolves from
|
|
6
6
|
# public PyPI too — kirby-cost, kirby-combat, kirby-dice and kirby-ai all
|
|
@@ -91,7 +91,7 @@ dependencies = [
|
|
|
91
91
|
# columns stay NULL on every import, and a rebuilt character silently
|
|
92
92
|
# counts purchases HD keeps out of its totals — Cobra came back DCV 12
|
|
93
93
|
# where the canonical .hdc load says 10.
|
|
94
|
-
"kirby-cost>=0.6.
|
|
94
|
+
"kirby-cost>=0.6.2",
|
|
95
95
|
"lxml>=5.3",
|
|
96
96
|
|
|
97
97
|
# Utilities
|