Ga naar inhoud

raplan_rws

Bundelingstool voor onderhoudsmaatregelen op basis van RaPlan voor Rijkswaterstaat.

gelijkenis

Aanzet tot een gelijkenisanalyse voor POEMA maatregelen.

gelijkenisanalyse

gelijkenisanalyse(
    project: Project | RwsProject,
    horizon: Horizon | None = None,
) -> SimilarityAnalysis

Bouw een gelijkenisanalyse-object van een RaPlan of RWS project.

Parameters:

Name Type Description Default
project Project | RwsProject

RaPlan Project of RwsProject.

required
horizon Horizon | None

Horizon om op te filteren (optioneel). Filtert maatregelen uit de procedures als hun tijd buiten de horizon-grenzen valt.

None

Returns:

Type Description
SimilarityAnalysis

RaGraph SimilarityAnalysis object voor de gelijkenisanalyse.

Source code in src/raplan_rws/gelijkenis.py
def gelijkenisanalyse(
    project: Project | RwsProject, horizon: Horizon | None = None
) -> SimilarityAnalysis:
    """Bouw een gelijkenisanalyse-object van een RaPlan of RWS project.

    Arguments:
        project: RaPlan `Project` of `RwsProject`.
        horizon: Horizon om op te filteren (optioneel).
            Filtert maatregelen uit de procedures als hun tijd buiten de horizon-grenzen valt.

    Returns:
        RaGraph `SimilarityAnalysis` object voor de gelijkenisanalyse.
    """
    proj = project.project if isinstance(project, RwsProject) else project
    g = Graph()
    procedures: list[Node] = []
    tasks: list[Node] = []
    edges: list[Edge] = []

    added_tasks = set()

    for n in proj.get_procedures():
        p = Node(name=n.name, kind="procedure")
        g.add_node(p)
        procedures.append(p)

        for m in n.maintenance:
            if horizon is not None:
                time: int | float = m.time + proj.horizon.start
                if not horizon.is_satisfied_by(time):
                    logger.debug(
                        f"maintenance time {time} ({m.time}) not satisfying horizon {horizon}"
                    )
                    continue
            if m.task.name not in added_tasks:
                t = Node(name=m.task.name, kind="task")
                g.add_node(t)
                tasks.append(t)
                added_tasks.add(m.task.name)

            edges.append(Edge(source=p, target=g[m.task.name], kind="mapping"))  # type: ignore
            edges.append(Edge(target=p, source=g[m.task.name], kind="mapping"))  # type: ignore

    return SimilarityAnalysis(
        rows=procedures,
        cols=tasks,
        edges=edges,
    )

model

Modelinstellingen voor het RWS project die worden vertaald naar instellingen voor de Mixed-Integer Linear Programming (MILP) solver in RaPlan.

IngreepInstellingen

Instellingen voor het schuiven van ingrepen in de tijd. Besparingsbijdragen zijn <0, kosten >0.

Attributes:

Name Type Description
jaar_zelfde_complex int | float

Doelfunctiebijdrage voor het uitvoeren van gebundelde werkzaamheden aan hetzelfde complex. Factor maal de opgetelde kosten van de ingreep. Staat standaard op 1.0 (als straf) zodat beschikbaarheid wordt gemaximaliseerd.

verbied_zelfde_complex bool

Of ingrepen aan hetzelfde complex moeten worden verboden. Geeft een grote kans dat het model niet kan worden opgelost. Staat standaard op False.

specifieke_combis dict[frozenset[UUID], int | float]

Set van frozensets van paren van UUIDs van ingrepen die een specifieke score moeten krijgen als ze samenvallen in hetzelfde tijdslot.

uitgesloten_combis set[frozenset[UUID]]

Set van frozensets van paren UUIDs van ingrepen die niet samen mogen voorkomen.

dynamische_combi

dynamische_combi(
    ingrepen: dict[UUID, Procedure],
    maintenance_naar_geplande_maatregel: dict[
        UUID, GeplandeMaatregel
    ],
) -> Callable[[frozenset[UUID]], int | float | None]

Maak een dynamische combi-functie voor het belonen/straffen van samenvallende ingrepen op basis van deze instellingen en het project.

Source code in src/raplan_rws/model.py
def dynamische_combi(
    self,
    ingrepen: dict[UUID, raplan.Procedure],
    maintenance_naar_geplande_maatregel: dict[UUID, GeplandeMaatregel],
) -> Callable[[frozenset[UUID]], int | float | None]:
    """Maak een dynamische combi-functie voor het belonen/straffen van samenvallende ingrepen
    op basis van deze instellingen en het project.
    """

    def ingreep_combi(uuids: frozenset[UUID]) -> int | float | None:
        """Dynamische combi-functie voor het belonen/afstraffen van samenvallende ingrepen."""

        if uuids in self.specifieke_combis:
            return self.specifieke_combis[uuids]

        score: int | float | None = None
        volgorde = sorted(uuids)
        zelfde_complex = self.jaar_zelfde_complex
        for i, a in enumerate(volgorde):
            iga: raplan.Procedure = ingrepen[a]
            ma: raplan.Maintenance = iga.maintenance[0]
            gma: GeplandeMaatregel = maintenance_naar_geplande_maatregel[ma.uuid]
            boa: BeheerObject = gma.beheerobject
            for b in volgorde[i + 1 :]:
                igb: raplan.Procedure = ingrepen[b]
                mb: raplan.Maintenance = igb.maintenance[0]
                gmb: GeplandeMaatregel = maintenance_naar_geplande_maatregel[mb.uuid]
                bob: BeheerObject = gmb.beheerobject

                # TODO: implement netwerkanalyse hier.

                if (
                    zelfde_complex is not None
                    and boa.complex_code
                    and bob.complex_code
                    and boa.complex_code == bob.complex_code
                ):
                    score = zelfde_complex if score is None else score + zelfde_complex

        return score

    return ingreep_combi

dynamische_uitsluiting

dynamische_uitsluiting(
    ingrepen: dict[UUID, Procedure],
    maintenance_naar_geplande_maatregel: dict[
        UUID, GeplandeMaatregel
    ],
) -> Callable[[frozenset[UUID]], bool]

Maak een dynamische uitsluitfunctie voor ingrepen op basis van deze instellingen en het project.

Source code in src/raplan_rws/model.py
def dynamische_uitsluiting(
    self,
    ingrepen: dict[UUID, raplan.Procedure],
    maintenance_naar_geplande_maatregel: dict[UUID, GeplandeMaatregel],
) -> Callable[[frozenset[UUID]], bool]:
    """Maak een dynamische uitsluitfunctie voor ingrepen op basis van deze instellingen en het
    project."""

    def ingreep_excl(uuids: frozenset[UUID]) -> bool:
        """Een dynamische uitsluitfunctie voor ingrepen."""

        if uuids in self.uitgesloten_combis:
            return True

        volgorde = sorted(uuids)
        verbieding = self.verbied_zelfde_complex
        for i, a in enumerate(volgorde):
            iga: raplan.Procedure = ingrepen[a]
            ma: raplan.Maintenance = iga.maintenance[0]
            gma: GeplandeMaatregel = maintenance_naar_geplande_maatregel[ma.uuid]
            boa: BeheerObject = gma.beheerobject
            for b in volgorde[i + 1 :]:
                igb: raplan.Procedure = ingrepen[b]
                mb: raplan.Maintenance = igb.maintenance[0]
                gmb: GeplandeMaatregel = maintenance_naar_geplande_maatregel[mb.uuid]
                bob: BeheerObject = gmb.beheerobject

                # TODO: implement netwerkanalyse hier.

                # Zelfde complex uitsluiten.
                if (
                    verbieding
                    and boa.complex_code
                    and bob.complex_code
                    and boa.complex_code == bob.complex_code
                ):
                    return True

        return False

    return ingreep_excl

maak_kopie

maak_kopie() -> IngreepInstellingen

Maak een kopie van deze instellingen.

Source code in src/raplan_rws/model.py
def maak_kopie(self) -> "IngreepInstellingen":
    """Maak een kopie van deze instellingen."""
    return deepcopy(self)

MaatregelSynchronisatie

Instellingen voor het bundelen van maatregelen w.b. doelfunctie-bijdragen en randvoorwaarden. Bijdragen <0 zijn beloningen, >0 zijn kosten.

Attributes:

Name Type Description
forceer_gelijk_starten bool

Stel dat maatregelen geforceerd gelijktijdig moeten beginnen en niet deels mogen overlappen in een ingreep. Standaard op False.

verbied_duplicatie bool

Geeft aan dat een taak met dezelfde omschrijving op hetzelfde object en element-bouwdeel nooit op hetzelfde moment mag worden gepland. Staat standaard op True.

jaar_samenvallen int | float

Doelfunctiebijdrage van het samenvallen van taken in de meest generiek zin. Dit getal wordt vermenigvuldigd met N-1 taken die samenvallen. Standaard op -1.

zelfde_discipline int | float

Doelfunctiebijdrage bij het samenvallen van eenzelfde discipline (ongeacht welke) gedurende ƩƩn jaar en opzichte van de som van de raming van beide taken. Standaard op -0.1 (10% korting).

discipline_combis dict[frozenset[Discipline], int | float]

Doelfunctiebijdrage voor specifieke combinaties van disciplines gedurende ƩƩn jaar tussen twee maatregelen. De beste bijdrage tussen de "zelfde discipline" en deze wordt gebruikt in het model. Standaard op -0.05 (5% korting) voor enkele combinaties.

specifieke_combis dict[frozenset[UUID], int | float]

Set van frozensets van paren van UUIDs van maatregelen die een specifieke score moeten krijgen als ze samenvallen.

uitgesloten_combis set[frozenset[UUID]]

Set van frozensets van paren UUIDs van maatregelen die niet samen mogen voorkomen.

dynamische_combi

dynamische_combi(
    maintenance_naar_geplande_maatregel: dict[
        UUID, GeplandeMaatregel
    ],
) -> Callable[[frozenset[UUID]], int | float | None]

Maak een dynamische maatregel-combinatie beloningsfunctie op basis van deze instellingen en het project.

Source code in src/raplan_rws/model.py
def dynamische_combi(
    self,
    maintenance_naar_geplande_maatregel: dict[UUID, GeplandeMaatregel],
) -> Callable[[frozenset[UUID]], int | float | None]:
    """Maak een dynamische maatregel-combinatie beloningsfunctie op basis van deze instellingen
    en het project.
    """

    def maatregel_combi(uuids: frozenset[UUID]) -> int | float | None:
        """Een dynamische maatregel-combinatie belongingsfunctie."""

        if uuids in self.specifieke_combis:
            return self.specifieke_combis[uuids]

        beste: int | float = 0
        volgorde = sorted(uuids)
        kosten_totaal: int | float = 0.0
        for i, a in enumerate(volgorde):
            gma: GeplandeMaatregel = maintenance_naar_geplande_maatregel[a]
            kosten_totaal += gma.raming_incl
            for b in volgorde[i + 1 :]:
                gmb: GeplandeMaatregel = maintenance_naar_geplande_maatregel[b]

                # neem steeds het minimum omdat negatievere penalties beter zijn!
                for dis_a in gma.disciplines:
                    for dis_b in gmb.disciplines:
                        if dis_a == dis_b:
                            beste = min(beste, self.zelfde_discipline)
                        try:
                            combi: frozenset[Discipline] = frozenset((dis_a, dis_b))
                            score: float | int = self.discipline_combis[combi]
                            beste = min(beste, score)
                        except KeyError:
                            continue

        return None if beste == 0 else beste * kosten_totaal

    return maatregel_combi

dynamische_uitsluiting

dynamische_uitsluiting(
    maintenance_naar_geplande_maatregel: dict[
        UUID, GeplandeMaatregel
    ],
) -> Callable[[frozenset[UUID]], bool]

Maak een dynamische uitsluitfunctie op basis van deze instellingen en het project.

Source code in src/raplan_rws/model.py
def dynamische_uitsluiting(
    self,
    maintenance_naar_geplande_maatregel: dict[UUID, GeplandeMaatregel],
) -> Callable[[frozenset[UUID]], bool]:
    """Maak een dynamische uitsluitfunctie op basis van deze instellingen en het project."""

    def maatregel_excl(uuids: frozenset[UUID]) -> bool:
        """Een dynamische uitsluitfunctie voor combinaties van maatregelen."""

        if uuids in self.uitgesloten_combis:
            return True

        if not self.verbied_duplicatie:
            return False

        taken: set[tuple[BeheerObject, ElementBouwdeel, str]] = set()
        for uuid in uuids:
            gm: GeplandeMaatregel = maintenance_naar_geplande_maatregel[uuid]
            taak = (gm.beheerobject, gm.element_bouwdeel, gm.maatregel)

            if taak in taken:
                return True
            else:
                taken.add(taak)

        return False

    return maatregel_excl

maak_kopie

maak_kopie() -> MaatregelSynchronisatie

Maak een kopie van deze instellingen.

Source code in src/raplan_rws/model.py
def maak_kopie(self) -> "MaatregelSynchronisatie":
    """Maak een kopie van deze instellingen."""
    return deepcopy(self)

MaatregelVerschuiving

Doelfunctie instellingen voor het verschuiven van een maatregel in de tijd.

Attributes:

Name Type Description
gratis_naar_voren bool

Naar voren halen wordt gratis. Standaard op True.

vaste_factor int | float | tuple[int | float, int | float]

Vaste verschuivingskosten of het nu een jaar naar voor of achter is. Standaard op 0 (inactief).

kosten_per_jaar_factor int | float | tuple[int | float, int | float]

Hoe zwaar de maatregelkosten per jaar van de herhalingsperiode moet worden meegenomen. Standaard op 0 (inactief).

frequentie_factor int | float | tuple[int | float, int | float]

Hoe zwaar puur de frequentie (zonder kosten) moet worden meegenomen. Standaard op 0 (inactief).

specifiek dict[UUID, int | float | tuple[int | float, int | float]]

Dictionary van maintenance UUID naar specifieke score per tijdseenheid naar voren en naar achter (als tuple) of uniform (ƩƩn getal).

Note

De niet-specifieke kostenfactoren worden vermenigvuldigd met de absolute waarde van de delta. Het is mogelijk om een verschil aan te brengen door een tuple op te geven in de vorm van de wegingen (naar_voor, naar_achter) voor de verschillende factoren.

dynamische_functie

dynamische_functie(
    maintenance_naar_geplande_maatregel: dict[
        UUID, GeplandeMaatregel
    ],
) -> Callable[[UUID, float], int | float | None]

Maak een dynamische verschuivingsfunctie op basis van de instellingen en het project.

Source code in src/raplan_rws/model.py
def dynamische_functie(
    self,
    maintenance_naar_geplande_maatregel: dict[UUID, GeplandeMaatregel],
) -> Callable[[UUID, float], int | float | None]:
    """Maak een dynamische verschuivingsfunctie op basis van de instellingen en het project."""

    def verschuiving(maintenance_id: UUID, delta: float) -> int | float | None:
        naar_voren = delta < 0
        naar_achter = int(not naar_voren)  # 1 voor naar achter, anders 0

        if maintenance_id in self.specifiek:
            spec = self.specifiek[maintenance_id]
            if isinstance(spec, (int, float)):
                return spec * abs(delta)
            else:
                return abs(delta) * spec[naar_achter]

        if naar_voren and self.gratis_naar_voren:
            return 0

        afstand = abs(delta)

        if isinstance(self.vaste_factor, (int, float)):
            kosten = afstand * self.vaste_factor
        else:
            factor = self.vaste_factor[naar_achter]
            kosten = afstand * factor

        if self.kosten_per_jaar_factor != 0:
            if isinstance(self.kosten_per_jaar_factor, (int, float)):
                factor = self.kosten_per_jaar_factor
            else:
                factor = self.kosten_per_jaar_factor[naar_achter]
            maatregel = maintenance_naar_geplande_maatregel[maintenance_id]
            kosten += afstand * factor * maatregel.frequentie() * maatregel.raming_incl

        if self.frequentie_factor != 0:
            if isinstance(self.frequentie_factor, (int, float)):
                factor = self.frequentie_factor
            else:
                factor = self.frequentie_factor[naar_achter]
            kosten += afstand * factor * maatregel.frequentie()

        return kosten

    return verschuiving

maak_kopie

maak_kopie() -> MaatregelVerschuiving

Maak een kopie van deze instellingen.

Source code in src/raplan_rws/model.py
def maak_kopie(self) -> "MaatregelVerschuiving":
    """Maak een kopie van deze instellingen."""
    return deepcopy(self)

ModelInstellingen

Instellingen voor het bundelen van maatregelen.

Attributes:

Name Type Description
maatregel_verschuiving MaatregelVerschuiving

Instellingen voor verschuivingskosten van een maatregel.

maatregel_synchronisatie MaatregelSynchronisatie

Instellingen voor de onderlinge afstemming van maatregelen.

ingreep_instellingen IngreepInstellingen

Instellingen voor het onderlinge afstemmen van ingrepen.

tijdsduur_naar_jaar int | float

Omrekenfactor van de tijdsduur van maatregelen naar hele jaren zodat tijdsduur*factor == jaren. Staat standaard op 1.

