zaphiro-technologies / cimgen

Code generation from CIM data model for several programming languages
Apache License 2.0
0 stars 0 forks source link

[Feature] Handle correctly `<cims:stereotype rdf:resource="http://iec.ch/TC57/NonStandard/UML#enumeration"/>` classes as Enum and `<cims:stereotype>CIMDatatype</cims:stereotype>` as a type annotation #9

Open chicco785 opened 1 month ago

chicco785 commented 1 month ago

Is your feature request related to a problem? Please describe the problem.

The current models get generated as follows, for example:

@dataclass
class CurrentFlow(Base):
    """
    Electrical current with sign convention: positive flow is out of the conducting equipment into the connectivity
      node. Can be both AC and DC.

    multiplier: 
    unit: 
    value: 
    """

    multiplier : Optional[str] = Field(default=None, json_schema_extra={"in_profiles":[Profile.DY, Profile.EQ, Profile.SV, Profile.SSH, ]}) 

    unit : Optional[str] = Field(default=None, json_schema_extra={"in_profiles":[Profile.DY, Profile.EQ, Profile.SV, Profile.SSH, ]}) 

    value : float = Field(default=0.0, json_schema_extra={"in_profiles":[Profile.DY, Profile.EQ, Profile.SV, Profile.SSH, ]}) 

First, CurrentFlow is dataType, so generating a class that then is never used by actual cim classes is not much of use. This should become an annotation to the field of related classes, making easier validation, serialisation, ect.

Secondly, in the RDF schema is well defined CurrentFlow unit is A, while the generated class allows for every string (not even a string in a range):

<rdf:Description rdf:about="#CurrentFlow.unit">
    <cims:stereotype rdf:resource="http://iec.ch/TC57/NonStandard/UML#attribute"/>
    <rdfs:label xml:lang="en">unit</rdfs:label>
    <rdfs:domain rdf:resource="#CurrentFlow"/>
    <rdfs:range rdf:resource="#UnitSymbol"/>
    <cims:multiplicity rdf:resource="http://iec.ch/TC57/1999/rdf-schema-extensions-19990926#M:0..1" />
    <cims:isFixed rdfs:Literal="A" />
    <rdf:type rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"/>
     </rdf:Description>

Thirdly, it does not make sense that we generate a class for UnitSymbol:

@dataclass
class UnitSymbol(Base):
    """
    The units defined for usage in the CIM.

    """

    # No attributes defined for this class.

    @cached_property
    def possible_profiles(self)->set[BaseProfile]:
        """
        A resource can be used by multiple profiles. This is the set of profiles
        where this element can be found.
        """
        return { Profile.DL, Profile.DY, Profile.EQ_BD, Profile.EQ, Profile.SV, Profile.SSH,  }

when it is an Enum with well defined values.

Describe the solution you'd like

  1. Generate UnitSymbol and other <cims:stereotype rdf:resource="http://iec.ch/TC57/NonStandard/UML#enumeration"/>

  2. Create datatypes and annotate them in models.

Additional context

Example of what could look like a generated CurrentFlow in the new way:

CurrentFlow = CIMDatatype(name="CurrentFlow", type=float, symbol=UnitSymbol.A, multiplier=UnitMultiplier.none, profiles=[Profile.SSH,Profile.EQ,Profile.SV,Profile.SC,])

...

@dataclass(config=DataclassConfig)
class CIMDatatype(Primitive):

    multiplier: UnitMultiplier = Field(frozen=True)
    symbol: UnitSymbol = Field(frozen=True)

...

