457 lines
14 KiB
Python
457 lines
14 KiB
Python
"""
|
|
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!")
|