segmenten_per_jaar int

Aantal segmenten om ieder jaar in op te delen. Voor een planning op jaarniveau mag deze op 1 blijven staan. Staat standaard op 1

score_precisie int | float

Nauwkeurigheid waarop de doelfunctie moet worden berekend. Staat standaard op 1.

model_output bool

Of de modeloutput moet worden weergegeven. Ziet er technisch uit, maar geeft inzage in de voortgang. Staat standaard op False.

maak_kopie

maak_kopie() -> ModelInstellingen

Maak een kopie van deze instellingen.

Source code in src/raplan_rws/model.py
def maak_kopie(self) -> "ModelInstellingen":
    """Maak een kopie van deze instellingen."""
    return deepcopy(self)

netwerk

Functionaliteit om de bereikbaarheid van het vaarwegennetwerk te controleren gegeven een stel beheerobjecten in onderhoud.

Omvaarroute

Data voor een omvaarroute.

Attributes:

Name Type Description
source str

Beginpunt van de route (beheerobject/stop).

target str

Eindpunt van de route (beheerobject/stop).

route list[str]

Pad van de omvaarroute (stops en beheerobjecten).

afstand float

Opgetelde hemelsbrede afstand tussen alle beheerobjecten en stops.

originele_route list[str]

Originele route.

originele_afstand float

Originele afstand tussen alle stops in hemelsbrede kilometers.

afstand_delta float

Verschil in afstand en originele afstand.

maak_kopie

maak_kopie() -> Omvaarroute

Maak een kopie van dit object.

Source code in src/raplan_rws/netwerk.py
def maak_kopie(self) -> "Omvaarroute":
    """Maak een kopie van dit object."""
    return deepcopy(self)

Onbereikbaar

Onbereikbare route.

Attributes:

Name Type Description
source str

Beginpunt van de route (beheerobject/stop).

target str

Eindpunt van de route (beheerobject/stop).

originele_route list[str]

Pad van de originele route (stops en beheerobjecten).

maak_kopie

maak_kopie() -> Onbereikbaar

Maak een kopie van dit object.

Source code in src/raplan_rws/netwerk.py
def maak_kopie(self) -> "Onbereikbaar":
    """Maak een kopie van dit object."""
    return deepcopy(self)

cemt_view

cemt_view(
    graph: Graph,
    subject: str | None = None,
    in_onderhoud: set[str] | None = None,
    cemt: int = 0,
) -> GraphView

Filter de graaf op basis van een CEMT klasse.

Parameters:

Name Type Description Default
graph Graph

Graaf met brondata.

required
subject str | None

Beheerobject dat wordt bekeken (en moet blijven bestaan in output).

None
in_onderhoud set[str] | None

De set van sluizen in onderhoud die moet worden weggelaten. Als 'subject' hierin zit, wordt deze genegeerd.

None
cemt int

De minimale CEMT klasse voor nodes en edges om in de GraphView te worden meegenomen.

0

Returns:

Type Description
GraphView

GraphView object dat voldoet aan de filter.

Source code in src/raplan_rws/netwerk.py
def cemt_view(
    graph: Graph, subject: str | None = None, in_onderhoud: set[str] | None = None, cemt: int = 0
) -> GraphView:
    """Filter de graaf op basis van een CEMT klasse.

    Arguments:
        graph: Graaf met brondata.
        subject: Beheerobject dat wordt bekeken (en moet blijven bestaan in output).
        in_onderhoud: De set van sluizen in onderhoud die moet worden weggelaten.
            Als 'subject' hierin zit, wordt deze genegeerd.
        cemt: De minimale CEMT klasse voor nodes en edges om in de `GraphView` te worden meegenomen.

    Returns:
        `GraphView` object dat voldoet aan de filter.
    """

    return GraphView(
        graph,
        view_func=_cemt_view_filter_functie,
        view_kwargs=dict(
            subject=subject,
            in_onderhoud=in_onderhoud,
            cemt=cemt,
        ),
    )

check_objecten

check_objecten(g: Graph, disk_codes: Iterable[str])

Sanity check of alle opgegeven objecten in de graaf zitten.

Source code in src/raplan_rws/netwerk.py
def check_objecten(g: Graph, disk_codes: Iterable[str]):
    """Sanity check of alle opgegeven objecten in de graaf zitten."""

    for disk in disk_codes:
        if g[disk]:
            continue

        else:
            raise KeyError(f"Object '{disk}' zit niet in de graaf.")

    logger.info("Objecten OK!")

netwerk_inladen

netwerk_inladen(
    bestand: str, edge_klasse: str = "verbinding klasse"
) -> Graph

Laad het vaarwegennetwerk in.

Source code in src/raplan_rws/netwerk.py
def netwerk_inladen(bestand: str, edge_klasse: str = "verbinding klasse") -> Graph:
    """Laad het vaarwegennetwerk in."""
    graph = Graph()
    with open(bestand, "r") as f:
        reader: Reader = csv.reader(f)
        header: list[str] = next(reader)
        if header is None:
            return graph
        columns: dict[str, int] = {name.lower(): index for index, name in enumerate(header)}

        try:
            source_id: int = columns["source-id"]
            source_name: int = columns["source"]
            target_id: int = columns["target-id"]
            target_name: int = columns["target"]
            source_class: int = columns["source klasse"]
            target_class: int = columns["target klasse"]
            distance: int = columns["afstand"]
            edge_class: int = columns[edge_klasse.lower()]
        except KeyError as e:
            raise KeyError(f"Kolom niet gevonden in CSV: {e}")

        for row in reader:
            _add_edges_and_nodes(
                graph,
                row,
                source_id,
                source_name,
                target_id,
                target_name,
                source_class,
                target_class,
                edge_class,
                distance,
            )

    return graph

relevante_cemt_klasse

relevante_cemt_klasse(g: Graph, disk: str) -> int

Vind de relevante CEMT klasse voor een beheerobject.

Source code in src/raplan_rws/netwerk.py
def relevante_cemt_klasse(g: Graph, disk: str) -> int:
    """Vind de relevante CEMT klasse voor een beheerobject."""

    def cemts() -> Generator[int, Never, None]:
        for e in g.edges_to(disk):
            yield int(e.weights["CEMT"])
        for e in g.edges_from(disk):
            yield int(e.weights["CEMT"])

    return max(cemts(), default=0)

route_vanaf

route_vanaf(
    source: str, target: str, prev_map: dict[str, str]
) -> list[str]

Reconstrueer het pad vanaf een doel terug naar de origine op basis van een vorige mapping.

Source code in src/raplan_rws/netwerk.py
def route_vanaf(source: str, target: str, prev_map: dict[str, str]) -> list[str]:
    """Reconstrueer het pad vanaf een doel terug naar de origine op basis van een vorige mapping."""

    route = [target]

    current = target

    while current in prev_map:
        prev = prev_map[current]

        route.append(prev)

        current = prev

    if route[-1] != source:
        route.append(source)

    return list(reversed(route))

zoek_alle_beslis_nodes

zoek_alle_beslis_nodes(
    g: Graph, in_onderhoud: set[str], cemt: int
) -> tuple[dict[str, set[str]], dict[str, set[str]]]

Zoek alle beslis-nodes gegeven een set objecten in onderhoud en een CEMT klasse waarop moet worden gefilterd.

Parameters:

Name Type Description Default
g Graph

Graaf met brondata.

required
in_onderhoud set[str]

Set van objecten in onderhoud.

required
cemt int

CEMT klasse om het netwerk op te filteren.

required

Returns:

Type Description
dict[str, set[str]]

Twee dictionaries met elk per sluis in onderhoud de beslis-sources respectievelijk

dict[str, set[str]]

beslis-target nodes.

Source code in src/raplan_rws/netwerk.py
def zoek_alle_beslis_nodes(
    g: Graph, in_onderhoud: set[str], cemt: int
) -> tuple[dict[str, set[str]], dict[str, set[str]]]:
    """Zoek alle beslis-nodes gegeven een set objecten in onderhoud en een CEMT klasse waarop moet
    worden gefilterd.


    Arguments:
        g: Graaf met brondata.
        in_onderhoud: Set van objecten in onderhoud.
        cemt: CEMT klasse om het netwerk op te filteren.

    Returns:
        Twee dictionaries met elk per sluis in onderhoud de beslis-sources respectievelijk
        beslis-target nodes.
    """

    beslis_sources: dict[str, set[str]] = dict()
    beslis_targets: dict[str, set[str]] = dict()
    onderhoud_g: GraphView = cemt_view(g, subject=None, in_onderhoud=in_onderhoud, cemt=cemt)

    for subject in in_onderhoud:
        bezochte_sources = set()
        bezochte_targets = set()
        sub_view = cemt_view(g, subject=subject, in_onderhoud=in_onderhoud, cemt=cemt)

        # the nodes directly attached should have at least 3 edges to be a decision node
        nieuw_bezocht = _zoek_beslis_nodes(
            g=sub_view,
            min_edges=3,
            subject=subject,
            kandidaat=subject,
            beslis_sources=beslis_sources,
            beslis_targets=beslis_targets,
            bezochte_sources=bezochte_sources,
            bezochte_targets=bezochte_targets,
        )

        # the nodes not directly attached to the renovated lock should have at least 2 edges to be
        # a decision node
        # repeat at increased depth if either source or target is empty or the single source and
        # target are the same.
        while (len(bezochte_sources) == 0 and len(bezochte_targets) == 0) or (
            len(bezochte_sources) == 1
            and len(bezochte_targets) == 1
            and bezochte_sources == bezochte_targets
        ):
            volgende: set[str] = set()
            for nieuw_subject in nieuw_bezocht:
                nieuw = _zoek_beslis_nodes(
                    onderhoud_g,
                    min_edges=2,
                    subject=subject,
                    kandidaat=nieuw_subject,
                    beslis_sources=beslis_sources,
                    beslis_targets=beslis_targets,
                    bezochte_sources=bezochte_sources,
                    bezochte_targets=bezochte_targets,
                )
                volgende.update(nieuw)
            nieuw_bezocht = volgende
            volgende.clear()

    return beslis_sources, beslis_targets

zoek_hoogste_cemt_klasse

zoek_hoogste_cemt_klasse(
    g: Graph, disk_codes: set[str]
) -> tuple[int, set[str]]

Zoek de hoogste relevante CEMT klasse en geef alleen de lijst met objecten terug die dat ook heeft.

Source code in src/raplan_rws/netwerk.py
def zoek_hoogste_cemt_klasse(g: Graph, disk_codes: set[str]) -> tuple[int, set[str]]:
    """Zoek de hoogste relevante CEMT klasse en geef alleen de lijst met objecten terug die dat ook
    heeft.
    """
    cemts: dict[str, int] = {disk: relevante_cemt_klasse(g, disk) for disk in disk_codes}
    max_cemt: int = max(cemts.values())

    overgebleven: set[str] = set()
    for disk, cemt in cemts.items():
        if cemt < max_cemt:
            logger.info(
                f"'{disk}': CEMT klasse te laag ({cemt} < {max_cemt}) voor analyse. "
                "Er zijn objecten met een hogere CEMT klasse opgegeven, waaraan prioriteit gegeven "
                "wordt."
            )
            continue
        overgebleven.add(disk)

    return max_cemt, overgebleven

ohs

OHS sheet readout module.

OhsBepData

OHS BEP sheet data.

maak_kopie

maak_kopie() -> OhsBepData

Maak een kopie van dit object.

Source code in src/raplan_rws/ohs.py
def maak_kopie(self) -> "OhsBepData":
    """Maak een kopie van dit object."""
    return deepcopy(self)

van_excel_bestand classmethod

van_excel_bestand(
    bestandspad: str | Path, instellingen: OhsInstellingen
) -> OhsBepData

Importeer de BEP data uit een OHS Excel-bestand.

Source code in src/raplan_rws/ohs.py
@classmethod
def van_excel_bestand(
    cls, bestandspad: str | Path, instellingen: OhsInstellingen
) -> "OhsBepData":
    """Importeer de BEP data uit een OHS Excel-bestand."""
    wb: openpyxl.Workbook = openpyxl.open(bestandspad)
    ws = wb["BEP"]
    assert isinstance(ws, Worksheet)
    return cls.van_excel_worksheet(ws, instellingen=instellingen)

van_excel_worksheet classmethod

van_excel_worksheet(
    ws: Worksheet, instellingen: OhsInstellingen
) -> OhsBepData

Importeer de BEP data uit het BEP sheet uit een OHS Excel-bestand.

Source code in src/raplan_rws/ohs.py
@classmethod
def van_excel_worksheet(cls, ws: Worksheet, instellingen: OhsInstellingen) -> "OhsBepData":
    """Importeer de BEP data uit het BEP sheet uit een OHS Excel-bestand."""
    data = OhsBepData()

    class_fields = ["beheer_object", "element_bouwdeel", "discipline"]

    header_row: int | None = None
    for candidate, cell in enumerate(ws["A:A"]):
        if str(cell.value).lower().replace(" ", "_") == "object":
            header_row = candidate
            break

    assert header_row is not None, (
        "OHS BEP sheet moet altijd een 'object' kolom hebben ergens in kolom A."
    )

    header: dict[str, int] = {
        str(col[header_row].value).lower().replace(" ", "_"): index
        for index, col in enumerate(ws.iter_cols())
    }
    rename = dict(
        beheer_object="object", element_bouwdeel="element_/_bouwdeel", discipline="type"
    )
    for want, old in rename.items():
        header[want] = header.pop(old)

    field_to_index: dict[str, int] = dict()
    for f in class_fields:
        if f in header:
            field_to_index[f] = header[f]

    # Excel rows are 1-based and we are one past the header row.
    for row in ws.iter_rows(min_row=header_row + 2):
        params = {f: row[index].value for f, index in field_to_index.items()}
        if not params["beheer_object"] or not params["element_bouwdeel"]:
            continue
        if (
            "kosten" in params["element_bouwdeel"].lower()
            or "kosten" in params["beheer_object"].lower()
        ):
            continue
        try:
            new = OhsBepRij.van_kolomwaarden(**params, instellingen=instellingen)
            data.rijen.append(new)
        except SerdeError as e:
            logger.warning(f"Kan '{params}' niet inladen als OHS-BEP rij: {e}")
        except ValueError as e:
            logger.warning(f"Kan '{params}' niet inladen als OHS-BEP rij: {e}")

    return data

OhsBepRij

Rij in het OHS BEP (Beheer en Exploitatieplan) sheet.

Attributes:

Name Type Description
beheer_object BeheerObject

Objectnaam. Kan DISK code bevatten maar hoeft niet altijd.

element_bouwdeel ElementBouwdeel

Element / bouwdeel benaming.

discipline set[Discipline]

Betrokken discipline(s). Staat als 'Type' in de BEP Excel-sheet.

maak_kopie

maak_kopie() -> OhsBepRij

Maak een kopie van dit object.

Source code in src/raplan_rws/ohs.py
def maak_kopie(self) -> "OhsBepRij":
    """Maak een kopie van dit object."""
    return deepcopy(self)

OhsData

OHS sheet data. Zie de van_excel_bestand functie om deze data in te laden.

Attributes:

Name Type Description
bep OhsBepData

Beheer- en ExploĆÆtatieplan sheet data.

element_functie OhsElementFunctieData

Element-Functie mapping.

maak_kopie

maak_kopie() -> OhsData

Maak een kopie van dit object.

Source code in src/raplan_rws/ohs.py
def maak_kopie(self) -> "OhsData":
    """Maak een kopie van dit object."""
    return deepcopy(self)

van_excel_bestand classmethod

van_excel_bestand(
    bestandspad: str | Path, instellingen: OhsInstellingen
) -> OhsData

Importeer de relevante data uit een OHS Excel-bestand.

Source code in src/raplan_rws/ohs.py
@classmethod
def van_excel_bestand(cls, bestandspad: str | Path, instellingen: OhsInstellingen) -> "OhsData":
    """Importeer de relevante data uit een OHS Excel-bestand."""
    wb: openpyxl.Workbook = openpyxl.open(bestandspad)
    return cls.van_excel_workbook(wb, instellingen=instellingen)

van_excel_workbook classmethod

van_excel_workbook(
    wb: Workbook, instellingen: OhsInstellingen
) -> OhsData

Importeer de relevante data uit een ingeladen OHS Excel-workbook.

Source code in src/raplan_rws/ohs.py
@classmethod
def van_excel_workbook(cls, wb: Workbook, instellingen: OhsInstellingen) -> "OhsData":
    """Importeer de relevante data uit een ingeladen OHS Excel-workbook."""
    bep_sheet = wb["BEP"]
    assert isinstance(bep_sheet, Worksheet)
    bep = OhsBepData.van_excel_worksheet(bep_sheet, instellingen=instellingen)

    element_functie_sheet = wb["Element-Functie"]
    assert isinstance(element_functie_sheet, Worksheet)
    element_functie = OhsElementFunctieData.van_excel_worksheet(
        element_functie_sheet, instellingen=instellingen
    )

    return OhsData(bep=bep, element_functie=element_functie)

verwerk

verwerk() -> VerwerkteOhsData

