Category Archives: hacks

hacks

Project visualization with redesigned Gantt charts

When reading the Edward Tufte forum on Project Management Graphics I was surprised (and relieved) that a lot of people seem to have their problems with Gantt charts and their doubtful usefulness for project management.

After some thoughts on the topic I’d like to present my proposal for visualization of project flows. In case it matters: I’m currently part of a half a billion Euro construction project in Austria.

The real world example

The image below shows one Gantt chart of the project I’m currently involved in.

dfgh

Actual Gantt chart of “my” current project.

Aside from its professional look it transports surprisingly little content. My main points of criticism are:

  • On the one hand, with more than a hundred tasks on the left I get overwhelmed by things I’m not responsible for anyway.
  • On the other, my 15 tasks lack important detail.
  • Since it’s not possible to combine overview with sub-project specific detail I have to manage my tasks in parallel.
  • I easily lose track between tasks and the bars on the far right. Gridlines would be a bad idea as well since they add too much clutter.
  • Data is sparse and the chart needs too much space for the presentation thereof.
  • In my context of software development the chart’s implicit focus on sequential, waterfall-like project flows does not fit my reality of ever-changing project plans.
  • I definitely need to print it out to be able to use it.

What would I need?

First of all: Everybody can read a Gantt chart so let’s stick to some reasonable habits: bars that encode project duration by length and diamond symbols that mean milestones, i.e. important dates.

When I think of project management, the whole story is about one core task: Align your output and handover dates with those of others.

As a member of any project team I simply cannot be interested in the task of each individual. However the Gantt chart’s focus lies on the presentation of single tasks. I state that any visualization for project management should focus on people or teams, i.e. responsibilities, rather than tasks.

My second point revolves around overview and detail: My own tasks should be presented in greater detail than data of other teams. My only concern with them is deliverables when we approach mutual handover dates.

The Redesign

Here’s some example data taken from one of my schedules:

Responsible Task Start End
Construction Define location Sun Jan 01 2012 Sun Apr 15 2012
Construction Finish site Thu Aug 01 2013 Milestone
IT Develop prototype Sun Jan 15 2012 Sat Jun 02 2012
IT Testing Sun Jun 03 2012 Sun Jul 01 2012
Business Process Give feedback Sun Jun 03 2012 Sun Jul 01 2012
Supplier Deliver parts (prototype) Thu Mar 01 2012 Sun Aug 05 2012
IT Update integration Sun Jul 01 2012 Tue Aug 14 2012
IT Evaluate prototype (hands-on) Wed Aug 15 2012 Mon Oct 01 2012
IT Evaluate prototype (remote monitoring) Tue Oct 02 2012 Wed May 01 2013
IT Production Mon Aug 19 2013 Wed Jan 01 2014
Business Process Launch prototype environment Wed Aug 15 2012 Milestone
Business Process Go live Mon Aug 19 2013 Milestone
Procurement Procure Mon Apr 01 2013 Wed May 01 2013
Supplier Deliver parts (production) Wed May 01 2013 Mon Aug 12 2013

Classic Gantt chart

And here goes the corresponding Gantt chart:

srg

Gantt chart encoding the tabular data above. (Written in JavaScript).

My version, aka: Gantt90

My enhanced version is shown below. Instead of single tasks I only show the responsible team. Additionally I rotate by 90 degrees – thus Gantt90 – to shift the attention from process flow to responsibility, from a mechanical point of view to a more social one if you want to put it like this.

While the information conveyed is almost equal, the charts size is halved. Hence even the thumbnail becomes readable.

dsf

Proposed redesign of a Gantt chart: teams instead of tasks and rotation by 90 degrees.

Adding detail

Since I’m a member of IT, the two blue bars on the right should now convey more information than the others. I do that by highlighting my team and showing the specific tasks along the time axis. Enough detail for the team, and I manage to simultaneously keep track of other teams’ schedules.

sd

Overview and detail combined. The chart could even be made recursive with sub-teams of IT on the right. (…)

Thinking of my big example from the beginning it will be necessary to further divide the detail view on the right. Since projects – tasks as well as teams responsible – fit into hierarchical, tree-like data structures my chart would recursively expand to the right. (IT would then be divided into developers, testers, administrators, etc. and the new chart would resemble its left neighbour – both aligned with the common time axis.)

What about connecting lines?

You may have noticed that I skipped the connecting lines between the taks. I’m convinced that a proper visualization should signalize the project’s inherent structure on its own. Even if the lines are not there, you can see the dependencies between tasks and/or milestones anyway. Your brain does quite a good job at pattern recognition.

tldr: Like spreadsheets, Gantt charts are used to visualize business fiction rather than to manage projects. I’ve proposed a modified Gantt chart to better visualize projects by drawing attention to responsibility rather to single tasks.

Thanks to Elena Reitman’s tweet I stumbled upon the Tufte forum.

I highly recommend the book Envisioning information by Edward Tufte. The charts and the source code used can be found here.

hacks

Campus-Tetris revisited

Ich habe bereits über die Raumbuchung am Campus WU und eine damit verbundene Visualisierung der Belegung gebloggt. Hier in aller Kürze eine Visualisierung der Nachfrage nach Räumen.

Das Raumbuchungstool des Campus WU ist darauf ausgelegt, dass Mitarbeiter_innen wie Studierende – also etwa 30.000 Personen – selbständig und unbürokratisch Räume buchen können. Das dadurch drohende Chaos eines heillos belegten Gebäudes verhindern wir durch mehrere Parameter, wie etwa schrittweises Freigeben von Ressourcen, Speichern von Bedarfen in Raumgruppen anstatt in spezifischen Räumen, automatisierte Allokation, oder individuelle Berechtigungen. (Viele der hier genannten Dinge haben wir uns übrigens bei der Konkurrenz simulieren lassen.)

Anyway – wir sind nun seit genau einer Woche online und haben rund 8.000 Termine und somit bereits die Hälfte des kommenden Wintersemesters eingesammelt. Die Frage liegt also nahe, welche Räume (Raumgruppen) stark nachgefragt werden, und – am allerwichtigsten – wo etwaige Konflikte auftreten. Nach ein paar Stunden Python und Javascript kam dann mein Erfolgserlebnis, das ich auch gleich auf Twitter verewigen musste.

