Initial commit
This commit is contained in:
0
hsbg_ai/backend/database/__init__.py
Normal file
0
hsbg_ai/backend/database/__init__.py
Normal file
33
hsbg_ai/backend/database/db.py
Normal file
33
hsbg_ai/backend/database/db.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""Connexion et sessions de base de données async."""
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
|
||||
from backend.config.settings import get_settings
|
||||
from backend.database.models import Base
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
# Convertir l'URL SQLite en version async
|
||||
DB_URL = settings.database_url
|
||||
if DB_URL.startswith("sqlite:///"):
|
||||
DB_URL = DB_URL.replace("sqlite:///", "sqlite+aiosqlite:///")
|
||||
|
||||
engine = create_async_engine(DB_URL, echo=settings.debug, pool_pre_ping=True)
|
||||
AsyncSessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
|
||||
async def init_db():
|
||||
"""Crée toutes les tables si elles n'existent pas."""
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
|
||||
async def get_db():
|
||||
"""Dépendance FastAPI - fournit une session DB."""
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
yield session
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
finally:
|
||||
await session.close()
|
||||
0
hsbg_ai/backend/database/migrations/__init__.py
Normal file
0
hsbg_ai/backend/database/migrations/__init__.py
Normal file
128
hsbg_ai/backend/database/models.py
Normal file
128
hsbg_ai/backend/database/models.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""Modèles SQLAlchemy - Base de données HSBG AI."""
|
||||
from datetime import datetime
|
||||
from sqlalchemy import (
|
||||
Column, Integer, String, Float, Boolean, Text,
|
||||
DateTime, JSON, ForeignKey
|
||||
)
|
||||
from sqlalchemy.orm import DeclarativeBase, relationship
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
class Hero(Base):
|
||||
__tablename__ = "heroes"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
card_id = Column(String(64), unique=True, index=True, nullable=False)
|
||||
name = Column(String(128), nullable=False)
|
||||
hero_power = Column(Text, default="")
|
||||
hp_cost = Column(Integer, default=0)
|
||||
hp_cooldown = Column(Integer, default=0)
|
||||
description = Column(Text, default="")
|
||||
strengths = Column(JSON, default=list)
|
||||
weaknesses = Column(JSON, default=list)
|
||||
synergies = Column(JSON, default=list)
|
||||
tier_rating = Column(Float, default=5.0)
|
||||
patch_added = Column(String(16), default="")
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
|
||||
class Minion(Base):
|
||||
__tablename__ = "minions"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
card_id = Column(String(64), unique=True, index=True, nullable=False)
|
||||
name = Column(String(128), nullable=False)
|
||||
tier = Column(String(2), nullable=False, default="1")
|
||||
race = Column(JSON, default=list)
|
||||
attack = Column(Integer, default=0)
|
||||
health = Column(Integer, default=0)
|
||||
tavern_cost = Column(Integer, default=3)
|
||||
is_golden = Column(Boolean, default=False)
|
||||
has_divine = Column(Boolean, default=False)
|
||||
has_taunt = Column(Boolean, default=False)
|
||||
has_windfury = Column(Boolean, default=False)
|
||||
has_poisonous = Column(Boolean, default=False)
|
||||
has_reborn = Column(Boolean, default=False)
|
||||
battlecry = Column(Text, default="")
|
||||
deathrattle = Column(Text, default="")
|
||||
on_attack = Column(Text, default="")
|
||||
passive = Column(Text, default="")
|
||||
synergies = Column(JSON, default=list)
|
||||
keywords = Column(JSON, default=list)
|
||||
patch_added = Column(String(16), default="")
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
|
||||
class Spell(Base):
|
||||
__tablename__ = "spells"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
card_id = Column(String(64), unique=True, index=True, nullable=False)
|
||||
name = Column(String(128), nullable=False)
|
||||
tier = Column(String(2), default="1")
|
||||
cost = Column(Integer, default=0)
|
||||
effect = Column(Text, default="")
|
||||
target = Column(String(64), default="minion")
|
||||
keywords = Column(JSON, default=list)
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class GameSession(Base):
|
||||
__tablename__ = "game_sessions"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
started_at = Column(DateTime, default=datetime.utcnow)
|
||||
ended_at = Column(DateTime, nullable=True)
|
||||
hero_id = Column(Integer, ForeignKey("heroes.id"), nullable=True)
|
||||
final_place = Column(Integer, nullable=True)
|
||||
total_turns = Column(Integer, default=0)
|
||||
is_active = Column(Boolean, default=True)
|
||||
session_meta = Column(JSON, default=dict)
|
||||
hero = relationship("Hero")
|
||||
decisions = relationship("AIDecision", back_populates="session")
|
||||
|
||||
|
||||
class AIDecision(Base):
|
||||
__tablename__ = "ai_decisions"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
session_id = Column(Integer, ForeignKey("game_sessions.id"), nullable=False)
|
||||
turn = Column(Integer, default=0)
|
||||
phase = Column(String(32), default="recruit")
|
||||
game_state = Column(JSON, default=dict)
|
||||
recommendation = Column(JSON, default=dict)
|
||||
reasoning = Column(Text, default="")
|
||||
confidence = Column(Float, default=0.5)
|
||||
was_followed = Column(Boolean, nullable=True)
|
||||
outcome_rating = Column(Integer, nullable=True)
|
||||
user_feedback = Column(Text, nullable=True)
|
||||
better_decision = Column(JSON, nullable=True)
|
||||
model_used = Column(String(64), default="heuristic")
|
||||
processing_ms = Column(Integer, default=0)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
session = relationship("GameSession", back_populates="decisions")
|
||||
|
||||
|
||||
class LearningFeedback(Base):
|
||||
__tablename__ = "learning_feedback"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
decision_id = Column(Integer, ForeignKey("ai_decisions.id"), nullable=False)
|
||||
rating = Column(String(8), default="neutral")
|
||||
better_action = Column(JSON, nullable=True)
|
||||
comment = Column(Text, nullable=True)
|
||||
trained = Column(Boolean, default=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
decision = relationship("AIDecision")
|
||||
|
||||
|
||||
class Patch(Base):
|
||||
__tablename__ = "patches"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
version = Column(String(16), unique=True, nullable=False)
|
||||
release_date = Column(DateTime)
|
||||
changes = Column(JSON, default=dict)
|
||||
notes = Column(Text, default="")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
0
hsbg_ai/backend/database/seeds/__init__.py
Normal file
0
hsbg_ai/backend/database/seeds/__init__.py
Normal file
225
hsbg_ai/backend/database/seeds/seed_data.py
Normal file
225
hsbg_ai/backend/database/seeds/seed_data.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""
|
||||
Peuplement initial de la base de données HSBG.
|
||||
Usage: python -m backend.database.seeds.seed_data
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Ajouter le répertoire racine au path
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")))
|
||||
|
||||
from backend.database.db import AsyncSessionLocal, init_db
|
||||
from backend.database.models import Hero, Minion, Spell
|
||||
|
||||
# ─── Données Héros ────────────────────────────────────────────────────────────
|
||||
HEROES = [
|
||||
{
|
||||
"card_id": "HERO_ragnaros", "name": "Ragnaros l'Illuminé", "tier_rating": 7.0,
|
||||
"hero_power": "Niveau de lancement: Donne +3 ATT à un serviteur ami aléatoire.",
|
||||
"hp_cost": 2, "hp_cooldown": 0,
|
||||
"description": "Excellent en mid/late game agressif. Meilleur avec des boards denses.",
|
||||
"strengths": ["combat", "aggressive_boards", "mid_game"],
|
||||
"weaknesses": ["divine_shield_heavy", "defensive_boards"],
|
||||
"synergies": ["beast", "demon", "all"], "patch_added": "15.0",
|
||||
},
|
||||
{
|
||||
"card_id": "HERO_millificent", "name": "Millificent Manastorm", "tier_rating": 6.5,
|
||||
"hero_power": "Passif: La taverne propose toujours au moins un Mécanisme.",
|
||||
"hp_cost": 0, "hp_cooldown": 0,
|
||||
"description": "Setup méca rapide et consistant. Idéal pour les synergies méca.",
|
||||
"strengths": ["mech_setup", "consistency", "divine_shield_generation"],
|
||||
"weaknesses": ["slow_early", "no_mechs_in_rotation"],
|
||||
"synergies": ["mech"], "patch_added": "15.0",
|
||||
},
|
||||
{
|
||||
"card_id": "HERO_finley", "name": "Sire Finley de Mrglton", "tier_rating": 7.5,
|
||||
"hero_power": "Avance vos serviteurs de la taverne. Coût: 2.",
|
||||
"hp_cost": 2, "hp_cooldown": 0,
|
||||
"description": "Très polyvalent, s'adapte à toutes les compositions. Top pick en général.",
|
||||
"strengths": ["flexible", "tempo", "any_comp"],
|
||||
"weaknesses": ["gold_hungry"],
|
||||
"synergies": ["all"], "patch_added": "15.0",
|
||||
},
|
||||
{
|
||||
"card_id": "HERO_mukla", "name": "Roi Mukla", "tier_rating": 4.0,
|
||||
"hero_power": "Passif: Gagnez 1 or de plus par tour (mais donne des Bananes aux adversaires).",
|
||||
"hp_cost": 0, "hp_cooldown": 0,
|
||||
"description": "Avantage économique constant mais renforce les adversaires.",
|
||||
"strengths": ["economy", "fast_tier", "late_game_scaling"],
|
||||
"weaknesses": ["banana_enemies", "weak_early_game"],
|
||||
"synergies": ["economy", "late_game"], "patch_added": "16.0",
|
||||
},
|
||||
{
|
||||
"card_id": "HERO_deathwing", "name": "Deathwing", "tier_rating": 5.5,
|
||||
"hero_power": "Passif: Tous vos serviteurs gagnent +3 ATT.",
|
||||
"hp_cost": 0, "hp_cooldown": 0,
|
||||
"description": "Buff d'attaque passif. Excellent pour les compositions agressives early.",
|
||||
"strengths": ["early_aggression", "tempo", "all_minions"],
|
||||
"weaknesses": ["late_game_scaling", "no_defensive_option"],
|
||||
"synergies": ["all"], "patch_added": "15.0",
|
||||
},
|
||||
{
|
||||
"card_id": "HERO_jaraxxus", "name": "Lord Jaraxxus", "tier_rating": 6.0,
|
||||
"hero_power": "Octroie +1/+1 à un Démon ami aléatoire. Coût: 1.",
|
||||
"hp_cost": 1, "hp_cooldown": 0,
|
||||
"description": "Scale très bien avec une composition démon.",
|
||||
"strengths": ["demon_comp", "consistent_scaling"],
|
||||
"weaknesses": ["requires_demons", "slow_if_no_demons"],
|
||||
"synergies": ["demon"], "patch_added": "15.0",
|
||||
},
|
||||
{
|
||||
"card_id": "HERO_patchwork", "name": "Patchwork", "tier_rating": 6.0,
|
||||
"hero_power": "Passif: Votre premier serviteur vendu chaque tour donne +1/+1 à un serviteur ami aléatoire.",
|
||||
"hp_cost": 0, "hp_cooldown": 0,
|
||||
"description": "Excellent pour les compositions avec beaucoup de ventes.",
|
||||
"strengths": ["sell_synergy", "flexible", "economy"],
|
||||
"weaknesses": ["requires_selling_strategy"],
|
||||
"synergies": ["all"], "patch_added": "17.0",
|
||||
},
|
||||
]
|
||||
|
||||
# ─── Données Serviteurs ───────────────────────────────────────────────────────
|
||||
MINIONS = [
|
||||
# ── Tier 1 ──
|
||||
{"card_id": "BGS_t1_01", "name": "Gardien de la Sécurité", "tier": "1",
|
||||
"race": ["mech"], "attack": 2, "health": 3, "tavern_cost": 3,
|
||||
"has_divine": True, "deathrattle": "Donne +3 ATT à un méca ami aléatoire.",
|
||||
"synergies": ["mech"], "keywords": ["divine_shield", "deathrattle"], "patch_added": "16.0"},
|
||||
|
||||
{"card_id": "BGS_t1_02", "name": "Hyène Affamée", "tier": "1",
|
||||
"race": ["beast"], "attack": 1, "health": 1, "tavern_cost": 3,
|
||||
"passive": "Quand un Murloc ami meurt: gagne +2 ATT.",
|
||||
"synergies": ["beast", "murloc"], "keywords": ["passive"], "patch_added": "15.0"},
|
||||
|
||||
{"card_id": "BGS_t1_03", "name": "Murloc Tidecaller", "tier": "1",
|
||||
"race": ["murloc"], "attack": 1, "health": 2, "tavern_cost": 3,
|
||||
"passive": "Gagne +1 ATT chaque fois qu'un Murloc est invoqué.",
|
||||
"synergies": ["murloc"], "keywords": ["passive"], "patch_added": "15.0"},
|
||||
|
||||
{"card_id": "BGS_t1_04", "name": "Drone Déchiqueté", "tier": "1",
|
||||
"race": ["mech"], "attack": 1, "health": 1, "tavern_cost": 3,
|
||||
"deathrattle": "Invoque deux 1/1 Drones.",
|
||||
"synergies": ["mech", "token"], "keywords": ["deathrattle"], "patch_added": "15.0"},
|
||||
|
||||
# ── Tier 2 ──
|
||||
{"card_id": "BGS_t2_01", "name": "Dragueur de Taverne", "tier": "2",
|
||||
"race": ["none"], "attack": 2, "health": 4, "tavern_cost": 3,
|
||||
"battlecry": "Donne +2/+2 à un serviteur ami aléatoire.",
|
||||
"synergies": [], "keywords": ["battlecry"], "patch_added": "15.0"},
|
||||
|
||||
{"card_id": "BGS_t2_02", "name": "Attaquant du Fouet", "tier": "2",
|
||||
"race": ["murloc"], "attack": 2, "health": 1, "tavern_cost": 3,
|
||||
"has_poisonous": True, "battlecry": "Donne Venimeux à un Murloc ami aléatoire.",
|
||||
"synergies": ["murloc"], "keywords": ["poisonous", "battlecry"], "patch_added": "15.0"},
|
||||
|
||||
{"card_id": "BGS_t2_03", "name": "Paladin Défenseur", "tier": "2",
|
||||
"race": ["none"], "attack": 2, "health": 2, "tavern_cost": 3,
|
||||
"has_divine": True, "has_taunt": True,
|
||||
"synergies": [], "keywords": ["divine_shield", "taunt"], "patch_added": "15.0"},
|
||||
|
||||
# ── Tier 3 ──
|
||||
{"card_id": "BGS_t3_01", "name": "Infernal de Sang", "tier": "3",
|
||||
"race": ["demon"], "attack": 3, "health": 4, "tavern_cost": 4,
|
||||
"has_taunt": True, "deathrattle": "Invoque un 3/3 Infernal.",
|
||||
"synergies": ["demon"], "keywords": ["taunt", "deathrattle"], "patch_added": "15.0"},
|
||||
|
||||
{"card_id": "BGS_t3_02", "name": "Vif-Argent Libre", "tier": "3",
|
||||
"race": ["mech"], "attack": 3, "health": 4, "tavern_cost": 4,
|
||||
"has_divine": True,
|
||||
"passive": "Quand un méca ami gagne un Bouclier Divin: gagne +2 ATT.",
|
||||
"synergies": ["mech"], "keywords": ["divine_shield", "passive"], "patch_added": "16.0"},
|
||||
|
||||
{"card_id": "BGS_t3_03", "name": "Infesteur de Crève-Mort", "tier": "3",
|
||||
"race": ["undead"], "attack": 3, "health": 3, "tavern_cost": 4,
|
||||
"deathrattle": "Infeste un serviteur ami aléatoire avec +3/+3.",
|
||||
"synergies": ["undead"], "keywords": ["deathrattle"], "patch_added": "22.0"},
|
||||
|
||||
{"card_id": "BGS_t3_04", "name": "Défenseur de Sombrebourg", "tier": "3",
|
||||
"race": ["none"], "attack": 2, "health": 5, "tavern_cost": 4,
|
||||
"has_taunt": True, "passive": "Chaque fois qu'un serviteur ami gagne du Bouclier Divin: gagne +2/+2.",
|
||||
"synergies": ["divine_shield"], "keywords": ["taunt", "passive"], "patch_added": "16.0"},
|
||||
|
||||
# ── Tier 4 ──
|
||||
{"card_id": "BGS_t4_01", "name": "Brann Bronzebeard", "tier": "4",
|
||||
"race": ["none"], "attack": 2, "health": 4, "tavern_cost": 5,
|
||||
"passive": "Vos cris de guerre se déclenchent deux fois.",
|
||||
"synergies": ["battlecry"], "keywords": ["passive"], "patch_added": "17.0"},
|
||||
|
||||
{"card_id": "BGS_t4_02", "name": "Chevauche-Tempête", "tier": "4",
|
||||
"race": ["dragon"], "attack": 5, "health": 4, "tavern_cost": 5,
|
||||
"battlecry": "Donne +3 ATT à vos dragons amis.",
|
||||
"synergies": ["dragon"], "keywords": ["battlecry"], "patch_added": "17.0"},
|
||||
|
||||
{"card_id": "BGS_t4_03", "name": "Paladin Gardien", "tier": "4",
|
||||
"race": ["none"], "attack": 3, "health": 3, "tavern_cost": 5,
|
||||
"has_divine": True, "has_taunt": True,
|
||||
"passive": "Au début de votre tour: donne Bouclier Divin à un serviteur ami aléatoire.",
|
||||
"synergies": ["divine_shield"], "keywords": ["divine_shield", "taunt", "passive"], "patch_added": "17.0"},
|
||||
|
||||
# ── Tier 5 ──
|
||||
{"card_id": "BGS_t5_01", "name": "Kangaro Boxeur", "tier": "5",
|
||||
"race": ["beast"], "attack": 5, "health": 5, "tavern_cost": 6,
|
||||
"battlecry": "Gagne +1 ATT par serviteur ami sur le board.",
|
||||
"synergies": ["beast"], "keywords": ["battlecry"], "patch_added": "18.0"},
|
||||
|
||||
{"card_id": "BGS_t5_02", "name": "Murozond", "tier": "5",
|
||||
"race": ["dragon"], "attack": 5, "health": 5, "tavern_cost": 6,
|
||||
"battlecry": "Gagne les capacités de vos autres types de races sur le board.",
|
||||
"synergies": ["dragon", "all"], "keywords": ["battlecry"], "patch_added": "19.0"},
|
||||
|
||||
# ── Tier 6 ──
|
||||
{"card_id": "BGS_t6_01", "name": "Amalgame de l'Égout", "tier": "6",
|
||||
"race": ["all"], "attack": 6, "health": 4, "tavern_cost": 7,
|
||||
"passive": "Hérite des capacités de toutes les races de vos serviteurs amis.",
|
||||
"synergies": ["all"], "keywords": ["passive"], "patch_added": "17.0"},
|
||||
|
||||
{"card_id": "BGS_t6_02", "name": "Zapp Brannigan", "tier": "6",
|
||||
"race": ["mech"], "attack": 7, "health": 10, "tavern_cost": 7,
|
||||
"passive": "Attaque toujours le serviteur ennemi avec l'ATT la plus faible.",
|
||||
"synergies": ["mech"], "keywords": ["passive"], "patch_added": "15.0"},
|
||||
]
|
||||
|
||||
# ─── Données Sorts ────────────────────────────────────────────────────────────
|
||||
SPELLS = [
|
||||
{"card_id": "SP_01", "name": "Soif de Sang", "tier": "1", "cost": 0,
|
||||
"effect": "Donne +3/+1 à un serviteur ami.", "target": "minion", "keywords": ["buff"]},
|
||||
{"card_id": "SP_02", "name": "Bière Frelatée", "tier": "1", "cost": 0,
|
||||
"effect": "Donne +1/+1 à tous vos serviteurs.", "target": "board", "keywords": ["aoe_buff"]},
|
||||
{"card_id": "SP_03", "name": "Armure Réactive", "tier": "2", "cost": 1,
|
||||
"effect": "Votre héros gagne +4 armure.", "target": "hero", "keywords": ["armor"]},
|
||||
{"card_id": "SP_04", "name": "Maître en Cris de Guerre", "tier": "3", "cost": 2,
|
||||
"effect": "Déclenche le cri de guerre d'un serviteur ami.", "target": "minion", "keywords": ["battlecry"]},
|
||||
{"card_id": "SP_05", "name": "Huile de Pierre Sacrée", "tier": "4", "cost": 2,
|
||||
"effect": "Donne Bouclier Divin à tous vos serviteurs.", "target": "board", "keywords": ["divine_shield"]},
|
||||
]
|
||||
|
||||
|
||||
async def seed():
|
||||
print("\n🌱 Peuplement de la base de données HSBG...")
|
||||
await init_db()
|
||||
|
||||
async with AsyncSessionLocal() as db:
|
||||
# Héros
|
||||
for h in HEROES:
|
||||
db.add(Hero(**h))
|
||||
await db.commit()
|
||||
print(f" ✅ {len(HEROES)} héros ajoutés")
|
||||
|
||||
# Serviteurs
|
||||
for m in MINIONS:
|
||||
db.add(Minion(**m))
|
||||
await db.commit()
|
||||
print(f" ✅ {len(MINIONS)} serviteurs ajoutés")
|
||||
|
||||
# Sorts
|
||||
for s in SPELLS:
|
||||
db.add(Spell(**s))
|
||||
await db.commit()
|
||||
print(f" ✅ {len(SPELLS)} sorts ajoutés")
|
||||
|
||||
print("\n✅ Base de données HSBG prête!\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(seed())
|
||||
Reference in New Issue
Block a user