Coding an Assessment

Hello,
I want to build an assessment for sustainability. There are 19 KPIs to assess by traffic lights:

  1. Each participant choses his votes - than on screen shall be one the voted color.
    How shall I code that?
  2. What is the best way to save the votes (have a table with all 19 kpis as fields and count up OR just have 4 fields Green Lightgreen Orange Red and enter chosen as list)
    What would be best for calculating afterwards (Average, Standard-Dev. etc.)
    Thanks for your help.
    Aaron

Hi and welcome to the forum.

  1. It would probably be easiest to use a repeating panel or data grid to display questions. This could be loaded from the data tables service.

A second form could then be shown with the averages selected answers once the participant has submitted their answers.

  1. That really depends on much you want to be able to examine the data - my thoughts are always to save as much data so if you need it you have it. On this case I would have a data table with 19 columns to represent each question. I would use number columns to represent the choice ie 1 = green, 2 = yellow etc. This way averaging the data should be simple.

There is a good tutorial by @brooke to get you started which should have most of what you need in it.

2 Likes

This is the canonical approach, but I usually use one data table with one simple object column and put all the info in one spot.

The disadvantages are that it’s difficult to sort or search by any of the columns, because the columns don’t exist.

The advantages are that you are free to add or remove KPIs without the need to redesign your database or edit the code.

The simple object would contain something like:

[
    {'title': 'Trend Screening', 'description': 'Wie...', 'answer': 2},
    {'title': 'KPI #2', 'description': 'Cookies!', 'answer': 3},
    [...]
]

Another disadvantage of using simple objects would be that you would keep repeating the description of each KPI.

Another advantage of using simple objects would be that you always repeat the description of each KPI, which will allow you to keep the record of old data with old descriptions.

Elaborating on Rick’s answer, you could add a repeating panel to the form and assign to self.repeating_panel.items a list containing the 19 KPIs.

Then edit the repeating panel’s template form so it looks just like the snapshot on your question: one label for the title, one for the description and 4 links. Each link can have icon set to fa:circle and the foreground set to the color you like. If you really need a number with a colored round background, then you will need to play with the css and create one role per color.

3 Likes

I haven’t played with it much, but the docs do say you can search by the contents of simple object columns. A trivial example is on the blog post announcing them: Simple Object Storage

2 Likes

Thank you very much Rick, Stefano and Jay.
Very inspiring and leading.
I will build both ways (per KPI a column AND Simple Object Storage) and experiment what works best for calculations (& skip the other later) - I will let you know.
Cheers Aaron

2 Likes

Hi,
I have got the assessment working so far:

Now I need a counter in the form, how many rows in the repeating panels have been answered. I cannot figure out, where to count (guess in the repeating panel code) and to put that variable to the form and refresh it continously.
Best Aaron

One easy way would be to raise an event from the item_template that increments the counter on the main form.

If you can share a clone link that would be helpful.

Thank you - very good tip & and it works fine

1 Like

Hi Stefano, Quick question: when arranging data into a simple object, does your chosen IDE have auto-completion for the fields? This would save some time, especially with 19 fields!

No autocompletion with simple objects, because the IDE doesn’t know what’s inside.

I often use simple objects to store data in the database, but I deserialize them into well designed classes as soon as I get them out of the database and never interact with them. I only interact with my objects.

I usually create one class (or hierarchy of classes) that takes care of the logic. In the class there is one function that (calls a server function and) gets the values from the database, then all the code uses objects, not dictionaries. My workflow is described here: [Wiki] Best practices: Test-driven development with Anvil

I would create something like this, one container class:

class KPIs(AttributeToKey):
    def __init__():
        items = anvil.server.call('get_kpis')
        self.items = [
            KPI.from_dict(item)
            for item in items
        ]
    
    def __getitem__(self, index):
        return self.items[index]

    @property
    def counter(self):
        # add properties to the collection
        return sum(1 for item in self.items if item.has_answer)

And one KPI class:

class KPI(AttributeToKey):
    @classmethod
    def from_dict(self, d):
        kpi = KPI()
        kpi._dict = d
        # I get some properties in the constructor
        kpi.prop1 = d.get('prop1', default_prop1)
        return kpi

    @property
    def prop2(self):
        # I calculate other properties in the accessor (usually the ones that are more complex than this one)
        return self.d['prop2']

With this setup, then you can do:

kpis = KPIs()
self.repeating_panel.items = kpis.items
print(kpis.counter)     # this works with code completion
print(kpis['counter'])  # no code completion, but it works with DataGrid databinding (which expects a dictionary like object)
print(kpis[0].prop1, kpis[0]['prop1'])
print(kpis[0].prop2, kpis[0]['prop2'])

Very cool and well done.
I store them in an own table with all data for localisation (i.e. description_en, description_de)
Question decorators:
Right now I only use @anvil.server.callable - why do you need @property?

I have defined kpi.prop1 = d.get('prop1', default_prop1) to show how a read-and-write property can be easily added.

I have decorated the prop2 function with the @property decorator to show that it is possible. You may want to use it for different reasons:

  • because you want the property to be read-only
  • because you need to calculate the value of the property every time it is accessed, rather than pre-calculate it and store it (for example the property Line.length could return the calculated distance between Line.start_point and Line.end_point so it’s correct even after the points have changed)
  • because the property is rarely used, calculating its value is expensive and you want to do it lazily, only when really required
  • because you want to cache it (calculate it once and store its value, so the second time it’s called you just return the calculated value)
  • because you want to define the @prop.setter to execute some code to invalidate the cache or to initialize other fields when the property value is changed
  • etc.

At the end of the day, your form sees a class with a bunch of properties. It doesn’t know if they are simple variables defined in some function like prop1 or they are fully fledged functions exposed as simple properties like prop2, but they just work. And if you use the workflow I describe in the test driven development post, they can be used both as properties and as dictionary values.