Programmieren mit d3.js (Data Driven Documents) macht also Spaß.

  d3.select("#applications")
    .append("g") 
      .attr("id", "application-frame")
    .selectAll("rect.application_frame")
    .data(data.slots)
    .enter()
    .append("rect")
      .attr("class", "application_frame")
      .attr("x", function(d) { return linear_scale(time_to_float(d.start) ) })
      .attr("y", function(d) { return POOL_POS[d.pool_id].y } )
      .attr("height", function(d) { return POOL_POS[d.pool_id].height } )
      .style("fill", function(d) { return occupancy_to_color(d.occupancy) })
      .transition()
        .attr("width",fifteen)
        .duration(500) // this is 1s
        .delay(function() { i = i +1; return i * 2; })         
  ; 

Ergebnis der Bastelarbeiten ist ein interaktives Diagramm, das mit dem Kalender-Widget oben gesteuert wird. Entlang der Tageszeit wird pro Raum-Pool (die Kapazität ist anhand der Fläche codiert) die Belegung inkl. der angefragten Termine visualisiert. Je stärker die Nachfrage, desto dunkler der Blauton. Liegt die Nachfrage über dem Angebot, wird das Blau zu einem gefährlichen Rot – hier gibt’s also dann etwas zu bereinigen.

sdsd

Visualisierung der angefragten und verfügbaren Kapazitäten pro Datum.

Das Ergebnis mit der traumhaft-schönen Animation auf YouTube. Das Diagramm ist erstaunlich responsive – immerhin werden mit jedem Klick eine Tabelle mit einer halben Million Einträge sechsmal durchsucht, der Tag in 60 Viertelstunden zerteilt und anschließend die Auslastung live berechnet.

Links

hacks

Terminals in Serienproduktion

Roald Amundsen schrieb beim Durchqueren der Nordwestpassage ins Tagebuch, das Schiff sei ein glückliches, weil jedes Crewmitglied einfach wisse, was gerade zu tun sei. Zwei Tage im Jänner 2013 segelte ich auf einem derart glücklichen Schiff, wo tausende Handgriffe am Ende ein wunderbares Ergebnis brachten – oder hat man als Softwareentwickler sonst einen Akkuschrauber in der Hand? Meinen Crewmitgliedern sei Dank!

test

Die Terminals gehen in Serie.

Einen kleinen Zusammenschnitt der beiden Arbeitstage gibt’s als Video (ordentliche Auflösung direkt bei YouTube):

Mehr zum Thema: The Octopus has landed!, Terminal-Update, Service Terminal Plus

hacks

Tetris 3D!

Vor kurzem habe ich über die Tetris-Maschine, einen Algorithmus zum Finden optimaler Raum-Zeit-Kombinationen in Reservierungssystemen, gebloggt. Diesmal ist die Aufgabe dieselbe, der Lösungsweg allerdings ein gänzlich anderer.

März oder Oktober – die WU plant – wie andere Universitäten ebenso – Lehrveranstaltungstermine des jeweils kommenden Semesters. Der Prozess gleicht dem Einräumen meines Kühlschranks nach dem Großeinkauf: Zuerst müssen große Veranstaltungen fixiert werden, kleinere müssen sich an die Gegebenheiten anpassen – also nehmen, was noch übrig ist. Mit zunehmender Fragmentierung durch Blockveranstaltungen oder Prüfungstermine wird es somit immer schwieriger, einen fixen Raum oder auch nur eine stabile Beginn- und Endzeit für Veranstaltungen zu finden. In diesem Fall kann eine Visualisierung der Belegung sinnvoll sein.

Doch wie soll man knapp 20.000 Veranstaltungen in 150 Räumen pro Semester übersichtlich darstellen?

Die Referenz

Anbei ein Screenshot vom aktuellen Buchungssystem: Wochentagsweise werden die Belegungen dargestellt – pro Raum gibt es eine derartige Auswertung. Die einzelnen Kürzel codieren die Veranstaltungen, ein Überblick über den gesamten Campus ist allerdings so nicht möglich.

Belegung eines Raums über ein ganzes Semester nach Wochentagen. ASCII-Art rules!

Der Re-Write

Trotz des geekigen Charmes der obigen ASCII-Darstellung lautete der Plan, die Belegungen interaktiv zu visualisieren. Typische Anwendungsfälle sind etwa:

  • “Ich benötige kommendes Semester einen 60er-Raum am Donnerstag Nachmittag.”
  • “Ich benötige in der ersten Semesterhälfte einen 30er-Raum am Montag und Mittwoch zur selben Beginnzeit.”
  • “Ich benötige für Termine 1, 2, 3, 4 und 5 einen Hörsaal.”

Die Idee: Die Belegung sollte Raum, Uhrzeit und Datum dreidimensional darstellen, also Datum über Datum legen. Mit semitransparenten Balken pro Belegungen könnte die Farbintensität somit die Dichte der Buchungen codieren. Durch Zu- und Wegschalten einzelner Tage, wäre es außerdem möglich, schnell in der Datenmenge zu navigieren.

Mock-up Dez. 2011: Meine ursprüngliche Idee, wie man dreidimensional durch die Buchungen am Campus blicken sollte.
Low-Fidelity Prototype auf Papier. Die Achsen für Uhrzeiten und Räume sind nun vertauscht, denn eine variable Anzahl an Räumen expandiert besser nach unten als nach rechts.

Nach einigen Spielereien mit absoluter Positionierung von <div>-Elementen im DOM fiel die Wahl doch recht schnell auf das weitaus effektivere d3.js. Diese Javascript-Library ermöglicht das Erstellen interaktiver SVG-Dokumente. Meine Hassliebe zu Javascript war jedenfalls überwunden, als das method chaining der Library endlich von den Fingern ins Gehirn übergegangen war:

function actually_draw(data) {

  d3.select("#bookings")
    .append("g")
      .attr("id", "day" + data.day)
    .selectAll("rect.booking")
    .data(data.bookings)
    .enter()
    .append("rect")
      .attr("class", "booking")
      .attr("width", function(d) {
        return linear_scale(time_to_float(d.end)) - linear_scale( time_to_float(d.start))
       })
      .attr("height", R_AXIS.incr - 2)
      .attr("x", function(d) { return linear_scale( time_to_float(d.start) ) } )
      .attr("y", function(d) { return  room_y(d.room)});  
}

Exzessives method chaining gepaart mit einigen wenig intuitiven Methoden wie .selectAll() (macht man, obwohl noch nichts vorhanden ist) oder .data(data).enter() (“steigt” hinein in die Daten). Ist man da mal durch, ist plötzlich alles möglich!

