Repository: UKGovernmentBEIS/inspect_ai
Issue: Log Viewer fails when score value is not C/P/I in version > 0.3.18
Root cause: Validation Gap

I am trying to use a slightly modified version of `model_graded_fact()` that returns letter scores (A, B, C, D, E). I have a custom accuracy metric that maps letters to 1/0 (ex. {A, B, C} -> 1, {D, E} -> 0).

Prior to 0.3.18 this worked fine with the log viewer. Starting in 0.13.8 I get this error when I run `inspect view`:

```
Error: scoreDescriptor.render is not a function
at TypeError: scoreDescriptor.render is not a function
    at Object.rendered (http://localhost:7575/src/samples/SamplesDescriptor.mjs:225:40)
    at W.SampleScores [as constructor] (http://localhost:7575/src/samples/SampleScores.mjs:9:22)
    at W.ce [as render] (http://localhost:7575/preact/preact.mjs:2:9028)
    at G (http://localhost:7575/preact/preact.mjs:2:6544)
    at _e (http://localhost:7575/preact/preact.mjs:2:1971)
    at fe (http://localhost:7575/preact/preact.mjs:2:8274)
    at G (http://localhost:7575/preact/preact.mjs:2:7092)
    at _e (http://localhost:7575/preact/preact.mjs:2:1971)
    at fe (http://localhost:7575/preact/preact.mjs:2:8274)
    at G (http://localhost:7575/preact/preact.mjs:2:7092)
```

## Example task

This is a simplified version. In the actual thing I have more complex criteria for grades A-E and the custom accuracy metric.

```python
from inspect_ai import Task, task
from inspect_ai.dataset import example_dataset
from inspect_ai.scorer import model_graded_fact
from inspect_ai.solver import generate

@task
def theory_of_mind():
    return Task(
        dataset=example_dataset("theory_of_mind"),
        plan=generate(),
        scorer=model_graded_fact(
            instructions="""
Randomly print only a single choice from "A" or "B" (without quotes or punctuation) on its own line as the answer. At the end, repeat just the answer by itself on a new line.
""",
            grade_pattern=r"(?i)\n([AB])\n*$",
            model="openai/gpt-4o",
        ),
    )
```

## Example log

```json
{
  "version": 2,
  "status": "success",
  "eval": {
    "task": "theory_of_mind",
    "task_version": 0,
    "task_id": "GvV83CQzjsJL72DV9x6MX7",
    "run_id": "4kNm7EZs3EobtRYkKq8gst",
    "created": "2024-08-01T15:09:57-04:00",
    "dataset": {
      "name": "theory_of_mind",
      "location": "example://theory_of_mind",
      "samples": 100,
      "shuffled": false
    },
    "model": "openai/gpt-4o",
    "task_attribs": {},
    "task_args": {},
    "model_args": {},
    "config": {
      "limit": 1
    },
    "packages": {
      "inspect_ai": "0.3.18"
    }
  },
  "plan": {
    "name": "plan",
    "steps": [
      {
        "solver": "generate",
        "params": {}
      }
    ],
    "config": {}
  },
  "results": {
    "scores": [
      {
        "name": "model_graded_fact",
        "scorer": "model_graded_fact",
        "params": {
          "instructions": "\nRandomly print only a single choice from \"A\" or \"B\" (without quotes or punctuation) on its own line as the answer. At the end, repeat just the answer by itself on a new line.\n",
          "grade_pattern": "(?i)\\n([AB])\\n*$",
          "model": "openai/gpt-4o"
        },
        "metrics": {
          "accuracy": {
            "name": "accuracy",
            "value": 0.0,
            "options": {}
          },
          "bootstrap_std": {
            "name": "bootstrap_std",
            "value": 0.0,
            "options": {}
          }
        }
      }
    ]
  },
  "stats": {
    "started_at": "2024-08-01T15:09:57-04:00",
    "completed_at": "2024-08-01T15:09:59-04:00",
    "model_usage": {
      "openai/gpt-4o": {
        "input_tokens": 239,
        "output_tokens": 13,
        "total_tokens": 252
      }
    }
  },
  "samples": [
    {
      "id": 1,
      "epoch": 1,
      "input": [
        {
          "content": "Jackson entered the hall. Chloe entered the hall. The boots is in the bathtub. Jackson exited the hall. Jackson entered the dining_room. Chloe moved the boots to the pantry. Where was the boots at the beginning?",
          "source": "input",
          "role": "user"
        }
      ],
      "target": "bathtub",
      "messages": [
        {
          "content": "Jackson entered the hall. Chloe entered the hall. The boots is in the bathtub. Jackson exited the hall. Jackson entered the dining_room. Chloe moved the boots to the pantry. Where was the boots at the beginning?",
          "source": "input",
          "role": "user"
        },
        {
          "content": "The boots were in the bathtub at the beginning.",
          "source": "generate",
          "role": "assistant"
        }
      ],
      "output": {
        "model": "gpt-4o-2024-05-13",
        "choices": [
          {
            "message": {
              "content": "The boots were in the bathtub at the beginning.",
              "source": "generate",
              "role": "assistant"
            },
            "stop_reason": "stop"
          }
        ],
        "usage": {
          "input_tokens": 51,
          "output_tokens": 10,
          "total_tokens": 61
        }
      },
      "scores": {
        "model_graded_fact": {
          "value": "A",
          "answer": "The boots were in the bathtub at the beginning.",
          "explanation": "A\nA",
          "metadata": {
            "grading": [
              {
                "content": "\nYou are comparing a submitted answer to an expert answer on a given question. Here is the data:\n\n[BEGIN DATA]\n************\n[Question]: Jackson entered the hall. Chloe entered the hall. The boots is in the bathtub. Jackson exited the hall. Jackson entered the dining_room. Chloe moved the boots to the pantry. Where was the boots at the beginning?\n************\n[Expert]: bathtub\n************\n[Submission]: The boots were in the bathtub at the beginning.\n************\n[END DATA]\n\nCompare the factual content of the submitted answer with the expert answer. Ignore any differences in style, grammar, or punctuation.\n\nDoes the submission contain the content in the expert answer?\n\n\nRandomly print only a single choice from \"A\" or \"B\" (without quotes or punctuation) on its own line as the answer. At the end, repeat just the answer by itself on a new line.\n\n",
                "role": "user"
              },
              {
                "content": "A\nA",
                "source": "generate",
                "role": "assistant"
              }
            ]
          }
        }
      },
      "metadata": {}
    }
  ],
  "logging": [
    {
      "level": "warning",
      "message": "Unable to convert value to float: A",
      "created": 1722539398788.3022
    },
    {
      "level": "warning",
      "message": "Unable to convert value to float: A",
      "created": 1722539398791.9668
    }
  ]
}
```

@dragonstyle Could we try to fix this for the upcoming release?

Yeah I'll look at it tonight!