.claude/skills/metadata-driven-similarity-conversion/SKILL.md
# Metadata-Driven Similarity Conversion Use this skill when converting existing similarity calculators to the metadata-driven approach with configurable, type-specific rules. ## 5-Step Conversion Process ### Step 1: Add Imports Add these imports to your calculator class: ```java import no.cantara.electronic.component.lib.metadata.ComponentTypeMetadata; import no.cantara.electronic.component.lib.metadata.ComponentTypeMetadataRegistry; import no.cantara.electronic.component.lib.metadata.Simil
npx skillsauth add Cantara/lib-electronic-components .claude/skills/metadata-driven-similarity-conversionInstall 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 converting existing similarity calculators to the metadata-driven approach with configurable, type-specific rules.
Add these imports to your calculator class:
import no.cantara.electronic.component.lib.metadata.ComponentTypeMetadata;
import no.cantara.electronic.component.lib.metadata.ComponentTypeMetadataRegistry;
import no.cantara.electronic.component.lib.metadata.SimilarityProfile;
import no.cantara.electronic.component.lib.metadata.ToleranceRule;
import no.cantara.electronic.component.lib.specs.base.SpecUnit;
import no.cantara.electronic.component.lib.specs.base.SpecValue;
import java.util.Optional;
// Add field
private final ComponentTypeMetadataRegistry metadataRegistry =
ComponentTypeMetadataRegistry.getInstance();
@Override
public double calculateSimilarity(String mpn1, String mpn2, PatternRegistry registry) {
if (mpn1 == null || mpn2 == null) return 0.0;
// ✅ NEW: Try metadata-driven approach first
Optional<ComponentTypeMetadata> metadataOpt = metadataRegistry.getMetadata(ComponentType.RESISTOR);
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 resistance1 = extractResistance(mpn1);
String resistance2 = extractResistance(mpn2);
String package1 = extractPackage(mpn1);
String package2 = extractPackage(mpn2);
String tolerance1 = extractTolerance(mpn1);
String tolerance2 = extractTolerance(mpn2);
// Short-circuit check for CRITICAL incompatibility
ComponentTypeMetadata.SpecConfig resistanceConfig = metadata.getSpecConfig("resistance");
if (resistanceConfig != null &&
resistanceConfig.getImportance() == SpecImportance.CRITICAL &&
!resistance1.isEmpty() && !resistance2.isEmpty() &&
!resistance1.equals(resistance2)) {
logger.debug("CRITICAL spec mismatch - returning LOW_SIMILARITY");
return LOW_SIMILARITY;
}
double totalScore = 0.0;
double maxPossibleScore = 0.0;
// Compare each spec with weighted scoring
if (resistanceConfig != null && !resistance1.isEmpty() && !resistance2.isEmpty()) {
ToleranceRule rule = resistanceConfig.getToleranceRule();
SpecValue<String> orig = new SpecValue<>(resistance1, SpecUnit.OHM);
SpecValue<String> cand = new SpecValue<>(resistance2, SpecUnit.OHM);
double specScore = rule.compare(orig, cand);
double specWeight = profile.getEffectiveWeight(resistanceConfig.getImportance());
totalScore += specScore * specWeight;
maxPossibleScore += specWeight;
}
// Repeat for other specs (package, tolerance, etc.)
// ...
double similarity = maxPossibleScore > 0 ? totalScore / maxPossibleScore : 0.0;
// Apply boosts for known equivalent groups
if (areEquivalentParts(mpn1, mpn2)) {
similarity = Math.max(similarity, HIGH_SIMILARITY);
}
return similarity;
}
private String extractResistance(String mpn) {
if (mpn == null || mpn.isEmpty()) return "";
// Manufacturer-specific extraction logic
// e.g., Yageo RC0805JR-0710KL → "10K"
return "";
}
private String extractPackage(String mpn) {
if (mpn == null || mpn.isEmpty()) return "";
// e.g., RC0805 → "0805"
return "";
}
private String extractTolerance(String mpn) {
if (mpn == null || mpn.isEmpty()) return "";
// e.g., "JR" → "5%"
return "";
}
BEFORE (exact equality):
@Test
void shouldCalculateHighSimilarityForSameResistance() {
double similarity = calculator.calculateSimilarity("RC0805JR-0710KL", "RC0805FR-0710KL", registry);
assertEquals(0.9, similarity); // ❌ Exact match - fragile!
}
AFTER (threshold assertion):
@Test
void shouldCalculateHighSimilarityForSameResistance() {
double similarity = calculator.calculateSimilarity("RC0805JR-0710KL", "RC0805FR-0710KL", registry);
assertTrue(similarity >= HIGH_SIMILARITY, // ✅ Threshold - robust!
"Same resistance should have HIGH_SIMILARITY (>= 0.9), got: " + similarity);
}
Why: Metadata-driven approach produces more precise scores (0.95-0.99 for very similar parts) instead of fixed thresholds (0.9).
Pattern: Metadata first, legacy fallback.
@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);
}
Example from ResistorSimilarityCalculator:
Benefits:
Pattern: Check CRITICAL specs early, return 0.0 on mismatch.
// Short-circuit check for CRITICAL incompatibility
ComponentTypeMetadata.SpecConfig capacitanceConfig = metadata.getSpecConfig("capacitance");
ComponentTypeMetadata.SpecConfig voltageConfig = metadata.getSpecConfig("voltage");
// Capacitance mismatch
if (capacitanceConfig != null &&
capacitanceConfig.getImportance() == SpecImportance.CRITICAL &&
!capacitance1.isEmpty() && !capacitance2.isEmpty() &&
!capacitance1.equals(capacitance2)) {
logger.debug("CRITICAL capacitance mismatch - returning 0.0");
return 0.0;
}
// Voltage downgrade (50V → 5V is incompatible)
if (voltageConfig != null &&
voltageConfig.getImportance() == SpecImportance.CRITICAL &&
voltage1 > voltage2) {
logger.debug("CRITICAL voltage downgrade - returning 0.0");
return 0.0;
}
Example (Capacitor):
similarity = totalScore / maxPossibleScore
where:
totalScore = Σ(specScore × effectiveWeight)
maxPossibleScore = Σ(effectiveWeight)
effectiveWeight = profile.getEffectiveWeight(specImportance)
Example calculation (Resistor):
| Spec | Value1 | Value2 | Score | Importance | Weight | Contribution | |------|--------|--------|-------|------------|--------|--------------| | Resistance | 10kΩ | 10kΩ | 1.0 | CRITICAL | 1.0 | 1.0 × 1.0 = 1.0 | | Package | 0805 | 0805 | 1.0 | MEDIUM | 0.4 | 1.0 × 0.4 = 0.4 | | Tolerance | 5% | 1% | 0.8 | LOW | 0.2 | 0.8 × 0.2 = 0.16 |
totalScore = 1.0 + 0.4 + 0.16 = 1.56
maxPossibleScore = 1.0 + 0.4 + 0.2 = 1.6
similarity = 1.56 / 1.6 = 0.975 ≈ 0.98
assertEquals(0.9, similarity);
assertEquals(0.0, similarity);
assertEquals(1.0, similarity);
Problem: Metadata produces more precise scores (0.95, 0.99, 0.703) that don't match exact thresholds.
// HIGH similarity (≥ 0.9)
assertTrue(similarity >= HIGH_SIMILARITY,
"Expected HIGH_SIMILARITY (>= 0.9), got: " + similarity);
// MEDIUM similarity (≥ 0.5)
assertTrue(similarity >= MEDIUM_SIMILARITY,
"Expected MEDIUM_SIMILARITY (>= 0.5), got: " + similarity);
// LOW similarity (< 0.5)
assertTrue(similarity < MEDIUM_SIMILARITY,
"Expected LOW_SIMILARITY (< 0.5), got: " + similarity);
// ZERO similarity
assertEquals(0.0, similarity, 0.001,
"Expected ZERO similarity for incompatible parts");
Constants:
private static final double HIGH_SIMILARITY = 0.9;
private static final double MEDIUM_SIMILARITY = 0.5;
private static final double LOW_SIMILARITY = 0.3;
| Calculator | Complexity | Priority | Notes | |-----------|------------|----------|-------| | PassiveComponentCalculator | Medium | HIGH | Generic passive handler, needs multi-type metadata | | MicrocontrollerSimilarityCalculator | High | HIGH | Complex specs (flash, RAM, peripherals) | | MCUSimilarityCalculator | High | HIGH | Similar to Microcontroller, may merge | | LevenshteinCalculator | Low | LOW | String-based, may not need metadata | | DefaultSimilarityCalculator | Low | LOW | Fallback, intentionally simple |
Recommendation: Start with PassiveComponentCalculator (straightforward, high impact).
BEFORE (pattern-based):
AFTER (metadata-driven):
BEFORE:
AFTER:
BEFORE:
AFTER:
If no metadata exists for a type, calculator falls back to legacy behavior. This enables gradual migration.
Each manufacturer encodes specs differently in MPNs. Extraction logic must handle multiple formats.
Exact equality (assertEquals(0.9, ...)) breaks when metadata produces 0.95 or 0.99. Use threshold assertions (>= HIGH_SIMILARITY).
Checking CRITICAL specs early avoids unnecessary extraction and comparison of other specs.
/similarity-metadata - Complete metadata framework documentation/component-spec-extraction - Spec extraction patternsComponentTypeMetadata.java - Metadata configurationSimilarityProfile.java - Context-aware profilesdata-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.