Endergebnis: Über 2000 Termine auf einer Seite visualisiert, interaktiv mit dem Kalender zu steuern. Echte Daten der WU im Sommersemester 2013.

Auch wenn der Screenshot noch nicht toll aussieht: Der Browser holt sich mehrere tausend Termine und rendert diese ohne Performance-Probleme bei jedem Klick im Kalender. Sourcecode und Live-Demo sind verfügbar unter:

http://www.zieglergasse.at/stuff/2012/bookingviz/view.html

 

hacks

Die DIY-Ökonomie

Bislang war im Blog entweder von IT-Hacks oder von politisch-ökonomischen Themen zu lesen. Diesmal wage ich während einer Reise durch Japan die Synthese meiner beiden Interessen mit der – katastrophal erfolglosen – Simulation eines künstlichen Wirtschaftssystems…

Doch alles der Reihe nach: Simulationen sind ein enorm spannender Bereich der Informatik. Im Grunde geht es dabei darum, Ergebnisse weniger anhand bekannter Algorithmen zu errechnen, sondern das Verhalten von Lösungsteilen zu modellieren, um sich später das Gesamtergebnis einfach anzusehen.

Mit solchen Simulationen lässt sich etwa die Kreiszahl Pi berechnen, man kann voraussagen, ob die Wasserqualität eines Badesees hält, oder aber man simuliert einen Verkehrsstau oder einen Gebäudebrand.

Bestückt mit Wirtschaftsstudium und – wichtiger – langjähriger Erfahrung als Bürgermeister von SimCity und zahlreichen Siedler-Inseln, oder einfach mangels naturwissenschaftlicher Expertise, lag die Aufgabenstellung in meinem Fall jedenfalls am Tisch: Die Simulation einer künstlichen Marktwirtschaft bestehend aus Produzenten, Konsumenten, Einwohnern, Arbeitgebern, Produkten, Preisen und Steuern.

Die Wirtschaftsmodelle der Wuselspiele haben mit freien Märkten nichts zu tun. Im Screenshot: Widelands, ein Open Source Siedler-Klon.

Software-Agenten statt homo oeconomicus

Den Wirtschaftswissenschaften wird oft vorgeworfen, an den homo oeconomicus, also das rein rationale Verhalten der Akteure zu glauben. Dass es diesen homo oeconomicus nicht gibt, erkennt man beispielsweise daran, dass Kinos trotz Breitbandanschlüssen noch nicht komplett leer stehen, oder dass mein Fitnesscenter einen gut frequentierten Aufzug besitzt.

Auch im Reisegepäck dabei: Kahnemann, Langsames Denken, schnelles Denken, 2011. Lesenswertes Buch über die Irrationalität der Menschen, Buch bei Amazon.

Wie auch immer, Wirtschaftsprognosen sind gerade auch deshalb so schwierig, weil das Verhalten vieler Akteure komplett unterschiedlich abläuft und die Interaktionen unüberschaubar komplex sind. Ohne das Problem verstehen zu wollen, kann man jedenfalls versuchen, das Problem mit Rechenleistung zu erschlagen.

Meine Annahme: Warum nicht eine Vielzahl künstlicher, unterschiedlich agierender Agenten erschaffen und sie anschließend Wirtschaft spielen lassen. Dabei könnten etwa Konsumenten vorkommen, die sich bis auf ein Maximum verschulden, andere würden eisern sparen. Manche Produzenten wären schneller mit Investitionen, andere vorsichtiger.

Meine auf der Flugzeugserviette entworfene Ökonomie sieht jedenfalls so aus:

              -------------
              Markt
              -------------
              - Jobangebote
              - Löhne
              -------------
                  |
  -----------     |   -----------
  Arbeitgeber  ---+->  Einwohner
  -----------         -----------
      |                    ^
      |                    |
      v                    |
  -----------         -----------
   Produzent   <--+--  Konsument
  -----------     |   -----------
                  |
               ------------
               Markt
               ------------
               - Produkte
               - Preise 
               - Steuern
               ------------

Nach einer schlaflosen Nacht zwischen Minsk und Vladivostok und einem weiteren High-Speed Hack im Shinkansen von Tokio nach Kyoto ist sie also fertig - meine künstliche Wirtschaft mit hundert Einwohnern und sieben produzierenden Firmen, gegossen in ein paar Python-Module und einen Volkswirtschaftssimulator, der über zehn Jahre hinweg Bevölkerungszahlen, BIP, Privat- und Firmenvermögen sowie Jobs und Arbeitslosenrate misst. Einwohner suchen sich Jobs, verdienen Geld und geben es nach Lust und Laune wieder aus.

Meine Volkswirtschaft in Python gegossen. Links die Vermögens- und Arbeitsmarktstatistiken meiner Modellwelt.

Klingt vielleicht beeindruckend, ist es aber nicht: Statt Antworten eröffnen sich volkswirtschaftliche Lücken.

Woher kommt eigentlich das Wachstum?

Meine modellhafte Volkswirtschaft kann zwar nicht alle hundert Einwohner ernähren, aber nach ein paar Monaten hat der Großteil der Leute einen Job gefunden. Die Arbeitslosenrate sinkt – auch dank der Verhungerten – auf Null und dann passiert… nichts. Die Firmen verdienen genau das Geld, das sie wiederum den Arbeitenden auszahlen. Das BIP stagniert, woher käme denn auch das Wachstum?

Zahlreiche ökonomische Theorien beruhen auf der Ansicht, Wachstum entstünde zwangsweise durch Ausbeutung von Ressourcen. Ganz egal, ob nun damit etwa die Natur (Physiokratie), Arbeiter (Marxismus), deren Kombination in Übersee (Globalisierungskritik) oder das Sparvermögen des Mittelstands (“Draghismus“) gemeint sind. Das Gegenstück zu den diversen Ausbeutungstheorien bildet Schumpeters Theorie der wirtschaftlichen Entwicklung. Diese besagt, dass Wachstum aus neuen Ideen entsteht, aus der Zerstörung des Alten und Erschaffung des Neuen.

Nun ja, im angesprochenen Programm sind alle Optionen für Wachstum absent: Die Bevölkerung arbeitet bereits unter Vollauslastung, es gibt keine unentdeckten Bodenschätze und disruptive Innovation ist irgendwie schwer zu simulieren…

