# Heuristics



| #  | Heuristic                                               | Meaning                                                            | Example problem                                                               | Better design                                                     |
| -- | ------------------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| 1  | Visibility of system status                             | The system should show users what is happening.                    | User clicks submit and nothing changes, so they do not know if it worked.     | Show a loading spinner, progress bar, or success message.         |
| 2  | Match between system and real world                     | Use familiar language, concepts, and real-world logic.             | A banking app says “execute transaction payload” instead of “transfer money”. | Use user-friendly wording such as “Send payment”.                 |
| 3  | User control and freedom                                | Users need ways to undo, cancel, go back, or exit.                 | User deletes a file and cannot recover it.                                    | Add undo, cancel, back, or confirmation options.                  |
| 4  | Consistency and standards                               | Similar actions should look and behave the same.                   | One page says “checkout”, another says “payment”, another says “finalise”.    | Use the same labels, icons, and layouts across the site.          |
| 5  | Error prevention                                        | Stop mistakes before they happen.                                  | A form lets users submit an invalid email, then fails later.                  | Validate the email before submission and show required formats.   |
| 6  | Recognition rather than recall                          | Show information and options instead of making users remember.     | User must remember a product code from the previous page.                     | Keep key details visible or provide dropdown options.             |
| 7  | Flexibility and efficiency of use                       | Support both beginners and experienced users.                      | Experienced users must click through every slow step every time.              | Add shortcuts, saved preferences, autocomplete, or quick actions. |
| 8  | Aesthetic and minimalist design                         | Remove unnecessary clutter.                                        | Checkout page has ads, popups, banners, and too many buttons.                 | Show only essential payment, delivery, and confirmation details.  |
| 9  | Help users recognise, diagnose, and recover from errors | Error messages should clearly explain the issue and how to fix it. | Form says “Error 400” with no explanation.                                    | Say “Password must include at least one number”.                  |
| 10 | Help and documentation                                  | Provide help when users need guidance.                             | A complex settings page has no instructions or support.                       | Add searchable help, examples, tooltips, or FAQs.                 |


Example:

> This violates **visibility of system status** because the user is not given feedback after pressing submit. This may make them unsure whether the action worked, causing repeated clicks or confusion. A better design would show a loading message followed by a clear confirmation.

## Common comparisons

| Comparison                           | Difference                                                                                                                                              |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Error prevention vs error recovery   | Error prevention stops the mistake before it happens. Error recovery helps the user fix it afterwards.                                                  |
| Recognition vs recall                | Recognition means the user can see the information. Recall means the user must remember it.                                                             |
| Consistency vs match with real world | Consistency is about keeping the interface internally predictable. Match with real world is about using language and concepts users already understand. |
| Minimalist design vs lack of help    | Minimalist design removes clutter, but it should not remove important guidance.                                                                         |

## Quick scenario examples

* **No loading message after clicking submit:** visibility of system status.
* **Technical language users do not understand:** match between system and real world.
* **No back, undo, or cancel button:** user control and freedom.
* **Same button does different things on different pages:** consistency and standards.
* **Form accepts bad input then crashes:** error prevention.
* **User must remember information from another page:** recognition rather than recall.
* **No shortcuts for repeated expert tasks:** flexibility and efficiency of use.
* **Too many competing buttons and banners:** aesthetic and minimalist design.
* **Error message says only “Invalid”:** help users recognise, diagnose, and recover from errors.
* **No help page for a complex process:** help and documentation.

---

<br><br><br>
# JSON helper functions

JSON files are useful when a Flask app needs to **save data permanently** instead of losing it every time the server restarts.

Json store as:

* a list `[]`
* a dictionary`{}`
* a list of dictionaries
* or a dictionary of lists


## Helper function code

```python
import json
import os

def create_if_not_exist(filename, empty_data):
    if not os.path.exists(filename):
        write_json_file(filename, empty_data)

def write_json_file(filename, info_to_save):
    my_file = open(filename, "w")
    json.dump(info_to_save, my_file)
    my_file.close()

def read_json_file(filename):
    my_file = open(filename, "r")
    information = json.load(my_file)
    my_file.close()
    return information
```
<br>


## Use case example

```python
def get_data():
    create_if_not_exist('tournament.json', [])
    data = read_json_file('tournament.json')
    return data


def save_data(data):
    write_json_file('tournament.json', data)
```

<br><br><br>


# Common data function patterns

Examples from practice exam to reference for the coding process:

Important!!! Inside the function, use the parameter names, not random outside variables.




## Pattern 1: Adding to a list with `.append()`

Use `.append()` when the question asks you to add a new record to a list.

```python
data.append(
    {
        "id": id,
        "name": name,
        "faculty": faculty,
        "elo": 1500,
        "past_games": []
    }
)
```