Verwerk deze rauwe data tot een verwerkt data object met beheerobjecten en structuren.

Source code in src/raplan_rws/ohs.py
def verwerk(self) -> "VerwerkteOhsData":
    """Verwerk deze rauwe data tot een verwerkt data object met beheerobjecten en structuren."""
    result = VerwerkteOhsData()
    result.verwerk_data(self)
    return result

OhsElementFunctieData

maak_kopie

maak_kopie() -> OhsElementFunctieData

Maak een kopie van dit object.

Source code in src/raplan_rws/ohs.py
def maak_kopie(self) -> "OhsElementFunctieData":
    """Maak een kopie van dit object."""
    return deepcopy(self)

van_excel_bestand classmethod

van_excel_bestand(
    bestandspad: str | Path, instellingen: OhsInstellingen
) -> OhsElementFunctieData

Importeer de Element-Functie data uit een OHS Excel-bestand.

Source code in src/raplan_rws/ohs.py
@classmethod
def van_excel_bestand(
    cls, bestandspad: str | Path, instellingen: OhsInstellingen
) -> "OhsElementFunctieData":
    """Importeer de Element-Functie data uit een OHS Excel-bestand."""
    wb: openpyxl.Workbook = openpyxl.open(bestandspad)
    ws = wb["Element-Functie"]
    assert isinstance(ws, Worksheet)
    return cls.van_excel_worksheet(ws, instellingen=instellingen)

van_excel_worksheet classmethod

van_excel_worksheet(
    ws: Worksheet, instellingen: OhsInstellingen
) -> OhsElementFunctieData

Importeer de Element-Functie data uit het Element-Functie sheet uit een OHS Excel-bestand.

Source code in src/raplan_rws/ohs.py
@classmethod
def van_excel_worksheet(
    cls, ws: Worksheet, instellingen: OhsInstellingen
) -> "OhsElementFunctieData":
    """Importeer de Element-Functie data uit het Element-Functie sheet uit een OHS
    Excel-bestand.
    """
    data = OhsElementFunctieData()
    class_fields = [
        "beheer_object",
        "element_bouwdeel",
        "schutten",
        "spuien",
        "kruisen",
        "keren",
        "vgm",
        "secundair",
    ]

    header_row: int | None = None
    for candidate, cell in enumerate(ws["A:A"]):
        if str(cell.value).lower().replace(" ", "_") == "beheer_object":
            header_row = candidate
            break

    assert header_row is not None

    header: dict[str, int] = {
        str(col[header_row].value).lower().replace(" ", "_"): index
        for index, col in enumerate(ws.iter_cols())
    }
    rename = {"element_bouwdeel": "element_-_bouwdeel_naam"}
    for want, old in rename.items():
        header[want] = header.pop(old)

    # Excel rows are 1-based and we are one past the header row.
    beheer_object: str | None = None
    for row in ws.iter_rows(min_row=header_row + 2):
        params = {f: row[header[f]].value for f in class_fields}
        # Object doesn't have an entry in each row, only when it changes.
        nieuw_object = params.pop("beheer_object")
        if nieuw_object:
            candi = str(nieuw_object).strip()
            if candi:
                beheer_object = candi

        if isinstance(beheer_object, str) and "kosten" in beheer_object.lower():
            continue

        if params["element_bouwdeel"] is None or "kosten" in params["element_bouwdeel"].lower():
            continue

        for k, v in params.items():
            if k in {"schutten", "spuien", "kruisen", "keren", "vgm", "secundair"}:
                params[k] = str(v).strip().lower() == "x"
        try:
            new = OhsElementFunctieRij.van_kolomwaarden(
                **params,
                beheer_object=beheer_object,  # type: ignore
                instellingen=instellingen,
            )
            data.rijen.append(new)
        except SerdeError as e:
            logger.warning(f"Kan {params} niet inladen als Element-Functie rij: {e}")
        except ValueError as e:
            logger.warning(f"Kan {params} niet inladen als Element-Functie rij: {e}")

    return data

OhsElementFunctieRij

Rij in het OHS Element-Functie sheet.

Attributes:

Name Type Description
beheer_object BeheerObject

Naam voor het beheerobject. Kan DISK code bevatten maar hoeft niet altijd.

element_bouwdeel ElementBouwdeel

Naam voor het element/bouwdeel.

schutten ElementBouwdeel

Of het element/bouwdeel bijdraagt aan de functie schutten.

spuien ElementBouwdeel

Of het element/bouwdeel bijdraagt aan de functie spuien.

kruisen ElementBouwdeel

Of het element/bouwdeel bijdraagt aan de functie kruisen.

keren ElementBouwdeel

Of het element/bouwdeel bijdraagt aan de functie keren.

vgm ElementBouwdeel

Of het element/bouwdeel bijdraagt aan de functie VGM.

secundair ElementBouwdeel

Of het element/bouwdeel secundair bijdraagt.

code ElementBouwdeel

DISK code op basis van de beheerobjectnaam.

maak_kopie

maak_kopie() -> OhsElementFunctieRij

Maak een kopie van dit object.

Source code in src/raplan_rws/ohs.py
def maak_kopie(self) -> "OhsElementFunctieRij":
    """Maak een kopie van dit object."""
    return deepcopy(self)

OhsInstellingen

Importeer-instellingen voor de OHS Excel workbook.

Attributes:

Name Type Description
vast_beheerobject BeheerObject | None

ƉƩn vast beheerobject welke geldt voor het in te laden OHS-sheet. Gaat meestal gepaard met disk_in_objectnaam en element_in_objectkolom=True. Standaard op None.

object_naar_disk_override dict[str, str]

Een dictionary mapping van OHS waardes in de "object" kolom naar de bijbehorende DISK code.

disk_in_objectnaam bool

Of de DISK code kan worden afgeleid uit de waarden in de "object" kolom in de BEP- en Element-Functie sheets. Standaard op True.

element_in_objectkolom bool

Of het element in de "object" kolom is geplaatst in plaats van het beheerobject. Gaat meestal gepaard met een vaste_disk_code en disk_in_objectnaam=False. Standaard op False.

beheer_object

beheer_object(object: str) -> BeheerObject

Leidt de DISK code af met behulp van deze instellingen.

Source code in src/raplan_rws/ohs.py
def beheer_object(self, object: str) -> BeheerObject:
    """Leidt de DISK code af met behulp van deze instellingen."""
    if object in self.object_naar_disk_override:
        return BeheerObject(self.object_naar_disk_override[object], object)

    if self.vast_beheerobject is not None:
        return self.vast_beheerobject

    value: str | None
    if self.disk_in_objectnaam:
        value = extract_disk_code(object)

    if value is None:
        logger.warning(f"Kon geen DISK code afleiden voor '{object}'.")
        return BeheerObject(object, object)
    return BeheerObject(value, object)

element_bouwdeel

element_bouwdeel(
    object: str, element_bouwdeel: str
) -> ElementBouwdeel

Importeer de Element/Bouwdeel combinatie.

Source code in src/raplan_rws/ohs.py
def element_bouwdeel(self, object: str, element_bouwdeel: str) -> ElementBouwdeel:
    """Importeer de Element/Bouwdeel combinatie."""
    if self.element_in_object_kolom:
        return ElementBouwdeel(object, element_bouwdeel)
    return _parse_element_bouwdeel(element_bouwdeel)

maak_kopie

maak_kopie() -> OhsInstellingen

Maak een kopie van dit object.

Source code in src/raplan_rws/ohs.py
def maak_kopie(self) -> "OhsInstellingen":
    """Maak een kopie van dit object."""
    return deepcopy(self)

VerwerkteOhsData

Verwerkte OHS data.

leid_missende_disciplines_af

leid_missende_disciplines_af()

Probeer missende discipline data af te leiden uit element-bouwdeel namen.

Source code in src/raplan_rws/ohs.py
def leid_missende_disciplines_af(self):
    """Probeer missende discipline data af te leiden uit element-bouwdeel namen."""
    for disk, s in self.structuren.items():
        for eb in s.element_bouwdelen:
            if not s.disciplines[eb]:
                try:
                    s.disciplines[eb].update(Discipline.van_element_bouwdeel(eb))
                except ValueError as e:
                    logger.warning(f"OHS {disk}: {e}")

maak_kopie

maak_kopie() -> VerwerkteOhsData

Maak een kopie van dit object.

Source code in src/raplan_rws/ohs.py
def maak_kopie(self) -> "VerwerkteOhsData":
    """Maak een kopie van dit object."""
    return deepcopy(self)

migreer_complex

migreer_complex()

Combineer alle data naar een 'complex' object met afgekorte DISK code.

Source code in src/raplan_rws/ohs.py
def migreer_complex(self):
    """Combineer alle data naar een 'complex' object met afgekorte DISK code."""

    mig = VerwerkteOhsData()

    for obj in self.objecten.values():
        complex = extract_comp_code(obj.code)
        mig.objecten[complex] = BeheerObject(complex, "COMPLEX")

    for obj, obj_structuur in self.structuren.items():
        complex = extract_comp_code(obj)
        structuur = mig.structuren[complex]
        structuur.element_bouwdelen.update(obj_structuur.element_bouwdelen)
        for eb, dis in obj_structuur.disciplines.items():
            structuur.disciplines[eb].update(dis)
        for eb, imp in obj_structuur.impacts.items():
            structuur.impacts[eb] += imp

    self.objecten.update(mig.objecten)
    for code, structuur in mig.structuren.items():
        te_vullen = self.structuren[code]
        te_vullen.element_bouwdelen.update(structuur.element_bouwdelen)
        for eb, dis in structuur.disciplines.items():
            te_vullen.disciplines[eb].update(dis)
        for eb, imp in structuur.impacts.items():
            te_vullen.impacts[eb] += imp

verwerk_bep

verwerk_bep(bep: OhsBepData)

Verwerk BEP sheet data.

Source code in src/raplan_rws/ohs.py
def verwerk_bep(self, bep: OhsBepData):
    """Verwerk BEP sheet data."""
    for rij in bep:
        disk = rij.beheer_object.code
        if disk not in self.objecten:
            self.objecten[disk] = rij.beheer_object

        structuur: ObjectStructuur = self.structuren[disk]
        eb: ElementBouwdeel = rij.element_bouwdeel
        structuur.element_bouwdelen.add(eb)
        structuur.disciplines[eb].update(rij.discipline)

verwerk_data

verwerk_data(data: OhsData)

Verwerk extra OHS data in dit object.

Source code in src/raplan_rws/ohs.py
def verwerk_data(self, data: OhsData):
    """Verwerk extra OHS data in dit object."""
    self.verwerk_bep(data.bep)
    self.verwerk_element_functie(data.element_functie)
    self.leid_missende_disciplines_af()
    self.migreer_complex()
    self.zoek_binnen_complex()

verwerk_element_functie

verwerk_element_functie(
    element_functie: OhsElementFunctieData,
)

Verwerk Element-Functie sheet data.

Source code in src/raplan_rws/ohs.py
def verwerk_element_functie(self, element_functie: OhsElementFunctieData):
    """Verwerk Element-Functie sheet data."""
    for rij in element_functie:
        disk = rij.beheer_object.code
        if disk not in self.objecten:
            self.objecten[disk] = rij.beheer_object

        structuur: ObjectStructuur = self.structuren[disk]
        eb: ElementBouwdeel = rij.element_bouwdeel
        structuur.element_bouwdelen.add(eb)
        structuur.impacts[eb] += rij.impact

zoek_binnen_complex

zoek_binnen_complex()

Doe een poging om missende waarden te vullen vanuit de gedeelde complex waarden.

Source code in src/raplan_rws/ohs.py
def zoek_binnen_complex(self):
    """Doe een poging om missende waarden te vullen vanuit de gedeelde complex waarden."""
    for obj in self.objecten.values():
        if obj.naam == "COMPLEX":
            continue

        obj_structuur = self.structuren[obj.code]
        complex = extract_comp_code(obj.code)
        comp_structuur = self.structuren[complex]

        for eb in obj_structuur.element_bouwdelen:
            if not obj_structuur.disciplines[eb]:
                if eb in comp_structuur.disciplines:
                    obj_structuur.disciplines[eb].update(comp_structuur.disciplines[eb])
                    logger.warning(
                        "OHS migratie van disciplines van complexniveau naar "
                        f"{obj.code}: {eb.element} - {eb.bouwdeel}"
                    )
            if eb not in obj_structuur.impacts:
                if eb in comp_structuur.impacts:
                    obj_structuur.impacts[eb] = comp_structuur.impacts[eb]
                    logger.warning(
                        "OHS migratie van impact van complexniveau naar "
                        f"{obj.code}: {eb.element} - {eb.bouwdeel}"
                    )

plot

Plots voor visualisatie in de bundelingstool.

gantt_beheerobject

gantt_beheerobject(
    system: System, horizon: Horizon
) -> Figure

Gantt chart voor een enkel beheerobject (RaPlan System).

Laat de planning voor ingrepen zien over de verschillende element-bouwdelen.

Parameters:

Name Type Description Default
system System

RaPlan System dat het beheerobject vertegenwoordigd.

required
horizon Horizon

Planningshorizon om de figuren op af te knippen.

required

Returns:

Type Description
Figure

Plotly figuur van de Gantt chart.

Source code in src/raplan_rws/plot.py
def gantt_beheerobject(system: System, horizon: Horizon) -> go.Figure:
    """Gantt chart voor een enkel beheerobject (RaPlan `System`).

    Laat de planning voor ingrepen zien over de verschillende element-bouwdelen.

    Arguments:
        system: RaPlan `System` dat het beheerobject vertegenwoordigd.
        horizon: Planningshorizon om de figuren op af te knippen.

    Returns:
        Plotly figuur van de Gantt chart.
    """
    fig = plot.gantt_system(
        system=system, horizon=horizon, x_axis_title="kalenderjaar", y_axis_title="element-bouwdeel"
    )
    _fix_gantt_beheerobject_layout(horizon, system, fig)
    return fig

gantt_ingrepen

gantt_ingrepen(
    project: Project | RwsProject, horizon: Horizon
) -> Figure

Gantt chart voor een geheel RaPlan Project.

Laat de planning van ingrepen zien over de alle verschillende beheerobjecten in het project.

Parameters:

Name Type Description Default
project Project | RwsProject

RaPlan Project dat de nodige data bevat. Een RwsProject mag ook.

required
horizon Horizon

Planningshorizon om de figuren op af te knippen.

required

Returns:

Type Description
Figure

Plotly figuur van de Gantt chart.

Source code in src/raplan_rws/plot.py
def gantt_ingrepen(project: Project | RwsProject, horizon: Horizon) -> go.Figure:
    """Gantt chart voor een geheel RaPlan `Project`.

    Laat de planning van ingrepen zien over de alle verschillende beheerobjecten in het project.

    Arguments:
        project: RaPlan `Project` dat de nodige data bevat. Een `RwsProject` mag ook.
        horizon: Planningshorizon om de figuren op af te knippen.

    Returns:
        Plotly figuur van de Gantt chart.
    """
    proj = project.project if isinstance(project, RwsProject) else project
    fig = plot.gantt_project_procedures(
        x_axis_title="jaar",
        y_axis_title="beheerobject",
        project=proj,
        horizon=horizon,
    )
    _fix_gantt_ingrepen_layout(horizon, proj, fig)
    return fig

mdm_gelijkenisanalyse

mdm_gelijkenisanalyse(sa: SimilarityAnalysis) -> Figure

Maak een gelijkenisanalyse figuur voor de ingrepen op beheerobjecten.

Parameters:

Name Type Description Default
sa SimilarityAnalysis

Gelijkenisanalyseobject.

required

Returns:

Type Description
Figure

Plotly figuur.

Source code in src/raplan_rws/plot.py
def mdm_gelijkenisanalyse(sa: SimilarityAnalysis) -> go.Figure:
    """Maak een gelijkenisanalyse figuur voor de ingrepen op beheerobjecten.

    Arguments:
        sa: Gelijkenisanalyseobject.

    Returns:
        Plotly figuur.
    """

    def get_uuid_suffix_name(n: Node):
        """Move any prefixed UUID to the back of the string such that we can sort alphabetically
        by node name, but keep UUIDs as tie-breakers.
        """
        uuid_str = str(n.uuid)
        if n.name.startswith(uuid_str):
            return n.name[len(uuid_str) :] + " " + uuid_str
        return n.name + " " + uuid_str

    def sort_key(n: Node):
        """Custom sort key for this plot."""
        return (
            -len(n.parent.children) if n.parent else 0,  # Parent size in plot.
            get_uuid_suffix_name(n.parent) if n.parent else get_uuid_suffix_name(n),  # Parent name.
            get_uuid_suffix_name(n),  # Own name.
        )

    def get_annotations(nodes, nrows):
        """Get annotations to add."""

        n2num = {n: idx for idx, n in enumerate(nodes)}
        portfolios = set([n.parent for n in nodes if n.parent and "node" in n.parent.name])
        annotations = []
        for p in portfolios:
            cmin = min([n2num[c] for c in p.children])
            cmax = max([n2num[c] for c in p.children])
            text = f"Productieportfolio {int(p.name.split('node')[-1]) + 1}"
            annotations.append(
                dict(
                    x=cmax + 12,
                    y=nrows - cmin - 0.5 * (cmax - cmin) - 0.5,
                    text=text,
                    showarrow=False,
                    xref="x9",
                    yref="y9",
                    align="left",
                    font=dict(
                        size=36,
                    ),
                )
            )

        return annotations

    sorted_rows = sorted(sa.rows, key=sort_key)
    sorted_cols = sorted(sa.cols, key=sort_key)

    fig = ragraph.plot.mdm(
        leafs=sorted_rows + sorted_cols,
        sort=False,
        edges=sa.graph.edges,
        style=ragraph.plot.Style(
            piemap=dict(display="weights", fields=["similarity"]),
        ),
    )

    annotations = get_annotations(sorted_rows, len(sorted_rows) + len(sorted_cols))
    fig.layout.update(annotations=list(fig.layout.annotations) + annotations)

    return fig

