DEV Community

electricalsteel
electricalsteel

Posted on AI-assisted

Parsing Electrical Steel Grade Codes in Python: 50A310, M310-50A and 50W310 Are the Same Steel

I work on the supply side of electrical steel, the thin silicon steel that motor and transformer cores are stamped from. A lot of my week goes to one dull problem: the same material shows up under three different names.

A buyer in Vietnam asks for 50A310. The motor drawing from Germany says M310-50A. The mill certificate from China says 50W310. All three are the same steel: 0.50 mm thick, guaranteed core loss of no more than 3.10 W/kg at 1.5 T and 50 Hz.

If you build software for procurement, ERP, BOMs, or quality systems in manufacturing, you will run into this. Here is a small Python parser that handles it, plus a short calculation that tells you how much strip is actually in a coil.

The three naming schemes

For non-oriented electrical steel (the kind used in motors), the three common standards encode the same two numbers in different orders:

Standard Example Pattern
JIS C 2552 (Japan) 50A310 thickness×100, A, loss×100
GB/T 2521.1 (China) 50W310 thickness×100, W, loss×100
EN 10106 / IEC (Europe) M310-50A M, loss×100, -, thickness×100, A

Two things worth noticing:

  1. The loss number is a ceiling, not a measurement. 310 means the core loss is guaranteed to be at most 3.10 W/kg. The actual coil may test lower, and the real value is printed on the mill certificate.
  2. The test point is part of the meaning. For non-oriented grades, the loss is measured at 1.5 T / 50 Hz. Grain-oriented (transformer) grades are measured at 1.7 T and use different letters, so a parser for motor steel should reject them instead of guessing.

The parser

import re
from dataclasses import dataclass


@dataclass(frozen=True)
class Grade:
    thickness_mm: float  # nominal thickness
    max_loss: float      # max core loss, W/kg at 1.5 T / 50 Hz

    def as_jis(self):
        return f"{self.t100}A{self.loss100}"

    def as_en(self):
        return f"M{self.loss100}-{self.t100}A"

    def as_gb(self):
        return f"{self.t100}W{self.loss100}"

    @property
    def t100(self):
        return round(self.thickness_mm * 100)

    @property
    def loss100(self):
        return round(self.max_loss * 100)


PATTERNS = {
    "JIS C 2552":  re.compile(r"^(?P<t>\d{2})A(?P<loss>\d{3,4})$"),
    "GB/T 2521.1": re.compile(r"^(?P<t>\d{2})W(?P<loss>\d{3,4})$"),
    "EN 10106":    re.compile(r"^M(?P<loss>\d{3,4})-(?P<t>\d{2})A$"),
}


def parse(code):
    code = code.strip().upper().replace(" ", "")
    for standard, pattern in PATTERNS.items():
        m = pattern.match(code)
        if m:
            grade = Grade(int(m["t"]) / 100, int(m["loss"]) / 100)
            return standard, grade
    raise ValueError(f"Not a non-oriented grade code I know: {code!r}")
Enter fullscreen mode Exit fullscreen mode

The named groups t and loss keep the EN pattern readable even though it puts the numbers in the opposite order. Loss gets \d{3,4} because the high-loss grades go past 10 W/kg, for example M1000-65A.

Running it:

for code in ["50A310", "M310-50A", "50w310", "35A250", "M800-65A"]:
    standard, g = parse(code)
    print(f"{code:>9}  {standard:<12} {g.thickness_mm:.2f} mm  "
          f"<= {g.max_loss:.2f} W/kg  ->  "
          f"{g.as_jis()} / {g.as_en()} / {g.as_gb()}")
Enter fullscreen mode Exit fullscreen mode
   50A310  JIS C 2552   0.50 mm  <= 3.10 W/kg  ->  50A310 / M310-50A / 50W310
 M310-50A  EN 10106     0.50 mm  <= 3.10 W/kg  ->  50A310 / M310-50A / 50W310
   50w310  GB/T 2521.1  0.50 mm  <= 3.10 W/kg  ->  50A310 / M310-50A / 50W310
   35A250  JIS C 2552   0.35 mm  <= 2.50 W/kg  ->  35A250 / M250-35A / 35W250
 M800-65A  EN 10106     0.65 mm  <= 8.00 W/kg  ->  65A800 / M800-65A / 65W800
Enter fullscreen mode Exit fullscreen mode

Because Grade is a frozen dataclass, equality works out of the box. That is the useful part for deduplicating a parts master:

>>> parse("50A310")[1] == parse("M310-50A")[1] == parse("50W310")[1]
True
Enter fullscreen mode Exit fullscreen mode

And a grain-oriented code fails loudly instead of being misread:

>>> parse("30Q120")
ValueError: Not a non-oriented grade code I know: '30Q120'
Enter fullscreen mode Exit fullscreen mode

What this parser does not do

  • It does not prove the grade exists. The converter will happily turn a JIS code into an EN-style string, but not every thickness/loss pair is listed in every standard. Check the target standard's table (or a grade equivalents chart) before you print the result on a purchase order.
  • It ignores mill brand codes. Steelmakers sell the same material under their own catalogue codes, like B50A310 or 35JN210. Those are not standard designations. Map them in a lookup table, not with a regex.
  • It does not handle grain-oriented grades. They are measured at 1.7 T and use different letters in each standard. That deserves its own pattern set and its own Grade type, so the two can never be compared by accident.

Bonus: how much strip is in that coil?

Steel is bought by weight, but a stamping press consumes it by length. Converting between the two is plain geometry:

def strip_length_m(coil_kg, thickness_mm, width_mm, density=7.65):
    """Metres of strip in a coil. density in g/cm3."""
    kg_per_m = thickness_mm * width_mm * density / 1000
    return coil_kg / kg_per_m
Enter fullscreen mode Exit fullscreen mode

The units cancel neatly: mm × mm × g/cm³ ÷ 1000 gives kg per metre. For a real order we shipped recently (a 4.8-ton coil of 0.50 mm strip slit to 113 mm):

>>> length = strip_length_m(4800, 0.50, 113)
>>> f"{length:,.0f} m"
'11,105 m'
Enter fullscreen mode Exit fullscreen mode

That is about 11 km of strip on one coil, or roughly 2.3 km per ton.

Slit silicon steel strip coils wrapped in blue film, lined up on a container floor

Slit coils from that order, loaded in the container.

That number changes how you think about quality data. A burr height or camber check is done on a sample a few metres long, but the press runs 11 km of strip. If your QA software stores one measurement per coil, it is storing one point from an 11 km line.

The default density of 7.65 g/cm³ is a reasonable figure for silicon steel. Lower-loss grades contain more silicon and are slightly lighter, so if you need precision, take the density from the supplier's datasheet for the exact grade.

Wrapping up

  • Normalize grade codes into a value object (thickness, max loss) as early as possible, and compare those, not strings.
  • Keep non-oriented and grain-oriented grades in separate types. They are tested at different conditions, and comparing them is meaningless.
  • Leave brand codes to a lookup table.
  • When a system tracks steel by weight, store the width and thickness too, so you can always derive length.

If you have dealt with other messy industrial naming schemes (bearing codes, fastener specs, wire gauges), I would like to hear how you modelled them in the comments.


Disclosure: I work at Zhongxin Steel, a Chinese supplier of silicon steel, and the coil example comes from one of our orders. The code above is free to use.

Top comments (0)