Weitere Stunden später, in denen ich meine Liliput-Ökonomie weiter parametrisiert und einen etwas intelligenteren Marktmechanismus (Preise und Löhne) implementiert habe, gebe ich schließlich entnervt auf. Wirtschaft ist wohl nicht zu simulieren, denn die Komplexität ist nicht abbildbar. Während bei technischen Aufgabenstellung oft das Prinzip Divide and Conquer zum Erfolg führt, ist das hier nicht machbar: Entweder es ist alles berücksichtigt (Lohnverhandlungen, Preisbildung, Investitionen der Unternehmen, Kundenpräferenzen für deren Produkte, Steuern, Subventionen, Arbeitslosenunterstützung, Unternehmensgründung durch einstige Konsumenten, Währungen, Zinsen, Staaten, Banken, usw.) oder die Simulation bleibt ein netter Zeitvertreib – was es allerdings wirklich gewesen ist.

Was ich dennoch gelernt habe

  1. Am Schlimmsten sind die Sparer unter meinen Einwohnern: Denn deren Konsumverzicht häuft unproduktives Kapital an.
  2. Mein in der Folge chronischer Hungersnöte eingeführtes Grundeinkommen hat die Modellwirtschaft nachhaltig stabilisiert. Zum Teil finanziert sich mein Staat über die Umsatzsteuer, ebenso findet aber eine massive Umverteilung vom Staat in Richtung Unternehmen statt. Der Staat mach also in jeder Modellvariante Schulden, damit Arbeitslose von Produzenten kaufen können. Folglich werde ich Arbeitslosenunterstützung künftig auch als Subvention für die Wirtschaft begreifen.
  3. Ein Käufermarkt, wo sämtliche Grundbedürfnisse der Menschen vielfach befriedigt sind, und Geld über massives Marketing nicht-lebensnotwendiger Produkte gemacht wird, wäre wohl gar nicht mehr zu simulieren.
  4. Im Urlaub sollte man keinen Notebook dabei haben.
hacks

Writing emails to a website

Unix’ beauty lies in the fact that you can easily build powerful systems out of simple components. This post describes an email-to-database interface for one of our web applications using Python and some Unix tools.

Users came up with the urgent need to annotate database records with arbitrary content. Rather than bending our application into a document management system (which apparently it is not) I came up with the idea to offer an email-to-website service.

Or as my friend Florian put it: “With a few billion users, email is quite a succesful social network.”

Mailing to a script

In order to write mails to a program rather than a conventional mailbox you need to edit the file /etc/aliases .

booking:        |/opt/wu-wien/roomsmta/bin/booking.sh

Here I’m sending the mail to an address called booking. Every incoming mail invokes the shell script booking.sh.

If you want to forward your mail to other locations simply add mailboxes or addresses after a comma. Don’t forget to run newaliases after the file is saved.

 Shell wrapper

#!/bin/sh

PYTHON_EGG_CACHE=/opt/wu-wien/roomsmta/cache
export PYTHON_EGG_CACHE

exec /opt/wu-wien/roomsmta/bin/booking.py

The incoming mail is passed to a shell script which sets some environment variables needed for virtualenv. The Unix command exec passes standard input on to the Python script. The result will be a custom Python interpreter set up with its very own environment.

The Python script

My script reads the email from STDIN, does some parsing magic and eventually puts the content into a database. (For reasons of clarity I have omitted the latter part in my example.) Since my mail configuration saves the mail in a proper mailbox as well I do not store attachments in the database. If you’re interested in parsing attachments I recommend Ian Lewis’ post.

#!/opt/wu-wien/roomsmta/v_mtapy/bin/python
import email.FeedParser
from email.utils import parseaddr

import sys, os
import base64
import re

import cx_Oracle

def parse_email(email_input):
    """Return message object"""
    
    parser = email.FeedParser.FeedParser()
    msg = None
    for msg_line in email_input:
       msg = parser.feed(msg_line)
    msg = parser.close()
    
    return msg


class Email(object):
    
    
    def __init__(self, msg):
        
        self.msg = msg

        self.subject = msg['Subject']
        self.date = msg['Date']
        (self.from_name, self.from_email) = parseaddr(msg['From'])
        
        self.to_email = parseaddr(msg['To'])
        
        self.has_attachments = 0
        
        msgbody = []


        for part in msg.walk():
            
            if part.get_content_type() == 'text/plain':
                
                if part['Content-Transfer-Encoding'] == 'base64':
                    body = unicode(base64.b64decode(part.get_payload()), part.get_content_charset(), 'replace' )
                
                else:
                    
                    try:
                        body = unicode(part.get_payload(), part.get_content_charset(), 'replace')
                    except TypeError:
                        # if no charset is given
                        body = unicode(part.get_payload())
                msgbody.append(body)
                
            elif part.get_content_type() == 'text/html':
                
                html = unicode(part.get_payload(), part.get_content_charset(), 'replace')
                raw = nltk.clean_html(html)          

                msgbody.append(raw)
                #print raw.encode('utf8', 'replace')

            elif part.get("Content-Disposition"):
                #print part.get("Content-Disposition")

                self.has_attachments = 1
                msgbody.append(u'ATTACHMENT: %s' % (part.get_filename(),))

        self.body = u'\n'.join(msgbody)
    
    def save(self):
        
        print 'GOING on to save'


if __name__ == '__main__':
    
    
    email_input = sys.stdin.readlines()
    msg = parse_email(email_input)
    
    Email(msg).save()

Testing the setup

Testing your code turns out to be a nightmare since you will need a mail client to invoke your script. Fortunately Unix comes with pipes which mock the email behavior:

less my_test_mail.eml | ./booking.py

Addressing records in the database

Did you know that you can have plus signs in a valid mail address?

Until now we have written all our email to booking@servername. But what you need in an application context is the possibility to clearly  attribute incoming email to specific database records. I have seen some solutions carrying the magic in the subject field, but I prefer RFC2822‘s possibility to write content after a plus sign into the address field. In my case this will either be the record’s id and the database instance in use (i.e. test or production).

        tocc = '%s %s' % (self.msg['To'], self.msg['Cc'])

        # has formats:
        #   booking+1234T@localhost
        #   booking+1234P@localhost
        #
        m = re.search('booking\+\d+[PT]', tocc)
        
        try:
            
            tocc = m.group(0)

            instance = tocc[-1:] 
            tid = tocc[:-1].replace('booking+','')
            
            return (instance, tid)
        
        except AttributeError:
            raise NoEventFound

Voilá, I can write mails to booking+1234T@servername.

Mail’s in the database – mission accomplished.