class UnitSymbol(str,Enum):

    '''
    The derived units defined for usage in the CIM. In some cases, the derived unit is equal to an SI unit. Whenever possible, the standard derived symbol is used instead of the formula for the derived unit. For example, the unit symbol Farad is defined as "F" instead of "CPerV". In cases where a standard symbol does not exist for a derived unit, the formula for the unit is used as the unit symbol. For example, density does not have a standard symbol and so it is represented as "kgPerm3". With the exception of the "kg", which is an SI unit, the unit symbols do not contain multipliers and therefore represent the base derived unit to which a multiplier can be applied as a whole.  Every unit symbol is treated as an unparseable text as if it were a single-letter symbol. The meaning of each unit symbol is defined by the accompanying descriptive text and not by the text contents of the unit symbol. To allow the widest possible range of serializations without requiring special character handling, several substitutions are made which deviate from the format described in IEC 80000-1. The division symbol "/" is replaced by the letters "Per". Exponents are written in plain text after the unit as "m3" instead of being formatted as "m" with a superscript of 3  or introducing a symbol as in "m^3". The degree symbol "[SYMBOL REMOVED]" is replaced with the letters "deg". Any clarification of the meaning for a substitution is included in the description for the unit symbol. Non-SI units are included in list of unit symbols to allow sources of data to be correctly labelled with their non-SI units (for example, a GPS sensor that is reporting numbers that represent feet instead of meters). This allows software to use the unit symbol information correctly convert and scale the raw data of those sources into SI-based units.  The integer values are used for harmonization with IEC 61850.
    '''

    none = "none" #Dimension less quantity, e.g. count, per unit, etc.
    m = "m" #Length in metres.
    kg = "kg" #Mass in kilograms.  Note: multiplier &quot;k&quot; is included in this unit symbol for compatibility with IEC 61850-7-3.
    s = "s" #Time in seconds.
    A = "A" #Current in amperes.
    K = "K" #Temperature in kelvins.
    mol = "mol" #Amount of substance in moles.
    cd = "cd" #Luminous intensity in candelas.
    deg = "deg" #Plane angle in degrees.
    rad = "rad" #Plane angle in radians (m/m).
    sr = "sr" #Solid angle in steradians (m2/m2).
    Gy = "Gy" #Absorbed dose in grays (J/kg).
    Bq = "Bq" #Radioactivity in becquerels (1/s).
    degC = "degC" #Relative temperature in degrees Celsius. In the SI unit system the symbol is [SYMBOL REMOVED]C. Electric charge is measured in coulomb that has the unit symbol C. To distinguish degree Celsius from coulomb the symbol used in the UML is degC. The reason for not using [SYMBOL REMOVED]C is that the special character [SYMBOL REMOVED] is difficult to manage in software.
    Sv = "Sv" #Dose equivalent in sieverts (J/kg).
    F = "F" #Electric capacitance in farads (C/V).
    C = "C" #Electric charge in coulombs (A·s).
    S = "S" #Conductance in siemens.
    H = "H" #Electric inductance in henrys (Wb/A).
    V = "V" #Electric potential in volts (W/A).
    ohm = "ohm" #Electric resistance in ohms (V/A).
    J = "J" #Energy in joules (N·m = C·V = W·s).
    N = "N" #Force in newtons (kg·m/s²).
    Hz = "Hz" #Frequency in hertz (1/s).
    lx = "lx" #Illuminance in lux (lm/m²).
    lm = "lm" #Luminous flux in lumens (cd·sr).
    Wb = "Wb" #Magnetic flux in webers (V·s).
    T = "T" #Magnetic flux density in teslas (Wb/m2).
    W = "W" #Real power in watts (J/s). Electrical power may have real and reactive components. The real portion of electrical power (I&amp;#178;R or VIcos(phi)), is expressed in Watts. See also apparent power and reactive power.
    Pa = "Pa" #Pressure in pascals (N/m²). Note: the absolute or relative measurement of pressure is implied with this entry. See below for more explicit forms.
    m2 = "m2" #Area in square metres (m²).
    m3 = "m3" #Volume in cubic metres (m³).
    mPers = "mPers" #Velocity in metres per second (m/s).
    mPers2 = "mPers2" #Acceleration in metres per second squared (m/s²).
    m3Pers = "m3Pers" #Volumetric flow rate in cubic metres per second (m³/s).
    mPerm3 = "mPerm3" #Fuel efficiency in metres per cubic metres (m/m³).
    kgm = "kgm" #Moment of mass in kilogram metres (kg·m) (first moment of mass). Note: multiplier &quot;k&quot; is included in this unit symbol for compatibility with IEC 61850-7-3.
    kgPerm3 = "kgPerm3" #Density in kilogram/cubic metres (kg/m³). Note: multiplier &quot;k&quot; is included in this unit symbol for compatibility with IEC 61850-7-3.
    m2Pers = "m2Pers" #Viscosity in square metres / second (m²/s).
    WPermK = "WPermK" #Thermal conductivity in watt/metres kelvin.
    JPerK = "JPerK" #Heat capacity in joules/kelvin.
    ppm = "ppm" #Concentration in parts per million.
    rotPers = "rotPers" #Rotations per second (1/s). See also Hz (1/s).
    radPers = "radPers" #Angular velocity in radians per second (rad/s).
    WPerm2 = "WPerm2" #Heat flux density, irradiance, watts per square metre.
    JPerm2 = "JPerm2" #Insulation energy density, joules per square metre or watt second per square metre.
    SPerm = "SPerm" #Conductance per length (F/m).
    KPers = "KPers" #Temperature change rate in kelvins per second.
    PaPers = "PaPers" #Pressure change rate in pascals per second.
    JPerkgK = "JPerkgK" #Specific heat capacity, specific entropy, joules per kilogram Kelvin.
    VA = "VA" #Apparent power in volt amperes. See also real power and reactive power.
    VAr = "VAr" #Reactive power in volt amperes reactive. The &quot;reactive&quot; or &quot;imaginary&quot; component of electrical power (VIsin(phi)). (See also real power and apparent power). Note: Different meter designs use different methods to arrive at their results. Some meters may compute reactive power as an arithmetic value, while others compute the value vectorially. The data consumer should determine the method in use and the suitability of the measurement for the intended purpose.
    cosPhi = "cosPhi" #Power factor, dimensionless. Note 1: This definition of power factor only holds for balanced systems. See the alternative definition under code 153. Note 2 : Beware of differing sign conventions in use between the IEC and EEI. It is assumed that the data consumer understands the type of meter in use and the sign convention in use by the utility.
    Vs = "Vs" #Volt seconds (Ws/A).
    V2 = "V2" #Volt squared (W²/A²).
    As = "As" #Ampere seconds (A·s).
    A2 = "A2" #Amperes squared (A²).
    A2s = "A2s" #Ampere squared time in square amperes (A²s).
    VAh = "VAh" #Apparent energy in volt ampere hours.
    Wh = "Wh" #Real energy in watt hours.
    VArh = "VArh" #Reactive energy in volt ampere reactive hours.
    VPerHz = "VPerHz" #Magnetic flux in volt per hertz.
    HzPers = "HzPers" #Rate of change of frequency in hertz per second.
    character = "character" #Number of characters.
    charPers = "charPers" #Data rate (baud) in characters per second.
    kgm2 = "kgm2" #Moment of mass in kilogram square metres (kg·m²) (Second moment of mass, commonly called the moment of inertia). Note: multiplier &quot;k&quot; is included in this unit symbol for compatibility with IEC 61850-7-3.
    dB = "dB" #Sound pressure level in decibels. Note:  multiplier &quot;d&quot; is included in this unit symbol for compatibility with IEC 61850-7-3.
    WPers = "WPers" #Ramp rate in watts per second.
    lPers = "lPers" #Volumetric flow rate in litres per second.
    dBm = "dBm" #Power level (logarithmic ratio of signal strength , Bel-mW), normalized to 1mW. Note:  multiplier &quot;d&quot; is included in this unit symbol for compatibility with IEC 61850-7-3.
    h = "h" #Time in hours, hour = 60 min = 3600 s.
    min = "min" #Time in minutes, minute  = 60 s.
    Q = "Q" #Quantity power, Q.
    Qh = "Qh" #Quantity energy, Qh.
    ohmm = "ohmm" #Resistivity, ohm metres, (rho).
    APerm = "APerm" #A/m, magnetic field strength, amperes per metre.
    V2h = "V2h" #Volt-squared hour, volt-squared-hours.
    A2h = "A2h" #Ampere-squared hour, ampere-squared hour.
    Ah = "Ah" #Ampere-hours, ampere-hours.
    count = "count" #Amount of substance, Counter value.
    ft3 = "ft3" #Volume, cubic feet.
    m3Perh = "m3Perh" #Volumetric flow rate, cubic metres per hour.
    gal = "gal" #Volume in gallons, US gallon (1 gal = 231 in3 = 128 fl ounce).
    Btu = "Btu" #Energy, British Thermal Units.
    l = "l" #Volume in litres, litre = dm3 = m3/1000.
    lPerh = "lPerh" #Volumetric flow rate, litres per hour.
    lPerl = "lPerl" #Concentration, The ratio of the volume of a solute divided by the volume of  the solution. Note: Users may need use a prefix such a ‘µ' to express a quantity such as ‘µL/L'.
    gPerg = "gPerg" #Concentration, The ratio of the mass of a solute divided by the mass of  the solution. Note: Users may need use a prefix such a ‘µ' to express a quantity such as ‘µg/g'.
    molPerm3 = "molPerm3" #Concentration, The amount of substance concentration, (c), the amount of solvent in moles divided by the volume of solution in m³.
    molPermol = "molPermol" #Concentration, Molar fraction, the ratio of the molar amount of a solute divided by the molar amount of the solution.
    molPerkg = "molPerkg" #Concentration, Molality, the amount of solute in moles and the amount of solvent in kilograms.
    sPers = "sPers" #Time, Ratio of time.  Note: Users may need to supply a prefix such as ‘&amp;#181;' to show rates such as ‘&amp;#181;s/s'.
    HzPerHz = "HzPerHz" #Frequency, rate of frequency change.   Note: Users may need to supply a prefix such as ‘m' to show rates such as ‘mHz/Hz'.
    VPerV = "VPerV" #Voltage, ratio of voltages.  Note: Users may need to supply a prefix such as ‘m' to show rates such as ‘mV/V'.
    APerA = "APerA" #Current, ratio of amperages.   Note: Users may need to supply a prefix such as ‘m' to show rates such as ‘mA/A'.
    VPerVA = "VPerVA" #Power factor, PF, the ratio of the active power to the apparent power.  Note: The sign convention used for power factor will differ between IEC meters and EEI (ANSI) meters. It is assumed that the data consumers understand the type of meter being used and agree on the sign convention in use at any given utility.
    rev = "rev" #Amount of rotation, revolutions.
    kat = "kat" #Catalytic activity, katal = mol / s.
    JPerkg = "JPerkg" #Specific energy, Joules / kg.
    m3Uncompensated = "m3Uncompensated" #Volume, cubic metres, with the value uncompensated for weather effects.
    m3Compensated = "m3Compensated" #Volume, cubic metres, with the value compensated for weather effects.
    WPerW = "WPerW" #Signal Strength, ratio of power.   Note: Users may need to supply a prefix such as ‘m' to show rates such as ‘mW/W'.
    therm = "therm" #Energy, therms.
    onePerm = "onePerm" #Wavenumber, reciprocal metres,  (1/m).
    m3Perkg = "m3Perkg" #Specific volume, cubic metres per kilogram, v.
    Pas = "Pas" #Dynamic viscosity, pascal seconds.
    Nm = "Nm" #Moment of force, newton metres.
    NPerm = "NPerm" #Surface tension, newton per metre.
    radPers2 = "radPers2" #Angular acceleration, radians per second squared.
    JPerm3 = "JPerm3" #Energy density, joules per cubic metre.
    VPerm = "VPerm" #Electric field strength, volts per metre.
    CPerm3 = "CPerm3" #Electric charge density, coulombs per cubic metre.
    CPerm2 = "CPerm2" #Surface charge density, coulombs per square metre.
    FPerm = "FPerm" #Permittivity, farads per metre.
    HPerm = "HPerm" #Permeability, henrys per metre.
    JPermol = "JPermol" #Molar energy, joules per mole.
    JPermolK = "JPermolK" #Molar entropy, molar heat capacity, joules per mole kelvin.
    CPerkg = "CPerkg" #Exposure (x rays), coulombs per kilogram.
    GyPers = "GyPers" #Absorbed dose rate, grays per second.
    WPersr = "WPersr" #Radiant intensity, watts per steradian.
    WPerm2sr = "WPerm2sr" #Radiance, watts per square metre steradian.
    katPerm3 = "katPerm3" #Catalytic activity concentration, katals per cubic metre.
    d = "d" #Time in days, day = 24 h = 86400 s.
    anglemin = "anglemin" #Plane angle, minutes.
    anglesec = "anglesec" #Plane angle, seconds.
    ha = "ha" #Area, hectares.
    tonne = "tonne" #Mass in tons, &quot;tonne&quot; or &quot;metric  ton&quot; (1000 kg = 1 Mg).
    bar = "bar" #Pressure in bars, (1 bar = 100 kPa).
    mmHg = "mmHg" #Pressure, millimetres of mercury (1 mmHg is approximately 133.3 Pa).
    M = "M" #Length, nautical miles (1 M = 1852 m).
    kn = "kn" #Speed, knots (1 kn = 1852/3600) m/s.
    Mx = "Mx" #Magnetic flux, maxwells (1 Mx = 10-8 Wb).
    G = "G" #Magnetic flux density, gausses (1 G = 10-4 T).
    Oe = "Oe" #Magnetic field in oersteds, (1 Oe = (103/4p) A/m).
    Vh = "Vh" #Volt-hour, Volt hours.
    WPerA = "WPerA" #Active power per current flow, watts per Ampere.
    onePerHz = "onePerHz" #Reciprocal of frequency (1/Hz).
    VPerVAr = "VPerVAr" #Power factor, PF, the ratio of the active power to the apparent power. Note: The sign convention used for power factor will differ between IEC meters and EEI (ANSI) meters. It is assumed that the data consumers understand the type of meter being used and agree on the sign convention in use at any given utility.
    ohmPerm = "ohmPerm" #Electric resistance per length in ohms per metre ((V/A)/m).
    kgPerJ = "kgPerJ" #Weight per energy in kilograms per joule (kg/J). Note: multiplier &quot;k&quot; is included in this unit symbol for compatibility with IEC 61850-7-3.
    JPers = "JPers" #Energy rate in joules per second (J/s).
