diff --git a/pyproject.toml b/pyproject.toml
index 6c0fe14fe0b1e29cfd2e6d6bc8e5a3c341838202..f9f5fc8e012d1eac82be68c01fee02a046e23318 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -15,3 +15,20 @@ generated-members = ["FindSequence"]
 
 [tool.pylint.FORMAT]
 max-line-length=80
+
+[tool.pylint.deprecated_builtins]
+
+# Run the spell check every once in a while, having it enabled always, is too
+# annoying.
+[tool.pylint.spelling]
+max-spelling-suggestions = 4
+
+spelling-dict = "en_GB"
+
+spelling-ignore-comment-directives = "fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:"
+
+spelling-ignore-words = ""
+
+spelling-private-dict-file = ".spelling"
+
+spelling-store-unknown-words = false
diff --git a/validation/.spelling b/validation/.spelling
new file mode 100644
index 0000000000000000000000000000000000000000..5d4c8a6c473cac08229109dbc9ffa4d35912d146
--- /dev/null
+++ b/validation/.spelling
@@ -0,0 +1,9 @@
+CIF
+Dockerfile
+MSA
+UTF
+gzipped
+stdout
+uncategorised
+usr
+whitespaces
diff --git a/validation/Dockerfile b/validation/Dockerfile
index 34a884df7e9791c67ab2dd2365ae1c1f1c2af9c6..a75f3a9e6acb7b3b26efedd7f16143a5a718bb09 100644
--- a/validation/Dockerfile
+++ b/validation/Dockerfile
@@ -1,5 +1,5 @@
 ARG VERSION_PYTHON="3.9"
-ARG VERSION_BASE_IMAGE="python:${VERSION_PYTHON}-alpine3.17"
+ARG VERSION_BASE_IMAGE="python:${VERSION_PYTHON}-alpine3.19"
 FROM ${VERSION_BASE_IMAGE}
 # We need to declare ARGs again which were declared before the build stage
 # (FROM directive), otherwise they won't be available in this stage.
@@ -80,7 +80,7 @@ COPY --chmod=755 validate-mmcif-file.py /usr/local/bin/validate-mmcif-file
 ## https://github.com/ihmwg/ModelCIF/blob/master/dist/mmcif_ma.dic.
 ## Dictionaries do not change that frequently therefore we skip the hassle of
 ## keeping them in an external volume.
-ARG USE_DICT_VERSION="1.4.5"
+ARG USE_DICT_VERSION="1.4.6"
 ENV USE_DICT_VERSION=${USE_DICT_VERSION}
 LABEL org.modelarchive.dict_release="${USE_DICT_VERSION}"
 WORKDIR ${SRC_DIR}
@@ -91,7 +91,7 @@ RUN set -e pipefail; \
     export _GIT_URL="https://raw.github.com/ihmwg/ModelCIF/master"; \
     # Use the path of an actual commit to keep the dict immutable (RCSB refuses
     # to use Git tags for versions).
-    export _MA_DICT_URL="https://raw.githubusercontent.com/ihmwg/ModelCIF/ba728c4/archive/mmcif_ma-v${USE_DICT_VERSION}.dic"; \
+    export _MA_DICT_URL="https://raw.githubusercontent.com/ihmwg/ModelCIF/d18ba38/archive/mmcif_ma-v${USE_DICT_VERSION}.dic"; \
     mkdir ${_DICT_DIR}; \
     mkdir ${MMCIF_DICTS_DIR}; \
     cd ${_DICT_DIR}; \
@@ -117,11 +117,15 @@ RUN set -e pipefail; \
     ## Build the PDBx/mmCIF SDB
     DictToSdb -ddlFile mmcif_ddl.dic \
               -dictFile mmcif_pdbx_v50.dic \
-              -dictSdbFile ${MMCIF_DICTS_DIR}/mmcif_pdbx_v50.dic.sdb; \
+              -dictSdbFile ${MMCIF_DICTS_DIR}/mmcif_pdbx_v50.sdb; \
     #
     ## Get versions of ModelCIF & PDBx/mmCIF dictionaries
     get-mmcif-dict-versions --child-location ${_MA_DICT_URL} mmcif_ma.dic; \
     mv mmcif_ma_version.json ${MMCIF_DICTS_DIR}/; \
+    get-mmcif-dict-versions --child-location ${_DICT_URL}/mmcif_pdbx_v50.dic \
+                            --output mmcif_pdbx_v50_version.json \
+                            mmcif_pdbx_v50.dic; \
+    mv mmcif_pdbx_v50_version.json ${MMCIF_DICTS_DIR}/; \
     #
     ## Make SDBs readable and keep possible error logs from building them
     mv *.log ${MMCIF_DICTS_DIR}/ 2>/dev/null || :; \
@@ -137,10 +141,22 @@ RUN set -e pipefail; \
 ARG ADD_DEV
 RUN set -e pipefail; \
     if test xYES = x`echo ${ADD_DEV} | tr '[:lower:]' '[:upper:]'`; then \
-      apk add bash emacs gcc build-base; \
-      /usr/local/bin/python -m pip install pylint black; \
+      apk add bash \
+              binutils \
+              build-base \
+              emacs \
+              enchant2-dev \
+              gcc \
+              hunspell-en-gb \
+              py3-enchant; \
+      /usr/local/bin/python -m pip install pylint[spelling] black; \
       apk del gcc build-base; \
-    fi
+    fi; \
+    # the alias assumes you are in the directory containing the code \
+    echo "alias black_n_pylint=\"black --config ../pyproject.toml " \
+         "test-suite.py validate-mmcif-file.py && pylint " \
+         "--rc-file=../pyproject.toml test-suite.py validate-mmcif-file.py\"" \
+         >> /etc/bash/bashrc
 
 ## Add a dedicated user for mmCIF file validation
 ## MMCIF_USER_ID can be used to avoid file permission issues in development.
diff --git a/validation/README.md b/validation/README.md
index 7072d9b87e7f1c048b877ac047398da20ec2ef0d..24758bd57b3dc0d6d6d5876a48cca04b28db4e52 100644
--- a/validation/README.md
+++ b/validation/README.md
@@ -93,7 +93,7 @@ $
 
 **`--help`** (**`-h`**) Print a help/ usage page for the validation tool.
 
-**`--dict-sdb <SDB FILE>`** (**`-d`**) Format dictionary in (binary) SDB format used for validating a ModelCIF file. The Docker container comes with a SDB for ModelCIF (`/usr/local/share/mmcif-dict-suite/mmcif_ma.sdb`) and one for the original PDBx/mmCIF (`/usr/local/share/mmcif-dict-suite/mmcif_pdbx_v50.dic.sdb`) format.
+**`--dict-sdb <SDB FILE>`** (**`-d`**) Format dictionary in (binary) SDB format used for validating a ModelCIF file. The Docker container comes with a SDB for ModelCIF (`/usr/local/share/mmcif-dict-suite/mmcif_ma.sdb`) and one for the original PDBx/mmCIF (`/usr/local/share/mmcif-dict-suite/mmcif_pdbx_v50.sdb`) format.
 
 **`--out-file <JSON FILE>`** (**`-o`**) Instead of printing the output to `stdout`, store it in a JSON formatted file.
 
diff --git a/validation/test-suite.py b/validation/test-suite.py
index 494e4ec8cc9629dc627a92799862cf197fa8c351..88de428251a9871338d7332e558da85cecdc93a0 100644
--- a/validation/test-suite.py
+++ b/validation/test-suite.py
@@ -22,7 +22,7 @@ DCKR_IMG_RPO = (  # Docker image "name"
     "registry.scicore.unibas.ch/schwede/modelcif-converters/"
     + "mmcif-dict-suite"
 )
-# collection of docker commads used
+# collection of docker commands used
 DCKR_CMDS = {
     "build": [DCKR, "build"],
     "images": [DCKR, "images", "--format", "json"],
@@ -39,7 +39,7 @@ def _parse_args():
         "--test-file",
         default=None,
         action="store",
-        help="Only run test for this file.",
+        help="Only run test for this ModelCIF/ mmCIF file.",
     )
     parser.add_argument(
         "-l",
@@ -116,8 +116,8 @@ def _check_docker_installed():
 
 
 def _get_modelcif_dic_version():
-    """Get the latest versionstring of the ModelCIF dictionary from the
-    official GitHub repo."""
+    """Get the latest version string of the ModelCIF dictionary from the
+    official GitHub repository."""
     rspns = requests.get(
         "https://api.github.com/repos/ihmwg/ModelCIF/contents/archive",
         headers={"accept": "application/vnd.github+json"},
@@ -331,9 +331,42 @@ def _main():
             "status": "aborted",
             "diagnosis": [],
         },
+        # missing items, key & mandatory items missing
+        "missing_items.cif": {
+            "ret_val": 2,
+            "errors": [],
+            "cifcheck-errors": [],
+            "status": "completed",
+            "diagnosis": [
+                'ERROR - In block "Q9Y5J9-Q9Y5L4_UNRELAXED_RANK_1_MODEL_5", '
+                + 'key item "entity_id" not found in category '
+                + '"entity_poly_seq"',
+                'ERROR - In block "Q9Y5J9-Q9Y5L4_UNRELAXED_RANK_1_MODEL_5", '
+                + 'mandatory item "entity_id" is not in category '
+                + '"entity_poly_seq"',
+                'ERROR - In block "Q9Y5J9-Q9Y5L4_UNRELAXED_RANK_1_MODEL_5", '
+                + 'key item "entity_id" not found in category '
+                + '"pdbx_poly_seq_scheme"',
+                'ERROR - In block "Q9Y5J9-Q9Y5L4_UNRELAXED_RANK_1_MODEL_5", '
+                + 'mandatory item "entity_id" is not in category '
+                + '"pdbx_poly_seq_scheme"',
+                'ERROR - In block "Q9Y5J9-Q9Y5L4_UNRELAXED_RANK_1_MODEL_5", '
+                + 'mandatory item "label_entity_id" is not in category '
+                + '"atom_site"',
+            ],
+        },
+        # missing category
+        "missing_category.cif": {
+            "ret_val": 2,
+            "errors": [],
+            "cifcheck-errors": [],
+            "status": "completed",
+            "diagnosis": [
+                '++ERROR - In block "Q9Y5J9-Q9Y5L4_UNRELAXED_RANK_1_MODEL_5", '
+                + 'parent category "entry", of category "struct", is missing.'
+            ],
+        },
         # duplicated item in _loop category
-        # missing category (entity)
-        # missing item (struct_ref.db_name)
         # parent-child relationship issue (remove an atom_type.symbol)
     }
 
