.claude/skills/similarity-metadata/SKILL.md
# Similarity Metadata System **Use this skill when:** - Working with component similarity calculations using metadata-driven architecture - Configuring spec importance levels and tolerance rules - Understanding context-aware similarity profiles - Converting legacy similarity calculators to metadata-driven approach - Troubleshooting similarity score calculations --- ## Overview The library uses a **metadata-driven architecture** for component similarity calculations, replacing hardcoded logic
npx skillsauth add Cantara/lib-electronic-components .claude/skills/similarity-metadataInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill when:
The library uses a metadata-driven architecture for component similarity calculations, replacing hardcoded logic with configurable, type-specific similarity rules.
Problem: Previous similarity calculators had hardcoded weights and thresholds, making them difficult to tune and inconsistent across component types.
Solution: Centralized metadata system that defines:
| Class | Responsibility |
|-------|----------------|
| ComponentTypeMetadata | Defines specs, importance levels, tolerance rules for a component type |
| ComponentTypeMetadataRegistry | Singleton registry; maps ComponentType → metadata |
| SpecImportance | Enum: CRITICAL, HIGH, MEDIUM, LOW, OPTIONAL (with base weights) |
| ToleranceRule | Interface for comparing spec values with scoring logic |
| SimilarityProfile | Enum: 5 context-aware profiles that adjust importance multipliers |
| SpecValue<T> | Unit-aware value representation with min/max ranges |
| SpecUnit | Unit types: NONE, OHM, FARAD, HENRY, VOLT, AMPERE, WATT, HERTZ, PERCENTAGE |
Defines how important a spec is for similarity matching:
| Level | Base Weight | Mandatory | Use Case | |-------|-------------|-----------|----------| | CRITICAL | 1.0 | Yes | Specs that affect core functionality (resistance value, capacitance, polarity) | | HIGH | 0.7 | No | Specs that affect reliability (package type, tolerance, dielectric) | | MEDIUM | 0.4 | No | Specs that affect performance (power rating, ESR, temp coefficient) | | LOW | 0.2 | No | Secondary considerations (gate charge, viewing angle, certifications) | | OPTIONAL | 0.0 | No | Informational only (lifecycle status, manufacturer notes) |
Key Design: Only CRITICAL specs are mandatory. Effective weight = baseWeight × profile multiplier.
Defines how to compare candidate vs original spec values:
ToleranceRule.exactMatch()
ToleranceRule.percentageTolerance(double maxPercent)
percentageTolerance(5.0) allows ±5% deviationToleranceRule.minimumRequired()
ToleranceRule.maximumAllowed(double maxMultiplier)
maximumAllowed(1.5) allows up to 1.5× the original valueToleranceRule.rangeTolerance(double lowerPercent, double upperPercent)
rangeTolerance(-10.0, 20.0) allows -10% to +20%Adjusts importance multipliers based on use case:
| Profile | Threshold | CRITICAL | HIGH | MEDIUM | LOW | Use Case | |---------|-----------|----------|------|--------|-----|----------| | DESIGN_PHASE | 0.85 | 1.0 | 0.9 | 0.7 | 0.4 | Exact match for new designs | | REPLACEMENT | 0.75 | 1.0 | 0.7 | 0.4 | 0.2 | Default: Direct replacement | | PERFORMANCE_UPGRADE | 0.70 | 1.0 | 0.8 | 0.5 | 0.2 | Better performance acceptable | | COST_OPTIMIZATION | 0.60 | 1.0 | 0.4 | 0.2 | 0.0 | Maintain critical specs, relax others | | EMERGENCY_SOURCING | 0.50 | 0.8 | 0.4 | 0.2 | 0.0 | Urgent replacement, relaxed requirements |
Effective Weight Calculation:
effectiveWeight = importance.baseWeight × profile.multiplier
Example: HIGH spec (0.7) in COST_OPTIMIZATION (0.4) → 0.7 × 0.4 = 0.28
Built using fluent builder pattern:
ComponentTypeMetadata resistor = ComponentTypeMetadata.builder(ComponentType.RESISTOR)
.addSpec("resistance", SpecImportance.CRITICAL, ToleranceRule.percentageTolerance(1.0))
.addSpec("tolerance", SpecImportance.CRITICAL, ToleranceRule.exactMatch())
.addSpec("package", SpecImportance.HIGH, ToleranceRule.exactMatch())
.addSpec("powerRating", SpecImportance.MEDIUM, ToleranceRule.minimumRequired())
.addSpec("temperatureCoefficient", SpecImportance.LOW, ToleranceRule.percentageTolerance(20.0))
.defaultProfile(SimilarityProfile.REPLACEMENT)
.build();
// Check if spec is configured
SpecConfig config = metadata.getSpecConfig("resistance");
if (config != null) {
SpecImportance importance = config.getImportance();
ToleranceRule rule = config.getToleranceRule();
}
// Check if spec is critical
boolean critical = metadata.isCritical("resistance"); // true
// Get all configured specs
Set<String> allSpecs = metadata.getAllSpecs();
// Get default profile
SimilarityProfile profile = metadata.getDefaultProfile();
Singleton registry pre-configured for top 10 component types:
ComponentTypeMetadataRegistry registry = ComponentTypeMetadataRegistry.getInstance();
// Lookup metadata
Optional<ComponentTypeMetadata> metadata = registry.getMetadata(ComponentType.RESISTOR);
// Fallback to base type for manufacturer-specific types
Optional<ComponentTypeMetadata> yageoMeta = registry.getMetadata(ComponentType.RESISTOR_CHIP_YAGEO);
// Returns RESISTOR metadata (base type fallback)
// Custom registration
ComponentTypeMetadata customMetadata = ComponentTypeMetadata.builder(ComponentType.INDUCTOR)
.addSpec("inductance", SpecImportance.CRITICAL, ToleranceRule.percentageTolerance(5.0))
.build();
registry.register(customMetadata);
The registry initializes with metadata for:
| ComponentType | Critical Specs | HIGH Specs | Notes | |---------------|----------------|------------|-------| | RESISTOR | resistance, tolerance | package | 2 critical, 5 total specs | | CAPACITOR | capacitance, voltage, dielectric | package | 3 critical, 7 total specs | | MOSFET | voltageRating, currentRating, channel | rdsOn | 3 critical, 6 total specs | | TRANSISTOR | polarity, voltageRating | collectorCurrent | 2 critical, 6 total specs | | DIODE | type, voltageRating | currentRating | 2 critical, 5 total specs | | OPAMP | configuration, inputType | supplyVoltageMin | 2 critical, 6 total specs | | MICROCONTROLLER | family, flashSize | ramSize | 2 critical, 5 total specs | | MEMORY | type, capacity, interface | speed | 3 critical, 5 total specs | | LED | color, brightness | wavelength | 2 critical, 4 total specs | | CONNECTOR | pinCount, pitch, gender | mountingType | 3 critical, 6 total specs |
ComponentTypeMetadata resistor = ComponentTypeMetadata.builder(ComponentType.RESISTOR)
.addSpec("resistance", SpecImportance.CRITICAL, ToleranceRule.percentageTolerance(1.0))
.addSpec("tolerance", SpecImportance.CRITICAL, ToleranceRule.exactMatch())
.addSpec("package", SpecImportance.HIGH, ToleranceRule.exactMatch())
.addSpec("powerRating", SpecImportance.MEDIUM, ToleranceRule.minimumRequired())
.defaultProfile(SimilarityProfile.REPLACEMENT)
.build();
// Check if 10.1kΩ is similar to 10kΩ (within 1%)
boolean similar = resistor.isCritical("resistance"); // true
SpecConfig config = resistor.getSpecConfig("resistance");
ToleranceRule rule = config.getToleranceRule();
SpecValue<Double> original = new SpecValue<>(10000.0, SpecUnit.OHM);
SpecValue<Double> candidate = new SpecValue<>(10100.0, SpecUnit.OHM);
double score = rule.calculateScore(original, candidate); // 0.0 (outside 1% tolerance)
ComponentTypeMetadata capacitor = registry.getMetadata(ComponentType.CAPACITOR).orElseThrow();
// Design phase: strict matching (0.85 threshold)
SimilarityProfile designProfile = SimilarityProfile.DESIGN_PHASE;
double designMultiplier = designProfile.getMultiplier(SpecImportance.HIGH); // 0.9
boolean passesDesign = designProfile.meetsThreshold(0.82); // false
// Emergency sourcing: relaxed matching (0.50 threshold)
SimilarityProfile emergencyProfile = SimilarityProfile.EMERGENCY_SOURCING;
double emergencyMultiplier = emergencyProfile.getMultiplier(SpecImportance.HIGH); // 0.4
boolean passesEmergency = emergencyProfile.meetsThreshold(0.55); // true
Each refactored calculator follows this pattern:
public class XxxSimilarityCalculator implements ComponentSimilarityCalculator {
private final ComponentTypeMetadataRegistry metadataRegistry;
public XxxSimilarityCalculator() {
this.metadataRegistry = ComponentTypeMetadataRegistry.getInstance();
}
@Override
public double calculateSimilarity(String mpn1, String mpn2, PatternRegistry registry) {
// Get metadata
Optional<ComponentTypeMetadata> metadataOpt = metadataRegistry.getMetadata(ComponentType.XXX);
if (metadataOpt.isEmpty()) {
return calculateLegacySimilarity(mpn1, mpn2); // Fallback
}
ComponentTypeMetadata metadata = metadataOpt.get();
SimilarityProfile profile = metadata.getDefaultProfile();
// Extract specs from MPNs
String spec1 = extractSpec(mpn1);
String spec2 = extractSpec(mpn2);
double totalScore = 0.0;
double maxPossibleScore = 0.0;
// For each spec: compare using ToleranceRule
ComponentTypeMetadata.SpecConfig config = metadata.getSpecConfig("specName");
if (config != null && spec1 != null && spec2 != null) {
ToleranceRule rule = config.getToleranceRule();
SpecValue<T> orig = new SpecValue<>(parseSpec(spec1), SpecUnit.XXX);
SpecValue<T> cand = new SpecValue<>(parseSpec(spec2), SpecUnit.XXX);
double specScore = rule.compare(orig, cand);
double specWeight = profile.getEffectiveWeight(config.getImportance());
totalScore += specScore * specWeight;
maxPossibleScore += specWeight;
}
// Normalize to [0.0, 1.0]
return maxPossibleScore > 0 ? totalScore / maxPossibleScore : 0.0;
}
private double calculateLegacySimilarity(String mpn1, String mpn2) {
// Keep old hardcoded logic for backward compatibility
}
}
1. Legacy Fallback
if (metadataOpt.isEmpty()) {
logger.warn("No metadata found for {} type, falling back to legacy scoring", type);
return calculateLegacySimilarity(mpn1, mpn2);
}
Ensures backward compatibility if metadata unavailable.
2. Value Parsing Add type-specific parsing methods:
parseResistanceValue(String) → Double (in ohms): "10K" → 10000.0parseCapacitanceValue(String) → Double (in farads): "0.1µF" → 1.0e-73. Normalization Critical change: all scores now normalized to [0.0, 1.0]:
double normalizedSimilarity = maxPossibleScore > 0 ? totalScore / maxPossibleScore : 0.0;
4. Voltage Asymmetry is Correct MinimumRequiredRule creates intentional asymmetry:
This is correct for component replacement: you can replace a 5V part with a 50V part, but not vice versa.
effectiveWeight = baseWeight × profileMultiplier
totalScore = Σ(specScore × effectiveWeight)
normalized = totalScore / maxPossibleScore
Example (resistor with REPLACEMENT profile):
165 comprehensive tests covering:
ToleranceRuleTest (35 tests):
SimilarityProfileTest (36 tests):
ComponentTypeMetadataTest (29 tests):
ComponentTypeMetadataRegistryTest (35 tests):
SpecImportanceTest (30 tests):
// NO static factory method - use constructor
SpecValue<Double> value = new SpecValue<>(100.0, SpecUnit.FARAD); // ✓
SpecValue<Double> value = SpecValue.of(100.0); // ✗ Does not exist
// Methods return directly, not Optional (except registry lookup)
SpecConfig config = metadata.getSpecConfig("resistance"); // Can be null
Set<String> specs = metadata.getAllSpecs(); // Never null
boolean critical = metadata.isCritical("resistance"); // false if not found
// Throws IllegalArgumentException (not NPE) for null component type
ComponentTypeMetadata.builder(null); // ✗ IllegalArgumentException
// Throws IllegalStateException if no specs added
ComponentTypeMetadata.builder(ComponentType.IC).build(); // ✗ IllegalStateException
// getSpecConfig(null) throws NullPointerException (expected Map behavior)
metadata.getSpecConfig(null); // ✗ NullPointerException
isAcceptable() in addition to score for filteringToleranceRule.compare() not calculateScore()SpecUnit.OHMS not SpecUnit.OHM (plural form)Problem: The micro sign µ (U+00B5) becomes Greek capital MU Μ (U+039C) when uppercased in Java:
"0.1µF".toUpperCase() // Returns "0.1ΜF" (Greek MU, not micro!)
"0.1µF".toUpperCase().contains("µF") // false! Doesn't match
Solution: Replace micro variants before normalizing:
String normalized = value.replace("µ", "u").replace("Μ", "u");
normalized = normalizeValue(normalized); // Now toUpperCase() works
if (normalized.contains("UF")) { // Matches both µF and plain UF
// Parse value
}
Why This Matters: Component MPNs use µF for microfarads, and normalizeValue() calls toUpperCase(). Without the replacement, value parsing silently fails and comparisons return 0.0 similarity.
Affected: CapacitorSimilarityCalculator parseCapacitanceValue() method. Any future value parsing with Greek-origin SI prefixes (µ, Ω) must handle this.
12 of 17 calculators converted (71% complete)
| Calculator | Status | Specs | Critical Specs | |-----------|--------|-------|----------------| | ResistorSimilarityCalculator | ✅ | resistance, package, tolerance | resistance | | CapacitorSimilarityCalculator | ✅ | capacitance, voltage, dielectric, package | capacitance, voltage | | TransistorSimilarityCalculator | ✅ | polarity, voltageRating, currentRating, hfe, package | polarity, voltageRating, currentRating | | DiodeSimilarityCalculator | ✅ | type, voltageRating, currentRating, package | type, voltageRating, currentRating | | MosfetSimilarityCalculator | ✅ | channel, voltageRating, currentRating, rdsOn, package | channel, voltageRating, currentRating | | VoltageRegulatorSimilarityCalculator | ✅ | regulatorType, outputVoltage, polarity, currentRating, package | regulatorType, outputVoltage, polarity | | OpAmpSimilarityCalculator | ✅ | configuration, family, package | configuration | | MemorySimilarityCalculator | ✅ | memoryType, capacity, interface, package | memoryType, capacity | | LEDSimilarityCalculator | ✅ | color, family, brightness, package | color | | ConnectorSimilarityCalculator | ✅ | pinCount, pitch, family, mountingType | pinCount, pitch | | LogicICSimilarityCalculator | ✅ | function, series, technology, package | function | | SensorSimilarityCalculator | ✅ | sensorType, family, interface, package | sensorType | | MCUSimilarityCalculator | ⏳ | family, series, features | - | | MicrocontrollerSimilarityCalculator | ⏳ | manufacturer, series, package | - | | PassiveComponentCalculator | ⏳ | value, sizeCode, tolerance | - | | DefaultSimilarityCalculator | ⏳ | - | - | | LevenshteinCalculator | ⏳ | - | - |
/similarity - Main similarity architecture and calculator overview/similarity-resistor - Resistor-specific similarity patterns/similarity-capacitor - Capacitor-specific similarity patterns/similarity-transistor - Transistor-specific similarity patterns/similarity-mosfet - MOSFET-specific similarity patterns/similarity-diode - Diode-specific similarity patterns/similarity-* skills for component-specific patternsHISTORY.md - Historical conversion progress and milestones.docs/history/SIMILARITY_METADATA_EVOLUTION.md - Detailed conversion journey (if created)src/main/java/no/cantara/electronic/component/lib/metadata/ - Implementation classessrc/test/java/no/cantara/electronic/component/lib/metadata/ - Test suitedata-ai
Cost-effective task delegation strategy using Haiku model for straightforward work. Use when planning how to approach simple, pattern-following tasks to minimize costs.
tools
Use when working with component similarity calculations - comparing MPNs, finding equivalent parts, implementing new similarity calculators, or understanding how component matching works.
testing
Use when working with transistor similarity calculations - comparing BJT MPNs, understanding NPN/PNP polarity matching, equivalent groups like 2N2222/PN2222, or transistor-specific similarity logic.
testing
Use when working with sensor similarity calculations - comparing temperature/accelerometer/humidity sensor MPNs, understanding sensor families, equivalent parts, or sensor-specific similarity logic.