Skip to main content
NYC Permit Pulse tracks 18+ distinct permit types from the DOB NOW system. Each type has a unique code, color, and emoji for visual identification on the map.

All Permit Types

The app normalizes verbose work_type strings from the NYC Open Data API into short 2-4 letter codes:
CodeLabelColorEmojiWork Type
NBNew Buildingβ–  #00ff88πŸ—New Building
DMDemolitionβ–  #ff2222πŸ’₯Full Demolition
GCGeneral Constructionβ–  #ff8800πŸ”¨General Construction
PLPlumbingβ–  #4466ffπŸ”΅Plumbing
MEMechanicalβ–  #00ccffβš™οΈMechanical
SOLSolarβ–  #ffe600β˜€οΈSolar Photovoltaic
SHDSidewalk Shedβ–  #cc44ff🏚Sidewalk Shed
SCFScaffoldβ–  #ff44aaπŸͺœScaffold
FNCConst. Fenceβ–  #44ffdd🚧Construction Fence
SGSignβ–  #ffffffπŸ“‹Sign
FNDFoundationβ–  #a0522dπŸͺ¨Foundation
STRStructuralβ–  #ff6600πŸ”©Structural
BLRBoilerβ–  #ff0066πŸ”₯Boiler
SPRSprinklersβ–  #00aaffπŸ’§Sprinkler
EWEarth Workβ–  #88ff00🌍Earth Work
ANTAntennaβ–  #dd00ffπŸ“‘Antenna
CCCurb Cutβ–  #ffaa00πŸ›€Curb Cut
STPStandpipeβ–  #0055ff🚿Standpipe
OTHOtherβ–  #888888πŸ“Œ(any unrecognized type)

Color Palette

Colors are chosen for maximum visibility on the dark isometric map background:
export const WORK_TYPE_COLORS: Record<string, string> = {
  NB:  '#00ff88',  // bright green      β€” new building
  DM:  '#ff2222',  // red               β€” demolition
  GC:  '#ff8800',  // orange            β€” general construction
  PL:  '#4466ff',  // blue              β€” plumbing
  ME:  '#00ccff',  // cyan              β€” mechanical
  SOL: '#ffe600',  // yellow            β€” solar
  SHD: '#cc44ff',  // purple            β€” sidewalk shed
  SCF: '#ff44aa',  // pink              β€” scaffold
  FNC: '#44ffdd',  // teal              β€” construction fence
  SG:  '#ffffff',  // white             β€” sign
  FND: '#a0522d',  // brown             β€” foundation
  STR: '#ff6600',  // deep orange       β€” structural (distinct from GC)
  BLR: '#ff0066',  // hot pink-red      β€” boiler
  SPR: '#00aaff',  // sky blue          β€” sprinkler
  EW:  '#88ff00',  // yellow-green      β€” earth work
  ANT: '#dd00ff',  // violet            β€” antenna
  CC:  '#ffaa00',  // amber             β€” curb cut
  STP: '#0055ff',  // deep blue         β€” standpipe
  OTH: '#888888',  // grey              β€” other
};

Color Selection Criteria

  • High contrast - All colors pop against the dark map background
  • Distinct hues - Even color-blind users can distinguish major types
  • Semantic meaning - Red for demolition, green for new construction, blue for water-related (plumbing/sprinklers)

Emoji Mapping

Emojis provide quick visual recognition in the sidebar and permit drawer:
export const WORK_TYPE_EMOJIS: Record<string, string> = {
  NB:  'πŸ—',   DM:  'πŸ’₯',   GC:  'πŸ”¨',   PL:  'πŸ”΅',
  ME:  'βš™οΈ',   SOL: 'β˜€οΈ',   SHD: '🏚',   SCF: 'πŸͺœ',
  FNC: '🚧',   SG:  'πŸ“‹',   FND: 'πŸͺ¨',   STR: 'πŸ”©',
  BLR: 'πŸ”₯',   SPR: 'πŸ’§',   EW:  '🌍',   ANT: 'πŸ“‘',
  CC:  'πŸ›€',   STP: '🚿',   OTH: 'πŸ“Œ',
};

Work Type Normalization

The NYC Open Data work_type field contains verbose strings that need to be normalized:
export function workTypeToCode(workType: string): string {
  const wt = workType.toLowerCase();
  if (wt.includes('new building'))           return 'NB';
  if (wt.includes('full demolition'))        return 'DM';
  if (wt.includes('general construction'))   return 'GC';
  if (wt.includes('plumbing'))               return 'PL';
  if (wt.includes('mechanical'))             return 'ME';
  if (wt.includes('solar'))                  return 'SOL';
  if (wt.includes('sidewalk shed'))          return 'SHD';
  if (wt.includes('scaffold'))               return 'SCF';
  if (wt.includes('construction fence'))     return 'FNC';
  if (wt.includes('sign'))                   return 'SG';
  if (wt.includes('foundation'))             return 'FND';
  if (wt.includes('structural'))             return 'STR';
  if (wt.includes('boiler'))                 return 'BLR';
  if (wt.includes('sprinkler'))              return 'SPR';
  if (wt.includes('earth work'))             return 'EW';
  if (wt.includes('antenna'))                return 'ANT';
  if (wt.includes('curb cut'))               return 'CC';
  if (wt.includes('standpipe'))              return 'STP';
  return 'OTH';
}