poema

POEMA Excel data import.

PoemaData

maak_kopie

maak_kopie() -> PoemaData

Maak een kopie van dit object.

Source code in src/raplan_rws/poema.py
def maak_kopie(self) -> "PoemaData":
    """Maak een kopie van dit object."""
    return deepcopy(self)

verwerk

verwerk(
    ohs: VerwerkteOhsData | None = None,
    horizon: Horizon | None = None,
    negeer_verleden: bool = True,
    negeer_toekomst: bool = True,
    minimale_periode: int = 2,
) -> VerwerktePoemaData

Verwerk de POEMA data - eventueel aangevuld/gematcht met verwerkte OHS data.

Source code in src/raplan_rws/poema.py
def verwerk(
    self,
    ohs: VerwerkteOhsData | None = None,
    horizon: Horizon | None = None,
    negeer_verleden: bool = True,
    negeer_toekomst: bool = True,
    minimale_periode: int = 2,
) -> "VerwerktePoemaData":
    """Verwerk de POEMA data - eventueel aangevuld/gematcht met verwerkte OHS data."""
    result = VerwerktePoemaData()
    diagnostiek = result.verwerk(
        self,
        ohs=ohs,
        horizon=horizon,
        negeer_verleden=negeer_verleden,
        negeer_toekomst=negeer_toekomst,
        minimale_periode=minimale_periode,
    )
    diagnostiek.rapporteer()
    return result

PoemaDiagnostiek

Diagnostiek tijdens het inladen en matchen van POEMA data.

maak_kopie

maak_kopie() -> PoemaDiagnostiek

Maak een kopie van dit object.

Source code in src/raplan_rws/poema.py
def maak_kopie(self) -> "PoemaDiagnostiek":
    """Maak een kopie van dit object."""
    return deepcopy(self)

rapporteer

rapporteer()

Rapporteer de diagnostieke bevindingen van een POEMA/OHS import.

Source code in src/raplan_rws/poema.py
def rapporteer(self):
    """Rapporteer de diagnostieke bevindingen van een POEMA/OHS import."""
    for obj in self.missende_ohs_objecten:
        logger.warning(f"Missend OHS object {obj}")

    for obj, eb in self.missende_impact:
        if obj in self.missende_ohs_objecten:
            continue
        logger.warning(f"Mist OHS functie-impact {obj}: {eb.element} - {eb.bouwdeel}")

    for obj, eb in self.missende_disciplines:
        if obj in self.missende_ohs_objecten:
            continue
        logger.warning(f"Mist OHS disciplines {obj}: {eb.element} - {eb.bouwdeel}")

    for obj, eb, dis in self.afgeleide_disciplines:
        if obj in self.missende_ohs_objecten:
            continue
        logger.warning(
            "Disciplines afgeleid van object/bouwdeel "
            f"{obj}: {eb.element} - {eb.bouwdeel}: "
            f"{'-'.join(s for s in dis)} | "
        )

PoemaRij

Rij in het POEMA Excel export data format.

Attributes:

Name Type Description
referentie_nummer str

Specifiek referentienummer voor de maatregel.

netwerk_code str | None

Netwerkcode zoals "HWVN".

dienst_code str | None

Dienstcode zoals "RWS MN".

maatregel_type str | None

Type maatregel zoals "Variabel onderhoud".

kan_later_dan_uiterst str | None

Of de maatregel nog later mag dan het uiterst adviesjaar.

is_vernieuwing str | None

Of het vervanging betreft: j/n

bron str

Bron van de maatregel zoals "P-IHP".

maatregel_naam str

Korte omschrijving van de maatregel.

maatregel_omschrijving str

Toelichting Bouwdeel en eventuele specificaties van aantallen en/of locatie.

object_code str

DISK code van het object/de sluiskolk.

ih_onderdeel str

Instandhoudingsonderdeel.

advies_plan_jaar int | float

Advies planjaar vanuit de bron.

uiterst_advies_jaar int | float

Laatst toegestaan uitvoeringsjaar op basis van techniek.

uitvoerings_kosten int | float

Kosten in kEur.

frequentie_tekst str

Indicatie frequentie in tekst zoals "Maatregel eens per XX jaar uitvoeren."

bronbestand str

Bronbestand waar data vandaan komt zoals: "RCM_Irenesluis_2024_202407091330_PM.xlsx"

element_bouwdeel

element_bouwdeel() -> ElementBouwdeel

Leidt het ElementBouwdeel af van het maatregel_omschrijving veld.

Source code in src/raplan_rws/poema.py
def element_bouwdeel(self) -> ElementBouwdeel:
    """Leidt het `ElementBouwdeel` af van het `maatregel_omschrijving` veld."""
    match = _POEMA_BD_PAT.match(self.maatregel_omschrijving)
    if match is None:
        logger.warning(
            "POEMA Element/Bouwdeel zou altijd moeten matchen. "
            f"Gefaald op: {self.maatregel_omschrijving}"
        )
        return ElementBouwdeel(
            element=self.maatregel_omschrijving, bouwdeel=self.maatregel_omschrijving
        )
    bouwdeel, element = match.groups()
    assert isinstance(element, str), "Element zou altijd moeten matchen."
    assert isinstance(bouwdeel, str), "Bouwdeel zou altijd moeten matchen."
    return ElementBouwdeel(
        element=element.strip(),
        bouwdeel=bouwdeel.strip(),
    )

maak_kopie

maak_kopie() -> PoemaRij

Maak een kopie van dit object.

Source code in src/raplan_rws/poema.py
def maak_kopie(self) -> "PoemaRij":
    """Maak een kopie van dit object."""
    return deepcopy(self)

periode

periode() -> float | None

Lengte van de periode voor deze maatregel indien het een herhalende preventieve maatregel betreft.

Source code in src/raplan_rws/poema.py
def periode(self) -> float | None:
    """Lengte van de periode voor deze maatregel indien het een herhalende
    preventieve maatregel betreft."""
    match = _POEMA_PERIODE_PAT.match(self.frequentie_tekst)
    if match is None:
        return None

    return float(match.groups()[0])

referentie_uuid

referentie_uuid() -> UUID

Haal het UUID uit het referentienummer veld.

Source code in src/raplan_rws/poema.py
def referentie_uuid(self) -> UUID:
    """Haal het UUID uit het referentienummer veld."""
    ref = self.referentie_nummer
    idx = 1
    n_dash = 0
    while n_dash < 2:
        if ref[idx] == "-":
            n_dash += 1
        idx += 1
    return UUID(ref[idx : idx + 36])

vernieuwing

vernieuwing() -> bool

Of deze maatregel vernieuwing beschrijft. Wanneer de data niet beschikbaar is, wordt aangenomen van wel.

Source code in src/raplan_rws/poema.py
def vernieuwing(self) -> bool:
    """Of deze maatregel vernieuwing beschrijft. Wanneer de data niet beschikbaar is, wordt
    aangenomen van wel."""
    return is_truey(self.is_vernieuwing, default=True)

VerwerktePoemaData

Verwerkte POEMA data.

Attributes:

Name Type Description
planning MaatregelPlanning

Maatregelplanning op basis van POEMA rijen.

objecten dict[str, BeheerObject]

Dictionary van DISK codes naar beheerobjecten en complexen.

structuren defaultdict[str, ObjectStructuur]

Dictionary van DISK codes naar extra informatie w.b. element-bouwdelen en bijbehorende disciplines en functie-impact.

horizon Horizon

Opgegeven of geobserveerde tijdshorizon.

check_element_bouwdeel

check_element_bouwdeel(
    disk: str,
    complex: str,
    eb: ElementBouwdeel,
    diag: PoemaDiagnostiek,
) -> tuple[set[Discipline], FunctieImpact]

Check of het bouwdeel bestaat voor de opgegeven DISK code.

Source code in src/raplan_rws/poema.py
def check_element_bouwdeel(
    self, disk: str, complex: str, eb: ElementBouwdeel, diag: PoemaDiagnostiek
) -> tuple[set[Discipline], FunctieImpact]:
    """Check of het bouwdeel bestaat voor de opgegeven DISK code."""
    obj_struct = self.structuren[disk]
    comp_struct = self.structuren[complex]

    if eb not in obj_struct.element_bouwdelen:
        diag.missende_ohs_element_bouwdelen.add((disk, eb))
        obj_struct.element_bouwdelen.add(eb)
        obj_struct.disciplines[eb].update(comp_struct.disciplines[eb])
        if eb in comp_struct.impacts:
            obj_struct.impacts[eb] += comp_struct.impacts[eb]
        else:
            diag.missende_impact.add((disk, eb))

    dis = obj_struct.disciplines[eb]
    if not dis:
        try:
            dis = Discipline.van_element_bouwdeel(eb)
            obj_struct.disciplines[eb] = dis
            diag.afgeleide_disciplines.add((disk, eb, tuple(dis)))
        except ValueError as _:
            diag.missende_disciplines.add((disk, eb))

    imp = obj_struct.impacts[eb]

    return dis, imp

check_object

check_object(
    disk: str, diag: PoemaDiagnostiek
) -> BeheerObject

Check of het object bestaat en maak het aan als dat niet zo is.

Source code in src/raplan_rws/poema.py
def check_object(self, disk: str, diag: PoemaDiagnostiek) -> BeheerObject:
    """Check of het object bestaat en maak het aan als dat niet zo is."""
    try:
        obj = self.objecten[disk]
    except KeyError:
        diag.missende_ohs_objecten.add(disk)
        obj = BeheerObject(disk, disk)
        self.objecten[disk] = obj
    finally:
        return obj

is_overbodig

is_overbodig(
    rij: PoemaRij,
    horizon: Horizon,
    negeer_verleden: bool,
    negeer_toekomst: bool,
    minimale_periode: int,
) -> bool

Check of een POEMA rij binnen de horizon valt, inachtnemend de filters.

Source code in src/raplan_rws/poema.py
def is_overbodig(
    self,
    rij: PoemaRij,
    horizon: Horizon,
    negeer_verleden: bool,
    negeer_toekomst: bool,
    minimale_periode: int,
) -> bool:
    """Check of een POEMA rij binnen de horizon valt, inachtnemend de filters."""

    if negeer_verleden:
        if rij.advies_plan_jaar < horizon.start and rij.uiterst_advies_jaar < horizon.start:
            return True
    if negeer_toekomst and horizon.end is not None:
        if rij.advies_plan_jaar > horizon.end and rij.uiterst_advies_jaar > horizon.end:
            return True

    periode = rij.periode()
    if periode is not None and periode < minimale_periode:
        return True

    return False

maak_kopie

maak_kopie() -> VerwerktePoemaData

Maak een kopie van dit object.

Source code in src/raplan_rws/poema.py
def maak_kopie(self) -> "VerwerktePoemaData":
    """Maak een kopie van dit object."""
    return deepcopy(self)

verwerk

verwerk(
    poema: PoemaData,
    ohs: VerwerkteOhsData | None = None,
    horizon: Horizon | None = None,
    negeer_verleden: bool = True,
    negeer_toekomst: bool = True,
    minimale_periode: int = 2,
) -> PoemaDiagnostiek

Verwerk POEMA data, eventueel aangevuld met te matchen verwerkte OHS data.

Source code in src/raplan_rws/poema.py
def verwerk(
    self,
    poema: PoemaData,
    ohs: VerwerkteOhsData | None = None,
    horizon: Horizon | None = None,
    negeer_verleden: bool = True,
    negeer_toekomst: bool = True,
    minimale_periode: int = 2,
) -> PoemaDiagnostiek:
    """Verwerk POEMA data, eventueel aangevuld met te matchen verwerkte OHS data."""
    diag = PoemaDiagnostiek()

    if ohs is not None:
        self.objecten = ohs.objecten
        self.structuren = ohs.structuren

    for rij in poema:
        if horizon is not None and self.is_overbodig(
            rij, horizon, negeer_verleden, negeer_toekomst, minimale_periode
        ):
            continue

        disk: str = rij.object_code
        complex: str = extract_comp_code(disk)

        obj: BeheerObject = self.check_object(disk, diag)
        eb: ElementBouwdeel = rij.element_bouwdeel()
        dis, imp = self.check_element_bouwdeel(disk, complex, eb, diag)

        kan_later_dan_uiterst: bool = is_truey(
            str(rij.kan_later_dan_uiterst),
            default=False,
        )

        gmr = GeplandeMaatregel(
            referentie_nummer=rij.referentie_nummer,
            beheerobject=obj,
            element=eb.element,
            bouwdeel=eb.bouwdeel,
            pakket_id=None,
            maatregel=rij.maatregel_naam,
            disciplines=dis,
            gepland_uitvoeringsjaar=int(rij.advies_plan_jaar),
            gepland_uitvoeringsjaar_reden="adviesplanjaar",
            gewenst_uitvoeringsjaar=int(rij.advies_plan_jaar),
            advies_plan_jaar=int(rij.advies_plan_jaar),
            uiterst_advies_jaar=int(rij.uiterst_advies_jaar),
            kan_later_dan_uiterst=kan_later_dan_uiterst,
            impact=imp,
            verlies=FunctieVerlies.volledig(imp),
            raming_incl=float(rij.uitvoerings_kosten),
            periode=rij.periode(),
        )

        if horizon is None:
            start = min(gmr.advies_plan_jaar, gmr.uiterst_advies_jaar)
            self.horizon.start = min(self.horizon.start, start)
            end = start + 1
            self.horizon.end = end if self.horizon.end is None else max(self.horizon.end, end)

        self.planning.maatregelen.append(gmr)

    if horizon is not None:
        self.horizon = horizon

    return diag

project

Rijkswaterstaat project Python raplan.

RwsProject

Data voor een RWS project.

Attributes:

Name Type Description
naam str | None

Optionele projectnaam.

planning MaatregelPlanning

Het hart van de data, de maatregelplanning.

objecten dict[str, BeheerObject]

Alle beheerobject-namen en code per DISK code.

structuren defaultdict[str, ObjectStructuur]

Alle beheerobject-structuren bestaande uit de element-bouwdelen en de daarbij betrokken disciplines en functie-impact per element-bouwdeel.

horizon Horizon | None

Opgegeven of geobserveerde horizon.

vervroeging_factor float

Hoeveel jaar een taak naar voren mag worden gehaald t.o.v. de afstand tussen het advies plan jaar en het uiterst advies jaar (standaard 25%).

later_dan_uiterst int

Maximale uitloop voor maatregelen die later dan hun uiterste advies plan jaar worden gepland wanneer dat is toegestaan door het veld kan_later_dan_uiterst.

max_vervroeging int | None

Maximaal aantal jaar dat een maatregel mag worden vervroegd.

max_uitstel int | None

Maximaal aantal jaar dat een maatregel mag worden uitgesteld.

project Project

RaPlan project dat correspondeert met de RWS data.

component_naar_disk_eb dict[UUID, tuple[str, ElementBouwdeel]]

Mapping van Component UUID's uit het RaPlan project naar element-bouwdelen uit de RWS wereld.

disk_eb_naar_component dict[tuple[str, ElementBouwdeel], Component]

Mapping van een DISK code en ElementBouwdeel naar een RaPlan project Component.

maintenance_naar_geplande_maatregel dict[UUID, GeplandeMaatregel]

Mapping van Maintenance UUID's uit het RaPlan project naar geplande maatregelen in de RWS planning.

geplande_maatregel_naar_maintenance dict[UUID, Maintenance]

Mapping van een geplande maatregel naar de bijbehorende RaPlan Maintenance.

system_naar_structuur dict[UUID, ObjectStructuur]

Mapping van System UUID's uit het RaPlan project naar beheerobject structuren in de RWS wereld.

bundel_maatregelen_enkele_kolk

bundel_maatregelen_enkele_kolk(
    disk: str,
    instellingen: ModelInstellingen | None = None,
    uitvoeren: bool = True,
    direct_doorvoeren: bool = True,
    dynamische_verschuiving: (
        Callable[[UUID, float], int | float | None] | None
    ) = None,
    dynamische_combis: (
        Callable[[frozenset[UUID]], int | float | None]
        | None
    ) = None,
    dynamische_uitsluiting: (
        Callable[[frozenset[UUID]], bool] | None
    ) = None,
) -> LpSyncProject

Maak een synchronisatiemodel van de maatregelplanning.

Parameters:

