Lib-electronic-components mpn-normalization

MPN Normalization

install
source · Clone the upstream repo
git clone https://github.com/Cantara/lib-electronic-components
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Cantara/lib-electronic-components "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/mpn-normalization" ~/.claude/skills/cantara-lib-electronic-components-mpn-normalization && rm -rf "$T"
manifest: .claude/skills/mpn-normalization/SKILL.md
source content

MPN Normalization

Use this skill when working with MPN (Manufacturer Part Number) normalization, package suffix handling, and component equivalence checking.

Core Methods

1. stripPackageSuffix() - Remove Packaging Codes

Purpose: Strip manufacturer-specific package suffixes to get the base component part number.

Supported patterns:

PatternDelimiterExampleManufacturersUse Case
Plus suffix
+
MAX3483EESA**+** → MAX3483EESAMaxim, Analog DevicesLead-free indicator
Hash suffix
#
LTC2053HMS8**#PBF** → LTC2053HMS8Linear TechnologyRoHS, Tape & Reel (#PBF, #TR, #TRPBF)
Slash suffix
/
TJA1050T**/CM,118** → TJA1050TNXPOrdering codes (/CM,118, /SN)
Comma suffix
,
NC7WZ04**,315** → NC7WZ04VariousGeneric ordering codes

API:

String base = MPNUtils.stripPackageSuffix("MAX3483EESA+");
// Returns: "MAX3483EESA"

Implementation (MPNUtils.java lines 565-597):

public static String stripPackageSuffix(String mpn) {
    if (mpn == null || mpn.trim().isEmpty()) {
        return "";
    }

    String trimmed = mpn.trim();

    // Pattern 1: + suffix (Maxim/Analog Devices lead-free)
    if (trimmed.endsWith("+")) {
        return trimmed.substring(0, trimmed.length() - 1);
    }

    // Pattern 2: # suffix (Linear Tech: #PBF, #TR, #TRPBF)
    int hashIndex = trimmed.indexOf('#');
    if (hashIndex > 0) {
        return trimmed.substring(0, hashIndex);
    }

    // Pattern 3: / suffix (NXP ordering codes: /CM,118)
    int slashIndex = trimmed.indexOf('/');
    if (slashIndex > 0) {
        return trimmed.substring(0, slashIndex);
    }

    // Pattern 4: , suffix (some ordering codes)
    int commaIndex = trimmed.indexOf(',');
    if (commaIndex > 0) {
        return trimmed.substring(0, commaIndex);
    }

    // No recognized suffix
    return trimmed;
}

Gotcha: Single-letter suffixes are NOT stripped to avoid false positives:

MPNUtils.stripPackageSuffix("NC7WZ04G");  // Returns: "NC7WZ04G" (not "NC7WZ04")
// "G" could be part of base MPN, not a packaging suffix

2. getSearchVariations() - Generate Search Variants

Purpose: Generate MPN variations for datasheet searches, component lookups, and supplier matching.

Use cases:

  • Datasheet searches: Try both "MAX3483EESA+" and "MAX3483EESA" for better results
  • Component deduplication: Recognize variants as same component
  • Supplier matching: Match parts despite different packaging codes

API:

List<String> variations = MPNUtils.getSearchVariations("LTC2053HMS8#PBF");
// Returns: ["LTC2053HMS8#PBF", "LTC2053HMS8"]

Implementation (MPNUtils.java lines 619-636):

public static List<String> getSearchVariations(String mpn) {
    List<String> variations = new ArrayList<>();

    if (mpn == null || mpn.trim().isEmpty()) {
        return variations;
    }

    // Always include original
    variations.add(mpn);

    // Add base part if different
    String base = stripPackageSuffix(mpn);
    if (!base.isEmpty() && !base.equals(mpn)) {
        variations.add(base);
    }

    return variations;
}

Examples:

MPNUtils.getSearchVariations("MAX3483EESA+");
// → ["MAX3483EESA+", "MAX3483EESA"]

MPNUtils.getSearchVariations("LTC2053HMS8#PBF");
// → ["LTC2053HMS8#PBF", "LTC2053HMS8"]

MPNUtils.getSearchVariations("ADS1115");
// → ["ADS1115"]  // No variations if no suffix

3. isEquivalentMPN() - Component Equivalence Check

Purpose: Check if two MPNs refer to the same base component, ignoring packaging differences.

