← back to Nationalrealestate

src/lib/commercial_types.ts

56 lines

/**
 * Canonical commercial property-type taxonomy for the LA-region commercial layer.
 *
 * LA County assessor rows carry a coarse `use_desc1` (Commercial | Industrial) and a
 * finer free-text `use_desc2` (e.g. "Office Building", "Shopping Center (Regional)").
 * This maps those to a small canonical set the UI/API filter on, using ordered
 * substring rules so string variants across counties still classify. "Studio" is
 * broken out on purpose — Motion Picture/Radio/TV is an LA-distinctive commercial
 * category worth surfacing in a local commercial-RE-news product.
 *
 * Derived from the full LA use_desc2 distribution (154k commercial+industrial parcels).
 */

export type CommercialType =
  | 'office' | 'retail' | 'industrial' | 'hospitality'
  | 'studio' | 'parking' | 'other';

export interface CommercialCategory { key: CommercialType; label: string }

export const COMMERCIAL_CATEGORIES: CommercialCategory[] = [
  { key: 'office', label: 'Office' },
  { key: 'retail', label: 'Retail' },
  { key: 'industrial', label: 'Industrial' },
  { key: 'hospitality', label: 'Hospitality' },
  { key: 'studio', label: 'Studio / Media' },
  { key: 'parking', label: 'Parking' },
  { key: 'other', label: 'Other Commercial' },
];

/** Ordered [regex, type] rules — first match wins (most specific first). */
const RULES: [RegExp, CommercialType][] = [
  [/motion picture|radio|television|studio/i, 'studio'],
  [/hotel|motel|hospitality|lodging/i, 'hospitality'],
  [/parking/i, 'parking'],
  [/office|professional building|bank|savings/i, 'office'],
  [/warehous|distribution|storage|manufactur|industrial|food processing|mineral|lumber|open storage/i, 'industrial'],
  [/store|shopping|supermarket|department|restaurant|cocktail|service station|repair shop|paint shop|laundry|auto|recreation equipment|wholesale|outlet|nursery|greenhouse|kennel|retail/i, 'retail'],
];

/**
 * Classify an LA assessor row into a canonical commercial type.
 * Returns null when the row is not commercial/industrial (residential, farm, etc.).
 */
export function classifyCommercial(useDesc1: string | null, useDesc2: string | null): CommercialType | null {
  const d1 = (useDesc1 || '').trim();
  if (d1 !== 'Commercial' && d1 !== 'Industrial') return null;
  const d2 = (useDesc2 || '').trim();
  for (const [re, type] of RULES) if (re.test(d2)) return type;
  // Industrial-bucket rows with an unmatched/blank sub-type default to industrial; else other.
  return d1 === 'Industrial' ? 'industrial' : 'other';
}

export function categoryLabel(type: CommercialType): string {
  return COMMERCIAL_CATEGORIES.find(c => c.key === type)?.label ?? 'Other Commercial';
}