Files
gartenmanager/backend/app/seeds/initial_data.py
Faultier314 834a3bf4d5 feat: Phase 1 complete – full working application
Backend (FastAPI):
- REST API: auth, plants, beds, plantings
- CRUD layer with CRUDBase
- Pydantic v2 schemas for all entities
- Alembic migration: complete schema + all enums
- Seed data: 28 global plants + 15 compatibilities

Frontend (Vue 3 + PrimeVue):
- Axios client with JWT interceptor + auto-refresh
- Pinia stores: auth, beds, plants
- Views: Login, Beds, BedDetail, PlantLibrary
- Components: AppLayout, BedForm, PlantingForm, PlantForm

Docker:
- docker-compose.yml (production)
- docker-compose.dev.yml (development with hot-reload)
- Nginx config with SPA fallback + API proxy
- Multi-stage frontend Dockerfile
- .env.example, .gitignore

Version: 1.0.0-alpha
2026-04-06 07:45:00 +02:00

172 lines
8.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Seed-Daten: Globale Pflanzenfamilien, Pflanzen und Kompatibilitäten.
Idempotent kann mehrfach ausgeführt werden ohne Fehler.
"""
import asyncio
import uuid
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import AsyncSessionLocal
from app.models.plant import (
CompatibilityRating,
NutrientDemand,
Plant,
PlantCompatibility,
PlantFamily,
WaterDemand,
)
FAMILIES = [
{"name": "Solanaceae", "latin_name": "Solanaceae"},
{"name": "Kreuzblütler", "latin_name": "Brassicaceae"},
{"name": "Doldenblütler", "latin_name": "Apiaceae"},
{"name": "Hülsenfrüchtler", "latin_name": "Fabaceae"},
{"name": "Kürbisgewächse", "latin_name": "Cucurbitaceae"},
{"name": "Korbblütler", "latin_name": "Asteraceae"},
{"name": "Lauchgewächse", "latin_name": "Alliaceae"},
{"name": "Gänsefußgewächse", "latin_name": "Amaranthaceae"},
{"name": "Lippenblütler", "latin_name": "Lamiaceae"},
{"name": "Süßgräser", "latin_name": "Poaceae"},
]
# (name, latin_name, family_name, nutrient, water, spacing_cm, sow_start, sow_end, rest_years)
PLANTS = [
# Solanaceae
("Tomate", "Solanum lycopersicum", "Solanaceae", NutrientDemand.STARK, WaterDemand.MITTEL, 60, 3, 4, 3),
("Paprika", "Capsicum annuum", "Solanaceae", NutrientDemand.STARK, WaterDemand.MITTEL, 45, 2, 3, 3),
("Aubergine", "Solanum melongena", "Solanaceae", NutrientDemand.STARK, WaterDemand.MITTEL, 50, 2, 3, 3),
# Kreuzblütler
("Brokkoli", "Brassica oleracea var. italica", "Kreuzblütler", NutrientDemand.STARK, WaterDemand.MITTEL, 45, 3, 4, 4),
("Weißkohl", "Brassica oleracea var. capitata", "Kreuzblütler", NutrientDemand.STARK, WaterDemand.MITTEL, 50, 3, 4, 4),
("Kohlrabi", "Brassica oleracea var. gongylodes", "Kreuzblütler", NutrientDemand.MITTEL, WaterDemand.MITTEL, 22, 3, 7, 3),
("Radieschen", "Raphanus sativus", "Kreuzblütler", NutrientDemand.SCHWACH, WaterDemand.MITTEL, 5, 3, 8, 2),
# Doldenblütler
("Möhre", "Daucus carota", "Doldenblütler", NutrientDemand.MITTEL, WaterDemand.WENIG, 5, 3, 7, 3),
("Petersilie", "Petroselinum crispum", "Doldenblütler", NutrientDemand.MITTEL, WaterDemand.MITTEL, 18, 3, 5, 2),
("Sellerie", "Apium graveolens", "Doldenblütler", NutrientDemand.STARK, WaterDemand.VIEL, 28, 2, 3, 3),
# Hülsenfrüchtler
("Buschbohne", "Phaseolus vulgaris", "Hülsenfrüchtler", NutrientDemand.SCHWACH, WaterDemand.MITTEL, 12, 5, 7, 3),
("Erbse", "Pisum sativum", "Hülsenfrüchtler", NutrientDemand.SCHWACH, WaterDemand.MITTEL, 8, 3, 5, 3),
# Kürbisgewächse
("Gurke", "Cucumis sativus", "Kürbisgewächse", NutrientDemand.STARK, WaterDemand.VIEL, 60, 4, 5, 2),
("Zucchini", "Cucurbita pepo", "Kürbisgewächse", NutrientDemand.STARK, WaterDemand.VIEL, 90, 4, 5, 2),
("Kürbis", "Cucurbita maxima", "Kürbisgewächse", NutrientDemand.STARK, WaterDemand.VIEL, 180, 4, 5, 2),
# Korbblütler
("Kopfsalat", "Lactuca sativa", "Korbblütler", NutrientDemand.SCHWACH, WaterDemand.MITTEL, 22, 3, 8, 2),
("Feldsalat", "Valerianella locusta", "Korbblütler", NutrientDemand.SCHWACH, WaterDemand.WENIG, 8, 8, 9, 2),
# Lauchgewächse
("Zwiebel", "Allium cepa", "Lauchgewächse", NutrientDemand.MITTEL, WaterDemand.WENIG, 12, 3, 4, 3),
("Lauch", "Allium porrum", "Lauchgewächse", NutrientDemand.MITTEL, WaterDemand.MITTEL, 12, 2, 3, 3),
("Knoblauch", "Allium sativum", "Lauchgewächse", NutrientDemand.SCHWACH, WaterDemand.WENIG, 10, 10, 11, 4),
("Schnittlauch", "Allium schoenoprasum", "Lauchgewächse", NutrientDemand.SCHWACH, WaterDemand.WENIG, 10, 3, 4, 3),
# Gänsefußgewächse
("Mangold", "Beta vulgaris var. cicla", "Gänsefußgewächse", NutrientDemand.MITTEL, WaterDemand.MITTEL, 28, 3, 6, 3),
("Spinat", "Spinacia oleracea", "Gänsefußgewächse", NutrientDemand.MITTEL, WaterDemand.MITTEL, 12, 3, 9, 3),
("Rote Bete", "Beta vulgaris var. conditiva", "Gänsefußgewächse", NutrientDemand.MITTEL, WaterDemand.MITTEL, 10, 4, 6, 3),
# Lippenblütler
("Basilikum", "Ocimum basilicum", "Lippenblütler", NutrientDemand.SCHWACH, WaterDemand.MITTEL, 20, 4, 5, 2),
("Thymian", "Thymus vulgaris", "Lippenblütler", NutrientDemand.SCHWACH, WaterDemand.WENIG, 25, 3, 4, 2),
("Minze", "Mentha spicata", "Lippenblütler", NutrientDemand.SCHWACH, WaterDemand.VIEL, 30, 3, 4, 2),
("Oregano", "Origanum vulgare", "Lippenblütler", NutrientDemand.SCHWACH, WaterDemand.WENIG, 25, 3, 4, 2),
]
# (plant_a_name, plant_b_name, rating, reason)
COMPATIBILITIES = [
("Tomate", "Basilikum", CompatibilityRating.GUT, "Basilikum fördert das Tomatenwachstum und hält Schädlinge fern."),
("Tomate", "Möhre", CompatibilityRating.GUT, "Gute Nachbarn, gegenseitige Förderung."),
("Tomate", "Petersilie", CompatibilityRating.GUT, "Petersilie stärkt die Tomaten."),
("Tomate", "Brokkoli", CompatibilityRating.SCHLECHT, "Kohl hemmt das Tomatenwachstum."),
("Tomate", "Weißkohl", CompatibilityRating.SCHLECHT, "Kohl hemmt das Tomatenwachstum."),
("Möhre", "Zwiebel", CompatibilityRating.GUT, "Zwiebeln halten die Möhrenfliege fern."),
("Möhre", "Lauch", CompatibilityRating.GUT, "Lauch schützt die Möhre vor der Möhrenfliege."),
("Möhre", "Erbse", CompatibilityRating.GUT, "Erbsen lockern den Boden für Möhren."),
("Gurke", "Buschbohne", CompatibilityRating.GUT, "Klassische gute Nachbarschaft."),
("Weißkohl", "Sellerie", CompatibilityRating.GUT, "Sellerie hält die Kohlfliege fern."),
("Brokkoli", "Sellerie", CompatibilityRating.GUT, "Sellerie hält Kohlschädlinge fern."),
("Spinat", "Radieschen", CompatibilityRating.GUT, "Platzsparende Kombination, gegenseitig förderlich."),
("Zwiebel", "Buschbohne", CompatibilityRating.SCHLECHT, "Zwiebeln hemmen das Bohnenwachstum."),
("Zwiebel", "Erbse", CompatibilityRating.SCHLECHT, "Zwiebeln und Hülsenfrüchtler vertragen sich nicht."),
("Zwiebel", "Weißkohl", CompatibilityRating.SCHLECHT, "Konkurrenz um Nährstoffe."),
]
async def seed_initial_data(db: AsyncSession) -> None:
# 1. Plant families
family_map: dict[str, PlantFamily] = {}
for f in FAMILIES:
result = await db.execute(select(PlantFamily).where(PlantFamily.name == f["name"]))
existing = result.scalar_one_or_none()
if existing:
family_map[f["name"]] = existing
else:
obj = PlantFamily(id=uuid.uuid4(), **f)
db.add(obj)
await db.flush()
family_map[f["name"]] = obj
# 2. Global plants
plant_map: dict[str, Plant] = {}
for (name, latin, family_name, nutrient, water, spacing, sow_start, sow_end, rest) in PLANTS:
result = await db.execute(
select(Plant).where(Plant.name == name, Plant.tenant_id == None) # noqa: E711
)
existing = result.scalar_one_or_none()
if existing:
plant_map[name] = existing
else:
obj = Plant(
id=uuid.uuid4(),
tenant_id=None,
family_id=family_map[family_name].id,
name=name,
latin_name=latin,
nutrient_demand=nutrient,
water_demand=water,
spacing_cm=spacing,
sowing_start_month=sow_start,
sowing_end_month=sow_end,
rest_years=rest,
)
db.add(obj)
await db.flush()
plant_map[name] = obj
# 3. Compatibilities (both directions)
for (name_a, name_b, rating, reason) in COMPATIBILITIES:
if name_a not in plant_map or name_b not in plant_map:
continue
id_a = plant_map[name_a].id
id_b = plant_map[name_b].id
result = await db.execute(
select(PlantCompatibility).where(
PlantCompatibility.plant_id_a == id_a,
PlantCompatibility.plant_id_b == id_b,
)
)
if not result.scalar_one_or_none():
db.add(PlantCompatibility(id=uuid.uuid4(), plant_id_a=id_a, plant_id_b=id_b, rating=rating, reason=reason))
# Reverse direction
result = await db.execute(
select(PlantCompatibility).where(
PlantCompatibility.plant_id_a == id_b,
PlantCompatibility.plant_id_b == id_a,
)
)
if not result.scalar_one_or_none():
db.add(PlantCompatibility(id=uuid.uuid4(), plant_id_a=id_b, plant_id_b=id_a, rating=rating, reason=reason))
await db.commit()
print("Seed-Daten erfolgreich eingespielt.")
async def main() -> None:
async with AsyncSessionLocal() as db:
await seed_initial_data(db)
if __name__ == "__main__":
asyncio.run(main())