import { readFileSync } from 'fs';
import { join } from 'path';
import { ClassificationVersionResource, NACECodeEnhanced, ClassificationItemResource } from './types';
export class NACEUtils {
private static naceData: ClassificationVersionResource | null = null;
private static processedCodes: NACECodeEnhanced[] | null = null;
private static loadNACEData(): ClassificationVersionResource {
if (!this.naceData) {
try {
const dataPath = join(process.cwd(), 'data', 'nace-codes-full.json');
const rawData = readFileSync(dataPath, 'utf-8');
this.naceData = JSON.parse(rawData) as ClassificationVersionResource;
} catch (error) {
console.error('Failed to load NACE data:', error);
throw new Error('Failed to load NACE classification data');
}
}
return this.naceData;
}
private static getProcessedCodes(): NACECodeEnhanced[] {
if (!this.processedCodes) {
const data = this.loadNACEData();
this.processedCodes = this.buildCodeHierarchy(data.classificationItems);
}
return this.processedCodes;
}
private static buildCodeHierarchy(items: ClassificationItemResource[]): NACECodeEnhanced[] {
const codeMap = new Map<string, NACECodeEnhanced>();
// First pass: create all items with basic info
items.forEach(item => {
codeMap.set(item.code, {
code: item.code,
name: item.name,
shortName: item.shortName,
parentCode: item.parentCode,
level: item.level,
notes: item.notes,
fullCodePath: item.code // Will be updated in second pass
});
});
// Second pass: build full code paths
codeMap.forEach((code, codeStr) => {
const path = this.buildFullCodePath(codeStr, codeMap);
code.fullCodePath = path;
});
return Array.from(codeMap.values());
}
private static buildFullCodePath(code: string, codeMap: Map<string, NACECodeEnhanced>): string {
const item = codeMap.get(code);
if (!item) return code;
if (!item.parentCode || item.parentCode === code) {
return item.code;
}
const parentPath = this.buildFullCodePath(item.parentCode, codeMap);
return `${parentPath} / ${item.code}`;
}
/**
* Get all NACE codes that match a hierarchical filter
* @param parentCode The parent code to filter by (e.g., "01.1" will match all codes starting with "01 / 01.1")
* @returns Array of matching NACE codes
*/
static getHierarchicalCodes(parentCode: string): NACECodeEnhanced[] {
const processedCodes = this.getProcessedCodes();
// Find the full path for the parent code
const parentEntry = processedCodes.find(item => item.code === parentCode);
if (!parentEntry) {
return [];
}
const parentPath = parentEntry.fullCodePath;
// Return all codes that have this parent in their path
return processedCodes.filter(item =>
item.fullCodePath.startsWith(parentPath) &&
item.code !== parentCode
);
}
/**
* Get all child codes for a given NACE code (including the code itself)
* @param parentCode The parent NACE code
* @returns Array of all codes in the hierarchy (including parent)
*/
static getAllChildCodes(parentCode: string): string[] {
const processedCodes = this.getProcessedCodes();
// Find the full path for the parent code
const parentEntry = processedCodes.find(item => item.code === parentCode);
if (!parentEntry) {
return [parentCode]; // Return original code if not found in our data
}
const parentPath = parentEntry.fullCodePath;
// Return all codes that have this parent in their path, including the parent itself
const matchingCodes = processedCodes
.filter(item => item.fullCodePath.startsWith(parentPath))
.map(item => item.code);
// Always include the parent code itself
if (!matchingCodes.includes(parentCode)) {
matchingCodes.push(parentCode);
}
return matchingCodes;
}
/**
* Search NACE codes by text description
* @param searchText Text to search for in descriptions
* @returns Array of matching NACE codes
*/
static searchNACECodes(searchText: string): NACECodeEnhanced[] {
const processedCodes = this.getProcessedCodes();
const lowerSearchText = searchText.toLowerCase();
return processedCodes.filter(item =>
item.name.toLowerCase().includes(lowerSearchText) ||
item.shortName.toLowerCase().includes(lowerSearchText) ||
item.code.toLowerCase().includes(lowerSearchText) ||
item.notes.toLowerCase().includes(lowerSearchText)
);
}
/**
* Get NACE code by exact code
* @param code The NACE code to look up
* @returns The NACE code entry or null if not found
*/
static getNACEByCode(code: string): NACECodeEnhanced | null {
const processedCodes = this.getProcessedCodes();
return processedCodes.find(item => item.code === code) || null;
}
/**
* Get all NACE codes
* @returns Array of all NACE codes
*/
static getAllNACECodes(): NACECodeEnhanced[] {
return this.getProcessedCodes();
}
/**
* Get NACE codes by level
* @param level The level to filter by (1-5)
* @returns Array of NACE codes at the specified level
*/
static getNACECodesByLevel(level: string): NACECodeEnhanced[] {
const processedCodes = this.getProcessedCodes();
return processedCodes.filter(item => item.level === level);
}
/**
* Get top-level NACE codes (level 1)
* @returns Array of top-level NACE codes
*/
static getTopLevelNACECodes(): NACECodeEnhanced[] {
return this.getNACECodesByLevel('1');
}
/**
* Get classification metadata
* @returns Classification version information
*/
static getClassificationInfo(): ClassificationVersionResource {
return this.loadNACEData();
}
/**
* Expand industry codes to include all hierarchical children
* @param industryCodes Array of industry codes to expand
* @returns Array of all codes including hierarchical children
*/
static expandIndustryCodes(industryCodes: string[]): string[] {
const expandedCodes: string[] = [];
for (const code of industryCodes) {
const childCodes = this.getAllChildCodes(code);
expandedCodes.push(...childCodes);
}
// Remove duplicates while preserving order
return [...new Set(expandedCodes)];
}
/**
* Get direct children of a parent code
* @param parentCode The parent code
* @returns Array of direct child codes
*/
static getDirectChildren(parentCode: string): NACECodeEnhanced[] {
const processedCodes = this.getProcessedCodes();
return processedCodes.filter(item => item.parentCode === parentCode);
}
/**
* Get parent code information
* @param code The child code
* @returns Parent code information or null if not found
*/
static getParentCode(code: string): NACECodeEnhanced | null {
const item = this.getNACEByCode(code);
if (!item || !item.parentCode || item.parentCode === code) {
return null;
}
return this.getNACEByCode(item.parentCode);
}
}