Initial commit edera-ml edera-ml origin/edera-ml
authorRoberto Stomeo <Roberto Stomeo@dyrectalab.local>
Tue, 1 Jul 2025 14:48:29 +0000 (16:48 +0200)
committerRoberto Stomeo <Roberto Stomeo@dyrectalab.local>
Tue, 1 Jul 2025 14:48:29 +0000 (16:48 +0200)
15 files changed:
edera-ml/.dockerignore [new file with mode: 0644]
edera-ml/.gitignore [new file with mode: 0644]
edera-ml/README.md [new file with mode: 0644]
edera-ml/bitbucket-pipelines.yml [new file with mode: 0644]
edera-ml/data/model.h5 [new file with mode: 0644]
edera-ml/data/plot.png [new file with mode: 0644]
edera-ml/main.py [new file with mode: 0644]
edera-ml/predictor.py [new file with mode: 0644]
edera-ml/requirements.txt [new file with mode: 0644]
edera-ml/scripts/docker/Dockerfile [new file with mode: 0644]
edera-ml/scripts/kube/api.yml [new file with mode: 0644]
edera-ml/scripts/service-account.json.base64 [new file with mode: 0644]
edera-ml/scripts/set-env.sh [new file with mode: 0644]
edera-ml/simulator.py [new file with mode: 0644]
edera-ml/test.py [new file with mode: 0644]

