From 4cc8efe948885be5c5262cf97fd597df3095242f Mon Sep 17 00:00:00 2001
From: "Hock, Martin" <martin.hock@fst.tu-darmstadt.de>
Date: Thu, 2 Mar 2023 06:19:40 +0100
Subject: [PATCH] Finish "minimal example".

---
 ausarbeitung.ipynb | 310 +++++++++++++++++++++++++++++++++++----------
 1 file changed, 245 insertions(+), 65 deletions(-)

diff --git a/ausarbeitung.ipynb b/ausarbeitung.ipynb
index 264e2c7..b3e1f05 100644
--- a/ausarbeitung.ipynb
+++ b/ausarbeitung.ipynb
@@ -37,11 +37,22 @@
     "## Eigene Module und Minimalbeispiel\n",
     "Für die Ausarbeitung nutzen wir zwei eigene Module (Python-Dateien), die im Ordner `functions` gespeichert sind.\n",
     "Das Modul `calculation_rules` erweitern Sie während der Ausarbeitung. Um die Änderungen zu nutzen, müssen Sie das Notebook neu starten.\n",
-    "Im Modul `classes` befinden sich die  Klassen `LegoComponent, LegoAssembly, AggergationLayer, KPIEncoder` und die Funktion `print_assembly_tree`.\n",
-    "`LegoComponent` bildet einzelne Komponenten ab, während `LegoAssembly` zusammengesetzte Aggregationsebenen abdeckt, also Bauteil, Baugruppe und System. Zur Unterscheidung dient die Klasse Aggregationslayer, diese ist für `LegoComponent` immer `Component`, muss für `LegoAssembly` aber entsprechend auf `SYSTEM, ASSEMBLY` oder `SUBASSEMBLY` gesetzt werden.\n",
+    "Im Modul `classes` befinden sich die komplette Klassen und Funktionen zur Verwendung.\n",
     "\n",
