Lib-electronic-components similarity
Use when working with component similarity calculations - comparing MPNs, finding equivalent parts, implementing new similarity calculators, or understanding how component matching works.
git clone https://github.com/Cantara/lib-electronic-components
T=$(mktemp -d) && git clone --depth=1 https://github.com/Cantara/lib-electronic-components "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/similarity" ~/.claude/skills/cantara-lib-electronic-components-similarity && rm -rf "$T"
.claude/skills/similarity/SKILL.mdComponent Similarity Calculator Skill
This skill provides guidance for working with component similarity calculators in the lib-electronic-components library.
For metadata-driven similarity architecture, see
/similarity-metadata:
- SpecImportance levels (CRITICAL, HIGH, MEDIUM, LOW, OPTIONAL)
- ToleranceRule types (exactMatch, percentageTolerance, minimumRequired, etc.)
- SimilarityProfile contexts (DESIGN_PHASE, REPLACEMENT, COST_OPTIMIZATION, etc.)
- Calculator integration patterns and gotchas
Overview
Similarity calculators determine how similar two electronic components are based on their MPNs (Manufacturer Part Numbers). They return a value between 0.0 (completely different) and 1.0 (identical or equivalent).
Core Interfaces
SimilarityCalculator (Simple)
public interface SimilarityCalculator { double calculateSimilarity(String normalizedMpn1, String normalizedMpn2); }
Used for generic calculators that don't need component type context.
ComponentSimilarityCalculator (Type-Aware)
public interface ComponentSimilarityCalculator { boolean isApplicable(ComponentType type); double calculateSimilarity(String mpn1, String mpn2, PatternRegistry registry); }
Used for component-specific calculators that need to check applicability.
Standard Similarity Thresholds
private static final double HIGH_SIMILARITY = 0.9; // Equivalent/interchangeable private static final double MEDIUM_SIMILARITY = 0.7; // Similar, may work as substitute private static final double LOW_SIMILARITY = 0.3; // Same category but different specs
Available Calculators
| Calculator | Interface | Component Types | Key Features |
|---|---|---|---|
| Component | RESISTOR, RESISTOR_* | Value, package, tolerance |
| Component | CAPACITOR, CAPACITOR_* | Value, voltage, dielectric |
| Component | TRANSISTOR, TRANSISTOR_* | NPN/PNP polarity, equivalent groups |
| Component | DIODE, DIODE_* | Signal/rectifier/zener types |
| Component | MOSFET, MOSFET_* | N/P channel, equivalent groups |
| Component | OPAMP, OPAMP_* | Single/dual/quad, equivalent families |
| Component | VOLTAGE_REGULATOR* | Fixed (78xx) vs adjustable (LM317) |
| Component | LOGIC_IC, IC | 74xx/CD4000 series, function groups |
| Component | LED, LED_* | Color, bins, families |
| Component | MEMORY, MEMORY_* | I2C/SPI EEPROM, Flash equivalents |
| Component | SENSOR, TEMPERATURE_SENSOR, ACCELEROMETER | Sensor families, package variants |
| Component | CONNECTOR, CONNECTOR_* | Pin count, pitch, family |
| Component | MICROCONTROLLER* | Series, package, manufacturer |
| Simple | (generic) | Family, series, features |
| Simple | (generic) | Value, size code, tolerance |
| Simple | (generic) | String edit distance |
| Simple | (generic) | Prefix, numeric, suffix weights |
Creating a New Similarity Calculator
1. Implement the Interface
public class NewComponentSimilarityCalculator implements ComponentSimilarityCalculator { private static final double HIGH_SIMILARITY = 0.9; private static final double MEDIUM_SIMILARITY = 0.7; private static final double LOW_SIMILARITY = 0.3; @Override public boolean isApplicable(ComponentType type) { if (type == null) return false; return type == ComponentType.NEW_COMPONENT || type.name().startsWith("NEW_COMPONENT_"); } @Override public double calculateSimilarity(String mpn1, String mpn2, PatternRegistry registry) { if (mpn1 == null || mpn2 == null) return 0.0; // Check if both are the component type we handle if (!isComponentType(mpn1) || !isComponentType(mpn2)) { return 0.0; } // Compare components // ... return similarity; } }
2. Key Design Principles
- Return 0.0 for null inputs - Always check for null MPNs and registry
- Return 0.0 for non-matching types - If the MPN isn't your component type
- Use equivalent groups - Define known equivalent parts (e.g., 2N2222 ≈ PN2222)
- Consider package variants - Same part in different package should be high similarity
- Ensure symmetry -
sim(A,B) == sim(B,A) - Keep in [0.0, 1.0] - Never return values outside this range
3. Common Patterns
Equivalent Groups
private static final Map<String, Set<String>> EQUIVALENT_GROUPS = new HashMap<>(); static { EQUIVALENT_GROUPS.put("2N2222", Set.of("2N2222", "2N2222A", "PN2222", "PN2222A")); } private boolean areEquivalent(String mpn1, String mpn2) { for (Set<String> group : EQUIVALENT_GROUPS.values()) { if (group.contains(mpn1) && group.contains(mpn2)) { return true; } } return false; }
Package Code Extraction
private String extractBasePart(String mpn) { // Remove common package suffixes return mpn.replaceAll("(?:CT|T|N|P|DG|PW|DR)$", ""); }
Polarity/Type Checking
private boolean areSamePolarity(String mpn1, String mpn2) { boolean isNPN1 = NPN_PATTERNS.stream().anyMatch(mpn1::matches); boolean isNPN2 = NPN_PATTERNS.stream().anyMatch(mpn2::matches); return isNPN1 == isNPN2; }
Testing
Test Structure
@Nested @DisplayName("isApplicable tests") class IsApplicableTests { /* ... */ } @Nested @DisplayName("Equivalent groups tests") class EquivalentGroupTests { /* ... */ } @Nested @DisplayName("Edge cases and null handling") class EdgeCaseTests { /* ... */ } @Nested @DisplayName("Symmetry and property tests") class PropertyTests { /* ... */ }
Run Tests
# All similarity calculator tests mvn test -Dtest="*SimilarityCalculatorTest,PassiveComponentCalculatorTest" # Specific calculator mvn test -Dtest=TransistorSimilarityCalculatorTest
Related Skills
- Resistor similarity details/similarity-resistor
- Transistor equivalent groups and polarity/similarity-transistor
- MOSFET N/P channel comparison/similarity-mosfet
- Op-amp families and equivalents/similarity-opamp
- Memory IC equivalents (I2C/SPI EEPROM, Flash)/similarity-memory
- Sensor family comparison/similarity-sensor
- LED bins and color temperature/similarity-led
- Voltage regulator comparison (78xx, LM317)/similarity-regulator
- Logic IC function groups (74xx, CD4000)/similarity-logic
Metadata-Driven Architecture (January 2026)
The similarity system now uses a metadata-driven architecture for configurable, type-specific similarity rules.
Conversion Status: 12 of 17 calculators converted (71% complete)
| Calculator | Status | PR | Conversion Date |
|---|---|---|---|
| ResistorSimilarityCalculator | ✅ Converted | - | Jan 2026 |
| CapacitorSimilarityCalculator | ✅ Converted | - | Jan 2026 |
| TransistorSimilarityCalculator | ✅ Converted | - | Jan 2026 |
| DiodeSimilarityCalculator | ✅ Converted | - | Jan 2026 |
| MosfetSimilarityCalculator | ✅ Converted | - | Jan 2026 |
| VoltageRegulatorSimilarityCalculator | ✅ Converted | - | Jan 2026 |
| OpAmpSimilarityCalculator | ✅ Converted | #116 | Jan 2026 |
| MemorySimilarityCalculator | ✅ Converted | #117 | Jan 2026 |
| LEDSimilarityCalculator | ✅ Converted | #118 | Jan 2026 |
| ConnectorSimilarityCalculator | ✅ Converted | (pre-existing) | Jan 2026 |
| LogicICSimilarityCalculator | ✅ Converted | #119 | Jan 2026 |
| SensorSimilarityCalculator | ✅ Converted | #120 | Jan 2026 |
| MicrocontrollerSimilarityCalculator | ⏳ Legacy | - | - |
| MCUSimilarityCalculator | ⏳ Legacy | - | - |
| PassiveComponentCalculator | ⏳ Legacy | - | - |
| LevenshteinCalculator | ⏳ Legacy | - | - |
| DefaultSimilarityCalculator | ⏳ Legacy | - | - |
Core Metadata Classes
| Class | Purpose |
|---|---|
| Defines specs, importance levels, tolerance rules per component type |
| Singleton registry mapping ComponentType → metadata |
| Enum: CRITICAL (1.0), HIGH (0.7), MEDIUM (0.4), LOW (0.2), OPTIONAL (0.0) |
| Interface for comparing spec values (ExactMatch, Percentage, MinRequired, MaxAllowed, Range) |
| Context-aware profiles (DESIGN_PHASE, REPLACEMENT, COST_OPTIMIZATION, PERFORMANCE_UPGRADE, EMERGENCY_SOURCING) |
Retrieving Metadata
ComponentTypeMetadataRegistry registry = ComponentTypeMetadataRegistry.getInstance(); // Get metadata for a component type Optional<ComponentTypeMetadata> metadata = registry.getMetadata(ComponentType.RESISTOR); // Query specs if (metadata.isPresent()) { ComponentTypeMetadata meta = metadata.get(); // Check if spec is critical boolean critical = meta.isCritical("resistance"); // true // Get tolerance rule for a spec SpecConfig config = meta.getSpecConfig("resistance"); if (config != null) { ToleranceRule rule = config.getToleranceRule(); SpecImportance importance = config.getImportance(); } // Get all configured specs Set<String> allSpecs = meta.getAllSpecs(); }
Pre-Registered Types (10)
RESISTOR, CAPACITOR, MOSFET, TRANSISTOR, DIODE, OPAMP, MICROCONTROLLER, MEMORY, LED, CONNECTOR
Each type has:
- Critical specs (must match for similarity)
- High/Medium/Low importance specs (contribute to score)
- Tolerance rules (how to compare values)
- Default similarity profile
Context-Aware Profiles
Adjust 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 |
| COST_OPTIMIZATION | 0.60 | 1.0 | 0.4 | 0.2 | 0.0 | Maintain critical specs only |
| EMERGENCY_SOURCING | 0.50 | 0.8 | 0.4 | 0.2 | 0.0 | Urgent, relaxed requirements |
// Check if similarity meets threshold for a profile SimilarityProfile profile = SimilarityProfile.REPLACEMENT; double similarity = 0.78; boolean passes = profile.meetsThreshold(similarity); // true // Get effective weight for a spec double effectiveWeight = profile.getEffectiveWeight(SpecImportance.HIGH); // 0.7 × 0.7 = 0.49
Converted Calculator Implementation Pattern
Calculators converted to metadata-driven approach follow this pattern:
@Override public double calculateSimilarity(String mpn1, String mpn2, PatternRegistry registry) { if (mpn1 == null || mpn2 == null) return 0.0; // Try metadata-driven approach first Optional<ComponentTypeMetadata> metadataOpt = metadataRegistry.getMetadata(ComponentType.OPAMP); if (metadataOpt.isPresent()) { logger.trace("Using metadata-driven similarity calculation"); return calculateMetadataDrivenSimilarity(mpn1, mpn2, metadataOpt.get()); } // Fallback to legacy pattern-based approach logger.trace("No metadata found, using legacy approach"); return calculateLegacySimilarity(mpn1, mpn2); } private double calculateMetadataDrivenSimilarity(String mpn1, String mpn2, ComponentTypeMetadata metadata) { SimilarityProfile profile = metadata.getDefaultProfile(); // Extract specs from MPNs String config1 = extractConfiguration(mpn1); // e.g., "dual", "quad" String config2 = extractConfiguration(mpn2); // ... extract other specs // Short-circuit check for CRITICAL incompatibility if (!config1.isEmpty() && !config2.isEmpty() && !config1.equals(config2)) { return LOW_SIMILARITY; } double totalScore = 0.0; double maxPossibleScore = 0.0; // Compare each spec with weighted scoring ComponentTypeMetadata.SpecConfig configSpec = metadata.getSpecConfig("configuration"); if (configSpec != null && !config1.isEmpty() && !config2.isEmpty()) { ToleranceRule rule = configSpec.getToleranceRule(); SpecValue<String> orig = new SpecValue<>(config1, SpecUnit.NONE); SpecValue<String> cand = new SpecValue<>(config2, SpecUnit.NONE); double specScore = rule.compare(orig, cand); double specWeight = profile.getEffectiveWeight(configSpec.getImportance()); totalScore += specScore * specWeight; maxPossibleScore += specWeight; } // Repeat for other specs (family, package, etc.) // ... double similarity = maxPossibleScore > 0 ? totalScore / maxPossibleScore : 0.0; // Apply boosts for equivalent groups if (areEquivalentParts(mpn1, mpn2)) { similarity = Math.max(similarity, HIGH_SIMILARITY); } return similarity; }
Key Features of Converted Calculators:
- Dual-path approach - Try metadata first, fall back to legacy
- Short-circuit checks - Early return for CRITICAL spec mismatches
- Weighted scoring -
formulatotalScore / maxPossibleScore - Spec extraction - Component-specific methods to extract values from MPNs
- Equivalent boost - Apply known equivalence rules after scoring
- Profile support - Use
for context-aware weightsgetEffectiveWeight()
Converted Calculator Specs
| Calculator | Critical Specs | High Importance | Medium Importance | Low Importance |
|---|---|---|---|---|
| OpAmp | configuration | family | package | - |
| Memory | memoryType, capacity | interface | - | package |
| LED | color | family, brightness | - | package |
| Connector | pinCount, pitch | family | mountingType | - |
| LogicIC | function | series, technology | - | package |
| Sensor | sensorType | family | interface | package |
Migration Path
For converting existing calculators:
- Add imports:
,SimilarityProfile
,ToleranceRule
,SpecUnitSpecValue - Modify
to check for metadata firstcalculateSimilarity() - Implement
methodcalculateMetadataDrivenSimilarity() - Add spec extraction helper methods
- Update tests to use threshold assertions (
)>= HIGH_SIMILARITY - Run full test suite to verify backward compatibility
For new calculators:
- Start with metadata-driven approach from the beginning
- Define specs in
ComponentTypeMetadataRegistry - Implement spec extraction methods
- Use
wrapper for type-safe comparisonSpecValue - Apply context-aware profiles with
SimilarityProfile
See CLAUDE.md § "Metadata-Driven Similarity Framework" for complete architecture details.
Learnings & Quirks
General Patterns
- Always normalize MPNs to uppercase before comparison
- Package suffixes vary by manufacturer - don't assume consistency
- Some calculators return HIGH_SIMILARITY (0.9) for identical parts, not 1.0
Edge Cases
- Parts starting with digits (e.g., "2N2222") need special regex handling -
won't match^[A-Za-z]+ - Some MPNs have significant hyphens (Molex) vs decorative hyphens (TI)
- Reel/tape suffixes (-RL, -T, -TR) should generally be ignored
Metadata System Gotchas (January 2026)
SpecValue Instantiation:
// NO static factory - use constructor SpecValue<Double> v = new SpecValue<>(100.0, SpecUnit.FARAD); // ✓ SpecValue<Double> v = SpecValue.of(100.0); // ✗ Does not exist
API Return Types:
// Registry returns Optional, metadata methods return direct values Optional<ComponentTypeMetadata> meta = registry.getMetadata(type); // Optional SpecConfig config = metadata.getSpecConfig("resistance"); // Can be null boolean critical = metadata.isCritical("resistance"); // false if not found
Singleton Side Effects:
- Registry is shared across all tests
- Custom registrations persist
- Use unregistered types (CRYSTAL, FUSE) for tests, not RESISTOR/CAPACITOR
Profile Multiplier Values:
- COST_OPTIMIZATION maintains CRITICAL=1.0 (safety specs never compromised)
- EMERGENCY_SOURCING relaxes CRITICAL to 0.8 (only for urgent scenarios)
Builder Validation:
<!-- Add new learnings above this line -->ComponentTypeMetadata.builder(null).build(); // IllegalArgumentException ComponentTypeMetadata.builder(ComponentType.IC).build(); // IllegalStateException (no specs)
See Also
Advanced Skills
- Calculator registration, ordering, and the OpAmp IC interception bug/similarity-calculator-architecture
- Converting calculators to metadata-driven approach/metadata-driven-similarity-conversion
- How to extract specs from MPNs for comparison/component-spec-extraction
- Hardcoded equivalent groups across calculators/equivalent-group-identification