Beispiel #1
0
def blend2list(blend_dict):
    try:
        if blend_dict and "label" in blend_dict and "ingredients" in blend_dict and len(blend_dict["ingredients"]) > 1:
            return [encodeLocal(blend_dict["label"]), [ [encodeLocal(i["coffee"]), i["ratio"]] for i in blend_dict["ingredients"]] ]
        else:
            return None
    except:
        return None
Beispiel #2
0
def extractProfileCropsterXLS(file):
    res = {}  # the interpreted data set

    book = xlrd.open_workbook(file)

    sheet_names = book.sheet_names()

    # extract general profile information
    general_sh = book.sheet_by_index(0)
    if general_sh.nrows >= 1:
        general_data = dict(
            zip([x.value for x in general_sh.row(0)], general_sh.row(1)))

        if 'Id-Tag' in general_data:
            try:
                id_tag = general_data['Id-Tag'].value
                batch_prefix = id_tag.rstrip('0123456789')
                batch_number = int(id_tag[len(batch_prefix):])
                res["roastbatchprefix"] = batch_prefix
                res["roastbatchnr"] = batch_number
            except:
                pass
        for tag, label in zip([
                'Profile', 'Lot name', 'Machine', 'Roast technician',
                'Sensorial notes', 'Roasting notes'
        ], [
                'title', 'beans', 'roastertype', 'operator', 'cuppingnotes',
                'roastingnotes'
        ]):
            if tag in general_data:
                try:
                    value = str(general_data[tag].value)
                    res[label] = value
                except:
                    pass
        if 'Date' in general_data:
            try:
                raw_date = general_data['Date'].value
                date_tuple = xlrd.xldate_as_tuple(raw_date, book.datemode)
                date = QDateTime(*date_tuple)
                res["roastdate"] = encodeLocal(date.date().toString())
                res["roastisodate"] = encodeLocal(date.date().toString(
                    Qt.ISODate))
                res["roasttime"] = encodeLocal(date.time().toString())
                res["roastepoch"] = int(date.toTime_t())
                res["roasttzoffset"] = libtime.timezone
            except:
                pass
        if 'Ambient temp.' in general_data:
            try:
                value = general_data['Ambient temp.'].value
                res['ambientTemp'] = float(value)
            except:
                pass
        if 'Start weight' in general_data or 'End weight' in general_data:
            cropster_weight_units = ["G", "KG", "LBS", "OZ"]
            artisan_weight_units = ["g", "Kg", "lb", "oz"]
            weight = [0, 0, artisan_weight_units[0]]
            try:
                value = general_data['End weight unit'].value
                idx = cropster_weight_units.index(value)
                weight[2] = artisan_weight_units[idx]
            except:
                pass
            try:
                value = general_data['Start weight unit'].value
                idx = cropster_weight_units.index(value)
                weight[2] = artisan_weight_units[idx]
            except:
                pass
            try:
                value = general_data['Start weight'].value
                weight[0] = value
            except:
                pass
            try:
                value = general_data['End weight'].value
                weight[1] = value
            except:
                pass
            res["weight"] = weight
    # BT:
    try:
        BT_idx = sheet_names.index("Curve - Bean temp.")
        BT_sh = book.sheet_by_index(BT_idx)
        if BT_sh.ncols >= 1:
            time = BT_sh.col(0)
            temp = BT_sh.col(1)
            if len(time) > 0 and len(temp) > 0 and len(time) == len(temp):
                if "FAHRENHEIT" in str(temp[0].value):
                    res["mode"] = 'F'
                else:
                    res["mode"] = 'C'
                res["timex"] = [t.value for t in time[1:]]
                res["temp2"] = [t.value for t in temp[1:]]
                res["temp1"] = [-1] * len(res["timex"])
                res["timeindex"] = [0, 0, 0, 0, 0, 0, len(res["timex"]) - 1, 0]
    except:
        pass
    # ET
    try:
        ET_idx = sheet_names.index("Curve - Env. temp.")
        ET_sh = book.sheet_by_index(ET_idx)
        if ET_sh.ncols >= 1:
            time = ET_sh.col(0)
            temp = ET_sh.col(1)
            if len(time) > 0 and len(temp) > 0 and len(time) == len(temp):
                if "FAHRENHEIT" in str(temp[0].value):
                    res["mode"] = 'F'
                else:
                    res["mode"] = 'C'
                res["temp1"] = [t.value for t in temp[1:]]
                if len(res["timex"]) != len(res["temp1"]):
                    res["timex"] = [t.value for t in time[1:]]
                if "temp2" not in res or len(res["temp2"]) != len(
                        res["timex"]):
                    res["temp2"] = [-1] * len(res["timex"])
                res["timeindex"] = [0, 0, 0, 0, 0, 0, len(res["timex"]) - 1, 0]
    except:
        pass
    # extra temperature curves
    channel = 1  # toggle between channel 1 and 2 to be filled with extra temperature curve data
    for sn in sheet_names:
        if sn.startswith(
                "Curve"
        ) and "temp." in sn and sn != "Curve - Bean temp." and sn != "Curve - Env. temp.":
            try:
                extra_curve_name = sn.split("Curve - ")
                if len(extra_curve_name) > 1:
                    extra_curve_name = extra_curve_name[1].split("temp.")
                    extra_curve_name = extra_curve_name[0]
                else:
                    extra_curve_name = extra_curve_name[0]
                CT_idx = sheet_names.index(sn)
                CT_sh = book.sheet_by_index(CT_idx)
                if CT_sh.ncols >= 1:
                    time = CT_sh.col(0)
                    temp = CT_sh.col(1)
                    if len(time) > 0 and len(temp) > 0 and len(time) == len(
                            temp):
                        if "extradevices" not in res:
                            res["extradevices"] = []
                        if "extraname1" not in res:
                            res["extraname1"] = []
                        if "extraname2" not in res:
                            res["extraname2"] = []
                        if "extratimex" not in res:
                            res["extratimex"] = []
                        if "extratemp1" not in res:
                            res["extratemp1"] = []
                        if "extratemp2" not in res:
                            res["extratemp2"] = []
                        if "extramathexpression1" not in res:
                            res["extramathexpression1"] = []
                        if "extramathexpression2" not in res:
                            res["extramathexpression2"] = []
                        if channel == 1:
                            channel = 2
                            res["extradevices"].append(25)
                            res["extraname1"].append(extra_curve_name)
                            res["extratimex"].append(
                                [t.value for t in time[1:]])
                            res["extratemp1"].append(
                                [t.value for t in temp[1:]])
                            res["extramathexpression1"].append("")
                        elif (len(time) - 1) == len(
                                res["extratimex"][-1]
                        ):  # only if time lengths is same as of channel 1
                            channel = 1
                            res["extraname2"].append(extra_curve_name)
                            res["extratemp2"].append(
                                [t.value for t in temp[1:]])
                            res["extramathexpression2"].append("")
            except:
                pass
    if "extraname1" in res and "extraname2" in res and len(
            res["extraname1"]) != len(res["extraname2"]):
        # we add an empty second extra channel if needed
        res["extraname2"].append("Extra 2")
        res["extratemp2"].append([-1] * len(res["extratemp1"][-1]))
        res["extramathexpression2"].append("")
    # add events
    try:
        COMMENTS_idx = sheet_names.index("Comments")
        COMMENTS_sh = book.sheet_by_index(COMMENTS_idx)
        gas_event = False  # set to True if a Gas event exists
        airflow_event = False  # set to True if an Airflow event exists
        specialevents = []
        specialeventstype = []
        specialeventsvalue = []
        specialeventsStrings = []
        if COMMENTS_sh.ncols >= 4:
            takeClosest = lambda num, collection: min(
                collection, key=lambda x: abs(x - num))
            for r in range(COMMENTS_sh.nrows):
                if r > 0:
                    try:
                        time = COMMENTS_sh.cell(r, 0).value
                        comment_type = COMMENTS_sh.cell(r, 2).value
                        if comment_type not in [
                                "Turning point"
                        ]:  # TP is ignored as it is automatically assigned
                            comment_value = COMMENTS_sh.cell(r, 3).value
                            c = takeClosest(time, res["timex"])
                            timex_idx = res["timex"].index(c)
                            if comment_type == "Color change":
                                res["timeindex"][1] = timex_idx
                            elif comment_type == "First crack":
                                res["timeindex"][2] = timex_idx
                            elif comment_type == "Second crack":
                                res["timeindex"][4] = timex_idx
                            else:
                                specialevents.append(timex_idx)
                                if comment_type == "Airflow":
                                    airflow_event = True
                                    specialeventstype.append(0)
                                elif comment_type == "Gas":
                                    gas_event = True
                                    specialeventstype.append(3)
                                else:
                                    specialeventstype.append(4)
                                try:
                                    v = float(comment_value)
                                    v = v / 10. + 1
                                    specialeventsvalue.append(v)
                                except:
                                    specialeventsvalue.append(0)
                                if comment_type not in [
                                        "Airflow", "Gas", "Comment"
                                ]:
                                    specialeventsStrings.append(comment_type)
                                else:
                                    specialeventsStrings.append(comment_value)
                    except:
                        pass
        if len(specialevents) > 0:
            res["specialevents"] = specialevents
            res["specialeventstype"] = specialeventstype
            res["specialeventsvalue"] = specialeventsvalue
            res["specialeventsStrings"] = specialeventsStrings
            if gas_event or airflow_event:
                # first set etypes to defaults
                res["etypes"] = [
                    QApplication.translate("ComboBox", "Air", None),
                    QApplication.translate("ComboBox", "Drum", None),
                    QApplication.translate("ComboBox", "Damper", None),
                    QApplication.translate("ComboBox", "Burner", None), "--"
                ]
                # update
                if airflow_event:
                    res["etypes"][0] = "Airflow"
                if gas_event:
                    res["etypes"][3] = "Gas"
    except:
        pass
    return res
