diff --git a/e2e/calculate-all-params.e2e-spec.ts b/e2e/calculate-all-params.e2e-spec.ts
index ace899e9eafbbf0eb508fdfb9969f4b577dc94a5..f865b65b4acae6de12ea0668c6806f48a2e0e41d 100644
--- a/e2e/calculate-all-params.e2e-spec.ts
+++ b/e2e/calculate-all-params.e2e-spec.ts
@@ -23,7 +23,7 @@ describe("ngHyd − calculate all parameters of all calculators", () => {
     11, 12, 13, 15, 17, 18, 19, 20,
     21,
     // 22 - Solveur is not calculated here because it is not independent
-    23, 24
+    23, 24, 25
   ];
 
   // for each calculator
diff --git a/e2e/check-translations.e2e-spec.ts b/e2e/check-translations.e2e-spec.ts
index 40799429fd5507884c8fdc8b9cb6b368e2c9f089..69b622c5962ad22d05a394779a253e3dfdc5c113 100644
--- a/e2e/check-translations.e2e-spec.ts
+++ b/e2e/check-translations.e2e-spec.ts
@@ -25,7 +25,7 @@ describe("ngHyd − check translation of all calculators", () => {
   });
 
   // get calculators list (IDs) @TODO read it from config, but can't import jalhyd here :/
-  const calcTypes = [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 17, 18, 19, 20, 21, 22, 23, 24 ];
+  const calcTypes = [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25 ];
 
   // options of "Language" selector on preferences page
   const langs = [ "English", "Français" ];
diff --git a/e2e/clone-all-calc.e2e-spec.ts b/e2e/clone-all-calc.e2e-spec.ts
index 4f0b7c3d4c831e6e4deaeb316730ba53763e7158..95168422ee8dd785c34b8602bfb069d271cfbb2c 100644
--- a/e2e/clone-all-calc.e2e-spec.ts
+++ b/e2e/clone-all-calc.e2e-spec.ts
@@ -23,7 +23,7 @@ describe("ngHyd − clone all calculators with all possible <select> values", ()
     11, 12, 13, 15, 17, 18, 19, 20,
     21,
     // 22 - Solveur is not cloned here because it is not independent
-    23, 24
+    23, 24, 25
   ];
 
   // for each calculator
diff --git a/src/app/calculators/spp/spp.config.json b/src/app/calculators/spp/spp.config.json
new file mode 100644
index 0000000000000000000000000000000000000000..0c3c2427242c7a15825837810866460d93f825a8
--- /dev/null
+++ b/src/app/calculators/spp/spp.config.json
@@ -0,0 +1,38 @@
+[
+    {
+        "id": "fs_spp",
+        "type": "fieldset",
+        "defaultOperation": "SUM",
+        "fields": [
+            {
+                "id": "select_spp_operation",
+                "type": "select",
+                "source": "spp_operation"
+            },
+            "Y"
+        ]
+    },
+    {
+        "id": "fs_yaxn",
+        "type": "fieldset_template",
+        "calcType": "YAXN",
+        "fields": [
+            "A",
+            "X",
+            "N"
+        ]
+    },
+    {
+        "id": "yaxn_container",
+        "type": "template_container",
+        "templates": [
+            "fs_yaxn"
+        ]
+    },
+    {
+        "type": "options",
+        "idCal": "Y",
+        "operationSelectId": "select_spp_operation",
+        "_help": "util/trigo.html"
+    }
+]
\ No newline at end of file
diff --git a/src/app/calculators/spp/spp.en.json b/src/app/calculators/spp/spp.en.json
new file mode 100644
index 0000000000000000000000000000000000000000..cbc4a10f3562024223f4537006b243e6b316b3ec
--- /dev/null
+++ b/src/app/calculators/spp/spp.en.json
@@ -0,0 +1,17 @@
+{
+    "fs_spp": "Parameters",
+
+    "select_spp_operation": "Operation",
+    "select_spp_operation_0": "Sum",
+    "select_spp_operation_1": "Product",
+
+    "Y": "Y",
+
+    "yaxn_container": "Powers",
+
+    "fs_yaxn": "Power",
+
+    "A": "A",
+    "X": "X",
+    "N": "N"
+}
\ No newline at end of file
diff --git a/src/app/calculators/spp/spp.fr.json b/src/app/calculators/spp/spp.fr.json
new file mode 100644
index 0000000000000000000000000000000000000000..dd2ae4c5b2f68fead14d3e1e64e0ec221cdd1237
--- /dev/null
+++ b/src/app/calculators/spp/spp.fr.json
@@ -0,0 +1,17 @@
+{
+    "fs_spp": "Paramètres",
+
+    "select_spp_operation": "Opération",
+    "select_spp_operation_0": "Somme",
+    "select_spp_operation_1": "Produit",
+
+    "Y": "Y",
+
+    "yaxn_container": "Puissances",
+
+    "fs_yaxn": "Puissance",
+
+    "A": "A",
+    "X": "X",
+    "N": "N"
+}
\ No newline at end of file
diff --git a/src/app/components/calculator-list/calculator-list.component.ts b/src/app/components/calculator-list/calculator-list.component.ts
index 94552a7d5f97a6680ad543569b18048308af6d1f..ac72b2ff360ae15ffbe60f0428614f1234935742 100644
--- a/src/app/components/calculator-list/calculator-list.component.ts
+++ b/src/app/components/calculator-list/calculator-list.component.ts
@@ -12,6 +12,7 @@ import { FormulairePab } from "../../formulaire/definition/concrete/form-pab";
 import { HttpService } from "../../services/http.service";
 import { AppComponent } from "../../app.component";
 import { FormulaireMacrorugoCompound } from "../../formulaire/definition/concrete/form-macrorugo-compound";
+import { FormulaireSPP } from "../../formulaire/definition/concrete/form-spp";
 
 
 @Component({
@@ -87,6 +88,7 @@ export class CalculatorListComponent implements OnInit {
                         t !== CalculatorType.Structure
                         && t !== CalculatorType.Section
                         && t !== CalculatorType.CloisonAval
+                        && t !== CalculatorType.YAXN
                     ) {
                         unusedTheme.calculators.push({
                             type: t,
@@ -135,6 +137,15 @@ export class CalculatorListComponent implements OnInit {
                     }
                 }
             }
+            // on ajoute un YAXN après l'ouverture du module de calcul "somme / produit de puissances"
+            if (f instanceof FormulaireSPP) {
+                for (const e of f.allFormElements) {
+                    if (e instanceof FieldsetContainer) {
+                        e.addFromTemplate(0);
+                        break;
+                    }
+                }
+            }
         });
     }
 
diff --git a/src/app/formulaire/definition/concrete/form-spp.ts b/src/app/formulaire/definition/concrete/form-spp.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a587364130bb8fc91af3ddccce44e0e372dcfd57
--- /dev/null
+++ b/src/app/formulaire/definition/concrete/form-spp.ts
@@ -0,0 +1,139 @@
+import { FormulaireBase } from "./form-base";
+import { FieldsetTemplate } from "../../fieldset-template";
+import { FieldSet } from "../../fieldset";
+import { FormulaireNode } from "../../formulaire-node";
+import { FieldsetContainer } from "../../fieldset-container";
+
+import { SPP, Nub, Props, Session, YAXN } from "jalhyd";
+
+/**
+ * Formulaire pour "somme / produit de puissances"
+ */
+export class FormulaireSPP extends FormulaireBase {
+
+    /* constructor() {
+        super();
+        this._formResult = new FormResultPab(this);
+
+        // remove obsolete observer set by super()
+        this.removeObserver(this._formCompute);
+        this._formCompute = new FormComputePab(this, (this._formResult as FormResultPab));
+    } */
+
+    /* public get pabNub(): Pab {
+        return this.currentNub as Pab;
+    } */
+
+    public get sppNub(): SPP {
+        return this.currentNub as SPP;
+    }
+
+    private createYaxnNub(templ: FieldsetTemplate): Nub {
+        const params = {};
+        params["calcType"] = templ.calcTypeFromConfig;
+        return this.createYaxn(new Props(params));
+    }
+
+    /**
+     * ajoute un nub YAXN
+     * @param mr nub à ajouter
+     * @param after position après laquelle insérer le nub, à la fin sinon
+     */
+    private addYaxnNub(mr: YAXN, after?: number) {
+        this.sppNub.addChild(mr, after);
+    }
+
+    /**
+     * Asks JaLHyd to create a YAXN nub as a child of the current Calculator Module
+     * and return it; does not store it in the Session (for YAXN, not for Calculator Modules)
+     * @param p properties for the new Nub
+     */
+    protected createYaxn(p: Props): YAXN {
+        return Session.getInstance().createNub(p, this.sppNub) as YAXN;
+    }
+
+    /**
+     * Replaces the given YAXN sn in the current calculator module,
+     * with a new one built with properties "params"
+     * @param mr YAXN to replace
+     * @param params properties to build the new Nub (calcType)
+     */
+    protected replaceNub(mr: YAXN, params: Props): Nub {
+        const parent = this.sppNub;
+        const newStructure = this.createYaxn(params);
+        parent.replaceChildInplace(mr, newStructure);
+        return newStructure;
+    }
+
+    public createFieldset(parent: FormulaireNode, json: {}, data?: {}, nub?: Nub): FieldSet {
+        if (json["calcType"] === "YAXN") {
+            // indice après lequel insérer le nouveau FieldSet
+            const after = data["after"];
+
+            const res: FieldSet = new FieldSet(parent);
+            let mrn: Nub;
+            if (nub) { // use existing Nub (build interface based on model)
+                mrn = nub;
+            } else {
+                mrn = this.createYaxnNub(data["template"]);
+                this.addYaxnNub(mrn as YAXN, after);
+            }
+            res.setNub(mrn, false);
+
+            if (after !== undefined) {
+                parent.kids.splice(after + 1, 0, res);
+            } else {
+                parent.kids.push(res);
+            }
+
+            this.resetResults();
+
+            return res;
+        } else {
+            return super.createFieldset(parent, json, data);
+        }
+    }
+
+    public moveFieldsetUp(fs: FieldSet) {
+        if (fs.nub instanceof YAXN) {
+            // déplacement du nub
+            fs.nub.parent.moveChildUp(fs.nub);
+            // déplacement du fieldset
+            this.fieldsetContainer.moveFieldsetUp(fs);
+
+            this.resetResults();
+        } else {
+            super.moveFieldsetUp(fs);
+        }
+    }
+
+    public moveFieldsetDown(fs: FieldSet) {
+        if (fs.nub instanceof YAXN) {
+            // déplacement du nub
+            fs.nub.parent.moveChildDown(fs.nub);
+            // déplacement du fieldset
+            this.fieldsetContainer.moveFieldsetDown(fs);
+
+            this.resetResults();
+        } else { super.moveFieldsetDown(fs); }
+    }
+
+    public removeFieldset(fs: FieldSet) {
+        if (fs.nub instanceof YAXN) {
+            // suppression du sous-nub dans le Nub parent
+            this.sppNub.deleteChild(fs.nub.findPositionInParent());
+            // suppression du fieldset
+            this.fieldsetContainer.removeFieldset(fs);
+
+            this.resetResults();
+        } else { super.removeFieldset(fs); }
+    }
+
+    private get fieldsetContainer(): FieldsetContainer {
+        const n = this.getFormulaireNodeById("yaxn_container");
+        if (n === undefined || !(n instanceof FieldsetContainer)) {
+            throw new Error("l'élément 'yaxn_container' n'est pas du type FieldsetContainer");
+        }
+        return n as FieldsetContainer;
+    }
+}
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index cca4508d69b7809b040ef1c0f0a5605a0681f8f2..64d33ddf1062056b3554792c1a2fc49da219da59 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -13,6 +13,7 @@ import {
     Solveur,
     TrigoOperation,
     TrigoUnit,
+    SPPOperation,
 } from "jalhyd";
 
 import { FormulaireElement } from "./formulaire-element";
@@ -295,6 +296,10 @@ export class FieldSet extends FormulaireElement implements Observer {
                 this.setSelectValueFromProperty("select_operation", "trigoOperation");
                 this.setSelectValueFromProperty("select_unit", "trigoUnit");
                 break;
+
+            case "spp_operation": // SPP
+                this.setSelectValueFromProperty("spp_operation", "sppOperation");
+                break;
         }
     }
 