+    "Mit einem Minimalbeispiel wird Ihnen gezeigt, wie sie die Module nutzen. "
+   ]
+  },
+  {
+   "attachments": {},
+   "cell_type": "markdown",
+   "id": "6d3310be",
+   "metadata": {},
+   "source": [
+    "### Modul classes\n",
+    "`LegoComponent, LegoAssembly, AggergationLayer, KPIEncoder` und die Funktion `print_assembly_tree`.\n",
+    "`LegoComponent` bildet einzelne Komponenten ab, während `LegoAssembly` zusammengesetzte Aggregationsebenen abdeckt, also Bauteil, Baugruppe und System. Zur Unterscheidung dient die Klasse Aggregationslayer, diese ist für `LegoComponent` immer `Component`, muss für `LegoAssembly`  entsprechend auf `SYSTEM, ASSEMBLY` oder `SUBASSEMBLY` gesetzt werden.\n",
     "\n",
-    "Mit einem Minimalbeispiel wird Ihnen gezeigt, wie sie die Module nutzen. Dabei wird nur aus Achse, Rahmen und Reifen ein Tretroller gebaut."
+    "Wir bauen aus Achse, Rahmen und Reifen einen Tretroller zusammen."
    ]
   },
   {
@@ -49,33 +60,48 @@
    "execution_count": 1,
    "id": "b2778dee",
    "metadata": {},
+   "outputs": [],
+   "source": [
+    "# import modules \n",
+    "import json\n",
+    "import pprint\n",
+    "from functions import calculation_rules\n",
+    "\n",
+    "# Importing all modules one by one to provide an overview\n",
+    "# The next commented line would provide the same result in one line\n",
+    "# from functions.classes import *\n",
+    "from functions.classes import LegoComponent\n",
+    "from functions.classes import LegoAssembly\n",
+    "from functions.classes import AggregationLayer\n",
+    "from functions.classes import KPIEncoder\n",
+    "from functions.classes import print_assembly_tree"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "id": "0b1f9aff",
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "{'item number.1': 32073, 'item description': 'Axle 5 studs', 'category': 'axle', 'price [Euro]': 0.001, 'mass [g]': 0.66, 'delivery time [days]': 3, 'Abmessung [studs]': 5}\n"
-     ]
-    },
-    {
-     "ename": "KeyError",
-     "evalue": "'name'",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[1;31mKeyError\u001b[0m                                  Traceback (most recent call last)",
-      "Cell \u001b[1;32mIn[1], line 18\u001b[0m\n\u001b[0;32m     16\u001b[0m front_axle \u001b[39m=\u001b[39m classes\u001b[39m.\u001b[39mLegoComponent(\u001b[39m'\u001b[39m\u001b[39mfront axle\u001b[39m\u001b[39m'\u001b[39m,axles[\u001b[39m'\u001b[39m\u001b[39m32073\u001b[39m\u001b[39m'\u001b[39m])\n\u001b[0;32m     17\u001b[0m \u001b[39m# Both name and the data dict are optional parameters. You can view, add or edit the properties just any dict:\u001b[39;00m\n\u001b[1;32m---> 18\u001b[0m \u001b[39mprint\u001b[39m(front_axle\u001b[39m.\u001b[39;49mproperties[\u001b[39m'\u001b[39;49m\u001b[39mname\u001b[39;49m\u001b[39m'\u001b[39;49m])\n\u001b[0;32m     19\u001b[0m front_axle\u001b[39m.\u001b[39mproperties[\u001b[39m'\u001b[39m\u001b[39mcolor\u001b[39m\u001b[39m'\u001b[39m]\u001b[39m=\u001b[39m \u001b[39m'\u001b[39m\u001b[39mgrey\u001b[39m\u001b[39m'\u001b[39m\n\u001b[0;32m     20\u001b[0m \u001b[39m# Viewing dicts in one line is not easy to read, a better output comes with pretty print (pprint): \u001b[39;00m\n",
-      "\u001b[1;31mKeyError\u001b[0m: 'name'"
+      "{'item number': 32073, 'item description': 'Axle 5 studs', 'category': 'axle', 'price [Euro]': 0.001, 'mass [g]': 0.66, 'delivery time [days]': 3, 'Abmessung [studs]': 5}\n",
+      "front axle\n",
+      "{'Abmessung [studs]': 5,\n",
+      " 'category': 'axle',\n",
+      " 'color': 'grey',\n",
+      " 'delivery time [days]': 3,\n",
+      " 'item description': 'Axle 5 studs',\n",
+      " 'item number': 32073,\n",
+      " 'label': 'back axle',\n",
+      " 'mass [g]': 0.66,\n",
+      " 'price [Euro]': 0.001}\n"
      ]
     }
    ],
    "source": [
-    "# import modules\n",
-    "import json\n",
-    "import pprint\n",
-    "from functions import calculation_rules\n",
-    "from functions import classes\n",
-    "\n",
     "## ## Create the wheels and axles as single components first\n",
     "# Look up the specific item you want from the provided json files, we can load the full file into a dict\n",
     "with open('datasheets/axles.json') as json_file:\n",
@@ -83,26 +109,27 @@
     "# Pick a specific axle via its 'item number'\n",
     "print(axles['32073'])\n",
     "\n",
-    "\n",
     "# Create the component with the dict:\n",
-    "front_axle = classes.LegoComponent('front axle',axles['32073'])\n",
+    "front_axle = LegoComponent('front axle',axles['32073'])\n",
     "# Both label and the data dict are optional parameters. You can view, add or edit the properties just any dict:\n",
     "print(front_axle.properties['label'])\n",
     "front_axle.properties['color']= 'grey'\n",
-    "# Viewing dicts in one line is not easy to read, a better output comes with pretty print (pprint): \n",
-    "pprint.pprint(front_axle.properties)\n",
+    "\n",
     "\n",
     "# Create the second axle\n",
-    "back_axle = classes.LegoComponent()\n",
+    "back_axle = LegoComponent()\n",
     "back_axle.properties['label'] = 'back axle'\n",
     "back_axle.properties.update(axles['32073']) \n",
     "# Do not use = here, otherwise you'd overwrite existing properties.\n",
-    "back_axle.properties['color'] = 'grey'\n"
+    "# Instead use update to have all entries added to properties\n",
+    "back_axle.properties['color'] = 'grey'\n",
+    "# Viewing dicts in one line is not easy to read, a better output comes with pretty print (pprint): \n",
+    "pprint.pprint(back_axle.properties)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 3,
    "id": "b4a8e8c8",
    "metadata": {},
    "outputs": [
@@ -115,9 +142,9 @@
       " 'delivery time [days]': 5,\n",
       " 'diameter [mm]': 81.6,\n",
       " 'item description': 'wheel 81,6',\n",
-      " 'item number.1': 2903,\n",
+      " 'item number': 2903,\n",
+      " 'label': 'front wheel',\n",
       " 'mass [g]': 30.0,\n",
-      " 'name': 'front wheel',\n",
       " 'paint': 'glossy',\n",
       " 'price [Euro]': 1.31,\n",
       " 'related items': 2902,\n",
@@ -126,91 +153,244 @@
     }
    ],
    "source": [
-    "\n",
     "# Now wheels\n",
     "with open('datasheets/wheels.json') as json_file:\n",
     "    wheels = json.load(json_file)\n",
     "# Adding the color here already as dict, and the surface as key-value argument.\n",
-    "# Multiple of both are supported, but only in this \n",
-    "#front_wheel = classes.LegoComponent('front wheel', wheels['2903'],{'color':'yellow'},winter='true')\n",
+    "# Multiple of both parameters are supported, but only in this order.\n",
+    "#front_wheel = LegoComponent('front wheel', wheels['2903'],{'color':'yellow'},winter='true')\n",
     "\n",
-    "front_wheel = classes.LegoComponent('front wheel', wheels['2903'],surface='rough', paint = 'glossy')\n",
-    "# front_\n",
+    "front_wheel = LegoComponent('front wheel', wheels['2903'],surface='rough', paint = 'glossy')\n",
     "pprint.pprint(front_wheel.properties)\n",
     "\n",
-    "# We included a clone function, so you can easily create multiple items:\n",
-    "back_wheel = front_wheel.clone()\n",
+    "# We included a clone function to both Lego classes, so you can easily create duplicate objects:\n",
+    "back_wheel = front_wheel.clone('back wheel') # Passing the new label is optional"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "id": "25bd06c5",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "front axle\n"
+     ]
+    }
+   ],
+   "source": [
+    "## Create subassemblies and add the wheels and axles\n",
     "\n",
+    "# The AggregationLayer must be used, passing the label, or additional properties is optional\n",
+    "front_wheel_assembly = LegoAssembly(AggregationLayer.SUBASSEMBLY, 'front wheel assembly', \n",
+    "                                    assembly_method='stick together like lego blocks')\n",
+    "# Add LegoComponents to the LegoAssembly, single or as list\n",
+    "front_wheel_assembly.add([front_wheel,front_axle])\n",
+    "# You can access the components of an assembly like this:\n",
+    "print(front_wheel_assembly.components[1].properties['label'])\n",
     "\n",
+    "# Assemblies can be cloned as well (including their children), but don't forget to adjust \n",
+    "# labels or you might be stuck with a 'front wheel' in your 'back wheel assembly'\n",
     "\n",
-    "\n"
+    "# Stick together back wheel parts\n",
+    "back_wheel_assembly = LegoAssembly(AggregationLayer.SUBASSEMBLY, 'back wheel assembly')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
-   "id": "bd3c7a6b",
+   "execution_count": 5,
+   "id": "2b6648e1",
    "metadata": {},
    "outputs": [],
    "source": [
+    "## Create frame component and assemble the system\n",
     "\n",
+    "with open('datasheets/frame.json') as json_file:\n",
+    "    frame = json.load(json_file)\n",
+    "scooter_frame = LegoComponent('scooter frame', frame['3702'], {'color' : 'red'})\n",
     "\n",
+    "# The scooter is our system level assembly, you can choose the AggregationLayer\n",
+    "# But the hiercarchy (SYSTEM > ASSEMBLY > SUBASSEMBLY) must be in order.\n",
+    "# Components can be added to all LegoAssembly objects\n",
     "\n",
+    "scooter = LegoAssembly(AggregationLayer.SYSTEM,'scooter', manufacturer='FST', comment='Faster! Harder! Scooter!')\n",
+    "# add frame and subassemblies\n",
+    "scooter.add([scooter_frame, front_wheel_assembly, back_wheel_assembly])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "id": "71324895",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "LegoAssembly faster, harder, scooter [faaf8065-7a4b-4d5b-8ede-72f051cca87f]\n",
+      "├── LegoAssembly front wheel assembly [97a43799-b91b-49b5-8d78-38bb72257931]\n",
+      "│   ├── LegoComponent front wheel [9cfcfc76-7ce1-4066-a8ab-ebf30f59683d]\n",
+      "│   └── LegoComponent front axle [010ba850-ca1b-41e2-90b9-13a3528c49e8]\n",
+      "├── LegoAssembly back wheel assembly [82f000db-da62-44bd-993d-b8042baf03d3]\n",
+      "└── LegoComponent scooter frame [87489434-ba11-42d2-91b2-bbf9735b7091]\n"
+     ]
+    }
+   ],
+   "source": [
+    "## Look at the assembly\n",
     "\n",
-    "\n",
-    "\n",
-    "\n",
-    "# Also need a frame\n",
-    "with open('datasheets/Gestell.json') as json_file:\n",
-    "    Gestelle = json.load(json_file)\n",
-    "Gestellbeschreibung = \"Technic, Brick 1 x 8 with Holes\"\n",
-    "Gestell = Gestelle[Gestellbeschreibung]"
+    "# We can get all LegoComponents from this assembly. \n",
+    "# Without parameter 'max_depth' only direct children will be listed.\n",
+    "scooter.get_component_list(5)"
    ]
   },
   {
+   "attachments": {},
    "cell_type": "markdown",
-   "id": "3b69752c",
+   "id": "001f1c77",
    "metadata": {},
    "source": [
-    "### Modul calculation_results\n",
-    "Sie können die unterschiedlichen Funktionen (Berechnungsvorschriften) aufrufen. Beachten Sie dabei die Übergabe- und Rückgabewerte."
+    "### Modul calculation_rules\n",
+    "\n",
+    "Um für unser System \"Tretroller\" ein KPI  für das Gesamtgewicht zu erzeugen, wurde in `functions.calculation_rules` die Funktion `kpi_sum` definiert. Zusammen mit den Hilfsfunktionen der Klasse können wir nun das KPI Gewicht für das System hinzufügen. Die Massen der einzelnen Komponenten sind in den Datenblättern unter `mass [g]` enthalten."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
-   "id": "294c680b",
+   "execution_count": 21,
+   "id": "7b60d0fb",
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "You called the test function.\n"
+     ]
+    }
+   ],
    "source": [
     "# test the import\n",
     "calculation_rules.test_function()"
    ]
   },
   {
-   "cell_type": "markdown",
-   "id": "a2c87a3c",
+   "cell_type": "code",
+   "execution_count": 23,
+   "id": "fe4edad6",
    "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Gesamtgewicht:  33.51 g\n"
+     ]
+    }
+   ],
    "source": [
-    "### Modul classes\n",
-    "Wir bauen ein Auto zusammen."
+    "## Add mass to assemblies\n",
+    "# List all components' mass\n",
+    "combined_weight = 0\n",
+    "for c in scooter.get_component_list(100):\n",
+    "    combined_weight += c.properties['mass [g]']\n",
+    "print(\"Gesamtgewicht: \",combined_weight,\"g\")\n",
+    "# Add KPI to system\n",
+    "scooter.properties['mass [g]'] = combined_weight\n",
+    "\n",
+    "# We can also add the mass to the subassemblies\n",
+    "# children() returns a dict with a list added parts\n",
+    "for a in scooter.children()['assemblies']:\n",
+    "    a_mass = 0\n",
+    "    for c in a.get_component_list(99):\n",
+    "        a_mass += c.properties['mass [g]']\n",
+    "    a.properties['mass [g]'] = a_mass\n",
+    "    \n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
-   "id": "8db386db",
+   "execution_count": 15,
+   "id": "4d56419f",
    "metadata": {},
-   "outputs": [],
-   "source": []
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "LegoAssembly faster, harder, scooter [faaf8065-7a4b-4d5b-8ede-72f051cca87f]\n",
+      "├── LegoAssembly front wheel assembly [97a43799-b91b-49b5-8d78-38bb72257931]\n",
+      "│   ├── LegoComponent front wheel [9cfcfc76-7ce1-4066-a8ab-ebf30f59683d]\n",
+      "│   └── LegoComponent front axle [010ba850-ca1b-41e2-90b9-13a3528c49e8]\n",
+      "├── LegoAssembly back wheel assembly [82f000db-da62-44bd-993d-b8042baf03d3]\n",
+      "└── LegoComponent scooter frame [87489434-ba11-42d2-91b2-bbf9735b7091]\n",
+      "LegoAssembly front wheel assembly [97a43799-b91b-49b5-8d78-38bb72257931]\n",
+      "LegoAssembly faster, harder, scooter [faaf8065-7a4b-4d5b-8ede-72f051cca87f]\n"
+     ]
+    }
+   ],
+   "source": [
+    "## Look at the full assembly with KPI\n",
+    "# Print the full assembly with all levels\n",
+    "print_assembly_tree(scooter)"
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
-   "id": "8f2ae9f4",
+   "execution_count": 27,
    "metadata": {},
    "outputs": [],
-   "source": []
+   "source": [
+    "# Dump to json file with to_dict() and KPIEncoder\n",
+    "with open(\"scooter.json\", \"w\") as fp:\n",
+    "    json.dump(scooter.to_dict(), fp, cls=KPIEncoder)\n",
+    "# full view is too big for Juypter (try it)\n",
+    "# pprint.pprint(scooter.to_dict())"
+   ]
+  },
+  {
+   "attachments": {},
+   "cell_type": "markdown",
+   "id": "53793ae8",
+   "metadata": {},
+   "source": [
+    "In dieser exportierten json-Datei ('scooter.json') lassen sind die Werte maschinen- und menschenlesbar hinterlegt.\n",
+    "Zusammen mit der Berechnungsvorschrift in `calculation_rules` ist auch die Entstehung des KPI nachvollziehbar und wiederverwendbar dokumentiert und damit 'FAIR'."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "id": "b91fed73",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Child: LegoAssembly front wheel assembly [97a43799-b91b-49b5-8d78-38bb72257931]\n",
+      "Parent: LegoAssembly faster, harder, scooter [faaf8065-7a4b-4d5b-8ede-72f051cca87f]\n",
+      "Top parent:  LegoAssembly faster, harder, scooter [faaf8065-7a4b-4d5b-8ede-72f051cca87f]\n"
+     ]
+    }
+   ],
+   "source": [
+    "## Additional details\n",
+    "# Each child knows its parent\n",
+    "first_child = scooter.children()['assemblies'][0]\n",
+    "print(\"Child:\", first_child)\n",
+    "print(\"Parent:\", first_child.parent)\n",
+    "# Also we can access the \"top\" parent\n",
+    "latest_child = first_child.children()['components'][0]\n",
+    "print(\"Top parent: \",latest_child.get_root_assembly())\n",
+    "\n",
+    "# Each part created has a unique identifier (the long number in [])\n",
+    "# Don't try add the identical part again."
+   ]
   },
   {
    "cell_type": "markdown",
-- 
GitLab