Name Type Description Default
disk str

DISK code van het beheerobject om de maatregelen van te bundelen.

required
instellingen ModelInstellingen | None

Instellingen voor het optimalisatiemodel.

None
uitvoeren bool

Of de optimalisaties gelijk moeten worden uitgevoerd of alleen worden klaargezet.

True
direct_doorvoeren bool

Of de resultaten gelijk moeten worden doorgevoerd in de planning.

True
dynamische_verschuiving Callable[[UUID, float], int | float | None] | None

Optie om een 'custom' verschuivingsfunctie op te geven die de standaardimplementatie via de MaatregelVerschuiving instellingen vervangt.

None
dynamische_combis Callable[[frozenset[UUID]], int | float | None] | None

Optie om een 'custom' combinatie-functie op te geven die de standaardimplementatie via de MaatregelSynchronisatie instellingen vervangt.

None
dynamische_uitsluiting Callable[[frozenset[UUID]], bool] | None

Optie om een 'custom' combinatie-functie op te geven die de standaardimplementatie via de MaatregelSynchronisatie instellingen vervangt.

None
Source code in src/raplan_rws/project.py
def bundel_maatregelen_enkele_kolk(
    self,
    disk: str,
    instellingen: ModelInstellingen | None = None,
    uitvoeren: bool = True,
    direct_doorvoeren: bool = True,
    dynamische_verschuiving: Callable[[UUID, float], int | float | None] | None = None,
    dynamische_combis: Callable[[frozenset[UUID]], int | float | None] | None = None,
    dynamische_uitsluiting: Callable[[frozenset[UUID]], bool] | None = None,
) -> LpSyncProject:
    """Maak een synchronisatiemodel van de maatregelplanning.

    Arguments:
        disk: DISK code van het beheerobject om de maatregelen van te bundelen.
        instellingen: Instellingen voor het optimalisatiemodel.
        uitvoeren: Of de optimalisaties gelijk moeten worden uitgevoerd of alleen worden
            klaargezet.
        direct_doorvoeren: Of de resultaten gelijk moeten worden doorgevoerd in de planning.
        dynamische_verschuiving: Optie om een 'custom' verschuivingsfunctie op te geven die
            de standaardimplementatie via de `MaatregelVerschuiving` instellingen vervangt.
        dynamische_combis: Optie om een 'custom' combinatie-functie op te geven die
            de standaardimplementatie via de `MaatregelSynchronisatie` instellingen vervangt.
        dynamische_uitsluiting: Optie om een 'custom' combinatie-functie op te geven die
            de standaardimplementatie via de `MaatregelSynchronisatie` instellingen vervangt.
    """

    settings: ModelInstellingen = instellingen or ModelInstellingen()

    builder = LpBuilder(
        duration_scale=settings.tijdsduur_naar_jaar,
        force_sync=settings.maatregel_synchronisatie.forceer_gelijk_starten,
        movement_objective=0,
        concurrent_objective=settings.maatregel_synchronisatie.jaar_samenvallen,
        dynamic_movement_objective=settings.maatregel_verschuiving.dynamische_functie(
            self.maintenance_naar_geplande_maatregel
        )
        if dynamische_verschuiving is None
        else dynamische_verschuiving,
        pair_objective=settings.maatregel_synchronisatie.dynamische_combi(
            self.maintenance_naar_geplande_maatregel
        )
        if dynamische_combis is None
        else dynamische_combis,
        pair_exclusions=settings.maatregel_synchronisatie.dynamische_uitsluiting(
            self.maintenance_naar_geplande_maatregel
        )
        if dynamische_uitsluiting is None
        else dynamische_uitsluiting,
        only_positive=False,
        silent=not settings.model_output,
    )
    builder.lp.setOptionValue("kkt_tolerance", settings.score_precisie)

    sys = self.disk_naar_system[disk]

    lp_sync = LpSyncProject(
        builder, uuid=self.project.uuid, systems=[LpSyncSystem.build(builder, sys)]
    )
    logger.info(
        f"'{sys.name}' Maatregelen ter optimalisatie: {len(sys.get_unordered_maintenance())}"
    )

    if uitvoeren:
        logger.info(f"'{sys.name}' Model wordt uitgevoerd...")

        lp_sync.run()
        score_na = lp_sync.objective
        logger.info(f"'{sys.name}' Score: {score_na}")

        if direct_doorvoeren:
            logger.info(f"'{sys.name}' Bundeling wordt doorgevoerd...")
            if lp_sync.model_status == HighsModelStatus.kOptimal:
                self.voer_bundeling_door(lp_sync, new=False)
            else:
                logger.warning(
                    f"Model voor '{sys.name}' heeft geen optimale oplossing kunnen vinden."
                )
    return lp_sync

bundel_maatregelen_ineens

bundel_maatregelen_ineens(
    instellingen: ModelInstellingen | None = None,
    uitvoeren: bool = True,
    direct_doorvoeren: bool = True,
    dynamische_verschuiving: (
        Callable[[UUID, float], int | float | None] | None
    ) = None,
    dynamische_combis: (
        Callable[[frozenset[UUID]], int | float | None]
        | None
    ) = None,
    dynamische_uitsluiting: (
        Callable[[frozenset[UUID]], bool] | None
    ) = None,
) -> LpSyncProject

Maak een synchronisatiemodel van de maatregelplanning.

Parameters:

Name Type Description Default
instellingen ModelInstellingen | None

Modelinstellingen voor de onderliggende optimalisatie.

None
uitvoeren bool

Of het model gelijk moet worden uitgevoerd.

True
direct_doorvoeren bool

Of de resultaten gelijk moeten worden verwerkt in de planning van dit project object.

True
dynamische_verschuiving Callable[[UUID, float], int | float | None] | None

Optie om een 'custom' verschuivingsfunctie op te geven die de standaardimplementatie via de MaatregelVerschuiving instellingen vervangt.

None
dynamische_combis Callable[[frozenset[UUID]], int | float | None] | None

Optie om een 'custom' combinatie-functie op te geven die de standaardimplementatie via de MaatregelSynchronisatie instellingen vervangt.

None
dynamische_uitsluiting Callable[[frozenset[UUID]], bool] | None

Optie om een 'custom' combinatie-functie op te geven die de standaardimplementatie via de MaatregelSynchronisatie instellingen vervangt.

None
Source code in src/raplan_rws/project.py
def bundel_maatregelen_ineens(
    self,
    instellingen: ModelInstellingen | None = None,
    uitvoeren: bool = True,
    direct_doorvoeren: bool = True,
    dynamische_verschuiving: Callable[[UUID, float], int | float | None] | None = None,
    dynamische_combis: Callable[[frozenset[UUID]], int | float | None] | None = None,
    dynamische_uitsluiting: Callable[[frozenset[UUID]], bool] | None = None,
) -> LpSyncProject:
    """Maak een synchronisatiemodel van de maatregelplanning.

    Arguments:
        instellingen: Modelinstellingen voor de onderliggende optimalisatie.
        uitvoeren: Of het model gelijk moet worden uitgevoerd.
        direct_doorvoeren: Of de resultaten gelijk moeten worden verwerkt in de planning van
            dit project object.
        dynamische_verschuiving: Optie om een 'custom' verschuivingsfunctie op te geven die
            de standaardimplementatie via de `MaatregelVerschuiving` instellingen vervangt.
        dynamische_combis: Optie om een 'custom' combinatie-functie op te geven die
            de standaardimplementatie via de `MaatregelSynchronisatie` instellingen vervangt.
        dynamische_uitsluiting: Optie om een 'custom' combinatie-functie op te geven die
            de standaardimplementatie via de `MaatregelSynchronisatie` instellingen vervangt.
    """
    settings: ModelInstellingen = instellingen or ModelInstellingen()

    builder = LpBuilder(
        duration_scale=settings.tijdsduur_naar_jaar,
        force_sync=settings.maatregel_synchronisatie.forceer_gelijk_starten,
        movement_objective=0,
        concurrent_objective=settings.maatregel_synchronisatie.jaar_samenvallen,
        dynamic_movement_objective=settings.maatregel_verschuiving.dynamische_functie(
            self.maintenance_naar_geplande_maatregel
        )
        if dynamische_verschuiving is None
        else dynamische_verschuiving,
        pair_objective=settings.maatregel_synchronisatie.dynamische_combi(
            self.maintenance_naar_geplande_maatregel
        )
        if dynamische_combis is None
        else dynamische_combis,
        pair_exclusions=settings.maatregel_synchronisatie.dynamische_uitsluiting(
            self.maintenance_naar_geplande_maatregel
        )
        if dynamische_uitsluiting is None
        else dynamische_uitsluiting,
        only_positive=False,
        silent=not settings.model_output,
    )
    builder.lp.setOptionValue("kkt_tolerance", settings.score_precisie)

    lp_sync = LpSyncProject.build(builder, self.project)

    if uitvoeren:
        lp_sync.run()

        if direct_doorvoeren:
            if lp_sync.model_status == HighsModelStatus.kOptimal:
                self.voer_bundeling_door(lp_sync, new=False)
            else:
                raise RuntimeError("Model heeft geen optimale oplossing kunnen vinden.")

    return lp_sync

bundel_maatregelen_per_kolk

bundel_maatregelen_per_kolk(
    instellingen: ModelInstellingen | None = None,
    uitvoeren: bool = True,
    direct_doorvoeren: bool = True,
    dynamische_verschuiving: (
        Callable[[UUID, float], int | float | None] | None
    ) = None,
    dynamische_combis: (
        Callable[[frozenset[UUID]], int | float | None]
        | None
    ) = None,
    dynamische_uitsluiting: (
        Callable[[frozenset[UUID]], bool] | None
    ) = None,
) -> dict[UUID, LpSyncProject]

Maak een synchronisatiemodel van de maatregelplanning.

Parameters:

Name Type Description Default
instellingen ModelInstellingen | None

Instellingen voor het optimalisatiemodel.

None
uitvoeren bool

Of de optimalisaties gelijk moeten worden uitgevoerd of alleen worden klaargezet.

True
direct_doorvoeren bool

Of de resultaten gelijk moeten worden doorgevoerd in de planning.

True
dynamische_verschuiving Callable[[UUID, float], int | float | None] | None

Optie om een 'custom' verschuivingsfunctie op te geven die de standaardimplementatie via de MaatregelVerschuiving instellingen vervangt.

None
dynamische_combis Callable[[frozenset[UUID]], int | float | None] | None

Optie om een 'custom' combinatie-functie op te geven die de standaardimplementatie via de MaatregelSynchronisatie instellingen vervangt.

None
dynamische_uitsluiting Callable[[frozenset[UUID]], bool] | None

Optie om een 'custom' combinatie-functie op te geven die de standaardimplementatie via de MaatregelSynchronisatie instellingen vervangt.

None
Source code in src/raplan_rws/project.py
def bundel_maatregelen_per_kolk(
    self,
    instellingen: ModelInstellingen | None = None,
    uitvoeren: bool = True,
    direct_doorvoeren: bool = True,
    dynamische_verschuiving: Callable[[UUID, float], int | float | None] | None = None,
    dynamische_combis: Callable[[frozenset[UUID]], int | float | None] | None = None,
    dynamische_uitsluiting: Callable[[frozenset[UUID]], bool] | None = None,
) -> dict[UUID, LpSyncProject]:
    """Maak een synchronisatiemodel van de maatregelplanning.

    Arguments:
        instellingen: Instellingen voor het optimalisatiemodel.
        uitvoeren: Of de optimalisaties gelijk moeten worden uitgevoerd of alleen worden
            klaargezet.
        direct_doorvoeren: Of de resultaten gelijk moeten worden doorgevoerd in de planning.
        dynamische_verschuiving: Optie om een 'custom' verschuivingsfunctie op te geven die
            de standaardimplementatie via de `MaatregelVerschuiving` instellingen vervangt.
        dynamische_combis: Optie om een 'custom' combinatie-functie op te geven die
            de standaardimplementatie via de `MaatregelSynchronisatie` instellingen vervangt.
        dynamische_uitsluiting: Optie om een 'custom' combinatie-functie op te geven die
            de standaardimplementatie via de `MaatregelSynchronisatie` instellingen vervangt.
    """
    lp_syncs: dict[UUID, LpSyncProject] = dict()
    for system in self.project.systems:
        lp_syncs[system.uuid] = self.bundel_maatregelen_enkele_kolk(
            system.name,  # type: ignore
            instellingen=instellingen,
            uitvoeren=uitvoeren,
            direct_doorvoeren=direct_doorvoeren,
            dynamische_verschuiving=dynamische_verschuiving,
            dynamische_combis=dynamische_combis,
            dynamische_uitsluiting=dynamische_uitsluiting,
        )
    return lp_syncs

gen_ingreep_procedures

gen_ingreep_procedures() -> (
    Generator[Procedure, Never, None]
)

Genereer de RaPlan Procedure objecten voor alle ingrepen op objecten.

Source code in src/raplan_rws/project.py
def gen_ingreep_procedures(self) -> Generator[raplan.Procedure, Never, None]:
    """Genereer de RaPlan `Procedure` objecten voor alle ingrepen op objecten."""
    for proc in self.project.gen_procedures(uuid_fn=self.pakket_id):
        yield proc

herbouw_project

herbouw_project()

Herbouw het intern opgeslagen RaPlan project en update de mapping dictionaries.

Source code in src/raplan_rws/project.py
def herbouw_project(self):
    """Herbouw het intern opgeslagen RaPlan project en update de mapping dictionaries."""
    if self.horizon is None:
        self.horizon: Horizon = self.planning.horizon()
    horizon: Horizon = self.horizon
    self.project = raplan.Project(name=self.naam, horizon=horizon)
    self._herbouw_structuur()
    self.planning.pas_horizon_toe(horizon)
    self._clear_mappings()
    start = int(horizon.start)

    for m in self.planning:
        if not m.valt_binnen_horizon(horizon):
            logger.debug(
                f"Maatregel '{m.referentie_nummer}' valt buiten horizon "
                f"'{horizon.start}-{horizon.end}'"
            )
            continue
        self._registreer_maatregel(m, start)

maak_kopie

maak_kopie() -> RwsProject

Maak een kopie van dit project.

Source code in src/raplan_rws/project.py
def maak_kopie(self) -> "RwsProject":
    """Maak een kopie van dit project."""
    return deepcopy(self)

naar_excel_bestand

naar_excel_bestand(
    bestandspad: str | Path | None = None,
) -> Workbook

Schrijf de planning weg naar een Excelbestand.

Source code in src/raplan_rws/project.py
def naar_excel_bestand(self, bestandspad: str | Path | None = None) -> Workbook:
    """Schrijf de planning weg naar een Excelbestand."""
    return self.planning.naar_excel_bestand(bestandspad=bestandspad)

pakket_id

pakket_id(maintenance: list[Maintenance]) -> UUID

Bereken een stabiel pakket UUID op basis van de maatregel-inhoud.

Source code in src/raplan_rws/project.py
def pakket_id(self, maintenance: list[raplan.Maintenance]) -> UUID:
    """Bereken een stabiel pakket UUID op basis van de maatregel-inhoud."""

    return with_uuid4s_to_uuid5(maintenance)

plan_ingrepen

plan_ingrepen(
    instellingen: ModelInstellingen | None = None,
    uitvoeren: bool = True,
    direct_doorvoeren: bool = True,
    dynamische_maatregel_verschuiving: (
        Callable[[UUID, float], int | float | None] | None
    ) = None,
    dynamische_ingreep_combis: (
        Callable[[frozenset[UUID]], int | float | None]
        | None
    ) = None,
    dynamische_ingreep_uitsluiting: (
        Callable[[frozenset[UUID]], bool] | None
    ) = None,
) -> LpProcedureProject

Maak een optimalisatiemodel voor de ingrepen (gebundelde maatregelen).

Parameters:

Name Type Description Default
instellingen ModelInstellingen | None

Instellingen voor het optimalisatiemodel.

None
uitvoeren bool

Of de optimalisaties gelijk moeten worden uitgevoerd of alleen worden klaargezet.

True
direct_doorvoeren bool

Of de resultaten gelijk moeten worden doorgevoerd in de planning.

True
dynamische_maatregel_verschuiving Callable[[UUID, float], int | float | None] | None

Optie om een 'custom' verschuivingsfunctie op te geven die de standaardimplementatie via de MaatregelVerschuiving instellingen vervangt.

None
dynamische_ingreep_combis Callable[[frozenset[UUID]], int | float | None] | None

Optie om een 'custom' combinatie-functie op te geven die de standaardimplementatie via de Ingreepinstellingen.dynamische_combi functie vervangt.

None
dynamische_ingreep_uitsluiting Callable[[frozenset[UUID]], bool] | None

Optie om een 'custom' combinatie-functie op te geven die de standaardimplementatie via de IngreepInstellingen.dynamische_uitsluiting functie vervangt.

