"""initial v2 schema

Revision ID: 1bec465da1e1
Revises: 
Create Date: 2026-07-03 04:10:56.665723
"""
from __future__ import annotations

from alembic import op
import sqlalchemy as sa
import pgvector.sqlalchemy
from sqlalchemy.dialects import postgresql

revision = '1bec465da1e1'
down_revision = None
branch_labels = None
depends_on = None


def upgrade() -> None:
    # Extensions first (manual addition — autogenerate doesn't emit these):
    #   vector  — pgvector, required by embeddings.embedding VECTOR(768)
    #   pg_trgm — trigram fuzzy matching for the note-pipeline entity
    #             resolver + search (P1/P4)
    op.execute("CREATE EXTENSION IF NOT EXISTS vector")
    op.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm")

    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('assistant_templates',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('name', sa.Text(), nullable=False),
    sa.Column('persona_prompt', sa.Text(), nullable=False),
    sa.Column('voice_examples', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'[]'::jsonb"), nullable=True),
    sa.Column('matching_profile', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_assistant_templates'))
    )
    op.create_table('gardens',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('name', sa.Text(), nullable=False),
    sa.Column('timezone', sa.Text(), server_default=sa.text("'America/Vancouver'"), nullable=False),
    sa.Column('latitude', sa.Double(), nullable=True),
    sa.Column('longitude', sa.Double(), nullable=True),
    sa.Column('settings', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_gardens'))
    )
    op.create_table('overview_versions',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('entity_type', sa.Text(), nullable=False),
    sa.Column('entity_id', sa.BigInteger(), nullable=False),
    sa.Column('content', sa.Text(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('author', sa.Text(), server_default=sa.text("'user'"), nullable=False),
    sa.Column('change_note', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_overview_versions'))
    )
    op.create_index('idx_overview_versions_entity', 'overview_versions', ['entity_type', 'entity_id', 'created_at'], unique=False)
    op.create_table('users',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('external_username', sa.Text(), nullable=False),
    sa.Column('display_name', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.Column('email', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.Column('settings', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('last_seen_at', sa.DateTime(timezone=True), nullable=True),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_users')),
    sa.UniqueConstraint('external_username', name=op.f('uq_users_external_username'))
    )
    op.create_table('activity_log',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('category', sa.Text(), nullable=False),
    sa.Column('entity_type', sa.Text(), nullable=False),
    sa.Column('entity_id', sa.BigInteger(), nullable=True),
    sa.Column('summary', sa.Text(), nullable=False),
    sa.Column('field', sa.Text(), nullable=True),
    sa.Column('old_value', sa.Text(), nullable=True),
    sa.Column('new_value', sa.Text(), nullable=True),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_activity_log_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_activity_log'))
    )
    op.create_index('idx_activity_log_category', 'activity_log', ['category', 'created_at'], unique=False)
    op.create_index('idx_activity_log_created', 'activity_log', ['created_at'], unique=False)
    op.create_index('idx_activity_log_entity', 'activity_log', ['entity_type', 'entity_id', 'created_at'], unique=False)
    op.create_index(op.f('ix_activity_log_garden_id'), 'activity_log', ['garden_id'], unique=False)
    op.create_table('ai_usage',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('job', sa.Text(), nullable=False),
    sa.Column('provider', sa.Text(), nullable=False),
    sa.Column('model', sa.Text(), nullable=False),
    sa.Column('input_tokens', sa.Integer(), server_default=sa.text('0'), nullable=True),
    sa.Column('output_tokens', sa.Integer(), server_default=sa.text('0'), nullable=True),
    sa.Column('cached_input_tokens', sa.Integer(), server_default=sa.text('0'), nullable=True),
    sa.Column('cost_usd', sa.Double(), nullable=True),
    sa.Column('capture_id', sa.BigInteger(), nullable=True),
    sa.Column('session_id', sa.BigInteger(), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_ai_usage_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_ai_usage'))
    )
    op.create_index('ix_ai_usage_garden_created', 'ai_usage', ['garden_id', 'created_at'], unique=False)
    op.create_index(op.f('ix_ai_usage_garden_id'), 'ai_usage', ['garden_id'], unique=False)
    op.create_table('areas',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('name', sa.Text(), nullable=False),
    sa.Column('notes', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('parent_id', sa.BigInteger(), nullable=True),
    sa.Column('sort_order', sa.Integer(), nullable=True),
    sa.Column('short_name', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('length_ft', sa.Double(), server_default=sa.text('0'), nullable=True),
    sa.Column('width_ft', sa.Double(), server_default=sa.text('0'), nullable=True),
    sa.Column('garden_area_sqft', sa.Double(), server_default=sa.text('0'), nullable=True),
    sa.Column('structure_features', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('sunlight', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('soil_environment', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('featured_image_path', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('sketch_rotation', sa.Integer(), server_default=sa.text('0'), nullable=True),
    sa.Column('parent_pos_x', sa.Double(), nullable=True),
    sa.Column('parent_pos_y', sa.Double(), nullable=True),
    sa.Column('is_archived', sa.Boolean(), server_default=sa.text('false'), nullable=True),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_areas_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_areas'))
    )
    op.create_index('idx_areas_archived', 'areas', ['is_archived'], unique=False)
    op.create_index(op.f('ix_areas_garden_id'), 'areas', ['garden_id'], unique=False)
    op.create_table('artifacts',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('kind', sa.Text(), nullable=False),
    sa.Column('title', sa.Text(), nullable=False),
    sa.Column('filename', sa.Text(), nullable=False),
    sa.Column('generated_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('notes', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_artifacts_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_artifacts')),
    sa.UniqueConstraint('filename', name=op.f('uq_artifacts_filename'))
    )
    op.create_index('idx_artifacts_kind', 'artifacts', ['kind'], unique=False)
    op.create_index(op.f('ix_artifacts_garden_id'), 'artifacts', ['garden_id'], unique=False)
    op.create_table('assistants',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('name', sa.Text(), nullable=False),
    sa.Column('role', sa.Text(), server_default=sa.text("'primary'"), nullable=False),
    sa.Column('persona_prompt', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('avatar_path', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('voice_examples', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'[]'::jsonb"), nullable=True),
    sa.Column('notification_style', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=True),
    sa.Column('model_prefs', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=True),
    sa.Column('is_active', sa.Boolean(), server_default=sa.text('true'), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.CheckConstraint("role IN ('primary', 'interviewer')", name=op.f('ck_assistants_role')),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_assistants_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_assistants'))
    )
    op.create_index(op.f('ix_assistants_garden_id'), 'assistants', ['garden_id'], unique=False)
    op.create_table('chat_sessions',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('ended_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('title', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('summary', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('activity_log_id', sa.BigInteger(), nullable=True),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('status', sa.Text(), server_default=sa.text("'open'"), nullable=False),
    sa.Column('resolution', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
    sa.Column('resolution_note_id', sa.BigInteger(), nullable=True),
    sa.CheckConstraint("status IN ('open', 'resolved', 'discarded')", name=op.f('ck_chat_sessions_status')),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_chat_sessions_garden_id_gardens')),
    sa.ForeignKeyConstraint(['resolution_note_id'], ['notes.id'], name='fk_chat_sessions_resolution_note', use_alter=True),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_chat_sessions'))
    )
    op.create_index(op.f('ix_chat_sessions_created_at'), 'chat_sessions', ['created_at'], unique=False)
    op.create_index(op.f('ix_chat_sessions_garden_id'), 'chat_sessions', ['garden_id'], unique=False)
    op.create_table('embeddings',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('source_type', sa.Text(), nullable=False),
    sa.Column('source_id', sa.BigInteger(), nullable=False),
    sa.Column('model', sa.Text(), nullable=False),
    sa.Column('content_hash', sa.Text(), nullable=False),
    sa.Column('embedding', pgvector.sqlalchemy.vector.VECTOR(dim=768), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.CheckConstraint("source_type IN ('note', 'field_note', 'chat_message')", name=op.f('ck_embeddings_source_type')),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_embeddings_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_embeddings')),
    sa.UniqueConstraint('source_type', 'source_id', 'model', name=op.f('uq_embeddings_source_type'))
    )
    op.create_index('ix_embeddings_embedding_hnsw', 'embeddings', ['embedding'], unique=False, postgresql_using='hnsw', postgresql_ops={'embedding': 'vector_cosine_ops'})
    op.create_index(op.f('ix_embeddings_garden_id'), 'embeddings', ['garden_id'], unique=False)
    op.create_table('field_notes',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('captured_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('audio_path', sa.Text(), nullable=True),
    sa.Column('photo_paths', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('text', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('transcript', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('status', sa.Text(), server_default=sa.text("'new'"), nullable=False),
    sa.Column('processed_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('processing_notes', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('body', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('kind', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('comment_date', sa.Date(), nullable=True),
    sa.Column('parent_id', sa.BigInteger(), nullable=True),
    sa.Column('is_archived', sa.Boolean(), server_default=sa.text('false'), nullable=True),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_field_notes_garden_id_gardens')),
    sa.ForeignKeyConstraint(['parent_id'], ['field_notes.id'], name=op.f('fk_field_notes_parent_id_field_notes'), ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_field_notes'))
    )
    op.create_index(op.f('ix_field_notes_comment_date'), 'field_notes', ['comment_date', 'created_at'], unique=False)
    op.create_index(op.f('ix_field_notes_garden_id'), 'field_notes', ['garden_id'], unique=False)
    op.create_index(op.f('ix_field_notes_is_archived'), 'field_notes', ['is_archived'], unique=False)
    op.create_index(op.f('ix_field_notes_status'), 'field_notes', ['status', 'created_at'], unique=False)
    op.create_table('garden_members',
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('user_id', sa.BigInteger(), nullable=False),
    sa.Column('role', sa.Text(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.CheckConstraint("role IN ('owner', 'member')", name=op.f('ck_garden_members_role')),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_garden_members_garden_id_gardens')),
    sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('fk_garden_members_user_id_users')),
    sa.PrimaryKeyConstraint('garden_id', 'user_id', name=op.f('pk_garden_members'))
    )
    op.create_table('notification_channels',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('user_id', sa.BigInteger(), nullable=False),
    sa.Column('kind', sa.Text(), nullable=False),
    sa.Column('address', sa.Text(), nullable=False),
    sa.Column('config', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=True),
    sa.Column('is_active', sa.Boolean(), server_default=sa.text('true'), nullable=True),
    sa.Column('failure_count', sa.Integer(), server_default=sa.text('0'), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('last_used_at', sa.DateTime(timezone=True), nullable=True),
    sa.CheckConstraint("kind IN ('webpush','telegram')", name=op.f('ck_notification_channels_kind')),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_notification_channels_garden_id_gardens')),
    sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('fk_notification_channels_user_id_users')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_notification_channels')),
    sa.UniqueConstraint('kind', 'address', name=op.f('uq_notification_channels_kind'))
    )
    op.create_index(op.f('ix_notification_channels_garden_id'), 'notification_channels', ['garden_id'], unique=False)
    op.create_table('species',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('name', sa.Text(), nullable=False),
    sa.Column('common_name', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('description', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('featured_image_path', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('plants_per_unit', sa.Integer(), server_default=sa.text('1'), nullable=True),
    sa.Column('space_per_unit_sqft', sa.Double(), server_default=sa.text('1.0'), nullable=True),
    sa.Column('is_archived', sa.Boolean(), server_default=sa.text('false'), nullable=True),
    sa.Column('type', sa.Text(), server_default=sa.text("'plant'"), nullable=False),
    sa.Column('primary_function', sa.Text(), server_default=sa.text("'edible'"), nullable=False),
    sa.Column('mature_spacing_sqft', sa.Double(), nullable=True),
    sa.Column('rotation_family', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.Column('sun_pref', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.Column('life_cycle', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.Column('edible_part', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.Column('notes_local', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_species_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_species')),
    sa.UniqueConstraint('garden_id', 'name', name=op.f('uq_species_garden_id'))
    )
    op.create_index('idx_species_archived', 'species', ['is_archived'], unique=False)
    op.create_index(op.f('ix_species_garden_id'), 'species', ['garden_id'], unique=False)
    op.create_table('supplies',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('name', sa.Text(), nullable=False),
    sa.Column('category', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('amount', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('notes', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('quantity_on_hand', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('quantity_needed', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('needed', sa.Boolean(), server_default=sa.text('false'), nullable=True),
    sa.Column('featured_image_path', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('is_archived', sa.Boolean(), server_default=sa.text('false'), nullable=True),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_supplies_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_supplies'))
    )
    op.create_index('idx_supplies_archived', 'supplies', ['is_archived'], unique=False)
    op.create_index(op.f('ix_supplies_garden_id'), 'supplies', ['garden_id'], unique=False)
    op.create_table('watering_stations',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('name', sa.Text(), nullable=False),
    sa.Column('notes', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('current_schedule', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('featured_image_path', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('is_archived', sa.Boolean(), server_default=sa.text('false'), nullable=True),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_watering_stations_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_watering_stations'))
    )
    op.create_index('idx_watering_stations_archived', 'watering_stations', ['is_archived'], unique=False)
    op.create_index(op.f('ix_watering_stations_garden_id'), 'watering_stations', ['garden_id'], unique=False)
    op.create_table('weather_daily',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('date', sa.Date(), nullable=False),
    sa.Column('kind', sa.Text(), nullable=False),
    sa.Column('temp_min_c', sa.Double(), nullable=True),
    sa.Column('temp_max_c', sa.Double(), nullable=True),
    sa.Column('precip_mm', sa.Double(), nullable=True),
    sa.Column('wind_max_kph', sa.Double(), nullable=True),
    sa.Column('conditions', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.Column('raw', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
    sa.Column('fetched_at', sa.DateTime(timezone=True), nullable=False),
    sa.CheckConstraint("kind IN ('observed', 'forecast')", name=op.f('ck_weather_daily_kind')),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_weather_daily_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_weather_daily')),
    sa.UniqueConstraint('garden_id', 'date', 'kind', name=op.f('uq_weather_daily_garden_id'))
    )
    op.create_index(op.f('ix_weather_daily_garden_id'), 'weather_daily', ['garden_id'], unique=False)
    op.create_table('years',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('year', sa.Integer(), nullable=False),
    sa.Column('notes', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('featured_image_path', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('is_archived', sa.Boolean(), server_default=sa.text('false'), nullable=True),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_years_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_years')),
    sa.UniqueConstraint('garden_id', 'year', name=op.f('uq_years_garden_id'))
    )
    op.create_index('idx_years_archived', 'years', ['is_archived'], unique=False)
    op.create_index(op.f('ix_years_garden_id'), 'years', ['garden_id'], unique=False)
    op.create_table('area_stations',
    sa.Column('area_id', sa.BigInteger(), nullable=False),
    sa.Column('station_id', sa.BigInteger(), nullable=False),
    sa.ForeignKeyConstraint(['area_id'], ['areas.id'], name=op.f('fk_area_stations_area_id_areas'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['station_id'], ['watering_stations.id'], name=op.f('fk_area_stations_station_id_watering_stations'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('area_id', 'station_id', name=op.f('pk_area_stations'))
    )
    op.create_table('chat_messages',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('session_id', sa.BigInteger(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('role', sa.Text(), nullable=False),
    sa.Column('text', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('photo_paths', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('tool_calls', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('tokens_used', sa.Integer(), server_default=sa.text('0'), nullable=True),
    sa.ForeignKeyConstraint(['session_id'], ['chat_sessions.id'], name=op.f('fk_chat_messages_session_id_chat_sessions'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_chat_messages'))
    )
    op.create_index(op.f('ix_chat_messages_session_id'), 'chat_messages', ['session_id', 'id'], unique=False)
    op.create_table('field_note_targets',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('field_note_id', sa.BigInteger(), nullable=False),
    sa.Column('target_type', sa.Text(), nullable=False),
    sa.Column('target_id', sa.BigInteger(), nullable=False),
    sa.Column('is_primary', sa.Boolean(), server_default=sa.text('false'), nullable=True),
    sa.ForeignKeyConstraint(['field_note_id'], ['field_notes.id'], name=op.f('fk_field_note_targets_field_note_id_field_notes'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_field_note_targets')),
    sa.UniqueConstraint('field_note_id', 'target_type', 'target_id', name=op.f('uq_field_note_targets_field_note_id'))
    )
    op.create_index(op.f('ix_field_note_targets_field_note_id'), 'field_note_targets', ['field_note_id'], unique=False)
    op.create_index(op.f('ix_field_note_targets_target_type'), 'field_note_targets', ['target_type', 'target_id'], unique=False)
    op.create_table('notes',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('assistant_id', sa.BigInteger(), nullable=True),
    sa.Column('source_capture_id', sa.BigInteger(), nullable=True),
    sa.Column('source_conversation_id', sa.BigInteger(), nullable=True),
    sa.Column('author', sa.Text(), nullable=False),
    sa.Column('body_md', sa.Text(), nullable=False),
    sa.Column('body_source', sa.Text(), server_default=sa.text("'assistant'"), nullable=False),
    sa.Column('voice_locked', sa.Boolean(), server_default=sa.text('false'), nullable=False),
    sa.Column('kind', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.Column('note_date', sa.Date(), nullable=True),
    sa.Column('is_pinned', sa.Boolean(), server_default=sa.text('false'), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('is_archived', sa.Boolean(), server_default=sa.text('false'), nullable=False),
    sa.CheckConstraint("author IN ('assistant', 'user')", name=op.f('ck_notes_author')),
    sa.CheckConstraint("body_source IN ('assistant', 'user_edited', 'user_raw')", name=op.f('ck_notes_body_source')),
    sa.ForeignKeyConstraint(['assistant_id'], ['assistants.id'], name=op.f('fk_notes_assistant_id_assistants')),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_notes_garden_id_gardens')),
    sa.ForeignKeyConstraint(['source_capture_id'], ['field_notes.id'], name=op.f('fk_notes_source_capture_id_field_notes')),
    sa.ForeignKeyConstraint(['source_conversation_id'], ['chat_sessions.id'], name=op.f('fk_notes_source_conversation_id_chat_sessions')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_notes'))
    )
    op.create_index(op.f('ix_notes_garden_id'), 'notes', ['garden_id'], unique=False)
    op.create_index('ix_notes_garden_id_note_date', 'notes', ['garden_id', 'note_date'], unique=False)
    op.create_table('onboarding_sessions',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('chat_session_id', sa.BigInteger(), nullable=True),
    sa.Column('stage', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('bootstrap', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=True),
    sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.ForeignKeyConstraint(['chat_session_id'], ['chat_sessions.id'], name=op.f('fk_onboarding_sessions_chat_session_id_chat_sessions')),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_onboarding_sessions_garden_id_gardens')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_onboarding_sessions'))
    )
    op.create_index(op.f('ix_onboarding_sessions_garden_id'), 'onboarding_sessions', ['garden_id'], unique=False)
    op.create_table('pipeline_runs',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('capture_id', sa.BigInteger(), nullable=False),
    sa.Column('stage', sa.Text(), nullable=False),
    sa.Column('model', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('prompt_version', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('output', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
    sa.Column('confidence', sa.Double(), nullable=True),
    sa.Column('latency_ms', sa.Integer(), nullable=True),
    sa.Column('tokens', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.ForeignKeyConstraint(['capture_id'], ['field_notes.id'], name=op.f('fk_pipeline_runs_capture_id_field_notes')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_pipeline_runs'))
    )
    op.create_index(op.f('ix_pipeline_runs_capture_id'), 'pipeline_runs', ['capture_id'], unique=False)
    op.create_table('plantings',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('name', sa.Text(), nullable=False),
    sa.Column('year_id', sa.BigInteger(), nullable=True),
    sa.Column('source', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('notes', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('status', sa.Text(), server_default=sa.text("'planted'"), nullable=True),
    sa.Column('featured_image_path', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('is_archived', sa.Boolean(), server_default=sa.text('false'), nullable=True),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_plantings_garden_id_gardens')),
    sa.ForeignKeyConstraint(['year_id'], ['years.id'], name=op.f('fk_plantings_year_id_years'), ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_plantings'))
    )
    op.create_index('idx_plantings_archived', 'plantings', ['is_archived'], unique=False)
    op.create_index(op.f('ix_plantings_garden_id'), 'plantings', ['garden_id'], unique=False)
    op.create_table('tool_suggestions',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('session_id', sa.BigInteger(), nullable=True),
    sa.Column('name', sa.Text(), nullable=False),
    sa.Column('signature', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('why_useful', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('example_question', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('status', sa.Text(), server_default=sa.text("'pending'"), nullable=False),
    sa.ForeignKeyConstraint(['session_id'], ['chat_sessions.id'], name=op.f('fk_tool_suggestions_session_id_chat_sessions'), ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_tool_suggestions'))
    )
    op.create_index(op.f('ix_tool_suggestions_status'), 'tool_suggestions', ['status', 'created_at'], unique=False)
    op.create_table('varieties',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('species_id', sa.BigInteger(), nullable=True),
    sa.Column('name', sa.Text(), nullable=False),
    sa.Column('description', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('featured_image_path', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('plants_per_unit', sa.Integer(), nullable=True),
    sa.Column('space_per_unit_sqft', sa.Double(), nullable=True),
    sa.Column('is_archived', sa.Boolean(), server_default=sa.text('false'), nullable=True),
    sa.Column('mature_spacing_sqft', sa.Double(), nullable=True),
    sa.Column('harvest_window', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.Column('flavor_notes', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.ForeignKeyConstraint(['species_id'], ['species.id'], name=op.f('fk_varieties_species_id_species'), ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_varieties'))
    )
    op.create_index('idx_varieties_archived', 'varieties', ['is_archived'], unique=False)
    op.create_table('care_rules',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('species_id', sa.BigInteger(), nullable=True),
    sa.Column('variety_id', sa.BigInteger(), nullable=True),
    sa.Column('action', sa.Text(), nullable=False),
    sa.Column('cadence', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
    sa.Column('condition', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
    sa.Column('note', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('source', sa.Text(), server_default=sa.text("'seed_default'"), nullable=False),
    sa.Column('enabled', sa.Boolean(), server_default=sa.text('true'), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.CheckConstraint("source IN ('seed_default','assistant','user')", name=op.f('ck_care_rules_source')),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_care_rules_garden_id_gardens')),
    sa.ForeignKeyConstraint(['species_id'], ['species.id'], name=op.f('fk_care_rules_species_id_species')),
    sa.ForeignKeyConstraint(['variety_id'], ['varieties.id'], name=op.f('fk_care_rules_variety_id_varieties')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_care_rules'))
    )
    op.create_index(op.f('ix_care_rules_garden_id'), 'care_rules', ['garden_id'], unique=False)
    op.create_table('note_media',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('note_id', sa.BigInteger(), nullable=False),
    sa.Column('kind', sa.Text(), nullable=False),
    sa.Column('path', sa.Text(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.CheckConstraint("kind IN ('photo', 'audio')", name=op.f('ck_note_media_kind')),
    sa.ForeignKeyConstraint(['note_id'], ['notes.id'], name=op.f('fk_note_media_note_id_notes'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_note_media'))
    )
    op.create_table('note_revisions',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('note_id', sa.BigInteger(), nullable=False),
    sa.Column('body', sa.Text(), nullable=False),
    sa.Column('body_source', sa.Text(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.ForeignKeyConstraint(['note_id'], ['notes.id'], name=op.f('fk_note_revisions_note_id_notes'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_note_revisions'))
    )
    op.create_table('note_targets',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('note_id', sa.BigInteger(), nullable=False),
    sa.Column('target_type', sa.Text(), nullable=False),
    sa.Column('target_id', sa.BigInteger(), nullable=False),
    sa.Column('is_primary', sa.Boolean(), server_default=sa.text('false'), nullable=False),
    sa.Column('confidence', sa.Double(), nullable=True),
    sa.Column('link_source', sa.Text(), server_default=sa.text("'resolver'"), nullable=False),
    sa.CheckConstraint("link_source IN ('resolver', 'user', 'confirmed')", name=op.f('ck_note_targets_link_source')),
    sa.ForeignKeyConstraint(['note_id'], ['notes.id'], name=op.f('fk_note_targets_note_id_notes'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_note_targets')),
    sa.UniqueConstraint('note_id', 'target_type', 'target_id', name=op.f('uq_note_targets_note_id'))
    )
    op.create_index(op.f('ix_note_targets_target_type'), 'note_targets', ['target_type', 'target_id'], unique=False)
    op.create_table('plants',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('planting_id', sa.BigInteger(), nullable=True),
    sa.Column('species_id', sa.BigInteger(), nullable=True),
    sa.Column('variety_id', sa.BigInteger(), nullable=True),
    sa.Column('area_id', sa.BigInteger(), nullable=True),
    sa.Column('quantity', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('source', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('status', sa.Text(), server_default=sa.text("'planted'"), nullable=True),
    sa.Column('notes', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('featured_image_path', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('positions', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('is_archived', sa.Boolean(), server_default=sa.text('false'), nullable=True),
    sa.Column('flavor_rating', sa.Integer(), nullable=True),
    sa.Column('vigor_rating', sa.Integer(), nullable=True),
    sa.Column('would_plant_again', sa.Integer(), nullable=True),
    sa.Column('outcome_summary', sa.Text(), server_default=sa.text("''"), nullable=False),
    sa.CheckConstraint("status IN ('idea', 'unplanted', 'planted', 'removed')", name=op.f('ck_plants_status_vocab')),
    sa.ForeignKeyConstraint(['area_id'], ['areas.id'], name=op.f('fk_plants_area_id_areas'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_plants_garden_id_gardens')),
    sa.ForeignKeyConstraint(['planting_id'], ['plantings.id'], name=op.f('fk_plants_planting_id_plantings'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['species_id'], ['species.id'], name=op.f('fk_plants_species_id_species'), ondelete='SET NULL'),
    sa.ForeignKeyConstraint(['variety_id'], ['varieties.id'], name=op.f('fk_plants_variety_id_varieties'), ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_plants'))
    )
    op.create_index('idx_plants_archived', 'plants', ['is_archived'], unique=False)
    op.create_index(op.f('ix_plants_garden_id'), 'plants', ['garden_id'], unique=False)
    op.create_table('resolver_feedback',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('note_id', sa.BigInteger(), nullable=True),
    sa.Column('mention', sa.Text(), nullable=False),
    sa.Column('wrong_target', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
    sa.Column('correct_target', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_resolver_feedback_garden_id_gardens')),
    sa.ForeignKeyConstraint(['note_id'], ['notes.id'], name=op.f('fk_resolver_feedback_note_id_notes')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_resolver_feedback'))
    )
    op.create_index(op.f('ix_resolver_feedback_garden_id'), 'resolver_feedback', ['garden_id'], unique=False)
    op.create_table('scheduled_actions',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('assistant_id', sa.BigInteger(), nullable=True),
    sa.Column('title', sa.Text(), nullable=False),
    sa.Column('body_md', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('action_type', sa.Text(), nullable=False),
    sa.Column('target_type', sa.Text(), nullable=True),
    sa.Column('target_id', sa.BigInteger(), nullable=True),
    sa.Column('trigger_type', sa.Text(), nullable=False),
    sa.Column('trigger_config', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
    sa.Column('urgency', sa.Text(), server_default=sa.text("'normal'"), nullable=False),
    sa.Column('fire_limit', sa.Text(), server_default=sa.text("'once'"), nullable=False),
    sa.Column('next_eval_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('status', sa.Text(), server_default=sa.text("'armed'"), nullable=False),
    sa.Column('snooze_until', sa.DateTime(timezone=True), nullable=True),
    sa.Column('last_fired_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('source_note_id', sa.BigInteger(), nullable=True),
    sa.Column('source_session_id', sa.BigInteger(), nullable=True),
    sa.Column('care_rule_id', sa.BigInteger(), nullable=True),
    sa.Column('created_by', sa.Text(), server_default=sa.text("'user'"), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
    sa.CheckConstraint("action_type IN ('fertilize','prune','pest','water','plant','harvest','winterize','observe','custom')", name=op.f('ck_scheduled_actions_action_type')),
    sa.CheckConstraint("created_by IN ('user','assistant')", name=op.f('ck_scheduled_actions_created_by')),
    sa.CheckConstraint("fire_limit IN ('once','once_per_season','unlimited')", name=op.f('ck_scheduled_actions_fire_limit')),
    sa.CheckConstraint("status IN ('armed','fired','done','dismissed','expired','paused')", name=op.f('ck_scheduled_actions_status')),
    sa.CheckConstraint("trigger_type IN ('date','recurrence','weather','cadence','garden_state')", name=op.f('ck_scheduled_actions_trigger_type')),
    sa.CheckConstraint("urgency IN ('digest','normal','urgent')", name=op.f('ck_scheduled_actions_urgency')),
    sa.ForeignKeyConstraint(['assistant_id'], ['assistants.id'], name=op.f('fk_scheduled_actions_assistant_id_assistants')),
    sa.ForeignKeyConstraint(['care_rule_id'], ['care_rules.id'], name=op.f('fk_scheduled_actions_care_rule_id_care_rules')),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_scheduled_actions_garden_id_gardens')),
    sa.ForeignKeyConstraint(['source_note_id'], ['notes.id'], name=op.f('fk_scheduled_actions_source_note_id_notes')),
    sa.ForeignKeyConstraint(['source_session_id'], ['chat_sessions.id'], name=op.f('fk_scheduled_actions_source_session_id_chat_sessions')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_scheduled_actions'))
    )
    op.create_index(op.f('ix_scheduled_actions_garden_id'), 'scheduled_actions', ['garden_id'], unique=False)
    op.create_index('ix_scheduled_actions_garden_status_eval', 'scheduled_actions', ['garden_id', 'status', 'next_eval_at'], unique=False)
    op.create_table('notification_dispatches',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('garden_id', sa.BigInteger(), nullable=False),
    sa.Column('scheduled_action_id', sa.BigInteger(), nullable=True),
    sa.Column('channel_id', sa.BigInteger(), nullable=True),
    sa.Column('kind', sa.Text(), nullable=False),
    sa.Column('fire_key', sa.Text(), nullable=False),
    sa.Column('title', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('body', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('payload', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=True),
    sa.Column('status', sa.Text(), nullable=False),
    sa.Column('error_code', sa.Text(), nullable=True),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('sent_at', sa.DateTime(timezone=True), nullable=True),
    sa.CheckConstraint("kind IN ('reminder','proactive','digest','system')", name=op.f('ck_notification_dispatches_kind')),
    sa.CheckConstraint("status IN ('pending','sent','failed','suppressed')", name=op.f('ck_notification_dispatches_status')),
    sa.ForeignKeyConstraint(['channel_id'], ['notification_channels.id'], name=op.f('fk_notification_dispatches_channel_id_notification_channels')),
    sa.ForeignKeyConstraint(['garden_id'], ['gardens.id'], name=op.f('fk_notification_dispatches_garden_id_gardens')),
    sa.ForeignKeyConstraint(['scheduled_action_id'], ['scheduled_actions.id'], name=op.f('fk_notification_dispatches_scheduled_action_id_scheduled_actions')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_notification_dispatches')),
    sa.UniqueConstraint('fire_key', 'channel_id', name=op.f('uq_notification_dispatches_fire_key'))
    )
    op.create_index(op.f('ix_notification_dispatches_garden_id'), 'notification_dispatches', ['garden_id'], unique=False)
    op.create_table('reminder_events',
    sa.Column('id', sa.BigInteger(), sa.Identity(always=False), nullable=False),
    sa.Column('scheduled_action_id', sa.BigInteger(), nullable=False),
    sa.Column('fired_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('rendered_text', sa.Text(), server_default=sa.text("''"), nullable=True),
    sa.Column('channels', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=True),
    sa.Column('acknowledged_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('done_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('dismissed_at', sa.DateTime(timezone=True), nullable=True),
    sa.Column('done_note_id', sa.BigInteger(), nullable=True),
    sa.ForeignKeyConstraint(['done_note_id'], ['notes.id'], name=op.f('fk_reminder_events_done_note_id_notes')),
    sa.ForeignKeyConstraint(['scheduled_action_id'], ['scheduled_actions.id'], name=op.f('fk_reminder_events_scheduled_action_id_scheduled_actions')),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_reminder_events'))
    )
    # ### end Alembic commands ###

    # Manual addition: the chat_sessions <-> notes FK cycle is broken with
    # use_alter=True on the model, which autogenerate does NOT emit inside
    # create_table — add the constraint explicitly after both tables exist.
    op.create_foreign_key(
        "fk_chat_sessions_resolution_note",
        "chat_sessions",
        "notes",
        ["resolution_note_id"],
        ["id"],
    )


def downgrade() -> None:
    # Manual: drop the cycle-breaking FK before its target tables go.
    op.drop_constraint(
        "fk_chat_sessions_resolution_note", "chat_sessions", type_="foreignkey"
    )
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('reminder_events')
    op.drop_index(op.f('ix_notification_dispatches_garden_id'), table_name='notification_dispatches')
    op.drop_table('notification_dispatches')
    op.drop_index('ix_scheduled_actions_garden_status_eval', table_name='scheduled_actions')
    op.drop_index(op.f('ix_scheduled_actions_garden_id'), table_name='scheduled_actions')
    op.drop_table('scheduled_actions')
    op.drop_index(op.f('ix_resolver_feedback_garden_id'), table_name='resolver_feedback')
    op.drop_table('resolver_feedback')
    op.drop_index(op.f('ix_plants_garden_id'), table_name='plants')
    op.drop_index('idx_plants_archived', table_name='plants')
    op.drop_table('plants')
    op.drop_index(op.f('ix_note_targets_target_type'), table_name='note_targets')
    op.drop_table('note_targets')
    op.drop_table('note_revisions')
    op.drop_table('note_media')
    op.drop_index(op.f('ix_care_rules_garden_id'), table_name='care_rules')
    op.drop_table('care_rules')
    op.drop_index('idx_varieties_archived', table_name='varieties')
    op.drop_table('varieties')
    op.drop_index(op.f('ix_tool_suggestions_status'), table_name='tool_suggestions')
    op.drop_table('tool_suggestions')
    op.drop_index(op.f('ix_plantings_garden_id'), table_name='plantings')
    op.drop_index('idx_plantings_archived', table_name='plantings')
    op.drop_table('plantings')
    op.drop_index(op.f('ix_pipeline_runs_capture_id'), table_name='pipeline_runs')
    op.drop_table('pipeline_runs')
    op.drop_index(op.f('ix_onboarding_sessions_garden_id'), table_name='onboarding_sessions')
    op.drop_table('onboarding_sessions')
    op.drop_index('ix_notes_garden_id_note_date', table_name='notes')
    op.drop_index(op.f('ix_notes_garden_id'), table_name='notes')
    op.drop_table('notes')
    op.drop_index(op.f('ix_field_note_targets_target_type'), table_name='field_note_targets')
    op.drop_index(op.f('ix_field_note_targets_field_note_id'), table_name='field_note_targets')
    op.drop_table('field_note_targets')
    op.drop_index(op.f('ix_chat_messages_session_id'), table_name='chat_messages')
    op.drop_table('chat_messages')
    op.drop_table('area_stations')
    op.drop_index(op.f('ix_years_garden_id'), table_name='years')
    op.drop_index('idx_years_archived', table_name='years')
    op.drop_table('years')
    op.drop_index(op.f('ix_weather_daily_garden_id'), table_name='weather_daily')
    op.drop_table('weather_daily')
    op.drop_index(op.f('ix_watering_stations_garden_id'), table_name='watering_stations')
    op.drop_index('idx_watering_stations_archived', table_name='watering_stations')
    op.drop_table('watering_stations')
    op.drop_index(op.f('ix_supplies_garden_id'), table_name='supplies')
    op.drop_index('idx_supplies_archived', table_name='supplies')
    op.drop_table('supplies')
    op.drop_index(op.f('ix_species_garden_id'), table_name='species')
    op.drop_index('idx_species_archived', table_name='species')
    op.drop_table('species')
    op.drop_index(op.f('ix_notification_channels_garden_id'), table_name='notification_channels')
    op.drop_table('notification_channels')
    op.drop_table('garden_members')
    op.drop_index(op.f('ix_field_notes_status'), table_name='field_notes')
    op.drop_index(op.f('ix_field_notes_is_archived'), table_name='field_notes')
    op.drop_index(op.f('ix_field_notes_garden_id'), table_name='field_notes')
    op.drop_index(op.f('ix_field_notes_comment_date'), table_name='field_notes')
    op.drop_table('field_notes')
    op.drop_index(op.f('ix_embeddings_garden_id'), table_name='embeddings')
    op.drop_index('ix_embeddings_embedding_hnsw', table_name='embeddings', postgresql_using='hnsw', postgresql_ops={'embedding': 'vector_cosine_ops'})
    op.drop_table('embeddings')
    op.drop_index(op.f('ix_chat_sessions_garden_id'), table_name='chat_sessions')
    op.drop_index(op.f('ix_chat_sessions_created_at'), table_name='chat_sessions')
    op.drop_table('chat_sessions')
    op.drop_index(op.f('ix_assistants_garden_id'), table_name='assistants')
    op.drop_table('assistants')
    op.drop_index(op.f('ix_artifacts_garden_id'), table_name='artifacts')
    op.drop_index('idx_artifacts_kind', table_name='artifacts')
    op.drop_table('artifacts')
    op.drop_index(op.f('ix_areas_garden_id'), table_name='areas')
    op.drop_index('idx_areas_archived', table_name='areas')
    op.drop_table('areas')
    op.drop_index(op.f('ix_ai_usage_garden_id'), table_name='ai_usage')
    op.drop_index('ix_ai_usage_garden_created', table_name='ai_usage')
    op.drop_table('ai_usage')
    op.drop_index(op.f('ix_activity_log_garden_id'), table_name='activity_log')
    op.drop_index('idx_activity_log_entity', table_name='activity_log')
    op.drop_index('idx_activity_log_created', table_name='activity_log')
    op.drop_index('idx_activity_log_category', table_name='activity_log')
    op.drop_table('activity_log')
    op.drop_table('users')
    op.drop_index('idx_overview_versions_entity', table_name='overview_versions')
    op.drop_table('overview_versions')
    op.drop_table('gardens')
    op.drop_table('assistant_templates')
    # ### end Alembic commands ###