chicco785 commented 1 month ago

cf https://github.com/zaphiro-technologies/cimgen/tree/relationship-support

HUG0-D commented 1 month ago

@chicco785 I have a question: What are the advantages of using the CIMdatatype (eg. CurrentFlow) as an annotation (option A) rather than using the class that is currently defined as a true data type (option B)?

Option A:

CurrentFlow = CIMDatatype(name="CurrentFlow", type=float, symbol=UnitSymbol.A, multiplier=UnitMultiplier.none, profiles=[Profile.EQ,Profile.SC,Profile.SSH,Profile.SV,])

@dataclass
class CurrentLimit(OperationalLimit):
    """
    Operational limit on current.

    normalValue: The normal value for limit on current flow. The attribute shall be a positive value or zero.
    value: Limit on current flow. The attribute shall be a positive value or zero.
    """

    normalValue : float = Field(default=0.0, data_type = CurrentFlow, json_schema_extra={"in_profiles":[Profile.EQ, ]}) 

    value : float = Field(default=0.0, data_type = CurrentFlow, json_schema_extra={"in_profiles":[Profile.SSH, ]}) 

    @cached_property
    def possible_profiles(self)->set[BaseProfile]:
        """
        A resource can be used by multiple profiles. This is the set of profiles
        where this element can be found.
        """
        return { Profile.EQ, Profile.SSH,  }