None
Source code in src/raplan_rws/project.py
def plan_ingrepen(
    self,
    instellingen: ModelInstellingen | None = None,
    uitvoeren: bool = True,
    direct_doorvoeren: bool = True,
    dynamische_maatregel_verschuiving: Callable[[UUID, float], int | float | None]
    | None = None,
    dynamische_ingreep_combis: Callable[[frozenset[UUID]], int | float | None] | None = None,
    dynamische_ingreep_uitsluiting: Callable[[frozenset[UUID]], bool] | None = None,
) -> LpProcedureProject:
    """Maak een optimalisatiemodel voor de ingrepen (gebundelde maatregelen).

    Arguments:
        instellingen: Instellingen voor het optimalisatiemodel.
        uitvoeren: Of de optimalisaties gelijk moeten worden uitgevoerd of alleen worden
            klaargezet.
        direct_doorvoeren: Of de resultaten gelijk moeten worden doorgevoerd in de planning.
        dynamische_maatregel_verschuiving: Optie om een 'custom' verschuivingsfunctie op te
            geven die de standaardimplementatie via de `MaatregelVerschuiving` instellingen
            vervangt.
        dynamische_ingreep_combis: Optie om een 'custom' combinatie-functie op te geven die
            de standaardimplementatie via de `Ingreepinstellingen.dynamische_combi` functie
            vervangt.
        dynamische_ingreep_uitsluiting: Optie om een 'custom' combinatie-functie op te geven die
            de standaardimplementatie via de `IngreepInstellingen.dynamische_uitsluiting`
            functie vervangt.
    """

    settings: ModelInstellingen = instellingen or ModelInstellingen()

    ingrepen: dict[UUID, raplan.Procedure] = {p.uuid: p for p in self.gen_ingreep_procedures()}

    builder = LpBuilder(
        duration_scale=settings.tijdsduur_naar_jaar,
        force_sync=False,
        movement_objective=0,
        concurrent_objective=settings.maatregel_synchronisatie.jaar_samenvallen,
        dynamic_movement_objective=settings.maatregel_verschuiving.dynamische_functie(
            self.maintenance_naar_geplande_maatregel
        )
        if dynamische_maatregel_verschuiving is None
        else dynamische_maatregel_verschuiving,
        pair_objective=settings.ingreep_instellingen.dynamische_combi(
            ingrepen,
            self.maintenance_naar_geplande_maatregel,
        )
        if dynamische_ingreep_combis is None
        else dynamische_ingreep_combis,
        pair_exclusions=settings.ingreep_instellingen.dynamische_uitsluiting(
            ingrepen, self.maintenance_naar_geplande_maatregel
        )
        if dynamische_ingreep_uitsluiting is None
        else dynamische_ingreep_uitsluiting,
        only_positive=False,
        silent=not settings.model_output,
    )
    builder.lp.setOptionValue("kkt_tolerance", settings.score_precisie)

    lp_proj = LpProcedureProject.build(builder, self.project)

    if uitvoeren:
        lp_proj.run()

        if direct_doorvoeren:
            if lp_proj.model_status == HighsModelStatus.kOptimal:
                lp_proj.apply_to(self.project)

            else:
                raise RuntimeError("Model heeft geen optimale oplossing kunnen vinden.")

    return lp_proj

sluit_specifieke_ingreep_combi_uit

sluit_specifieke_ingreep_combi_uit(
    pakket_id_a: UUID,
    pakket_id_b: UUID,
    instellingen: ModelInstellingen,
    ongedaan_maken: bool = False,
)

Sluit een specifieke combinatie van ingrepen uit door deze IDs toe te voegen aan de verboden combinaties in het ModelInstellingen object.

Parameters:

Name Type Description Default
pakket_id_a UUID

Helft van het betrokken Ingreep paar in de vorm van het 'pakket_id'.

required
pakket_id_b UUID

Andere helft van het betrokken Ingreep paar in de vorm van het 'pakket_id'.

required
instellingen ModelInstellingen

De ModelInstellingen die geüpdatet moeten worden.

required
ongedaan_maken bool

Verwijder het paar uit de verboden combinaties van de instellingen.

False
Source code in src/raplan_rws/project.py
def sluit_specifieke_ingreep_combi_uit(
    self,
    pakket_id_a: UUID,
    pakket_id_b: UUID,
    instellingen: ModelInstellingen,
    ongedaan_maken: bool = False,
):
    """Sluit een specifieke combinatie van ingrepen uit door deze IDs toe te voegen aan de
    verboden combinaties in het `ModelInstellingen` object.

    Arguments:
        pakket_id_a: Helft van het betrokken `Ingreep` paar in de vorm van het 'pakket_id'.
        pakket_id_b: Andere helft van het betrokken `Ingreep` paar in de vorm van het
            'pakket_id'.
        instellingen: De `ModelInstellingen` die geüpdatet moeten worden.
        ongedaan_maken: Verwijder het paar uit de verboden combinaties van de instellingen.
    """
    if ongedaan_maken:
        instellingen.maatregel_synchronisatie.uitgesloten_combis.remove(
            frozenset((pakket_id_a, pakket_id_b))
        )
    else:
        instellingen.maatregel_synchronisatie.uitgesloten_combis.add(
            frozenset((pakket_id_a, pakket_id_b))
        )

sluit_specifieke_maatregel_combi_uit

sluit_specifieke_maatregel_combi_uit(
    a: GeplandeMaatregel,
    b: GeplandeMaatregel,
    instellingen: ModelInstellingen,
    ongedaan_maken: bool = False,
)

Sluit een specifieke combinatie van maatregelen uit door deze IDs toe te voegen aan de verboden combinaties in het ModelInstellingen object.

Parameters:

Name Type Description Default
a GeplandeMaatregel

Helft van het betrokken GeplandeMaatregel paar.

required
b GeplandeMaatregel

Andere helft van het betrokken GeplandeMaatregel paar.

required
instellingen ModelInstellingen

De ModelInstellingen die geüpdatet moeten worden.

required
ongedaan_maken bool

Verwijder het paar uit de verboden combinaties van de instellingen.

False
Source code in src/raplan_rws/project.py
def sluit_specifieke_maatregel_combi_uit(
    self,
    a: GeplandeMaatregel,
    b: GeplandeMaatregel,
    instellingen: ModelInstellingen,
    ongedaan_maken: bool = False,
):
    """Sluit een specifieke combinatie van maatregelen uit door deze IDs toe te voegen aan de
    verboden combinaties in het `ModelInstellingen` object.

    Arguments:
        a: Helft van het betrokken `GeplandeMaatregel` paar.
        b: Andere helft van het betrokken `GeplandeMaatregel` paar.
        instellingen: De `ModelInstellingen` die geüpdatet moeten worden.
        ongedaan_maken: Verwijder het paar uit de verboden combinaties van de instellingen.
    """
    a_id = self.geplande_maatregel_naar_maintenance[a.uuid].uuid
    b_id = self.geplande_maatregel_naar_maintenance[b.uuid].uuid
    if ongedaan_maken:
        instellingen.maatregel_synchronisatie.uitgesloten_combis.remove(frozenset((a_id, b_id)))
    else:
        instellingen.maatregel_synchronisatie.uitgesloten_combis.add(frozenset((a_id, b_id)))

stel_specifieke_ingreep_combi_in

stel_specifieke_ingreep_combi_in(
    pakket_id_a: UUID,
    pakket_id_b: UUID,
    score: int | float | None,
    instellingen: ModelInstellingen,
)

Stel een specifieke score in voor het gelijkvallen van deze twee maatregelen op hetzelfde object. Dit wordt doorgevoerd in het instellingen object.

Source code in src/raplan_rws/project.py
def stel_specifieke_ingreep_combi_in(
    self,
    pakket_id_a: UUID,
    pakket_id_b: UUID,
    score: int | float | None,
    instellingen: ModelInstellingen,
):
    """Stel een specifieke score in voor het gelijkvallen van deze twee maatregelen op
    hetzelfde object. Dit wordt doorgevoerd in het instellingen object.
    """
    if score is None:
        instellingen.ingreep_instellingen.specifieke_combis.pop(
            frozenset((pakket_id_a, pakket_id_b))
        )
    else:
        instellingen.maatregel_synchronisatie.specifieke_combis[
            frozenset((pakket_id_a, pakket_id_b))
        ] = score

stel_specifieke_maatregel_combi_in

stel_specifieke_maatregel_combi_in(
    a: GeplandeMaatregel,
    b: GeplandeMaatregel,
    score: int | float | None,
    instellingen: ModelInstellingen,
)

Stel een specifieke score in voor het gelijkvallen van deze twee maatregelen op hetzelfde object. Dit wordt doorgevoerd in het instellingen object.

Source code in src/raplan_rws/project.py
def stel_specifieke_maatregel_combi_in(
    self,
    a: GeplandeMaatregel,
    b: GeplandeMaatregel,
    score: int | float | None,
    instellingen: ModelInstellingen,
):
    """Stel een specifieke score in voor het gelijkvallen van deze twee maatregelen op
    hetzelfde object. Dit wordt doorgevoerd in het instellingen object.
    """
    a_id = self.geplande_maatregel_naar_maintenance[a.uuid].uuid
    b_id = self.geplande_maatregel_naar_maintenance[b.uuid].uuid
    if score is None:
        instellingen.maatregel_synchronisatie.specifieke_combis.pop(frozenset((a_id, b_id)))
    else:
        instellingen.maatregel_synchronisatie.specifieke_combis[frozenset((a_id, b_id))] = score

stel_specifieke_verschuiving_in

stel_specifieke_verschuiving_in(
    m: GeplandeMaatregel,
    score: (
        int | float | tuple[int | float, int | float] | None
    ),
    instellingen: ModelInstellingen,
)

Stel een specifieke verschuiving in voor een geplande maatregel of verwijder hem. Dit wordt doorgevoerd in het instellingen object.

Source code in src/raplan_rws/project.py
def stel_specifieke_verschuiving_in(
    self,
    m: GeplandeMaatregel,
    score: int | float | tuple[int | float, int | float] | None,
    instellingen: ModelInstellingen,
):
    """Stel een specifieke verschuiving in voor een geplande maatregel of verwijder hem.
    Dit wordt doorgevoerd in het instellingen object.
    """
    m_id = self.geplande_maatregel_naar_maintenance[m.uuid].uuid
    if score is None:
        instellingen.maatregel_verschuiving.specifiek.pop(m_id)
    else:
        instellingen.maatregel_verschuiving.specifiek[m_id] = score

van_excel_bestand classmethod

van_excel_bestand(
    bestandspad: str | Path,
    naam: str | None = None,
    horizon: Horizon | None = None,
    vervroeging_factor: float = 0.25,
    later_dan_uiterst: int = 0,
    max_vervroeging: int | None = 1,
    max_uitstel: int | None = 5,
) -> RwsProject

Laad een RWS project in vanaf en Excel bestand met de maatregelplanning.

Parameters:

Name Type Description Default
bestandspad str | Path

Pad naar het Excel bestand met de maatregelplanning.

required
naam str | None

Optionele naam voor het project.

None
horizon Horizon | None

Optionele horizon om maatregelen die moeten worden meegenomen te filteren.

None
vervroeging_factor float

Factor om te bepalen hoeveel de maximale vervroeging van een maatregel is, ten opzichte van het verschil tussen het advies plan jaar en het uiterst advies jaar (meestal tussen 0.0 en 1.0, standaard op 0.25).

0.25
later_dan_uiterst int

Aantal jaar dat een maatregel mag worden uitgesteld bovenop hun uiterst advies jaar. Standaard op 0.

0
max_vervroeging int | None

Maximaal aantal jaar dat een maatregel naar voren gehaald mag worden t.o.v. het advies plan jaar. Standaard op 1.

1
max_uitstel int | None

Maximaal aantal jaar dat een maatregel mag worden uitgesteld t.o.v. het advies plan jaar. Standaard op 5.

5
Source code in src/raplan_rws/project.py
@classmethod
def van_excel_bestand(
    cls,
    bestandspad: str | Path,
    naam: str | None = None,
    horizon: Horizon | None = None,
    vervroeging_factor: float = 0.25,
    later_dan_uiterst: int = 0,
    max_vervroeging: int | None = 1,
    max_uitstel: int | None = 5,
) -> "RwsProject":
    """Laad een RWS project in vanaf en Excel bestand met de maatregelplanning.

    Arguments:
        bestandspad: Pad naar het Excel bestand met de maatregelplanning.
        naam: Optionele naam voor het project.
        horizon: Optionele horizon om maatregelen die moeten worden meegenomen te filteren.
        vervroeging_factor: Factor om te bepalen hoeveel de maximale vervroeging van een
            maatregel is, ten opzichte van het verschil tussen het advies plan jaar en het
            uiterst advies jaar (meestal tussen 0.0 en 1.0, standaard op 0.25).
        later_dan_uiterst: Aantal jaar dat een maatregel mag worden uitgesteld bovenop hun
            uiterst advies jaar. Standaard op 0.
        max_vervroeging: Maximaal aantal jaar dat een maatregel naar voren gehaald mag worden
            t.o.v. het advies plan jaar. Standaard op 1.
        max_uitstel: Maximaal aantal jaar dat een maatregel mag worden uitgesteld t.o.v. het
            advies plan jaar. Standaard op 5.
    """
    planning: MaatregelPlanning = MaatregelPlanning.van_excel_bestand(bestandspad)
    return cls.van_maatregel_planning(
        planning,
        naam=naam,
        horizon=horizon,
        vervroeging_factor=vervroeging_factor,
        later_dan_uiterst=later_dan_uiterst,
        max_vervroeging=max_vervroeging,
        max_uitstel=max_uitstel,
    )

van_maatregel_planning classmethod

van_maatregel_planning(
    planning: MaatregelPlanning,
    naam: str | None = None,
    horizon: Horizon | None = None,
    vervroeging_factor: float = 0.25,
    later_dan_uiterst: int = 0,
    max_vervroeging: int | None = 1,
    max_uitstel: int | None = 5,
)

Neem een maatregelplanning over in een RWS project.

Parameters:

Name Type Description Default
planning MaatregelPlanning

Maatregelplanning object.

required
naam str | None

Optionele naam voor het project.

None
horizon Horizon | None

Optionele horizon om maatregelen die moeten worden meegenomen te filteren.

None
vervroeging_factor float

Factor om te bepalen hoeveel de maximale vervroeging van een maatregel is, ten opzichte van het verschil tussen het advies plan jaar en het uiterst advies jaar (meestal tussen 0.0 en 1.0, standaard op 0.25).

0.25
later_dan_uiterst int

Aantal jaar dat een maatregel mag worden uitgesteld bovenop hun uiterst advies jaar. Standaard op 0.

0
max_vervroeging int | None

Maximaal aantal jaar dat een maatregel naar voren gehaald mag worden t.o.v. het advies plan jaar. Standaard op 1.

1
max_uitstel int | None

Maximaal aantal jaar dat een maatregel mag worden uitgesteld t.o.v. het advies plan jaar. Standaard op 5.

5
Source code in src/raplan_rws/project.py
@classmethod
def van_maatregel_planning(
    cls,
    planning: MaatregelPlanning,
    naam: str | None = None,
    horizon: Horizon | None = None,
    vervroeging_factor: float = 0.25,
    later_dan_uiterst: int = 0,
    max_vervroeging: int | None = 1,
    max_uitstel: int | None = 5,
):
    """Neem een maatregelplanning over in een RWS project.

    Arguments:
        planning: Maatregelplanning object.
        naam: Optionele naam voor het project.
        horizon: Optionele horizon om maatregelen die moeten worden meegenomen te filteren.
        vervroeging_factor: Factor om te bepalen hoeveel de maximale vervroeging van een
            maatregel is, ten opzichte van het verschil tussen het advies plan jaar en het
            uiterst advies jaar (meestal tussen 0.0 en 1.0, standaard op 0.25).
        later_dan_uiterst: Aantal jaar dat een maatregel mag worden uitgesteld bovenop hun
            uiterst advies jaar. Standaard op 0.
        max_vervroeging: Maximaal aantal jaar dat een maatregel naar voren gehaald mag worden
            t.o.v. het advies plan jaar. Standaard op 1.
        max_uitstel: Maximaal aantal jaar dat een maatregel mag worden uitgesteld t.o.v. het
            advies plan jaar. Standaard op 5.
    """
    objecten: dict[str, BeheerObject] = dict()
    structuren: dict[str, ObjectStructuur] = defaultdict(ObjectStructuur)

    for mr in planning:
        disk = mr.beheerobject.code

        if disk not in objecten:
            objecten[disk] = mr.beheerobject

        if disk not in structuren:
            structuren[disk] = ObjectStructuur()

        structuur: ObjectStructuur = structuren[disk]

        eb = mr.element_bouwdeel
        structuur.element_bouwdelen.add(eb)

        if eb not in structuur.disciplines:
            structuur.disciplines[eb] = mr.disciplines

        if eb not in structuur.impacts:
            structuur.impacts[eb] = mr.impact

    return RwsProject(
        naam=naam,
        planning=planning,
        horizon=horizon,
        vervroeging_factor=vervroeging_factor,
        later_dan_uiterst=later_dan_uiterst,
        max_vervroeging=max_vervroeging,
        max_uitstel=max_uitstel,
    )

van_poema_data classmethod