Example Mappings

Raw work_type StringNormalized Code
"General Construction"GC
"GENERAL CONSTRUCTION (GC)"GC
"Plumbing"PL
"Mechanical Equipment"ME
"Solar Photovoltaic - Residential"SOL
"Sidewalk Shed - Public Safety"SHD
"Scaffold"SCF
"Construction Fence"FNC
"Sign - Accessory"SG
"Foundation"FND
"Boiler Equipment"BLR
"Fire Suppression - Sprinklers"SPR
"Antenna"ANT
"Curb Cut"CC
"Standpipe"STP
"Equipment Work"OTH
The workTypeToCode() function uses substring matching (includes()) to handle variations in capitalization, prefixes, and suffixes. This makes it robust to changes in the NYC Open Data schema.

Helper Functions

Get Color

export function getJobColor(jobType: string): string {
  return WORK_TYPE_COLORS[jobType] ?? '#666666';
}

// Usage
const color = getJobColor('NB'); // '#00ff88'

Get Emoji

export function getJobEmoji(jobType: string): string {
  return WORK_TYPE_EMOJIS[jobType] ?? 'πŸ“Œ';
}

// Usage
const emoji = getJobEmoji('DM'); // 'πŸ’₯'

Get Label

export const WORK_TYPE_LABELS: Record<string, string> = {
  NB: 'New Building',  DM: 'Demolition',      GC: 'General Construction',
  PL: 'Plumbing',      ME: 'Mechanical',      SOL: 'Solar',
  SHD: 'Sidewalk Shed', SCF: 'Scaffold',      FNC: 'Const. Fence',
  SG: 'Sign',          FND: 'Foundation',     STR: 'Structural',
  BLR: 'Boiler',       SPR: 'Sprinklers',     EW: 'Earth Work',
  ANT: 'Antenna',      CC: 'Curb Cut',        STP: 'Standpipe',
  OTH: 'Other',
};

export function getJobLabel(jobType: string): string {
  return WORK_TYPE_LABELS[jobType] ?? jobType;
}

// Usage
const label = getJobLabel('SOL'); // 'Solar'

Filtering Logic

The filter UI allows users to select which permit types to display:
const [filters, setFilters] = useState<FilterState>({
  jobTypes: new Set(ALL_JOB_TYPES),  // all types enabled by default
  boroughs: new Set(['MANHATTAN']),
  daysBack: 7,
});

const filteredPermits = useMemo(() => permits.filter(p => {
  const jt = p.job_type?.toUpperCase() ?? 'OTHER';
  const jobTypeMatch = filters.jobTypes.has(jt) || 
    (!ALL_JOB_TYPES.includes(jt) && filters.jobTypes.has('OTHER'));
  return jobTypeMatch && /* borough match */;
}), [permits, filters]);

Default Enabled Types

export const ALL_JOB_TYPES = [
  'NB', 'DM', 'GC', 'PL', 'ME', 'SOL', 
  'SHD', 'SCF', 'FNC', 'STR', 'FND', 'SG'
];
These 12 types appear in the filter UI by default. Less common types (BLR, SPR, EW, ANT, CC, STP) are grouped under β€œOTHER”.

Toggle Handler

const toggleJobType = (jt: string) => setFilters(prev => {
  const next = new Set(prev.jobTypes);
  next.has(jt) ? next.delete(jt) : next.add(jt);
  return { ...prev, jobTypes: next };
});

Permit Type Distribution

Based on typical 7-day Manhattan dataset (~2,000 permits):
TypeApprox. %Example Count
GC35%~700
PL20%~400
ME15%~300
SHD10%~200
SCF8%~160
SOL5%~100
NB3%~60
FND2%~40
DM1%~20
Other (combined)1%~20
General Construction (GC) dominates because it covers alterations, renovations, and interior work - the most common construction activity in NYC.

Marker Styling

On the map, each marker is color-coded and has hover effects:
.permit-marker {
  width: 10px;
  height: 10px;
  background: var(--color);
  border-radius: 50%;
  box-shadow: 0 0 6px var(--color), 0 0 12px var(--color);
  transition: opacity 0.15s, transform 0.15s;
  cursor: pointer;
}

.permit-marker:hover {
  transform: scale(1.5);
  opacity: 1 !important;
  z-index: 1000;
}

.permit-marker--selected {
  box-shadow: 0 0 12px var(--color), 0 0 24px var(--color), 0 0 36px #fff;
  transform: scale(1.3);
  z-index: 999;
}
The --color CSS custom property is set dynamically:
markerElement.style.setProperty('--color', getJobColor(permit.job_type ?? ''));

Next Steps

Architecture

See how permit types integrate with the component system

Data Sources

Learn where permit type data comes from

Build docs developers (and LLMs) love