""" SQLAlchemy Datenmodell für Architektur-Planungs-App Verwendet PostgreSQL mit PostGIS Extension """ from sqlalchemy import ( Column, String, Integer, Float, Enum as SQLEnum, ForeignKey, Table, Text, ARRAY ) from sqlalchemy.dialects.postgresql import UUID, ENUM from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship from geoalchemy2 import Geometry import uuid import enum Base = declarative_base() # ============================================================================ # ENUMS # ============================================================================ class StatusProzess(enum.Enum): EINGANG = "Eingang" ANALYSE = "Analyse" STUDIE = "Studie" PLANUNG = "Planung" BAURECHTSVERFAHREN = "Baurechtsverfahren" UMSETZUNG = "Umsetzung" ARCHIV = "Archiv" class DokumentTyp(enum.Enum): DATEI = "Datei" URL = "Url" class TagTyp(enum.Enum): KATASTER_OBJEKTE = "Kataster Objekte" KATASTER_WERKELEITUNGEN = "Kataster Werkeleitungen" KATASTER_BELASTETE_STANDORTE = "Kataster Belastete Standorte" KATASTER_BAEUME = "Kataster Bäume" ZONENPLAN = "Zonenplan" PGB = "Planungs- und Baugesetz (PGB)" BZO = "Bau- und Zonenordnung (BZO)" PARKPLATZVERORDNUNG = "Parkplatzverordnung" EIGENTUEMER_AUSKUNFT = "Eigentümerauskunft" GRUNDBUCHAUSZUG = "Grundbuchauszug" class GeoTagTyp(enum.Enum): REFERENZPUNKT_KAT1 = "Referenzpunkt Kat. 1" REFERENZPUNKT_KAT2 = "Referenzpunkt Kat. 2" REFERENZPUNKT_KAT3 = "Referenzpunkt Kat. 3" GEOMETER_AUFNAHME = "Geometeraufnahme" class JaNein(enum.Enum): LEER = "" JA = "Ja" NEIN = "Nein" # ============================================================================ # JUNCTION TABLES (Many-to-Many Beziehungen) # ============================================================================ # Projekt <-> Dokument (Bauherrschaft) projekt_dokumente_bauherrschaft = Table( 'projekt_dokumente_bauherrschaft', Base.metadata, Column('projekt_id', UUID(as_uuid=True), ForeignKey('projekt.id')), Column('dokument_id', UUID(as_uuid=True), ForeignKey('dokument.id')) ) # Projekt <-> Dokument (Planung) projekt_dokumente_planung = Table( 'projekt_dokumente_planung', Base.metadata, Column('projekt_id', UUID(as_uuid=True), ForeignKey('projekt.id')), Column('dokument_id', UUID(as_uuid=True), ForeignKey('dokument.id')) ) # Projekt <-> Parzelle projekt_parzelle = Table( 'projekt_parzelle', Base.metadata, Column('projekt_id', UUID(as_uuid=True), ForeignKey('projekt.id')), Column('parzelle_id', UUID(as_uuid=True), ForeignKey('parzelle.id')) ) # Parzelle <-> Parzelle (Nachbarn) parzelle_nachbar = Table( 'parzelle_nachbar', Base.metadata, Column('parzelle_id', UUID(as_uuid=True), ForeignKey('parzelle.id')), Column('nachbar_id', UUID(as_uuid=True), ForeignKey('parzelle.id')) ) # Parzelle <-> Dokument parzelle_dokument = Table( 'parzelle_dokument', Base.metadata, Column('parzelle_id', UUID(as_uuid=True), ForeignKey('parzelle.id')), Column('dokument_id', UUID(as_uuid=True), ForeignKey('dokument.id')) ) # Dokument <-> Tag (Many-to-Many, da Tags wiederverwendbar) dokument_tag = Table( 'dokument_tag', Base.metadata, Column('dokument_id', UUID(as_uuid=True), ForeignKey('dokument.id')), Column('tag', ENUM(TagTyp, name='tag_typ')) ) # ============================================================================ # MODELS # ============================================================================ class Projekt(Base): __tablename__ = 'projekt' id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) label = Column(String, nullable=False) status_prozess = Column(ARRAY(ENUM(StatusProzess, name='status_prozess'))) # Relationships perimeter = relationship( 'Parzelle', secondary=projekt_parzelle, back_populates='projekte' ) dokumente_bauherrschaft = relationship( 'Dokument', secondary=projekt_dokumente_bauherrschaft ) dokumente_planung = relationship( 'Dokument', secondary=projekt_dokumente_planung ) geo_baulinie = relationship('GeoPunkt', back_populates='projekt_baulinie') kontext_informationen = relationship( 'Kontext', foreign_keys='Kontext.projekt_id', back_populates='projekt' ) class Parzelle(Base): __tablename__ = 'parzelle' id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) label = Column(String, nullable=False) parzellen_nummern = Column(ARRAY(String)) eigentuemerschaaft = Column(String) strasse_nr = Column(String) # Geografischer Kontext land_id = Column(UUID(as_uuid=True), ForeignKey('land.id')) kanton_id = Column(UUID(as_uuid=True), ForeignKey('kanton.id')) gemeinde_id = Column(UUID(as_uuid=True), ForeignKey('gemeinde.id')) # Geometrie (PostGIS) geo_umfang = Column(Geometry('POLYGON', srid=2056)) # LV95 Koordinatensystem # Bauliche Parameter bauzone = Column(String) az = Column(Float) # Ausnützungsziffer bz = Column(Float) # Bebauungsziffer vollgeschoss_zahl = Column(Integer) anrechenbar_dachgeschoss = Column(Float) anrechenbar_untergeschoss = Column(Float) gebaeudehoehe_max = Column(Float) # Regelungen regeln_grenzabstand = Column(Text) regeln_mehrlaengenzuschlag = Column(Text) regeln_mehrhoehenzuschlag = Column(Text) # Schutzzonen hochwasserschutzzone = Column(String) laermschutzzone = Column(String) grundwasserschutzzone = Column(String) # Eigenschaften parzelle_bebaut = Column(ENUM(JaNein, name='ja_nein')) parzelle_erschlossen = Column(ENUM(JaNein, name='ja_nein')) hanglage = Column(ENUM(JaNein, name='ja_nein')) # Relationships projekte = relationship( 'Projekt', secondary=projekt_parzelle, back_populates='perimeter' ) nachbar_eigentuemer = relationship( 'Parzelle', secondary=parzelle_nachbar, primaryjoin=id == parzelle_nachbar.c.parzelle_id, secondaryjoin=id == parzelle_nachbar.c.nachbar_id ) kontext_land = relationship('Land', back_populates='parzellen') kontext_kanton = relationship('Kanton', back_populates='parzellen') kontext_gemeinde = relationship('Gemeinde', back_populates='parzellen') spezifische_dokumente = relationship( 'Dokument', secondary=parzelle_dokument ) kontext_informationen = relationship( 'Kontext', foreign_keys='Kontext.parzelle_id', back_populates='parzelle' ) class Dokument(Base): __tablename__ = 'dokument' id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) label = Column(String, nullable=False) versionsbezeichnung = Column(String) typ = Column(ENUM(DokumentTyp, name='dokument_typ'), nullable=False) format = Column(String) dokument_referenz = Column(String, nullable=False) # Pfad oder URL # Tags als Array (einfache Variante) # Alternative: Many-to-Many über Junction Table tags = Column(ARRAY(ENUM(TagTyp, name='tag_typ'))) class GeoPunkt(Base): __tablename__ = 'geo_punkt' id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) # Koordinaten (einzeln gespeichert für Flexibilität) x = Column(Float, nullable=False) y = Column(Float, nullable=False) z = Column(Float) # Optional # Alternative: PostGIS Point # koordinaten = Column(Geometry('POINTZ', srid=2056)) # Kategorisierung referenzen = Column(ARRAY(ENUM(GeoTagTyp, name='geo_tag_typ'))) # Foreign Keys (je nach Verwendung) projekt_id = Column(UUID(as_uuid=True), ForeignKey('projekt.id')) # Relationships projekt_baulinie = relationship('Projekt', back_populates='geo_baulinie') class Kontext(Base): __tablename__ = 'kontext' id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) thema = Column(String, nullable=False) inhalt = Column(Text, nullable=False) # Polymorphe Beziehung (kann zu verschiedenen Entitäten gehören) projekt_id = Column(UUID(as_uuid=True), ForeignKey('projekt.id')) parzelle_id = Column(UUID(as_uuid=True), ForeignKey('parzelle.id')) land_id = Column(UUID(as_uuid=True), ForeignKey('land.id')) kanton_id = Column(UUID(as_uuid=True), ForeignKey('kanton.id')) gemeinde_id = Column(UUID(as_uuid=True), ForeignKey('gemeinde.id')) # Relationships projekt = relationship('Projekt', back_populates='kontext_informationen') parzelle = relationship('Parzelle', back_populates='kontext_informationen') land = relationship('Land', back_populates='kontext_informationen') kanton = relationship('Kanton', back_populates='kontext_informationen') gemeinde = relationship('Gemeinde', back_populates='kontext_informationen') class Gemeinde(Base): __tablename__ = 'gemeinde' id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) label = Column(String, nullable=False) plz = Column(String) # BZO Dokumente bzo_aktuell_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id')) bzo_revision_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id')) # Relationships parzellen = relationship('Parzelle', back_populates='kontext_gemeinde') kontext_informationen = relationship( 'Kontext', foreign_keys='Kontext.gemeinde_id', back_populates='gemeinde' ) bzo_aktuell = relationship('Dokument', foreign_keys=[bzo_aktuell_id]) bzo_revision = relationship('Dokument', foreign_keys=[bzo_revision_id]) class Kanton(Base): __tablename__ = 'kanton' id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) label = Column(String, nullable=False) # Regelwerke baureglement_aktuell_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id')) baureglement_revision_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id')) bauverordnung_aktuell_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id')) bauverordnung_revision_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id')) # Relationships parzellen = relationship('Parzelle', back_populates='kontext_kanton') kontext_informationen = relationship( 'Kontext', foreign_keys='Kontext.kanton_id', back_populates='kanton' ) baureglement_aktuell = relationship('Dokument', foreign_keys=[baureglement_aktuell_id]) baureglement_revision = relationship('Dokument', foreign_keys=[baureglement_revision_id]) bauverordnung_aktuell = relationship('Dokument', foreign_keys=[bauverordnung_aktuell_id]) bauverordnung_revision = relationship('Dokument', foreign_keys=[bauverordnung_revision_id]) class Land(Base): __tablename__ = 'land' id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) label = Column(String, nullable=False) # Relationships parzellen = relationship('Parzelle', back_populates='kontext_land') kontext_informationen = relationship( 'Kontext', foreign_keys='Kontext.land_id', back_populates='land' ) # ============================================================================ # DATENBANK-SETUP # ============================================================================ def create_database_engine(): """ Erstellt die Database Engine mit PostGIS Support """ from sqlalchemy import create_engine # Beispiel Connection String DATABASE_URL = "postgresql://user:password@localhost:5432/architektur_app" engine = create_engine( DATABASE_URL, echo=True # SQL Logging für Development ) return engine def init_database(engine): """ Initialisiert die Datenbank und erstellt alle Tabellen """ # PostGIS Extension aktivieren (manuell oder via SQL) with engine.connect() as conn: conn.execute("CREATE EXTENSION IF NOT EXISTS postgis;") conn.commit() # Tabellen erstellen Base.metadata.create_all(engine) # ============================================================================ # BEISPIEL USAGE # ============================================================================ if __name__ == "__main__": from sqlalchemy.orm import sessionmaker # Engine erstellen engine = create_database_engine() # Datenbank initialisieren init_database(engine) # Session erstellen Session = sessionmaker(bind=engine) session = Session() # Beispiel: Schweiz erstellen schweiz = Land( label="Schweiz" ) session.add(schweiz) # Beispiel: Kanton Zürich erstellen kanton_zh = Kanton( label="Zürich" ) session.add(kanton_zh) # Beispiel: Gemeinde Zürich erstellen gemeinde_zh = Gemeinde( label="Zürich", plz="8000" ) session.add(gemeinde_zh) # Beispiel: Parzelle erstellen parzelle = Parzelle( label="Bahnhofstrasse 1", parzellen_nummern=["1234"], eigentuemerschaaft="Mustermann AG", strasse_nr="Bahnhofstrasse 1", land_id=schweiz.id, kanton_id=kanton_zh.id, gemeinde_id=gemeinde_zh.id, bauzone="W3", az=1.5, bz=0.4, vollgeschoss_zahl=4, parzelle_bebaut=JaNein.NEIN, parzelle_erschlossen=JaNein.JA ) session.add(parzelle) # Beispiel: Projekt erstellen projekt = Projekt( label="Neubau Wohnhaus", status_prozess=[StatusProzess.EINGANG, StatusProzess.ANALYSE] ) projekt.perimeter.append(parzelle) session.add(projekt) # Beispiel: Kontext hinzufügen kontext = Kontext( thema="Dienstbarkeiten", inhalt="Wegrecht zugunsten Parzelle 1235 entlang Ostgrenze", parzelle_id=parzelle.id ) session.add(kontext) # Speichern session.commit() print("Datenbank erfolgreich initialisiert und Beispieldaten eingefügt!")