Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f93bd79
feat: bump version number to v1.0
rmroot Feb 11, 2026
4723730
Merge pull request #186 from ORNL-AMO/epic-183
rmroot Feb 11, 2026
b448f8c
chore: remove emissions from MB database
rmroot Feb 26, 2026
4a27bcf
bugfix: example data
rmroot Feb 26, 2026
28ce7d0
Merge pull request #190 from ORNL-AMO/issue-187
rmroot Feb 26, 2026
7dc0c54
Merge pull request #189 from ORNL-AMO/issue-188
rmroot Feb 26, 2026
057f410
chore: find and replace NEBs and other emissions language
rmroot Mar 2, 2026
9f2d182
fixup: new example
rmroot Mar 2, 2026
1e6a3d9
Merge pull request #191 from ORNL-AMO/issue-188-b
rmroot Mar 2, 2026
24fabf0
fixup: remove MBs
rmroot Mar 3, 2026
6446115
Merge pull request #192 from ORNL-AMO/issue-188-c
RLiNREL Mar 3, 2026
8b7049e
fixup: updated slideshow, examples and words
rmroot Mar 5, 2026
a03742e
Merge pull request #193 from ORNL-AMO/issue-188-c
rmroot Mar 5, 2026
1018749
chore: migrate to ng v21
rmroot Mar 6, 2026
15765f1
deps: update dependencies to address vulnerabilities
rmroot Mar 6, 2026
d898413
Merge pull request #194 from ORNL-AMO/issue-184-b
rmroot Mar 6, 2026
75076c0
fix utility URL
rmroot Mar 6, 2026
86b4068
Merge pull request #195 from ORNL-AMO/issue-184-b
rmroot Mar 6, 2026
480a093
chore: increased water savings benefit
rmroot Mar 20, 2026
25019e2
Merge pull request #197 from ORNL-AMO/fixup
rmroot Mar 20, 2026
b3c6556
changes water savings language
rmroot Mar 23, 2026
891b980
MBs in excel report
rmroot Mar 23, 2026
90c9dc7
update export buttons for custom reports
rmroot Mar 23, 2026
079ac7e
Merge pull request #198 from ORNL-AMO/water-savings
rmroot Mar 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ ALLIANCE FOR ENERGY INNOVATION, LLC, UT-BATTELLE, LLC AND THE GOVERNMENT MAKE NO
****************************************************************************************************************
NOTICE TO USERS