Option B

@dataclass
class CurrentFlow(Base):
    """
    Electrical current with sign convention: positive flow is out of the conducting equipment into the connectivity
      node. Can be both AC and DC.

    multiplier: 
    unit: 
    value: 
    """

    multiplier : Optional[str] = Field(default=None, json_schema_extra={"in_profiles":[Profile.DY, Profile.EQ, Profile.SV, Profile.SSH, ]}) 

    unit : Optional[str] = Field(default=None, json_schema_extra={"in_profiles":[Profile.DY, Profile.EQ, Profile.SV, Profile.SSH, ]}) 

    value : float = Field(default=0.0, json_schema_extra={"in_profiles":[Profile.DY, Profile.EQ, Profile.SV, Profile.SSH, ]}) 

@dataclass
class CurrentLimit(OperationalLimit):
    """
    Operational limit on current.

    normalValue: The normal value for limit on current flow. The attribute shall be a positive value or zero.
    value: Limit on current flow. The attribute shall be a positive value or zero.
    """

    normalValue : CurrentFlow= Field(default=0.0, json_schema_extra={"in_profiles":[Profile.EQ, ]}) 

    value : CurrentFlow= Field(default=0.0, json_schema_extra={"in_profiles":[Profile.SSH, ]}) 

    @cached_property
    def possible_profiles(self)->set[BaseProfile]:
        """
        A resource can be used by multiple profiles. This is the set of profiles
        where this element can be found.
        """
        return { Profile.EQ, Profile.SSH,  }

To me option B seems to be a more "logical" use of a datatype but I am probably missing something :)

chicco785 commented 1 month ago

what i was proposing is A, mostly because a datatype is static, i.e. the assigned unit symbol or multiplier do not change. i.e. in the end this is a specialisation of Float, string or something else.

while also in B you could make the unit and the multiplier, "static" i would be highly redundant, and probably make serialisation more complex (you may end up with a lot of chinese boxes)

chicco785 commented 1 month ago

ps: "data_type" = CurrentFlow should go in json_schema_extra otherwise it don't think it will work for Pydantic