van_poema_data(
    data: VerwerktePoemaData,
    naam: str | None = None,
    horizon: Horizon | None = None,
    vervroeging_factor: float = 0.25,
    later_dan_uiterst: int = 0,
    max_vervroeging: int | None = 1,
    max_uitstel: int | None = 5,
) -> RwsProject

Maak een RWS project gebaseerd op verwerkte POEMA data.

Parameters:

Name Type Description Default
data VerwerktePoemaData

Verwerkte POEMA data.

required
naam str | None

Optionele naam voor het project.

None
horizon Horizon | None

Optionele horizon om maatregelen die moeten worden meegenomen te filteren.

None
vervroeging_factor float

Factor om te bepalen hoeveel de maximale vervroeging van een maatregel is, ten opzichte van het verschil tussen het advies plan jaar en het uiterst advies jaar (meestal tussen 0.0 en 1.0, standaard op 0.25).

0.25
later_dan_uiterst int

Aantal jaar dat een maatregel mag worden uitgesteld bovenop hun uiterst advies jaar. Standaard op 0.

0
max_vervroeging int | None

Maximaal aantal jaar dat een maatregel naar voren gehaald mag worden t.o.v. het advies plan jaar. Standaard op 1.

1
max_uitstel int | None

Maximaal aantal jaar dat een maatregel mag worden uitgesteld t.o.v. het advies plan jaar. Standaard op 5.

5
Source code in src/raplan_rws/project.py
@classmethod
def van_poema_data(
    cls,
    data: VerwerktePoemaData,
    naam: str | None = None,
    horizon: Horizon | None = None,
    vervroeging_factor: float = 0.25,
    later_dan_uiterst: int = 0,
    max_vervroeging: int | None = 1,
    max_uitstel: int | None = 5,
) -> "RwsProject":
    """Maak een RWS project gebaseerd op verwerkte POEMA data.

    Arguments:
        data: Verwerkte POEMA data.
        naam: Optionele naam voor het project.
        horizon: Optionele horizon om maatregelen die moeten worden meegenomen te filteren.
        vervroeging_factor: Factor om te bepalen hoeveel de maximale vervroeging van een
            maatregel is, ten opzichte van het verschil tussen het advies plan jaar en het
            uiterst advies jaar (meestal tussen 0.0 en 1.0, standaard op 0.25).
        later_dan_uiterst: Aantal jaar dat een maatregel mag worden uitgesteld bovenop hun
            uiterst advies jaar. Standaard op 0.
        max_vervroeging: Maximaal aantal jaar dat een maatregel naar voren gehaald mag worden
            t.o.v. het advies plan jaar. Standaard op 1.
        max_uitstel: Maximaal aantal jaar dat een maatregel mag worden uitgesteld t.o.v. het
            advies plan jaar. Standaard op 5.
    """
    return cls(
        naam=naam,
        planning=data.planning,
        horizon=horizon or data.horizon,
        vervroeging_factor=vervroeging_factor,
        later_dan_uiterst=later_dan_uiterst,
        max_vervroeging=max_vervroeging,
        max_uitstel=max_uitstel,
    )

van_poema_ohs_bestanden classmethod

van_poema_ohs_bestanden(
    poema: list[str | Path],
    ohs: list[tuple[str | Path, OhsInstellingen]],
    horizon: Horizon | None = None,
    negeer_verleden: bool = True,
    negeer_toekomst: bool = True,
    minimale_periode: int = 2,
    naam: str | None = None,
    vervroeging_factor: float = 0.25,
    later_dan_uiterst: int = 0,
    max_vervroeging: int | None = 1,
    max_uitstel: int | None = 5,
) -> RwsProject

Maak een RWS project gebaseerd op POEMA en OHS bestanden/instellingen.

Parameters:

Name Type Description Default
poema list[str | Path]

Lijst van bestandspaden naar POEMA Excel bestanden.

required
ohs list[tuple[str | Path, OhsInstellingen]]

Lijst van combinaties van bestandspaden en instellingen voor OHS sheets.

required
naam str | None

Optionele naam voor het project.

None
horizon Horizon | None

Optionele horizon om maatregelen die moeten worden meegenomen te filteren.

None
negeer_verleden bool

Of maatregelen die in het verleden staan geprogrammeerd ten opzichte van de opgegeven horizon moeten worden genegeerd.

True
negeer_toekomst bool

Of maatregelen die in de toekomst staan geprogrammeerd ten opzichte van het eind van de opgegeven horizon moeten worden genegeerd.

True
vervroeging_factor float

Factor om te bepalen hoeveel de maximale vervroeging van een maatregel is, ten opzichte van het verschil tussen het advies plan jaar en het uiterst advies jaar (meestal tussen 0.0 en 1.0, standaard op 0.25).

0.25
later_dan_uiterst int

Aantal jaar dat een maatregel mag worden uitgesteld bovenop hun uiterst advies jaar. Standaard op 0.

0
max_vervroeging int | None

Maximaal aantal jaar dat een maatregel naar voren gehaald mag worden t.o.v. het advies plan jaar. Standaard op 1.

1
max_uitstel int | None

Maximaal aantal jaar dat een maatregel mag worden uitgesteld t.o.v. het advies plan jaar. Standaard op 5.

5
Source code in src/raplan_rws/project.py
@classmethod
def van_poema_ohs_bestanden(
    cls,
    poema: list[str | Path],
    ohs: list[tuple[str | Path, OhsInstellingen]],
    horizon: Horizon | None = None,
    negeer_verleden: bool = True,
    negeer_toekomst: bool = True,
    minimale_periode: int = 2,
    naam: str | None = None,
    vervroeging_factor: float = 0.25,
    later_dan_uiterst: int = 0,
    max_vervroeging: int | None = 1,
    max_uitstel: int | None = 5,
) -> "RwsProject":
    """Maak een RWS project gebaseerd op POEMA en OHS bestanden/instellingen.

    Arguments:
        poema: Lijst van bestandspaden naar POEMA Excel bestanden.
        ohs: Lijst van combinaties van bestandspaden en instellingen voor OHS sheets.
        naam: Optionele naam voor het project.
        horizon: Optionele horizon om maatregelen die moeten worden meegenomen te filteren.
        negeer_verleden: Of maatregelen die in het verleden staan geprogrammeerd ten opzichte
            van de opgegeven horizon moeten worden genegeerd.
        negeer_toekomst: Of maatregelen die in de toekomst staan geprogrammeerd ten opzichte
            van het eind van de opgegeven horizon moeten worden genegeerd.
        vervroeging_factor: Factor om te bepalen hoeveel de maximale vervroeging van een
            maatregel is, ten opzichte van het verschil tussen het advies plan jaar en het
            uiterst advies jaar (meestal tussen 0.0 en 1.0, standaard op 0.25).
        later_dan_uiterst: Aantal jaar dat een maatregel mag worden uitgesteld bovenop hun
            uiterst advies jaar. Standaard op 0.
        max_vervroeging: Maximaal aantal jaar dat een maatregel naar voren gehaald mag worden
            t.o.v. het advies plan jaar. Standaard op 1.
        max_uitstel: Maximaal aantal jaar dat een maatregel mag worden uitgesteld t.o.v. het
            advies plan jaar. Standaard op 5.
    """
    ohs_data = OhsData()
    for bestand, instellingen in ohs:
        ohs_data += OhsData.van_excel_bestand(bestand, instellingen)
    ohs_verwerkt = ohs_data.verwerk()

    poema_data = PoemaData()
    for bestand in poema:
        poema_data += PoemaData.van_excel_bestand(bestand)

    data = poema_data.verwerk(
        ohs=ohs_verwerkt,
        horizon=horizon,
        negeer_verleden=negeer_verleden,
        negeer_toekomst=negeer_toekomst,
        minimale_periode=minimale_periode,
    )

    return cls.van_poema_data(
        data,
        naam=naam,
        horizon=horizon,
        vervroeging_factor=vervroeging_factor,
        later_dan_uiterst=later_dan_uiterst,
        max_vervroeging=max_vervroeging,
        max_uitstel=max_uitstel,
    )

voer_bundeling_door

voer_bundeling_door(
    lp_sync: LpSyncProject, new: bool = True
) -> RwsProject

Voer de bundeling door in het interne RaPlan project en wijzig de maatregelplanning.

Source code in src/raplan_rws/project.py
def voer_bundeling_door(self, lp_sync: LpSyncProject, new: bool = True) -> "RwsProject":
    """Voer de bundeling door in het interne RaPlan project en wijzig de maatregelplanning."""

    rws = deepcopy(self) if new else self
    start = rws.project.horizon.start
    lp_sync.apply_to(rws.project, new=False)  # Handled by deepcopy above.

    for proc in rws.gen_ingreep_procedures():
        ingreep_id = str(proc.uuid) if len(proc.maintenance) > 1 else None
        for m in proc.maintenance:
            gep: GeplandeMaatregel = rws.maintenance_naar_geplande_maatregel[m.uuid]
            gep.pakket_id = ingreep_id

            jaar = int(m.time + start)
            if jaar != gep.gewenst_uitvoeringsjaar:
                gep.gewenst_uitvoeringsjaar = jaar
                gep.gepland_uitvoeringsjaar = jaar
                gep.gepland_uitvoeringsjaar_reden = "bundeling"

    return rws

voer_ingreep_herplanning

voer_ingreep_herplanning(
    lp_proc: LpProcedureProject, new: bool = True
) -> RwsProject

Voer een optimalisatie door van de ingrepen/procedures.

Source code in src/raplan_rws/project.py
def voer_ingreep_herplanning(
    self, lp_proc: LpProcedureProject, new: bool = True
) -> "RwsProject":
    """Voer een optimalisatie door van de ingrepen/procedures."""
    rws = deepcopy(self) if new else self
    start = rws.project.horizon.start
    lp_proc.apply_to(rws.project, new=False)  # Handled by deepcopy above.

    for m in rws.project.gen_maintenance():
        gep: GeplandeMaatregel = rws.maintenance_naar_geplande_maatregel[m.uuid]
        jaar = int(m.time + start)
        if jaar != gep.gewenst_uitvoeringsjaar:
            gep.gewenst_uitvoeringsjaar = jaar
            gep.gepland_uitvoeringsjaar = jaar
            gep.gepland_uitvoeringsjaar_reden = "kolkoverstijdend"

    return rws

shared

Gedeelde classes voor zowel de POEMA als OHS import.

BeheerObject

Gegevens met betrekking op een beheerobject van Rijkswaterstaat.

code instance-attribute

code: str

Beheerobject code.

complex_code property

complex_code: str

Complex code, i.e. de eerste twee delen van de DISK code.

naam instance-attribute

naam: str

Beheerobject naam.

maak_kopie

maak_kopie() -> BeheerObject

Maak een kopie van dit object.

Source code in src/raplan_rws/shared.py
def maak_kopie(self) -> "BeheerObject":
    """Maak een kopie van dit object."""
    return deepcopy(self)

Discipline

Bases: PascalCaseStrEnum

Disciplines die betrokken is bij de uitvoering van een maatregel of maatregelpakket.

ElementBouwdeel

Gegevens met betrekking op een element/bouwdeel van een beheerobject van Rijkswaterstaat.

bouwdeel instance-attribute

bouwdeel: str

Naam van het bouwdeel van het element.

element instance-attribute

element: str

Naam van het element.

maak_kopie

maak_kopie() -> ElementBouwdeel

Maak een kopie van dit element-bouwdeel.

Source code in src/raplan_rws/shared.py
def maak_kopie(self) -> "ElementBouwdeel":
    """Maak een kopie van dit element-bouwdeel."""
    return deepcopy(self)

FunctieImpact

Beschrijving van de betrokkenheid van een element-bouwdeel op ƩƩn van de objectfuncties.

faciliteren_vaarweg_verkeer instance-attribute

faciliteren_vaarweg_verkeer: bool

Faciliteren vaarwegverkeer en kruisen omgeving (functie).

faciliteren_wegverkeer instance-attribute

faciliteren_wegverkeer: bool

Faciliteren wegverkeeer (functie).

hoogwater_afvoeren instance-attribute

hoogwater_afvoeren: bool

Hoogwater afvoeren op meren en kanalen (functie).

secundaire_functies instance-attribute

secundaire_functies: bool

Of de maatregel impact heeft op de secundaire functies van het object.

vgm instance-attribute

vgm: bool

Of de maatregel leidt tot een beperking van de risico's wat betreft Veiligheid, Gezondheid en Milieu.

water_keren instance-attribute

water_keren: bool

Water keren (functie).

geen classmethod

geen() -> FunctieImpact

Maak een lege functie impact aan.

Source code in src/raplan_rws/shared.py
@classmethod
def geen(cls) -> "FunctieImpact":
    """Maak een lege functie impact aan."""
    return FunctieImpact(False, False, False, False, False, False)

maak_kopie

maak_kopie() -> FunctieImpact

Maak een kopie van dit object.

Source code in src/raplan_rws/shared.py
def maak_kopie(self) -> "FunctieImpact":
    """Maak een kopie van dit object."""
    return deepcopy(self)

FunctieVerlies

Beschrijving van het functieverlies van een maatregel in [0.0-1.0] waarbij 1.0 staat voor 100% functieverlies.

faciliteren_vaarweg_verkeer instance-attribute

faciliteren_vaarweg_verkeer: float

Faciliteren vaarwegverkeer en kruisen omgeving (functie).

faciliteren_wegverkeer instance-attribute

faciliteren_wegverkeer: float

Faciliteren wegverkeeer (functie).

hoogwater_afvoeren instance-attribute

hoogwater_afvoeren: float

Hoogwater afvoeren op meren en kanalen (functie).

secundaire_functies instance-attribute

secundaire_functies: float

Of de maatregel impact heeft op de secundaire functies van het object.

vgm instance-attribute

vgm: float

Of de maatregel leidt tot een beperking van de risico's wat betreft Veiligheid, Gezondheid en Milieu.

water_keren instance-attribute

water_keren: float

Water keren (functie).

geen classmethod

geen() -> FunctieVerlies

Maak een lege functie impact aan.

Source code in src/raplan_rws/shared.py
@classmethod
def geen(cls) -> "FunctieVerlies":
    """Maak een lege functie impact aan."""
    return FunctieVerlies(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)

maak_kopie

maak_kopie() -> FunctieVerlies

Maak een kopie van dit object.

Source code in src/raplan_rws/shared.py
def maak_kopie(self) -> "FunctieVerlies":
    """Maak een kopie van dit object."""
    return deepcopy(self)

volledig classmethod

volledig(impact: FunctieImpact) -> FunctieVerlies

Maak een volledig functieverlies aan op basis van de impact van het betreffende element-bouwdeel van het onderhoud.

Source code in src/raplan_rws/shared.py
@classmethod
def volledig(cls, impact: FunctieImpact) -> "FunctieVerlies":
    """Maak een volledig functieverlies aan op basis van de impact van het betreffende
    element-bouwdeel van het onderhoud."""
    return cls(
        faciliteren_vaarweg_verkeer=float(impact.faciliteren_vaarweg_verkeer),
        hoogwater_afvoeren=float(impact.hoogwater_afvoeren),
        faciliteren_wegverkeer=float(impact.faciliteren_wegverkeer),
        water_keren=float(impact.water_keren),
        vgm=float(impact.vgm),
        secundaire_functies=float(impact.secundaire_functies),
    )

GeplandeMaatregel

Geplande maatregel inclusief origine.

advies_plan_jaar instance-attribute

advies_plan_jaar: int

Het optimale vervangjaar van de specifieke maatregel volgens het ingeladen scenario.

beheerobject instance-attribute

beheerobject: BeheerObject

Beheerobject waarop deze maatregel van toepassing is.

bouwdeel instance-attribute

bouwdeel: str

Buwdeel waarop deze maatregel van toepassing is.

disciplines instance-attribute

disciplines: set[Discipline]

Disciplines met betrekking op het element.

element instance-attribute

element: str

Element waarop deze maatregel van toepassing is.

element_bouwdeel property

element_bouwdeel: ElementBouwdeel

Element-bouwdeel combi waar deze maatregel van toepassing op is.

gepland_uitvoeringsjaar instance-attribute

gepland_uitvoeringsjaar: int

Het geprogrammeerd uitvoeringsjaar op basis van maakbaarheid van de geclusterde maatregelen en maatregelpakketten.

gepland_uitvoeringsjaar_reden instance-attribute

gepland_uitvoeringsjaar_reden: str

De reden voor de laatst gemaakte planningswijziging.

gewenst_uitvoeringsjaar instance-attribute

gewenst_uitvoeringsjaar: int

Gewenst uitvoeringsjaar op basis van logische clustering van de maatregelen.

impact instance-attribute

impact: FunctieImpact

Beschrijving van de betrokkenheid van een element-bouwdeel op ƩƩn van de objectfuncties.

kan_later_dan_uiterst instance-attribute

kan_later_dan_uiterst: bool

Of het uiterste advies jaar kan worden overschreden.

maatregel instance-attribute

maatregel: str

Naam van de maatregel.

pakket_id instance-attribute

pakket_id: str | None

Identificatie voor een maatregelpakket.

periode instance-attribute

periode: float | None

Lengte van de periode voor deze maatregel indien het een herhalende preventieve maatregel betreft.

