document-classification-nlp
Automatically classify and extract information from construction documents using NLP. Categorize RFIs, submittals, change orders, specifications, and contracts.
What this skill does
# Document Classification with NLP
## Overview
This skill implements NLP-based document classification and information extraction for construction projects. Automate document sorting, key term extraction, and content analysis.
**Document Types:**
- RFIs (Requests for Information)
- Submittals and shop drawings
- Change orders and variations
- Specifications and standards
- Contracts and agreements
- Safety reports and permits
## Quick Start
```python
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
import pandas as pd
# Sample training data
documents = [
("Please clarify the steel reinforcement spacing for the foundation slab", "RFI"),
("Attached shop drawing for HVAC ductwork layout", "Submittal"),
("Additional cost for unforeseen soil conditions", "Change Order"),
("Fire-rated wall assembly specification Section 09 21 16", "Specification"),
]
texts, labels = zip(*documents)
# Train classifier
classifier = Pipeline([
('tfidf', TfidfVectorizer(max_features=1000, ngram_range=(1, 2))),
('clf', MultinomialNB())
])
classifier.fit(texts, labels)
# Classify new document
new_doc = "Request to approve substitution of specified light fixtures"
prediction = classifier.predict([new_doc])[0]
print(f"Classification: {prediction}") # Output: Submittal
```
## Advanced Classification System
### Document Classifier Class
```python
import re
import pandas as pd
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import LinearSVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import LabelEncoder
from typing import List, Dict, Tuple, Optional
import spacy
from dataclasses import dataclass
@dataclass
class ClassificationResult:
document_id: str
predicted_class: str
confidence: float
alternative_classes: List[Tuple[str, float]]
extracted_entities: Dict[str, List[str]]
keywords: List[str]
class ConstructionDocumentClassifier:
"""Classify and analyze construction documents"""
# Document type patterns
DOCUMENT_PATTERNS = {
'RFI': [
r'request\s+for\s+information',
r'clarification\s+(needed|required|requested)',
r'please\s+(clarify|confirm|advise)',
r'question\s+(regarding|about)',
r'rfi\s*#?\d*'
],
'Submittal': [
r'submittal',
r'shop\s+drawing',
r'product\s+data',
r'sample\s+submission',
r'approval\s+request',
r'material\s+submission'
],
'Change Order': [
r'change\s+order',
r'variation\s+order',
r'cost\s+(increase|adjustment|addition)',
r'scope\s+change',
r'additional\s+work',
r'unforeseen\s+conditions'
],
'Specification': [
r'section\s+\d{2}\s+\d{2}\s+\d{2}',
r'specification',
r'performance\s+requirement',
r'material\s+standard',
r'quality\s+standard'
],
'Safety Report': [
r'incident\s+report',
r'safety\s+(inspection|violation|observation)',
r'hazard\s+(identification|assessment)',
r'near\s+miss',
r'osha',
r'jha|jsa'
],
'Contract': [
r'contract\s+agreement',
r'terms\s+and\s+conditions',
r'scope\s+of\s+work',
r'payment\s+terms',
r'warranty\s+provision'
]
}
def __init__(self, use_spacy: bool = True):
self.classifier = None
self.vectorizer = None
self.label_encoder = LabelEncoder()
if use_spacy:
try:
self.nlp = spacy.load("en_core_web_sm")
except:
self.nlp = None
else:
self.nlp = None
def train(self, documents: List[str], labels: List[str]) -> Dict:
"""Train the document classifier"""
# Encode labels
y = self.label_encoder.fit_transform(labels)
# Create pipeline
self.classifier = Pipeline([
('tfidf', TfidfVectorizer(
max_features=5000,
ngram_range=(1, 3),
stop_words='english',
sublinear_tf=True
)),
('clf', LinearSVC(C=1.0, class_weight='balanced'))
])
# Train
self.classifier.fit(documents, y)
# Cross-validation
scores = cross_val_score(self.classifier, documents, y, cv=5)
return {
'accuracy_mean': scores.mean(),
'accuracy_std': scores.std(),
'classes': list(self.label_encoder.classes_)
}
def classify(self, document: str) -> ClassificationResult:
"""Classify a single document"""
if self.classifier is None:
# Use rule-based classification if no model trained
return self._rule_based_classify(document)
# Get prediction
prediction = self.classifier.predict([document])[0]
predicted_class = self.label_encoder.inverse_transform([prediction])[0]
# Get confidence scores
decision_scores = self.classifier.decision_function([document])[0]
probs = self._softmax(decision_scores)
alternatives = [
(self.label_encoder.inverse_transform([i])[0], float(probs[i]))
for i in np.argsort(probs)[::-1][1:4]
]
# Extract entities and keywords
entities = self._extract_entities(document)
keywords = self._extract_keywords(document)
return ClassificationResult(
document_id="",
predicted_class=predicted_class,
confidence=float(probs[prediction]),
alternative_classes=alternatives,
extracted_entities=entities,
keywords=keywords
)
def _rule_based_classify(self, document: str) -> ClassificationResult:
"""Rule-based classification using patterns"""
doc_lower = document.lower()
scores = {}
for doc_type, patterns in self.DOCUMENT_PATTERNS.items():
score = sum(
1 for pattern in patterns
if re.search(pattern, doc_lower)
)
scores[doc_type] = score
if max(scores.values()) == 0:
predicted = 'Other'
confidence = 0.5
else:
predicted = max(scores, key=scores.get)
confidence = scores[predicted] / len(self.DOCUMENT_PATTERNS[predicted])
return ClassificationResult(
document_id="",
predicted_class=predicted,
confidence=confidence,
alternative_classes=[],
extracted_entities=self._extract_entities(document),
keywords=self._extract_keywords(document)
)
def _extract_entities(self, document: str) -> Dict[str, List[str]]:
"""Extract named entities from document"""
entities = {
'dates': [],
'organizations': [],
'people': [],
'monetary': [],
'references': []
}
# Date patterns
date_pattern = r'\d{1,2}[/-]\d{1,2}[/-]\d{2,4}'
entities['dates'] = re.findall(date_pattern, document)
# Money patterns
money_pattern = r'\$[\d,]+(?:\.\d{2})?'
entities['monetary'] = re.findall(money_pattern, document)
# Reference numbers
ref_pattern = r'(?:RFI|CO|SI|PR)[-#]?\s*\d+'
entities['references'] = re.findall(ref_pattern, document, re.IGNORECASE)
# Use spaCy for NER if available
if self.nlp:
doc = self.nlp(document)
for ent in doc.ents:
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.