AgentSkillsCN

similarity

适用于与元件相似度计算——比较 MPN,寻找等效零件,实现全新的相似度计算器,或深入理解元件匹配的运作机制。

SKILL.md
--- frontmatter
name: similarity
description: Use when working with component similarity calculations - comparing MPNs, finding equivalent parts, implementing new similarity calculators, or understanding how component matching works.

Component 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)

java
public interface SimilarityCalculator {
    double calculateSimilarity(String normalizedMpn1, String normalizedMpn2);
}

Used for generic calculators that don't need component type context.

ComponentSimilarityCalculator (Type-Aware)

java
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

java
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

CalculatorInterfaceComponent TypesKey Features
ResistorSimilarityCalculatorComponentRESISTOR, RESISTOR_*Value, package, tolerance
CapacitorSimilarityCalculatorComponentCAPACITOR, CAPACITOR_*Value, voltage, dielectric
TransistorSimilarityCalculatorComponentTRANSISTOR, TRANSISTOR_*NPN/PNP polarity, equivalent groups
DiodeSimilarityCalculatorComponentDIODE, DIODE_*Signal/rectifier/zener types
MosfetSimilarityCalculatorComponentMOSFET, MOSFET_*N/P channel, equivalent groups
OpAmpSimilarityCalculatorComponentOPAMP, OPAMP_*Single/dual/quad, equivalent families
VoltageRegulatorSimilarityCalculatorComponentVOLTAGE_REGULATOR*Fixed (78xx) vs adjustable (LM317)
LogicICSimilarityCalculatorComponentLOGIC_IC, IC74xx/CD4000 series, function groups
LEDSimilarityCalculatorComponentLED, LED_*Color, bins, families
MemorySimilarityCalculatorComponentMEMORY, MEMORY_*I2C/SPI EEPROM, Flash equivalents
SensorSimilarityCalculatorComponentSENSOR, TEMPERATURE_SENSOR, ACCELEROMETERSensor families, package variants
ConnectorSimilarityCalculatorComponentCONNECTOR, CONNECTOR_*Pin count, pitch, family
MicrocontrollerSimilarityCalculatorComponentMICROCONTROLLER*Series, package, manufacturer
MCUSimilarityCalculatorSimple(generic)Family, series, features
PassiveComponentCalculatorSimple(generic)Value, size code, tolerance
LevenshteinCalculatorSimple(generic)String edit distance
DefaultSimilarityCalculatorSimple(generic)Prefix, numeric, suffix weights

Creating a New Similarity Calculator

1. Implement the Interface

java
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

  1. Return 0.0 for null inputs - Always check for null MPNs and registry
  2. Return 0.0 for non-matching types - If the MPN isn't your component type
  3. Use equivalent groups - Define known equivalent parts (e.g., 2N2222 ≈ PN2222)
  4. Consider package variants - Same part in different package should be high similarity
  5. Ensure symmetry - sim(A,B) == sim(B,A)
  6. Keep in [0.0, 1.0] - Never return values outside this range

3. Common Patterns

Equivalent Groups

java
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

java
private String extractBasePart(String mpn) {
    // Remove common package suffixes
    return mpn.replaceAll("(?:CT|T|N|P|DG|PW|DR)$", "");
}

Polarity/Type Checking

java
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

java
@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

bash
# All similarity calculator tests
mvn test -Dtest="*SimilarityCalculatorTest,PassiveComponentCalculatorTest"

# Specific calculator
mvn test -Dtest=TransistorSimilarityCalculatorTest

Related Skills

  • /similarity-resistor - Resistor similarity details
  • /similarity-transistor - Transistor equivalent groups and polarity
  • /similarity-mosfet - MOSFET N/P channel comparison
  • /similarity-opamp - Op-amp families and equivalents
  • /similarity-memory - Memory IC equivalents (I2C/SPI EEPROM, Flash)
  • /similarity-sensor - Sensor family comparison
  • /similarity-led - LED bins and color temperature
  • /similarity-regulator - Voltage regulator comparison (78xx, LM317)
  • /similarity-logic - Logic IC function groups (74xx, CD4000)

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)