<br>

## Pattern 2: Searching for information in a list of dictionaries


```python
for player in data:
    if player["id"] == id:
        return player
```

<br>


## Pattern 3: Finding the maximum value

Use a tracking variable when you need to find the highest number in a list.

```python
highest_elo = 0
highest_player = None

for player in data:
    if player["elo"] > highest_elo:
        highest_elo = player["elo"]
        highest_player = player

return highest_player
```


## Pattern 4: Counting values in nested lists

This pattern is useful when each dictionary contains another list inside it.

In this example, each player has a list called `past_games`.

```python
for past_game in player["past_games"]:
    if past_game["result"] == "win":
        wins += 1
```


```python
wins = 3
draws = 1
losses = 2

return f"{wins} wins, {draws} draws, {losses} losses"
```

<br>


# Flask and pyhtml route patterns


## Pattern 1: Creating a route and displaying a list of records

Use this when the page needs to show every item in a list, such as every player, product, post, or game.

```python
@app.get("/")
def home_page():
    """
    Home page
    """
    player_data = get_data()
    display = []

    for player in player_data:
        display.append(
            p.a(href=f"/player/{player['id']}")(player["name"])
        )
        display.append(
            p.p(
                f"ID: {player['id']} | "
                f"Elo: {player['elo']} | "
                f"Record: {get_wins_draws_losses_text(player)}"
            )
        )

    return str(
        p.html(
            head_html("Chess Tournament"),
            p.body(
                navbar_html(),
                p.main(
                    p.p("Best of luck for your exam. You've got this!"),
                    p.p("Home page"),
                    *display
                ),
            ),
        )
    )
```

Important !!!

```python
*display
Is better than:
p.p(display)
```


## Pattern 2: Creating links using values from dictionaries

This line creates a link using the player's ID:

```python
p.a(href=f"/player/{player['id']}")(player["name"])
```

If the player dictionary is:

```python
{
    "id": "Mgs",
    "name": "Magnus",
    "elo": 1500
}
```

then the link becomes:

```text
/player/mgs
```

and the clickable text is:

```text
<Magnus>
```



## Pattern 3: Creating a dynamic route for one specific dictionary

Use this when the URL contains a value that decides what information to show.

```python
@app.get("/player/<player_id>")
def player_page(player_id: str):
    """
    Player info page
    """
    page_data = get_player_by_id(get_data(), player_id)

    player_stats = []
    player_stats.append(p.p(f"Name: {page_data['name']}"))
    player_stats.append(p.p(f"Faculty: {page_data['faculty']}"))
    player_stats.append(p.p(f"Elo: {page_data['elo']}"))
    player_stats.append(p.p(f"Record: {get_wins_draws_losses_text(page_data)}"))
    player_stats.append(p.p(f"Past games: {page_data['past_games']}"))

    return str(
        p.html(
            head_html(f"Chess Tournament - {page_data['name']}"),
            p.body(
                navbar_html(),
                p.main(
                    p.h1("Player info page"),
                    *player_stats
                ),
            ),
        )
    )
```


## Pattern 4: Making a form with DROPDOWNS (p.select)

```python
@app.get("/add_player")
def add_player_form():
    """
    Add player form page
    """
    return str(
        p.html(
            head_html("Chess Tournament - Add Player"),
            p.body(
                navbar_html(),
                p.main(
                    p.h1("Add player"),
                    p.p("Add a player to the tournament"),
                    p.form(
                        p.input(type="text", placeholder="Enter ID:", name="ID_tb"),
                        p.br(),
                        p.br(),
                        p.input(type="text", placeholder="Enter Name:", name="Name_tb"),
                        p.br(),
                        p.br(),
                        p.select(
                            p.option("Engineering", value="Engineering"),
                            p.option("Business", value="Business"),
                            p.option("Arts", value="Arts"),
                            p.option("Law", value="Law"),
                            name="Faculty_tb"
                        ),
                        p.br(),
                        p.br(),
                        p.input(type="submit", value="Add Player"),
                        action="/add_player",
                        method="post"
                    )
                ),
            ),
        )
    )
```


> `request.form.get()` references the 'name' of the form


```python
@app.post("/add_player")
def add_player_submit():
    """
    Add player results
    """
    player_id = request.form.get("ID_tb")
    name = request.form.get("Name_tb")
    faculty = request.form.get("Faculty_tb")

    data = get_data()
    add_player(data, player_id, name, faculty)
    save_data(data)

    return redirect(f"/player/{player_id}")
```



<br>

# Cookies

## Main session patterns

| Pattern | Code | Meaning |
| ------- | ---- | ------- |
| Get saved data | `data = session.get("key", [])` | Get the saved value. If it does not exist yet, use `[]`. |
| Save data | `session["key"] = data` | Store the updated value in the cookie/session. |
| Clear data | `session.pop("key", None)` | Remove the saved value. |
| Check if exists | `if "key" in session:` | Check whether something has been saved. |