Thanks to Roland and Willi who did the tricky work;-)

hacks

Die Tetris-Maschine

Der innerste Kern meiner Tätigkeit als Softwareentwickler ist die Erschaffung von Algorithmen. Dass das deutlich spannender sein kann als es klingt, will ich anhand eines aktuellen Projekts zeigen.

Beginnen wir mit einer Aufgabe: Gegeben ist die dargestellte Auslastung von vier Hörsälen. Es soll nun eine weitere Veranstaltung zwischen 13:00 und 14:00 (blau markiert) gebucht werden – welcher Raum ist nicht nur verfügbar, sondern auch optimal passend?

In welchem Raum soll der gewünschte Termin (13:00 bis 14:00) untergebracht werden?

Als Algorithmen werden in der Informatik Handlungsanweisungen zur Lösung eines Problems verstanden. Algorithmen können so etwa als Kochrezepte für Computerprogramme verstanden werden. Sie berechnen Manager-Boni, kümmern sich bei Word um die Rechtschreibkorrektur, twittern Aktienkurse oder – wie in meinem Fall – verhelfen der WU künftig zu einer optimierten Ausnutzung der Ressource Raum.

Algorithmisches Denken ist die Umkehrung von Schul-Mathematik. Beim Programmieren ist die Lösung bekannt, aber es gilt den Weg dorthin zu entdecken.

Das Teaching Center am Campus WU: Stählerner Schauplatz für Raumbuchungs-Tetris im XXL-Format. Mathias, 2012.

Jedenfalls: Bei unserem Re-Write der Raumverwaltung für den Campus WU sind wir mit einigen, höchst spannenden Anforderungen konfrontiert: Raumbuchungen sollen künftig automatisch und optimal durchgeführt werden, den Administratoren bleibt allerdings jedwede Flexibilität erhalten. Die Software kümmert sich dabei um Studien- wie Stundenpläne, um Verfügbarkeit der Lehrenden und “Extrawürschteln” ebenso, wie um Minimierung von Buchungslücken oder Reduktion unnötigen Reinigungsaufwands aufgrund zu vieler, “angepatzter” Hörsäle. Die Ressourcen sollen flexibel verfügbar sein (Stichwort dynamische Lagerhaltung), ein allzu häufiger Ortswechsel macht Studierende wie Lehrende aber wohl kaum glücklich.

Wir benötigen daher wieder einmal den “Do what I want“-Button:

       +-----------------+
       |                 |
       | DO WHAT I WANT  |
       |                 |
       +-----------------+
Button, der in jeder Software genau 
das tut, was der User will. 
(Grobkonzept)

Von der Idee zum Algorithmus

Die Aufgabenstellung lautet demnach:

  • Liefere für eine Serie von Terminen (z.B.: ein ganzes Semester) Buchungsvorschläge
  • innerhalb der angefragten Raumkategorie,
  • versuche dabei die gewünschten Zeiten zu erfüllen,
  • d.h. immer dieselbe Beginnzeit zu ermöglichen,
  • vermeide außerdem Raumwechsel,
  • aber vermeide noch viel mehr den Wechsel von Gebäuden.
  • Beachte, dass Buchungen nahtlos aufeinanderfolgen sollten.
  • Entstehen dennoch Lücken, so sind z.B.: 30 Minuten sehr schlecht, weil dieser Zeitraum sicher verloren ist,
  • 90 Minuten hingegen sind gar nicht so schlimm, weil ja noch etwas reinpassen könnte.
  • Bereits “angepatzte” Räume sind leeren vorzuziehen, weil dadurch der Reinigungsaufwand verkleinert wird.
ggg

Geistreiche Bilder vom Entstehungsprozess des Algorithmus. (…)

 

Die Grenzen der Berechenbarkeit

Ein naiver Lösungsansatz wäre nun, alle Möglichkeiten zu versuchen und die beste zu wählen. (Tatsächlich funktionieren viele Algorithmen nach diesem Prinzip.) In diesem Fall bekommen wir aber ein Problem mit der Dimension der Aufgabe:

Will etwa jemand an acht Montagen irgendwann zwischen 9:00 und 12:00 eine zweistündige Lehrveranstaltung abhalten, so ergeben sich (zu) viele Buchungsmöglichkeiten. Entlang eines 15-minütigen Rasters erhalten wir zunächst fünf mögliche Beginnzeiten (9:00, 9:15, 9:30, 9:45, 10:00). Diese fünf Beginnzeiten über rund 50 gleichwertige Seminarräume an acht Montagen ergeben acht mal fünf mal 50 – also zweitausend – Einzelmöglichkeiten. Die Berechnung der Kombinationsmöglichkeiten ergibt 15 Quintillionen [sic - 250hoch8 - eine 15 mit 18 Nullen]!

Wir reden allerdings nicht nur von einer Veranstaltung, sondern von knapp 4000 jährlich mit rund 35000 Einzelterminen – Tendenz Dank erweiterter Buchungsmöglichkeit für Mitarbeiter und Studierende stark steigend. Selbst wenn ein leistungsfähiger Rechner eine Milliarde Kombinationen pro Sekunde ausprobieren würde, wäre die erste Raumbuchung erst nach knapp 500 Jahren fertig…

Tetris

Glücklicherweise gibt es Tetris! Ähnlich wie bei den von oben fallenden Steinen kommen auch die Buchungswünsche Schritt für Schritt herein. Jede Buchung muss auf die jeweils aktuelle Belegung Rücksicht nehmen, kümmert sich aber nicht um das, was noch kommt. Genauso wie bei Tetris ist es auch bei Raumbuchungen sinnvoll, Lücken bestmöglich zu füllen, oder doch für passgenaue Steine in naher Zukunft frei zu halten.

Tetris-Klon von Nintendo: Dr. Mario. Aufgrund unseres akademischen Kontexts an der WU die wohl bessere Analogie.

Die Matrix

Tetris-Analogie hin oder her, noch besteht das Problem der zu vielen Lösungen. Manchmal hilft es, die Aufgabenstellung in kleine Teile zu trennen, das Problem zu abstrahieren, sich typische Situationen aber vor allem auch Grenzfälle (corner cases) zu überlegen.

Eine Terminserie zu unterschiedlichen Beginnzeiten, in Gebäuden und darin enthaltenen Räumen entspricht einer vierdimensionalen Matrix, so einer Art Excel-Tabelle, wo in jeder Zelle nochmal eine Tabelle enthalten ist. (Keine Sorge, so etwas kann auch ich mir nicht wirklich vorstellen.)