@@ -360,6 +365,7 @@ export class FieldSet extends FormulaireElement implements Observer {
         this.setPropertyValueFromConfig(json, "varCalc", "varCalc");
         this.setPropertyValueFromConfig(json, "defaultOperation", "trigoOperation", TrigoOperation);
         this.setPropertyValueFromConfig(json, "defaultUnit", "trigoUnit", TrigoUnit);
+        this.setPropertyValueFromConfig(json, "defaultOperation", "sppOperation", SPPOperation);
 
         this.updateFields();
     }
@@ -458,6 +464,9 @@ export class FieldSet extends FormulaireElement implements Observer {
                         case "select_unit": // Trigo
                             this.setPropValue("trigoUnit", data.value.value);
                             break;
+                        case "select_spp_operation": // SPP
+                            this.setPropValue("sppOperation", data.value.value);
+                            break;
                     }
                     break;
             }
diff --git a/src/app/formulaire/select-field.ts b/src/app/formulaire/select-field.ts
index 174fc258c664be103a99cd378fa711daa3ee8237..eeed82a32dfcc790056c811a20d0f53398c264e5 100644
--- a/src/app/formulaire/select-field.ts
+++ b/src/app/formulaire/select-field.ts
@@ -10,7 +10,8 @@ import {
     GrilleType,
     GrilleProfile,
     TrigoUnit,
-    TrigoOperation
+    TrigoOperation,
+    SPPOperation
  } from "jalhyd";
 
 import { Field } from "./field";