Beispiel #3
0
def extractProfileRoastPathHTML(url):
    res = {}  # the interpreted data set
    try:
        s = requests.Session()
        s.mount('file://', FileAdapter())
        page = s.get(url.toString())
        tree = html.fromstring(page.content)

        title = ""
        date = tree.xpath(
            '//div[contains(@class, "roast-top")]/*/*[local-name() = "h5"]/text()'
        )
        if date and len(date) > 0:
            date_str = date[0].strip().split()
            if len(date_str) > 2:
                dateQt = QDateTime.fromString(date_str[-2] + date_str[-1],
                                              "yyyy-MM-ddhh:mm")
                if dateQt.isValid():
                    res["roastdate"] = encodeLocal(dateQt.date().toString())
                    res["roastisodate"] = encodeLocal(dateQt.date().toString(
                        Qt.ISODate))
                    res["roasttime"] = encodeLocal(dateQt.time().toString())
                    res["roastepoch"] = int(dateQt.toTime_t())
                    res["roasttzoffset"] = libtime.timezone
                title = "".join(date_str[:-2]).strip()
                if len(title
                       ) > 0 and title[-1] == "-":  # we remove a trailing -
                    title = title[:-1].strip()
            elif len(date_str) > 0:
                title = "".join(date_str)

        coffee = ""
        for m in [
                "Roasted By", "Coffee", "Batch Size", "Notes", "Organization"
        ]:
            s = '//*[@class="ml-2" and normalize-space(text())="Details"]/following::table[1]/tbody/tr[*]/td/*[normalize-space(text())="{}"]/following::td[1]/text()'.format(
                m)
            value = tree.xpath(s)
            if len(value) > 0:
                meta = value[0].strip()
                try:
                    if m == "Roasted By":
                        res["operator"] = meta
                    elif m == "Coffee":
                        coffee = meta
                        if title == "" or title == "Roast":
                            res["title"] = coffee
                    elif m == "Notes":
                        res["roastingnotes"] = meta
                    elif m == "Batch Size":
                        v, u = meta.split()
                        res["weight"] = [
                            float(v), 0, ("Kg" if u.strip() == "kg" else "lb")
                        ]
                    elif m == "Organization":
                        res["organization"] = meta
                except:
                    pass

        beans = ""
        for m in [
                "Nickname", "Country", "Region", "Farm", "Varietal", "Process"
        ]:
            s = '//*[@class="ml-2" and normalize-space(text())="Greens"]/following::table[1]/tbody/tr/td[count(//table/thead/tr/th[normalize-space(text())="{}"]/preceding-sibling::th)+1]/text()'.format(
                m)
            value = tree.xpath(s)
            if len(value) > 0:
                meta = value[0].strip()
                if meta != "":
                    if beans == "":
                        beans = meta
                    else:
                        beans += ", " + meta
        if coffee != "" and title in res:  # we did not use the "coffee" as title
            if beans == "":
                beans = coffee
            else:
                beans = coffee + "\n" + beans
        if beans != "":
            res["beans"] = beans
        if not "title" in res and title != "":
            res["title"] = title

        data = {}
        for e in [
                "btData", "etData", "atData", "eventData", "rorData",
                "noteData"
        ]:
            d = re.findall("var {} = JSON\.parse\('(.+?)'\);".format(e),
                           page.content.decode("utf-8"), re.S)
            bt = []
            if d:
                data[e] = json.loads(d[0])

        if "btData" in data and len(
                data["btData"]) > 0 and "Timestamp" in data["btData"][0]:
            # BT
            bt = data["btData"]
            baseTime = dateutil.parser.parse(bt[0]["Timestamp"]).timestamp()
            res["mode"] = 'C'
            res["timex"] = [
                dateutil.parser.parse(d["Timestamp"]).timestamp() - baseTime
                for d in bt
            ]
            res["temp2"] = [d["StandardValue"] for d in bt]

            # ET
            if "etData" in data and len(data["btData"]) == len(data["etData"]):
                et = data["etData"]
                res["temp1"] = [d["StandardValue"] for d in et]
            else:
                res["temp1"] = [-1] * len(res["temp2"])

            # Events
            timeindex = [-1, 0, 0, 0, 0, 0, 0, 0]
            if "eventData" in data:
                marks = {
                    "Charge": 0,
                    "Green > Yellow": 1,
                    "First Crack": 2,
                    "Second Crack": 4,
                    "Drop": 6
                }
                for d in data["eventData"]:
                    if "EventName" in d and d[
                            "EventName"] in marks and "Timestamp" in d:
                        tx = dateutil.parser.parse(
                            d["Timestamp"]).timestamp() - baseTime
                        try:
                            #                            tx_idx = res["timex"].index(tx) # does not cope with dropouts as the next line:
                            tx_idx = next(
                                i for i, item in enumerate(res["timex"])
                                if item >= tx)
                            timeindex[marks[d["EventName"]]] = tx_idx
                        except:
                            pass
            res["timeindex"] = timeindex

            # Notes
            if "noteData" in data:
                specialevents = []
                specialeventstype = []
                specialeventsvalue = []
                specialeventsStrings = []
                noteData = data["noteData"]
                for n in noteData:
                    if "Timestamp" in n and "NoteTypeId" in n and "Note" in n:
                        c = dateutil.parser.parse(
                            n["Timestamp"]).timestamp() - baseTime
                        try:
                            timex_idx = res["timex"].index(c)
                            specialevents.append(timex_idx)
                            note_type = n["NoteTypeId"]
                            if note_type == 0:  # Fuel
                                specialeventstype.append(3)
                            elif note_type == 1:  # Fan
                                specialeventstype.append(0)
                            elif note_type == 2:  # Drum
                                specialeventstype.append(1)
                            else:  # n == 3: # Notes
                                specialeventstype.append(4)
                            try:
                                v = float(n["Note"])
                                v = v / 10. + 1
                                specialeventsvalue.append(v)
                            except:
                                specialeventsvalue.append(0)
                            specialeventsStrings.append(n["Note"])
                        except:
                            pass
                if len(specialevents) > 0:
                    res["specialevents"] = specialevents
                    res["specialeventstype"] = specialeventstype
                    res["specialeventsvalue"] = specialeventsvalue
                    res["specialeventsStrings"] = specialeventsStrings

            if "atData" in data or "rorData" in data:
                res["extradevices"] = []
                res["extratimex"] = []
                res["extratemp1"] = []
                res["extratemp2"] = []
                res["extraname1"] = []
                res["extraname2"] = []
                res["extramathexpression1"] = []
                res["extramathexpression2"] = []
                res["extraLCDvisibility1"] = []
                res["extraLCDvisibility2"] = []
                res["extraCurveVisibility1"] = []
                res["extraCurveVisibility2"] = []
                res["extraDelta1"] = []
                res["extraDelta2"] = []
                res["extraFill1"] = []
                res["extraFill2"] = []
                res["extradevicecolor1"] = []
                res["extradevicecolor2"] = []
                res["extramarkersizes1"] = []
                res["extramarkersizes2"] = []
                res["extramarkers1"] = []
                res["extramarkers2"] = []
                res["extralinewidths1"] = []
                res["extralinewidths2"] = []
                res["extralinestyles1"] = []
                res["extralinestyles2"] = []
                res["extradrawstyles1"] = []
                res["extradrawstyles2"] = []

                # AT
                if "atData" in data:
                    res["extradevices"].append(25)
                    res["extraname1"].append("AT")
                    res["extraname2"].append("")
                    res["extramathexpression1"].append("")
                    res["extramathexpression2"].append("")
                    res["extraLCDvisibility1"].append(True)
                    res["extraLCDvisibility2"].append(False)
                    res["extraCurveVisibility1"].append(True)
                    res["extraCurveVisibility2"].append(False)
                    res["extraDelta1"].append(False)
                    res["extraDelta2"].append(False)
                    res["extraFill1"].append(False)
                    res["extraFill2"].append(False)
                    res["extradevicecolor1"].append('black')
                    res["extradevicecolor2"].append('black')
                    res["extramarkersizes1"].append(6.0)
                    res["extramarkersizes2"].append(6.0)
                    res["extramarkers1"].append(None)
                    res["extramarkers2"].append(None)
                    res["extralinewidths1"].append(1.0)
                    res["extralinewidths2"].append(1.0)
                    res["extralinestyles1"].append('-')
                    res["extralinestyles2"].append('-')
                    res["extradrawstyles1"].append('default')
                    res["extradrawstyles2"].append('default')
                    at = data["atData"]
                    timex = [
                        dateutil.parser.parse(d["Timestamp"]).timestamp() -
                        baseTime for d in at
                    ]
                    res["extratimex"].append(timex)
                    res["extratemp1"].append([d["StandardValue"] for d in at])
                    res["extratemp2"].append([-1] * len(timex))

                # BT RoR
                if "rorData" in data:
                    res["extradevices"].append(25)
                    res["extraname1"].append("RoR")
                    res["extraname2"].append("")
                    res["extramathexpression1"].append("")
                    res["extramathexpression2"].append("")
                    res["extraLCDvisibility1"].append(True)
                    res["extraLCDvisibility2"].append(False)
                    res["extraCurveVisibility1"].append(False)
                    res["extraCurveVisibility2"].append(False)
                    res["extraDelta1"].append(True)
                    res["extraDelta2"].append(False)
                    res["extraFill1"].append(False)
                    res["extraFill2"].append(False)
                    res["extradevicecolor1"].append('black')
                    res["extradevicecolor2"].append('black')
                    res["extramarkersizes1"].append(6.0)
                    res["extramarkersizes2"].append(6.0)
                    res["extramarkers1"].append(None)
                    res["extramarkers2"].append(None)
                    res["extralinewidths1"].append(1.0)
                    res["extralinewidths2"].append(1.0)
                    res["extralinestyles1"].append('-')
                    res["extralinestyles2"].append('-')
                    res["extradrawstyles1"].append('default')
                    res["extradrawstyles2"].append('default')
                    ror = data["rorData"]
                    timex = [
                        dateutil.parser.parse(d["Timestamp"]).timestamp() -
                        baseTime for d in ror
                    ]
                    res["extratimex"].append(timex)
                    res["extratemp1"].append([d["StandardValue"] for d in ror])
                    res["extratemp2"].append([-1] * len(timex))

    except Exception as e:
        #        import traceback
        #        import sys
        #        traceback.print_exc(file=sys.stdout)
        pass
    return res
Beispiel #4
0
def extractProfileRoastLog(url, _):
    res = {}  # the interpreted data set
    try:
        s = requests.Session()
        s.mount('file://', FileAdapter())
        page = s.get(url.toString(),
                     timeout=(4, 15),
                     headers={"Accept-Encoding": "gzip"})
        tree = html.fromstring(page.content)

        title = ""
        title_elements = tree.xpath('//h2[contains(@id,"page-title")]/text()')
        if len(title_elements) > 0:
            title = title_elements[0].strip()

        tag_values = {}
        for tag in [
                "Roastable:", "Starting mass:", "Ending mass:", "Roasted on:",
                "Roasted by:", "Roaster:", "Roast level:", "Roast Notes:"
        ]:
            tag_elements = tree.xpath(
                '//td[contains(@class,"text-rt") and normalize-space(text())="{}"]/following::td[1]/text()'
                .format(tag))
            if len(tag_elements) > 0:
                tag_values[tag] = "\n".join([e.strip() for e in tag_elements])
        # {'Roastable:': '2003000 Diablo FTO BULK', 'Starting mass:': '140.00 lb', 'Ending mass:': '116.80 lb', 'Roasted on:': 'Thu, Jun 6th, 2019 11:11 PM', 'Roasted by:': '*****@*****.**', 'Roaster:': 'Diedrich CR-70'}

        if "Roasted on:" in tag_values:
            try:
                dt = dateutil.parser.parse(tag_values["Roasted on:"])
                dateQt = QDateTime.fromTime_t(int(round(dt.timestamp())))
                if dateQt.isValid():
                    res["roastdate"] = encodeLocal(dateQt.date().toString())
                    res["roastisodate"] = encodeLocal(dateQt.date().toString(
                        Qt.ISODate))
                    res["roasttime"] = encodeLocal(dateQt.time().toString())
                    res["roastepoch"] = int(dateQt.toTime_t())
                    res["roasttzoffset"] = libtime.timezone
            except:
                pass

        w_in = 0
        w_out = 0
        u = "lb"
        if "Starting mass:" in tag_values:
            w_in, u = tag_values["Starting mass:"].strip().split(" ")
        if "Ending mass:" in tag_values:
            w_out, u = tag_values["Ending mass:"].strip().split(" ")
        res["weight"] = [
            float(w_in),
            float(w_out), ("Kg" if u.strip() == "kg" else "lb")
        ]

        if "Roasted by:" in tag_values:
            res["operator"] = tag_values["Roasted by:"]
        if "Roaster:" in tag_values:
            res["roastertype"] = tag_values["Roaster:"]
        if "Roast level:" in tag_values:
            try:
                c = int(round(float(tag_values["Roast level:"])))
                res["ground_color"] = c
            except:
                pass
        if "Roast Notes:" in tag_values:
            res["roastingnotes"] = tag_values["Roast Notes:"]

        if title == "" and "Roastable:" in tag_values:
            title = tag_values["Roastable:"]
        if title != "":
            res["title"] = title

        # if DROP temp > 300 => F else C

        source_elements = tree.xpath('//script[contains(@id,"source")]/text()')
        if len(source_elements) > 0:
            source_element = source_elements[0].strip()

            pattern = re.compile(r"\"rid=(\d+)\"")
            d = pattern.findall(source_element)
            if len(d) > 0:
                rid = d[0]

                url = "https://roastlog.com/roasts/profiles/?rid={}".format(
                    rid)
                headers = {
                    "X-Requested-With": "XMLHttpRequest",
                    "Accept": "application/json",
                    "Accept-Encoding": "gzip"
                }
                response = requests.get(url, timeout=(4, 15), headers=headers)
                data_json = response.json()

                timeindex = [-1, 0, 0, 0, 0, 0, 0, 0]
                specialevents = []
                specialeventstype = []
                specialeventsvalue = []
                specialeventsStrings = []

                if "line_plots" in data_json:
                    mode = "F"
                    timex = []
                    temp1, temp2, temp3, temp4 = [], [], [], []
                    temp3_label = "TC3"
                    temp4_label = "TC4"
                    #                    temp1ror = []
                    for lp in data_json["line_plots"]:
                        if "channel" in lp and "data" in lp:
                            if lp["channel"] == 0:  # BT
                                data = lp["data"]
                                timex = [d[0] / 1000 for d in data]
                                temp1 = [d[1] for d in data]
                            elif lp["channel"] == 1:  # ET
                                temp2 = [d[1] for d in lp["data"]]
                            elif lp["channel"] == 2:  # XT1
                                temp3 = [d[1] for d in lp["data"]]
                                if "label" in lp:
                                    temp3_label = lp["label"]
                            elif lp["channel"] == 3:  # XT2
                                temp4 = [d[1] for d in lp["data"]]
                                if "label" in lp:
                                    temp4_label = lp["label"]