Die Einträge in dieser Matrix sind jedenfalls:

  • pro Termin
  • pro Beginnzeit
  • pro Gebäude
  • jede verfügbare Raumnummer
  • mit Bewertung der Passgenauigkeit

Die Passgenauigkeit beträgt 1, wenn der Raum einfach frei ist, 0 wenn er gebucht ist. Sollte ein Termin nahtlos reinpassen, erhöht sich der Wert über 1, bleiben Lücken, so bekommt der Raum Strafabzüge. Die Datenstruktur sieht früher oder später so aus:

{Termin1: {'12:00': {Gebaeude1: {Raum1: 0.47250000000000003, Raum2: 1.6}}},
 Termin2: {'12:00': {Gebaeude2: {Raum5: 1},
               Gebaeude1: {Raum1: 1,
                           Raum2: 1,
                           Raum3: 1,
                           Raum4: 1}}},
 Termin3: {'12:00': {Gebaeude1: {Raum1: 2, Raum2: 2}}},
 Termin4: {'12:00': {Gebaeude2: {Raum5: 1},
               Gebaeude1: {Raum1: 2,
                           Raum2: 2,
                           Raum3: 1,
                           Raum4: 1}}},
 Termin5: {'12:00': {Gebaeude2: {Raum5: 1},
               Gebaeude1: {Raum1: 0.45,
                           Raum2: 0.3375,
                           Raum3: 1,
                           Raum4: 1}}},
 Termin6: {'12:00': {Gebaeude1: {Raum2: 0.45, Raum3: 1, Raum4: 1}}},
 Termin7: {'12:00': {Gebaeude2: {Raum5: 1},
               Gebaeude1: {Raum1: 1, Raum3: 1, Raum4: 1}}}}

Die optimale Buchung

Die beste Lösung findet man nun nicht durch stumpfsinniges Ausprobieren, sondern durch geschicktes Durchschreiten der Datenstruktur. Der Lösungsweg lautet:

FÜR jede Terminanfrage    
  FÜR jede Beginnzeit gereiht nach Häufigkeit terminunabhängig
    FÜR jedes Gebäude gereiht nach Häufigkeit in Beginnzeit
       FÜR jeden Raum gereiht nach Passgenauigkeit in Beginnzeit
         VERSUCHE zu buchen
         ODER gehe weiter

Die Lösung der eingangs gestellten Aufgabe lautet übrigens “Raum B”. Oder wenn Sie zufällig ein Algorithmus sind:

{1: {'12:00': {123456789: {66778801: 0.47250000000000003, 66778802: 1.6}}}}

Übrigens: Geschafft in 0.7 Sekunden anstatt von 500 Jahren.

tltr;

Raumbuchen ist wie Tetris-Spielen; Excel kennt zum Glück keine Tabellen in Zellen.

hacks

Kiosk-Terminal mit schnellem Login

Dem so genannten Octopus habe ich mich an dieser Stelle bereits ausgiebig gewidmet. Der von uns als Buckelwal bezeichnete Kiosk-Terminal kam bislang zu kurz. Hier also ein schnelles Update.

Der Anwendungsfall ist nicht neu: Im öffentlichen Bereich der WU Wirtschaftsuniversität Wien sollen Kiosk-Terminals schnellen und unkomplizierten Zugang zu Informationen, also dem Internet, sichern. Seit mehr als zehn Jahren gibt es mit der so genannten ByteBar bereits eine Lösung. Inzwischen macht sie vielleicht optisch nicht mehr allzu viel her – auf Seiten der Zuverlässigkeit und Wartungsarmut läuft das System allerdings beeindruckend vor sich hin.

ByteBar WU Wirtschaftsuniversität Wien.

Als ich mir gegen Ende 2010 das erste Mal Gedanken machte, was man am Baulichen und Haptischen verbessern könnte, fiel mein Blick auf die Suchmaschine bei Ikea. Diese vereint in durchdachter Schlichtheit und Pragmatik Features wie Stabilität, Touchscreen und/oder Tastatur, Kosteneffizienz, Servicierungskonzept, Sicherheit und Raumausnutzung.

Bei Ikea wissen die Designer was sie tun: Optimale Raumausnutzung, Schutz gegen Wagerl-Kollisionen, Kosteneffizienz. Mathias, Ikea Wien Süd (2010)

Mit dem schwedischen role model im Gepäck beauftragte ich 2011 einen Kiosk-Hersteller mit der Anfertigung eines Prototypen. Aus Designgründen entschieden wir, die Hardware hinter der Oberfläche verschwinden zu lassen, unterschiedliche Arbeitsplatzhöhen sollten außerdem unser Bekenntnis zu mehr Barrierefreiheit verdeutlichen.

Rendering unseres Prototypen: Unterschiedliche Arbeitsplatzhöhen, viel Karma für Design-Götter.

Nach Liefer-Verzögerungen und einigen Tests wurde der Prototyp Februar 2012 an der WU aufgestellt. Die Software läuft nach wie vor noch nicht so, wie wir uns das am Ende vorstellen. Aber Sinn und Zweck eines Prototypen ist ja eben der vorzeitige Erkenntnisgewinn.

Steht seit Februar 2012 an der WU für öffentliche Tests.

Öffentliches Internet versus die Abwehr anonymer Surfer

Der Großteil des studentischen Feedbacks auf Facebook drehte sich schon bald um die Haptik der Tastatur. Ich ahnte bis dato jedenfalls nicht, wie viel Emotion im Tastatur-Thema liegen kann. Nach einer Abstimmung unter den Studierenden fiel die Wahl schließlich auf eine (hygienische) Metalltastatur mit Trackpad.

Ende Juni veröffentlichte ich das Abstimmungsergebnis auf Facebook. Gleich die allererste Reaktionen sollte das Thema Browser/Software nochmal komplett drehen:

“Viel wichtiger wäre es wenn das ZID es endlich mal schaffen würde das man auf der WU eigenen (!) Homepage endlich die Suchfunktion nutzen könnte! Ich habe dass schön mehrmals angeregt bin bis jetzt aber immer auf taube Ohren gestoßen.”

Studierender auf Facebook

