wiki/mandates/pek/datenmodell/models.py
ValueOn AG 15f0e51bd0 pek
2025-10-24 21:42:47 +02:00

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!")