#                            elif lp["channel"] == 4: # BT RoR
#                                temp1ror = [d[1] for d in lp["data"]]
                    res["timex"] = timex
                    if len(timex) == len(temp1):
                        res["temp2"] = temp1
                    else:
                        res["temp2"] = [-1] * len(timex)
                    if len(timex) == len(temp2):
                        res["temp1"] = temp2
                    else:
                        res["temp1"] = [-1] * len(timex)
                    if len(temp3) == len(timex) or len(temp4) == len(timex):
                        temp3_visibility = True
                        temp4_visibility = True
                        # add one (virtual) extra device
                        res["extradevices"] = [25]
                        res["extratimex"] = [timex]
                        if len(temp3) == len(timex):
                            res["extratemp1"] = [temp3]
                        else:
                            res["extratemp1"] = [[-1] * len(timex)]
                            temp3_visibility = False
                        if len(temp4) == len(timex):
                            res["extratemp2"] = [temp4]
                        else:
                            res["extratemp2"] = [[-1] * len(timex)]
                            temp4_visibility = False
                        res["extraname1"] = [temp3_label]
                        res["extraname2"] = [temp4_label]
                        res["extramathexpression1"] = [""]
                        res["extramathexpression2"] = [""]
                        res["extraLCDvisibility1"] = [temp3_visibility]
                        res["extraLCDvisibility2"] = [temp4_visibility]
                        res["extraCurveVisibility1"] = [temp3_visibility]
                        res["extraCurveVisibility2"] = [temp4_visibility]
                        res["extraDelta1"] = [False]
                        res["extraDelta2"] = [False]
                        res["extraFill1"] = [False]
                        res["extraFill2"] = [False]
                        res["extradevicecolor1"] = ['black']
                        res["extradevicecolor2"] = ['black']
                        res["extramarkersizes1"] = [6.0]
                        res["extramarkersizes2"] = [6.0]
                        res["extramarkers1"] = [None]
                        res["extramarkers2"] = [None]
                        res["extralinewidths1"] = [1.0]
                        res["extralinewidths2"] = [1.0]
                        res["extralinestyles1"] = ['-']
                        res["extralinestyles2"] = ['-']
                        res["extradrawstyles1"] = ['default']
                        res["extradrawstyles2"] = ['default']

                timex_events = {
                    "Intro temperature": 0,
                    "yellowing": 1,
                    "Yellow": 1,
                    "DRY": 1,
                    "Dry": 1,
                    "dry": 1,
                    "Dry End": 1,
                    "Start 1st crack": 2,
                    "START 1st": 2,
                    "End 1st crack": 3,
                    "Start 2nd crack": 4,
                    "End 2nd crack": 5,
                    "Drop temperature": 6
                }
                if "table_events" in data_json:
                    for te in data_json["table_events"]:
                        if "label" in te and "time" in te:
                            if te["label"] in timex_events:
                                try:
                                    timex_idx = res["timex"].index(
                                        stringtoseconds(te["time"]))
                                    timeindex[timex_events[
                                        te["label"]]] = timex_idx
                                except:
                                    pass
                            else:
                                try:
                                    timex_idx = res["timex"].index(
                                        stringtoseconds(te["time"]))
                                    specialeventsStrings.append(te["label"])
                                    specialevents.append(timex_idx)
                                    specialeventstype.append(4)
                                    specialeventsvalue.append(0)
                                except:
                                    pass
                res["timeindex"] = timeindex

                mode = "F"
                if timeindex[6] != 0 and len(temp1) > timeindex[6] and temp1[
                        timeindex[6]] < 250:
                    mode = "C"
                if timeindex[0] > -1 and len(temp1) > timeindex[0] and temp1[
                        timeindex[0]] < 250:
                    mode = "C"
                res["mode"] = mode

                if len(specialevents) > 0:
                    res["specialevents"] = specialevents
                    res["specialeventstype"] = specialeventstype
                    res["specialeventsvalue"] = specialeventsvalue
                    res["specialeventsStrings"] = specialeventsStrings
    except Exception as e:
        #        import traceback
        #        import sys
        #        traceback.print_exc(file=sys.stdout)
        pass
    return res
Beispiel #5
0
def extractProfileIkawaCSV(file,_):
    res = {} # the interpreted data set

    res["samplinginterval"] = 1.0

    # set profile date from the file name if it has the format "IKAWA yyyy-mm-dd hhmmss.csv"
    filename = os.path.basename(file)
    p = re.compile('IKAWA \d{4,4}-\d{2,2}-\d{2,2} \d{6,6}.csv')
    if p.match(filename):
        s = filename[6:-4] # the extracted date time string
        date = QDateTime.fromString(s,"yyyy-MM-dd HHmmss")
        res["roastdate"] = encodeLocal(date.date().toString())
        res["roastisodate"] = encodeLocal(date.date().toString(Qt.ISODate))
        res["roasttime"] = encodeLocal(date.time().toString())
        res["roastepoch"] = int(date.toTime_t())
        res["roasttzoffset"] = libtime.timezone

    csvFile = io.open(file, 'r', newline="",encoding='utf-8')
    data = csv.reader(csvFile,delimiter=',')
    #read file header
    header = next(data)
    
    fan = None # holds last processed fan event value
    fan_last = None # holds the fan event value before the last one
    heater = None # holds last processed heater event value
    heater_last = None # holds the heater event value before the last one
    fan_event = False # set to True if a fan event exists
    heater_event = False # set to True if a heater event exists
    specialevents = []
    specialeventstype = []
    specialeventsvalue = []
    specialeventsStrings = []
    timex = []
    temp1 = []
    temp2 = []
    extra1 = []
    extra2 = []
    timeindex = [-1,0,0,0,0,0,0,0] #CHARGE index init set to -1 as 0 could be an actal index used
    i = 0
    for row in data:
        i = i + 1
        items = list(zip(header, row))
        item = {}
        for (name, value) in items:
            item[name] = value.strip()
        # take i as time in seconds
        timex.append(i)
        if 'inlet temp' in item:
            temp1.append(float(item['inlet temp']))
        elif 'temp below' in item:
            temp1.append(float(item['temp below']))
        else:
            temp1.append(-1)
        # we map IKAWA Exhaust to BT as main events like CHARGE and DROP are marked on BT in Artisan
        if 'exaust temp' in item:
            temp2.append(float(item['exaust temp']))
        elif 'temp above' in item:
            temp2.append(float(item['temp above']))
        else:
            temp2.append(-1)
        # mark CHARGE
        if not timeindex[0] > -1 and 'state' in item and item['state'] == 'doser open':
            timeindex[0] = max(0,i)
        # mark DROP
        if timeindex[6] == 0 and 'state' in item and item['state'] == 'cooling':
            timeindex[6] = max(0,i)
        # add SET and RPM
        if 'temp set' in item:
            extra1.append(float(item['temp set']))
        elif 'setpoint' in item:
            extra1.append(float(item['setpoint']))
        else:
            extra1.append(-1)
        if 'fan speed (RPM)' in item:
            rpm = float(item['fan speed (RPM)'])
            extra2.append(rpm/100)
        elif 'fan speed' in item:
            rpm = float(item['fan speed'])
            extra2.append(rpm/100)
        else:
            extra2.append(-1)
        
        if "fan set (%)" in item or "fan set" in item:
            try:
                if "fan set (%)" in item:
                    v = float(item["fan set (%)"])
                elif "fan set" in item:
                    v = float(item["fan set"])
                if v != fan:
                    # fan value changed
                    if v == fan_last:
                        # just a fluctuation, we remove the last added fan value again
                        fan_last_idx = next(i for i in reversed(range(len(specialeventstype))) if specialeventstype[i] == 0)
                        del specialeventsvalue[fan_last_idx]
                        del specialevents[fan_last_idx]
                        del specialeventstype[fan_last_idx]
                        del specialeventsStrings[fan_last_idx]
                        fan = fan_last
                        fan_last = None
                    else:
                        fan_last = fan
                        fan = v
                        fan_event = True
                        v = v/10. + 1
                        specialeventsvalue.append(v)
                        specialevents.append(i)
                        specialeventstype.append(0)
                        specialeventsStrings.append("{}".format(fan) + "%")
                else:
                    fan_last = None
            except:
                pass
        if "heater power (%)" in item or "heater" in item:
            try:
                if "heater power (%)" in item:
                    v = float(item["heater power (%)"])
                elif "heater" in item:
                    v = float(item["heater"])
                if v != heater:
                    # heater value changed
                    if v == heater_last:
                        # just a fluctuation, we remove the last added heater value again
                        heater_last_idx = next(i for i in reversed(range(len(specialeventstype))) if specialeventstype[i] == 3)
                        del specialeventsvalue[heater_last_idx]
                        del specialevents[heater_last_idx]
                        del specialeventstype[heater_last_idx]
                        del specialeventsStrings[heater_last_idx]
                        heater = heater_last
                        heater_last = None
                    else:
                        heater_last = heater
                        heater = v
                        heater_event = True
                        v = v/10. + 1
                        specialeventsvalue.append(v)
                        specialevents.append(i)
                        specialeventstype.append(3)
                        specialeventsStrings.append("{}".format(heater) + "%")
                else:
                    heater_last = None
            except:
                pass
    csvFile.close()
    
    res["mode"] = 'C'
            
    res["timex"] = timex
    res["temp1"] = temp1
    res["temp2"] = temp2
    res["timeindex"] = timeindex
    
    res["extradevices"] = [25]
    res["extratimex"] = [timex[:]]
    
    res["extraname1"] = ["SET"]
    res["extratemp1"] = [extra1]
    res["extramathexpression1"] = [""]
    
    res["extraname2"] = ["RPM"]
    res["extratemp2"] = [extra2]
    res["extramathexpression2"] = ["x/100"]
    
    if len(specialevents) > 0:
        res["specialevents"] = specialevents
        res["specialeventstype"] = specialeventstype
        res["specialeventsvalue"] = specialeventsvalue
        res["specialeventsStrings"] = specialeventsStrings
        if heater_event or fan_event:
            # first set etypes to defaults
            res["etypes"] = [QApplication.translate("ComboBox", "Air",None),
                             QApplication.translate("ComboBox", "Drum",None),
                             QApplication.translate("ComboBox", "Damper",None),
                             QApplication.translate("ComboBox", "Burner",None),
                             "--"]
            # update
            if fan_event:
                res["etypes"][0] = "Fan"
            if heater_event:
                res["etypes"][3] = "Heater"
    return res
                