diff --git a/edera-ml/.dockerignore b/edera-ml/.dockerignore
new file mode 100644 (file)
index 0000000..02c3065
--- /dev/null
@@ -0,0 +1,3 @@
+env/*
+data/*.csv
+data/*.png
\ No newline at end of file
diff --git a/edera-ml/.gitignore b/edera-ml/.gitignore
new file mode 100644 (file)
index 0000000..d2c67ab
--- /dev/null
@@ -0,0 +1,6 @@
+*.csv
+__pycache__
+*.pyc
+*.pyo
+env/
+.idea/
\ No newline at end of file
diff --git a/edera-ml/README.md b/edera-ml/README.md
new file mode 100644 (file)
index 0000000..02b211e
--- /dev/null
@@ -0,0 +1,39 @@
+# Leggimi
+
+Lo scopo del progetto è fornire un modello che consenta di predire il numero di ore di utilizzo di una apparecchiatura 
+elettrica in un singolo giorno e di poter effettuare previsioni per i giorni successivi. 
+
+## Come funziona
+
+Il modello è stato sviluppato utilizzando la libreria [TensorFlow](https://www.tensorflow.org/) e la rete neurale LSTM.
+I dataset per l'addestramento sono stati generati manualmente.
+
+Puoi sempre generare nuovi dataset utilizzando `Simulator` come nell'esempio riportato di seguito:
+
+```python
+from simulator import Simulator
+from predictor import Predictor
+
+machines = 10   # Numero delle tipologie di apparecchiature elettromedicali da simulare.
+samples = 5000  # Numero dei campioni da generare
+
+simulator = Simulator()
+train_features, train_labels = simulator.generate(machines, samples, 'data/train.csv')
+test_features, test_labels = simulator.generate(machines, samples, 'data/test.csv')
+
+Predictor.train(train_features, train_labels, test_features, test_labels, "model.h5")
+```
+
+Successivamente puoi utilizzare il modello per effettuare previsioni:
+
+```python
+from predictor import Predictor
+
+predictor = Predictor("model.h5")
+
+# Previsione per il giorno 1
+features = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+prediction = predictor.predict(features)
+print(prediction)
+predictor.plot(prediction, "day1.png")
+```
diff --git a/edera-ml/bitbucket-pipelines.yml b/edera-ml/bitbucket-pipelines.yml
new file mode 100644 (file)
index 0000000..edafbc8
--- /dev/null
@@ -0,0 +1,54 @@
+image: google/cloud-sdk:412.0.0-slim
+definitions:
+  steps:
+    - step: &publish
+        name: "Build"
+        caches:
+          - maven
+          - pip
+        services:
+          - docker
+        script:
+          - source scripts/set-env.sh
+          - cat scripts/service-account.json.base64 | base64 --decode >> service-account.json
+          - gcloud auth activate-service-account --key-file service-account.json
+          - export VERSION=1.0.${BITBUCKET_BUILD_NUMBER}
+          - export IMAGE_BASE="eu.gcr.io/${PROJECT_ID}/${NAMESPACE}-${MODULE}"
+          - export IMAGE="${IMAGE_BASE}:${VERSION}"
+          - gcloud config set project ${PROJECT_ID}
+          - gcloud config set compute/zone europe-west1-b
+          - gcloud auth configure-docker
+          - >-
+            docker build -t ${IMAGE} --build-arg PROFILE=${PROFILE} -f ./scripts/docker/Dockerfile .
+          - docker push ${IMAGE}
+          - docker tag $IMAGE "${IMAGE_BASE}:latest"
+          - docker push "${IMAGE_BASE}:latest"
+          - git tag -a "${VERSION}" -m "version ${VERSION}"
+          - git push origin "${VERSION}"
+          - mkdir -p ./build
+          - echo $VERSION > ./build/VERSION
+          - echo $IMAGE > ./build/IMAGE
+        artifacts:
+          - build/**
+    - step: &deploy
+        name: Deploy
+        script:
+          - source scripts/set-env.sh
+          - export VERSION=$(cat ./build/VERSION)
+          - export IMAGE=$(cat ./build/IMAGE)
+          - cat scripts/service-account.json.base64 | base64 --decode >> service-account.json
+          - gcloud auth activate-service-account --key-file service-account.json
+          - gcloud config set project ${PROJECT_ID}
+          - gcloud config set compute/zone europe-west1-b
+          - apt install kubectl
+          - apt install google-cloud-sdk-gke-gcloud-auth-plugin
+          - export USE_GKE_GCLOUD_AUTH_PLUGIN=True
+          - gcloud container clusters get-credentials applica
+          - kubectl set image deployment/${MODULE} ${MODULE}=${IMAGE} -n ${NAMESPACE}
+        artifacts:
+          - build/**
+pipelines:
+  branches:
+    main:
+      - step: *publish
+      - step: *deploy
\ No newline at end of file
diff --git a/edera-ml/data/model.h5 b/edera-ml/data/model.h5
new file mode 100644 (file)
index 0000000..c74ca73
Binary files /dev/null and b/edera-ml/data/model.h5 differ
diff --git a/edera-ml/data/plot.png b/edera-ml/data/plot.png
new file mode 100644 (file)
index 0000000..9a7fd63
Binary files /dev/null and b/edera-ml/data/plot.png differ
diff --git a/edera-ml/main.py b/edera-ml/main.py
new file mode 100644 (file)
index 0000000..e01128c
--- /dev/null
@@ -0,0 +1,49 @@
+import json
+
+from flask import Flask, jsonify, request
+from predictor import Predictor, PredictionItemEncoder
+
+app = Flask(__name__)
+predictor = Predictor('data/model.h5')
+
+
+@app.route('/predict', methods=['POST'])
+def post_data():
+    json_request = request.get_json()
+    machine = json_request.get('machine')
+    day = json_request.get('day')
+    
+    max_hours = json_request.get('max_hours', 24)
+    max_days = json_request.get('max_days', 7)
+    
+    threshold = json_request.get('threshold', 0.5)
+    
+    breakpoint_hours = json_request.get('breakpoint_hours', 40)
+    accumulated_hours = json_request.get('accumulated_hours', 0)
+    
+    if machine is None or day is None:
+        return jsonify({'message': 'Machine and day are required'}), 400
+
+    if not isinstance(max_hours, int) or not isinstance(max_days, int):
+        return jsonify({'message': 'max_hours and max_days must be integers'}), 400
+    
+    if max_hours <= 0 or max_days <= 0:
+        return jsonify({'message': 'max_hours and max_days must be positive'}), 400
+    
+    predictions = predictor.predict_until_break(machine,
+                                                day,
+                                                max_hours,
+                                                max_days,
+                                                accumulated_hours,
+                                                breakpoint_hours,
+                                                threshold)
+    return jsonify(json.loads(json.dumps(predictions, cls=PredictionItemEncoder))), 200
+
+
+@app.route('/')
+def hello_world():
+    return jsonify({'message': 'Hello, World!'})
+
+
+if __name__ == '__main__':
+    app.run(debug=True, host='0.0.0.0')
diff --git a/edera-ml/predictor.py b/edera-ml/predictor.py
new file mode 100644 (file)
index 0000000..ed4d098
--- /dev/null
@@ -0,0 +1,217 @@
+import json
+import os
+# Disable Tensorflow warnings
+os.environ['TF_CPP_MIN_LOG_LEVEL'] = '4'
+
+import string
+import tensorflow as tf
+from typing import Hashable
+import matplotlib.pyplot as plt
+
+
+class PredictionItem:
+    """
+    A prediction item.
+    """
+    day = 0
+    hours = 0
+    accumulated_hours = 0
+    broken = False
+    
+    def __init__(self, day: int, hours: int, accumulated_hours: int, broken: bool):
+        """
+        Initialize the prediction item.
+        
+        :type day: int
+        :param day: The day of the year.
+        :type hours: int
+        :param hours: The number of hours.
+        :type accumulated_hours: int
+        :param accumulated_hours: The accumulated hours.
+        :type broken: bool
+        :param broken: Whether the machine has a break.
+        :rtype: None
+        """
+        self.day = day
+        self.hours = hours
+        self.accumulated_hours = accumulated_hours
+        self.broken = broken
+    
+    def __str__(self):
+        """
+        Convert the prediction item to a string.
+        
+        :rtype: string
+        :return: The string representation of the prediction item.
+        """
+        return 'Day: %d, Hours: %d, Accumulated Hours: %d, Has Break: %s' % (
+            self.day,
+            self.hours,
+            self.accumulated_hours,
+            self.broken
+        )
+
+
+class PredictionItemEncoder(json.JSONEncoder):
+    def default(self, obj):
+        if isinstance(obj, PredictionItem):
+            return {
+                "day": obj.day,
+                "hours": obj.hours,
+                "accumulated_hours": obj.accumulated_hours,
+                "broken": obj.broken
+            }
+        return json.JSONEncoder.default(self, obj)
+
+
+class Predictor:
+    """
+    A predictor for machine usage hours and break prediction.
+    """
+    model = None
+    
+    def __init__(self, model_path: string):
+        """
+        Initialize the predictor.
+        
+        :type model_path: string
+        :param model_path: The path to the model file.
+        :rtype: None
+        
+        :exception: Exception if the model file does not exist.
+        """
+        if not os.path.exists(model_path):
+            raise Exception('Model not found: %s' % model_path)
+        
+        self.model = tf.keras.models.load_model(model_path)
+    
+    @staticmethod
+    def train(train_features: Hashable,
+              train_labels: Hashable,
+              test_features: Hashable,
+              test_labels: Hashable,
+              path: string) -> list:
+        """
+        Train the model.
+        
+        :param train_features: The training features.
+        :type train_features: Hashable
+        :param train_labels: The training labels.
+        :type train_labels: Hashable
+        :param test_features: The test features.
+        :type test_features: Hashable
+        :param test_labels: The test labels.
+        :param path: The path to save the model.
+        :return: The test loss and accuracy.
+        """
+        samples = len(train_features)
+        model = tf.keras.Sequential([
+            tf.keras.layers.Embedding(samples, 3, input_length=3),
+            tf.keras.layers.LSTM(2),
+            tf.keras.layers.Dense(1, activation='sigmoid')
+        ])
+        
+        model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
+        model.fit(train_features, train_labels, epochs=50, batch_size=20)
+        model.save(path)
+        
+        test_loss, test_acc = model.evaluate(test_features, test_labels, verbose=2)
+        return [test_loss, test_acc]
+    
+    def predict_hours(self, machine: int, day: int, max_hours: int, threshold: float) -> int:
+        """
+        Predict how many hours the machine will be used on a given day.
+        :param machine: The machine type.
+        :param day: The day of the year.
+        :param max_hours: The maximum number of hours to predict.
+        :param threshold: The threshold to stop predicting.
+        :return: The number of hours the machine will be used.
+        """
+        hours = 1
+        while hours < max_hours:
+            prediction = self.model.predict([[machine, day, hours]])
+            if prediction.item() > threshold:
+                break
+            hours += 1
+        
+        return hours
+    
+    def predict(self,
+                machine: int,
+                day: int,
+                max_days: int,
+                max_hours: int,
+                accumulated_hours: int,
+                breakpoint_hours: int,
+                threshold: float) -> list[PredictionItem]:
+        """
+        Predict how many hours the machine will be used on a given day.
+        :param machine: The machine type.
+        :param day: The day of the year.
+        :param max_days: The maximum number of days to predict.
+        :param max_hours: The maximum number of hours to predict.
+        :param accumulated_hours: The accumulated hours.
+        :param breakpoint_hours: The breakpoint hours.
+        :param threshold: The threshold to use to validate the prediction.
+        :return: The number of hours the machine will be used.
+        """
+        predictions = []
+        while day < max_days:
+            hours = self.predict_hours(machine, day, max_hours, threshold)
+            day += 1
+            accumulated_hours += hours
+            broken = accumulated_hours >= breakpoint_hours
+            prediction_item = PredictionItem(day, hours, accumulated_hours, broken)
+            predictions.append(prediction_item)
+            
+        return predictions
+    
+    def predict_until_break(self,
+                            machine: int,
+                            day: int,
+                            max_hours: int,
+                            max_days: int,
+                            accumulated_hours: int,
+                            breakpoint_hours: int,
+                            threshold: float) -> list[PredictionItem]:
+        """
+        Predict machine usage hours until a break is predicted.
+        :param machine: The machine type.
+        :param day: The day of the year.
+        :param max_hours: The maximum number of hours to predict.
+        :param max_days: The maximum number of days to predict.
+        :param accumulated_hours: The accumulated hours.
+        :param breakpoint_hours: The breakpoint hours.
+        :param threshold: The threshold to use to validate the prediction.
+        :return: The number of hours the machine will be used.
+        """
+        predictions = []
+        max_days_to_predict = day + max_days
+        while day < max_days_to_predict:
+            hours = self.predict_hours(machine, day, max_hours, threshold)
+            day += 1
+            accumulated_hours += hours
+            broken = accumulated_hours >= breakpoint_hours
+            prediction_item = PredictionItem(day, hours, accumulated_hours, broken)
+            predictions.append(prediction_item)
+            
+            if broken:
+                break
+            
+        return predictions
+            
+    @staticmethod
+    def plot(predictions: list[PredictionItem], threshold: float, machine: int, path: string) -> None:
+        plt.clf()
+        # For each prediction show accumulated hours per day
+        plt.plot([prediction.day for prediction in predictions], [prediction.accumulated_hours for prediction in predictions])
+        # Show red circle when broken is True
+        plt.plot([prediction.day for prediction in predictions if prediction.broken],
+                 [prediction.accumulated_hours for prediction in predictions if prediction.broken], 'ro')
+        # Plot hours per day
+        plt.plot([prediction.day for prediction in predictions], [prediction.hours for prediction in predictions])
+        plt.xlabel('Day')
+        plt.ylabel('Hours')
+        plt.legend(['Total Hours', 'Breakpoint', 'Daily Hours'])
+        plt.title('Machine %d' % machine)
+        plt.savefig(path)
diff --git a/edera-ml/requirements.txt b/edera-ml/requirements.txt
new file mode 100644 (file)
index 0000000..c659d70
--- /dev/null
@@ -0,0 +1,6 @@
+pandas~=2.1.4
+scikit-learn
+matplotlib~=3.8.2
+flask~=3.0.1
+numpy~=1.26.3
+tensorflow
\ No newline at end of file
diff --git a/edera-ml/scripts/docker/Dockerfile b/edera-ml/scripts/docker/Dockerfile
new file mode 100644 (file)
index 0000000..66170c6
--- /dev/null
@@ -0,0 +1,12 @@
+FROM tensorflow/tensorflow
+
+WORKDIR /app
+COPY . /app
+
+# Install pip and then install the requirements
+RUN pip install --upgrade pip
+RUN pip install -r requirements.txt --ignore-installed
+# Configure firewall to allow traffic on port 5000
+
+EXPOSE 5000
+CMD ["python", "main.py"]
\ No newline at end of file
diff --git a/edera-ml/scripts/kube/api.yml b/edera-ml/scripts/kube/api.yml
new file mode 100644 (file)
index 0000000..e67dae4
--- /dev/null
@@ -0,0 +1,38 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: ml
+  labels:
+    app: ml
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: ml
+  template:
+    metadata:
+      labels:
+        app: ml
+    spec:
+      containers:
+        - image: eu.gcr.io/applica-general/edera-ml:latest
+          name: ml
+          ports:
+            - containerPort: 5000
+              name: ml
+          imagePullPolicy: Always
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: ml
+  name: ml
+spec:
+  type: NodePort
+  ports:
+    - port: 5000
+      targetPort: 5000
+      protocol: TCP
+  selector:
+    app: ml
diff --git a/edera-ml/scripts/service-account.json.base64 b/edera-ml/scripts/service-account.json.base64
new file mode 100644 (file)
index 0000000..6841642
--- /dev/null
@@ -0,0 +1,42 @@
+ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIsCiAgInByb2plY3RfaWQiOiAiYXBwbGljYS1n
+ZW5lcmFsIiwKICAicHJpdmF0ZV9rZXlfaWQiOiAiNDk3YzZmNTU5N2Y0NTI3NWY3ZTlmZTQwMzFj
+NGI4MjllMjY4NzEyMSIsCiAgInByaXZhdGVfa2V5IjogIi0tLS0tQkVHSU4gUFJJVkFURSBLRVkt
+LS0tLVxuTUlJRXZnSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2d3Z2dTa0FnRUFBb0lCQVFD
+dDRrY1B3UVJ6bUQ3b1xucHlVQVBlTnBnS01TWGxTM25CYmVtSUFlcmk3dm5GeHJIMFV4RlJndmRN
+VTlzRTJ6L0h5WXNTMGNnR1R3Y01Fd1xuQlVVYVF3UWpUMFVxdlBrN3ByY2lOWURSTXVNQXY1MW5Y
+WkpRZ0RYeitMSG15VldhaE1VRTlMczM4YzJxeUFpalxuVTBjakhqTmxsMmp3S2J5OWJVNUVUcWlT
+eEtxdE1FRVlwSUFzV2kvWG1iekx6NlVtSnladm56ZCtkRFNRQTU3aFxuY0NsOEFTdlBpQTdIeXVz
+b1lzVWJ2S2xRS0tmcndtV3NBUllEVjdRc0t6bEpsR0ViWE1sWlozdzB1VCszVkxXOVxuTWMyS1Fo
+SGpvaWpoTW9yRjdVdjVWMjJrTWkyL2V4RkI3L3ErbllCejU5K1MxUXZlRmpCd3E5L0gwQTRyTXpU
+a1xubWZDYzhjMWZBZ01CQUFFQ2dnRUFOR1dsdlRrUUl0Y2pTYzhvSnJEL2lLaXpPeE5DMnd0Rmx2
+RUVWbnB0ZVZXNFxuUWExc0Y3VEFFM2pRQU4xU0pPVDJGTHI3R1lZVkpLRU5qZTlnbWQvTTdPanpz
+a084cEwyQm5PVGJldTZuR2ZBalxudWVTbjlPc1ZsdjEvZWtoOEs3Skxma2xTNnpKSm8rZGdOdnNl
+eWhYTkxoVllrVm82WGlpRWQ2L3VPei9aSUpPVVxuTFlHN1hPVHExNWhKcmZaM2trdDBBRldJc2NK
+SXVFbFBlUTJOaDVWY3VSeVJwUVQwaFA0NUlRUTZFdElBWXp4OVxuUnRGUWFhd2tpWVFqZ0RkelFO
+Qm1tUHN2ZWc4Y2JkQXlWZVlxcitaVkFiNk1pWjJ2N01RR0hyeEFBcnhSeXRMZlxuMmd2VUl0K3pv
+dmJBemo3Q3h0YjUvS3JDdHUwMC9aWTcwNzRxTExnR1NRS0JnUUR2NXRkNmxWbUg5cXl3TThTc1xu
+NnA5NE5lTVFrbU9vME41SjBUUisxTGFITnBkdjBaY3B0bEJDS0lCTXorVTBBc0p0cG11dlpGTGdH
+cmp6U3E2cFxuVFlGUis0Mlc1NjJGMkoxWmlKZmdnWDdubWp3TmhUNzdrdWUwbjVWbE1iTHBUN0Iz
+ajB6V3Ftc0hqZ09lZWhDblxueEIwOXpKcXJEYm96MkV3UGVvRkhDVjB5dHdLQmdRQzVqVmpJd21F
+d01PU2g5UHF3QzJ5SVBYeS81UkpGTG4yL1xuNWxzUmhRbkkrTkxTZUJjWnRIV1BiOWcyNVF6SFVV
+dWRRNGhiYWpHaDM1R2t5SGtIcjJ5MGVjdTFUd2pVTitGMFxubzdhejRzekk4a3kzRk9sRUpvdXM4
+enFNQWsrYnhFR1hrUUNkWmFiL0ltS0svR1dRK21RcXlDV0RtUkFvR3NVcVxuTEJNdUhKOXltUUtC
+Z0YrRnJBRGNYT1RkWEk5Z1hZeDRjM3piQUFtR01IWjBqRDRhTmV2V2FNTllBbDU4dHRMZVxuREFE
+N3ZYSllTU3czZVJGTjlZekZ4cFlETGVkNXNpZ3BlemVZa1Iwb0xKaWgwcTFtelFxUXBXWTBySHE1
+dG9WWFxuVGpsR1hhY0liZk9tVGw2Y3lYeWtLSysrWlVTQjJBWGsrYnUwcjFVeXh4U0RxRzExV3Vw
+ZEdTWHJBb0dCQUo2YVxucm9CMGZvU2wxbGlGd2Q3RzlRK0RsMldqMWJraTQwUXNFRDNxZlJHM2R1
+V0cxeUFXdThKT3RQOC9UR3YzRm00blxuc3ArSkowR1ppN0hSMW5wMlBiSUt4ZENGN1NNUlhQckpr
+YnN6cXg0ODFzeEw2SlJqYWxMOFdWZ2lCWkE4OG1BdlxuQnRxRGNIcDNGc3A4c2doNXJ6Tk9mNXA4
+Tkc1RGE3TC9sNmw3dCtOSkFvR0JBT2d6MHM1WGErRzZLTmlVN0IySFxuc0l2MmRNMzZvNlZ3a21P
+T3FjdCtkVS8ycG12ZGM4aFpDbFZGYXBQeWZ4djFqVnNjSDUrZG5FaFJIaTEzYXdIU1xuVzc3MnQ5
+WUdBNWpGb2dnakVPRU5QbVR2QUwxd01NL0ExRCtmanVaWVArOEVDaEU0QmFmbVN3WWZGVzJRQmxE
+eFxuZlhoOHNmT0FnMjhuMEc1QWNGcXZVSFJNXG4tLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tXG4i
+LAogICJjbGllbnRfZW1haWwiOiAiYml0YnVja2V0LXBpcGVsaW5lc0BhcHBsaWNhLWdlbmVyYWwu
+aWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLAogICJjbGllbnRfaWQiOiAiMTA4Mzk0ODE1Njc3NDgx
+NDI2MjYwIiwKICAiYXV0aF91cmkiOiAiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1
+dGgyL2F1dGgiLAogICJ0b2tlbl91cmkiOiAiaHR0cHM6Ly9vYXV0aDIuZ29vZ2xlYXBpcy5jb20v
+dG9rZW4iLAogICJhdXRoX3Byb3ZpZGVyX3g1MDlfY2VydF91cmwiOiAiaHR0cHM6Ly93d3cuZ29v
+Z2xlYXBpcy5jb20vb2F1dGgyL3YxL2NlcnRzIiwKICAiY2xpZW50X3g1MDlfY2VydF91cmwiOiAi
+aHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9iaXRidWNr
+ZXQtcGlwZWxpbmVzJTQwYXBwbGljYS1nZW5lcmFsLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwK
+ICAidW5pdmVyc2VfZG9tYWluIjogImdvb2dsZWFwaXMuY29tIgp9Cg==
\ No newline at end of file
diff --git a/edera-ml/scripts/set-env.sh b/edera-ml/scripts/set-env.sh
new file mode 100644 (file)
index 0000000..5443338
--- /dev/null
@@ -0,0 +1,4 @@
+export PROJECT_ID=applica-general
+export PROJECT_NAME=applica-general
+export NAMESPACE=edera
+export MODULE=ml
\ No newline at end of file
diff --git a/edera-ml/simulator.py b/edera-ml/simulator.py
new file mode 100644 (file)
index 0000000..d79a461
--- /dev/null
@@ -0,0 +1,31 @@
+import pandas as pd
+import numpy as np
+import string
+
+
+class Simulator:
+    @staticmethod
+    def __generate_data(path: string, machines: int, size: int) -> None:
+        df = pd.DataFrame(columns=['machine', 'date', 'hours', 'failure'])
+        df['machine'] = np.random.randint(1, machines, size=size)
+        df['date'] = np.random.randint(1, 365, size=size)
+        df['hours'] = np.random.randint(1, 8, size=size)
+        df['failure'] = np.random.randint(0, 2, size=size)
+        df.to_csv(path, index=False)
+    
+    @staticmethod
+    def __load_data(path: string) -> pd.DataFrame:
+        df = pd.read_csv(path)
+        return df.sample(frac=1).reset_index(drop=True)
+    
+    @staticmethod
+    def __prepare(df: pd.DataFrame) -> pd.DataFrame:
+        features = df[['machine', 'date', 'hours']]
+        labels = df[['failure']]
+        
+        return features, labels
+    
+    def generate(self, machines: int, size: int, path: string) -> pd.DataFrame:
+        self.__generate_data(path, machines, size)
+        data = self.__load_data(path)
+        return self.__prepare(data)
diff --git a/edera-ml/test.py b/edera-ml/test.py
new file mode 100644 (file)
index 0000000..f090dca
--- /dev/null
@@ -0,0 +1,21 @@
+from simulator import Simulator
+from predictor import Predictor
+
+# Configuration
+
+# machines = 10  # Number of machine types to simulate
+# samples = 5000  # Number of samples to generate
+
+# simulator = Simulator()
+# train_features, train_labels = simulator.generate(machines, samples, 'data/train.csv')
+# test_features, test_labels = simulator.generate(machines, samples, 'data/test.csv')
+
+predictor = Predictor('data/model.h5')
+
+# predictions = predictor.predict(1, 1, 30, 8, 9, 120, 0.51)
+predictions = predictor.predict_until_break(1, 1, 8, 0, 120, 0.51)
+for prediction in predictions:
+    print(prediction)
+
+
+predictor.plot(predictions, 0.7, 0, 'data/plot.png')