Home Reference Source

src/parse/addObservation.js

import { roundNumber } from "./roundNumber";
import { store } from "../store";
import { RATINGS, TRENDS } from "../observations/config.js";

// Formatting to apply to observation values, e.g., rounding, encoding, etc...
const FORMATTERS = {
  score: (score) => roundNumber(score, 2),
  rank: (rank) => (rank != null ? parseInt(rank) : null),
  value: (value) => roundNumber(value, 2),
  isImputed: (isImputed) => (isImputed ? 1 : null),
  rating: (rating) => {
    // Convert rating
    if (rating == null || rating === "grey" || rating === "") rating = "gray";

    // Throw error if rating is not valid
    if (!RATINGS.includes(rating))
      throw new Error(
        `Rating ${rating} is not a valid. Must be one of: ${RATINGS}`
      );

    // Encode rating as number
    return RATINGS.indexOf(rating);
  },
  trend: (trend) => {
    if (trend == null || trend === "") trend = "•";

    // Throw error if trend is not valid
    if (!TRENDS.includes(trend))
      throw new Error(
        `Trend ${trend} is not a valid. Must be one of: ${TRENDS}`
      );

    // Encode trend as number
    return TRENDS.indexOf(trend);
  },
};

/**
 * Add an observation to the store
 * @param {Object} observation
 * @param {Object} observation.region -
 * the region to which the observation belongs
 * @param {Object} observation.assessment -
 * the assessment to which the observation belongs
 * @param {number} [observation.score] -
 * the score of the region on the assessment
 * @param {number} [observation.rank] -
 * the rank of the region on the assessment
 * @param {string} [observation.rating] -
 * the rating of the region on the assessment,
 * e.g., `green`, `yellow`, `orange`, `red`, or `gray`
 * @param {?string} [observation.trend] -
 * the trend of the region on the assessment,
 * e.g., `↑`, `➚`, `→`, `↓`, or `null`
 * @param {number} [observation.value] -
 * the raw value of the region on the assessment (applicable to indicators only)
 * @param {number} [observation.year] -
 * the raw value of the region on the assessment (applicable to indicators only)
 * @param {boolean} [observation.isImputed=false] -
 * whether the observation is imputed (`true` or `false`)
 * @param {...Object} [observation.params] -
 * any other properties to store on the observation
 * @return {Object} the observation that was added to the store
 */
export const addObservation = ({ region, assessment, ...observation }) => {
  // Format values (rounding, encoding, etc...)
  Object.entries(FORMATTERS).forEach(([key, formatter]) => {
    // Skip if property is not defined
    if (!Object.prototype.hasOwnProperty.call(observation, key)) return;

    // Format value using property formatter
    observation[key] = formatter(observation[key]);
  });

  // Prepare observations in store
  if (!store.observations) store.observations = {};

  // Verify ID does not yet exist
  const id = `${region.dataId}-${assessment.dataId}`;
  if (Object.prototype.hasOwnProperty.call(store.observations, id))
    throw new Error(`Observation with id ${id} already exists`);

  // Add observation
  store.observations[id] = observation;

  return observation;
};