raming_incl instance-attribute

raming_incl: float

Raming incl. BTW (gebaseerd op expert judgement, DISK, kosten vanuit vorige projecten, hulp van de kostenpool etc.)

referentie_nummer instance-attribute

referentie_nummer: str

POEMA referentie nummer.

uiterst_advies_jaar instance-attribute

uiterst_advies_jaar: int

Het uiterste vervangjaar van de specifieke maatregel volgens het ingeladen scenario.

uuid class-attribute instance-attribute

uuid: UUID = field(default_factory=uuid4)

RaPlan-RWS unieke identifier.

verlies instance-attribute

verlies: FunctieVerlies

Beschrijving van het functieverlies van een maatregel in [0.0-1.0].

frequentie

frequentie() -> float

Frequentie van deze maatregel (1.0 / periode) in keren per jaar.

Omgekeerd evenredig afhankelijk van de herhalingsperiode van gepland onderhoud. Wanneer het onderhoud geen periode heeft of een periode van 0, dan is defrequentie ook nul.

Source code in src/raplan_rws/shared.py
def frequentie(self) -> float:
    """Frequentie van deze maatregel (1.0 / periode) in keren per jaar.

    Omgekeerd evenredig afhankelijk van de herhalingsperiode van gepland onderhoud.
    Wanneer het onderhoud geen periode heeft of een periode van 0, dan is defrequentie ook nul.
    """
    if self.periode is None or self.periode == 0.0:
        return 0.0
    return 1.0 / self.periode

maak_kopie

maak_kopie() -> GeplandeMaatregel

Maak een kopie van deze geplande maatregel.

Source code in src/raplan_rws/shared.py
def maak_kopie(self) -> "GeplandeMaatregel":
    """Maak een kopie van deze geplande maatregel."""
    return deepcopy(self)

time

time(start: int) -> int

Geplande tijd van deze maatregel t.o.v. de horizon start.

Source code in src/raplan_rws/shared.py
def time(self, start: int) -> int:
    """Geplande tijd van deze maatregel t.o.v. de horizon start."""
    return self.gepland_uitvoeringsjaar - start

valt_binnen_horizon

valt_binnen_horizon(horizon: Horizon) -> bool

Of deze maatregel (deels) binnen de horizon valt.

Source code in src/raplan_rws/shared.py
def valt_binnen_horizon(self, horizon: raplan.Horizon) -> bool:
    """Of deze maatregel (deels) binnen de horizon valt."""
    return (
        horizon.is_satisfied_by(self.advies_plan_jaar)
        or horizon.is_satisfied_by(self.uiterst_advies_jaar)
        or horizon.is_satisfied_by(self.gepland_uitvoeringsjaar)
        or horizon.is_satisfied_by(self.gewenst_uitvoeringsjaar)
    )

window

window(
    start: int,
    vervroeging_factor: float = 0.25,
    later_dan_uiterst_bonus: int = 1,
    bevries_verleden: bool = True,
    max_vervroeging: int | None = None,
    max_uitstel: int | None = None,
) -> Window

Het afgeleide window in de tijd waarin deze maatregel mag worden gepland.

Parameters:

Name Type Description Default
start int

Startjaar van de planningshorizon. Alle latere jaartallen zijn delta's ten opzichte van deze waarde.

required
vervroeging_factor float

Hoeveel eerder een taak naar voren mag worden gehaald t.o.v. de afstand tussen het advies plan jaar en het uiterst advies jaar.

0.25
later_dan_uiterst_bonus int

Hoeveel jaar het uitstel mag worden opgerekt als het kan_later_dan_uiterst veld waar is.

1
bevries_verleden bool

Of in het verleden geplande maatregelen moeten worden vastgezet.

True
max_vervroeging int | None

Maximaal aantal jaar dat een maatregel mag worden vervroegd.

None
max_uitstel int | None

Maximaal aantal jaar dat een maatregel mag worden uitgesteld.

None

Returns:

Type Description
Window

Window met de gevonden begrenzingen.

Note

Het wordt als volgt berekend:

  • Het advies_plan_jaar wordt als initial gebruikt voor het Window.
  • Het regulier uitstel wordt berekend als de maximale waarde van advies_plan_jaar of uiterst_advies_jaar minus het advies_plan_jaar.
  • De maximale vervroeging max_rush wordt als de helft van dit regulier uitstel aangenomen.
  • Het maximale uitstel max_post is het regulier uitstel plus een bonus factor als die mag worden toegekend op basis van kan_later_dan_uiterst.
Source code in src/raplan_rws/shared.py
def window(
    self,
    start: int,
    vervroeging_factor: float = 0.25,
    later_dan_uiterst_bonus: int = 1,
    bevries_verleden: bool = True,
    max_vervroeging: int | None = None,
    max_uitstel: int | None = None,
) -> raplan.Window:
    """Het afgeleide window in de tijd waarin deze maatregel mag worden gepland.

    Arguments:
        start: Startjaar van de planningshorizon. Alle latere jaartallen zijn delta's
            ten opzichte van deze waarde.
        vervroeging_factor: Hoeveel eerder een taak naar voren mag worden gehaald t.o.v. de
            afstand tussen het advies plan jaar en het uiterst advies jaar.
        later_dan_uiterst_bonus: Hoeveel jaar het uitstel mag worden opgerekt als
            het `kan_later_dan_uiterst` veld waar is.
        bevries_verleden: Of in het verleden geplande maatregelen moeten worden vastgezet.
        max_vervroeging: Maximaal aantal jaar dat een maatregel mag worden vervroegd.
        max_uitstel: Maximaal aantal jaar dat een maatregel mag worden uitgesteld.

    Returns:
        Window met de gevonden begrenzingen.

    Note:
        Het wordt als volgt berekend:

        - Het `advies_plan_jaar` wordt als `initial` gebruikt voor het `Window`.
        - Het regulier uitstel wordt berekend als de maximale waarde van `advies_plan_jaar` of
          `uiterst_advies_jaar` minus het `advies_plan_jaar`.
        - De maximale vervroeging `max_rush` wordt als *de helft* van dit regulier uitstel
          aangenomen.
        - Het maximale uitstel `max_post` is het regulier uitstel plus een bonus factor als die
          mag worden toegekend op basis van `kan_later_dan_uiterst`.
    """
    if bevries_verleden and self.gepland_uitvoeringsjaar < 0:
        return raplan.Window.new_immovable(self.gepland_uitvoeringsjaar)

    advies = self.advies_plan_jaar - start

    # Reguliere "laatste" tijd.
    laatst: int = max(self.advies_plan_jaar, self.uiterst_advies_jaar) - start
    uitstel = laatst - advies
    vervroeging = int(vervroeging_factor * uitstel)

    bonus = self.kan_later_dan_uiterst * later_dan_uiterst_bonus
    uitstel = bonus + uitstel

    max_rush: int = (
        vervroeging if max_vervroeging is None else min(max_vervroeging, vervroeging)
    )
    max_post: int = uitstel if max_uitstel is None else min(max_uitstel, uitstel)

    return raplan.Window(
        initial=advies,
        max_rush=max_rush,
        max_post=max_post,
    )

MaatregelPlanning

Geplande maatregelen over de tijd.

maatregelen class-attribute instance-attribute

maatregelen: list[GeplandeMaatregel] = field(
    default_factory=list
)

Lijst van geplande maatregelen.

horizon

horizon() -> Horizon

Leidt een horizon af van de maatregelen in de planning of maak een horizon op alleen dit jaar.

Source code in src/raplan_rws/shared.py
def horizon(self) -> raplan.Horizon:
    """Leidt een horizon af van de maatregelen in de planning of maak een horizon op alleen dit
    jaar.
    """
    if self.maatregelen:
        first = self.maatregelen[0]
        start, end = first.gepland_uitvoeringsjaar, first.gepland_uitvoeringsjaar + 1

        for m in self.maatregelen[1:]:
            start = min(start, m.gepland_uitvoeringsjaar)
            end = max(end, m.gepland_uitvoeringsjaar + 1)
    else:
        start = datetime.today().year
        end = None
    return raplan.Horizon(start, end)

maak_kopie

maak_kopie() -> MaatregelPlanning

Maak een kopie van deze maatregelplanning.

Source code in src/raplan_rws/shared.py
def maak_kopie(self) -> "MaatregelPlanning":
    """Maak een kopie van deze maatregelplanning."""
    return deepcopy(self)

naar_excel_bestand

naar_excel_bestand(
    bestandspad: str | Path | None = None,
) -> Workbook

Convert this to an Excel workbook.

Source code in src/raplan_rws/shared.py
def naar_excel_bestand(self, bestandspad: str | Path | None = None) -> Workbook:
    """Convert this to an Excel workbook."""
    wb = _maak_maatregelplanning_excel(self)
    if bestandspad is not None:
        wb.save(bestandspad)
    return wb

objecten_structuren

objecten_structuren() -> (
    dict[str, tuple[BeheerObject, ObjectStructuur]]
)

Leid een dictionary af van DISK code naar beheerobject-omschrijvingen en hun objectstructuren.

Source code in src/raplan_rws/shared.py
def objecten_structuren(self) -> dict[str, tuple[BeheerObject, ObjectStructuur]]:
    """Leid een dictionary af van DISK code naar beheerobject-omschrijvingen en hun
    objectstructuren.
    """
    result: dict[str, tuple[BeheerObject, ObjectStructuur]] = dict()

    for m in self.maatregelen:
        disk = m.beheerobject.code
        if m.beheerobject.code not in result:
            result[disk] = (m.beheerobject, ObjectStructuur())
        structuur: ObjectStructuur = result[disk][1]
        eb = m.element_bouwdeel
        structuur.element_bouwdelen.add(eb)
        structuur.disciplines[eb].update(m.disciplines)
        structuur.impacts[eb] += m.impact

    return result

pas_horizon_toe

pas_horizon_toe(horizon: Horizon)

Pas een horizon filtering toe op deze planning.

Source code in src/raplan_rws/shared.py
def pas_horizon_toe(self, horizon: raplan.Horizon):
    """Pas een horizon filtering toe op deze planning."""
    self.maatregelen: list[GeplandeMaatregel] = [
        m for m in self.maatregelen if m.valt_binnen_horizon(horizon)
    ]

van_excel_bestand classmethod

van_excel_bestand(
    bestandspad: str | Path,
) -> MaatregelPlanning

Laad een maatregel planning terug in vanaf een Excelbestand.

Source code in src/raplan_rws/shared.py
@classmethod
def van_excel_bestand(cls, bestandspad: str | Path) -> "MaatregelPlanning":
    """Laad een maatregel planning terug in vanaf een Excelbestand."""
    wb: Workbook = openpyxl.load_workbook(bestandspad)
    ws = wb.worksheets[0]
    assert isinstance(ws, Worksheet)

    maatregelen = []

    kolommen = {_resolve_value(wb, c): i for i, c in enumerate(ws[1]) if isinstance(c, Cell)}
    types = {k: v for k, v in gen_dataclass_types(GeplandeMaatregel)}
    discipline_kolommen = {d: f"disciplines_{d.lower()}" for d in Discipline}
    impact_kolommen = {k[7:]: k for k in kolommen if k.startswith("impact_")}
    conversions = {
        k: _CELL_VALUE_CONVERSION.get(v, lambda x: x)
        for k, v in types.items()
        if k not in discipline_kolommen and k not in impact_kolommen
    }

    def extract(
        row: tuple[Cell, ...],
        key: str,
        is_uuid: bool = False,
        is_bool: bool = False,
        default: Any = None,
    ) -> Any:
        try:
            kolom = kolommen[key]
        except KeyError:
            return default

        try:
            cell = row[kolom]
        except IndexError:
            return default

        value = _resolve_value(wb, cell)

        if is_uuid:
            return _decode_uuid(value)

        if is_bool:
            if isinstance(value, bool):
                return value
            elif isinstance(value, str):
                return value.upper().strip() == "TRUE" or value.upper().strip() == "X"
            else:
                return bool(value)

        if not value:
            return default

        try:
            return conversions[key](value)
        except KeyError:
            return default

    for r in range(2, ws.max_row + 1):
        row: tuple[Cell, ...] = ws[r]

        beheerobject = BeheerObject(
            code=extract(row, "beheerobject_code"),
            naam=extract(row, "beheerobject_naam"),
        )

        disciplines = set()
        for d, key in discipline_kolommen.items():
            if extract(row, key, is_bool=True, default=False):
                disciplines.add(d)

        impact = FunctieImpact.geen()
        for i, key in impact_kolommen.items():
            if extract(row, key, is_bool=True, default=False):
                setattr(impact, i, True)

        maatregelen.append(
            GeplandeMaatregel(
                uuid=extract(row, "uuid", is_uuid=True, default=uuid4()),
                referentie_nummer=extract(row, "referentie_nummer"),
                beheerobject=beheerobject,
                element=extract(row, "element"),
                bouwdeel=extract(row, "bouwdeel"),
                pakket_id=extract(row, "pakket_id"),
                maatregel=extract(row, "maatregel"),
                disciplines=disciplines,
                gepland_uitvoeringsjaar=extract(row, "gepland_uitvoeringsjaar", default=0),
                gepland_uitvoeringsjaar_reden=extract(row, "gepland_uitvoeringsjaar_reden"),
                gewenst_uitvoeringsjaar=extract(row, "gewenst_uitvoeringsjaar"),
                advies_plan_jaar=extract(row, "advies_plan_jaar", default=0),
                uiterst_advies_jaar=extract(row, "uiterst_advies_jaar", default=0),
                kan_later_dan_uiterst=extract(
                    row, "kan_later_dan_uiterst", is_bool=True, default=False
                ),
                impact=impact,
                verlies=FunctieVerlies.volledig(impact),
                raming_incl=float(extract(row, "raming_incl", default=0.0)),
                periode=float(extract(row, "periode", default=float("inf"))),
            )
        )

    return MaatregelPlanning(maatregelen)

ObjectStructuur

Structuur van beheerobjecten: hun bouwdelen/elementen.

Attributes:

Name Type Description
element_bouwdelen set[ElementBouwdeel]

Set van element-bouwdelen in een object.

disciplines defaultdict[ElementBouwdeel, set[Discipline]]

Set van herkende disciplines per element-bouwdeel in een object.

impacts defaultdict[ElementBouwdeel, FunctieImpact]

De element-functie mapping voor een object.

maak_kopie

maak_kopie() -> ObjectStructuur

Maak een kopie van deze objectstructuur.

Source code in src/raplan_rws/shared.py
def maak_kopie(self) -> "ObjectStructuur":
    """Maak een kopie van deze objectstructuur."""
    return deepcopy(self)

utils

Various utilities for RWS applications.

extract_comp_code

extract_comp_code(disk: str) -> str

Extract the complex code from a DISK code.

Source code in src/raplan_rws/utils.py
def extract_comp_code(disk: str) -> str:
    """Extract the complex code from a DISK code."""
    parts: list[str] = disk.split("-")
    return "-".join(parts[:2])

extract_disk_code

extract_disk_code(input: str) -> str | None

Attempt to extract a DISK code from a string such as '39B-002-02'.

Source code in src/raplan_rws/utils.py
def extract_disk_code(input: str) -> str | None:
    """Attempt to extract a DISK code from a string such as '39B-002-02'."""
    match = re.search(DISK, input.upper())
    if match is None:
        return None
    groups = match.groups()
    if len(groups) == 2:
        return "-".join(groups)
    else:
        if groups[3] == "XX" or groups[3] is None:
            return "-".join((groups[0], groups[1]))
        else:
            return "-".join((groups[0], groups[1], groups[3]))

is_truey

is_truey(
    input: bool | str | int | None, default: bool = False
) -> bool

Whether this is a True-ish value.

Source code in src/raplan_rws/utils.py
def is_truey(input: bool | str | int | None, default: bool = False) -> bool:
    """Whether this is a True-ish value."""
    if input is None:
        return default

    match input:
        case str():
            input = input.strip().lower()
            if input:
                return "j" in input or "y" in input or "x" in input or input == "true"
            else:
                return default
        case bool():
            return input
        case int():
            return bool(input)

setup_raplan_logging

setup_raplan_logging(
    raplan_level: int = INFO,
    root_level: int = ERROR,
    fmt: str = "%(name)s %(levelname)s: %(message)s",
)

Stel een RaPlan logger in die zowel RaPlan als RaPlan-RWS output laat zien.

Source code in src/raplan_rws/utils.py
def setup_raplan_logging(
    raplan_level: int = logging.INFO,
    root_level: int = logging.ERROR,
    fmt: str = "%(name)s %(levelname)s: %(message)s",
):
    """Stel een RaPlan logger in die zowel RaPlan als RaPlan-RWS output laat zien."""
    # Handler that prints to notebook output
    handler = logging.StreamHandler(sys.stdout)
    handler.setLevel(raplan_level)
    handler.setFormatter(logging.Formatter(fmt))

    # Get the specific logger you want to see
    for log in [raplan_logger, logger]:
        log.setLevel(raplan_level)
        log.handlers = []
        log.addHandler(handler)
        log.propagate = False  # avoid duplicate output via root logger

    logging.getLogger().setLevel(root_level)