From b15e4bf646083d16c4f88395d0ace69a9bded514 Mon Sep 17 00:00:00 2001
From: "Hock, Martin" <martin.hock@fst.tu-darmstadt.de>
Date: Mon, 27 Feb 2023 07:08:23 +0100
Subject: [PATCH] Add creation items from datasheets and simple kpi calculation

---
 datasheets/create_json_from_excel.py |  2 +
 functions/calculation_rules.py       | 17 ++++--
 functions/classes.py                 | 72 ++++++++++++++++------
 test-classes.py                      | 91 +++++++++++++++++++++++++++-
 4 files changed, 157 insertions(+), 25 deletions(-)

diff --git a/datasheets/create_json_from_excel.py b/datasheets/create_json_from_excel.py
index 4cdd8cf..2626950 100644
--- a/datasheets/create_json_from_excel.py
+++ b/datasheets/create_json_from_excel.py
@@ -5,6 +5,8 @@ from collections import namedtuple
 from typing import Dict, List, NamedTuple
 
 import pandas as pd
+# pandas is using another dependency called openpyxl
+# both need to be installed
 
 
 COLUMN_FOR_INDEX_IN_EXCEL = 0
diff --git a/functions/calculation_rules.py b/functions/calculation_rules.py
index e9d20f1..7fa7396 100644
--- a/functions/calculation_rules.py
+++ b/functions/calculation_rules.py
@@ -1,10 +1,19 @@
-'''
+"""
 File consists of several functions for the calculation rules of FAIR Quality KPIs
-'''
+"""
+
+
 def test_function():
     print("You called the test function.")
 
 
+def kpi_sum(*args):
+    return sum(args[0])
+    # if arguments are handed over not as a list: sum(list(args))
+
+
 if __name__ == "__main__":
-    print("This script contains functions for calculating the FAIR Quality KPIs. It is not to be executed independently.")
-    pass
\ No newline at end of file
+    print(
+        "This script contains functions for calculating the FAIR Quality KPIs. It is not to be executed independently."
+    )
+    pass
diff --git a/functions/classes.py b/functions/classes.py
index f8d81c6..8e9a803 100644
--- a/functions/classes.py
+++ b/functions/classes.py
@@ -1,10 +1,10 @@
-'''
+"""
 File consists of several classes for the different elements of a device.