<br>

## Pattern 1: Loading cookie data

Use this when the user might not have any saved data yet.

```python
scarf_info = session.get("scarf_info", [])
```

What is happening:

1. Flask checks whether `"scarf_info"` exists in the session.
2. If it exists, it loads the saved scarf data.
3. If it does not exist, it uses an empty list instead.

This prevents the app from crashing the first time the user visits the page.

<br>

## Pattern 2: Adding a new item to a cookie list

Use this when the user submits a form and you want to add the new item to the existing cookie data.

```python
@app.post("/submit")
def submit():
    colour = request.form.get("Colour_dd")
    length = int(request.form.get("length_tb"))

    scarf_info = session.get("scarf_info", [])

    scarf_info.append(
        {
            "colour": colour,
            "length": length,
        }
    )

    session["scarf_info"] = scarf_info

    return redirect("/")
```

What is happening:

1. `request.form.get(...)` gets the submitted form values.
2. `session.get("scarf_info", [])` loads the existing scarf segments.
3. `.append(...)` adds the new segment to the list.
4. `session["scarf_info"] = scarf_info` saves the updated list back into the session.


<br>

## Pattern 3: Displaying saved cookie data on a page

```python
@app.get("/")
def homepage():
    scarf_info = session.get("scarf_info", [])

    rows = []

    for segment in scarf_info:
        rows.append(
            p.tr(
                p.td(segment["colour"]),
                p.td(segment["length"])
            )
        )

    return str(
        p.html(
            p.body(
                p.h1("Scarf App"),
                p.h2("Scarf preview"),
                p.table(
                    p.tr(
                        p.th("Colour"),
                        p.th("Length")
                    ),
                    *rows
                )
            )
        )
    )
```


<br>


## Pattern 4: Clearing cookie data



```python
@app.post("/order")
def order():
    scarf_info = session.get("scarf_info", [])

    if len(scarf_info) > 0:
        total_cost = calculate_scarf_cost(scarf_info)
    else:
        total_cost = 0

    session.pop("scarf_info", None)

```

<br>


## CSS

```python
p.head(
    p.title("Page Title"),
    p.link(rel="stylesheet", href="/static/style.css")
)
```

In pyhtml, use `class_` instead of `class`:

```python
p.div("Content", class_="card")
p.button("Submit", type="submit", class_="primary-button")
p.table(..., class_="data-table")
```
<br>

## Quickfire CSS attributes to remember

| Page type | CSS ideas / attributes to use |
| --------- | ----------------------------- |
| Whole page | `font-family`, `margin: 0`, `padding: 0`, `background`, `color`, `min-height: 100vh`, `line-height` |
| Page spacing | `padding`, `margin`, `max-width`, `margin: 0 auto`, `width` |
| Main titles | `font-size`, `font-weight`, `letter-spacing`, `line-height`, `margin`, `text-align` |
| Navigation | `display: flex`, `align-items: center`, `gap`, `padding`, `border-bottom`, `background`, `position: sticky`, `top: 0` |
| Links | `text-decoration: none`, `color`, `font-weight`, `border-radius`, `padding`, `:hover`, `transition` |
| Cards / boxes | `background`, `border`, `border-radius`, `padding`, `box-shadow`, `margin-top`, `max-width` |
| Forms | `display: grid` or `display: flex`, `gap`, `padding`, `border`, `border-radius`, `background` |
| Inputs / dropdowns | `font-size`, `padding`, `border`, `border-radius`, `font-family: inherit`, `background`, `width` |
| Buttons | `background`, `color`, `border: none`, `border-radius`, `padding`, `cursor: pointer`, `font-weight`, `:hover` |
| Tables | `width: 100%`, `border-collapse`, `border-spacing`, `border`, `padding`, `text-align: left`, `border-bottom` |
| Table headers | `background`, `color`, `font-weight`, `text-align`, `padding` |
| Table rows | `tr:nth-child(even)`, `background`, `border-bottom` |
| Multiple cards | `display: grid`, `grid-template-columns: 1fr 1fr 1fr`, `gap` |
| Buttons in a row | `display: flex`, `justify-content`, `align-items`, `gap`, `margin-top` |
| Alignment | `text-align: center`, `text-align: left`, `align-items: center`, `justify-content: center`, `margin: 0 auto` |
| Hover effects | `:hover`, `background`, `transform: translateY(-1px)`, `transition`, `text-decoration: underline` |




Simple rule:

```text
.  class_ can be reused many times.
#  id  should generally be used once.
```
`grid-template-columns: 1fr 1fr;`.
