From 4af15c04dceb5526838621692445840abd5bbaff Mon Sep 17 00:00:00 2001
From: Kevin Schaper <kevinschaper@gmail.com>
Date: Tue, 26 Sep 2023 08:19:12 -0700
Subject: [PATCH] Bring in similarty through copy & paste instead of import
 (#346)

Initially this PR started off with just pulling the schema and seeing if
we could minimally just move to using a Dict for now, but since the
frontend was using the typescript interfaces for the similarity classes,
I ended up just bringing the schema in minus the three conflicting
slots...which is actually the most minimal change.
---
 backend/src/monarch_py/cli.py                 |   2 +-
 backend/src/monarch_py/datamodels/model.py    | 174 +++++++++++----
 backend/src/monarch_py/datamodels/model.yaml  |   4 +-
 .../src/monarch_py/datamodels/similarity.yaml | 206 ++++++++++++++++++
 backend/src/monarch_py/utils/utils.py         |  14 +-
 frontend/src/api/model.ts                     |  15 +-
 6 files changed, 355 insertions(+), 60 deletions(-)
 create mode 100644 backend/src/monarch_py/datamodels/similarity.yaml

diff --git a/backend/src/monarch_py/cli.py b/backend/src/monarch_py/cli.py
index 19f105d2d..aafd47597 100644
--- a/backend/src/monarch_py/cli.py
+++ b/backend/src/monarch_py/cli.py
@@ -275,7 +275,7 @@ def compare(
     """Compare two entities using semantic similarity via OAK"""
     subjects = subjects.split(",")
     objects = objects.split(",")
-    response = oak.compare(subjects, objects)
+    response = oak().compare(subjects, objects)
     format_output(fmt, response, output)
 
 
diff --git a/backend/src/monarch_py/datamodels/model.py b/backend/src/monarch_py/datamodels/model.py
index 228b962a3..bad0a341b 100644
--- a/backend/src/monarch_py/datamodels/model.py
+++ b/backend/src/monarch_py/datamodels/model.py
@@ -47,13 +47,19 @@ class Association(ConfiguredBaseModel):
     id: str = Field(...)
     subject: str = Field(...)
     original_subject: Optional[str] = Field(None)
-    subject_namespace: Optional[str] = Field(None, description="""The namespace/prefix of the subject entity""")
-    subject_category: Optional[str] = Field(None, description="""The category of the subject entity""")
+    subject_namespace: Optional[str] = Field(
+        None, description="""The namespace/prefix of the subject entity"""
+    )
+    subject_category: Optional[str] = Field(
+        None, description="""The category of the subject entity"""
+    )
     subject_closure: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing subject id and the ids of all of it's ancestors""",
     )
-    subject_label: Optional[str] = Field(None, description="""the label or name for the first entity""")
+    subject_label: Optional[str] = Field(
+        None, description="""The name of the subject entity"""
+    )
     subject_closure_label: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing subject name and the names of all of it's ancestors""",
@@ -63,13 +69,19 @@ class Association(ConfiguredBaseModel):
     predicate: str = Field(...)
     object: str = Field(...)
     original_object: Optional[str] = Field(None)
-    object_namespace: Optional[str] = Field(None, description="""The namespace/prefix of the object entity""")
-    object_category: Optional[str] = Field(None, description="""The category of the object entity""")
+    object_namespace: Optional[str] = Field(
+        None, description="""The namespace/prefix of the object entity"""
+    )
+    object_category: Optional[str] = Field(
+        None, description="""The category of the object entity"""
+    )
     object_closure: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing object id and the ids of all of it's ancestors""",
     )
-    object_label: Optional[str] = Field(None, description="""the label or name for the second entity""")
+    object_label: Optional[str] = Field(
+        None, description="""The name of the object entity"""
+    )
     object_closure_label: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing object name and the names of all of it's ancestors""",
@@ -97,7 +109,9 @@ class Association(ConfiguredBaseModel):
         description="""count of supporting documents, evidence codes, and sources supplying evidence""",
     )
     pathway: Optional[str] = Field(None)