Beispiel #6
0
def extractProfileBulletDict(data, aw):
    try:
        res = {}  # the interpreted data set

        if "celsius" in data and not data["celsius"]:
            res["mode"] = 'F'
        else:
            res["mode"] = 'C'
        if "comments" in data:
            res["roastingnotes"] = data["comments"]
        try:
            if "dateTime" in data:
                try:
                    dateQt = QDateTime.fromString(
                        data["dateTime"], Qt.ISODate)  # RFC 3339 date time
                except:
                    dateQt = QDateTime.fromMSecsSinceEpoch(data["dateTime"])
                if dateQt.isValid():
                    res["roastdate"] = encodeLocal(dateQt.date().toString())
                    res["roastisodate"] = encodeLocal(dateQt.date().toString(
                        Qt.ISODate))
                    res["roasttime"] = encodeLocal(dateQt.time().toString())
                    res["roastepoch"] = int(dateQt.toTime_t())
                    res["roasttzoffset"] = time.timezone
        except:
            pass
        try:
            res["title"] = data["beanName"]
        except:
            pass
        if "roastName" in data:
            res["title"] = data["roastName"]
        try:
            if "roastNumber" in data:
                res["roastbatchnr"] = int(data["roastNumber"])
        except:
            pass
        if "beanName" in data:
            res["beans"] = data["beanName"]
        elif "bean" in data and "beanName" in data["bean"]:
            res["beans"] = data["bean"]["beanName"]
        try:
            if "weightGreen" in data or "weightRoasted" in data:
                wunit = aw.qmc.weight_units.index(aw.qmc.weight[2])
                if wunit in [1, 3]:  # turn Kg into g, and lb into oz
                    wunit = wunit - 1
                wgreen = 0
                if "weightGreen" in data:
                    wgreen = float(data["weightGreen"])
                wroasted = 0
                if "weightRoasted" in data:
                    wroasted = float(data["weightRoasted"])
                res["weight"] = [wgreen, wroasted, aw.qmc.weight_units[wunit]]
        except:
            pass
        try:
            if "agtron" in data:
                res["ground_color"] = int(round(data["agtron"]))
                res["color_system"] = "Agtron"
        except:
            pass
        try:
            if "roastMasterName" in data:
                res["operator"] = data["roastMasterName"]
        except:
            pass
        res["roastertype"] = "Aillio Bullet R1"

        if "ambient" in data:
            res['ambientTemp'] = data["ambient"]
        if "humidity" in data:
            res["ambient_humidity"] = data["humidity"]

        if "beanTemperature" in data:
            bt = data["beanTemperature"]
        else:
            bt = []
        if "drumTemperature" in data:
            dt = data["drumTemperature"]
        else:
            dt = []
        # make dt the same length as bt
        dt = dt[:len(bt)]
        dt.extend(-1 for _ in range(len(bt) - len(dt)))

        if "exitTemperature" in data:
            et = data["exitTemperature"]
        else:
            et = None
        if et is not None:
            # make et the same length as bt
            et = et[:len(bt)]
            et.extend(-1 for _ in range(len(bt) - len(et)))

        if "beanDerivative" in data:
            ror = data["beanDerivative"]
        else:
            ror = None
        if ror is not None:
            # make et the same length as bt
            ror = ror[:len(bt)]
            ror.extend(-1 for _ in range(len(bt) - len(ror)))

        if "sampleRate" in data:
            sr = data["sampleRate"]
        else:
            sr = 2.
        res["samplinginterval"] = 1.0 / sr
        tx = [x / sr for x in range(len(bt))]
        res["timex"] = tx
        res["temp1"] = dt
        res["temp2"] = bt

        timeindex = [-1, 0, 0, 0, 0, 0, 0, 0]
        if "roastStartIndex" in data:
            timeindex[0] = min(max(data["roastStartIndex"], 0), len(tx) - 1)
        else:
            timeindex[0] = 0

        labels = [
            "indexYellowingStart", "indexFirstCrackStart",
            "indexFirstCrackEnd", "indexSecondCrackStart",
            "indexSecondCrackEnd"
        ]
        for i in range(1, 6):
            try:
                idx = data[labels[i - 1]]
                # RoastTime seems to interpret all index values 1 based, while Artisan takes the 0 based approach. We substruct 1
                if idx > 1:
                    timeindex[i] = max(min(idx - 1, len(tx) - 1), 0)
            except:
                pass

        if "roastEndIndex" in data:
            timeindex[6] = max(0, min(data["roastEndIndex"], len(tx) - 1))
        else:
            timeindex[6] = max(0, len(tx) - 1)
        res["timeindex"] = timeindex

        # extract events from newer JSON format
        specialevents = []
        specialeventstype = []
        specialeventsvalue = []
        specialeventsStrings = []

        # extract events from older JSON format
        try:
            eventtypes = [
                "blowerSetting", "drumSpeedSetting", "--",
                "inductionPowerSetting"
            ]
            for j in range(len(eventtypes)):
                eventname = eventtypes[j]
                if eventname != "--":
                    last = None
                    ip = data[eventname]
                    for i in range(len(ip)):
                        v = ip[i] + 1
                        if last is None or last != v:
                            specialevents.append(i)
                            specialeventstype.append(j)
                            specialeventsvalue.append(v)
                            specialeventsStrings.append("")
                            last = v
        except:
            pass

        # extract events from newer JSON format
        try:
            for action in data["actions"]["actionTimeList"]:
                time_idx = action["index"] - 1
                value = action["value"] + 1
                if action["ctrlType"] == 0:
                    event_type = 3
                elif action["ctrlType"] == 1:
                    event_type = 0
                elif action["ctrlType"] == 2:
                    event_type = 1
                specialevents.append(time_idx)
                specialeventstype.append(event_type)
                specialeventsvalue.append(value)
                specialeventsStrings.append(str(value))
        except:
            pass
        if len(specialevents) > 0:
            res["specialevents"] = specialevents
            res["specialeventstype"] = specialeventstype
            res["specialeventsvalue"] = specialeventsvalue
            res["specialeventsStrings"] = specialeventsStrings

        if (ror is not None
                and len(ror) == len(tx)) or (et is not None
                                             and len(et) == len(tx)):
            # add one (virtual) extra device
            res["extradevices"] = [25]
            res["extratimex"] = [tx]

            temp3_visibility = True
            temp4_visibility = False
            if et is not None and len(et) == len(tx):
                res["extratemp1"] = [et]
            else:
                res["extratemp1"] = [[-1] * len(tx)]
                temp3_visibility = False
            if ror is not None and len(ror) == len(tx):
                res["extratemp2"] = [ror]
            else:
                res["extratemp2"] = [[-1] * len(tx)]
                temp4_visibility = False
            res["extraname1"] = ["Exhaust"]
            res["extraname2"] = ["RoR"]
            res["extramathexpression1"] = [""]
            res["extramathexpression2"] = [""]
            res["extraLCDvisibility1"] = [temp3_visibility]
            res["extraLCDvisibility2"] = [temp4_visibility]
            res["extraCurveVisibility1"] = [temp3_visibility]
            res["extraCurveVisibility2"] = [temp4_visibility]
            res["extraDelta1"] = [False]
            res["extraDelta2"] = [True]
            res["extraFill1"] = [False]
            res["extraFill2"] = [False]
            res["extradevicecolor1"] = ['black']
            res["extradevicecolor2"] = ['black']
            res["extramarkersizes1"] = [6.0]
            res["extramarkersizes2"] = [6.0]
            res["extramarkers1"] = [None]
            res["extramarkers2"] = [None]
            res["extralinewidths1"] = [1.0]
            res["extralinewidths2"] = [1.0]
            res["extralinestyles1"] = ['-']
            res["extralinestyles2"] = ['-']
            res["extradrawstyles1"] = ['default']
            res["extradrawstyles2"] = ['default']

        return res
    except:
        #        import traceback
        #        import sys
        #        traceback.print_exc(file=sys.stdout)
        return {}