@@ -208,6 +209,11 @@ export class SelectField extends Field {
                 this.addEntry(new SelectEntry(this._entriesBaseId + TrigoUnit.DEG, TrigoUnit.DEG));
                 this.addEntry(new SelectEntry(this._entriesBaseId + TrigoUnit.RAD, TrigoUnit.RAD));
                 break;
+
+            case "spp_operation": // SPP: opération (somme, produit)
+                this.addEntry(new SelectEntry(this._entriesBaseId + SPPOperation.SUM, SPPOperation.SUM));
+                this.addEntry(new SelectEntry(this._entriesBaseId + SPPOperation.PRODUCT, SPPOperation.PRODUCT));
+                break;
         }
 
         this.afterParseConfig();
diff --git a/src/app/services/formulaire.service.ts b/src/app/services/formulaire.service.ts
index 416056c4d436c6a3a177c20861bdf6e61423c6a3..0391989f5cf4d66530683e1e932f7a45bf8808b2 100644
--- a/src/app/services/formulaire.service.ts
+++ b/src/app/services/formulaire.service.ts
@@ -13,7 +13,8 @@ import {
     Pab,
     Props,
     Cloisons,
-    CloisonAval
+    CloisonAval,
+    SPP
 } from "jalhyd";
 
 import { ApplicationSetupService } from "./app-setup.service";