-'''
+"""
 from __future__ import annotations
 from enum import Enum, auto
 import uuid
-from typing import Any, Union, Literal, TypedDict, TypeVar, Type, List, Optional, Dict
+from typing import List, Dict
 import json
 
 
@@ -14,7 +14,8 @@ import json
 # - Gute String Darstellung
 # - Minimalbeispiel für KPIs
 # - Export als GraphViz
-
+# - Erlaube Listen bei add_component und add_assembly ?
+# - Zukunft: Erlaube Clone bei Assembly (jede Component muss durch Klon ersetzt werden)
 
 class ComponentCategory(Enum):
     BATTERY = auto()
@@ -33,8 +34,17 @@ class AggregationLayer(Enum):
 
 
 class LegoComponent:
-    def __init__(self, name: str, category: ComponentCategory, lego_id: str, cost: float, mass: float,
-                 delivery_time: int, layer: AggregationLayer = AggregationLayer.COMPONENT, **properties) -> None:
+    def __init__(
+        self,
+        name: str,
+        category: ComponentCategory,
+        lego_id: str,
+        cost: float,
+        mass: float,
+        delivery_time: int,
+        layer: AggregationLayer = AggregationLayer.COMPONENT,
+        **properties,
+    ) -> None:
         self.uuid: uuid.UUID = uuid.uuid4()
         self.parent: None | LegoAssembly = None
         self.name: str = name
@@ -47,8 +57,16 @@ class LegoComponent:
         self.properties: dict = properties
 
     def clone(self) -> LegoComponent:
-        clone = LegoComponent(self.name, self.category, self.lego_id, self.cost, self.mass, self.delivery_time,
-                              self.layer, **self.properties)
+        clone = LegoComponent(
+            self.name,
+            self.category,
+            self.lego_id,
+            self.cost,
+            self.mass,
+            self.delivery_time,
+            self.layer,
+            **self.properties,
+        )
         return clone
 
     def get_root_assembly(self):
@@ -60,7 +78,17 @@ class LegoComponent:
         return current_assembly
 
     def to_dict(self) -> Dict:
-        ATTRIBUTES = ["uuid", "name", "category", "lego_id", "cost", "mass", "delivery_time", "layer", "properties"]
+        ATTRIBUTES = [
+            "uuid",
+            "name",
+            "category",
+            "lego_id",
+            "cost",
+            "mass",
+            "delivery_time",
+            "layer",
+            "properties",
+        ]
         dict_ = {}
         # store attributes
         for attr in ATTRIBUTES:
@@ -95,26 +123,34 @@ class LegoAssembly:
 
     def add_component(self, component: LegoComponent) -> None:
         if not isinstance(component, LegoComponent):
-            raise TypeError(f"Argument should be of type {LegoComponent.__name__}, "
-                            f"got {type(component).__name__} instead.")
+            raise TypeError(
+                f"Argument should be of type {LegoComponent.__name__}, "
+                f"got {type(component).__name__} instead."
+            )
         if self.get_root_assembly().contains_uuid(component.uuid):
-            raise AssertionError(f"This assembly or a subassembly already contains the component with ID "
-                                 f"{component.uuid}.")
+            raise AssertionError(
+                f"This assembly or a subassembly already contains the component with ID "
+                f"{component.uuid}."
+            )
         component.parent = self
         self.components.append(component)
 
     def add_assembly(self, assembly: LegoAssembly) -> None:
         if not isinstance(assembly, LegoAssembly):
-            raise TypeError(f"Argument should be of type {LegoAssembly.__name__}, "
-                            f"got {type(assembly).__name__} instead.")
+            raise TypeError(
+                f"Argument should be of type {LegoAssembly.__name__}, "
+                f"got {type(assembly).__name__} instead."
+            )
         if self.get_root_assembly().contains_uuid(assembly.uuid):
-            raise AssertionError(f"This assembly or a subassembly already contains the assembly with ID "
-                                 f"{assembly.uuid}.")
+            raise AssertionError(
+                f"This assembly or a subassembly already contains the assembly with ID "
+                f"{assembly.uuid}."
+            )
         assembly.parent = self
         self.assemblies.append(assembly)
 
     def children(self) -> Dict[str, List[LegoComponent] | List[LegoAssembly]]:
-        return {'components': self.components, 'assemblies': self.assemblies}
+        return {"components": self.components, "assemblies": self.assemblies}
 
     def get_component_list(self, max_depth: int = -1) -> List[LegoComponent]:
         component_list = []
diff --git a/test-classes.py b/test-classes.py
index d1282ed..0fbc4be 100644
--- a/test-classes.py
+++ b/test-classes.py
@@ -1,6 +1,7 @@
 # import standard libraries
 import json
 import pprint
+
 # import classes from the classes module in functions package
 from functions.classes import LegoComponent
 from functions.classes import LegoAssembly
@@ -9,22 +10,24 @@ from functions.classes import AggregationLayer
 from functions.classes import KPIEncoder
 from functions.classes import print_assembly_tree
 
-
-# Test manually creating some item and components
+from functions.calculation_rules import kpi_sum
+# Test manually creating some assemblies and components
 
 battery = LegoComponent("nice battery", ComponentCategory.BATTERY, "bat42", 1, 2, 3)
 motor = LegoComponent("motor goes brrr", ComponentCategory.MOTOR, "motor", 1, 2, 3)
 wheel = LegoComponent("much round wheel", ComponentCategory.WHEEL, "round", 1, 2, 3)
 
-car = LegoAssembly("Car", AggregationLayer.SYSTEM)
 
+# Create subassemblies and combine to assembly (currently no parts present for this)
 chassis = LegoAssembly("Chassis", AggregationLayer.ASSEMBLY)
 door1 = LegoAssembly("Door 1", AggregationLayer.SUBASSEMBLY)
 door2 = LegoAssembly("Door 2", AggregationLayer.SUBASSEMBLY)
 chassis.add_assembly(door1)
 chassis.add_assembly(door2)
+chassis.properties
 
 engine = LegoAssembly("Engine", AggregationLayer.ASSEMBLY)
+# Showcase cloning - one motor for each axis?
 engine.add_component(motor.clone())
 
 
@@ -36,11 +39,17 @@ wheels = LegoAssembly("Wheels", AggregationLayer.ASSEMBLY)
 for _ in range(4):
     wheels.add_component(wheel.clone())
 
+
+# Create and assemble the system level car
+car = LegoAssembly("Car", AggregationLayer.SYSTEM)
 car.add_assembly(chassis)
 car.add_assembly(engine)
 car.add_assembly(fuel_tank)
 car.add_assembly(wheels)
 
+
+## Printing
+
 print_assembly_tree(car)
 
 # Dump to json file
@@ -49,3 +58,79 @@ with open("car.json", "w") as fp:
 
 pprint.pprint(car.to_dict())
 pass
+
+
+## Create components from datasheet
+# Need to run the script from datasheets folder first
+# Test loading dicts from datasheet jsons
+
+with open('datasheets/Achsen.json') as json_file:
+    Achsen = json.load(json_file)
+
+print((Achsen["Achse 5 studs"]))
+
+# This is hard to read, prettier please!
+
+pprint.pprint(Achsen)
+
+# Okay I'll use the 5 stud one
+Achse5 = Achsen["Achse 5 studs"] # I'd prefer to call it via "Designnummer"
+print(Achsen["Achse 5 studs"]["Designnummer"])
+
+with open('datasheets/Räder.json') as json_file:
+    Raeder = json.load(json_file)
+# Get me the expensive one
+Radpreis = []
+for key in Raeder: # a list would work easier here than a dict
+    Radpreis.append(Raeder[key]["Preis [Euro]"])
+Radpreis = max(Radpreis)
+for key in Raeder:
+    if Raeder[key]["Preis [Euro]"] == Radpreis:
+        TeuresRad=Raeder[key]
+
+# Also need a frame
+with open('datasheets/Gestell.json') as json_file:
+    Gestelle = json.load(json_file)
+Gestellbeschreibung = "Technic, Brick 1 x 8 with Holes"
+Gestell = Gestelle[Gestellbeschreibung]
+
+# Create Components from these items
+# LegoComponent(name: str, category: ComponentCategory, lego_id: str, cost: float,
+# mass: float, delivery_time: int, layer: AggregationLayer = AggregationLayer.COMPONENT, **properties)
+scooterframe = LegoComponent("running board", ComponentCategory.FRAME,
+                            Gestell["Designnummer"],
+                            Gestell["Preis [Euro]"], Gestell["Gewicht [g]"],
+                            0)
+
+scooterwheel = LegoComponent("Vorderrad", ComponentCategory.WHEEL,
+                            TeuresRad["Designnummer"],
+                            TeuresRad["Preis [Euro]"], TeuresRad["Gewicht [g]"],
+                            0)
+# Cloning is necessary because each lego object gets a unique id so you can't use the same part twice
+scooterwheel2 = scooterwheel.clone()
+scooterwheel2.name = "Hinterrad"
+print([scooterwheel.uuid, scooterwheel2.uuid])
+
+
+# Lets Assembly, first the Assembly object, directly add the frame?
+scooter = LegoAssembly("my first scooter", AggregationLayer.SYSTEM)
+scooter.add_component(scooterframe) # only one per call for now
+scooter.add_component(scooterwheel)
+scooter.add_component(scooterwheel2)
+# <Assembly>.add_assembly() works in the same way
+
+# Look at our work:
+print(scooter)
+print(scooter.children())
+
+## First KPI mass should be a sum
+listofmasses= []
+for c in scooter.components:
+    listofmasses.append(c.mass)
+for a in scooter.assemblies:
+    # only checking one layer deep here
+    listofmasses.append(a.mass)
+print(listofmasses)
+print(kpi_sum(listofmasses))
+
+print("theend")
\ No newline at end of file
-- 
GitLab