An der ByteBar erhält man beim Aufruf WU-fremder Seiten eine Fehlermeldung: Der Zugriff auf externe Seiten wird nämlich verhindert, um Surfzeiten zu begrenzen und somit Wartezeiten für andere zu verkürzen. Diese Maßnahme erachte ich allerdings aus zwei Gründen für höchst fraglich:

  1. Im Zeitalter von Web 2.0, Cloud und whatever liegen nunmal sehr viele nützliche Dinge nicht innerhalb der WU. Spätestens bei der outgesourcten Suche über Google – wie oben mokiert – wird die Sache peinlich.
  2. Die Begrenzung des Internetzugangs war vor zwölf Jahren sicher sinnvoll. Doch ist sie das dank Breitband und Smartphones noch immer?

Mein Vorschlag, Internet einfach “aufzudrehen”, fand jedenfalls keine Mehrheit. Ich kann die Bedenken nachvollziehen, dass dadurch jeder Fremde an die WU surfen kommen könnte – unangenehm wird das jedenfalls bei strafbaren Aktionen im Netz… Kurzum: Ein Login-Mechanismus musste her.

Python + WebKit + Studierendenausweis

Gemeinsam mit unserem kongenialen Hightech-Partner La Gentz war schnell die Idee geboren, den Studierendenausweis alternativ zur mühsamen Username/Passwort-Authentisierung  zu verwenden. Eine Authentisierung soll innerhalb kürzester Zeit erfolgen – das simple Hinhalten der MIFARE-Karte erfüllt diese Anforderung perfekt.

Außerdem entschieden wir uns, einen WebKit-basierten Browser from scratch zu implementieren. Das erspart die mühvolle Arbeit, den Browser gegen noch so kreative Usereingriffe abzusichern. Ein Login-Widget liegt semi-transparent über den Inhalten des Browserfensters und fordert zum Hinhalten der Karte auf. Aktuell überprüfen wir das UI noch auf seine Usability.

Details zur technischen Umsetzung finden sich im La Gentz Blog.

Fehlermeldung im unauthentisierten Betrieb. Das semi-transparente Widget lädt zum Einloggen via Karte oder Username/Passwort ein.

Nach dem Login ist unbegrenztes Surfen möglich. Ein Countdown visualisiert die verbleibende Zeit im Falle von Inaktivität.

Siehe da: Nach dem Login darf ich unbegrenzt surfen. Danach kann ich mich abmelden oder werde nach 1:30 Inaktivität automatisch rausgeschmissen.

Mit Ubuntu ist ein Betriebssystem ohne Wintendo-Krankheiten gefunden, LDAP bzw. JSON-RPC dienen zur Authentisierung. Demnächst folgt noch der Netzwerk-Boot (ähnlich den Door Displays).

Somit ist Websurfen künftig zufriendenstellend möglich, und wir brauchen keine Angst vor Internet-absaugenden Massen zu haben;-)

hacks

The octopus has landed!

Yes, yes, yes!

Das vom Foto vom Profi, (C) Jürgen.

Nach neun Monaten Planung und Entwicklung steht er nun funktionsfähig an der WU.

Unser Oktopus steht seit heute an der WU – produktiv und bereit für zwölf Monate Bewährungsprobe bevor am Campus WU alle SB-Terminals ausgetauscht werden.

Hinter uns liegt eines der spannendsten Projekte, das man wohl durchführen kann. Von der Bohrmaschine bis hin zum garbage collector gab es kein Werkzeug, das wir nicht in der Hand hatten, ich kenne nun die Eigenschaften von Melamin ebenso, wie die von 7Byte-RFID. Kurzum: Wir haben in den vergangenen neun Monaten gemeinsam mit den Tischlern alles selbst gemacht. Das war spannend, stressig, arbeitsintensiv, lehrreich, aber vor allem war es ziemlich außergewöhnlich.

Ergebnis ist ein Automat, der – ohne Übertreibung – weltweit einzigartig ist. Er druckt A4-Papier mit geringerer Fehlerrate als es der Drucker mit Original-Zubehör täte. Außerdem verfügt der Terminal über ein neuartiges Navigationskonzept, welches auf Lichtsignalen beruht. Die Optik ist ebenso außergewöhnlich wie die zahlreichen Anwendungsfälle, die er abdeckt. Wir sind stolz auf Barrierefreiheit und Inklusion, reduzierten Strom- und Papierverbrauch sowie ein absolut durchdachtes Service-Konzept. Trotz aller Superlativa haben wir dabei den Kostenrahmen gehalten.

Nennenswerte Verbesserungen

Aus Benutzersicht sind das…

  1. Erhöhte Gebrauchstauglichkeit dank kapazitivem Touch-Monitor, größeren UI-Elementen sowie verbesserter Navigationsstruktur.
  2. Einmaliger Ansatz für Barrierefreiheit mit Unterfahrbarkeit für Rollstuhlfahrer dank Auskragung.
  3. Zusätzlich Bezahlung via Kreditkarten. (Zurzeit aufgrund der unklaren Situation hinsichtlich Rechtmäßigkeit von Studiengebühren allerdings nicht freigeschalten.)

Für Wartungsmitarbeiter bedeutet das…

  1. Lösung des bestehenden Papierstau-Problems.
  2. Vergrößerte Service-Intervalle durch größeren Papiervorrat.
  3. Ausdrucke werden nur noch auf expliziten Wunsch gefertigt – weniger Müll.

Für uns Techniker sind das…

  1. Umstellung auf robustes Linux und Abstraktion aller Hardware-nahen Treiber auf Standardschnittstellen.
  2. 100% Python-Code, ein wunderschön-sauberer UI-Code dank PyQt.
  3. Größtmögliche Kompatibilität zur bestehenden Server-Schnittstelle, die in den kommenden 12 Monaten von zwei unterschiedlichen Terminal-Generationen genutzt wird.

Rückblick in Bildern

Neun Monate und 15.000 Zeilen Code später…

Die erste Studie zum “Service Terminal Plus”.

Mittels TeamViewer und Telefonkonferenz wird parallel in Scheibbs und Wien am 3D-Modell gearbeitet.

Während nun das Möbelstück gefertigt wird, verzweifle ich an Video4Linux…

Der erste Mock-Up macht das Terminal spürbar: Wir verorten Komponenten.

Mit kleinen Änderungen wird aus dem Mock-Up ein Prototyp.

Ein Prototyp, der über reichlich Innereien verfügt: Drei Drucker, Touch-Monitor, vier Lichtsegmente, Kamera, Bankomat-Terminal, Kartenleser, Papierlade, Netzwerk und Strom.

Letzte Änderungen fürs (materielle) User Interface werden direkt eingezeichnet.