@@ -40,6 +41,7 @@ import { FormulaireGrille } from "../formulaire/definition/concrete/form-grille"
 import { FormulaireBief } from "../formulaire/definition/concrete/form-bief";
 import { FormulaireSolveur } from "../formulaire/definition/concrete/form-solveur";
 import { AppComponent } from "../app.component";
+import { FormulaireSPP } from "../formulaire/definition/concrete/form-spp";
 
 @Injectable()
 export class FormulaireService extends Observable {
@@ -88,6 +90,7 @@ export class FormulaireService extends Observable {
         this.calculatorPaths[CalculatorType.Solveur] = "solveur";
         this.calculatorPaths[CalculatorType.YAXB] = "yaxb";
         this.calculatorPaths[CalculatorType.Trigo] = "trigo";
+        this.calculatorPaths[CalculatorType.SPP] = "spp";
     }
 
     private get _intlService(): I18nService {
@@ -337,6 +340,10 @@ export class FormulaireService extends Observable {
                 f = new FormulaireSolveur();
                 break;
 
+            case CalculatorType.SPP:
+                f = new FormulaireSPP();
+                break;
+
             default:
                 f = new FormulaireBase();
         }
@@ -392,6 +399,18 @@ export class FormulaireService extends Observable {
                 }
             }
 
+            // add fieldsets for existing YAXN if needed
+            // (when loading session only)
+            if (f.currentNub instanceof SPP) {
+                for (const c of f.currentNub.getChildren()) {
+                    for (const e of f.allFormElements) {
+                        if (e instanceof FieldsetContainer) { // @TODO manage many containers one day ?
+                            e.addFromTemplate(0, undefined, c);
+                        }
+                    }
+                }
+            }
+
             // when creating a new Pab, add one wall with one device, plus the downwall
             // (when loading session, those items are already present)
             if (
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index 8b45f7600fae877ccba848f43dba7e517f8ecccc..9413246aa9b4eac6cccb6f86a2847b4c6c95f80f 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -157,6 +157,8 @@
     "INFO_CHILD_TYPE_STRUCTURE_PLUR": "devices",
     "INFO_CHILD_TYPE_MACRORUGO": "apron",
     "INFO_CHILD_TYPE_MACRORUGO_PLUR": "aprons",
+    "INFO_CHILD_TYPE_YAXN": "power",
+    "INFO_CHILD_TYPE_YAXN_PLUR": "powers",
     "INFO_FIELDSET_ADD": "Add",
     "INFO_FIELDSET_COPY": "Copy",
     "INFO_FIELDSET_REMOVE": "Remove",
@@ -444,6 +446,8 @@
     "INFO_SNACKBAR_SETTINGS_SAVED": "Settings saved on this device",
     "INFO_SOLVEUR_TITRE": "Multimodule solver",
     "INFO_SOLVEUR_TITRE_COURT": "Solver",
+    "INFO_SPP_TITRE": "Sum and product of powers",
+    "INFO_SPP_TITRE_COURT": "SPP",
     "INFO_THEME_CREDITS": "Credit",
     "INFO_THEME_DEVALAISON_TITRE": "Downstream migration",
     "INFO_THEME_DEVALAISON_DESCRIPTION": "Tools for dimensioning the structures present on the water intakes of hydroelectric power plants known as \"ichthyocompatible\" and consisting of fine grid planes associated with one or more outlets.",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index 4ae53cad428f802274cc75a4c567684c7df556e6..e7d40eb14ad12c44d5ff4eef59934969d3f5edca 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -157,6 +157,8 @@
     "INFO_CHILD_TYPE_STRUCTURE_PLUR": "ouvrages",
     "INFO_CHILD_TYPE_MACRORUGO": "radier",
     "INFO_CHILD_TYPE_MACRORUGO_PLUR": "radiers",
+    "INFO_CHILD_TYPE_YAXN": "puissance",
+    "INFO_CHILD_TYPE_YAXN_PLUR": "puissances",
     "INFO_FIELDSET_ADD": "Ajouter",
     "INFO_FIELDSET_COPY": "Copier",
     "INFO_FIELDSET_REMOVE": "Supprimer",
@@ -443,6 +445,8 @@
     "INFO_SNACKBAR_SETTINGS_SAVED": "Paramètres enregistrés sur cet appareil",
     "INFO_SOLVEUR_TITRE": "Solveur multimodule",
     "INFO_SOLVEUR_TITRE_COURT": "Solveur",
+    "INFO_SPP_TITRE": "Somme et produit de puissances",
+    "INFO_SPP_TITRE_COURT": "SPP",
     "INFO_THEME_CREDITS": "Crédit",
     "INFO_THEME_DEVALAISON_TITRE": "Dévalaison",
     "INFO_THEME_DEVALAISON_DESCRIPTION": "Outils de dimensionnements des ouvrages présents sur les prises d'eau des centrales hydroélectriques dites \"ichtyocompatibles\" et constituées de plans de grilles fines associés à un ou plusieurs exutoires.",