Use cases:

  • BOM validation: Verify supplier parts match design MPNs
  • Component deduplication: Merge duplicate entries in inventory
  • Cross-referencing: Identify same component across different suppliers

API:

boolean equiv = MPNUtils.isEquivalentMPN("LTC2053HMS8#PBF", "LTC2053HMS8#TR");
// Returns: true (same base component, different tape/reel)

Implementation (MPNUtils.java lines 656-669):

public static boolean isEquivalentMPN(String mpn1, String mpn2) {
    if (mpn1 == null || mpn2 == null) {
        return false;
    }

    String base1 = stripPackageSuffix(mpn1);
    String base2 = stripPackageSuffix(mpn2);

    if (base1.isEmpty() || base2.isEmpty()) {
        return false;
    }

    return base1.equalsIgnoreCase(base2);  // Case-insensitive comparison
}

Examples:

// ✅ Equivalent (same base, different packaging)
MPNUtils.isEquivalentMPN("MAX3483EESA+", "MAX3483EESA");
// → true

MPNUtils.isEquivalentMPN("LTC2053HMS8#PBF", "LTC2053HMS8#TR");
// → true (same chip, different tape/reel)

MPNUtils.isEquivalentMPN("TJA1050T/CM,118", "TJA1050T/SN");
// → true (same part, different ordering codes)

// ❌ Not equivalent (different base parts)
MPNUtils.isEquivalentMPN("NC7WZ485M8X", "NC7WZ240");
// → false (different chips)

MPNUtils.isEquivalentMPN("LM358", "LM324");
// → false (different op-amp configurations: dual vs quad)

4. getPackageSuffix() - Extract Package Suffix

Purpose: Extract the package suffix from an MPN, if present.

API:

Optional<String> suffix = MPNUtils.getPackageSuffix("MAX3483EESA+");
// Returns: Optional.of("+")

Optional<String> suffix = MPNUtils.getPackageSuffix("ADS1115");
// Returns: Optional.empty()

Implementation (MPNUtils.java lines 685-696):

public static Optional<String> getPackageSuffix(String mpn) {
    if (mpn == null || mpn.trim().isEmpty()) {
        return Optional.empty();
    }

    String base = stripPackageSuffix(mpn);
    if (base.equals(mpn.trim())) {
        return Optional.empty();  // No suffix found
    }

    return Optional.of(mpn.trim().substring(base.length()));
}

Examples:

MPNUtils.getPackageSuffix("MAX3483EESA+");
// → Optional.of("+")

MPNUtils.getPackageSuffix("LTC2053HMS8#PBF");
// → Optional.of("#PBF")

MPNUtils.getPackageSuffix("TJA1050T/CM,118");
// → Optional.of("/CM,118")

MPNUtils.getPackageSuffix("ADS1115IDGSR");
// → Optional.empty()

5. normalize() - Case and Special Character Removal

Purpose: Normalize MPNs for consistent matching by removing special characters and converting to uppercase.

API:

String normalized = MPNUtils.normalize("LM358-N");
// Returns: "LM358N"

Implementation (MPNUtils.java lines 56-61):

private static final Pattern SPECIAL_CHARS = Pattern.compile("[^A-Z0-9]");

public static String normalize(String mpn) {
    if (mpn == null || mpn.trim().isEmpty()) {
        return "";
    }
    return SPECIAL_CHARS.matcher(mpn.trim().toUpperCase()).replaceAll("");
}

Critical for position-based extraction:

// ❌ WRONG: Using MPN directly with hyphens
String mpn = "ATMEGA328P-PU";
char packageChar = mpn.charAt(mpn.length() - 2);  // Returns 'P' (from "-PU"), not package!

// ✅ CORRECT: Normalize first
String normalized = MPNUtils.normalize("ATMEGA328P-PU");  // → "ATMEGA328PPU"
// Now position-based extraction works correctly

Examples:

MPNUtils.normalize("LM358-N");
// → "LM358N"

MPNUtils.normalize("stm32f103c8t6");
// → "STM32F103C8T6"

MPNUtils.normalize("  LM7805/CT  ");
// → "LM7805CT"

MPNUtils.normalize("MAX3483+");
// → "MAX3483+"  // Plus is removed by normalization (not A-Z0-9)
// Wait, that's wrong - let me check...

IMPORTANT NOTE:

normalize()
removes ALL non-alphanumeric characters, including package suffix delimiters (+, #, /, ,). This makes it INCOMPATIBLE with
stripPackageSuffix()
which relies on delimiters.

Use normalize() for:

  • MPN comparison (case-insensitive, whitespace-agnostic)
  • Manufacturer detection
  • Type detection

DO NOT use normalize() before stripPackageSuffix():

// ❌ WRONG: normalize() removes delimiters
String base = MPNUtils.stripPackageSuffix(MPNUtils.normalize("MAX3483+"));
// → "MAX3483" (normalize removed +, stripPackageSuffix does nothing)

// ✅ CORRECT: stripPackageSuffix first, then normalize if needed
String base = MPNUtils.stripPackageSuffix("MAX3483+");  // → "MAX3483"
String normalized = MPNUtils.normalize(base);           // → "MAX3483"

Unicode Gotcha: µ → Μ Uppercasing Issue

Problem: The micro sign µ (U+00B5) becomes Greek MU Μ (U+039C) when uppercased, breaking parsing.

The Issue

String mpn = "10µF";
String upper = mpn.toUpperCase();
// upper = "10ΜF"  (Greek MU Μ, NOT Latin M!)

// Parsing logic looking for "u" or "µ" fails
if (upper.contains("UF")) {  // ❌ FALSE - contains "ΜF" (Greek MU)
    // Parse microfarads
}

Character codes:

  • Micro sign: µ (U+00B5)
  • Greek MU (uppercase): Μ (U+039C)
  • Latin M (uppercase): M (U+004D)

These are DIFFERENT Unicode characters!

Impact on CapacitorSimilarityCalculator

parseCapacitanceValue() (lines 189-192):

// ✅ CORRECT: Replace µ/Μ with 'u' BEFORE normalizing
String normalized = value.replace("µ", "u").replace("Μ", "u");
normalized = normalizeValue(normalized);
if (normalized == null) {
    return null;

Why this matters:

// Input: "10µF"
// Step 1: Replace µ → u:  "10uF"
// Step 2: toUpperCase():   "10UF"
// Step 3: Parse successfully

// ❌ WRONG: toUpperCase() first
// Input: "10µF"
// Step 1: toUpperCase():   "10ΜF"  (Greek MU!)
// Step 2: Try to parse:    FAILS - looking for "UF", finds "ΜF"

Solution Pattern

Always replace µ/Μ with 'u' BEFORE calling toUpperCase() or normalize():

// ✅ CORRECT Pattern
public static String parseValue(String value) {
    // 1. Replace micro variants with 'u' FIRST
    String cleaned = value.replace("µ", "u").replace("Μ", "u");

    // 2. NOW safe to uppercase
    String upper = cleaned.toUpperCase();

    // 3. Parse normally
    if (upper.endsWith("UF")) {
        // Parse microfarads
    }
}

// ❌ WRONG Pattern
public static String parseValue(String value) {
    String upper = value.toUpperCase();  // µ → Μ (Greek MU)
    // Parsing will fail!
}

Real-World Examples

// Capacitor MPNs with micro sign
"GRM188R71H103KA01D"  // 10nF (no micro sign)
"C1206C105K4RAC"      // 1µF (might be written as "1uF" or "1µF")

// If user input contains µ:
"1µF" → replace µ with u → "1uF" → uppercase → "1UF" → parse ✅

// If uppercased first:
"1µF" → uppercase → "1ΜF" → parse fails ❌ (looking for "UF", finds "ΜF")

Testing Strategy

ParameterizedTest Examples

Test all 4 suffix patterns:

@ParameterizedTest
@CsvSource({
    "MAX3483EESA+, MAX3483EESA",           // Plus suffix
    "LTC2053HMS8#PBF, LTC2053HMS8",        // Hash suffix
    "TJA1050T/CM,118, TJA1050T",           // Slash suffix
    "NC7WZ04,315, NC7WZ04"                 // Comma suffix
})
void testStripPackageSuffix(String input, String expected) {
    assertEquals(expected, MPNUtils.stripPackageSuffix(input));
}

Test edge cases:

@Test
void testStripPackageSuffix_EdgeCases() {
    assertEquals("", MPNUtils.stripPackageSuffix(null));
    assertEquals("", MPNUtils.stripPackageSuffix(""));
    assertEquals("", MPNUtils.stripPackageSuffix("   "));
    assertEquals("TEST", MPNUtils.stripPackageSuffix("  TEST  "));
}

@Test
void testStripPackageSuffix_SingleCharacter() {
    // Single character - should NOT strip (ambiguous)
    assertEquals("NC7WZ04G", MPNUtils.stripPackageSuffix("NC7WZ04G"));
}

Test search variations:

@Test
void testGetSearchVariations_WithSuffix() {
    List<String> variations = MPNUtils.getSearchVariations("MAX3483EESA+");
    assertEquals(2, variations.size());
    assertTrue(variations.contains("MAX3483EESA+"));
    assertTrue(variations.contains("MAX3483EESA"));
}

@Test
void testGetSearchVariations_NoSuffix() {
    List<String> variations = MPNUtils.getSearchVariations("ADS1115");
    assertEquals(1, variations.size());
    assertTrue(variations.contains("ADS1115"));
}

Test equivalence:

@ParameterizedTest
@CsvSource({
    "MAX3483EESA+, MAX3483EESA, true",
    "LTC2053HMS8#PBF, LTC2053HMS8#TR, true",
    "TJA1050T/CM,118, TJA1050T/SN, true",
    "NC7WZ485M8X, NC7WZ240, false",
    "LM358, LM324, false"
})
void testIsEquivalentMPN(String mpn1, String mpn2, boolean expected) {
    assertEquals(expected, MPNUtils.isEquivalentMPN(mpn1, mpn2));
}

Manufacturer-Specific Notes

Maxim Integrated / Analog Devices

Suffix:

+
(plus sign)

  • Meaning: Lead-free, RoHS compliant
  • Examples:
    • MAX3483EESA+ → MAX3483EESA
    • MAX485ESA+ → MAX485ESA
    • ADS1115IDGSR+ → ADS1115IDGSR

Linear Technology (now Analog Devices)

Suffix:

#PBF
,
#TR
,
#TRPBF

  • Meaning:
    • PBF = Lead-free (Pb-Free)
    • TR = Tape and Reel
    • TRPBF = Tape and Reel, Lead-free
  • Examples:
    • LTC2053HMS8#PBF → LTC2053HMS8
    • LT1117CST#TR → LT1117CST
    • LTC2053HMS8#TRPBF → LTC2053HMS8

NXP Semiconductors

Suffix:

/CM,118
,
/SN

  • Meaning: Ordering codes, package variants
  • Examples:
    • TJA1050T/CM,118 → TJA1050T
    • MCP2551-I/SN → MCP2551-I

Various Manufacturers

Suffix:

,315
,
,118

  • Meaning: Generic ordering codes
  • Examples:
    • NC7WZ04,315 → NC7WZ04

Gotchas

1. First Delimiter Wins

If multiple delimiters exist, the first match is used:

MPNUtils.stripPackageSuffix("PART#ABC/XYZ");
// Returns: "PART" (stops at #, ignores /)

2. Conservative Design

Single-letter suffixes are NOT stripped to avoid false positives:

MPNUtils.stripPackageSuffix("NC7WZ04G");
// Returns: "NC7WZ04G" (not "NC7WZ04")
// "G" could be part of base MPN

3. No Manufacturer-Specific Logic

The stripPackageSuffix() method uses generic patterns, not ManufacturerHandler integration:

// Works for known patterns
MPNUtils.stripPackageSuffix("MAX3483+");  // → "MAX3483"

// Doesn't understand manufacturer-specific suffixes
MPNUtils.stripPackageSuffix("LM7805CT");  // → "LM7805CT" (CT not recognized)
// "CT" is a TI voltage regulator package suffix, but not a recognized delimiter

For manufacturer-specific package extraction, use

ManufacturerHandler.extractPackageCode()
instead.

4. Case-Insensitive Equivalence

isEquivalentMPN()
uses case-insensitive comparison:

MPNUtils.isEquivalentMPN("lm358n", "LM358N");
// → true

See Also

  • /component
    - Base skill for general component work
  • /handler-pattern-design
    - Handler patterns (uses normalization for package extraction)
  • MPNUtils.java
    (lines 54-697) - Implementation details
  • MPNPackageSuffixTest.java
    - 32 comprehensive tests
  • CLAUDE.md
    - MPN Package Suffix Support section
  • HISTORY.md
    - PR #128 (MPN package suffix support)