-    frequency_qualifier_label: Optional[str] = Field(None, description="""The name of the frequency_qualifier entity""")
+    frequency_qualifier_label: Optional[str] = Field(
+        None, description="""The name of the frequency_qualifier entity"""
+    )
     frequency_qualifier_namespace: Optional[str] = Field(
         None, description="""The namespace/prefix of the frequency_qualifier entity"""
     )
@@ -112,11 +126,15 @@ class Association(ConfiguredBaseModel):
         default_factory=list,
         description="""Field containing frequency_qualifier name and the names of all of it's ancestors""",
     )
-    onset_qualifier_label: Optional[str] = Field(None, description="""The name of the onset_qualifier entity""")
+    onset_qualifier_label: Optional[str] = Field(
+        None, description="""The name of the onset_qualifier entity"""
+    )
     onset_qualifier_namespace: Optional[str] = Field(
         None, description="""The namespace/prefix of the onset_qualifier entity"""
     )
-    onset_qualifier_category: Optional[str] = Field(None, description="""The category of the onset_qualifier entity""")
+    onset_qualifier_category: Optional[str] = Field(
+        None, description="""The category of the onset_qualifier entity"""
+    )
     onset_qualifier_closure: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing onset_qualifier id and the ids of all of it's ancestors""",
@@ -125,11 +143,15 @@ class Association(ConfiguredBaseModel):
         default_factory=list,
         description="""Field containing onset_qualifier name and the names of all of it's ancestors""",
     )
-    sex_qualifier_label: Optional[str] = Field(None, description="""The name of the sex_qualifier entity""")
+    sex_qualifier_label: Optional[str] = Field(
+        None, description="""The name of the sex_qualifier entity"""
+    )
     sex_qualifier_namespace: Optional[str] = Field(
         None, description="""The namespace/prefix of the sex_qualifier entity"""
     )
-    sex_qualifier_category: Optional[str] = Field(None, description="""The category of the sex_qualifier entity""")
+    sex_qualifier_category: Optional[str] = Field(
+        None, description="""The category of the sex_qualifier entity"""
+    )
     sex_qualifier_closure: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing sex_qualifier id and the ids of all of it's ancestors""",
@@ -138,11 +160,15 @@ class Association(ConfiguredBaseModel):
         default_factory=list,
         description="""Field containing sex_qualifier name and the names of all of it's ancestors""",
     )
-    stage_qualifier_label: Optional[str] = Field(None, description="""The name of the stage_qualifier entity""")
+    stage_qualifier_label: Optional[str] = Field(
+        None, description="""The name of the stage_qualifier entity"""
+    )
     stage_qualifier_namespace: Optional[str] = Field(
         None, description="""The namespace/prefix of the stage_qualifier entity"""
     )
-    stage_qualifier_category: Optional[str] = Field(None, description="""The category of the stage_qualifier entity""")
+    stage_qualifier_category: Optional[str] = Field(
+        None, description="""The category of the stage_qualifier entity"""
+    )
     stage_qualifier_closure: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing stage_qualifier id and the ids of all of it's ancestors""",
@@ -199,13 +225,19 @@ class DirectionalAssociation(Association):
     id: str = Field(...)
     subject: str = Field(...)
     original_subject: Optional[str] = Field(None)
-    subject_namespace: Optional[str] = Field(None, description="""The namespace/prefix of the subject entity""")
-    subject_category: Optional[str] = Field(None, description="""The category of the subject entity""")
+    subject_namespace: Optional[str] = Field(
+        None, description="""The namespace/prefix of the subject entity"""
+    )
+    subject_category: Optional[str] = Field(
+        None, description="""The category of the subject entity"""
+    )
     subject_closure: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing subject id and the ids of all of it's ancestors""",
     )
-    subject_label: Optional[str] = Field(None, description="""the label or name for the first entity""")
+    subject_label: Optional[str] = Field(
+        None, description="""The name of the subject entity"""
+    )
     subject_closure_label: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing subject name and the names of all of it's ancestors""",
@@ -215,13 +247,19 @@ class DirectionalAssociation(Association):
     predicate: str = Field(...)
     object: str = Field(...)
     original_object: Optional[str] = Field(None)