Aufräumen beim alten UI, welches sich stark an Mausbedienung orientiert…

… hin zu einem Touch-optimierten UI.

Plötzlich eröffnet sich die Frage der Montage. Es folgen Diskussionen, Standortbesichtigung und ein Gutachten, dass der Doppelboden der Belastung standhält.

Letzte Iteration im Büro. Alles fast fertig!

Feinschliff durch professionelle Grafik.

Das Wir im Bericht hat Namen, die ich gerne nennen will. Dank geht an Dennis, der die event loop der Applikation aber auch des Projekts über hat. Wolfgang und Andreas waren beim Möbelstück mit Herzblut dabei – dem Ergebnis kann man es mehr als nur ansehen. Roland und Seán haben sich um die Betriebssystemebene gekümmert. Dank Bernhard läuft das Terminal zu großen Teilen auf Basis des alten, zusätzlich war er unser Erleuchter bei den LED-Streifen, die übrigens eine Idee von Simon waren. Infos zum Drucker-Umbau kamen von einem hilfsbereiten Mitarbeiter der Firma Knapp. Den Bankomat-Code verdanken wir Gabor, und ich habe den Großteil meiner Programmierarbeit bei Stack Overflow ”gestohlen”. Der grafische Feinschliff kam von Markus und Wolfgang. 

Bisherige Blogeinträge zum Terminal: Service Terminals Plus und Terminal-Update

finance hacks

Python stock quote fetch

UPDATE

– 8< –

Nach den heldenhaften Ideen des ebenso heldenhaften Stefan twittert nun mein Portfolio wochentags um 15:00 die aktuelle Entwicklung: crashportfolio@twitter

– 8< –

Anfang der Woche habe ich mein fiktives Krisenportfolio gepostet – damit war’s plötzlich höchste Zeit, Kursentwicklungen aktuell zu halten. Zwei Stunden und ein paar Zeilen Python später ist nun alles automatisiert.

Natürlich ist die Verwaltung eines Portfolios etwas, was Yahoo!, Google und zig andere Dienste gratis anbieten. Außerdem sehe ich die Positionen ohnehin auf meinem Konto bei brokerjet, aber dennoch: Nichts macht so viel Spaß wie eine selbst gebastelte Lösung, nichts ist so flexibel verwendbar, und nichts geht abzüglich der anfänglichen Programmierzeit danach so schnell.

Erfassen

Zunächst war es wichtig, die fiktiven Investments strukturiert zu erfassen. Ich hasse zwar Tabellenkalkulation, aber CSV scheint dennoch ein brauchbares Austauschformat zu sein. Kurzum: Name, Einstiegskurs, Menge, Datum, sowie eine ID (=Symbol) und ein Pointer auf den jeweiligen data provider sind wichtig.

Commodity ETF,etf/RBS_Market_Access_Jim_Rogers_International_Commodity_Index_ETF,26.50,900,08.07.2012,finanzen.net
Altria,PHM7.F,28.51,300,08.07.2012,yahoo
BP,BPE5.DE,5.40,1400,08.07.2012,yahoo
Fuchs Petrolub,FPE.DE,40.80,180,08.07.2012,yahoo
Nestle,NESR.DE,48.64,150,08.07.2012,yahoo
Novo Nordisk,NOVA.DE,118.32,65,08.07.2012,yahoo
Pfizer,PFE.DE,18.21,400,08.07.2012,yahoo
Shoprite,HY7.F,15.64,480,08.07.2012,yahoo
Umicore,UMI.BR,35.32,200,08.07.2012,yahoo

Absaugen

Das CSV-File wird mittels Python eingelesen, die Klasse Paper wird pro Position instanziiert. Yahoo! bietet ein unerschöpfliches API für Aktienkurse, die Python-Library ystockquote macht das Ganze sehr einfach zu verwenden.

class Paper():
    """Any bond, etf or stock"""
    
    def __init__(self, data_provider, name, symbol, quant, purchase_price, date):
        
        self.data_provider = data_provider
            
        self.name = name
        self.symbol = symbol
        self.quant = int(quant)
        self.purchase_price = float(purchase_price)
        self.date = date
        
        self._price = None
    
    def __repr__(self):
        return "%20s %7.2f %7.2f %7.2f%% %5d" % (self.name, self.purchase_price, self.price, self.change, self.totalgain)
    
    @property
    def price(self):
        if not self._price:
            
            if self.data_provider == 'yahoo':
                self._price = float(ystockquote.get_price(self.symbol))
            elif self.data_provider == 'finanzen.net':
                self._price = FinanzenNetParser.get_price(self.symbol)
            else:
                raise NoDataProvider
        return self._price

    @property
    def change(self):
        return ((self.price / self.purchase_price ) - 1) * 100
    
    @property
    def totalgain(self):
        return self.change / 100.0 * (self.purchase_price * self.quant)

Für den Kurs des ETF benötige ich einen HTML-Parser, da Yahoo! dessen Kurs nicht anbietet. Mit BeautifulSoup habe ich einen neuen Lieblings-Parser ins Herz geschlossen – in Kürze war also der Kurs von finanzen.net eingebunden. (Python-Tipp: Webscraping with Python and beautifulsoup)

Natürlich muss ich das auch noch für die Goldmünzen und die Uhr machen, die sich ebenso in meinem Portfolio befinden.

class FinanzenNetParser():
         
    @classmethod
    def get_price(self, path):
        """Ok, tricky: price is the first th in a table.header_height"""
        
        url = 'http://www.finanzen.net/%s' % (path,)
        content = urllib2.urlopen(url).read()
        
        soup = BeautifulSoup(content)
        for table in  soup.find_all('table'):
        
            if table.get('class') == ['header_height']:
                return float(table.th.text.replace('EUR','').replace(',','.'))

Ausgeben

Der Output ist derzeit noch etwas mager, mit Jinja2 sind die Daten aber schnell in HTML gegossen und auf meinem Mobiltelefon verfügbar.

                name purchase current  change  total
       Commodity ETF   26.50   26.57    0.26%    62
              Altria   28.51   28.79    0.98%    84
                  BP    5.40    5.48    1.50%   113
      Fuchs Petrolub   40.80   40.65   -0.37%   -26
              Nestle   48.64   48.86    0.45%    32
        Novo Nordisk  118.32  120.15    1.55%   118
              Pfizer   18.21   18.52    1.67%   121
            Shoprite   15.64   15.89    1.62%   121
             Umicore   35.32   34.65   -1.91%  -134