Home Manual Reference Source Test

src/matchers/TaxonomicUnitMatcher.js

const { TaxonomicUnitWrapper } = require('../wrappers/TaxonomicUnitWrapper');
const { TaxonConceptWrapper } = require('../wrappers/TaxonConceptWrapper');
const { SpecimenWrapper } = require('../wrappers/SpecimenWrapper');

/**
 * The TaxonomicUnitMatcher matches pairs of taxonomic units and provides
 * a consistent report on:
 *  - Which taxonomic units have matched, and
 *  - Why the match occurred.
 *
 * In Model 2.0, we start by using direct matching in OWL, so this should no longer
 * be needed. However, I'll leave this around to provide matching in the
 * Curation Tool UI and in case it's needed again later.
 */
class TaxonomicUnitMatcher {
  /**
   * Create a Taxonomic Unit Matcher to match two taxonomic units. Matching
   * will occur immediately, so when this method returns, you can check
   * tuMatch.matched and tuMatch.matchReason to determine if the two TUs matched
   * and why.
   */
  constructor(tunit1, tunit2) {
    this.tunit1 = tunit1;
    this.tunit2 = tunit2;

    // Set up places to store the match results.
    this.matched = undefined; // Boolean variable for storing whether these TUnits matched.
    this.matchReason = undefined; // The reason provided for this match.

    // Execute the match.
    this.match();
  }

  /** Return this TUMatch as a JSON object for insertion into the PHYX file. */
  asJSONLD(idIRI) {
    if (!this.matched) return undefined;

    return {
      '@id': idIRI,
      reason: this.matchReason,
      matchesTaxonomicUnits: [
        { '@id': this.tunit1['@id'] },
        { '@id': this.tunit2['@id'] },
      ],
    };
  }

  /** Try to match the two taxonomic units using a number of matching methods. */
  match() {
    if (
      this.matchByNameComplete()
      || this.matchByExternalReferences()
      || this.matchByOccurrenceID()
    ) {
      this.matched = true;
    } else {
      this.matched = false;
      this.matchReason = undefined;
    }
  }

  /** Try to match by nameComplete, and return true if it could be matched. */
  matchByNameComplete() {
    // Note that this doesn't apply just to taxon concepts -- we try to match
    // any taxonomic units that have nameComplete, which might be taxon concepts
    // OR specimens with taxonomic units.
    const wrappedTName1 = new TaxonConceptWrapper(this.tunit1);
    const wrappedTName2 = new TaxonConceptWrapper(this.tunit2);

    if (
      wrappedTName1.nameComplete && wrappedTName2.nameComplete
      && wrappedTName1.nameComplete === wrappedTName2.nameComplete
    ) {
      this.matchReason = `Taxon name '${wrappedTName1.label}' and taxon name '${wrappedTName2.label}' share the same complete name`;
      return true;
    }

    return false;
  }

  /** Match by external references. */
  matchByExternalReferences() {
    const wrappedTUnit1 = new TaxonomicUnitWrapper(this.tunit1);
    const wrappedTUnit2 = new TaxonomicUnitWrapper(this.tunit2);

    const externalRefs1 = wrappedTUnit1.externalReferences;
    const externalRefs2 = wrappedTUnit2.externalReferences;

    return externalRefs1.some(
      extref1 => externalRefs2.some(
        (extref2) => {
          if (
            extref1
            && extref2
            && (extref1.toLowerCase() === extref2.toLowerCase())
          ) {
            this.matchReason = `External reference '${extref1}' is shared by taxonomic unit ${this.tunit1} and ${this.tunit2}`;
            return true;
          }

          return false;
        }
      )
    );
  }

  /** Match by occurrence ID */
  matchByOccurrenceID() {
    // Are both TUs specimens?
    const wrappedTUnit1 = new TaxonomicUnitWrapper(this.tunit1);
    const wrappedTUnit2 = new TaxonomicUnitWrapper(this.tunit2);

    if (!wrappedTUnit1.types.includes(TaxonomicUnitWrapper.TYPE_SPECIMEN)) return false;
    if (!wrappedTUnit2.types.includes(TaxonomicUnitWrapper.TYPE_SPECIMEN)) return false;

    // Occurrence IDs from both taxonomic units.
    const wrappedSpecimen1 = new SpecimenWrapper(this.tunit1);
    const wrappedSpecimen2 = new SpecimenWrapper(this.tunit2);

    if (
      wrappedSpecimen1.occurrenceID && wrappedSpecimen2.occurrenceID
      && wrappedSpecimen1.occurrenceID === wrappedSpecimen2.occurrenceID
    ) {
      this.matchReason = `Specimen identifier '${wrappedSpecimen1.occurrenceID}' is shared by taxonomic units`;

      return true;
    }

    return false;
  }
}

module.exports = {
  TaxonomicUnitMatcher,
};