diff --git a/Power-consumption-keras/.dockerignore b/Power-consumption-keras/.dockerignore
new file mode 100644
index 0000000..8ba9024
--- /dev/null
+++ b/Power-consumption-keras/.dockerignore
@@ -0,0 +1,4 @@
+data
+seed.npz
+*.tgz
+*.tar.gz
\ No newline at end of file
diff --git a/Power-consumption-keras/.gitignore b/Power-consumption-keras/.gitignore
new file mode 100644
index 0000000..4e531b1
--- /dev/null
+++ b/Power-consumption-keras/.gitignore
@@ -0,0 +1,6 @@
+data
+*.npz
+*.tgz
+*.tar.gz
+.mnist-keras
+client.yaml
\ No newline at end of file
diff --git a/Power-consumption-keras/Dockerfile b/Power-consumption-keras/Dockerfile
new file mode 100644
index 0000000..e953af6
--- /dev/null
+++ b/Power-consumption-keras/Dockerfile
@@ -0,0 +1,16 @@
+FROM python:3.10.6-slim as base
+LABEL maintainer="salman@scaleoutsystems.com"
+WORKDIR /app
+COPY requirements.txt .
+RUN apt-get update \ 
+    && apt-get install --no-install-recommends -y git \
+    && apt-get clean \
+    && rm -rf /var/lib/apt/lists/* \ 
+    && pip install git+https://github.com/scaleoutsystems/fedn.git@master#egg=fedn\&subdirectory=fedn \
+    && pip install --no-cache-dir -r requirements.txt
+
+
+FROM python:3.10.6-slim as build
+COPY --from=base /usr/local/lib/python3.10/site-packages/ /usr/local/lib/python3.10/site-packages/
+COPY --from=base /usr/local/bin/fedn /usr/local/bin/
+WORKDIR /app
diff --git a/Power-consumption-keras/client/data.py b/Power-consumption-keras/client/data.py
new file mode 100644
index 0000000..8566130
--- /dev/null
+++ b/Power-consumption-keras/client/data.py
@@ -0,0 +1,68 @@
+import os
+import shutil
+from math import floor
+
+import numpy as np
+dir_path = os.path.dirname(os.path.realpath(__file__))
+abs_path = os.path.abspath(dir_path)
+
+def copy_files(source_dir, destination_dir):
+    # Get a list of all files in the source directory
+    files = os.listdir(source_dir)
+
+    # Iterate through the files and copy them to the destination directory
+    for file_name in files:
+        source_file = os.path.join(source_dir, file_name)
+        destination_file = os.path.join(destination_dir, file_name)
+        shutil.copy2(source_file, destination_file)
+        print(f"Copied {file_name} to {destination_file}")
+
+def get_data(out_dir='data'):
+    # Make dir if necessary
+    if not os.path.exists(out_dir):
+        os.mkdir(out_dir)
+   
+    print('dir_path: ', dir_path)
+    parent_dir = os.path.abspath(os.path.join(dir_path, os.pardir))
+    print('parent_dir: ', parent_dir)
+     
+    source_dir = parent_dir+'/data'
+    destination_dir = dir_path+'/data'
+
+    copy_files(source_dir, destination_dir)
+
+def load_data(data_path=None, is_train=True):
+    """ Load data from disk.
+
+    :param data_path: Path to data file.
+    :type data_path: str
+    :param is_train: Whether to load training or test data.
+    :type is_train: bool
+    :return: Tuple of data and labels.
+    :rtype: tuple
+    """
+    if data_path is None:
+        data_path = os.environ.get("FEDN_DATA_PATH", abs_path+'/data/power.npz')
+
+    data = np.load(data_path)
+
+    if is_train:
+        X = data['x_train']
+        y = data['y_train']
+    else:
+        X = data['x_test']
+        y = data['y_test']
+
+    # Normalize
+    X = X / 255
+
+    return X, y
+
+if __name__ == '__main__':
+    # Prepare data if not already done
+    if not os.path.exists(abs_path+'/data'):
+        print('Note: The data directory does not exist. Loading the data..')
+        get_data()
+    else:
+        print('Good to go, The data directory exist.')
+
diff --git a/Power-consumption-keras/client/fedn.yaml b/Power-consumption-keras/client/fedn.yaml
new file mode 100644
index 0000000..4d08c58
--- /dev/null
+++ b/Power-consumption-keras/client/fedn.yaml
@@ -0,0 +1,10 @@
+python_env: python_env.yaml
+entry_points:
+  build:
+    command: python model.py
+  startup:
+    command: python data.py
+  train:
+    command: python train.py
+  validate:
+    command: python validate.py
diff --git a/Power-consumption-keras/client/model.py b/Power-consumption-keras/client/model.py
new file mode 100644
index 0000000..ac188e0
--- /dev/null
+++ b/Power-consumption-keras/client/model.py
@@ -0,0 +1,36 @@
+
+import json
+import os
+
+import numpy as np
+import tensorflow as tf
+
+
+from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics
+
+HELPER_MODULE = 'numpyhelper'
+helper = get_helper(HELPER_MODULE)
+
+
+def compile_model(img_rows=28, img_cols=28):
+    # Set input shape
+    #input_shape = (img_rows, img_cols, 1)
+
+    # Define model
+    opt = tf.keras.optimizers.SGD(lr=0.0001)
+    model = tf.keras.models.Sequential()
+    model.add(tf.keras.layers.Dense(64, input_dim=4, activation="relu"))
+    model.add(tf.keras.layers.Dense(32, activation="relu"))
+    model.add(tf.keras.layers.Dense(1, activation="linear"))
+    #model.summary()
+    model.compile(loss = "mse", optimizer = opt,metrics=['mae'])
+
+    return model
+
+def init_seed(out_path='seed.npz'):
+
+    weights = compile_model().get_weights()
+    helper.save(weights, out_path)
+
+if __name__ == "__main__":
+    init_seed('../seed.npz')
diff --git a/Power-consumption-keras/client/python_env.yaml b/Power-consumption-keras/client/python_env.yaml
new file mode 100644
index 0000000..cb2ca00
--- /dev/null
+++ b/Power-consumption-keras/client/python_env.yaml
@@ -0,0 +1,9 @@
+name: power-consumption-keras
+build_dependencies:
+  - pip
+  - setuptools
+  - wheel==0.37.1
+dependencies:
+  - tensorflow==2.13.1
+  - fire==0.3.1
+  - fedn==0.9.0
diff --git a/Power-consumption-keras/client/python_env_macosx.yaml b/Power-consumption-keras/client/python_env_macosx.yaml
new file mode 100644
index 0000000..d13fc46
--- /dev/null
+++ b/Power-consumption-keras/client/python_env_macosx.yaml
@@ -0,0 +1,10 @@
+name: mnist-keras
+build_dependencies:
+  - pip
+  - setuptools
+  - wheel==0.37.1
+dependencies:
+  - tensorflow-macos
+  - tensorflow-metal 
+  - fire==0.3.1
+  - fedn==0.9.0b2
diff --git a/Power-consumption-keras/client/train.py b/Power-consumption-keras/client/train.py
new file mode 100644
index 0000000..bd04942
--- /dev/null
+++ b/Power-consumption-keras/client/train.py
@@ -0,0 +1,64 @@
+import json
+import os
+import sys
+import fire
+import numpy as np
+import tensorflow as tf
+
+from data import load_data
+from model import compile_model
+
+from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics
+
+HELPER_MODULE = 'numpyhelper'
+helper = get_helper(HELPER_MODULE)
+
+def train(in_model_path, out_model_path, data_path=None, batch_size=32, epochs=1):
+    """ Complete a model update.
+
+    Load model paramters from in_model_path (managed by the FEDn client),
+    perform a model update, and write updated paramters
+    to out_model_path (picked up by the FEDn client).
+
+    :param in_model_path: The path to the input model.
+    :type in_model_path: str
+    :param out_model_path: The path to save the output model to.
+    :type out_model_path: str
+    :param data_path: The path to the data file.
+    :type data_path: str
+    :param batch_size: The batch size to use.
+    :type batch_size: int
+    :param epochs: The number of epochs to train.
+    :type epochs: int
+    """
+    # Load data
+    x_train, y_train = load_data(data_path)
+
+    # Load model
+    model = compile_model()
+    #weights = load_parameters(in_model_path)
+    weights = helper.load(in_model_path)
+    model.set_weights(weights)
+
+    # Train
+    model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs)
+
+    # Metadata needed for aggregation server side
+    metadata = {
+        # num_examples are mandatory
+        'num_examples': len(x_train),
+        'batch_size': batch_size,
+        'epochs': epochs,
+    }
+
+    # Save JSON metadata file (mandatory)
+    save_metadata(metadata, out_model_path)
+
+    # Save model update (mandatory)
+    weights = model.get_weights()
+    helper.save(weights, out_model_path)
+
+
+if __name__ == "__main__":
+    train(sys.argv[1], sys.argv[2])
+
diff --git a/Power-consumption-keras/client/validate.py b/Power-consumption-keras/client/validate.py
new file mode 100644
index 0000000..ec1ed98
--- /dev/null
+++ b/Power-consumption-keras/client/validate.py
@@ -0,0 +1,59 @@
+import json
+import os
+import sys
+import fire
+import numpy as np
+import tensorflow as tf
+
+from data import load_data
+from model import compile_model
+
+from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics
+
+HELPER_MODULE = 'numpyhelper'
+helper = get_helper(HELPER_MODULE)
+
+dir_path = os.path.dirname(os.path.realpath(__file__))
+abs_path = os.path.abspath(dir_path)
+
+
+def validate(in_model_path, out_json_path, data_path=None):
+    """ Validate model.
+
+    :param in_model_path: The path to the input model.
+    :type in_model_path: str
+    :param out_json_path: The path to save the output JSON to.
+    :type out_json_path: str
+    :param data_path: The path to the data file.
+    :type data_path: str
+    """
+
+    # Load data
+    x_train, y_train = load_data(data_path)
+    x_test, y_test = load_data(data_path, is_train=False)
+
+    # Load model
+    model = compile_model()
+    helper = get_helper(HELPER_MODULE)
+    weights = helper.load(in_model_path)
+    model.set_weights(weights)
+
+    # Evaluate
+    model_score = model.evaluate(x_train, y_train)
+    model_score_test = model.evaluate(x_test, y_test)
+    y_pred = model.predict(x_test)
+    y_pred = np.argmax(y_pred, axis=1)
+
+    # JSON schema
+    report = {
+        "training_loss": model_score[0],
+        "training_accuracy": model_score[1],
+        "test_loss": model_score_test[0],
+        "test_accuracy": model_score_test[1],
+    }
+
+    # Save JSON
+    save_metrics(report, out_json_path)
+
+if __name__ == "__main__":
+    validate(sys.argv[1], sys.argv[2])
diff --git a/Power-consumption-keras/requirements-macos.txt b/Power-consumption-keras/requirements-macos.txt
new file mode 100644
index 0000000..4770f97
--- /dev/null
+++ b/Power-consumption-keras/requirements-macos.txt
@@ -0,0 +1,4 @@
+tensorflow-macos
+tensorflow-metal 
+fire==0.3.1
+docker==5.0.2
diff --git a/Power-consumption-keras/requirements.txt b/Power-consumption-keras/requirements.txt
new file mode 100644
index 0000000..0572615
--- /dev/null
+++ b/Power-consumption-keras/requirements.txt
@@ -0,0 +1,4 @@
+tensorflow==2.13.1
+fire==0.3.1
+docker==6.1.1
+fedn==0.9.0
diff --git a/Power-consumption-pytorch/.dockerignore b/Power-consumption-pytorch/.dockerignore
new file mode 100644
index 0000000..8ba9024
--- /dev/null
+++ b/Power-consumption-pytorch/.dockerignore
@@ -0,0 +1,4 @@
+data
+seed.npz
+*.tgz
+*.tar.gz
\ No newline at end of file
diff --git a/Power-consumption-pytorch/.gitignore b/Power-consumption-pytorch/.gitignore
new file mode 100644
index 0000000..8bc2232
--- /dev/null
+++ b/Power-consumption-pytorch/.gitignore
@@ -0,0 +1,6 @@
+data
+*.npz
+*.tgz
+*.tar.gz
+.power-consumption-pytorch
+client.yaml
diff --git a/Power-consumption-pytorch/Dockerfile b/Power-consumption-pytorch/Dockerfile
new file mode 100644
index 0000000..e953af6
--- /dev/null
+++ b/Power-consumption-pytorch/Dockerfile
@@ -0,0 +1,16 @@
+FROM python:3.10.6-slim as base
+LABEL maintainer="salman@scaleoutsystems.com"
+WORKDIR /app
+COPY requirements.txt .
+RUN apt-get update \ 
+    && apt-get install --no-install-recommends -y git \
+    && apt-get clean \
+    && rm -rf /var/lib/apt/lists/* \ 
+    && pip install git+https://github.com/scaleoutsystems/fedn.git@master#egg=fedn\&subdirectory=fedn \
+    && pip install --no-cache-dir -r requirements.txt
+
+
+FROM python:3.10.6-slim as build
+COPY --from=base /usr/local/lib/python3.10/site-packages/ /usr/local/lib/python3.10/site-packages/
+COPY --from=base /usr/local/bin/fedn /usr/local/bin/
+WORKDIR /app
diff --git a/Power-consumption-pytorch/client/data.py b/Power-consumption-pytorch/client/data.py
new file mode 100644
index 0000000..e89b80a
--- /dev/null
+++ b/Power-consumption-pytorch/client/data.py
@@ -0,0 +1,114 @@
+import os
+import shutil
+from math import floor
+
+import torch
+import torchvision
+import numpy as np
+dir_path = os.path.dirname(os.path.realpath(__file__))
+abs_path = os.path.abspath(dir_path)
+
+def copy_files(source_dir, destination_dir):
+    # Get a list of all files in the source directory
+    files = os.listdir(source_dir)
+
+    # Iterate through the files and copy them to the destination directory
+    for file_name in files:
+        source_file = os.path.join(source_dir, file_name)
+        destination_file = os.path.join(destination_dir, file_name)
+        shutil.copy2(source_file, destination_file)
+        print(f"Copied {file_name} to {destination_file}")
+
+
+def get_data(out_dir='data'):
+    # Make dir if necessary
+    if not os.path.exists(out_dir):
+        os.mkdir(out_dir)
+   
+    print('dir_path: ', dir_path)
+    parent_dir = os.path.abspath(os.path.join(dir_path, os.pardir))
+    print('parent_dir: ', parent_dir)
+     
+    source_dir = parent_dir+'/data'
+    destination_dir = dir_path+'/data'
+
+    copy_files(source_dir, destination_dir)
+
+def load_data(data_path=None, is_train=True):
+    """ Load data from disk.
+
+    :param data_path: Path to data file.
+    :type data_path: str
+    :param is_train: Whether to load training or test data.
+    :type is_train: bool
+    :return: Tuple of data and labels.
+    :rtype: tuple
+    """
+    if data_path is None:
+        data_path = os.environ.get("FEDN_DATA_PATH", abs_path+'/data/power.npz')
+
+    data = np.load(data_path)
+
+    if is_train:
+        X = data['x_train']
+        y = data['y_train']
+    else:
+        X = data['x_test']
+        y = data['y_test']
+
+    # Normalize
+    X = X / 255
+
+    return X, y
+
+
+def splitset(dataset, parts):
+    n = dataset.shape[0]
+    local_n = floor(n/parts)
+    result = []
+    for i in range(parts):
+        result.append(dataset[i*local_n: (i+1)*local_n])
+    return result
+
+
+def split(out_dir='data'):
+
+    n_splits = int(os.environ.get("FEDN_NUM_DATA_SPLITS", 2))
+
+    # Make dir
+    if not os.path.exists(f'{out_dir}/clients'):
+        os.mkdir(f'{out_dir}/clients')
+
+    # Load and convert to dict
+    train_data = torchvision.datasets.MNIST(
+        root=f'{out_dir}/train', transform=torchvision.transforms.ToTensor, train=True)
+    test_data = torchvision.datasets.MNIST(
+        root=f'{out_dir}/test', transform=torchvision.transforms.ToTensor, train=False)
+    data = {
+        'x_train': splitset(train_data.data, n_splits),
+        'y_train': splitset(train_data.targets, n_splits),
+        'x_test': splitset(test_data.data, n_splits),
+        'y_test': splitset(test_data.targets, n_splits),
+    }
+
+    # Make splits
+    for i in range(n_splits):
+        subdir = f'{out_dir}/clients/{str(i+1)}'
+        if not os.path.exists(subdir):
+            os.mkdir(subdir)
+        torch.save({
+            'x_train': data['x_train'][i],
+            'y_train': data['y_train'][i],
+            'x_test': data['x_test'][i],
+            'y_test': data['y_test'][i],
+        },
+            f'{subdir}/mnist.pt')
+
+
+if __name__ == '__main__':
+    # Prepare data if not already done
+    if not os.path.exists(abs_path+'/data'):
+        print('Note: The data directory does not exist. Loading the data..')
+        get_data()
+    else: 
+        print('Good to go, The data directory exist.')
diff --git a/Power-consumption-pytorch/client/fedn.yaml b/Power-consumption-pytorch/client/fedn.yaml
new file mode 100644
index 0000000..b055041
--- /dev/null
+++ b/Power-consumption-pytorch/client/fedn.yaml
@@ -0,0 +1,10 @@
+python_env: python_env.yaml
+entry_points:
+  build:
+    command: python model.py
+  startup:
+    command: python data.py
+  train:
+    command: python train.py
+  validate:
+    command: python validate.py
\ No newline at end of file
diff --git a/Power-consumption-pytorch/client/model.py b/Power-consumption-pytorch/client/model.py
new file mode 100644
index 0000000..3bfcf9d
--- /dev/null
+++ b/Power-consumption-pytorch/client/model.py
@@ -0,0 +1,104 @@
+import collections
+
+import torch
+
+from fedn.utils.helpers.helpers import get_helper
+
+HELPER_MODULE = 'numpyhelper'
+helper = get_helper(HELPER_MODULE)
+
+from torch.nn import Linear
+from torch.nn import ReLU
+from torch.nn import Sigmoid
+from torch.nn import Module
+from torch.optim import SGD
+
+from torch.nn.init import kaiming_uniform_
+from torch.nn.init import xavier_uniform_
+
+
+def compile_model():
+    """ Compile the pytorch model.
+
+    :return: The compiled model.
+    :rtype: torch.nn.Module
+    """
+    class Net(torch.nn.Module):
+        def __init__(self):
+            super(Net, self).__init__()
+
+            self.hidden1 = torch.nn.Linear(4, 64)
+            kaiming_uniform_(self.hidden1.weight, nonlinearity='relu')
+            self.act1 = ReLU()
+
+            self.hidden2 = Linear(64, 32)
+            kaiming_uniform_(self.hidden2.weight, nonlinearity='relu')
+            self.act2 = ReLU()
+
+            self.hidden3 = Linear(32, 1)
+            xavier_uniform_(self.hidden3.weight)
+
+
+        def forward(self, x):
+
+            # input to first hidden layer
+            x = self.hidden1(x)
+            x = self.act1(x)
+
+            # second hidden layer
+            x = self.hidden2(x)
+            x = self.act2(x)
+
+            # third hidden layer and output
+            x = self.hidden3(x)
+            #x = self.act3(x) 
+
+            return x
+
+    # Return model
+    return Net()
+
+
+
+def save_parameters(model, out_path):
+    """ Save model paramters to file.
+
+    :param model: The model to serialize.
+    :type model: torch.nn.Module
+    :param out_path: The path to save to.
+    :type out_path: str
+    """
+    parameters_np = [val.cpu().numpy() for _, val in model.state_dict().items()]
+    helper.save(parameters_np, out_path)
+
+
+def load_parameters(model_path):
+    """ Load model parameters from file and populate model.
+
+    param model_path: The path to load from.
+    :type model_path: str
+    :return: The loaded model.
+    :rtype: torch.nn.Module
+    """
+    model = compile_model()
+    parameters_np = helper.load(model_path)
+
+    params_dict = zip(model.state_dict().keys(), parameters_np)
+    state_dict = collections.OrderedDict({key: torch.tensor(x) for key, x in params_dict})
+    model.load_state_dict(state_dict, strict=True)
+    return model
+
+
+def init_seed(out_path='seed.npz'):
+    """ Initialize seed model and save it to file.
+
+    :param out_path: The path to save the seed model to.
+    :type out_path: str
+    """
+    # Init and save
+    model = compile_model()
+    save_parameters(model, out_path)
+
+
+if __name__ == "__main__":
+    init_seed('../seed.npz')
diff --git a/Power-consumption-pytorch/client/python_env.yaml b/Power-consumption-pytorch/client/python_env.yaml
new file mode 100644
index 0000000..5b0f872
--- /dev/null
+++ b/Power-consumption-pytorch/client/python_env.yaml
@@ -0,0 +1,9 @@
+name: power-consumption-pytorch
+build_dependencies:
+  - pip
+  - setuptools
+  - wheel==0.37.1
+dependencies:
+  - torch==2.2.1
+  - torchvision==0.17.1
+  - fedn==0.9.0
diff --git a/Power-consumption-pytorch/client/train.py b/Power-consumption-pytorch/client/train.py
new file mode 100644
index 0000000..f69e9bc
--- /dev/null
+++ b/Power-consumption-pytorch/client/train.py
@@ -0,0 +1,87 @@
+import math
+import os
+import sys
+
+import torch
+from data import load_data
+from model import load_parameters, save_parameters
+
+import numpy as np
+from fedn.utils.helpers.helpers import save_metadata
+
+dir_path = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.abspath(dir_path))
+
+
+def train(in_model_path, out_model_path, data_path=None, batch_size=32, epochs=1, lr=0.01):
+    """ Complete a model update.
+
+    Load model paramters from in_model_path (managed by the FEDn client),
+    perform a model update, and write updated paramters
+    to out_model_path (picked up by the FEDn client).
+
+    :param in_model_path: The path to the input model.
+    :type in_model_path: str
+    :param out_model_path: The path to save the output model to.
+    :type out_model_path: str
+    :param data_path: The path to the data file.
+    :type data_path: str
+    :param batch_size: The batch size to use.
+    :type batch_size: int
+    :param epochs: The number of epochs to train.
+    :type epochs: int
+    :param lr: The learning rate to use.
+    :type lr: float
+    """
+    # Load data
+    x_train, y_train = load_data(data_path)
+
+    # Load parmeters and initialize model
+    model = load_parameters(in_model_path)
+
+    # Train
+    optimizer = torch.optim.SGD(model.parameters(), lr=lr)
+    n_batches = int(math.ceil(len(x_train) / batch_size))
+    #criterion = torch.nn.NLLLoss()
+    criterion = torch.nn.L1Loss()
+
+    for e in range(epochs):  # epoch loop
+        for b in range(n_batches):  # batch loop
+            # Retrieve current batch
+            #batch_x = x_train[b * batch_size:(b + 1) * batch_size]
+            batch_x_tmp = torch.from_numpy(x_train[b * batch_size:(b + 1) * batch_size])
+            batch_x = torch.tensor(batch_x_tmp, dtype=torch.float32)
+
+            #batch_y = y_train[b * batch_size:(b + 1) * batch_size]
+            batch_y_tmp = torch.from_numpy(np.expand_dims(y_train[b * batch_size:(b + 1) * batch_size],-1))
+            batch_y = torch.tensor(batch_y_tmp, dtype=torch.float32)
+
+            # Train on batch
+            optimizer.zero_grad()
+            outputs = model(batch_x)
+            loss = criterion(outputs, batch_y)
+            loss.backward()
+            optimizer.step()
+            # Log
+            if b % 100 == 0:
+                print(
+                    f"Epoch {e}/{epochs-1} | Batch: {b}/{n_batches-1} | Loss: {loss.item()}")
+
+    # Metadata needed for aggregation server side
+    metadata = {
+        # num_examples are mandatory
+        'num_examples': len(x_train),
+        'batch_size': batch_size,
+        'epochs': epochs,
+        'lr': lr
+    }
+
+    # Save JSON metadata file (mandatory)
+    save_metadata(metadata, out_model_path)
+
+    # Save model update (mandatory)
+    save_parameters(model, out_model_path)
+
+
+if __name__ == "__main__":
+    train(sys.argv[1], sys.argv[2])
diff --git a/Power-consumption-pytorch/client/validate.py b/Power-consumption-pytorch/client/validate.py
new file mode 100644
index 0000000..aabdba4
--- /dev/null
+++ b/Power-consumption-pytorch/client/validate.py
@@ -0,0 +1,69 @@
+import os
+import sys
+
+import torch
+from data import load_data
+from model import load_parameters
+
+import numpy as np
+
+from fedn.utils.helpers.helpers import save_metrics
+
+dir_path = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.abspath(dir_path))
+
+
+def validate(in_model_path, out_json_path, data_path=None):
+    """ Validate model.
+
+    :param in_model_path: The path to the input model.
+    :type in_model_path: str
+    :param out_json_path: The path to save the output JSON to.
+    :type out_json_path: str
+    :param data_path: The path to the data file.
+    :type data_path: str
+    """
+    # Load data
+    x_train, y_train = load_data(data_path)
+    x_test, y_test = load_data(data_path, is_train=False)
+
+    # Load model
+    model = load_parameters(in_model_path)
+    model.eval()
+
+    # Evaluate
+    criterion_mae = torch.nn.L1Loss()
+    criterion_mse = torch.nn.MSELoss()
+    with torch.no_grad():
+        x_train_t = torch.tensor(x_train, dtype=torch.float32)
+        train_out = model(x_train_t)
+
+        y_train = torch.from_numpy(np.expand_dims(y_train,-1))
+        y_train_t = torch.tensor(y_train, dtype=torch.float32)
+
+        training_loss_mae = criterion_mae(train_out, y_train_t)
+        training_loss_mse = criterion_mse(train_out, y_train_t)
+
+        x_test_t = torch.tensor(x_test, dtype=torch.float32)
+        test_out = model(x_test_t)
+
+        y_test = torch.from_numpy(np.expand_dims(y_test,-1))
+        y_test_t = torch.tensor(y_test, dtype=torch.float32)
+
+        test_loss_mae = criterion_mae(test_out, y_test_t)
+        test_loss_mse = criterion_mse(test_out, y_test_t)  
+
+    # JSON schema
+    report = {
+        "test_mae": str(test_loss_mae.item()),
+        "test_mse": str(test_loss_mse.item()),
+        "training_mae": str(training_loss_mae.item()),
+        "training_mse": str(training_loss_mse.item()),
+
+    }
+
+    # Save JSON
+    save_metrics(report, out_json_path) 
+
+if __name__ == "__main__":
+    validate(sys.argv[1], sys.argv[2])
diff --git a/Power-consumption-pytorch/requirements.txt b/Power-consumption-pytorch/requirements.txt
new file mode 100644
index 0000000..09253d8
--- /dev/null
+++ b/Power-consumption-pytorch/requirements.txt
@@ -0,0 +1,4 @@
+torch==2.2.1
+torchvision==0.17.1
+fedn==0.9.0
+