import csv import os from datetime import datetime from sqlmodel import Session, select from app.database import engine, init_db from app.models import ( Location, Plant, Fertilizer, SeedSource, FertilizationLog, Harvest, WateringLog, ) CSV_DIR = "." # Directory containing the export CSV files def parse_csv(path): """Parse a CSV file and return a list of dictionaries.""" rows = [] with open(path, "r", encoding="utf-8") as f: reader = csv.DictReader(f) for row in reader: rows.append(row) return rows def parse_date(date_str): """Parse date string in MM/DD/YYYY format.""" if not date_str: return None try: return datetime.strptime(date_str.strip(), "%m/%d/%Y").date() except ValueError: return None def parse_date_or_datetime(date_str): """Parse date or datetime string.""" if not date_str: return None try: # Try datetime first dt = datetime.strptime( date_str.strip(), "%m/%d/%Y %I:%M:%S %p" ) return dt except ValueError: pass try: # Try date only return datetime.strptime(date_str.strip(), "%m/%d/%Y") except ValueError: return None def import_locations(session): """Import locations from CSV.""" print("Importing locations...") path = os.path.join(CSV_DIR, "export - Locations.csv") if not os.path.exists(path): print(f" File not found: {path}") return {} rows = parse_csv(path) location_map = {} for row in rows: loc_id_str = row.get("Location ID", "").strip() loc_name = row.get("Name", "").strip() if not loc_id_str or not loc_name: continue # Check if already exists existing = session.exec( select(Location).where(Location.location_id == loc_id_str) ).first() if existing: location_map[loc_id_str] = existing.id continue location = Location( location_id=loc_id_str, name=loc_name, type=row.get("Type", "Container").strip(), light=row.get("Light", "Full Sun").strip(), pot_size=row.get("Pot Size", "").strip() or None, notes=row.get("Notes", "").strip() or None, ) session.add(location) session.flush() location_map[loc_id_str] = location.id print(f" {loc_id_str}: {loc_name}") session.commit() print(f" Imported {len(location_map)} locations") return location_map def import_plants(session, location_map): """Import plants from CSV.""" print("Importing plants...") path = os.path.join(CSV_DIR, "export - Plants.csv") if not os.path.exists(path): print(f" File not found: {path}") return {} rows = parse_csv(path) plant_map = {} for row in rows: plant_id_str = row.get("Plant ID", "").strip() variety_name = row.get("Variety Name", "").strip() if not plant_id_str or not variety_name: continue # Check if already exists existing = session.exec( select(Plant).where(Plant.plant_id == plant_id_str) ).first() if existing: plant_map[plant_id_str] = existing.id continue loc_id_str = row.get("Location", "").strip() loc_id = location_map.get(loc_id_str) plant = Plant( plant_id=plant_id_str, variety_name=variety_name, species_type=row.get("Species / Type", "").strip(), family_genus=row.get("Family / Genus", "").strip() or None, category=row.get("Category", "Annual").strip(), status=row.get("Status", "Growing").strip(), location_id=loc_id, date_started_indoors=parse_date( row.get("Date Started Indoors", "") ), date_planted=parse_date(row.get("Date Planted", "")), light=row.get("Light", "Full Sun").strip(), pot_size=row.get("Notes", "").strip() or None, notes=row.get("Notes", "").strip() or None, ) session.add(plant) session.flush() plant_map[plant_id_str] = plant.id print(f" {plant_id_str}: {variety_name}") session.commit() print(f" Imported {len(plant_map)} plants") return plant_map def import_fertilizers(session): """Import fertilizers from CSV.""" print("Importing fertilizers...") path = os.path.join(CSV_DIR, "export - Fertilizers.csv") if not os.path.exists(path): print(f" File not found: {path}") return {} rows = parse_csv(path) fert_map = {} for row in rows: fert_id_str = row.get("Fertilizer ID", "").strip() name = row.get("Name", "").strip() if not fert_id_str or not name: continue existing = session.exec( select(Fertilizer).where( Fertilizer.fertilizer_id == fert_id_str ) ).first() if existing: fert_map[fert_id_str] = existing.id continue fertilizer = Fertilizer( fertilizer_id=fert_id_str, name=name, npk_ratio=row.get("N-P-K", "").strip() or None, best_for=row.get("Best For", "").strip() or None, notes=row.get("Notes", "").strip() or None, ) session.add(fertilizer) session.flush() fert_map[fert_id_str] = fertilizer.id print(f" {fert_id_str}: {name}") session.commit() print(f" Imported {len(fert_map)} fertilizers") return fert_map def import_seed_sources(session, plant_map): """Import seed sources from CSV.""" print("Importing seed sources...") path = os.path.join(CSV_DIR, "export - Seed Sources.csv") if not os.path.exists(path): print(f" File not found: {path}") return rows = parse_csv(path) for row in rows: source_id_str = row.get("Source ID", "").strip() source = row.get("Source", "").strip() variety = row.get("Variety", "").strip() if not source_id_str or not source: continue existing = session.exec( select(SeedSource).where( SeedSource.source_id == source_id_str ) ).first() if existing: continue seed_source = SeedSource( source_id=source_id_str, source=source, variety=variety, type=row.get("Type", "Vendor Purchase").strip(), acquired_date=parse_date(row.get("Acquired Date", "")), notes=row.get("Notes", "").strip() or None, ) session.add(seed_source) session.commit() print(" Imported seed sources") def import_fertilization_logs(session, plant_map, location_map, fert_map): """Import fertilization logs from CSV.""" print("Importing fertilization logs...") path = os.path.join(CSV_DIR, "export - Fertilization Logs.csv") if not os.path.exists(path): print(f" File not found: {path}") return rows = parse_csv(path) count = 0 for row in rows: fert_id_str = row.get("Fertilization ID", "").strip() date_str = row.get("Date", "").strip() fertilizer_name = row.get("Fertilizer", "").strip() if not fert_id_str or not date_str: continue log_date = parse_date(date_str) if not log_date: continue # Try to match fertilizer by name fert_id = None for fid, name in fert_map.items(): if ( name.lower() in fertilizer_name.lower() or fertilizer_name.lower() in name.lower() ): fert_id = fid break # Try to match plant by name plant_id = None plant_name = row.get("Plant", "").strip() for pid, name in plant_map.items(): if name.lower() == plant_name.lower(): plant_id = pid break log = FertilizationLog( date=log_date, fertilizer_name=fertilizer_name, fertilizer_id=fert_id, npk_ratio=row.get("N-P-K", "").strip() or None, amount_used=row.get("Amount", "").strip() or None, plant_id=plant_id, location_id=None, # Will be added later notes=row.get("Notes", "").strip() or None, ) session.add(log) count += 1 session.commit() print(f" Imported {count} fertilization logs") def import_watering_logs(session, location_map): """Import watering logs from CSV.""" print("Importing watering logs...") path = os.path.join(CSV_DIR, "export - Watering Logs.csv") if not os.path.exists(path): print(f" File not found: {path}") return rows = parse_csv(path) count = 0 for row in rows: watering_id_str = row.get("Watering ID", "").strip() date_str = row.get("Date", "").strip() location_name = row.get("Location", "").strip() if not watering_id_str or not date_str: continue log_date = parse_date(date_str) if not log_date: continue # Try to match location by name loc_id = None for loc_id_str, loc_name in location_map.items(): if loc_name.lower() == location_name.lower(): loc_id = loc_id_str break if not loc_id: continue log = WateringLog( watering_id=watering_id_str, location_id=loc_id, date=log_date, method=row.get("Method", "").strip() or None, amount=row.get("Amount", "").strip() or None, notes=row.get("Notes", "").strip() or None, ) session.add(log) count += 1 session.commit() print(f" Imported {count} watering logs") def import_harvest_logs(session, plant_map): """Import harvest logs from CSV.""" print("Importing harvest logs...") path = os.path.join(CSV_DIR, "export - Harvest Logs.csv") if not os.path.exists(path): print(f" File not found: {path}") return rows = parse_csv(path) count = 0 for row in rows: harvest_id_str = row.get("Harvest ID", "").strip() date_str = row.get("Date", "").strip() plant_name = row.get("Plant", "").strip() quantity_str = row.get("Quantity", "").strip() if not harvest_id_str or not date_str: continue harvest_date = parse_date(date_str) if not harvest_date: continue # Try to match plant by name plant_id = None for pid, name in plant_map.items(): if name.lower() == plant_name.lower(): plant_id = pid break if not plant_id: continue try: quantity = int(quantity_str) if quantity_str else 0 except ValueError: quantity = 0 harvest = Harvest( harvest_id=harvest_id_str, plant_id=plant_id, date=harvest_date, quantity=quantity, unit=row.get("Unit", "fruit").strip() or "fruit", weight=( float(row.get("Weight", "")) if row.get("Weight", "").strip() else None ), notes=row.get("Notes", "").strip() or None, ) session.add(harvest) count += 1 session.commit() print(f" Imported {count} harvest logs") def main(): print("Verdant v2.0 - Garden Database Import Script") print("=" * 50) # Initialize database init_db() # Import in correct order with Session(engine) as session: location_map = import_locations(session) plant_map = import_plants(session, location_map) fert_map = import_fertilizers(session) import_seed_sources(session, plant_map) import_fertilization_logs( session, plant_map, location_map, fert_map ) import_watering_logs(session, location_map) import_harvest_logs(session, plant_map) print("=" * 50) print("Import complete!") print("Database: data/garden.db") if __name__ == "__main__": main()