技能详情(站内镜像,无评论)
许可证:MIT-0
MIT-0 ·免费使用、修改和重新分发。无需归因。
版本:v2.1.0
统计:⭐ 0 · 1.1k · 3 current installs · 3 all-time installs
⭐ 0
安装量(当前) 3
🛡 VirusTotal :良性 · OpenClaw :良性
Package:datadrivenconstruction/specification-extractor
安全扫描(ClawHub)
- VirusTotal :良性
- OpenClaw :良性
OpenClaw 评估
The skill is internally consistent with its stated purpose: an instruction-only PDF specification extractor that needs python3 and filesystem access to process user-supplied spec documents.
目的
Name/description, instructions, and required capabilities align: parsing PDF specs naturally requires Python (for the provided example implementation) and filesystem access to read user documents. There are no unrelated credentials, services, or binaries requested.
说明范围
SKILL.md shows parsing of PDFs (pdfplumber + regex) and extraction of sections/products/submittals — this stays within the stated scope. The instructions ask to use data provided by the user. I did not observe any directives to read unrelated system files, access environment secrets, or transmit data to external endpoints in the provided excerpts. Review the full SKILL.md for any hidden networking calls, but the visible content is focused on l…
安装机制
There is no install spec (instruction-only), which is low risk. However, the example code uses the third-party package pdfplumber (and possibly other Python libs) but the skill does not declare how those Python dependencies should be installed. That mismatch is an operational issue (missing dependency installation instructions) rather than a security red flag.
证书
The skill requests no environment variables or credentials. claw.json declares a 'filesystem' permission which is proportionate to the task of reading user-supplied spec files. No other sensitive access is requested.
持久
always is false and the skill is user-invocable; it does not request permanent global presence or elevated privileges. There is no indication it modifies other skills or system-wide settings.
综合结论
This skill appears coherent and focused on extracting data from PDFs. Before installing or running it: (1) confirm you have python3 and install required Python packages (e.g., pdfplumber) in a controlled environment; the skill doesn't include an install step for those dependencies. (2) Only feed non-sensitive example files at first to validate behavior. (3) Review the full SKILL.md/instructions for any network calls or pip/exec commands not sh…
安装(复制给龙虾 AI)
将下方整段复制到龙虾中文库对话中,由龙虾按 SKILL.md 完成安装。
请把本段交给龙虾中文库(龙虾 AI)执行:为本机安装 OpenClaw 技能「Specification Extractor」。简介:Extract structured data from construction specifications. Parse CSI sections, r…。
请 fetch 以下地址读取 SKILL.md 并按文档完成安装:https://raw.githubusercontent.com/openclaw/skills/refs/heads/main/skills/datadrivenconstruction/specification-extractor/SKILL.md
(来源:yingzhi8.cn 技能库)
SKILL.md
---
name: "specification-extractor"
description: "Extract structured data from construction specifications. Parse CSI sections, requirements, submittals, and product data from spec documents."
homepage: "https://datadrivenconstruction.io"
metadata: {"openclaw": {"emoji": "📑", "os": ["darwin", "linux", "win32"], "homepage": "https://datadrivenconstruction.io", "requires": {"bins": ["python3"]}}}
---
# Specification Extractor for Construction
## Overview
Extract structured data from construction specification documents. Parse CSI MasterFormat sections, identify requirements, submittals, product standards, and compile actionable data for estimating and procurement.
## Business Case
Automated spec extraction enables:
- **Faster Estimating**: Quickly identify scope and requirements
- **Procurement Accuracy**: Extract exact product specifications
- **Submittal Tracking**: Identify all required submittals
- **Compliance Checking**: Verify specs against standards
## Technical Implementation
```python
from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional
import re
import pdfplumber
from pathlib import Path
@dataclass
class SpecSection:
number: str # e.g., "03 30 00"
title: str
part1_general: Dict[str, Any]
part2_products: Dict[str, Any]
part3_execution: Dict[str, Any]
raw_text: str
@dataclass
class ProductRequirement:
section: str
manufacturer: str
product_name: str
model: str
standards: List[str]
properties: Dict[str, str]
@dataclass
class SubmittalRequirement:
section: str
submittal_type: str # shop drawings, samples, product data, etc.
description: str
timing: str
copies: int
@dataclass
class SpecExtractionResult:
document_name: str
total_pages: int
sections: List[SpecSection]
products: List[ProductRequirement]
submittals: List[SubmittalRequirement]
standards_referenced: List[str]
class SpecificationExtractor:
"""Extract structured data from construction specifications."""
# CSI MasterFormat patterns
CSI_SECTION_PATTERN = r'^(d{2}s?d{2}s?d{2})s*[-–]s*(.+?)$'
PART_PATTERN = r'^PARTs+(d+)s*[-–]s*(.+?)$'
ARTICLE_PATTERN = r'^(d+.d+)s+([A-Z][A-Zs]+)$'
# Submittal type keywords
SUBMITTAL_TYPES = {
'shop drawings': 'Shop Drawings',
'product data': 'Product Data',
'samples': 'Samples',
'certificates': 'Certificates',
'test reports': 'Test Reports',
'manufacturer instructions': 'Manufacturer Instructions',
'warranty': 'Warranty',
'maintenance data': 'Maintenance Data',
'mock-ups': 'Mock-ups',
}
# Common standard organizations
STANDARD_PATTERNS = [
r'ASTMs+[A-Z]d+',
r'ANSIs+[A-Z]?d+',
r'ACIs+d+',
r'AISCs+d+',
r'AWSs+[A-Z]d+',
r'ASCEs+d+',
r'ULs+d+',
r'FMs+d+',
r'NFPAs+d+',
r'IBCs+d+',
]
def __init__(self):
self.sections: Dict[str, SpecSection] = {}
def extract_from_pdf(self, pdf_path: str) -> SpecExtractionResult:
"""Extract specification data from PDF."""
path = Path(pdf_path)
all_text = ""
page_count = 0
with pdfplumber.open(pdf_path) as pdf:
page_count = len(pdf.pages)
for page in pdf.pages:
text = page.extract_text() or ""
all_text += text + "nn"
# Parse sections
sections = self._parse_sections(all_text)
# Extract products
products = self._extract_products(sections)
# Extract submittals
submittals = self._extract_submittals(sections)
# Extract standards
standards = self._extract_standards(all_text)
return SpecExtractionResult(
document_name=path.name,
total_pages=page_count,
sections=sections,
products=products,
submittals=submittals,
standards_referenced=standards
)
def _parse_sections(self, text: str) -> List[SpecSection]:
"""Parse CSI sections from specification text."""
sections = []
lines = text.split('n')
current_section = None
current_part = None
current_content = []
for line in lines:
line = line.strip()
if not line:
continue
# Check for section header
section_match = re.match(self.CSI_SECTION_PATTERN, line, re.IGNORECASE)
if section_match:
# Save previous section
if current_section:
sections.append(self._finalize_section(current_section, current_content))
current_section = {
'number': section_match.group(1).replace(' ', ''),
'title': section_match.group(2).strip(),
'parts': {}
}
current_content = []
current_part = None
continue
# Check for part header
part_match = re.match(self.PART_PATTERN, line, re.IGNORECASE)
if part_match and current_section:
part_num = part_match.group(1)
part_name = part_match.group(2).strip()
current_part = f"part{part_num}"
current_section['parts'][current_part] = {
'name': part_name,
'content': []
}
continue
# Add content to current part
if current_section and current_part:
current_section['parts'][current_part]['content'].append(line)
elif current_section:
current_content.append(line)
# Save last section
if current_section:
sections.append(self._finalize_section(current_section, current_content))
return sections
def _finalize_section(self, section_data: Dict, general_content: List[str]) -> SpecSection:
"""Finalize a section with parsed parts."""
parts = section_data.get('parts', {})
part1 = self._parse_part_content(parts.get('part1', {}).get('content', []))
part2 = self._parse_part_content(parts.get('part2', {}).get('content', []))
part3 = self._parse_part_content(parts.get('part3', {}).get('content', []))
return SpecSection(
number=section_data['number'],
title=section_data['title'],
part1_general=part1,
part2_products=part2,
part3_execution=part3,
raw_text='n'.join(general_content)
)
def _parse_part_content(self, content: List[str]) -> Dict[str, Any]:
"""Parse part content into structured data."""
result = {
'articles': {},
'items': []
}
current_article = None
for line in content:
# Check for article header
article_match = re.match(self.ARTICLE_PATTERN, line)
if article_match:
current_article = article_match.group(1)
result['articles'][current_article] = {
'title': article_match.group(2),
'items': []
}
continue
# Add to current article or general items
if current_article and current_article in result['articles']:
result['articles'][current_article]['items'].append(line)
else:
result['items'].append(line)
return result
def _extract_products(self, sections: List[SpecSection]) -> List[ProductRequirement]:
"""Extract product requirements from Part 2."""
products = []
for section in sections:
part2 = section.part2_products
for article_num, article in part2.get('articles', {}).items():
if 'MANUFACTURERS' in article['title'].upper():
for item in article['items']:
# Extract manufacturer names
if item.strip().startswith(('A.', 'B.', 'C.', '1.', '2.', '3.')):
mfr_name = re.sub(r'^[A-Zd]+.s*', '', item).strip()
products.append(ProductRequirement(
section=section.number,
manufacturer=mfr_name,
product_name='',
model='',
standards=[],
properties={}
))
elif 'MATERIALS' in article['title'].upper() or 'PRODUCTS' in article['title'].upper():
for item in article['items']:
# Extract material requirements
standards = self._extract_standards(item)
if standards:
products.append(ProductRequirement(
section=section.number,
manufacturer='',
product_name=item[:100],
model='',
standards=standards,
properties={}
))
return products
def _extract_submittals(self, sections: List[SpecSection]) -> List[SubmittalRequirement]:
"""Extract submittal requirements from Part 1."""
submittals = []
for section in sections:
part1 = section.part1_general
for article_num, article in part1.get('articles', {}).items():
if 'SUBMITTAL' in article['title'].upper():
for item in article['items']:
item_lower = item.lower()
for keyword, submittal_type in self.SUBMITTAL_TYPES.items():
if keyword in item_lower:
submittals.append(SubmittalRequirement(
section=section.number,
submittal_type=submittal_type,
description=item.strip(),
timing='Prior to fabrication',
copies=3
))
break
return submittals
def _extract_standards(self, text: str) -> List[str]:
"""Extract referenced standards from text."""
standards = []
for pattern in self.STANDARD_PATTERNS:
matches = re.findall(pattern, text, re.IGNORECASE)
standards.extend(matches)
return list(set(standards))
def generate_submittal_log(self, result: SpecExtractionResult) -> str:
"""Generate submittal log from extraction results."""
lines = ["# Submittal Log", ""]
lines.append(f"**Project Specs:** {result.document_name}")
lines.append(f"**Total Submittals:** {len(result.submittals)}")
lines.append("")
lines.append("| # | Section | Type | Description | Status |")
lines.append("|---|---------|------|-------------|--------|")
for i, sub in enumerate(result.submittals, 1):
desc = sub.description[:50] + "..." if len(sub.description) > 50 else sub.description
lines.append(f"| {i} | {sub.section} | {sub.submittal_type} | {desc} | Pending |")
return "n".join(lines)
def generate_product_schedule(self, result: SpecExtractionResult) -> str:
"""Generate product schedule from extraction results."""
lines = ["# Product Schedule", ""]
# Group by section
by_section = {}
for prod in result.products:
if prod.section not in by_section:
by_section[prod.section] = []
by_section[prod.section].append(prod)
for section, products in sorted(by_section.items()):
lines.append(f"## Section {section}")
lines.append("")
for prod in products:
if prod.manufacturer:
lines.append(f"- **Manufacturer:** {prod.manufacturer}")
if prod.product_name:
lines.append(f"- **Product:** {prod.product_name}")
if prod.standards:
lines.append(f"- **Standards:** {', '.join(prod.standards)}")
lines.append("")
return "n".join(lines)
def generate_report(self, result: SpecExtractionResult) -> str:
"""Generate comprehensive extraction report."""
lines = ["# Specification Extraction Report", ""]
lines.append(f"**Document:** {result.document_name}")
lines.append(f"**Pages:** {result.total_pages}")
lines.append(f"**Sections Found:** {len(result.sections)}")
lines.append("")
# Sections summary
lines.append("## Sections Extracted")
for section in result.sections:
lines.append(f"- **{section.number}** - {section.title}")
lines.append("")
# Standards
if result.standards_referenced:
lines.append("## Standards Referenced")
for std in sorted(set(result.standards_referenced)):
lines.append(f"- {std}")
lines.append("")
# Submittals summary
lines.append("## Submittals Required")
lines.append(f"Total: {len(result.submittals)}")
by_type = {}
for sub in result.submittals:
by_type[sub.submittal_type] = by_type.get(sub.submittal_type, 0) + 1
for t, count in sorted(by_type.items()):
lines.append(f"- {t}: {count}")
lines.append("")
# Products summary
lines.append("## Products/Manufacturers")
lines.append(f"Total: {len(result.products)}")
return "n".join(lines)
```
## Quick Start
```python
# Initialize extractor
extractor = SpecificationExtractor()
# Extract from PDF
result = extractor.extract_from_pdf("Project_Specifications.pdf")
print(f"Found {len(result.sections)} sections")
print(f"Found {len(result.submittals)} submittals")
print(f"Found {len(result.products)} product requirements")
# Generate submittal log
submittal_log = extractor.generate_submittal_log(result)
print(submittal_log)
# Generate product schedule
product_schedule = extractor.generate_product_schedule(result)
print(product_schedule)
# Full report
report = extractor.generate_report(result)
print(report)
```
## Dependencies
```bash
pip install pdfplumber
```