← back to Rentv Adintel

lib/classification.js

93 lines

'use strict';
/**
 * Classification helpers (spec §2, §6.16, §6.17).
 *
 * Pure functions — no database I/O. Import from lib/types (single vocabulary).
 */

const { RELATIONSHIP_STATUS, VERIFIED_STATUSES, STATUS_LABELS, normalizeName } = require('./types');

/** Returns true if the given status is one of the VERIFIED_* statuses. */
function isVerifiedStatus(status) {
  return VERIFIED_STATUSES.includes(status);
}

/**
 * Guard: prevents promoting a SPEAKER_OR_PANELIST_ONLY to any VERIFIED_*SPONSOR
 * or VERIFIED_ADVERTISER status without explicit sponsor evidence (§6.16, §6.17).
 *
 * @param {string} fromStatus  - current relationship status
 * @param {string} toStatus    - proposed new status
 * @param {boolean} hasSponsorEvidence - caller must have verified separate sponsor evidence
 * @throws {Error} if the promotion is prohibited
 */
function assertNotPanelistMislabeledAsSponsor(fromStatus, toStatus, hasSponsorEvidence) {
  if (fromStatus !== 'SPEAKER_OR_PANELIST_ONLY') return; // only applies to panelists

  const SPONSOR_OR_ADVERTISER_STATUSES = [
    'VERIFIED_ADVERTISER',
    'VERIFIED_CONFERENCE_SPONSOR',
    'VERIFIED_EXHIBITOR',
    'VERIFIED_MEDIA_PARTNER',
  ];

  if (!SPONSOR_OR_ADVERTISER_STATUSES.includes(toStatus)) return; // not a protected promotion

  if (!hasSponsorEvidence) {
    throw new Error(
      `Classification error: cannot promote "${fromStatus}" to "${toStatus}" without separate, ` +
      `explicit sponsor/advertiser evidence. Add a dated evidence record that directly proves ` +
      `the paid relationship before changing this status. (spec §6.16, §6.17)`
    );
  }
}

/**
 * Return the appropriate default relationship status for a given intake signal.
 *
 * @param {'AD' | 'EMAIL_AD' | 'CONFERENCE_SPONSOR' | 'PANELIST' | 'CONTENT_PRESENTATION' | 'PROSPECT' | 'UNKNOWN'} signal
 * @returns {string} one of RELATIONSHIP_STATUS values
 */
function defaultStatusForSignal(signal) {
  switch (signal) {
    case 'AD':                    return 'VERIFIED_ADVERTISER';
    case 'EMAIL_AD':              return 'VERIFIED_ADVERTISER';
    case 'CONFERENCE_SPONSOR':    return 'VERIFIED_CONFERENCE_SPONSOR';
    case 'EXHIBITOR':             return 'VERIFIED_EXHIBITOR';
    case 'MEDIA_PARTNER':         return 'VERIFIED_MEDIA_PARTNER';
    case 'CONTENT_PRESENTATION':  return 'VERIFIED_CONTENT_PARTNER';
    case 'PANELIST':              return 'SPEAKER_OR_PANELIST_ONLY';
    case 'SPEAKER':               return 'SPEAKER_OR_PANELIST_ONLY';
    case 'PROSPECT':              return 'LIKELY_PROSPECT';
    case 'PAST':                  return 'PAST_ADVERTISER';
    case 'UNKNOWN':               return 'RESEARCH_NEEDED';
    default:                      return 'RESEARCH_NEEDED';
  }
}

/**
 * Return the plain-English Simple View label for a status (spec §25).
 * @param {string} status
 * @returns {string}
 */
function statusLabel(status) {
  return STATUS_LABELS[status] || status;
}

/**
 * Validate that a given status string is in the canonical vocabulary.
 * @param {string} status
 * @returns {boolean}
 */
function isKnownStatus(status) {
  return RELATIONSHIP_STATUS.includes(status);
}

module.exports = {
  isVerifiedStatus,
  assertNotPanelistMislabeledAsSponsor,
  defaultStatusForSignal,
  statusLabel,
  isKnownStatus,
};