@@ -352,7 +385,8 @@ def _main():
     if not opts.local:
         # Make sure Docker is installed and necessary commands are available.
         _do_step(_check_docker_installed, "checking Docker installation")
-        # Get expected image tag (latest ModelCIF dic version from GitHub)
+        # Get expected image tag (latest ModelCIF dictionary version from
+        # GitHub).
         dic_version = _do_step(
             _get_modelcif_dic_version,
             "fetching latest ModelCIF dictionary version",
@@ -405,6 +439,3 @@ def _main():
 
 if __name__ == "__main__":
     _main()
-
-#  LocalWords:  pylint argparse ArgumentParser subprocess sys DCKR args exc
-#  LocalWords:  stdout stderr FileNotFoundError CalledProcessError returncode
diff --git a/validation/test_files/missing_category.cif b/validation/test_files/missing_category.cif
new file mode 100644
index 0000000000000000000000000000000000000000..b7ceda90a7817636ed88e5c6b5b8b2fa26b684c3
--- /dev/null
+++ b/validation/test_files/missing_category.cif
@@ -0,0 +1,407 @@
+data_Q9Y5J9-Q9Y5L4_UNRELAXED_RANK_1_MODEL_5
+_struct.entry_id Q9Y5J9-Q9Y5L4_UNRELAXED_RANK_1_MODEL_5
+_struct.pdbx_model_details 'Dimer model generated for TIMM8B and TIMM13, produced using AlphaFold-Multimer (AlphaFold v2.2.0) as implemented by ColabFold (v1.2.0) which uses MMseqs2 for MSA generation (UniRef30 + Environmental).'
+_struct.pdbx_structure_determination_methodology computational
+_struct.title 'Predicted interaction between TIMM8B and TIMM13'
+_audit_conform.dict_location https://raw.githubusercontent.com/ihmwg/ModelCIF/557bda7/base/mmcif_ma-core.dic
+_audit_conform.dict_name mmcif_ma.dic
+_audit_conform.dict_version 1.4.1
+#
+loop_
+_citation.id
+_citation.title
+_citation.journal_abbrev
+_citation.journal_volume
+_citation.page_first
+_citation.page_last
+_citation.year
+_citation.pdbx_database_id_PubMed
+_citation.pdbx_database_id_DOI
+1 'ColabFold: making protein folding accessible to all.' 'Nature Methods' 19 679
+682 2022 35637307 10.1038/s41592-022-01488-1
+2
+'MMseqs2 desktop and local web server app for fast, interactive sequence searches.'
+Bioinformatics 35 2856 2858 2019 30615063 10.1093/bioinformatics/bty1057
+3 'Protein complex prediction with AlphaFold-Multimer.' bioRxiv . . . 2021 .
+10.1101/2021.10.04.463034
+#
+#
+loop_
+_citation_author.citation_id
+_citation_author.name
+_citation_author.ordinal
+1 'Mirdita, M.' 1
+1 'Schuetze, K.' 2
+1 'Moriwaki, Y.' 3
+1 'Heo, L.' 4
+1 'Ovchinnikov, S.' 5
+1 'Steinegger, M.' 6
+2 'Mirdita, M.' 7
+2 'Steinegger, M.' 8
+2 'Soeding, J.' 9
+3 'Evans, R.' 10
+3 "O'Neill, M." 11
+3 'Pritzel, A.' 12
+3 'Antropova, N.' 13
+3 'Senior, A.' 14
+3 'Green, T.' 15
+3 'Zidek, A.' 16
+3 'Bates, R.' 17
+3 'Blackwell, S.' 18
+3 'Yim, J.' 19
+3 'Ronneberger, O.' 20
+3 'Bodenstein, S.' 21
+3 'Zielinski, M.' 22
+3 'Bridgland, A.' 23
+3 'Potapenko, A.' 24
+3 'Cowie, A.' 25
+3 'Tunyasuvunakool, K.' 26
+3 'Jain, R.' 27
+3 'Clancy, E.' 28
+3 'Kohli, P.' 29
+3 'Jumper, J.' 30
+3 'Hassabis, D.' 31
+#
+#
+loop_
+_software.pdbx_ordinal
+_software.name
+_software.classification
+_software.description
+_software.version
+_software.type
+_software.location
+_software.citation_id
+1 ColabFold 'model building' 'Structure prediction' 1.2.0 package
+https://github.com/sokrypton/ColabFold 1
+2 MMseqs2 'data collection' 'Many-against-Many sequence searching' . package
+https://github.com/soedinglab/mmseqs2 2
+3 AlphaFold-Multimer 'model building' 'Structure prediction' . package
+https://github.com/deepmind/alphafold 3
+#
+#
+loop_
+_ma_software_parameter.parameter_id
+_ma_software_parameter.group_id
+_ma_software_parameter.data_type
+_ma_software_parameter.name
+_ma_software_parameter.value
+_ma_software_parameter.description
+1 1 boolean use_templates NO .
+2 1 boolean use_amber NO .
+3 1 string msa_mode 'MMseqs2 (UniRef+Environmental)' .
+4 1 string model_type AlphaFold2-multimer-v2 .
+5 1 integer num_models 5 .
+6 1 integer num_recycles 3 .
+7 1 integer-csv model_order 3,4,5,1,2 .
+8 1 boolean keep_existing_results YES .
+9 1 string rank_by multimer .
+10 1 string pair_mode unpaired+paired .
+11 1 string host_url https://api.colabfold.com .
+12 1 integer 'stop_at_score' 100 .
+13 1 float recompile_padding 1.100 .
+14 1 boolean recompile_all_models YES .
+15 1 string commit b532e910b15434f707f0b7460abc25c70fcb9b26 .
+16 1 string version 1.2.0 .
+#
+#
+loop_
+_ma_software_group.ordinal_id
+_ma_software_group.group_id
+_ma_software_group.software_id
+_ma_software_group.parameter_group_id
+1 1 1 1
+2 1 2 1
+3 1 3 1
+#
+#
+loop_
+_audit_author.name
+_audit_author.pdbx_ordinal
+'Bartolec, T.K.' 1
+'Vazquez-Campos, X.' 2
+'Norman, A.' 3
+'Luong, C.' 4
+'Payne, R.J.' 5
+'Wilkins, M.R.' 6
+'Mackay, J.P.' 7
+'Low, J.K.K.' 8
+#
+#
+loop_
+_chem_comp.id
+_chem_comp.type
+_chem_comp.name
+_chem_comp.formula
+_chem_comp.formula_weight
+_chem_comp.ma_provenance
+ALA 'L-peptide linking' ALANINE 'C3 H7 N O2' 89.094 'CCD Core'
+ARG 'L-peptide linking' ARGININE 'C6 H15 N4 O2 1' 175.212 'CCD Core'
+ASN 'L-peptide linking' ASPARAGINE 'C4 H8 N2 O3' 132.119 'CCD Core'
+ASP 'L-peptide linking' 'ASPARTIC ACID' 'C4 H7 N O4' 133.103 'CCD Core'
+CYS 'L-peptide linking' CYSTEINE 'C3 H7 N O2 S' 121.154 'CCD Core'
+GLN 'L-peptide linking' GLUTAMINE 'C5 H10 N2 O3' 146.146 'CCD Core'
+GLU 'L-peptide linking' 'GLUTAMIC ACID' 'C5 H9 N O4' 147.130 'CCD Core'
+GLY 'peptide linking' GLYCINE 'C2 H5 N O2' 75.067 'CCD Core'
+HIS 'L-peptide linking' HISTIDINE 'C6 H10 N3 O2 1' 156.165 'CCD Core'
+ILE 'L-peptide linking' ISOLEUCINE 'C6 H13 N O2' 131.175 'CCD Core'
+LEU 'L-peptide linking' LEUCINE 'C6 H13 N O2' 131.175 'CCD Core'
+LYS 'L-peptide linking' LYSINE 'C6 H15 N2 O2 1' 147.198 'CCD Core'
+MET 'L-peptide linking' METHIONINE 'C5 H11 N O2 S' 149.208 'CCD Core'
+PHE 'L-peptide linking' PHENYLALANINE 'C9 H11 N O2' 165.192 'CCD Core'
+PRO 'L-peptide linking' PROLINE 'C5 H9 N O2' 115.132 'CCD Core'
+SER 'L-peptide linking' SERINE 'C3 H7 N O3' 105.093 'CCD Core'
+THR 'L-peptide linking' THREONINE 'C4 H9 N O3' 119.120 'CCD Core'
+TRP 'L-peptide linking' TRYPTOPHAN 'C11 H12 N2 O2' 204.229 'CCD Core'
+TYR 'L-peptide linking' TYROSINE 'C9 H11 N O3' 181.191 'CCD Core'
+VAL 'L-peptide linking' VALINE 'C5 H11 N O2' 117.148 'CCD Core'
+#
+#
+loop_
+_entity.id
+_entity.type
+_entity.src_method
+_entity.pdbx_description
+_entity.formula_weight
+_entity.pdbx_number_of_molecules
+_entity.details
+1 polymer nat 'Homo sapiens (Human) TIMM8B (Q9Y5J9)' 10831.880 1 .
+2 polymer nat 'Homo sapiens (Human) TIMM13 (Q9Y5L4)' 12206.562 1 .
+#
+#
+loop_
+_entity_src_nat.entity_id
+_entity_src_nat.pdbx_src_id
+_entity_src_nat.pdbx_ncbi_taxonomy_id
+_entity_src_nat.pdbx_organism_scientific
+_entity_src_nat.common_name
+_entity_src_nat.strain
+1 1 9606 'Homo sapiens (Human)' . .
+2 1 9606 'Homo sapiens (Human)' . .
+#
+#
+loop_
+_ma_target_ref_db_details.target_entity_id
+_ma_target_ref_db_details.db_name
+_ma_target_ref_db_details.db_name_other_details
+_ma_target_ref_db_details.db_code
+_ma_target_ref_db_details.db_accession
+_ma_target_ref_db_details.seq_db_isoform
+_ma_target_ref_db_details.seq_db_align_begin
+_ma_target_ref_db_details.seq_db_align_end
+_ma_target_ref_db_details.ncbi_taxonomy_id
+_ma_target_ref_db_details.organism_scientific
+_ma_target_ref_db_details.seq_db_sequence_version_date
+_ma_target_ref_db_details.seq_db_sequence_checksum
+1 UNP . TIM8B_HUMAN Q9Y5J9 . 1 83 9606 'Homo sapiens (Human)' 1999-11-01
+9DC47BB475DB8692
+2 UNP . TIM13_HUMAN Q9Y5L4 . 1 95 9606 'Homo sapiens (Human)' 1999-11-01
+E40E742C7CA55834
+#
+#
+loop_
+_entity_poly.entity_id
+_entity_poly.type
+_entity_poly.nstd_linkage
+_entity_poly.nstd_monomer
+_entity_poly.pdbx_strand_id
+_entity_poly.pdbx_seq_one_letter_code
+_entity_poly.pdbx_seq_one_letter_code_can
+1 polypeptide(L) no no A MAE MAE
+2 polypeptide(L) no no B MEG MEG
+#
+#
+loop_
+_entity_poly_seq.entity_id
+_entity_poly_seq.num
+_entity_poly_seq.mon_id
+_entity_poly_seq.hetero
+1 1 MET .
+1 2 ALA .
+1 3 GLU .
+2 1 MET .
+2 2 GLU .
+2 3 GLY .
+#
+#
+loop_
+_struct_asym.id
+_struct_asym.entity_id
+_struct_asym.details
+A 1 .
+B 2 .
+#
+#
+loop_
+_pdbx_poly_seq_scheme.asym_id
+_pdbx_poly_seq_scheme.entity_id
+_pdbx_poly_seq_scheme.seq_id
+_pdbx_poly_seq_scheme.mon_id
+_pdbx_poly_seq_scheme.pdb_seq_num
+_pdbx_poly_seq_scheme.auth_seq_num
+_pdbx_poly_seq_scheme.pdb_mon_id
+_pdbx_poly_seq_scheme.auth_mon_id
+_pdbx_poly_seq_scheme.pdb_strand_id
+_pdbx_poly_seq_scheme.pdb_ins_code
+A 1 1 MET 1 1 MET MET A .
+A 1 2 ALA 2 2 ALA ALA A .
+A 1 3 GLU 3 3 GLU GLU A .
+B 2 1 MET 1 1 MET MET B .
+B 2 2 GLU 2 2 GLU GLU B .
+B 2 3 GLY 3 3 GLY GLY B .
+#
+#
+loop_
+_ma_data.id
+_ma_data.name
+_ma_data.content_type
+_ma_data.content_type_other_details
+1 'Homo sapiens (Human) TIMM8B (Q9Y5J9)' target .
+2 'Homo sapiens (Human) TIMM13 (Q9Y5L4)' target .
+3 'Model 5 (top ranked model)' 'model coordinates' .
+4 UniRef30 'reference database' .
+5 'ColabFold DB' 'reference database' .
+#
+#
+loop_
+_ma_data_group.ordinal_id
+_ma_data_group.group_id
+_ma_data_group.data_id
+1 1 1
+2 1 2
+3 1 4
+4 1 5
+5 2 3
+#
+#
+loop_
+_ma_data_ref_db.data_id
+_ma_data_ref_db.name
+_ma_data_ref_db.location_url
+_ma_data_ref_db.version
+_ma_data_ref_db.release_date
+4 UniRef30 http://wwwuser.gwdg.de/~compbiol/colabfold/uniref30_2103.tar.gz
+2021_03 .
+5 'ColabFold DB'
+http://wwwuser.gwdg.de/~compbiol/colabfold/colabfold_envdb_202108.tar.gz 2021_08
+.
+#
+#
+loop_
+_ma_target_entity.entity_id
+_ma_target_entity.data_id
+_ma_target_entity.origin
+1 1 'reference database'
+2 2 'reference database'
+#
+#
+loop_
+_ma_target_entity_instance.asym_id
+_ma_target_entity_instance.entity_id
+_ma_target_entity_instance.details
+A 1 .
+B 2 .
+#
+#
+loop_
+_ma_protocol_step.ordinal_id
+_ma_protocol_step.protocol_id
+_ma_protocol_step.step_id
+_ma_protocol_step.method_type
+_ma_protocol_step.step_name
+_ma_protocol_step.details
+_ma_protocol_step.software_group_id
+_ma_protocol_step.input_data_group_id
+_ma_protocol_step.output_data_group_id
+1 1 1 modeling .
+'Model generated using ColabFold v1.2.0 with AlphaFold-Multimer (v2) producing 5 models with 3 recycles each, without model relaxation, without templates, ranked by ipTM*0.8+pTM*0.2, starting from paired and unpaired MSAs from MMseqs2 (UniRef+Environmental).'
+1 1 2
+2 1 2 'model selection' .
+'Select best model, which is either the top-ranked model as determined by the ColabFold pipeline (ipTM*0.8+pTM*0.2), or else the model with best congruence with crosslinks reported in the related study.'
+. 2 2
+#
+#
+loop_
+_ma_model_list.ordinal_id
+_ma_model_list.model_id
+_ma_model_list.model_group_id
+_ma_model_list.model_name
+_ma_model_list.model_group_name
+_ma_model_list.data_id
+_ma_model_list.model_type
+_ma_model_list.model_type_other_details
+1 1 1 'Model 5 (top ranked model)'
+'Crosslinked Heterodimer AlphaFold-Multimer v2 Models' 3 'Ab initio model' .
+#
+#
+loop_
+_atom_site.group_PDB
+_atom_site.id
+_atom_site.type_symbol
+_atom_site.label_atom_id
+_atom_site.label_alt_id
+_atom_site.label_comp_id
+_atom_site.label_seq_id
+_atom_site.auth_seq_id
+_atom_site.pdbx_PDB_ins_code
+_atom_site.label_asym_id
+_atom_site.Cartn_x
+_atom_site.Cartn_y
+_atom_site.Cartn_z
+_atom_site.occupancy
+_atom_site.label_entity_id
+_atom_site.auth_asym_id
+_atom_site.B_iso_or_equiv
+_atom_site.pdbx_PDB_model_num
+ATOM 1 N N . MET 1 1 ? A 8.317 39.011 19.688 1.000 1 A 42.340 1
+ATOM 2 C CA . MET 1 1 ? A 8.849 37.725 19.245 1.000 1 A 42.340 1
+ATOM 3 C C . MET 1 1 ? A 7.739 36.684 19.142 1.000 1 A 42.340 1
+ATOM 4 O O . MET 1 1 ? A 7.973 35.565 18.682 1.000 1 A 42.340 1
+ATOM 5 C CB . MET 1 1 ? A 9.938 37.236 20.200 1.000 1 A 42.340 1
+ATOM 6 C CG . MET 1 1 ? A 11.341 37.665 19.802 1.000 1 A 42.340 1
+ATOM 7 S SD . MET 1 1 ? A 12.633 36.531 20.445 1.000 1 A 42.340 1
+ATOM 8 C CE . MET 1 1 ? A 13.720 37.723 21.276 1.000 1 A 42.340 1
+ATOM 9 N N . ALA 2 2 ? A 6.663 36.923 19.908 1.000 1 A 50.210 1
+ATOM 10 C CA . ALA 2 2 ? A 5.472 36.082 19.989 1.000 1 A 50.210 1
+ATOM 11 C C . ALA 2 2 ? A 4.632 36.197 18.719 1.000 1 A 50.210 1
+ATOM 12 O O . ALA 2 2 ? A 3.979 35.234 18.311 1.000 1 A 50.210 1
+ATOM 13 C CB . ALA 2 2 ? A 4.637 36.457 21.211 1.000 1 A 50.210 1
+ATOM 14 N N . GLU 3 3 ? A 4.548 37.401 18.095 1.000 1 A 53.120 1
+ATOM 15 C CA . GLU 3 3 ? A 3.660 37.590 16.952 1.000 1 A 53.120 1
+ATOM 16 C C . GLU 3 3 ? A 4.168 36.832 15.728 1.000 1 A 53.120 1
+ATOM 17 O O . GLU 3 3 ? A 3.379 36.423 14.874 1.000 1 A 53.120 1
+ATOM 18 C CB . GLU 3 3 ? A 3.513 39.078 16.625 1.000 1 A 53.120 1
+ATOM 19 C CG . GLU 3 3 ? A 2.318 39.739 17.296 1.000 1 A 53.120 1
+ATOM 20 C CD . GLU 3 3 ? A 2.060 41.155 16.803 1.000 1 A 53.120 1
+ATOM 21 O OE1 . GLU 3 3 ? A 1.079 41.369 16.056 1.000 1 A 53.120 1
+ATOM 22 O OE2 . GLU 3 3 ? A 2.848 42.057 17.167 1.000 1 A 53.120 1
+ATOM 652 N N . MET 1 1 ? B 50.040 32.393 35.390 1.000 2 B 28.570 1
+ATOM 653 C CA . MET 1 1 ? B 49.521 31.790 36.614 1.000 2 B 28.570 1
+ATOM 654 C C . MET 1 1 ? B 48.376 32.619 37.186 1.000 2 B 28.570 1
+ATOM 655 O O . MET 1 1 ? B 47.433 32.071 37.759 1.000 2 B 28.570 1
+ATOM 656 C CB . MET 1 1 ? B 50.632 31.645 37.655 1.000 2 B 28.570 1
+ATOM 657 C CG . MET 1 1 ? B 50.733 30.251 38.253 1.000 2 B 28.570 1
+ATOM 658 S SD . MET 1 1 ? B 52.198 30.058 39.341 1.000 2 B 28.570 1
+ATOM 659 C CE . MET 1 1 ? B 51.684 28.617 40.317 1.000 2 B 28.570 1
+ATOM 660 N N . GLU 2 2 ? B 48.540 33.870 37.053 1.000 2 B 35.420 1
+ATOM 661 C CA . GLU 2 2 ? B 47.501 34.894 37.106 1.000 2 B 35.420 1
+ATOM 662 C C . GLU 2 2 ? B 46.554 34.783 35.915 1.000 2 B 35.420 1
+ATOM 663 O O . GLU 2 2 ? B 45.932 35.769 35.515 1.000 2 B 35.420 1
+ATOM 664 C CB . GLU 2 2 ? B 48.124 36.291 37.154 1.000 2 B 35.420 1
+ATOM 665 C CG . GLU 2 2 ? B 47.783 37.074 38.414 1.000 2 B 35.420 1
+ATOM 666 C CD . GLU 2 2 ? B 48.552 38.380 38.535 1.000 2 B 35.420 1
+ATOM 667 O OE1 . GLU 2 2 ? B 47.992 39.448 38.199 1.000 2 B 35.420 1
+ATOM 668 O OE2 . GLU 2 2 ? B 49.725 38.335 38.969 1.000 2 B 35.420 1
+ATOM 669 N N . GLY 3 3 ? B 46.387 33.521 35.322 1.000 2 B 37.540 1
+ATOM 670 C CA . GLY 3 3 ? B 45.732 33.310 34.041 1.000 2 B 37.540 1
+ATOM 671 C C . GLY 3 3 ? B 44.283 33.761 34.030 1.000 2 B 37.540 1
+ATOM 672 O O . GLY 3 3 ? B 43.519 33.433 34.939 1.000 2 B 37.540 1
+#
+#
+loop_
+_atom_type.symbol
+C
+N
+O
+S
+#
diff --git a/validation/test_files/missing_items.cif b/validation/test_files/missing_items.cif
new file mode 100644
index 0000000000000000000000000000000000000000..d8ec661d86a0d7bdaedf3293d0bb58eab4efff30
--- /dev/null
+++ b/validation/test_files/missing_items.cif
@@ -0,0 +1,401 @@
+data_Q9Y5J9-Q9Y5L4_UNRELAXED_RANK_1_MODEL_5
+_entry.id Q9Y5J9-Q9Y5L4_UNRELAXED_RANK_1_MODEL_5
+_struct.entry_id Q9Y5J9-Q9Y5L4_UNRELAXED_RANK_1_MODEL_5
+_struct.pdbx_model_details 'Dimer model generated for TIMM8B and TIMM13, produced using AlphaFold-Multimer (AlphaFold v2.2.0) as implemented by ColabFold (v1.2.0) which uses MMseqs2 for MSA generation (UniRef30 + Environmental).'
+_struct.pdbx_structure_determination_methodology computational
+_struct.title 'Predicted interaction between TIMM8B and TIMM13'
+_audit_conform.dict_location https://raw.githubusercontent.com/ihmwg/ModelCIF/557bda7/base/mmcif_ma-core.dic
+_audit_conform.dict_name mmcif_ma.dic
+_audit_conform.dict_version 1.4.1
+#
+loop_
+_citation.id
+_citation.title
+_citation.journal_abbrev
+_citation.journal_volume
+_citation.page_first
+_citation.page_last
+_citation.year
+_citation.pdbx_database_id_PubMed
+_citation.pdbx_database_id_DOI
+1 'ColabFold: making protein folding accessible to all.' 'Nature Methods' 19 679
+682 2022 35637307 10.1038/s41592-022-01488-1
+2
+'MMseqs2 desktop and local web server app for fast, interactive sequence searches.'
+Bioinformatics 35 2856 2858 2019 30615063 10.1093/bioinformatics/bty1057
+3 'Protein complex prediction with AlphaFold-Multimer.' bioRxiv . . . 2021 .
+10.1101/2021.10.04.463034
+#
+#
+loop_
+_citation_author.citation_id
+_citation_author.name
+_citation_author.ordinal
+1 'Mirdita, M.' 1
+1 'Schuetze, K.' 2
+1 'Moriwaki, Y.' 3
+1 'Heo, L.' 4
+1 'Ovchinnikov, S.' 5
+1 'Steinegger, M.' 6
+2 'Mirdita, M.' 7
+2 'Steinegger, M.' 8
+2 'Soeding, J.' 9
+3 'Evans, R.' 10
+3 "O'Neill, M." 11
+3 'Pritzel, A.' 12
+3 'Antropova, N.' 13
+3 'Senior, A.' 14
+3 'Green, T.' 15
+3 'Zidek, A.' 16
+3 'Bates, R.' 17
+3 'Blackwell, S.' 18
+3 'Yim, J.' 19
+3 'Ronneberger, O.' 20
+3 'Bodenstein, S.' 21
+3 'Zielinski, M.' 22
+3 'Bridgland, A.' 23
+3 'Potapenko, A.' 24
+3 'Cowie, A.' 25
+3 'Tunyasuvunakool, K.' 26
+3 'Jain, R.' 27
+3 'Clancy, E.' 28
+3 'Kohli, P.' 29
+3 'Jumper, J.' 30
+3 'Hassabis, D.' 31
+#
+#
+loop_
+_software.pdbx_ordinal
+_software.name
+_software.classification
+_software.description
+_software.version
+_software.type
+_software.location
+_software.citation_id
+1 ColabFold 'model building' 'Structure prediction' 1.2.0 package
+https://github.com/sokrypton/ColabFold 1
+2 MMseqs2 'data collection' 'Many-against-Many sequence searching' . package
+https://github.com/soedinglab/mmseqs2 2
+3 AlphaFold-Multimer 'model building' 'Structure prediction' . package
+https://github.com/deepmind/alphafold 3
+#
+#
+loop_
+_ma_software_parameter.parameter_id
+_ma_software_parameter.group_id
+_ma_software_parameter.data_type
+_ma_software_parameter.name
+_ma_software_parameter.value
+_ma_software_parameter.description
+1 1 boolean use_templates NO .
+2 1 boolean use_amber NO .
+3 1 string msa_mode 'MMseqs2 (UniRef+Environmental)' .
+4 1 string model_type AlphaFold2-multimer-v2 .
+5 1 integer num_models 5 .
+6 1 integer num_recycles 3 .
+7 1 integer-csv model_order 3,4,5,1,2 .
+8 1 boolean keep_existing_results YES .
+9 1 string rank_by multimer .
+10 1 string pair_mode unpaired+paired .
+11 1 string host_url https://api.colabfold.com .
+12 1 integer 'stop_at_score' 100 .
+13 1 float recompile_padding 1.100 .
+14 1 boolean recompile_all_models YES .
+15 1 string commit b532e910b15434f707f0b7460abc25c70fcb9b26 .
+16 1 string version 1.2.0 .
+#
+#
+loop_
+_ma_software_group.ordinal_id
+_ma_software_group.group_id
+_ma_software_group.software_id
+_ma_software_group.parameter_group_id
+1 1 1 1
+2 1 2 1
+3 1 3 1
+#
+#
+loop_
+_audit_author.name
+_audit_author.pdbx_ordinal
+'Bartolec, T.K.' 1
+'Vazquez-Campos, X.' 2
+'Norman, A.' 3
+'Luong, C.' 4
+'Payne, R.J.' 5
+'Wilkins, M.R.' 6
+'Mackay, J.P.' 7
+'Low, J.K.K.' 8
+#
+#
+loop_
+_chem_comp.id
+_chem_comp.type
+_chem_comp.name
+_chem_comp.formula
+_chem_comp.formula_weight
+_chem_comp.ma_provenance
+ALA 'L-peptide linking' ALANINE 'C3 H7 N O2' 89.094 'CCD Core'
+ARG 'L-peptide linking' ARGININE 'C6 H15 N4 O2 1' 175.212 'CCD Core'
+ASN 'L-peptide linking' ASPARAGINE 'C4 H8 N2 O3' 132.119 'CCD Core'
+ASP 'L-peptide linking' 'ASPARTIC ACID' 'C4 H7 N O4' 133.103 'CCD Core'
+CYS 'L-peptide linking' CYSTEINE 'C3 H7 N O2 S' 121.154 'CCD Core'
+GLN 'L-peptide linking' GLUTAMINE 'C5 H10 N2 O3' 146.146 'CCD Core'
+GLU 'L-peptide linking' 'GLUTAMIC ACID' 'C5 H9 N O4' 147.130 'CCD Core'
+GLY 'peptide linking' GLYCINE 'C2 H5 N O2' 75.067 'CCD Core'
+HIS 'L-peptide linking' HISTIDINE 'C6 H10 N3 O2 1' 156.165 'CCD Core'
+ILE 'L-peptide linking' ISOLEUCINE 'C6 H13 N O2' 131.175 'CCD Core'
+LEU 'L-peptide linking' LEUCINE 'C6 H13 N O2' 131.175 'CCD Core'
+LYS 'L-peptide linking' LYSINE 'C6 H15 N2 O2 1' 147.198 'CCD Core'
+MET 'L-peptide linking' METHIONINE 'C5 H11 N O2 S' 149.208 'CCD Core'
+PHE 'L-peptide linking' PHENYLALANINE 'C9 H11 N O2' 165.192 'CCD Core'
+PRO 'L-peptide linking' PROLINE 'C5 H9 N O2' 115.132 'CCD Core'
+SER 'L-peptide linking' SERINE 'C3 H7 N O3' 105.093 'CCD Core'
+THR 'L-peptide linking' THREONINE 'C4 H9 N O3' 119.120 'CCD Core'
+TRP 'L-peptide linking' TRYPTOPHAN 'C11 H12 N2 O2' 204.229 'CCD Core'
+TYR 'L-peptide linking' TYROSINE 'C9 H11 N O3' 181.191 'CCD Core'
+VAL 'L-peptide linking' VALINE 'C5 H11 N O2' 117.148 'CCD Core'
+#
+#
+loop_
+_entity.id
+_entity.type
+_entity.src_method
+_entity.pdbx_description
+_entity.formula_weight
+_entity.pdbx_number_of_molecules
+_entity.details
+1 polymer nat 'Homo sapiens (Human) TIMM8B (Q9Y5J9)' 10831.880 1 .
+2 polymer nat 'Homo sapiens (Human) TIMM13 (Q9Y5L4)' 12206.562 1 .
+#
+#
+loop_
+_entity_src_nat.entity_id
+_entity_src_nat.pdbx_src_id
+_entity_src_nat.pdbx_ncbi_taxonomy_id
+_entity_src_nat.pdbx_organism_scientific
+_entity_src_nat.common_name
+_entity_src_nat.strain
+1 1 9606 'Homo sapiens (Human)' . .
+2 1 9606 'Homo sapiens (Human)' . .
+#
+#
+loop_
+_ma_target_ref_db_details.target_entity_id
+_ma_target_ref_db_details.db_name
+_ma_target_ref_db_details.db_name_other_details
+_ma_target_ref_db_details.db_code
+_ma_target_ref_db_details.db_accession
+_ma_target_ref_db_details.seq_db_isoform
+_ma_target_ref_db_details.seq_db_align_begin
+_ma_target_ref_db_details.seq_db_align_end
+_ma_target_ref_db_details.ncbi_taxonomy_id
+_ma_target_ref_db_details.organism_scientific
+_ma_target_ref_db_details.seq_db_sequence_version_date
+_ma_target_ref_db_details.seq_db_sequence_checksum
+1 UNP . TIM8B_HUMAN Q9Y5J9 . 1 83 9606 'Homo sapiens (Human)' 1999-11-01
+9DC47BB475DB8692
+2 UNP . TIM13_HUMAN Q9Y5L4 . 1 95 9606 'Homo sapiens (Human)' 1999-11-01
+E40E742C7CA55834
+#
+#
+loop_
+_entity_poly.entity_id
+_entity_poly.pdbx_seq_one_letter_code
+_entity_poly.pdbx_seq_one_letter_code_can
+1 MAE MAE
+2 MEG MEG
+#
+#
+loop_
+_entity_poly_seq.num
+_entity_poly_seq.mon_id
+_entity_poly_seq.hetero
+1 MET .
+2 ALA .
+3 GLU .
+1 MET .
+2 GLU .
+3 GLY .
+#
+#
+loop_
+_struct_asym.id
+_struct_asym.entity_id
+_struct_asym.details
+A 1 .
+B 2 .
+#
+#
+loop_
+_pdbx_poly_seq_scheme.asym_id
+_pdbx_poly_seq_scheme.seq_id
+_pdbx_poly_seq_scheme.mon_id
+_pdbx_poly_seq_scheme.pdb_seq_num
+_pdbx_poly_seq_scheme.auth_seq_num
+_pdbx_poly_seq_scheme.pdb_mon_id
+_pdbx_poly_seq_scheme.auth_mon_id
+_pdbx_poly_seq_scheme.pdb_strand_id
+_pdbx_poly_seq_scheme.pdb_ins_code
+A 1 MET 1 1 MET MET A .
+A 2 ALA 2 2 ALA ALA A .
+A 3 GLU 3 3 GLU GLU A .
+B 1 MET 1 1 MET MET B .
+B 2 GLU 2 2 GLU GLU B .
+B 3 GLY 3 3 GLY GLY B .
+#
+#
+loop_
+_ma_data.id
+_ma_data.name
+_ma_data.content_type
+_ma_data.content_type_other_details
+1 'Homo sapiens (Human) TIMM8B (Q9Y5J9)' target .
+2 'Homo sapiens (Human) TIMM13 (Q9Y5L4)' target .
+3 'Model 5 (top ranked model)' 'model coordinates' .
+4 UniRef30 'reference database' .
+5 'ColabFold DB' 'reference database' .
+#
+#
+loop_
+_ma_data_group.ordinal_id
+_ma_data_group.group_id
+_ma_data_group.data_id
+1 1 1
+2 1 2
+3 1 4
+4 1 5
+5 2 3
+#
+#
+loop_
+_ma_data_ref_db.data_id
+_ma_data_ref_db.name
+_ma_data_ref_db.location_url
+_ma_data_ref_db.version
+_ma_data_ref_db.release_date
+4 UniRef30 http://wwwuser.gwdg.de/~compbiol/colabfold/uniref30_2103.tar.gz
+2021_03 .
+5 'ColabFold DB'
+http://wwwuser.gwdg.de/~compbiol/colabfold/colabfold_envdb_202108.tar.gz 2021_08
+.
+#
+#
+loop_
+_ma_target_entity.entity_id
+_ma_target_entity.data_id
+_ma_target_entity.origin
+1 1 'reference database'
+2 2 'reference database'
+#
+#
+loop_
+_ma_target_entity_instance.asym_id
+_ma_target_entity_instance.entity_id
+_ma_target_entity_instance.details
+A 1 .
+B 2 .
+#
+#
+loop_
+_ma_protocol_step.ordinal_id
+_ma_protocol_step.protocol_id
+_ma_protocol_step.step_id
+_ma_protocol_step.method_type
+_ma_protocol_step.step_name
+_ma_protocol_step.details
+_ma_protocol_step.software_group_id
+_ma_protocol_step.input_data_group_id
+_ma_protocol_step.output_data_group_id
+1 1 1 modeling .
+'Model generated using ColabFold v1.2.0 with AlphaFold-Multimer (v2) producing 5 models with 3 recycles each, without model relaxation, without templates, ranked by ipTM*0.8+pTM*0.2, starting from paired and unpaired MSAs from MMseqs2 (UniRef+Environmental).'
+1 1 2
+2 1 2 'model selection' .
+'Select best model, which is either the top-ranked model as determined by the ColabFold pipeline (ipTM*0.8+pTM*0.2), or else the model with best congruence with crosslinks reported in the related study.'
+. 2 2
+#
+#
+loop_
+_ma_model_list.ordinal_id
+_ma_model_list.model_id
+_ma_model_list.model_group_id
+_ma_model_list.model_name
+_ma_model_list.model_group_name
+_ma_model_list.data_id
+_ma_model_list.model_type
+_ma_model_list.model_type_other_details
+1 1 1 'Model 5 (top ranked model)'
+'Crosslinked Heterodimer AlphaFold-Multimer v2 Models' 3 'Ab initio model' .
+#
+#
+loop_
+_atom_site.group_PDB
+_atom_site.id
+_atom_site.type_symbol
+_atom_site.label_atom_id
+_atom_site.label_alt_id
+_atom_site.label_comp_id
+_atom_site.label_seq_id
+_atom_site.auth_seq_id
+_atom_site.pdbx_PDB_ins_code
+_atom_site.label_asym_id
+_atom_site.Cartn_x
+_atom_site.Cartn_y
+_atom_site.Cartn_z
+_atom_site.occupancy
+_atom_site.auth_asym_id
+_atom_site.B_iso_or_equiv
+_atom_site.pdbx_PDB_model_num
+ATOM 1 N N . MET 1 1 ? A 8.317 39.011 19.688 1.000 A 42.340 1
+ATOM 2 C CA . MET 1 1 ? A 8.849 37.725 19.245 1.000 A 42.340 1
+ATOM 3 C C . MET 1 1 ? A 7.739 36.684 19.142 1.000 A 42.340 1
+ATOM 4 O O . MET 1 1 ? A 7.973 35.565 18.682 1.000 A 42.340 1
+ATOM 5 C CB . MET 1 1 ? A 9.938 37.236 20.200 1.000 A 42.340 1
+ATOM 6 C CG . MET 1 1 ? A 11.341 37.665 19.802 1.000 A 42.340 1
+ATOM 7 S SD . MET 1 1 ? A 12.633 36.531 20.445 1.000 A 42.340 1
+ATOM 8 C CE . MET 1 1 ? A 13.720 37.723 21.276 1.000 A 42.340 1
+ATOM 9 N N . ALA 2 2 ? A 6.663 36.923 19.908 1.000 A 50.210 1
+ATOM 10 C CA . ALA 2 2 ? A 5.472 36.082 19.989 1.000 A 50.210 1
+ATOM 11 C C . ALA 2 2 ? A 4.632 36.197 18.719 1.000 A 50.210 1
+ATOM 12 O O . ALA 2 2 ? A 3.979 35.234 18.311 1.000 A 50.210 1
+ATOM 13 C CB . ALA 2 2 ? A 4.637 36.457 21.211 1.000 A 50.210 1
+ATOM 14 N N . GLU 3 3 ? A 4.548 37.401 18.095 1.000 A 53.120 1
+ATOM 15 C CA . GLU 3 3 ? A 3.660 37.590 16.952 1.000 A 53.120 1
+ATOM 16 C C . GLU 3 3 ? A 4.168 36.832 15.728 1.000 A 53.120 1
+ATOM 17 O O . GLU 3 3 ? A 3.379 36.423 14.874 1.000 A 53.120 1
+ATOM 18 C CB . GLU 3 3 ? A 3.513 39.078 16.625 1.000 A 53.120 1
+ATOM 19 C CG . GLU 3 3 ? A 2.318 39.739 17.296 1.000 A 53.120 1
+ATOM 20 C CD . GLU 3 3 ? A 2.060 41.155 16.803 1.000 A 53.120 1
+ATOM 21 O OE1 . GLU 3 3 ? A 1.079 41.369 16.056 1.000 A 53.120 1
+ATOM 22 O OE2 . GLU 3 3 ? A 2.848 42.057 17.167 1.000 A 53.120 1
+ATOM 652 N N . MET 1 1 ? B 50.040 32.393 35.390 1.000 B 28.570 1
+ATOM 653 C CA . MET 1 1 ? B 49.521 31.790 36.614 1.000 B 28.570 1
+ATOM 654 C C . MET 1 1 ? B 48.376 32.619 37.186 1.000 B 28.570 1
+ATOM 655 O O . MET 1 1 ? B 47.433 32.071 37.759 1.000 B 28.570 1
+ATOM 656 C CB . MET 1 1 ? B 50.632 31.645 37.655 1.000 B 28.570 1
+ATOM 657 C CG . MET 1 1 ? B 50.733 30.251 38.253 1.000 B 28.570 1
+ATOM 658 S SD . MET 1 1 ? B 52.198 30.058 39.341 1.000 B 28.570 1
+ATOM 659 C CE . MET 1 1 ? B 51.684 28.617 40.317 1.000 B 28.570 1
+ATOM 660 N N . GLU 2 2 ? B 48.540 33.870 37.053 1.000 B 35.420 1
+ATOM 661 C CA . GLU 2 2 ? B 47.501 34.894 37.106 1.000 B 35.420 1
+ATOM 662 C C . GLU 2 2 ? B 46.554 34.783 35.915 1.000 B 35.420 1
+ATOM 663 O O . GLU 2 2 ? B 45.932 35.769 35.515 1.000 B 35.420 1
+ATOM 664 C CB . GLU 2 2 ? B 48.124 36.291 37.154 1.000 B 35.420 1
+ATOM 665 C CG . GLU 2 2 ? B 47.783 37.074 38.414 1.000 B 35.420 1
+ATOM 666 C CD . GLU 2 2 ? B 48.552 38.380 38.535 1.000 B 35.420 1
+ATOM 667 O OE1 . GLU 2 2 ? B 47.992 39.448 38.199 1.000 B 35.420 1
+ATOM 668 O OE2 . GLU 2 2 ? B 49.725 38.335 38.969 1.000 B 35.420 1
+ATOM 669 N N . GLY 3 3 ? B 46.387 33.521 35.322 1.000 B 37.540 1
+ATOM 670 C CA . GLY 3 3 ? B 45.732 33.310 34.041 1.000 B 37.540 1
+ATOM 671 C C . GLY 3 3 ? B 44.283 33.761 34.030 1.000 B 37.540 1
+ATOM 672 O O . GLY 3 3 ? B 43.519 33.433 34.939 1.000 B 37.540 1
+#
+#
+loop_
+_atom_type.symbol
+C
+N
+O
+S
+#
diff --git a/validation/validate-mmcif-file.py b/validation/validate-mmcif-file.py
index e3e4d0acc86972adfc440b2c68a666f4bfdefb43..9fe748e52b4d1bf61d374638b92b43bccfc1bfa8 100755
--- a/validation/validate-mmcif-file.py
+++ b/validation/validate-mmcif-file.py
@@ -2,7 +2,7 @@
 """Validate mmCIF format in a model mmCIF file.
 
 Does not check if the model/ coordinates make sense. But includes associated
-cif files in the check by merging files. That is, as an example, associated
+CIF files in the check by merging files. That is, as an example, associated
 files with quality scores stored in mmCIF format will be merged with the model
 file and checked, but associated MSA files in FASTA format can not be merged
 and thus, won't be merged into the model mmCIF file and won't be checked.
@@ -11,9 +11,9 @@ and thus, won't be merged into the model mmCIF file and won't be checked.
 # pylint: enable=invalid-name
 
 # ToDo: enable testing of gzipped files
-# ToDo: add "modelcif-pedantic" mode, fail on categories that are technically
+# ToDo: add `modelcif-pedantic` mode, fail on categories that are technically
 #       allowed but discouraged to be used, like _exptl
-# ToDo: Remove pip installs which are in requirements.txt from Dockerfile
+# ToDo: Remove pip installs which are in `requirements.txt` from Dockerfile
 
 from io import TextIOWrapper
 import argparse
@@ -41,12 +41,11 @@ import mmcif.io.PdbxExceptions
 def _parse_command_line():
     """Get arguments."""
     parser = argparse.ArgumentParser(description=__doc__)
-
     parser.add_argument(
         "model_cif",
         type=str,
         metavar="<MODEL MMCIF FILE>",
-        help="Path to the model mmCIF file. This is the 'main' cif file of a "
+        help="Path to the model mmCIF file. This is the 'main' CIF file of a "
         + "modelling project including coordinates.",
     )
     parser.add_argument(
@@ -58,14 +57,24 @@ def _parse_command_line():
         + "external files attached.",
         default=None,
     )
-    parser.add_argument(
+    dic_grp = parser.add_mutually_exclusive_group()
+    dic_grp.add_argument(
         "--dict-sdb",
         "-d",
         type=str,
         metavar="<SDB FILE>",
-        help="The dictionary in SDB format used for checking.",
+        help="The dictionary in SDB format used for checking. Can't be "
+        + "combined with --modelcif-mode/-m and --pdbx-mmcif-mode/-p.",
         default="/usr/local/share/mmcif-dict-suite/mmcif_ma.sdb",
     )
+    dic_grp.add_argument(
+        "--pdbx-mmcif-mode",
+        "-p",
+        action="store_true",
+        help="Check with the PDBX/mmCIF dictionary, for verbatim mmCIF files. "
+        + "Can't be combined with --dict-sdb/-d and --modelcif-mode/-m. "
+        + "Default is to check with the ModelCIF dictionary.",
+    )
     parser.add_argument(
         "--out-file",
         "-o",
@@ -102,13 +111,13 @@ def _parse_command_line():
         help="Write some messages to stdout instead of just having it as JSON. "
         + "Useful for debugging on the command line.",
     )
-
     opts = parser.parse_args()
-
     # post process arguments
     if opts.extend_validated_file is not None:
         if opts.extend_validated_file == " same ":
             opts.extend_validated_file = opts.model_cif
+    if opts.pdbx_mmcif_mode is True:
+        opts.dict_sdb = "/usr/local/share/mmcif-dict-suite/mmcif_pdbx_v50.sdb"
 
     return opts
 
@@ -120,7 +129,7 @@ def _error(msg):
 
 
 def _warn(msg):
-    """Pritn a warning message."""
+    """Print a warning message."""
     print(f"WARNING: {msg}", file=sys.stderr)
 
 
@@ -138,42 +147,23 @@ def _parse_cifcheck_stderr(stderr):
     return error_lst
 
 
-def _parse_parser_file(filename):
-    """Parse the parser output file of CifCheck."""
-    parserfile = filename + "-parser.log"
-    if not os.path.exists(parserfile):
-        return []
-
-    error_lst = []
-    with open(parserfile, encoding="utf-8") as dfh:
-        for line in dfh:
-            line = line.strip()
-            error_lst.append(line)
-
-    # remove the diag file
-    os.unlink(parserfile)
-
-    return error_lst
-
-
-def _parse_diag_file(filename):
+def _get_cifcheck_out(filename, suffix, encoding):
     """Parse the diagnosis file of CifCheck."""
-    # CifCheck places the diag file in the cwd.
-    diagfile = filename + "-diag.log"
-    if not os.path.exists(diagfile):
+    # CifCheck places the diag file in the current working directory.
+    cifcheck = filename + suffix
+    if not os.path.exists(cifcheck):
         return []
 
     error_lst = []
     # CifCheck outputs diag files as iso-8859
-    with open(diagfile, encoding="iso-8859-1") as dfh:
+    with open(cifcheck, encoding=encoding) as dfh:
         for line in dfh:
             line = line.strip()
             if line == "":
                 continue
             error_lst.append(line)
-
     # remove the diag file
-    os.unlink(diagfile)
+    os.unlink(cifcheck)
 
     return error_lst
 
@@ -223,7 +213,7 @@ def _get_indeces(data_category, attribute_list):
 
 
 def _get_entry_id(cif_datablock, entry_id_map, datablock_idx):
-    """Get a mapping of the entry.id from a cif datablock."""
+    """Get a mapping of the entry.id from a CIF data block."""
     entry = cif_datablock.getObj("entry")
     if entry is not None:
         eidx = entry.getAttributeIndex("id")
@@ -277,7 +267,7 @@ def _get_arc_zipfile_handle(arc_file, assoc_dir):
 
 
 def _unzip_arc_cif(arc_zip, cif_file):
-    """Extract a cif file from a ZIP archive."""
+    """Extract a CIF file from a ZIP archive."""
     assoc_data = []
     with TextIOWrapper(arc_zip.open(cif_file), encoding="utf-8") as cif_fh:
         assoc_data = _read_mmcif(cif_fh)
@@ -285,8 +275,52 @@ def _unzip_arc_cif(arc_zip, cif_file):
     return assoc_data
 
 
+def _get_assoc_data_from_zip_arc(
+    dat_cat, archives, assoc_dir, assoc_files, cifcheck
+):
+    """Extract data to be appended to the main CIF file from associated
+    archives."""
+    idxs = _get_indeces(
+        dat_cat,
+        ["archive_file_id", "file_content", "file_format", "file_path"],
+    )
+    last_arc_id = ""
+    arc_zip = None
+    for row in dat_cat:
+        # Get a ZipFile object of the archive to read CIF files and check
+        # the presence of non-CIF files.
+        arc_id = row[idxs["archive_file_id"]]
+        arc_file = archives[arc_id][0]
+        if arc_id != last_arc_id:
+            last_arc_id = arc_id
+            if arc_zip is not None:
+                arc_zip.close()
+            arc_zip, arc_namelist = _get_arc_zipfile_handle(
+                arc_file, assoc_dir
+            )
+        if row[idxs["file_format"]] == "cif":
+            if row[idxs["file_content"]] == "local pairwise QA scores":
+                cif_file = row[idxs["file_path"]]
+                data = _unzip_arc_cif(arc_zip, cif_file)
+                assoc_files.append((data, archives[arc_id][1]))
+            elif row[idxs["file_content"]] != "other":
+                raise RuntimeError(
+                    "Unknown associated CIF file content "
+                    + f"found: {row[idxs['file_content']]}"
+                )
+        else:
+            if row[idxs["file_path"]] not in arc_namelist:
+                cifcheck.add_general_error(
+                    f"ma_entry_associated_files.file_url '{arc_file}' is "
+                    + "missing "
+                    + "ma_associated_archive_file_details.file_path "
+                    + f"'{row[idxs['file_path']]}'"
+                )
+    arc_zip.close()
+
+
 def _get_associated_files(model_cif_file, assoc_dir, cifcheck):
-    """Get the list of associated files from a model cif file."""
+    """Get the list of associated files from a model CIF file."""
     # This is an intermediate step, so we do not need to check/ report anything
     # here. The actual confirmation comes out of CifCheck at a later stage.
     entry_id_map = {}
@@ -328,49 +362,16 @@ def _get_associated_files(model_cif_file, assoc_dir, cifcheck):
         dat_cat = pdbx_cntnr.getObj("ma_associated_archive_file_details")
         if dat_cat is None:
             continue
-        idxs = _get_indeces(
-            dat_cat,
-            ["archive_file_id", "file_content", "file_format", "file_path"],
+        # get associated files/ data that can be added to the CIF content
+        _get_assoc_data_from_zip_arc(
+            dat_cat, archives, assoc_dir, assoc_files, cifcheck
         )
-        last_arc_id = ""
-        arc_zip = None
-        for row in dat_cat:
-            # Get a ZipFile object of the archive to read CIF files and check
-            # the presence of non-CIF files.
-            arc_id = row[idxs["archive_file_id"]]
-            arc_file = archives[arc_id][0]
-            if arc_id != last_arc_id:
-                last_arc_id = arc_id
-                if arc_zip is not None:
-                    arc_zip.close()
-                arc_zip, arc_namelist = _get_arc_zipfile_handle(
-                    arc_file, assoc_dir
-                )
-            if row[idxs["file_format"]] == "cif":
-                if row[idxs["file_content"]] == "local pairwise QA scores":
-                    cif_file = row[idxs["file_path"]]
-                    data = _unzip_arc_cif(arc_zip, cif_file)
-                    assoc_files.append((data, archives[arc_id][1]))
-                elif row[idxs["file_content"]] not in ["other"]:
-                    raise RuntimeError(
-                        "Unknown associated CIF file content "
-                        + f"found: {row[idxs['file_content']]}"
-                    )
-            else:
-                if row[idxs["file_path"]] not in arc_namelist:
-                    cifcheck.add_general_error(
-                        f"ma_entry_associated_files.file_url '{arc_file}' is "
-                        + "missing "
-                        + "ma_associated_archive_file_details.file_path "
-                        + f"'{row[idxs['file_path']]}'"
-                    )
-        arc_zip.close()
 
     return assoc_files, mdl_cif, entry_id_map
 
 
 def _cmp_cif_rows(a_row, b_row, a_idxs, b_idxs, attrs):
-    """Compare two cif rows by given attributes"""
+    """Compare two CIF rows by given attributes"""
     for i in attrs:
         if a_row[a_idxs[i]] != b_row[b_idxs[i]]:
             return False
@@ -379,8 +380,8 @@ def _cmp_cif_rows(a_row, b_row, a_idxs, b_idxs, attrs):
 
 
 def _add_row(row, src_idxs, dest_idxs, dest, attrs_l):
-    """Add a data row to an existing datablock with the right item order."""
-    # create a new row fitting dest's order
+    """Add a data row to an existing data block with the right item order."""
+    # create a new row fitting `dest`'s order
     new_row = list("?" * attrs_l)
     for i, j in src_idxs.items():
         new_row[dest_idxs[i]] = row[j]
@@ -388,8 +389,8 @@ def _add_row(row, src_idxs, dest_idxs, dest, attrs_l):
 
 
 def _add_or_extend_rows(src, dest, common, not_in_dest):
-    """Mix/ add rows from src into dest."""
-    # extend dest with new attributes
+    """Mix/ add rows from `src` into `dest`."""
+    # extend `dest` with new attributes
     for attr in not_in_dest:
         dest.appendAttribute(attr)
     s_idx = src.getAttributeIndexDict()
@@ -409,7 +410,7 @@ def _add_or_extend_rows(src, dest, common, not_in_dest):
                 break
         if not match:
             _add_row(src_row, s_idx, d_idx, dest, attrs_l)
-    # extend dest rows that never matched with "?" as default value
+    # extend `dest` rows that never matched with "?" as default value
     for i in d_rows:
         dest_row = dest[i]
         for attr in not_in_dest:
@@ -419,7 +420,7 @@ def _add_or_extend_rows(src, dest, common, not_in_dest):
 def _merge_cif_datacontainer(
     parent_datablock, datablock, exclude_categories=None
 ):
-    """Merge datablock into parent_datablock ignoring exclude_categories."""
+    """Merge data block into parent_datablock ignoring exclude_categories."""
     for category in datablock.getObjNameList():
         if category in exclude_categories:
             continue
@@ -431,7 +432,7 @@ def _merge_cif_datacontainer(
             not_in_p, in_both, _ = db_ctgry.cmpAttributeNames(p_ctgry)
             _add_or_extend_rows(db_ctgry, p_ctgry, in_both, not_in_p)
         else:
-            # data category does not exist in parent, append it to datablock
+            # data category does not exist in parent, append it to data block
             parent_datablock.append(db_ctgry)
 
 
@@ -444,17 +445,17 @@ def _try_os_remove(path):
 
 
 def _merge_cif_data(model_cif_data, assoc_cif, row_entry_id, entry_id_map):
-    """Merge contents of an associated file into cif data."""
+    """Merge contents of an associated file into CIF data."""
     error_msgs = {"cifcheck-errors": []}
 
-    # per datablock, check to which datablock it belongs in the parent cif
+    # per data block, check to which data block it belongs in the parent CIF
     for assoc_cntnr in assoc_cif:
         # check/ get 'entry_link'
         assoc_entry_link = assoc_cntnr.getObj("entry_link")
         if assoc_entry_link is None:
             error_msgs["cifcheck-errors"].append(
                 'ERROR - category "entry_link" is mandatory, but it is not '
-                + f'present in datablock "{assoc_cntnr.getName()}"'
+                + f'present in data block "{assoc_cntnr.getName()}"'
             )
             continue
         # make sure entry_id exists for entry_link
@@ -465,8 +466,8 @@ def _merge_cif_data(model_cif_data, assoc_cif, row_entry_id, entry_id_map):
                 + '"entry_id" is not in category "entry_link"'
             )
             continue
-        # For each entry_id, look up the corresponding datablock in
-        # model_cif_data and merge with that datablock.
+        # For each entry_id, look up the corresponding data block in
+        # model_cif_data and merge with that data block.
         for row in assoc_entry_link:
             entry_id = row[entry_id_idx]
             if entry_id != row_entry_id:
@@ -485,12 +486,50 @@ def _merge_cif_data(model_cif_data, assoc_cif, row_entry_id, entry_id_map):
     return error_msgs
 
 
+def _print_report(header, msgs, level=0):
+    """Print a message dictionary - report style."""
+    # check if there are any messages, otherwise skip
+    found_msgs = False
+    for lines in msgs.values():
+        if len(lines) > 0:
+            found_msgs = True
+            break
+    if not found_msgs:
+        return
+
+    lws = "   " * level  # leading whitespaces
+    if level == 0:
+        print(f"{lws}{header}")
+    else:
+        print(f"{lws}{header}:")
+    for sctn, lines in msgs.items():
+        if len(lines) == 0:
+            continue
+        if isinstance(lines, (list, set)):
+            if len(lines) == 1:
+                print(f"{lws}   {sctn}: {lines.pop()}")
+                continue
+            print(f"{lws}   {sctn}:")
+            for line in lines:
+                print(f"{lws}      {line}")
+        elif isinstance(lines, dict):
+            _print_report(sctn, lines, level=level + 1)
+        else:
+            raise NotImplementedError(
+                f"Unsupported type {type(lines)} found " + "for reporting."
+            )
+
+
 class _CifCheck:
     """Handling the CifCheck tool."""
 
     def __init__(self, dict_sdb, json_out_file=None, verbose=False):
         self._version = None
-        self.check_results = {"errors": [], "diagnosis": [], "cifcheck-errors": []}
+        self.check_results = {
+            "errors": [],
+            "diagnosis": [],
+            "cifcheck-errors": [],
+        }
         self.dict_sdb = os.path.abspath(dict_sdb)
         self.json_out_file = json_out_file
         self.verbose = verbose
@@ -521,10 +560,10 @@ class _CifCheck:
     def _execute(self, filepath):
         """Execute the CifCheck tool on a model mmCIF file."""
         # If permission errors occur with the source directory of the CIF file,
-        # consider copying the file to a Python tempfile generated path. That
-        # deals with missing $TMP, $TEMP, etc.... variables.
-        # At the moment, cwd is switched to the source directory since CifCheck
-        # copies the file, otherwise.
+        # consider copying the file to a Python `tempfile` generated path. That
+        # deals with missing `$TMP`, `$TEMP`, etc.... variables.
+        # At the moment, current working directory is switched to the source
+        # directory since CifCheck copies the file, otherwise.
         cifcheck_filepath = os.path.basename(filepath)
         cifcheck_cmd = [
             "CifCheck",
@@ -546,12 +585,14 @@ class _CifCheck:
         error_lst = []
         # get error messages on the command line
         error_lst.extend(_parse_cifcheck_stderr(cps.stderr))
-        error_lst.extend(_parse_parser_file(filepath))
+        error_lst.extend(_get_cifcheck_out(filepath, "-parser.log", "utf-8"))
         if len(error_lst) > 0:
             raise _CifCheckFailedError(cifcheck_cmd, error_lst)
 
         # get messages from diagnosis file
-        error_lst.extend(_parse_diag_file(filepath))
+        error_lst.extend(
+            _get_cifcheck_out(filepath, "-diag.log", "iso-8859-1")
+        )
 
         return error_lst
 
@@ -628,7 +669,7 @@ class _CifCheck:
                 for x in self.version["versions"]
             ],
         )
-        # We want nicely formatted cif files, so place audit_conform
+        # We want nicely formatted CIF files, so place audit_conform
         # after entry.
         objs = pdbx_cntnr.getObjCatalog()
         names = list(objs.keys())
@@ -704,25 +745,13 @@ class _CifCheck:
                 msgs["cifcheck-errors"]
             )
 
-    def make_report(self):
-        """Make a concise report out of the results.
-
-        Be aware, that cuts away the majority of the messages. But solving those
-        issues first, may already repair a mmCIF file."""
-        print("Report")
-        print("======")
-        print(f"Status of check: {self.check_results['status']}")
-        if "versions" in self.check_results:
-            print("CIF dictionaries used:")
-            for dct in self.check_results["versions"]:
-                print(f"   {dct['title']}/ {dct['version']}")
-                print(f"   {dct['location']}")
-
-        # condense diagnosis data
+    def _condense_diagnosis_data(self):
+        """Make the concise report bit for the "diagnosis" results."""
         rprt = {
-            "missing_cats": set(),
-            "missing_itms": set(),
-            "parchild_mm": set(),
+            "Datablock/ entry name": [],
+            "Missing categories": set(),
+            "Missing items": set(),
+            "Mismatching parent/ child relationships": set(),
         }
         for line in self.check_results["diagnosis"]:
             # missing categories
@@ -734,7 +763,7 @@ class _CifCheck:
             ]:
                 match = re.match(pttrn, line)
                 if match is not None:
-                    rprt["missing_cats"].add(match.group("cat"))
+                    rprt["Missing categories"].add(match.group("cat"))
                     _check_dblock_name(match.group("dblock"), rprt)
                     break
             if match is not None:
@@ -742,11 +771,13 @@ class _CifCheck:
             # missing items
             for pttrn in [
                 r"^ERROR - In block \"(?P<dblock>.*)\", mandatory "
-                + r"item \"(?P<itm>.*)\" is not in category \"(?P<cat>.*)\"$"
+                + r"item \"(?P<itm>.*)\" is not in category \"(?P<cat>.*)\"$",
+                r"ERROR - In block \"(?P<dblock>.*)\", key item "
+                + r"\"(?P<itm>.*)\" not found in category \"(?P<cat>.*)\"$",
             ]:
                 match = re.match(pttrn, line)
                 if match is not None:
-                    rprt["missing_itms"].add(
+                    rprt["Missing items"].add(
                         f"{match.group('cat')}.{match.group('itm')}"
                     )
                     _check_dblock_name(match.group("dblock"), rprt)
@@ -761,7 +792,7 @@ class _CifCheck:
                 line,
             )
             if match is not None:
-                rprt["parchild_mm"].add(
+                rprt["Mismatching parent/ child relationships"].add(
                     f"{match.group('chld')}->{match.group('prnt')}"
                 )
                 _check_dblock_name(match.group("dblock"), rprt)
@@ -771,15 +802,18 @@ class _CifCheck:
                 line,
             )
             if match is not None:
-                # prepare a string to be removed from parchild_mm
+                # prepare a string to be removed from Mismatching parent/ child
+                # relationships
                 chld = match.group("chld").split(".")[0][1:]
                 prnt = match.group("prnt").split(".")[0][1:]
                 try:
-                    rprt["parchild_mm"].remove(f"{chld}->{prnt}")
+                    rprt["Mismatching parent/ child relationships"].remove(
+                        f"{chld}->{prnt}"
+                    )
                 except KeyError:
                     pass
                 # add a more verbose line instead
-                rprt["parchild_mm"].add(
+                rprt["Mismatching parent/ child relationships"].add(
                     f"{match.group('chld')}->{match.group('prnt')}, "
                     + f"value={match.group('vle')}"
                 )
@@ -790,64 +824,73 @@ class _CifCheck:
             )
 
         # print above evaluation in the report
-        # datablock
-        print("Diagnosis:")
-        if "datablock" in rprt:
-            print("   Datablock/ entry name:", rprt["datablock"])
-        if len(rprt["missing_cats"]) > 0:
-            print("   Missing categories:")
-            for line in sorted(rprt["missing_cats"]):
-                print(f"      {line}")
-        if len(rprt["missing_itms"]) > 0:
-            print("   Missing items:")
-            for line in sorted(rprt["missing_itms"]):
-                print(f"      {line}")
-        if len(rprt["parchild_mm"]) > 0:
-            print("   Mismatching parent/ child relationships:")
-            for line in sorted(rprt["parchild_mm"]):
-                print(f"      {line}")
-
-        # condense 'other' errors
+        _print_report("Diagnosis:", rprt)
+
+    def _condense_other_errors(self):
+        """Gather errors not covered by diagnosis."""
         rprt = {
-            "missing_files": {},
+            "Missing (archive) files": {},
         }
         for line in self.check_results["errors"]:
-            match = re.match(r"ma_entry_associated_files.file_url '(?P<arc>.*)' is missing ma_associated_archive_file_details.file_path '(?P<fle>.*)'", line)
+            match = re.match(
+                r"ma_entry_associated_files.file_url '(?P<arc>.*)' is missing "
+                + r"ma_associated_archive_file_details.file_path '(?P<fle>.*)'",
+                line,
+            )
             if match is not None:
                 try:
-                    rprt["missing_files"][match.group('arc')].append(match.group('fle'))
+                    rprt["Missing (archive) files"][match.group("arc")].append(
+                        match.group("fle")
+                    )
                 except KeyError:
-                    rprt["missing_files"][match.group('arc')] = [match.group('fle')]
+                    rprt["Missing (archive) files"][match.group("arc")] = [
+                        match.group("fle")
+                    ]
                 continue
             # Unmatched lines need to be added to above evaluation
             raise RuntimeError(f'Unmatched error line found:\n"""{line}"""')
 
         # print above evaluation in the report
-        print("Other issues:")
-        if len(rprt["missing_files"]) > 0:
-            print("   Missing (archive) )files:")
-            for arc, fles in rprt["missing_files"].items():
-                print(f"      {arc}:")
-                for line in fles:
-                    print(f"         {line}")
+        _print_report("Other issues:", rprt)
+
+    def make_report(self):
+        """Make a concise report out of the results.
+
+        Be aware, that cuts away the majority of the messages. But solving those
+        issues first, may already repair a mmCIF file."""
+        print(
+            "Report\n======\nStatus of check: "
+            + f"{self.check_results['status']}"
+        )
+        self.to_json()  # get some extra data created for the JSON dump
+        if "versions" in self.check_results:
+            print("CIF dictionaries used:")
+            for dct in self.check_results["versions"]:
+                print(
+                    f"   {dct['title']}/ {dct['version']}\n"
+                    + f"   {dct['location']}"
+                )
 
-        # print erros/ messages caught
-        print("Errors by running CifCheck:")
-        for line in self.check_results["cifcheck-errors"]:
-            print(f"   {line}")
+        self._condense_diagnosis_data()
+        self._condense_other_errors()
+        # print errors/ messages caught
+        if len(self.check_results["cifcheck-errors"]) > 0:
+            print("Errors by running CifCheck:")
+            for line in self.check_results["cifcheck-errors"]:
+                print(f"   {line}")
 
 
 def _check_dblock_name(name, report):
-    """Compare datablock names."""
+    """Compare data block names."""
     try:
         # pylint: disable=used-before-assignment
-        if report["datablock"] != name:
+        if report["Datablock/ entry name"][0] != name:
             raise RuntimeError(
                 "Two different datablock (names) found: "
-                + f"{report['datablock']} vs {name}"
+                + f"{report['Datablock/ entry name'][0]} vs {name}"
             )
-    except KeyError:
-        report["datablock"] = name
+    except IndexError:
+        report["Datablock/ entry name"] = [name]
 
 
 def _find_utf(line):
@@ -891,7 +934,7 @@ def _main():
         cifcheck.make_json_output()
         sys.exit(1)
 
-    # check for associated files referenced by the model cif file
+    # check for associated files referenced by the model CIF file
     assoc_files, model_cif_data, entry_id_map = _get_associated_files(
         opts.model_cif,
         opts.associates_dir,
@@ -902,15 +945,15 @@ def _main():
         o_model_cif_data = copy.deepcopy(model_cif_data)
     # make sure associated files exist and merge all of them into the model
     for assoc, entry_id in assoc_files:
-        # merge the model.cif and the associated file
+        # merge the model CIF and the associated file
         msgs = _merge_cif_data(model_cif_data, assoc, entry_id, entry_id_map)
         cifcheck.add_to_results(msgs)
 
     validate_file = opts.model_cif
     if assoc_files:
-        # write merged data to disk, create tmp file, clean up when done
+        # write merged data to disk, create temporary file, clean up when done
         cfh, cfn = tempfile.mkstemp(suffix=".cif", text=True)
-        # register for deletion here and in cwd
+        # register for deletion here and in current working directory
         atexit.register(_try_os_remove, cfn)
         os.close(cfh)
         _write_mmcif(cfn, model_cif_data)
@@ -921,7 +964,8 @@ def _main():
     if not success:
         if opts.report:
             cifcheck.make_report()
-        cifcheck.make_json_output()
+        else:
+            cifcheck.make_json_output()
         sys.exit(1)
 
     # upon request (-e) extend the ORIGINAL file (not the merged one)
@@ -937,8 +981,8 @@ def _main():
     # print a report to stdout
     if opts.report:
         cifcheck.make_report()
-
-    cifcheck.make_json_output()
+    else:
+        cifcheck.make_json_output()
 
     if cifcheck.got_issues():
         # If CifCheck found issues with the mmCIF file, exit with code 2. Exit
@@ -949,5 +993,3 @@ def _main():
 
 if __name__ == "__main__":
     _main()
-
-#  LocalWords:  cif MSA FASTA pylint stdout CifCheck param src str dest cwd