-    object_namespace: Optional[str] = Field(None, description="""The namespace/prefix of the object entity""")
-    object_category: Optional[str] = Field(None, description="""The category of the object entity""")
+    object_namespace: Optional[str] = Field(
+        None, description="""The namespace/prefix of the object entity"""
+    )
+    object_category: Optional[str] = Field(
+        None, description="""The category of the object entity"""
+    )
     object_closure: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing object id and the ids of all of it's ancestors""",
     )
-    object_label: Optional[str] = Field(None, description="""the label or name for the second entity""")
+    object_label: Optional[str] = Field(
+        None, description="""The name of the object entity"""
+    )
     object_closure_label: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing object name and the names of all of it's ancestors""",
@@ -249,7 +287,9 @@ class DirectionalAssociation(Association):
         description="""count of supporting documents, evidence codes, and sources supplying evidence""",
     )
     pathway: Optional[str] = Field(None)
-    frequency_qualifier_label: Optional[str] = Field(None, description="""The name of the frequency_qualifier entity""")
+    frequency_qualifier_label: Optional[str] = Field(
+        None, description="""The name of the frequency_qualifier entity"""
+    )
     frequency_qualifier_namespace: Optional[str] = Field(
         None, description="""The namespace/prefix of the frequency_qualifier entity"""
     )
@@ -264,11 +304,15 @@ class DirectionalAssociation(Association):
         default_factory=list,
         description="""Field containing frequency_qualifier name and the names of all of it's ancestors""",
     )
-    onset_qualifier_label: Optional[str] = Field(None, description="""The name of the onset_qualifier entity""")
+    onset_qualifier_label: Optional[str] = Field(
+        None, description="""The name of the onset_qualifier entity"""
+    )
     onset_qualifier_namespace: Optional[str] = Field(
         None, description="""The namespace/prefix of the onset_qualifier entity"""
     )
-    onset_qualifier_category: Optional[str] = Field(None, description="""The category of the onset_qualifier entity""")
+    onset_qualifier_category: Optional[str] = Field(
+        None, description="""The category of the onset_qualifier entity"""
+    )
     onset_qualifier_closure: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing onset_qualifier id and the ids of all of it's ancestors""",
@@ -277,11 +321,15 @@ class DirectionalAssociation(Association):
         default_factory=list,
         description="""Field containing onset_qualifier name and the names of all of it's ancestors""",
     )
-    sex_qualifier_label: Optional[str] = Field(None, description="""The name of the sex_qualifier entity""")
+    sex_qualifier_label: Optional[str] = Field(
+        None, description="""The name of the sex_qualifier entity"""
+    )
     sex_qualifier_namespace: Optional[str] = Field(
         None, description="""The namespace/prefix of the sex_qualifier entity"""
     )
-    sex_qualifier_category: Optional[str] = Field(None, description="""The category of the sex_qualifier entity""")
+    sex_qualifier_category: Optional[str] = Field(
+        None, description="""The category of the sex_qualifier entity"""
+    )
     sex_qualifier_closure: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing sex_qualifier id and the ids of all of it's ancestors""",
@@ -290,11 +338,15 @@ class DirectionalAssociation(Association):
         default_factory=list,
         description="""Field containing sex_qualifier name and the names of all of it's ancestors""",
     )
-    stage_qualifier_label: Optional[str] = Field(None, description="""The name of the stage_qualifier entity""")
+    stage_qualifier_label: Optional[str] = Field(
+        None, description="""The name of the stage_qualifier entity"""
+    )
     stage_qualifier_namespace: Optional[str] = Field(
         None, description="""The namespace/prefix of the stage_qualifier entity"""
     )