Note that users should be aware that finding non-energy benefits can vary and are not guaranteed. The use of the JUSTIFI tool does not constitute or imply any endorsement, recommendation, or favoring of any entity, product, or service. Please refer to the terms and disclaimer located in this LICENSE file and in the application's documentation for more information.
Note that users should be aware that finding multiple benefits can vary and are not guaranteed. The use of the JUSTIFI tool does not constitute or imply any endorsement, recommendation, or favoring of any entity, product, or service. Please refer to the terms and disclaimer located in this LICENSE file and in the application's documentation for more information.
****************************************************************************************************************
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[![Last Commit](https://img.shields.io/github/last-commit/ORNL-AMO/JUSTIFI)](https://github.com/ORNL-AMO/JUSTIFI/commits/main)


JUSTIFI is a tool that is a member of **Oak Ridge National Laboratory's Industrial Resources** suite of applications. The application was developed in collaboration with the **National Laboratory of the Rockies (NLR, formerly known as NREL)** around the study of **Non-Energy Benefits (NEBs)**. JUSTIFI allows users to identify the Key Performance Metrics (KPM) for a manufacturing facility and quantify the impacts of NEBs from implementation of energy efficiency opportunities.
JUSTIFI is a tool that is a member of **Oak Ridge National Laboratory's Industrial Resources** suite of applications. The application was developed in collaboration with the **National Laboratory of the Rockies (NLR, formerly known as NREL)** around the study of **Multiple Benefits (MBs)**. JUSTIFI allows users to identify the Key Performance Metrics (KPM) for a manufacturing facility and quantify the impacts of MBs from implementation of energy efficiency opportunities.

JUSTIFI, like it's sibling applications [MEASUR](https://github.com/ORNL-AMO/MEASUR) and [VERIFI](https://github.com/ORNL-AMO/VERIFI), is developed as a web application but is also packaged and distributed as an installable desktop application.

Expand All @@ -24,8 +24,8 @@ Alternatively, downloads and additional information about ORNL's suite of tools
Track our progress and planned work on the [JUSTIFI GitHub Project Board](https://github.com/orgs/ORNL-AMO/projects/9/views/17).


# Non-Energy Benefits Research
NLR led the research effort around the study and quantification of NEBs. For more information visit the [Non-energy Benefits Knowledge Library](https://www.nrel.gov/manufacturing/non-energy-benefits).
# Multiple Benefits Research
NLR led the research effort around the study and quantification of MBs. For more information visit the [Multiple Benefits Knowledge Library](https://www.nlr.gov/manufacturing/multiple-benefits-of-manufacturing-energy-productivity).


# Details For Developers
Expand Down
7,167 changes: 3,372 additions & 3,795 deletions package-lock.json

Large diffs are not rendered by default.

48 changes: 24 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "justifi",
"version": "0.2.6-beta",
"version": "1.0.0",
"main": "main.js",
"author": "National Laboratory of the Rockies & Oak Ridge National Laboratory",
"description": "A tool to identify, estimate potential and report on non-energy benefits (NEBs) that can be found while conducting energy assessments",
"description": "A tool to identify, estimate potential and report on multiple benefits (MBs) that can be found while conducting energy assessments",
"repository": "https://github.com/ORNL-AMO/JUSTIFI.git",
"engines": {
"node": "24.5.0",
Expand All @@ -24,7 +24,7 @@
"private": true,
"build": {
"appId": "gov.ornl.justifi",
"copyright": "Copyright 2025 ORNL. All rights reserved.",
"copyright": "Copyright 2026 ORNL. All rights reserved.",
"productName": "JUSTIFI",
"directories": {
"output": "./output/"
Expand Down Expand Up @@ -65,29 +65,29 @@
}
},
"dependencies": {
"@angular/animations": "20.3.15",
"@angular/animations": "21.2.1",
"@angular/cdk": "20.2.14",
"@angular/common": "20.3.15",
"@angular/compiler": "20.3.15",
"@angular/core": "20.3.15",
"@angular/forms": "20.3.15",
"@angular/platform-browser": "20.3.15",
"@angular/platform-browser-dynamic": "20.3.15",
"@angular/router": "20.3.15",
"@angular/service-worker": "20.3.15",
"@fortawesome/angular-fontawesome": "3.0.0",
"@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@angular/common": "21.2.1",
"@angular/compiler": "21.2.1",
"@angular/core": "21.2.1",
"@angular/forms": "21.2.1",
"@angular/platform-browser": "21.2.1",
"@angular/platform-browser-dynamic": "21.2.1",
"@angular/router": "21.2.1",
"@angular/service-worker": "21.2.1",
"@fortawesome/angular-fontawesome": "4.0.0",
"@fortawesome/fontawesome-svg-core": "7.2.0",
"@fortawesome/free-solid-svg-icons": "7.2.0",
"angular-plotly.js": "6.0.0",
"bootstrap": "5.3.8",
"electron-log": "5.4.0",
"electron-updater": "6.6.2",
"exceljs": "4.4.0",
"jszip": "3.10.1",
"libphonenumber-js": "1.11.3",
"ngx-indexed-db": "19.3.2",
"ngx-webstorage": "20.0.0",
"plotly.js": "3.0.0",
"ngx-indexed-db": "22.0.0",
"ngx-webstorage": "21.0.1",
"plotly.js": "3.4.0",
"rxjs": "~7.8.0",
"semver": "7.6.2",
"tslib": "2.7.0",
Expand All @@ -96,13 +96,13 @@
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular/build": "20.3.12",
"@angular/cli": "20.3.12",
"@angular/compiler-cli": "20.3.15",
"@angular/build": "^21.2.1",
"@angular/cli": "21.2.1",
"@angular/compiler-cli": "21.2.1",
"@popperjs/core": "^2.11.8",
"@types/jasmine": "5.1.4",
"electron": "36.2.0",
"electron-builder": "26.0.12",
"electron": "40.8.0",
"electron-builder": "26.8.1",
"jasmine-core": "5.3.0",
"karma": "~6.4.4",
"karma-chrome-launcher": "~3.2.0",
Expand All @@ -113,4 +113,4 @@
"pptxgenjs": "3.12.0",
"typescript": "~5.9.3"
}
}
}
45 changes: 23 additions & 22 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
<div class="d-flex flex-column" [ngClass]="{'neb-container': !print}">
<app-navbar class="hide-print"></app-navbar>
<ng-template [ngIf]="dataInitialized" [ngIfElse]="initializingDataBlock">
<div class="d-flex w-100">
<div class="d-flex">
<app-sidebar></app-sidebar>
</div>
<div class="w-100 main-content" (click)="collapseSidebar()">
<router-outlet></router-outlet>
<app-toast-notifications *ngIf="!print"></app-toast-notifications>
</div>
</div>
</ng-template>
<ng-template #initializingDataBlock>
Initializing data...
</ng-template>
<ng-container *ngIf="!print">
<app-loading></app-loading>
<app-setup-wizard-modal></app-setup-wizard-modal>
<app-auto-update-toast></app-auto-update-toast>
<app-welcome-slideshow></app-welcome-slideshow>
<app-update-check></app-update-check>
</ng-container>
<app-navbar class="hide-print"></app-navbar>
@if (dataInitialized) {
<div class="d-flex w-100">
<div class="d-flex">
<app-sidebar></app-sidebar>
</div>
<div class="w-100 main-content" (click)="collapseSidebar()">
<router-outlet></router-outlet>
@if (!print) {
<app-toast-notifications></app-toast-notifications>
}
</div>
</div>
} @else {
Initializing data...
}
@if (!print) {
<app-loading></app-loading>
<app-setup-wizard-modal></app-setup-wizard-modal>
<app-auto-update-toast></app-auto-update-toast>
<app-welcome-slideshow></app-welcome-slideshow>
<app-update-check></app-update-check>
}
</div>
2 changes: 1 addition & 1 deletion src/app/core-components/about/about.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ <h4>
</p>
<p class="alert alert-info">
NOTICE TO USERS<br>
Note that users should be aware that finding non-energy benefits can vary and are not guaranteed.
Note that users should be aware that finding multiple benefits can vary and are not guaranteed.
The use of the JUSTIFI tool does not constitute or imply any endorsement, recommendation,
or favoring of any entity, product, or service.
Please refer to the terms and disclaimer located in the project <a
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,66 @@
<div [ngClass]="{'window-overlay': showExportModal}"></div>
<div class="popup" [ngClass]="{'open': showExportModal }">
<div class="popup-header">Export Data
<button type="button" class="btn-close float-end" aria-label="Close" (click)="closeExportDataModal()"></button>
<div class="popup-header">Export Data
<button type="button" class="btn-close float-end" aria-label="Close" (click)="closeExportDataModal()"></button>
</div>
<div class="popup-body">
<!-- Export file name -->
<div class="input-group">
<label for="exportFileName" class="col-form-label col-sm-4">File Name</label>
<input type="text" class="form-control col-sm-8" id="exportFileName" [(ngModel)]="exportFileName"
placeholder="Enter file name" />
</div>
<div class="popup-body">
<!-- Export file name -->
<div class="input-group">
<label for="exportFileName" class="col-form-label col-sm-4">File Name</label>
<input type="text" class="form-control col-sm-8" id="exportFileName" [(ngModel)]="exportFileName"
placeholder="Enter file name" />
<div class="fw-light small my-2">The file will be saved as {{exportFileName? exportFileName : 'JUSTIFI_backup'}}.json</div>
<!-- Export Options -->
<div class="mb-3">
<label class="form-label fw-bold">Export Option</label>
<div class="form-check">
<input class="form-check-input" type="radio" id="exportAll" name="exportOption"
[(ngModel)]="exportOption" value="all" (change)="onExportOptionChange()">
<label class="form-check-label" for="exportAll">Export all data</label>
</div>
@if (visitGuid) {
<div class="form-check">
<input class="form-check-input" type="radio" id="exportCurrentVisit" name="exportOption"
[(ngModel)]="exportOption" value="visit" (change)="onExportOptionChange()">
<label class="form-check-label" for="exportCurrentVisit">Export current visit</label>
</div>
<div class="fw-light small my-2">The file will be saved as {{exportFileName? exportFileName : 'JUSTIFI_backup'}}.json</div>
<!-- Export Options -->
<div class="mb-3">
<label class="form-label fw-bold">Export Option</label>
<div class="form-check">
<input class="form-check-input" type="radio" id="exportAll" name="exportOption"
[(ngModel)]="exportOption" value="all" (change)="onExportOptionChange()">
<label class="form-check-label" for="exportAll">Export all data</label>
</div>
<div *ngIf="visitGuid" class="form-check">
<input class="form-check-input" type="radio" id="exportCurrentVisit" name="exportOption"
[(ngModel)]="exportOption" value="visit" (change)="onExportOptionChange()">
<label class="form-check-label" for="exportCurrentVisit">Export current visit</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="exportCustom" name="exportOption"
[(ngModel)]="exportOption" value="custom" (change)="onExportOptionChange()">
<label class="form-check-label" for="exportCustom">Custom selection</label>
</div>
</div>
<!-- Custom Export Tree -->
<ng-template [ngIf]="exportTree && exportTree.length > 0" [ngIfElse]="noDataBlock">
<div *ngIf="exportOption === 'custom'">
<div class="d-flex gap-2 flex-wrap ms-2">
<button class="btn btn-outline-primary btn-sm border-0" (click)="selectAll()">
<fa-icon [icon]="faCheckSquare"></fa-icon> Select All</button>
<button class="btn btn-outline-secondary btn-sm border-0" (click)="unselectAll()">
<fa-icon [icon]="faSquare"></fa-icon> Unselect All</button>
<button class="btn btn-outline-primary btn-sm border-0" (click)="expandAll()">
<fa-icon [icon]="faAnglesDown"></fa-icon> Expand All</button>
<button class="btn btn-outline-secondary btn-sm border-0" (click)="collapseAll()">
<fa-icon [icon]="faAnglesUp"></fa-icon> Collapse All</button>
}
<div class="form-check">
<input class="form-check-input" type="radio" id="exportCustom" name="exportOption"
[(ngModel)]="exportOption" value="custom" (change)="onExportOptionChange()">
<label class="form-check-label" for="exportCustom">Custom selection</label>
</div>
</div>
<!-- Custom Export Tree -->
@if (exportTree && exportTree.length > 0) {
@if (exportOption === 'custom') {
<div>
<div class="d-flex gap-2 flex-wrap ms-2">
<button class="btn btn-outline-primary btn-sm border-0" (click)="selectAll()">
<fa-icon [icon]="faCheckSquare"></fa-icon> Select All</button>
<button class="btn btn-outline-secondary btn-sm border-0" (click)="unselectAll()">
<fa-icon [icon]="faSquare"></fa-icon> Unselect All</button>
<button class="btn btn-outline-primary btn-sm border-0" (click)="expandAll()">
<fa-icon [icon]="faAnglesDown"></fa-icon> Expand All</button>
<button class="btn btn-outline-secondary btn-sm border-0" (click)="collapseAll()">
<fa-icon [icon]="faAnglesUp"></fa-icon> Collapse All</button>
</div>
<hr class="mt-1 mb-2">
<app-export-backup-tree [nodes]="exportTree"></app-export-backup-tree>
</div>
<hr class="mt-1 mb-2">
<app-export-backup-tree [nodes]="exportTree"></app-export-backup-tree>
</div>
</ng-template>
<ng-template #noDataBlock>
<div class="alert alert-warning">
}
} @else {
<div class="alert alert-warning">
No data available to export.
</div>
</ng-template>
</div>
<hr class="mt-0 mb-2">
<div class="popup-footer d-flex justify-content-end">
<button class="btn btn-sm btn-secondary me-2" (click)="closeExportDataModal()">Cancel</button>
<button class="btn btn-sm btn-success" (click)="backupData()"
[disabled]="isNoneSelected(exportTree)"><fa-icon [icon]="faDownload"></fa-icon>
</div>
}
</div>
<hr class="mt-0 mb-2">
<div class="popup-footer d-flex justify-content-end">
<button class="btn btn-sm btn-secondary me-2" (click)="closeExportDataModal()">Cancel</button>
<button class="btn btn-sm btn-success" (click)="backupData()"
[disabled]="isNoneSelected(exportTree)"><fa-icon [icon]="faDownload"></fa-icon>
Export Data</button>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,27 +1,45 @@
<ul class="tree-list" *ngIf="nodes">
<li *ngFor="let node of nodes">
@if (nodes) {
<ul class="tree-list">
@for (node of nodes; track node) {
<li>
<div class="form-check">
<span *ngIf="node.children && node.children.length > 0" (click)="toggleNode(node)" class="tree-toggle">
<fa-icon [icon]="node.expanded ? faAngleDown : faAngleRight"></fa-icon>
@if (node.children && node.children.length > 0) {
<span (click)="toggleNode(node)" class="tree-toggle">
<fa-icon [icon]="node.expanded ? faAngleDown : faAngleRight"></fa-icon>
</span>
<input class="form-check-input" type="checkbox" [id]="'export_node' + node.item.guid"
[checked]="node.checked"
[indeterminate]="node.indeterminate"
[(ngModel)]="node.checked"
(change)="onCheckboxChange(node)" />
<label class="form-check-label" [for]="'export_node_' + node.item.guid">
<ng-container [ngSwitch]="node.type">
<fa-icon *ngSwitchCase="'company'" [icon]="faBuilding" class="me-1"></fa-icon>
<fa-icon *ngSwitchCase="'facility'" [icon]="faIndustry" class="me-1"></fa-icon>
<fa-icon *ngSwitchCase="'visit'" [icon]="faCalendar" class="me-1"></fa-icon>
<fa-icon *ngSwitchCase="'assessment'" [icon]="faScrewdriverWrench" class="me-1"></fa-icon>
<fa-icon *ngSwitchDefault [icon]="faScrewdriverWrench" class="me-1"></fa-icon>
</ng-container>
{{ node.name }}
</label>
}
<input class="form-check-input" type="checkbox" [id]="'export_node' + node.item.guid"
[checked]="node.checked"
[indeterminate]="node.indeterminate"
[(ngModel)]="node.checked"
(change)="onCheckboxChange(node)" />
<label class="form-check-label" [for]="'export_node_' + node.item.guid">
@switch (node.type) {
@case ('company') {
<fa-icon [icon]="faBuilding" class="me-1"></fa-icon>
}
@case ('facility') {
<fa-icon [icon]="faIndustry" class="me-1"></fa-icon>
}
@case ('visit') {
<fa-icon [icon]="faCalendar" class="me-1"></fa-icon>
}
@case ('assessment') {
<fa-icon [icon]="faScrewdriverWrench" class="me-1"></fa-icon>
}
@default {
<fa-icon [icon]="faScrewdriverWrench" class="me-1"></fa-icon>
}
}
{{ node.name }}
</label>
</div>
<div class="subtree-list" *ngIf="node.expanded && node.children && node.children.length > 0">
@if (node.expanded && node.children && node.children.length > 0) {
<div class="subtree-list">
<app-export-backup-tree [nodes]="node.children"></app-export-backup-tree>
</div>
</li>
</ul>
</div>
}
</li>
}
</ul>
}
Loading
Loading