diff --git a/isic_challenge_scoring/load_csv.py b/isic_challenge_scoring/load_csv.py
index 8caef0a..d0156a1 100644
--- a/isic_challenge_scoring/load_csv.py
+++ b/isic_challenge_scoring/load_csv.py
@@ -29,7 +29,7 @@ def parse_truth_csv(csv_file_stream: TextIO) -> Tuple[pd.DataFrame, pd.DataFrame
 
 def parse_csv(csv_file_stream: TextIO, categories: pd.Index) -> pd.DataFrame:
     try:
-        probabilities = pd.read_csv(csv_file_stream, header=0)
+        probabilities = pd.read_csv(csv_file_stream, header=0, index_col=False)
     except pd.errors.ParserError as e:
         # TODO: Test this case
         raise ScoreException(f'Could not parse CSV: "{str(e)}"')
@@ -37,17 +37,28 @@ def parse_csv(csv_file_stream: TextIO, categories: pd.Index) -> pd.DataFrame:
     if 'image' not in probabilities.columns:
         raise ScoreException('Missing column in CSV: "image".')
 
+    # Pandas represents strings as 'O' (object)
+    if probabilities['image'].dtype != np.dtype('O'):
+        # Coercing to 'U' (unicode) ensures that even NaN values are converted;
+        # however, the resulting type is still 'O'
+        probabilities['image'] = probabilities['image'].astype(np.dtype('U'))
+
     probabilities['image'] = probabilities['image'].str.replace(r'\.jpg$', '', case=False)
 
-    probabilities.set_index('image', drop=True, inplace=True, verify_integrity=True)
+    if not probabilities['image'].is_unique:
+        duplicate_images = probabilities['image'][probabilities['image'].duplicated()].unique()
+        raise ScoreException(f'Duplicate image rows detected in CSV: {duplicate_images.tolist()}.')
+
+    # The duplicate check is the same as performed by 'verify_integrity'
+    probabilities.set_index('image', drop=True, inplace=True, verify_integrity=False)
 
     missing_columns = categories.difference(probabilities.columns)
     if not missing_columns.empty:
-        raise ScoreException(f'Missing columns in CSV: {list(missing_columns)}.')
+        raise ScoreException(f'Missing columns in CSV: {missing_columns.tolist()}.')
 
     extra_columns = probabilities.columns.difference(categories)
     if not extra_columns.empty:
-        raise ScoreException(f'Extra columns in CSV: {list(extra_columns)}.')
+        raise ScoreException(f'Extra columns in CSV: {extra_columns.tolist()}.')
 
     # sort by the order in categories
     probabilities = probabilities.reindex(categories, axis='columns')

ValueError: Index has duplicate keys: Index(['0.12345', 'ISIC_0012345'], dtype='object', name='image')
Sentry Issue: [STADE-1S](https://sentry.io/organizations/isic/issues/1149020425/?referrer=github_integration)

```
ValueError: Index has duplicate keys: Index(['0.12345', 'ISIC_0012345'], dtype='object', name='image')
  File "core/tasks.py", line 152, in score_submission
    results = compute_metrics(truth_file, prediction_file)
  File "isic_challenge_scoring/task3.py", line 18, in compute_metrics
    prediction_probabilities = parse_csv(prediction_file_stream, categories)
  File "isic_challenge_scoring/load_csv.py", line 42, in parse_csv
    probabilities.set_index('image', drop=True, inplace=True, verify_integrity=True)
  File "pandas/core/frame.py", line 4189, in set_index
    dup=duplicates))

internal error scoring submission XXXX
```
diff --git a/tests/test_load_csv.py b/tests/test_load_csv.py
index b72384e..e81f88d 100644
--- a/tests/test_load_csv.py
+++ b/tests/test_load_csv.py
@@ -125,6 +125,26 @@ def test_parse_csv_misnamed_columns(categories):
     assert 'Missing columns in CSV: [\'DF\', \'NV\'].' == str(exc_info.value)
 
 
+def test_parse_csv_trailing_delimiters(categories):
+    prediction_file_stream = io.StringIO(
+        'image,MEL,NV,BCC,AKIEC,BKL,DF,VASC\n'
+        'ISIC_0000123,1.0,0.0,0.0,0.0,0.0,0.0,0.0,\n'
+        'ISIC_0000124,0.0,1.0,0.0,0.0,0.0,0.0,0.0,\n'
+    )
+
+    # If all data rows have trailing delimiters, 'pd.read_csv' can misinterpret the data without
+    # 'index_col=False'
+    prediction_probabilities = load_csv.parse_csv(prediction_file_stream, categories)
+
+    assert prediction_probabilities.equals(
+        pd.DataFrame(
+            [[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
+            index=['ISIC_0000123', 'ISIC_0000124'],
+            columns=categories,
+        )
+    )
+
+
 def test_parse_csv_reordered_columns(categories):
     prediction_file_stream = io.StringIO(
         'NV,BCC,BKL,DF,AKIEC,MEL,VASC,image\n' '0.0,0.0,0.0,0.0,0.0,1.0,0.0,ISIC_0000123\n'
@@ -150,6 +170,17 @@ def test_parse_csv_missing_index(categories):
     assert 'Missing column in CSV: "image".' == str(exc_info.value)
 
 
+def test_parse_csv_invalid_type_index(categories):
+    prediction_file_stream = io.StringIO(
+        'image,MEL,NV,BCC,AKIEC,BKL,DF,VASC\n' '5,1.0,0.0,0.0,0.0,0.0,0.0,0.0\n'
+    )
+
+    prediction_probabilities = load_csv.parse_csv(prediction_file_stream, categories)
+
+    # Apparent numeric 'image' fields should be coerced to string / NumPy 'O'
+    assert prediction_probabilities.index.is_object()
+
+
 def test_parse_csv_missing_values(categories):
     prediction_file_stream = io.StringIO(
         'image,MEL,NV,BCC,AKIEC,BKL,DF,VASC\n'
@@ -199,6 +230,23 @@ def test_parse_csv_out_of_range_values(categories):
     )
 
 
+def test_parse_csv_duplicate_images(categories):
+    prediction_file_stream = io.StringIO(
+        'image,MEL,NV,BCC,AKIEC,BKL,DF,VASC\n'
+        'ISIC_0000123,1.0,0.0,0.0,0.0,0.0,0.0,0.0\n'
+        'ISIC_0000123,0.0,1.0,0.0,0.0,0.0,0.0,0.0\n'
+        'ISIC_0000124,0.0,0.0,1.0,0.0,0.0,0.0,0.0\n'
+        'ISIC_0000124.jpg,0.0,0.0,1.0,0.0,0.0,0.0,0.0\n'
+    )
+
+    with pytest.raises(ScoreException) as exc_info:
+        load_csv.parse_csv(prediction_file_stream, categories)
+
+    assert 'Duplicate image rows detected in CSV: [\'ISIC_0000123\', \'ISIC_0000124\'].' == str(
+        exc_info.value
+    )
+
+
 def test_validate_rows_missing_images(categories):
     truth_probabilities = pd.DataFrame(
         [[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]],

pip install -e .[dev]
parse_log_pytest
pytest
pytest black flake8 flake8-blind-except flake8-docstrings flake8-import-order mypy pep8-naming
apt-get update apt-get install -y gcc
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
black==25.1.0
click==8.1.8
click-pathlib==2020.3.13.0
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
flake8==7.2.0
flake8-blind-except==0.2.1
flake8-docstrings==1.7.0
flake8-import-order==0.18.2
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
-e git+https://github.com/ImageMarkup/isic-challenge-scoring.git@9b1fc71389cf5b23495f85f4976e2f6a3fba870d#egg=isic_challenge_scoring
joblib==1.4.2
mccabe==0.7.0
mypy==1.15.0
mypy-extensions==1.0.0
numpy==2.0.2
packaging @ file:///croot/packaging_1734472117206/work
pandas==2.2.3
pathspec==0.12.1
pep8-naming==0.14.1
pillow==11.1.0
platformdirs==4.3.7
pluggy @ file:///croot/pluggy_1733169602837/work
pycodestyle==2.13.0
pydocstyle==6.3.0
pyflakes==3.3.2
pytest @ file:///croot/pytest_1738938843180/work
python-dateutil==2.9.0.post0
pytz==2025.2
scikit-learn==1.6.1
scipy==1.13.1
six==1.17.0
snowballstemmer==2.2.0
threadpoolctl==3.6.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
typing_extensions==4.13.0
tzdata==2025.2

name: isic-challenge-scoring
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - black==25.1.0
      - click==8.1.8
      - click-pathlib==2020.3.13.0
      - flake8==7.2.0
      - flake8-blind-except==0.2.1
      - flake8-docstrings==1.7.0
      - flake8-import-order==0.18.2
      - isic-challenge-scoring==4.2.2
      - joblib==1.4.2
      - mccabe==0.7.0
      - mypy==1.15.0
      - mypy-extensions==1.0.0
      - numpy==2.0.2
      - pandas==2.2.3
      - pathspec==0.12.1
      - pep8-naming==0.14.1
      - pillow==11.1.0
      - platformdirs==4.3.7
      - pycodestyle==2.13.0
      - pydocstyle==6.3.0
      - pyflakes==3.3.2
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - scikit-learn==1.6.1
      - scipy==1.13.1
      - six==1.17.0
      - snowballstemmer==2.2.0
      - threadpoolctl==3.6.0
      - typing-extensions==4.13.0
      - tzdata==2025.2
prefix: /opt/conda/envs/isic-challenge-scoring

tests/test_load_csv.py::test_parse_csv_trailing_delimiters tests/test_load_csv.py::test_parse_csv_invalid_type_index
tests/test_load_csv.py::test_parse_csv tests/test_load_csv.py::test_parse_csv_duplicate_images
tests/test_load_csv.py::test_parse_truth_csv tests/test_load_csv.py::test_parse_truth_csv_legacy tests/test_load_csv.py::test_parse_csv_missing_columns tests/test_load_csv.py::test_parse_csv_extra_columns tests/test_load_csv.py::test_parse_csv_misnamed_columns tests/test_load_csv.py::test_parse_csv_reordered_columns tests/test_load_csv.py::test_parse_csv_missing_index tests/test_load_csv.py::test_parse_csv_missing_values tests/test_load_csv.py::test_parse_csv_non_float_columns tests/test_load_csv.py::test_parse_csv_out_of_range_values tests/test_load_csv.py::test_validate_rows_missing_images tests/test_load_csv.py::test_validate_rows_extra_images tests/test_load_csv.py::test_sort_rows
isic_challenge_scoring/load_csv.py
isic_challenge_scoring/load_csv.py