Beispiel #7
0
def extractProfileCropsterXLS(file, _):
    res = {}  # the interpreted data set

    book = xlrd.open_workbook(file)

    sheet_names = book.sheet_names()

    id_tag_trans = [
        "Id-Tag",  # EN
        "ID-Tag",  # DE
        "Etiqueta de identificaci\u00f3n",  # ES
        "N\u00b0 d'identification",  # FR
        "Codice ID",  # IT
        "ID-Tag",  # PT
        "ID-\u0442ег",  # RU
        "ID-Tag",  # KO
        "ID-\u30bf\u30b0",  # JP
        "ID\uff0d\u6807\u7b7e",  # CN simplified
        "\u7de8\u865f\u0020\u002d\u0020\u6a19\u7c64"
    ]  # CN traditional

    date_trans = [
        "Date",  # EN
        "Datum",  # DE
        "Fecha",  # ES
        "Date",  # FR
        "Data",  # IT, PT
        "\u0414\u0430\u0442\u0430",  # RU
        "\ub0a0\uc9dc",  # KO
        "\u65e5\u4ed8",  # JP
        "\u65e5\u671f",  # CN simplified
        "\u65e5\u671f",  # CN traditional
    ]

    # list of Artisan tags for strings associated to tuple of (fixed) column nr (or Null) and list of tag translations
    string_tag_labels_trans = [
        (
            'beans',
            1,
            [
                "Lot name",  # EN
                "Chargenname",  # DE
                "Nombre del lote",  # ES
                "Nom du lot",  # FR
                "Nome lotto",  # IT
                "Nome do lote",  # PT
                "\u0418\u043c\u044f\u0020\u043b\u043e\u0442\u0430",  # RU
                "Lot \uc774\ub984",  # KO
                "\u30ed\u30c3\u30c8\u540d",  # JP
                "\u6279\u6b21\u540d\u79f0",  # CN Simplified
                "\u6279\u6b21\u540d\u7a31",  # CN Traditional
            ]),
        (
            'title',
            2,
            [
                "Profile",  # EN
                "Profil",  # DE, FR
                "Perfil",  # ES, pT
                "Profilo",  # IT
                "\u041f\u0440\u043e\u0444\u0438\u043b\u044c",  # RU
                "\ud504\ub85c\ud30c\uc77c",  # KO
                "\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb",  # JP
                "\u66f2\u7ebf\u6863\u6848",  # CN Simplified
                "\u66f2\u7dda\u6a94\u6848",  # CN Traditional
            ]),
        (
            'roastertype',
            3,
            [
                "Machine",  # EN, FR
                "Maschine",  # DE
                "Tostador",  # ES
                "Macchina",  # IT
                "M\u00e1quina",  # PT
                "\u041c\u0430\u0448\u0438\u043d\u0430",  # RU
                "\uba38\uc2e0",  # KO
                "\u6a5f\u68b0",  # JP
                "\u70d8\u7119\u673a",  # CN Simplified
                "\u70d8\u7119\u6a5f",  # CN Traditional
            ]),
        (
            'operator',
            4,
            [
                "Roast technician",  # EN
                "R\u00f6sttechniker",  # DE
                "T\u00e9cnico de tueste",  # ES
                "Torr\u00e9facteur",  # FR
                "Addetto alla tostatura",  # IT
                "Mestre de torra",  # PT
                "\u041e\u0431\u0436\u0430\u0440\u0449\u0438\u043a",  # RU
                "\ub85c\uc2a4\ud130",  # KO
                "\u30ed\u30fc\u30b9\u30c8\u30c6\u30af\u30cb\u30b7\u30e3\u30f3",  # JP
                "\u70d8\u7119\u5e08",  # CN Simplified
                "\u70d8\u7119\u5e2b",  # CN Traditional
            ]),
        (
            'cuppingnotes',
            None,
            [
                "Sensorial notes",  # EN
                "Sensorische Notizen",  # DE
                "Anotaciones sobre el an\u00e1lisis sensorial",  # ES
                "Commentaires sensoriels",  # FR
                "Note sensoriali",  # IT
                "Observa\u00e7\u00f5es sensoriais",  # PT
                "\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u044f\u0020\u043a\u0020\u0430\u043d\u0430\u043b\u0438\u0437\u0443\u0020\u0432\u043a\u0443\u0441\u043e\u0432\u044b\u0445\u0020\u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a",  # RU
                "\uc13c\uc11c\ub9ac\u0020\uba54\ubaa8",  # KO
                "\u77e5\u899a\u30e1\u30e2",  # JP
                "\u611f\u5b98\u7279\u5f81\u9644\u6ce8",  # CN Simplified
                "\u611f\u5b98\u7279\u5fb5\u9644\u8a3b",  # CN Traditional
            ]),
        (
            'roastingnotes',
            None,
            [
                "Roasting notes",  # EN
                "R\u00f6st-Notizen",  # DE
                "Anotaciones sobre el tostado",  # ES
                "Commentaires de torr\u00e9faction",  # FR
                "Note sulla tostatura",  # IT
                "Observa\u00e7\u00f5es da torrefa\u00e7\u00e3o",  # PT
                "\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u044f\u0020\u043a\u0020\u043e\u0431\u0436\u0430\u0440\u043a\u0435",  # RU
                "\ub85c\uc2a4\ud305\u0020\uba54\ubaa8",  # KO
                "\u30ed\u30fc\u30b9\u30c6\u30a3\u30f3\u30b0\u30ce\u30fc\u30c8",  # JP
                "\u70d8\u7119\u7b14\u8bb0",  # CN Simplified
                "\u70d8\u7119\u7b46\u8a18",  # CN Traditional
            ])
    ]

    ambient_temp_trans = [
        "Ambient temp.",  # EN
        "Raumtemp.",  # DE
        "Temperatura ambiente",  # ES
        "Temp. ambiante",  # FR, IT, PT
        "\u0422\u0435\u043c\u043f\u002e\u0020\u043e\u043a\u0440\u0443\u0436\u0430\u044e\u0449\u0435\u0433\u043e\u0020\u0432\u043e\u0437\u0434\u0443\u0445\u0430",  # RU
        "\uc8fc\ubcc0\u0020\uc628\ub3c4",  # KO
        "\u30a2\u30f3\u30d3\u30a8\u30f3\u30c8\u6e29\u5ea6",  # JP
        "\u5ba4\u5185\u6e29\u5ea6",  # CN Simplified
        "\u5ba4\u5167\u6eab\u5ea6",  # CN Traditional
    ]
    start_weight_trans = [
        "Start weight",  # EN
        "Startgewicht",  # DE
        "Peso inicial",  # ES, PT
        "Poids initial",  # FR
        "Peso iniziale",  # IT
        "\u041d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0439\u0020\u0432\u0435\u0441",  # RU
        "\uc2dc\uc791\u0020\ubb34\uac8c",  # KO
        "\u958b\u59cb\u91cd\u91cf",  # JP
        "\u5f00\u59cb\u91cd\u91cf",  # CN Simplified
        "\u958b\u59cb\u91cd\u91cf",  # CN Traditional
    ]
    start_weight_unit_trans = [
        "Start weight unit",  # EN
        "Einheit Startgewicht",  # DE
        "Unidad de peso inicial",  # ES
        "Unit\u00e9 poids initial",  # FR
        "Unit\u00e0 peso iniziale",  # IT
        "Unidade do peso inicial",  # PT
        "\u0415\u0434\u0438\u043d\u0438\u0446\u0430\u0020\u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f\u0020\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0433\u043e\u0020\u0432\u0435\u0441\u0430",  # RU
        "\uc2dc\uc791\u0020\ubb34\uac8c\u0020\ub2e8\uc704",  # KO
        "\u958b\u59cb\u91cd\u91cf\u30e6\u30cb\u30c3\u30c8",  # JP
        "\u5f00\u59cb\u91cd\u91cf\u5355\u4f4d",  # CN Simplified
        "\u958b\u59cb\u91cd\u91cf\u55ae\u4f4d",  # CN Traditional
    ]
    end_weight_trans = [
        "End weight",  # EN
        "Endgewicht",  # DE
        "Peso final",  # ES, PT
        "Poids final",  # FR
        "Peso finale",  # IT
        "\u041a\u043e\u043d\u0435\u0447\u043d\u044b\u0439\u0020\u0432\u0435\u0441",  # RU
        "\uc885\ub8cc\u0020\ubb34\uac8c",  # KO
        "\u958b\u59cb\u91cd\u91cf",  # JP
        "\u7ed3\u675f\u91cd\u91cf",  # CN Simplified
        "\u7d50\u675f\u91cd\u91cf",  # CN Traditional
    ]
    end_weight_unit_trans = [
        "End weight unit",  # EN
        "Einheit Endgewicht",  # DE
        "Unidad de peso final",  # ES
        "Unit\u00e9 poids final",  # FR
        "Unit\u00e0 peso finale",  # IT
        "Unidade de peso final",  # PT
        "\u0415\u0434\u0438\u043d\u0438\u0446\u0430\u0020\u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f\u0020\u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0433\u043e\u0020\u0432\u0435\u0441\u0430",  # RU
        "\uc885\ub8cc\u0020\ubb34\uac8c\u0020\ub2e8\uc704",  # KO
        "\u958b\u59cb\u91cd\u91cf\u30e6\u30cb\u30c3\u30c8",  # JP
        "\u7ed3\u675f\u91cd\u91cf\u5355\u4f4d",  # CN Simplified
        "\u7d50\u675f\u91cd\u91cf\u55ae\u4f4d",  # CN Traditional
    ]

    turning_point_trans = [
        "Turning point",  # EN
        "Wendepunkt",  # DE
        "Temperatura de fondo",  # ES
        "Point de balance",  # FR
        "Punto di flesso",  # IT
        "Temperatura de fundo",  # PT
        "\u041f\u043e\u0432\u043e\u0440\u043e\u0442\u043d\u0430\u044f\u0020\u0442\u043e\u0447\u043a\u0430",  # RU
        "\ud130\ub2dd\u0020\ud3ec\uc778\ud2b8",  # KO
        "\u30bf\u30fc\u30cb\u30f3\u30b0\u30dd\u30a4\u30f3\u30c8",  # JP
        "\u56de\u6e29\u70b9",  # CN Simplified
        "\u56de\u6e29\u9ede",  # CN Trraditional
    ]

    color_change_trans = [
        "Color change",  # EN
        "Farb\u00e4nderung",  # DE
        "Cambio de color",  # ES
        "Changement de couleur",  # FR
        "Cambiamento di colore",  # IT
        "Mudan\u00e7a de cor",  # PT
        "\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u0020\u0446\u0432\u0435\u0442\u0430",  # RU
        "\uc0c9\u0020\ubcc0\ud654",  # KO
        "\u8272\u306e\u5909\u5316",  # JP
        "\u989c\u8272\u53d8\u5316",  # CN Simplified
        "\u984f\u8272\u8b8a\u5316",  # CN Traditional
    ]
    first_crack_trans = [
        "First crack",  # EN
        "1. Crack",  # DE
        "Primer crac",  # ES
        "Premier crack",  # FR
        "Primo Crack",  # IT
        "Primeiro crack",  # PT
        "\u041f\u0435\u0440\u0432\u044b\u0439\u0020\u0442\u0440\u0435\u0441\u043a",  # RU
        "\u0031\ucc28\u0020\ud06c\ub799",  # KO
        "\uff11\u30cf\u30bc",  # JP
        "\u4e00\u7206",  # CN Simplified, Traditional
    ]
    second_crack_trans = [
        "Second crack",  # EN, FR
        "2. Crack",  # DE
        "Segundo crac",  # ES
        "Secondo Crack",  # IT
        "Segundo crack",  # PT
        "\u0412\u0442\u043e\u0440\u043e\u0439\u0020\u0442\u0440\u0435\u0441\u043a",  # RU
        "\u0032\ucc28\u0020\ud06c\ub799",  # KO
        "\uff12\u30cf\u30bc",  # JP
        "\u4e8c\u7206",  # CN Simplified, Traditional
    ]
    gas_trans = [
        "Gas",  # EN, DE, ES, IT
        "Gaz",  # FR
        "G\u00e1s",  # PT
        "\u0413\u0430\u0437",  # RU
        "\uac00\uc2a4",  # KO
        "\u30ac\u30b9",  # JP
        "\u706b\u529b",  # CN Timplified, Traditional
    ]
    airflow_trans = [
        "Airflow",  # EN, DE
        "Flujo de aire",  # ES
        "Arriv\u00e9e d'air",  # FR
        "Flusso d'aria",  # IT
        "Fluxo de ar",  # PT
        "\u0412\u043e\u0437\u0434\u0443\u0448\u043d\u044b\u0439\u0020\u043f\u043e\u0442\u043e\u043a",  # RU
        "\uacf5\uae30\u0020\ud750\ub984",  # KO
        "\u7a7a\u6c17\u306e\u6d41\u308c",  # JP
        "\u98ce\u95e8",  # CN Simplified
        "\u98a8\u9580",  # CN Traditional
    ]
    comment_trans = [
        "Comment",  # EN
        "Kommentar",  # DE
        "Comentar",  # ES
        "Commentaire",  # FR
        "Commento",  # IT
        "Coment\u00e1rio",  # PT
        "\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439",  # RU
        "\ucf54\uba58\ud2b8",  # KO
        "\u30b3\u30e1\u30f3\u30c8",  # JP
        "\u5907\u6ce8",  # CN Simplified
        "\u5099\u8a3b",  # CN Traditional
    ]

    # standard curves
    curve_bt_trans = [
        "Curve - Bean temp.",  # EN
        "Kurve - Bohnentemp.",  # DE
        "Curva - Temp. del grano",  # ES (potentially wrong)
        "Courbe Temp. grain",  # FR
        "Curva - Temp. chicco",  # IT
        "Curva - Temp. do gr\u00e3o",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0437\u0435\u0440\u0435\u043d",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\ube48\u0020\uc628\ub3c4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u8c46\u306e\u6e29\u5ea6\u3002",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u8c46\u6e29",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u8c46\u6eab",  # CH Traditional
    ]
    curve_et_trans = [  # not that we map Exhaust to ET and not Env. Temp. as it is available more often; Env. Temp. is mapped to an extra device curve if available
        "Curve - Exhaust temp.",  # EN
        "Kurve - Ablufttemp.",  # DE
        "Curva - Temp. de salida",  # ES (potentially wrong)
        "Courbe Temp. \u00e9chappement",  # FR
        "Curva - Temp. fumi",  # IT
        "Curva - Temp. de exaust\u00e3o",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u043d\u0430\u0020\u0432\u044b\u0445\u043e\u0434\u0435",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\ubc30\uae30\u0020\uc628\ub3c4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u6392\u6c17\u6e29\u5ea6\u3002",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u6392\u98ce\u6e29",  # CN Simplified, Traditional
    ]

    # extra temperature curves (C-F conversion applicable)

    curve_env_temp_trans = [
        "Curve - Env. temp.",  # EN
        "Kurve - Umgebungstemp.",  # DE
        "Curva - Temp. ambiente",  # ES
        "Courbe Temp. env.",  # FR
        "Curva - Temp. aria in tamburo",  # IT
        "Curva - Temp. ambiente",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\uc8fc\ubcc0\u0020\uc628\ub3c4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u5468\u56f2\u6e29\u5ea6",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u7089\u6e29",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u7210\u6eab",  # CN Traditional
    ]
    curve_burner_temp_trans = [
        "Curve - Burner temp.",  # EN
        "Kurve - Brennertemp.",  # DE
        "Curva - Temp. del quemador",  # ES
        "Courbe Temp. br\u00fbleur",  # FR
        "Curva - Temp. bruciatore",  # IT
        "Curva - Temp. do queimador",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0433\u043e\u0440\u0435\u043b\u043a\u0438",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\ubc84\ub108\u0020\uc628\ub3c4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30d0\u30fc\u30ca\u30fc\u6e29\u5ea6",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u71c3\u70df\u5668\u6e29\u5ea6",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u71c3\u7159\u5668\u6eab\u5ea6",  # CN Traditional
    ]
    curve_other_temp_trans = [
        "Curve - Other temp.",  # EN
        "Kurve - Andere Temp.",  # DE
        "Curva - Otras temperaturas",  # ES
        "Courbe Temp. autre",  # FR
        "Curva - Altra temp.",  # IT
        "Curva - Outra temp.",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0414\u0440\u0443\u0433\u0430\u044f\u0020\u0442\u0435\u043c\u043f.",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\uae30\ud0c0\u0020\uc628\ub3c4\u002e",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u4ed6\u306e\u6e29\u5ea6",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u5176\u5b83\u6e29\u5ea6",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u5176\u5b83\u6eab\u5ea6",  # CN Traditional
    ]
    curve_stack_temp_trans = [
        "Curve - Stack temp.",  # EN
        "Kurve - Schornsteintemp.",  # DE
        "Curva - Temp. del tiro",  # ES
        "Courbe Temp. broche",  # FR
        "Curva - Temp. camino",  # IT
        "Curva - Temp. do escoamento de",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0432\u0020\u0434\u044b\u043c\u043e\u0432\u043e\u0439\u0020\u0442\u0440\u0443\u0431\u0435",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\uc2a4\ud0dd\u0020\uc628\ub3c4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30b9\u30bf\u30c3\u30af\u6e29\u5ea6",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u70df\u56f1\u6e29\u5ea6",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u7159\u56ea\u6eab\u5ea6",  # CN Traditional
    ]
    curve_return_temp_trans = [
        "Curve - Return temp.",  # EN
        "Kurve - R\u00fccklauftemp.",  # DE
        "Curva - Temp. de retorno",  # ES
        "Courbe Temp. retour",  # FR
        "Curva - Temp. di ritorno",  # IT
        "Curva - Temperatura de Retorno",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\ubc30\uae30\u0020\uc628\ub3c4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u623b\u308a\u6e29\u5ea6\u3002",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u7a7a\u6c14\u56de\u7089\u6e29\u5ea6",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u7a7a\u6c23\u56de\u7210\u6eab\u5ea6",  # CN Traditional
    ]
    curve_inlet_temp_trans = [
        "Curve - Inlet temp.",  # EN
        "Kurve - Einlasstemp.",  # DE
        "Curva - Temp. de entrada",  # ES
        "Courbe Temp. entr\u00e9e",  # FR
        "Curva - Temp. in ingresso",  # IT
        "Curva - Temp. do ar de entrada",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u043d\u0430\u0020\u0432\u0445\u043e\u0434\u0435",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\ud761\uae30\u0020\uc628\ub3c4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u5438\u6c17\u53e3\u6e29\u5ea6",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u8fdb\u98ce\u6e29",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u9032\u98a8\u6eab",  # CN Traditional
    ]
    curve_afterburner_temp_trans = [
        "Curve - Afterburner temp.",  # EN
        "Kurve - Nachbrennertemp.",  # DE
        "Curva - Temp. del posquemador",  # ES
        "Courbe Temp. post-combustion",  # FR
        "Curva - Temp. bruciafumi",  # IT
        "Curva - Temp. p\u00f3s-combust\u00e3o",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0432\u0020\u0444\u043e\u0440\u0441\u0430\u0436\u043d\u043e\u0439\u0020\u043a\u0430\u043c\u0435",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\uc560\ud504\ud130\ubc84\ub108\u0020\uc628\ub3c4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30a2\u30d5\u30bf\u30fc\u30d0\u30fc\u30ca\u30fc\u6e29\u5ea6\u3002",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u540e\u7f6e\u71c3\u70df\u5668\u6e29\u5ea6",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u5f8c\u7f6e\u71c3\u7159\u5668\u6eab\u5ea6",  # CN Traditional
    ]
    curve_drum_temp_trans = [
        "Curve - Drum temp.",  # EN
        "Kurve - Trommeltemp.",  # DE
        "Curva - Temp. del Tambor",  # EE
        "Courbe Temp. tambour",  # FR
        "Curva - Temp. tamburo",  # IT
        "Curva - Temperatura do Tambor",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0431\u0430\u0440\u0430\u0431\u0430\u043d\u0430",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\ub4dc\ub7fc\u0020\uc628\ub3c4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30c9\u30e9\u30e0\u6e29\u5ea6\u3002",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u9505\u7089\u6e29\u5ea6",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u934b\u7210\u6eab\u5ea6",  # CN Traditional
    ]

    # extra non-temperature curves (no temperature conversion)

    curve_gas_control_trans = [
        "Curve - Gas control",  # EN
        "Kurve - Gas-Kontrolle",  # DE
        "Curva - Control del gas",  # ES
        "Courbe R\u00e9gulation du d\u00e9bit de g",  # FR
        "Curva - Controllo gas",  # IT
        "Curva - Controle de g\u00e1s",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0020\u0433\u0430\u0437\u043e\u043c",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\uac00\uc2a4\u0020\uc81c\uc5b4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30ac\u30b9\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u706b\u529b\u63a7\u5236",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u706b\u529b\u63a7\u5236",  # CN Traditional
    ]
    curve_drum_speed_trans = [
        "Curve - Drum speed",  # EN
        "Kurve - Trommelgeschw.",  # DE
        "Curva - Velocidad del tambor",  # ES
        "Courbe Vitesse du tambour",  # FR
        "Curva - Velocit\u00e0 tamburo",  # IT
        "Curva - Velocidade do tambor",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c\u0020\u0431\u0430\u0440\u0430\u0431\u0430\u043d\u0430",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\ub4dc\ub7fc\u0020\uc18d\ub3c4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30c9\u30e9\u30e0\u30b9\u30d4\u30fc\u30c9",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u8f6c\u901f",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u8f49\u901f",  # CN Traditional
    ]
    curve_airflow_trans = [
        "Curve - Airflow",  # EN
        "Kurve - Airflow",  # DE
        "Curva - Flujo de aire",  # ES
        "Courbe Arriv\u00e9e d'air",  # FR
        "Curva - Flusso d'aria",  # IT
        "Curva - Fluxo de ar",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0412\u043e\u0437\u0434\u0443\u0448\u043d\u044b\u0439\u0020\u043f\u043e\u0442\u043e\u043a",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\uacf5\uae30\u0020\ud750\ub984",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u7a7a\u6c17\u306e\u6d41\u308c",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u98ce\u95e8",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u98a8\u9580",  # CN Traditional
    ]
    curve_gas_trans = [
        "Curve - Gas",  # EN
        "Kurve - Gas",  # DE
        "Curva - Gas",  # ES
        "Courbe Gaz",  # FR
        "Curva - Gas",  # IT
        "Curva - G\u00e1s",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0413\u0430\u0437",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\uac00\uc2a4",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30ac\u30b9",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u706b\u529b",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u706b\u529b",  # CN Traditional
    ]
    #    curve_gas_comments_trans = [
    #        "Curve - Gas comments", # EN
    #        "Kurve - Kommentare Gas", # DE
    #        "Curva - Comentarios sobre el gas", # ES
    #        "Courbe Commentaires de type Gaz", # FR
    #        "Curva - Commenti sul Gas", # IT
    #        "Curva - Coment\u00e1rios do g\u00e1s", # PT
    #        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438\u0020\u043f\u043e\u0020\u043f\u043e\u0432\u043e\u0434\u0443",  # RU
    #        "\ucee4\ube0c\u0020\u002d\u0020\uac00\uc2a4\u0020\ucf54\uba58\ud2b8", # KO
    #        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30ac\u30b9\u306b\u3064\u3044\u3066\u306e\u30b3\u30e1\u30f3\u30c8", # JP
    #        "\u66f2\u7ebf\u0020\u002d\u0020\u706b\u529b\u5907\u6ce8", # CN Simplified
    #        "\u66f2\u7dda\u0020\u002d\u0020\u706b\u529b\u5099\u8a3b", # CN Traditional
    #    ]
    curve_drum_pressure_trans = [
        "Curve - Drum pressure",  # EN
        "Kurve - Trommeldruck",  # DE
        "Curva - Presi\u00f3n del tambor",  # ES
        "Courbe Pression du tambour",  # FR
        "Curva - Pressione tamburo",  # IT
        "Curva - Press\u00e3o do tambor",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0414\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0020\u0432\u0020\u0431\u0430\u0440\u0430\u0431\u0430\u043d\u0435",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\ub4dc\ub7fc\u0020\uc555\ub825",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30c9\u30e9\u30e0\u5727\u529b",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u7089\u538b",  # CN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u7210\u58d3",  # CN Traditional
    ]
    curve_airflow_control_trans = [
        "Curve - Airflow control",  # EN
        "Kurve - Airflow-Steuerung",  # DE
        "Curva - Regulaci\u00f3n del caudal de aire",  # ES
        "Courbe Contr\u00f4le de la ventilati",  # FR
        "Curva - Controllo del flusso d'",  # IT: "Curva - Controllo del flusso d'aria"
        "Curva - Controle de fluxo de ar",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u0020\u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0433\u043e\u0020\u043f\u043e",  # RU
        "\ucee4\ube0c\u0020\u002d\u0020\u0041\u0069\u0072\u0066\u006c\u006f\u0077\u0020\u0063\u006f\u006e\u0074\u0072\u006f\u006c",  # KO
        "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u7a7a\u6c17\u306e\u6d41\u308c\u306e\u7ba1\u7406",  # JP
        "\u66f2\u7ebf\u0020\u002d\u0020\u98ce\u95e8\u63a7\u5236",  # SN Simplified
        "\u66f2\u7dda\u0020\u002d\u0020\u0041\u0069\u0072\u0066\u006c\u006f\u0077\u0020\u0063\u006f\u006e\u0074\u0072\u006f\u006c",  # SN Traditional
    ]
    curve_fan_speed_trans = [
        "Curve - fanSpeed",  # EN
        "Kurve - fanSpeed",  # DE
        #"Curva - Velocidad del ventilador", # ES
        "Courbe fanSpeed",  # FR
        "Curva - fanSpeed",  # IT, ES
        "Curva - fanSpeed",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f - fanSpeed",  # RU
        "\ucee4\ube0c - fanSpeed",  # KO
        "\u30ab\u30fc\u30d6 - fanSpeed",  # JP
        "\u66f2\u7ebf - fanSpeed",  # CN Simplified
        "\u66f2\u7dda - fanSpeed",  # CN Traditional
    ]

    ####

    extra_temp_curve_trans = \
        curve_env_temp_trans + \
        curve_burner_temp_trans + \
        curve_other_temp_trans + \
        curve_stack_temp_trans + \
        curve_return_temp_trans + \
        curve_inlet_temp_trans + \
        curve_afterburner_temp_trans + \
        curve_drum_temp_trans

    extra_nontemp_curve_trans = \
        curve_gas_control_trans + \
        curve_drum_speed_trans + \
        curve_airflow_trans + \
        curve_gas_trans + \
        curve_drum_pressure_trans + \
        curve_airflow_control_trans + \
        curve_fan_speed_trans
    #curve_gas_comments_trans

    curve_prefixa = [
        # Curve + Temp
        "Curva - Temp.",  # ES (potentially wrong)
        "Courbe Temp.",  # FR
        "Curva - Temp.",  # IT
        "Curva - Temp.",  # PT
        "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020",  # RU
        # just Curve
        "Curve -",  # EN
        "Kurve -",  # DE
        "Curva -",  # ES, IT, PT
        "Courbe",  # FR
        "\u041a\u0440\u0438\u0432\u0430\u044f -",  # RU
        "\ucee4\ube0c -",  # KO
        "\u30ab\u30fc\u30d6 -",  # JP
        "\u66f2\u7ebf -",  # CN Simplified
        "\u66f2\u7dda -",  # CN Traditional
    ]

    curve_postfixa = [
        "temp.",  # EN
        "temp.",  # DE
        "\u0020\uc628\ub3c4",  # KO
        "\u6e29\u5ea6\u3002",  # JP
        "\u6e29\u5ea6",  # CN Simplified & Tranitional
    ]

    ##########

    # extract general profile information
    general_sh = book.sheet_by_index(0)
    if general_sh.nrows >= 1:
        row1 = general_sh.row(1)
        general_data = dict(zip([x.value for x in general_sh.row(0)], row1))

        res["samplinginterval"] = 1.0

        try:
            id_tag_value = None
            # try to find the "Id-Tag" value
            # 1. test the column name in all known translations
            for tag in id_tag_trans:
                if tag in general_data:
                    id_tag_value = general_data[tag].value
                    break
            # 2. take the first value of row1
            if id_tag_value is None and len(row1) > 0:
                id_tag_value = row1[0].value
            if id_tag_value is not None:
                batch_prefix = id_tag_value.rstrip('0123456789')
                batch_number = int(id_tag_value[len(batch_prefix):])
                res["roastbatchprefix"] = batch_prefix
                res["roastbatchnr"] = batch_number
        except:
            pass

        for (tag, pos, trans) in string_tag_labels_trans:
            value = None
            try:
                # 1. test the column name in all known translations
                for tr in trans:
                    if tr in general_data:
                        value = general_data[tr].value
                        break
                # 2. take the pos column value of row1
                if value is None and pos is not None and len(row1) > pos:
                    value = row1[pos].value
                if value is not None:
                    res[tag] = encodeLocal(value)
            except:
                pass

        try:
            date_tag_value = None
            # try to find the "Date" value
            # 1. test the column name in all known translations
            for tag in date_trans:
                if tag in general_data:
                    date_tag_value = general_data[tag].value
                    break
            # 2. take the first value of row1
            if date_tag_value is None:
                date_tag_value = row1[6].value
            if date_tag_value is not None:
                date_tuple = xlrd.xldate_as_tuple(date_tag_value,
                                                  book.datemode)
                date = QDateTime(*date_tuple)
                if date.isValid():
                    res["roastdate"] = encodeLocal(date.date().toString())
                    res["roastisodate"] = encodeLocal(date.date().toString(
                        Qt.ISODate))
                    res["roasttime"] = encodeLocal(date.time().toString())
                    res["roastepoch"] = int(date.toTime_t())
                    res["roasttzoffset"] = libtime.timezone
        except:
            pass

        try:
            ambient_tag_value = None
            # try to find the "Date" value
            # test the column name in all known translations
            for tag in ambient_temp_trans:
                if tag in general_data:
                    ambient_tag_value = general_data[tag].value
                    break
            if ambient_tag_value is None and len(row1) > 27:
                ambient_tag_value = row1[27]
            if ambient_tag_value is not None:
                res['ambientTemp'] = float(ambient_tag_value)
        except:
            pass

        try:
            start_weight_tag_value = None
            start_weight_unit_tag_value = None
            end_weight_tag_value = None
            end_weight_unit_tag_value = None

            # try to find the "Start weight" value
            # test the column name in all known translations
            for tag in start_weight_trans:
                if tag in general_data:
                    start_weight_tag_value = general_data[tag].value
                    break
            if start_weight_tag_value is None and len(row1) > 9:
                start_weight_tag_value = row1[9]
            # try to find the "Start weight unit" value
            # test the column name in all known translations
            for tag in start_weight_unit_trans:
                if tag in general_data:
                    start_weight_unit_tag_value = general_data[tag].value
                    break
            if start_weight_unit_tag_value is None and len(row1) > 10:
                start_weight_unit_tag_value = row1[10]
            # try to find the "End weight" value
            # test the column name in all known translations
            for tag in end_weight_trans:
                if tag in general_data:
                    end_weight_tag_value = general_data[tag].value
                    break
            if end_weight_tag_value is None and len(row1) > 11:
                end_weight_tag_value = row1[11]
            # try to find the "End weight unit" value
            # test the column name in all known translations
            for tag in end_weight_unit_trans:
                if tag in general_data:
                    end_weight_unit_tag_value = general_data[tag].value
                    break
            if end_weight_unit_tag_value is None and len(row1) > 12:
                end_weight_unit_tag_value = row1[12]

            if start_weight_tag_value is not None and end_weight_tag_value is not None:
                cropster_weight_units = ["G", "KG", "LBS", "OZ"]
                artisan_weight_units = ["g", "Kg", "lb", "oz"]
                weight = [0, 0, artisan_weight_units[0]]
                try:
                    if end_weight_unit_tag_value is not None:
                        idx = cropster_weight_units.index(
                            end_weight_unit_tag_value)
                        weight[2] = artisan_weight_units[idx]
                except:
                    pass
                try:
                    if start_weight_unit_tag_value:
                        idx = cropster_weight_units.index(
                            start_weight_unit_tag_value)
                        weight[2] = artisan_weight_units[idx]
                except:
                    pass
                try:
                    if start_weight_tag_value is not None:
                        weight[0] = start_weight_tag_value
                except:
                    pass
                try:
                    if end_weight_tag_value is not None:
                        weight[1] = end_weight_tag_value
                except:
                    pass
                res["weight"] = weight
        except:
            pass

    res["timex"] = []
    res["temp1"] = []
    res["temp2"] = []
    # BT:
    BT_idx = None
    ET_idx = None
    try:
        for i, sn in enumerate(sheet_names):
            if sn in curve_bt_trans:
                BT_idx = i
                BT_sh = book.sheet_by_index(BT_idx)
                if BT_sh.ncols >= 1:
                    time = BT_sh.col(0)
                    temp = BT_sh.col(1)
                    if len(time) > 0 and len(temp) > 0 and len(time) == len(
                            temp):
                        if "FAHRENHEIT" in str(temp[0].value):
                            res["mode"] = 'F'
                        else:
                            res["mode"] = 'C'
                        res["timex"] = [t.value for t in time[1:]]
                        res["temp2"] = [t.value for t in temp[1:]]
                        res["temp1"] = [-1] * len(res["timex"])
                        res["timeindex"] = [
                            0, 0, 0, 0, 0, 0,
                            len(res["timex"]) - 1, 0
                        ]
                break
    except:
        pass
    # ET
    try:
        for et_trans in [
                curve_env_temp_trans, curve_et_trans, curve_return_temp_trans,
                curve_burner_temp_trans
        ]:
            for i, sn in enumerate(sheet_names):
                if sn in et_trans:
                    ET_idx = i
                    ET_sh = book.sheet_by_index(ET_idx)
                    if ET_sh.ncols >= 1:
                        time = ET_sh.col(0)
                        temp = ET_sh.col(1)
                        if len(time) > 0 and len(temp) > 0 and len(
                                time) == len(temp):
                            if "FAHRENHEIT" in str(temp[0].value):
                                res["mode"] = 'F'
                            else:
                                res["mode"] = 'C'
                            res["temp1"] = [t.value for t in temp[1:]]
                            if len(res["timex"]) != len(res["temp1"]):
                                res["timex"] = [t.value for t in time[1:]]
                            if "temp2" not in res or len(res["temp2"]) != len(
                                    res["timex"]):
                                res["temp2"] = [-1] * len(res["timex"])
                            res["timeindex"] = [
                                0, 0, 0, 0, 0, 0,
                                len(res["timex"]) - 1, 0
                            ]
                    break
            if ET_idx is not None:
                break
    except:
        pass

    # extra temperature curves (only if ET or BT and its corresponding timex was already parsed successfully)
    if len(res["timex"]) > 0:
        channel = 1  # toggle between channel 1 and 2 to be filled with extra temperature curve data
        for CT_idx, sn in enumerate(sheet_names):
            sn = sn.strip()
            if CT_idx != BT_idx and CT_idx != ET_idx:  # all but temp and non-temp curves but for the already processed ET and BT curves
                if sn in extra_temp_curve_trans:
                    temp_curve = True
                elif sn in extra_nontemp_curve_trans:
                    temp_curve = False
                else:
                    continue
                try:
                    extra_curve_name = sn

                    # we split of the "Curve -" prefix
                    for px in curve_prefixa:
                        if extra_curve_name.startswith(px):
                            sp = extra_curve_name.split(px)
                            if len(sp) > 1:
                                extra_curve_name = sp[1]
                            else:
                                extra_curve_name = sp[0]
                            extra_curve_name = extra_curve_name.strip()
                            break

                    # we split of also the "temp." postfix
                    for px in curve_postfixa:
                        if extra_curve_name.endswith(px):
                            extra_curve_name = extra_curve_name.split(
                                px)[0].strip()
                            break

                    CT_sh = book.sheet_by_index(CT_idx)
                    if CT_sh.ncols >= 1:
                        time = CT_sh.col(0)
                        temp = CT_sh.col(1)
                        if len(time) > 0 and len(temp) > 0 and len(
                                time) == len(temp):
                            if "extradevices" not in res:
                                res["extradevices"] = []
                            if "extraname1" not in res:
                                res["extraname1"] = []
                            if "extraname2" not in res:
                                res["extraname2"] = []
                            if "extratimex" not in res:
                                res["extratimex"] = []
                            if "extratemp1" not in res:
                                res["extratemp1"] = []
                            if "extratemp2" not in res:
                                res["extratemp2"] = []
                            if "extramathexpression1" not in res:
                                res["extramathexpression1"] = []
                            if "extramathexpression2" not in res:
                                res["extramathexpression2"] = []
                            if "extraNoneTempHint1" not in res:
                                res["extraNoneTempHint1"] = []
                            if "extraNoneTempHint2" not in res:
                                res["extraNoneTempHint2"] = []
                            if channel == 1:
                                channel = 2
                                if temp_curve:
                                    # apply temp conversion
                                    res["extraNoneTempHint1"].append(False)
                                else:
                                    # no temp conversion
                                    res["extraNoneTempHint1"].append(True)
                                res["extradevices"].append(
                                    25)  # Virtual Device
                                res["extraname1"].append(
                                    encodeLocal(extra_curve_name))
                                res["extratimex"].append(
                                    [t.value for t in time[1:]])
                                res["extratemp1"].append(
                                    [t.value for t in temp[1:]])
                                res["extramathexpression1"].append("")
                            elif (len(time) - 1) == len(
                                    res["extratimex"][-1]
                            ):  # only if time lengths is same as of channel 1
                                channel = 1
                                if temp_curve:
                                    # apply temp conversion
                                    res["extraNoneTempHint2"].append(False)
                                else:
                                    # no temp conversion
                                    res["extraNoneTempHint2"].append(True)
                                res["extraname2"].append(
                                    encodeLocal(extra_curve_name))
                                res["extratemp2"].append(
                                    [t.value for t in temp[1:]])
                                res["extramathexpression2"].append("")
                except:
                    pass
        if "extraname1" in res and "extraname2" in res and len(
                res["extraname1"]) != len(res["extraname2"]):
            # we add an empty second extra channel if needed
            res["extraname2"].append("Extra 2")
            res["extratemp2"].append([-1] * len(res["extratemp1"][-1]))
            res["extraNoneTempHint2"].append(True)
            res["extramathexpression2"].append("")

        # add events
        try:
            COMMENTS_idx = 1
            try:
                sheet_names.index("Comments")
            except:
                pass
            COMMENTS_sh = book.sheet_by_index(COMMENTS_idx)
            gas_event = False  # set to True if a Gas event exists
            airflow_event = False  # set to True if an Airflow event exists
            specialevents = []
            specialeventstype = []
            specialeventsvalue = []
            specialeventsStrings = []
            if COMMENTS_sh.ncols >= 4:
                takeClosest = lambda num, collection: min(
                    collection, key=lambda x: abs(x - num))
                for r in range(COMMENTS_sh.nrows):
                    if r > 0:
                        try:
                            time = COMMENTS_sh.cell(r, 0).value
                            comment_type = COMMENTS_sh.cell(r, 2).value.strip()
                            if comment_type not in turning_point_trans:  # TP is ignored as it is automatically assigned
                                comment_value = COMMENTS_sh.cell(r, 3).value
                                c = takeClosest(time, res["timex"])
                                timex_idx = res["timex"].index(c)
                                if comment_type in color_change_trans:
                                    res["timeindex"][1] = timex_idx
                                elif comment_type in first_crack_trans:
                                    res["timeindex"][2] = timex_idx
                                elif comment_type == "First crack end":
                                    res["timeindex"][3] = timex_idx
                                elif comment_type in second_crack_trans:
                                    res["timeindex"][4] = timex_idx
                                elif comment_type == "Second crack end":
                                    res["timeindex"][5] = timex_idx
                                elif comment_type == "Duration":
                                    res["timeindex"][6] = timex_idx
                                else:
                                    specialevents.append(timex_idx)
                                    ae = False
                                    ge = False
                                    if comment_type in airflow_trans:
                                        ae = True
                                        airflow_event = True
                                        specialeventstype.append(0)
                                    elif comment_type in gas_trans:
                                        ge = True
                                        gas_event = True
                                        specialeventstype.append(3)
                                    else:
                                        specialeventstype.append(4)
                                    try:
                                        v = float(comment_value)
                                        v = v / 10. + 1
                                        specialeventsvalue.append(v)
                                    except:
                                        specialeventsvalue.append(0)
                                    if not ae and not ge and comment_type not in comment_trans:
                                        specialeventsStrings.append(
                                            encodeLocal(comment_type))
                                    else:
                                        specialeventsStrings.append(
                                            encodeLocal(comment_value))
                        except:
                            pass
            if len(specialevents) > 0:
                res["specialevents"] = specialevents
                res["specialeventstype"] = specialeventstype
                res["specialeventsvalue"] = specialeventsvalue
                res["specialeventsStrings"] = specialeventsStrings
                if gas_event or airflow_event:
                    # first set etypes to defaults
                    res["etypes"] = [
                        QApplication.translate("ComboBox", "Air", None),
                        QApplication.translate("ComboBox", "Drum", None),
                        QApplication.translate("ComboBox", "Damper", None),
                        QApplication.translate("ComboBox", "Burner", None),
                        "--"
                    ]
                    # update
                    if airflow_event:
                        res["etypes"][0] = "Airflow"
                    if gas_event:
                        res["etypes"][3] = "Gas"
        except:
            #            import traceback
            #           import sys
            #           traceback.print_exc(file=sys.stdout)
            pass
    return res
