# 🏭 Industry Classifier - NAICS & SIC Code Assignment (`that_red_bird/industry-classifier`) Actor

⚡ Assign a company a NAICS and/or SIC industry code with confidence and evidence, no LLM. ✅ Walks an embedded sector-then-detail code tree, scores with TF-IDF + phrase hits, and uses negative keywords so "SaaS for dentists" lands in software, not dentistry.

- **URL**: https://apify.com/that\_red\_bird/industry-classifier.md
- **Developed by:** [mohamed alaya](https://apify.com/that_red_bird) (community)
- **Categories:** Business, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Industry Classifier

Assign a company a **NAICS and/or SIC industry code** — with confidence and the exact evidence
that decided it — deterministically, with **no LLM and no embeddings model**. Industry
classification is effectively unserved on Apify: most "classify this company" tools either call
an LLM per row (slow, non-deterministic, costs a token every time) or don't exist at all. This
actor is the same "hierarchy done properly" approach as `product-taxonomy-classifier`, applied to
NAICS/SIC.

### What it actually does

**1. Hierarchy, done properly.** Level 1 picks a NAICS 2-digit **sector** (24 candidates — every
official 2017/2022 sector; Manufacturing's 31/32/33 split is represented as three rows). Level 2
is scored **only against that sector's own detail codes** — a company can never land on "Offices
of Dentists" unless "Health Care and Social Assistance" was actually the level-1 pick. Either
level can stop the walk and route to review instead of forcing a guess.

**2. A real embedded code table**, not a toy list — see `src/codes.js`: all 24 NAICS sectors plus
**125 hand-picked 4-6 digit NAICS detail codes** spanning software, professional services,
healthcare, retail, food service, finance, manufacturing, construction, transportation and more,
each with a rich keyword/synonym set, plus an approximate NAICS→SIC crosswalk for every code
included.

**3. Negative keywords — the real accuracy lever.** "SaaS for dentists" and "a dental clinic" both
say "dentist", but only one of them **is** a dental practice. Codes prone to this confusion
(Offices of Dentists, Offices of Physicians, Offices of Lawyers, Real Estate Agents, Full-Service
Restaurants, Fitness Centers, Hotels, Beauty Salons, Travel Agencies, Colleges, Veterinary
Services, CPA firms, Electronics Stores, Grocery Stores, Electronic Shopping...) carry negative
keywords like "software"/"saas"/"platform"/"api". When one fires, that code's score is crushed
(see `negativePenalty`) — and crucially this propagates **up to the sector level too**, so a
software company doesn't even win the Health Care sector just because "dental clinic" appears
verbatim in its "SaaS for dentists" pitch. Software Publishers / Custom Computer Programming carry
no such penalty and win on their own "software"/"saas"/"api" keywords instead.

**4. Four-signal lexical scoring** per level, computed against the current candidate set only (see
`src/classify.js`): TF-IDF-ish cosine similarity (IDF built from the sibling codes competing at
that level), token overlap, character-trigram Dice similarity (typo/word-order tolerance), and
exact multi-word **phrase hits** ("point of sale", "custom software development") — the strongest
single signal, and exactly what's returned as evidence.

**5. Evidence, not a black box.** Every classified row carries `evidence.matchedPhrases` — the
literal phrases that fired — and `evidence.suppressed`, showing which negative keyword knocked out
a competing code, so you can see *why*, not just *what*.

**6. Confidence + review routing**, same shape as the taxonomy classifier: confidence blends how
strong the winning score is with how far ahead it is of the runner-up. Anything below
`reviewBelow` at either level stops there and goes to the review queue with its top candidates,
instead of being force-fitted.

**7. Optional site fetch.** Pass a `url` per company and set `fetchSite: true` to fetch the page
(via the shared `fetchPage`/`extract` helpers) and fold its meta description + visible text into
the description before classifying — otherwise classification runs purely on the text you supply.

### Input

```json
{
  "companies": [
    { "name": "Bright Smile Dental Clinic", "description": "A friendly dental clinic offering checkups and cleanings." },
    { "name": "DentaFlow", "description": "SaaS practice management software platform for dentists, a cloud app with an API." }
  ],
  "fetchSite": false,
  "reviewBelow": 55,
  "negativePenalty": 80,
  "tfidfWeight": 40, "overlapWeight": 25, "trigramWeight": 15, "phraseWeight": 20
}
```

`reviewBelow`, `negativePenalty` and the four weights are 0-100 integers (Apify input schemas have
no float type); weights are normalized against each other, so only their ratio matters.

### Output

`type: "classified"` rows carry `naicsSector` (`{code, title}`), `naicsDetail` (`{code, title}`),
`sicCodes` (crosswalked), per-level `levelConfidences`, an overall `confidence` (the weaker of the
two levels), and `evidence`. `type: "review"` rows carry the reason and best-guess candidates.
`type: "sectorCount"` rows summarize where companies landed.

### Honest limitations

- **Lexical, not semantic.** There is no embeddings model — matching is TF-IDF/token/trigram/phrase
  based. Give unusual businesses explicit `tags` to bridge vocabulary the algorithm can't infer
  ("Footwear" and "Shoes" share almost no characters, for example).
- **Coverage is the included 125-code subset, not the full official lists.** The real NAICS 2022
  has ~1,057 six-digit codes and SIC has ~10,000 codes; this actor covers the ~125 detail codes
  most likely to come up classifying real companies. An unusual or hyper-specific business may
  correctly land in review rather than being force-fitted to the nearest included code.
- **The SIC crosswalk is an approximate "closest analogue" pick per included NAICS code**, not the
  official many-to-many Census Bureau concordance. Treat it as a helpful cross-reference, not a
  compliance-grade mapping.
- Negative keywords are hand-picked for the confusions we know are common (industry vs. the
  software/app that serves that industry). Confusions outside that pattern aren't specifically
  guarded against.
- Very short/sparse company text has little for any lexical method to work with and will often
  land in review rather than being force-fitted.
- Confidence is a relative signal (how well the winner beat the field), not a calibrated
  probability — tune `reviewBelow` against your own precision/recall needs.
- Capped at 50,000 companies per run.

# Actor input Schema

## `companies` (type: `array`):

The companies to classify, as an array of flat objects with a name, description/tags, and/or a url. Combine freely with companyDatasetIds.

## `companyDatasetIds` (type: `array`):

Apify dataset IDs to pull companies from, in addition to (or instead of) the inline companies list.

## `fetchSite` (type: `boolean`):

If true, fetch the page at each company's url field and append its meta description + visible text to the description before classifying. If false (default), classification uses only the text you already supplied.

## `nameField` (type: `string`):

Field holding the company's name. Carries the strongest classification signal.

## `descriptionField` (type: `string`):

Field holding the company's longer description text (what fetched site text, if any, is appended to).

## `tagsField` (type: `string`):

Field holding curated tags/keywords (string or array). Weighted between name and description.

## `urlField` (type: `string`):

Field holding the company's website URL, used only when fetchSite is true.

## `idField` (type: `string`):

Field on each company to use as its output ID. Defaults to an auto-generated C000001-style ID when omitted or blank.

## `reviewBelow` (type: `integer`):

If the best-matching sector, or the best-matching detail code within the accepted sector, scores below this confidence, classification stops there and the company is sent to the review queue instead of a forced guess. Expressed 0-100; 55 means 0.55.

## `negativePenalty` (type: `integer`):

How hard a matched negative keyword (e.g. "software"/"saas" on Offices of Dentists) crushes that code's score. 0 disables negative keywords entirely; 100 zeroes the score outright the moment one is found. Expressed 0-100; 80 means the score is multiplied by 0.2.

## `tfidfWeight` (type: `integer`):

Relative weight of TF-IDF-ish cosine similarity in the score. Weights are normalized against the other three weights, so only the ratio between them matters.

## `overlapWeight` (type: `integer`):

Relative weight of how much of a code's own vocabulary (title + keywords) is found in the company text.

## `trigramWeight` (type: `integer`):

Relative weight of character-trigram Dice similarity, which tolerates typos and word-order drift that token matching misses.

## `phraseWeight` (type: `integer`):

Relative weight of exact multi-word keyword-phrase matches (e.g. "point of sale", "custom software development") — the strongest single signal when it fires, and what is surfaced back to you as evidence.

## `fetchTimeoutSecs` (type: `integer`):

Timeout per company website fetch, only used when fetchSite is true.

## `includeSicCrosswalk` (type: `boolean`):

Include the approximate crosswalked SIC code(s) alongside the NAICS detail code on each classified row.

## `includeReviewItems` (type: `boolean`):

Emit rows for companies that could not be confidently classified, with the reason and the top candidate sectors/codes that were considered.

## Actor input object example

```json
{
  "companies": [
    {
      "name": "Bright Smile Dental Clinic",
      "description": "A friendly dental clinic offering checkups, cleanings, and orthodontist services for the whole family.",
      "tags": [
        "dental",
        "healthcare"
      ]
    },
    {
      "name": "DentaFlow",
      "description": "SaaS practice management software platform for dentists and dental clinics, built as a cloud app with an API.",
      "tags": [
        "software",
        "saas"
      ]
    },
    {
      "name": "Joe's Diner",
      "description": "A full-service, sit-down restaurant serving breakfast, lunch and dinner off a bistro menu.",
      "tags": [
        "restaurant"
      ]
    }
  ],
  "fetchSite": false,
  "nameField": "name",
  "descriptionField": "description",
  "tagsField": "tags",
  "urlField": "url",
  "reviewBelow": 55,
  "negativePenalty": 80,
  "tfidfWeight": 40,
  "overlapWeight": 25,
  "trigramWeight": 15,
  "phraseWeight": 20,
  "fetchTimeoutSecs": 30,
  "includeSicCrosswalk": true,
  "includeReviewItems": true
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `downloadCsv` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `count` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "companies": [
        {
            "name": "Bright Smile Dental Clinic",
            "description": "A friendly dental clinic offering checkups, cleanings, and orthodontist services for the whole family.",
            "tags": [
                "dental",
                "healthcare"
            ]
        },
        {
            "name": "DentaFlow",
            "description": "SaaS practice management software platform for dentists and dental clinics, built as a cloud app with an API.",
            "tags": [
                "software",
                "saas"
            ]
        },
        {
            "name": "Joe's Diner",
            "description": "A full-service, sit-down restaurant serving breakfast, lunch and dinner off a bistro menu.",
            "tags": [
                "restaurant"
            ]
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("that_red_bird/industry-classifier").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "companies": [
        {
            "name": "Bright Smile Dental Clinic",
            "description": "A friendly dental clinic offering checkups, cleanings, and orthodontist services for the whole family.",
            "tags": [
                "dental",
                "healthcare",
            ],
        },
        {
            "name": "DentaFlow",
            "description": "SaaS practice management software platform for dentists and dental clinics, built as a cloud app with an API.",
            "tags": [
                "software",
                "saas",
            ],
        },
        {
            "name": "Joe's Diner",
            "description": "A full-service, sit-down restaurant serving breakfast, lunch and dinner off a bistro menu.",
            "tags": ["restaurant"],
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("that_red_bird/industry-classifier").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "companies": [
    {
      "name": "Bright Smile Dental Clinic",
      "description": "A friendly dental clinic offering checkups, cleanings, and orthodontist services for the whole family.",
      "tags": [
        "dental",
        "healthcare"
      ]
    },
    {
      "name": "DentaFlow",
      "description": "SaaS practice management software platform for dentists and dental clinics, built as a cloud app with an API.",
      "tags": [
        "software",
        "saas"
      ]
    },
    {
      "name": "Joe'\''s Diner",
      "description": "A full-service, sit-down restaurant serving breakfast, lunch and dinner off a bistro menu.",
      "tags": [
        "restaurant"
      ]
    }
  ]
}' |
apify call that_red_bird/industry-classifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,that_red_bird/industry-classifier"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/c7BpTuVrhRv70sujb/builds/sr9f0OYyFeaX0hU3R/openapi.json
