LCA
Storing, processing and working with life-cycle assessment data has always been challenging. A multitude of data models and implementations exist already but every one of them makes huge compromises or lacks functionality.
This gem implements a denormalized database model for life-cycle assessment data as well as several vectors for convenient querying.
Installation
This gem is at home within a Rails 6+ console-only, API or full fledged application on top of a Postgres database.
Add this line to your application's Gemfile:
gem 'lca'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install lca
Upon installing the gem you must run the ActiveTree install process in your Rails application's root:
$ rails g active_tree:install
This will generate a config/active_tree.yml
which you may customize to your needs, an initializer and a migration file which you can also customize to add any database columns your models will require.
You must also run the LCA gem install generator:
$ rails g lca:install
This will create a migration file which you can edit to add your models' specific attributes.
Usage
Include the ActiveTree concern into one of your models which will own lifecycle trees (owner model):
class User < ApplicationRecord
include Lca::Lcable
# ...
end
This will extend your model and enable the following functionality:
# query with ActiveRecord syntax
User.last.active_trees
User.find_by(name: "Acme").active_trees.impacts.select("impact_unit, sum(impact_amount) as total_impact").group(:impact_unit)
User.last.active_trees.where(...)
User.last.active_trees.where(...).group(...)
User.last.active_trees.where(...).limit(...).offset(...)
# AR query with ltree syntax
User.last.active_trees.disabled.match_path("*.CustomProcess.*")
User.last.active_trees.match_path("*{5,10}.CustomProcess.*.Ecosphere.*")
User.last.active_trees.active.match_path("*.WhateverProcess.*.Ecosphere.*.CO2Emission.*").where( impact_amount: [100..150]).sum(:impact_amount)
Lca::Cycle.match_path("Top.Electric Vehicle.*").impacts.match_path("*.CO2*").where( impact_amount: [ 1000..10000 ]).average(:impact_amount)
Lca::Cycle::Product.where(name: "Electric Vehicle Battery").impacts.match_path("*.Cobalt.*").sum(:impact_amount)
Lca::Process.where(owner: User.last).match_path("*{10,20}.*Assembly, automated.*")
Lca::Impact.match_path("*.Transport by truck.*")
Lca::Exchange.match_path("*.Oil.*.Ecosphere.*").impacts.sum(:impact_amount)
Lca::Product.match_path("*.ElectricVehicle.*").processes.match_path("*.Processing.*").where(location: "EU").impacts.match_path("*.Lithium.*").where(location: ["CN", "Africa"]).sum(:impact_amount)
# pg_ltree queries
User.last.active_trees.last.parent
Lca::Process.match_path("*.Manual assembly.*").children
# pg_ltree combined with AR syntax
User.last.active_trees(type: "Lca::Product").children.match_path("*.Retail").children.exchanges
# all queries can be directed to a specific partition:
Lca::Process.owned_by( owner_id ).match_path("*.Recycling.*").where(impact_unit: "tons CO2/year").impacts.sum(:impact_amount)
The gem also creates some default models:
Lca::Process::Transport::ByAir.match_path("*.CO2Emission.*").impacts.sum(:impact_amount)
Lca::Impact::Ecosphere::Fauna.match_path("*.ResourceExtraction.*").where(impact_unit: "Species killed/year").sum(:impact_amount)
To see what syntax to use for path traversal please check out the following resources:
- active_tree gem https://github.com/nicksterious/active_tree
- pg_ltree gem https://github.com/sjke/pg_ltree
- Postgres ltree extension documentation https://www.postgresql.org/docs/9.1/ltree.html
Scalability
All LCA data can be stored into separate partitions using the ActiveTree partitioning feature. See config/active_tree.yml
for table and partitioning naming and options.
Caveats
pg_ltree
.child / .parent queries do not work across different models due to an ActiveRecord limitation that requires results to be related via inheritance.
The following behaviors have been observed:
Lca::Process.last.children.impacts.where(impact_amount: 50)
# => nil
Lca::Process.last.children.unscope(where: :type).impacts.where(impact_amount: 50)
# => ActiveRecord::SubclassNotFound (Invalid single-table inheritance type: Lca::Impact is not a subclass of Lca::Process)
Roadmap
- .child / .parent query support across models unrelated through inheritance
- more model templates for various LCA cycles, processes and impacts
- model validations
- postgres RECURSIVE queries
- builders
- seeds/fixtures
Contributing
Bug reports, pull requests and feature suggestions are welcome on GitHub at https://github.com/nicksterious/lca
License
The gem is available as open source under the terms of the MIT License.