Beispiel #8
0
def extractProfilePetronciniCSV(file, _):
    res = {}  # the interpreted data set

    res["samplinginterval"] = 1.0

    csvFile = io.open(file, 'r', newline="", encoding='utf-8')
    data = csv.reader(csvFile, delimiter=';')
    #read file header
    next(data)  # skip "Export path"
    next(data)  # skip path
    header = [i.strip() for i in next(data)]

    roast_date = None
    power = None  # holds last processed heater event value
    power_last = None  # holds the heater event value before the last one
    power_event = False  # set to True if a heater event exists
    specialevents = []
    specialeventstype = []
    specialeventsvalue = []
    specialeventsStrings = []
    timex = []
    temp1 = []  # outlet temperature as ET
    temp2 = []  # bean temperature
    extra1 = []  # inlet temperature
    extra2 = []  # burner percentage
    timeindex = [
        -1, 0, 0, 0, 0, 0, 0, 0
    ]  #CHARGE index init set to -1 as 0 could be an actal index used
    i = 0
    for row in data:
        if row == []:
            continue
        i = i + 1
        items = list(zip(header, row))
        item = {}
        for (name, value) in items:
            item[name] = value.strip()
        # take i as time in seconds
        timex.append(i)
        # extract roast_date
        if roast_date is None and 'Year' in item and 'Month' in item and 'Day' in item and 'Hour' in item and 'Minute' in item and 'Second' in item:
            try:
                date = QDate(int(item['Year']), int(item['Month']),
                             int(item['Day']))
                time = QTime(int(item['Hour']), int(item['Minute']),
                             int(item['Second']))
                roast_date = QDateTime(date, time)
            except:
                passs
        #
        if 'Outlet Temperature' in item:
            temp1.append(float(item['Outlet Temperature']))
        else:
            temp1.append(-1)
        if 'Beans Temperature' in item:
            temp2.append(float(item['Beans Temperature'].replace(",", ".")))
        else:
            temp2.append(-1)
        # mark CHARGE
        if not timeindex[0] > -1:
            timeindex[0] = i
        # mark DROP
        if timeindex[0] > -1 and i > 0:
            timeindex[6] = i - 1
        # add ror, power, speed and pressure
        if 'Inlet Temperature' in item:
            extra1.append(float(item['Inlet Temperature']))
        else:
            extra1.append(-1)
        if 'Burner Percentage' in item:
            extra2.append(float(item['Burner Percentage']))
        else:
            extra2.append(-1)

        if "Burner Percentage" in item:
            try:
                v = float(item["Burner Percentage"])
                if v != power:
                    # power value changed
                    if v == power_last:
                        # just a fluctuation, we remove the last added power value again
                        power_last_idx = next(
                            i for i in reversed(range(len(specialeventstype)))
                            if specialeventstype[i] == 3)
                        del specialeventsvalue[power_last_idx]
                        del specialevents[power_last_idx]
                        del specialeventstype[power_last_idx]
                        del specialeventsStrings[power_last_idx]
                        power = power_last
                        power_last = None
                    else:
                        power_last = power
                        power = v
                        power_event = True
                        v = v / 10. + 1
                        specialeventsvalue.append(v)
                        specialevents.append(i)
                        specialeventstype.append(3)
                        specialeventsStrings.append(item["power"] + "%")
                else:
                    power_last = None
            except Exception:
                pass

    csvFile.close()

    res["timex"] = timex
    res["temp1"] = replace_duplicates(temp1)
    res["temp2"] = replace_duplicates(temp2)
    res["timeindex"] = timeindex

    res["extradevices"] = [25]
    res["extratimex"] = [timex[:]]

    res["extraname1"] = ["IT"]
    res["extratemp1"] = [extra1]
    res["extramathexpression1"] = [""]

    res["extraname2"] = ["burner"]
    res["extratemp2"] = [replace_duplicates(extra2)]
    res["extramathexpression2"] = [""]

    # set date
    if roast_date is not None and roast_date.isValid():
        res["roastdate"] = encodeLocal(roast_date.date().toString())
        res["roastisodate"] = encodeLocal(roast_date.date().toString(
            Qt.ISODate))
        res["roasttime"] = encodeLocal(roast_date.time().toString())
        res["roastepoch"] = int(roast_date.toTime_t())
        res["roasttzoffset"] = libtime.timezone

    if len(specialevents) > 0:
        res["specialevents"] = specialevents
        res["specialeventstype"] = specialeventstype
        res["specialeventsvalue"] = specialeventsvalue
        res["specialeventsStrings"] = specialeventsStrings
        if power_event or speed_event:
            # first set etypes to defaults
            res["etypes"] = [
                QApplication.translate("ComboBox", "Air", None),
                QApplication.translate("ComboBox", "Drum", None),
                QApplication.translate("ComboBox", "Damper", None),
                QApplication.translate("ComboBox", "Burner", None), "--"
            ]
    return res