CalculatorStatusPRConversion 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#116Jan 2026
MemorySimilarityCalculator✅ Converted#117Jan 2026
LEDSimilarityCalculator✅ Converted#118Jan 2026
ConnectorSimilarityCalculator✅ Converted(pre-existing)Jan 2026
LogicICSimilarityCalculator✅ Converted#119Jan 2026
SensorSimilarityCalculator✅ Converted#120Jan 2026
MicrocontrollerSimilarityCalculator⏳ Legacy--
MCUSimilarityCalculator⏳ Legacy--
PassiveComponentCalculator⏳ Legacy--
LevenshteinCalculator⏳ Legacy--
DefaultSimilarityCalculator⏳ Legacy--

Core Metadata Classes

ClassPurpose
ComponentTypeMetadataDefines specs, importance levels, tolerance rules per component type
ComponentTypeMetadataRegistrySingleton registry mapping ComponentType → metadata
SpecImportanceEnum: CRITICAL (1.0), HIGH (0.7), MEDIUM (0.4), LOW (0.2), OPTIONAL (0.0)
ToleranceRuleInterface for comparing spec values (ExactMatch, Percentage, MinRequired, MaxAllowed, Range)
SimilarityProfileContext-aware profiles (DESIGN_PHASE, REPLACEMENT, COST_OPTIMIZATION, PERFORMANCE_UPGRADE, EMERGENCY_SOURCING)

Retrieving Metadata

java
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:

ProfileThresholdCRITICALHIGHMEDIUMLOWUse Case
DESIGN_PHASE0.851.00.90.70.4Exact match for new designs
REPLACEMENT0.751.00.70.40.2Default: Direct replacement
COST_OPTIMIZATION0.601.00.40.20.0Maintain critical specs only
EMERGENCY_SOURCING0.500.80.40.20.0Urgent, relaxed requirements
java
// 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:

java
@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:

  1. Dual-path approach - Try metadata first, fall back to legacy
  2. Short-circuit checks - Early return for CRITICAL spec mismatches
  3. Weighted scoring - totalScore / maxPossibleScore formula
  4. Spec extraction - Component-specific methods to extract values from MPNs
  5. Equivalent boost - Apply known equivalence rules after scoring
  6. Profile support - Use getEffectiveWeight() for context-aware weights

Converted Calculator Specs

CalculatorCritical SpecsHigh ImportanceMedium ImportanceLow Importance
OpAmpconfigurationfamilypackage-
MemorymemoryType, capacityinterface-package
LEDcolorfamily, brightness-package
ConnectorpinCount, pitchfamilymountingType-
LogicICfunctionseries, technology-package
SensorsensorTypefamilyinterfacepackage

Migration Path

For converting existing calculators:

  1. Add imports: SimilarityProfile, ToleranceRule, SpecUnit, SpecValue
  2. Modify calculateSimilarity() to check for metadata first
  3. Implement calculateMetadataDrivenSimilarity() method
  4. Add spec extraction helper methods
  5. Update tests to use threshold assertions (>= HIGH_SIMILARITY)
  6. Run full test suite to verify backward compatibility

For new calculators:

  1. Start with metadata-driven approach from the beginning
  2. Define specs in ComponentTypeMetadataRegistry
  3. Implement spec extraction methods
  4. Use SpecValue wrapper for type-safe comparison
  5. 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 - ^[A-Za-z]+ won't match
  • 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:

java
// 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:

java
// 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:

java
ComponentTypeMetadata.builder(null).build(); // IllegalArgumentException
ComponentTypeMetadata.builder(ComponentType.IC).build(); // IllegalStateException (no specs)
<!-- Add new learnings above this line -->

See Also

Advanced Skills

  • /similarity-calculator-architecture - Calculator registration, ordering, and the OpAmp IC interception bug
  • /metadata-driven-similarity-conversion - Converting calculators to metadata-driven approach
  • /component-spec-extraction - How to extract specs from MPNs for comparison
  • /equivalent-group-identification - Hardcoded equivalent groups across calculators