-    stage_qualifier_category: Optional[str] = Field(None, description="""The category of the stage_qualifier entity""")
+    stage_qualifier_category: Optional[str] = Field(
+        None, description="""The category of the stage_qualifier entity"""
+    )
     stage_qualifier_closure: Optional[List[str]] = Field(
         default_factory=list,
         description="""Field containing stage_qualifier id and the ids of all of it's ancestors""",
@@ -322,11 +374,15 @@ class Entity(ConfiguredBaseModel):
     id: str = Field(...)
     category: Optional[str] = Field(None)
     name: Optional[str] = Field(None)
-    full_name: Optional[str] = Field(None, description="""The long form name of an entity""")
+    full_name: Optional[str] = Field(
+        None, description="""The long form name of an entity"""
+    )
     description: Optional[str] = Field(None)
     xref: Optional[List[str]] = Field(default_factory=list)
     provided_by: Optional[str] = Field(None)
-    in_taxon: Optional[str] = Field(None, description="""The biolink taxon that the entity is in the closure of.""")
+    in_taxon: Optional[str] = Field(
+        None, description="""The biolink taxon that the entity is in the closure of."""
+    )
     in_taxon_label: Optional[str] = Field(
         None,
         description="""The label of the biolink taxon that the entity is in the closure of.""",
@@ -378,7 +434,9 @@ class Node(Entity):
     UI container class extending Entity with additional information
     """
 
-    in_taxon: Optional[str] = Field(None, description="""The biolink taxon that the entity is in the closure of.""")
+    in_taxon: Optional[str] = Field(
+        None, description="""The biolink taxon that the entity is in the closure of."""
+    )
     in_taxon_label: Optional[str] = Field(
         None,
         description="""The label of the biolink taxon that the entity is in the closure of.""",
@@ -404,7 +462,9 @@ class Node(Entity):
     id: str = Field(...)
     category: Optional[str] = Field(None)
     name: Optional[str] = Field(None)
-    full_name: Optional[str] = Field(None, description="""The long form name of an entity""")
+    full_name: Optional[str] = Field(
+        None, description="""The long form name of an entity"""
+    )
     description: Optional[str] = Field(None)
     xref: Optional[List[str]] = Field(default_factory=list)
     provided_by: Optional[str] = Field(None)
@@ -460,16 +520,22 @@ class EntityResults(Results):
 
 class SearchResult(Entity):
 
-    highlight: Optional[str] = Field(None, description="""matching text snippet containing html tags""")
-    score: Optional[str] = Field(None, description="""Abstract base slot for different kinds of scores""")
+    highlight: Optional[str] = Field(
+        None, description="""matching text snippet containing html tags"""
+    )
+    score: Optional[float] = Field(None)
     id: str = Field(...)
     category: str = Field(...)
     name: str = Field(...)
-    full_name: Optional[str] = Field(None, description="""The long form name of an entity""")
+    full_name: Optional[str] = Field(
+        None, description="""The long form name of an entity"""
+    )
     description: Optional[str] = Field(None)
     xref: Optional[List[str]] = Field(default_factory=list)
     provided_by: Optional[str] = Field(None)
-    in_taxon: Optional[str] = Field(None, description="""The biolink taxon that the entity is in the closure of.""")
+    in_taxon: Optional[str] = Field(
+        None, description="""The biolink taxon that the entity is in the closure of."""
+    )
     in_taxon_label: Optional[str] = Field(
         None,
         description="""The label of the biolink taxon that the entity is in the closure of.""",
@@ -510,21 +576,41 @@ class TermPairwiseSimilarity(PairwiseSimilarity):
     A simple pairwise similarity between two atomic concepts/terms
     """
 
-    subject_id: str = Field(..., description="""The first of the two entities being compared""")
-    subject_label: Optional[str] = Field(None, description="""the label or name for the first entity""")
-    subject_source: Optional[str] = Field(None, description="""the source for the first entity""")
-    object_id: Optional[str] = Field(None, description="""The second of the two entities being compared""")
-    object_label: Optional[str] = Field(None, description="""the label or name for the second entity""")
-    object_source: Optional[str] = Field(None, description="""the source for the second entity""")
+    subject_id: str = Field(
+        ..., description="""The first of the two entities being compared"""
+    )
+    subject_label: Optional[str] = Field(
+        None, description="""The name of the subject entity"""
+    )
+    subject_source: Optional[str] = Field(
+        None, description="""the source for the first entity"""
+    )
+    object_id: Optional[str] = Field(
+        None, description="""The second of the two entities being compared"""
+    )
+    object_label: Optional[str] = Field(
+        None, description="""The name of the object entity"""
+    )
+    object_source: Optional[str] = Field(
+        None, description="""the source for the second entity"""
+    )
     ancestor_id: Optional[str] = Field(
         None,
         description="""the most recent common ancestor of the two compared entities. If there are multiple MRCAs then the most informative one is selected""",
     )
-    ancestor_label: Optional[str] = Field(None, description="""the name or label of the ancestor concept""")
+    ancestor_label: Optional[str] = Field(
+        None, description="""the name or label of the ancestor concept"""
+    )
     ancestor_source: Optional[str] = Field(None)
-    object_information_content: Optional[float] = Field(None, description="""The IC of the object""")
-    subject_information_content: Optional[float] = Field(None, description="""The IC of the subject""")
-    ancestor_information_content: Optional[float] = Field(None, description="""The IC of the object""")
+    object_information_content: Optional[float] = Field(
+        None, description="""The IC of the object"""
+    )
+    subject_information_content: Optional[float] = Field(
+        None, description="""The IC of the subject"""
+    )
+    ancestor_information_content: Optional[float] = Field(
+        None, description="""The IC of the object"""
+    )
     jaccard_similarity: Optional[float] = Field(
         None,
         description="""The number of concepts in the intersection divided by the number in the union""",
diff --git a/backend/src/monarch_py/datamodels/model.yaml b/backend/src/monarch_py/datamodels/model.yaml
index 90076427b..fe6ffc201 100644
--- a/backend/src/monarch_py/datamodels/model.yaml
+++ b/backend/src/monarch_py/datamodels/model.yaml
@@ -4,10 +4,9 @@ description: Data models for the Monarch Initiative data access library
 prefixes:
   linkml: https://w3id.org/linkml/
   biolink: https://w3id.org/biolink/vocab/
-  oak: https://w3id.org/oak/
 imports:
   - linkml:types
-  - oak:similarity
+  - similarity
 default_range: string
 
 
@@ -239,7 +238,6 @@ classes:
         items:
           range: SearchResult
 
-
 slots:
   aggregator_knowledge_source:
     multivalued: true
diff --git a/backend/src/monarch_py/datamodels/similarity.yaml b/backend/src/monarch_py/datamodels/similarity.yaml
new file mode 100644
index 000000000..79f759334
--- /dev/null
+++ b/backend/src/monarch_py/datamodels/similarity.yaml
@@ -0,0 +1,206 @@
+id: https://w3id.org/monarch/monarch-py-similarity
+name: monarch-py-similarity
+description: Data models for the Monarch Initiative data access library
+prefixes:
+  linkml: https://w3id.org/linkml/
+imports:
+  - linkml:types
+default_range: string
+
+# This is a copy and paste from https://w3id.org/oak/similarity.yaml with some slots commented out because they
+# conflicted with slots in the monarch-py schema
+
+classes:
+  PairwiseSimilarity:
+    abstract: true
+    description: >-
+      Abstract grouping for representing individual pairwise similarities
+
+  TermPairwiseSimilarity:
+    is_a: PairwiseSimilarity
+    description: >-
+      A simple pairwise similarity between two atomic concepts/terms
+    slots:
+      - subject_id
+      - subject_label
+      - subject_source
+      - object_id
+      - object_label
+      - object_source
+      - ancestor_id
+      - ancestor_label
+      - ancestor_source
+      - object_information_content
+      - subject_information_content
+      - ancestor_information_content
+      - jaccard_similarity
+      - cosine_similarity
+      - dice_similarity
+      - phenodigm_score
+
+  TermSetPairwiseSimilarity:
+    is_a: PairwiseSimilarity
+    description: >-
+      A simple pairwise similarity between two sets of concepts/terms
+    slots:
+      - subject_termset
+      - object_termset
+      - subject_best_matches
+      - object_best_matches
+      - average_score
+      - best_score
+      - metric
+
+  TermInfo:
+    attributes:
+      id:
+        identifier: true
+      label:
+        slot_uri: rdfs:label
+
+  BestMatch:
+    attributes:
+      match_source:
+        identifier: true
+        comments:
+          - note that the match_source is either the subject or the object
+      match_source_label:
+      match_target:
+        description: the entity matches
+      match_target_label:
+      score:
+        range: float
+        required: true
+      match_subsumer:
+        range: uriorcurie
+      match_subsumer_label:
+      similarity:
+        range: TermPairwiseSimilarity
+        required: true
+
+
+types:
+  ZeroToOne:
+    typeof: float
+    minimum_value: 0
+    maximum_value: 0
+  NonNegativeFloat:
+    typeof: float
+    minimum_value: 0
+  NegativeLogValue:
+    typeof: float
+    minimum_value: 0
+  ItemCount:
+    typeof: integer
+    minimum_value: 0
+
+slots:
+  subject_id:
+    slot_uri: sssom:subject_id
+    required: true
+    range: uriorcurie
+    description: The first of the two entities being compared
+  # Excluded, since it conflicts with subject_label from this schema
+  #  subject_label:
+  #    slot_uri: sssom:subject_label
+  #    description: the label or name for the first entity
+  subject_source:
+    slot_uri: sssom:subject_source
+    description: the source for the first entity
+  object_id:
+    slot_uri: sssom:object_id
+    range: uriorcurie
+    description: The second of the two entities being compared
+  # Excluded, since it conflicts with object_label from this schema
+  #  object_label:
+  #    slot_uri: sssom:object_label
+  #    description: the label or name for the second entity
+  object_source:
+    slot_uri: sssom:object_source
+    description: the source for the second entity
+  ancestor_id:
+    range: uriorcurie
+    description: >-
+      the most recent common ancestor of the two compared entities. If there are multiple MRCAs then
+      the most informative one is selected
+    todos:
+      - decide on what to do when there are multiple possible ancestos
+  ancestor_label:
+    description: the name or label of the ancestor concept
+  ancestor_source:
+  # Excluded, conflicts with score from this schema
+  #  score:
+  #    abstract: true
+  #    description: Abstract base slot for different kinds of scores
+  information_content:
+    abstract: true
+    aliases:
+      - IC
+    is_a: score
+    range: NegativeLogValue
+    description: The IC is the negative log of the probability of the concept
+  subject_information_content:
+    is_a: information_content
+    description: The IC of the subject
+  object_information_content:
+    is_a: information_content
+    description: The IC of the object
+  ancestor_information_content:
+    is_a: information_content
+    description: The IC of the object
+  jaccard_similarity:
+    is_a: score
+    range: ZeroToOne
+    description: The number of concepts in the intersection divided by the number in the union
+  cosine_similarity:
+    is_a: score
+    range: float
+    description: the dot product of two node embeddings divided by the product of their lengths
+  dice_similarity:
+    is_a: score
+    range: ZeroToOne
+  phenodigm_score:
+    is_a: score
+    range: NonNegativeFloat
+    description: the geometric mean of the jaccard similarity and the information content
+    equals_expression: sqrt({jaccard_similarity} * {information_content})
+  overlap_coefficient:
+    is_a: score
+    range: ZeroToOne
+  subsumes_score:
+    is_a: score
+    range: ZeroToOne
+  subsumed_by_score:
+    is_a: score
+    range: ZeroToOne
+  intersection_count:
+    is_a: score
+    range: ItemCount
+  union_count:
+    is_a: score
+    range: ItemCount
+  # TermSets
+  subject_termset:
+    range: TermInfo
+    multivalued: true
+    inlined: true
+  object_termset:
+    range: TermInfo
+    multivalued: true
+    inlined: true
+  subject_best_matches:
+    range: BestMatch
+    multivalued: true
+    inlined: true
+  object_best_matches:
+    range: BestMatch
+    multivalued: true
+    inlined: true
+  metric:
+    range: uriorcurie
+  average_score:
+    range: float
+    required: false
+  best_score:
+    range: float
+    required: false
diff --git a/backend/src/monarch_py/utils/utils.py b/backend/src/monarch_py/utils/utils.py
index c01786749..72d04ee99 100644
--- a/backend/src/monarch_py/utils/utils.py
+++ b/backend/src/monarch_py/utils/utils.py
@@ -1,5 +1,7 @@
 import csv
+import json
 import sys
+from typing import Union, Dict
 
 import typer
 import yaml
@@ -86,14 +88,18 @@ def get_headers_from_obj(obj: ConfiguredBaseModel) -> list:
     return list(headers)
 
 
-def to_json(obj: ConfiguredBaseModel, file: str):
+def to_json(obj: Union[ConfiguredBaseModel, Dict], file: str):
     """Converts a pydantic model to a JSON string."""
+    if isinstance(obj, ConfiguredBaseModel):
+        json_value = obj.json(indent=4)
+    elif isinstance(obj, dict):
+        json_value = json.dumps(obj, indent=4)
     if file:
         with open(file, "w") as f:
-            f.write(obj.json(indent=4))
+            f.write(json_value)
         console.print(f"\nOutput written to {file}\n")
     else:
-        print_json(obj.json(indent=4))
+        print_json(json_value)
 
 
 def to_tsv(obj: ConfiguredBaseModel, file: str) -> str:
@@ -182,7 +188,7 @@ def to_yaml(obj: ConfiguredBaseModel, file: str):
     return
 
 
-def format_output(fmt: str, response: ConfiguredBaseModel, output: str):
+def format_output(fmt: str, response: Union[ConfiguredBaseModel, Dict], output: str):
     if fmt.lower() == "json":
         to_json(response, output)
     elif fmt.lower() == "tsv":
diff --git a/frontend/src/api/model.ts b/frontend/src/api/model.ts
index be1347dec..112c9980a 100644
--- a/frontend/src/api/model.ts
+++ b/frontend/src/api/model.ts
@@ -31,7 +31,7 @@ export interface Association {
     subject_category?: string,
     /** Field containing subject id and the ids of all of it's ancestors */
     subject_closure?: string[],
-    /** the label or name for the first entity */
+    /** The name of the subject entity */
     subject_label?: string,
     /** Field containing subject name and the names of all of it's ancestors */
     subject_closure_label?: string[],
@@ -46,7 +46,7 @@ export interface Association {
     object_category?: string,
     /** Field containing object id and the ids of all of it's ancestors */
     object_closure?: string[],
-    /** the label or name for the second entity */
+    /** The name of the object entity */
     object_label?: string,
     /** Field containing object name and the names of all of it's ancestors */
     object_closure_label?: string[],
@@ -174,7 +174,7 @@ export interface DirectionalAssociation extends Association {
     subject_category?: string,
     /** Field containing subject id and the ids of all of it's ancestors */
     subject_closure?: string[],
-    /** the label or name for the first entity */
+    /** The name of the subject entity */
     subject_label?: string,
     /** Field containing subject name and the names of all of it's ancestors */
     subject_closure_label?: string[],
@@ -189,7 +189,7 @@ export interface DirectionalAssociation extends Association {
     object_category?: string,
     /** Field containing object id and the ids of all of it's ancestors */
     object_closure?: string[],
-    /** the label or name for the second entity */
+    /** The name of the object entity */
     object_label?: string,
     /** Field containing object name and the names of all of it's ancestors */
     object_closure_label?: string[],
@@ -363,8 +363,7 @@ export interface Results {
 export interface SearchResult extends Entity {
     /** matching text snippet containing html tags */
     highlight?: string,
-    /** Abstract base slot for different kinds of scores */
-    score?: string,
+    score?: number,
     id: string,
     category: string,
     name: string,
@@ -406,13 +405,13 @@ export interface PairwiseSimilarity {
 export interface TermPairwiseSimilarity extends PairwiseSimilarity {
     /** The first of the two entities being compared */
     subject_id: string,
-    /** the label or name for the first entity */
+    /** The name of the subject entity */
     subject_label?: string,
     /** the source for the first entity */
     subject_source?: string,
     /** The second of the two entities being compared */
     object_id?: string,
-    /** the label or name for the second entity */
+    /** The name of the object entity */
     object_label?: string,
     /** the source for the second entity */
     object_source?: string,