9 |
10 | # carbonplan / cdr-database
11 |
12 | **reports on carbon removal projects and technologies**
13 |
14 | [](https://github.com/carbonplan/cdr-database/actions/workflows/main.yml)
15 | 
16 | [](https://opensource.org/licenses/MIT)
17 | [](https://zenodo.org/badge/latestdoi/252217021)
18 |
19 | ## resources
20 |
21 | - Main website: https://carbonplan.org/
22 | - This site: https://carbonplan.org/research/cdr-database
23 |
24 | ## to build the projects data
25 |
26 | To run the script that generates the project data, first install the requirements:
27 |
28 | ```shell
29 | pip install -r requirements.txt
30 | ```
31 |
32 | You will also need to unlock the Google Sheets key using [`git-crypt`](https://github.com/AGWA/git-crypt). Unlocking is simplest using a symmetric secret key securely shared by a team member.
33 |
34 | Finally, you may run the command to generate the projects list for all review cycles:
35 |
36 | ```shell
37 | python scripts/build_projects.py strp2020 strp2021q1 strp2021q4 msft2021
38 | ```
39 |
40 | ## to build the site locally
41 |
42 | Assuming you already have `Node.js` installed, you can install the build dependencies with:
43 |
44 | ```shell
45 | npm install .
46 | ```
47 |
48 | To start a development version of the site, simply run:
49 |
50 | ```shell
51 | npm run dev
52 | ```
53 |
54 | and then visit `http://localhost:5000/research/cdr-database` in your browser.
55 |
56 | ## license
57 |
58 | All the code in this repository is [MIT](https://choosealicense.com/licenses/mit/) licensed, but we request that you please provide attribution if reusing any of our digital content (graphics, logo, reports, etc.). Some of the data featured here is sourced from content made available under a [CC-BY-4.0](https://choosealicense.com/licenses/cc-by-4.0/) license. We include attribution for this content, and we please request that you also maintain that attribution if using this data.
59 |
60 | ## about us
61 |
62 | CarbonPlan is a nonprofit organization that uses data and science for climate action. We aim to improve the transparency and scientific integrity of climate solutions with open data and tools. Find out more at [carbonplan.org](https://carbonplan.org/) or get in touch by [opening an issue](https://github.com/carbonplan/cdr-database/issues/new) or [sending us an email](mailto:hello@carbonplan.org).
63 |
--------------------------------------------------------------------------------
/components/main.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Grid, Box, Text, Input } from 'theme-ui'
3 | import { Row, Column } from '@carbonplan/components'
4 | import Sidebar from '../components/sidebar'
5 | import Projects from './projects'
6 |
7 | const initFilters = {
8 | forests: true,
9 | dac: true,
10 | mineralization: true,
11 | soil: true,
12 | biomass: true,
13 | ocean: true,
14 | stripe: true,
15 | microsoft: true,
16 | avoided: true,
17 | removal: true,
18 | storage: true,
19 | group: false,
20 | 2020: true,
21 | 2021: true,
22 | search: '',
23 | rating: 3,
24 | }
25 |
26 | const initBounds = {
27 | volume: [],
28 | permanence: [],
29 | }
30 |
31 | const near = (a, b) => {
32 | return Math.abs(a - b) < 0.000001
33 | }
34 |
35 | const Main = ({ projects, metrics, settingsExpanded }) => {
36 | const [filters, setFilters] = useState(initFilters)
37 | const [filtered, setFiltered] = useState({ count: 0, init: false })
38 | const [bounds, setBounds] = useState(initBounds)
39 | const [highlighted, setHighlighted] = useState(null)
40 | const [tooltips, setTooltips] = useState(true)
41 |
42 | useEffect(() => {
43 | let obj = {}
44 | let count = 0
45 | projects.forEach((d) => {
46 | obj[d.id] = inFilter(d)
47 | if (obj[d.id]) {
48 | count += 1
49 | }
50 | })
51 | obj.count = count
52 | obj.init = true
53 | setFiltered(obj)
54 | }, [filters, bounds])
55 |
56 | function checkBounds(value, bounds, min, max) {
57 | if (bounds.length == 0) return true
58 | return (
59 | ((near(value, bounds[0]) || value > bounds[0]) &&
60 | (near(value, bounds[1]) || value < bounds[1])) ||
61 | ((near(max, bounds[1]) || bounds[1] > max) && value >= max) ||
62 | ((near(min, bounds[0]) || bounds[0] < min) && value <= min)
63 | )
64 | }
65 |
66 | function inFilter(d) {
67 | const inTags =
68 | (filters.forests && d.tags.length > 0 && d.tags[0] == 'forests') ||
69 | (filters.dac && d.tags.length > 0 && d.tags[0] == 'dac') ||
70 | (filters.mineralization &&
71 | d.tags.length > 0 &&
72 | d.tags[0] == 'mineralization') ||
73 | (filters.soil && d.tags.length > 0 && d.tags[0] == 'soil') ||
74 | (filters.biomass && d.tags.length > 0 && d.tags[0] == 'biomass') ||
75 | (filters.ocean && d.tags.length > 0 && d.tags[0] == 'ocean')
76 | const inSource =
77 | (filters.stripe && d.source.id.includes('STRP')) ||
78 | (filters.microsoft && d.source.id.includes('MSFT'))
79 | const inYear =
80 | (filters['2020'] && d.source.date.includes('2020')) ||
81 | (filters['2021'] && d.source.date.includes('2021'))
82 | const inMechanism =
83 | (filters.removal && d.metrics[0].value == 0) ||
84 | (filters.avoided && d.metrics[0].value == 1) ||
85 | (filters.removal && filters.avoided && d.metrics[0].value == 2) ||
86 | d.metrics[0].value == 3
87 | const inBounds =
88 | checkBounds(d.metrics[1].value, bounds.volume, 10, 1000000) &&
89 | checkBounds(d.metrics[3].value, bounds.permanence, 1, 1000)
90 | const searchTerm = filters.search.toLowerCase()
91 | const inSearch =
92 | searchTerm.length > 0 &&
93 | (d.applicant.toLowerCase().includes(searchTerm) ||
94 | d.keywords.toLowerCase().includes(searchTerm) ||
95 | d.location.name.toLowerCase().includes(searchTerm) ||
96 | d.tags[0].toLowerCase().includes(searchTerm) ||
97 | d.source.name.toLowerCase().includes(searchTerm) ||
98 | (d.tags.length > 1 && d.tags[1].toLowerCase().includes(searchTerm)))
99 | const isValidated = d.rating >= filters.rating
100 | const inFilter =
101 | inTags && inSource && inYear && inBounds && inMechanism && isValidated
102 | if (filters.search.length > 0 && inSearch && inFilter) return true
103 | if (filters.search.length == 0 && inFilter) return true
104 | else return false
105 | }
106 |
107 | return (
108 |
109 |
110 |
121 |
122 |
123 |
132 |
133 |
134 | )
135 | }
136 |
137 | export default Main
138 |
--------------------------------------------------------------------------------
/components/methods/desktop.js:
--------------------------------------------------------------------------------
1 | import { Grid, Box, Text, Link } from 'theme-ui'
2 | import { Row, Column } from '@carbonplan/components'
3 | import Intro from './intro.js'
4 | import Section from './section.js'
5 | import TOC from './toc.js'
6 |
7 | const Desktop = ({ section, setSection }) => {
8 | return (
9 |
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 |
40 |
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | export default Desktop
49 |
--------------------------------------------------------------------------------
/components/methods/inline-check.js:
--------------------------------------------------------------------------------
1 | import { CheckCircle } from '@carbonplan/icons'
2 |
3 | const InlineCheck = () => {
4 | return (
5 |
15 | )
16 | }
17 |
18 | export default InlineCheck
19 |
--------------------------------------------------------------------------------
/components/methods/intro.js:
--------------------------------------------------------------------------------
1 | import { Box, Text } from 'theme-ui'
2 | import { Button, Link } from '@carbonplan/components'
3 | import { Left } from '@carbonplan/icons'
4 | import Notice from '../notice'
5 |
6 | const Intro = ({ setSection }) => {
7 | return (
8 |
9 | }
14 | >
15 | Back
16 |
17 | `solid 1px ${colors.muted}`,
20 | mt: [4],
21 | display: ['inherit', 'inherit', 'none', 'none'],
22 | }}
23 | >
24 |
25 |
26 |
36 | Methods
37 |
38 |
48 | Descriptions of our metrics and notes on each carbon removal project we
49 | have analyzed. For more on this work read our articles on what we
50 | learned analyzing proposals submitted to{' '}
51 | Stripe in 2020 ,{' '}
52 | Microsoft in 2021,
53 | or Stripe in 2021. If
54 | you have questions or want to get in touch. check out the{' '}
55 | setSection('feedback')}
65 | >
66 | feedback
67 | {' '}
68 | section.
69 |
70 |
71 | )
72 | }
73 |
74 | export default Intro
75 |
--------------------------------------------------------------------------------
/components/methods/main.js:
--------------------------------------------------------------------------------
1 | import { Box } from 'theme-ui'
2 | import Desktop from './desktop'
3 | import Mobile from './mobile'
4 |
5 | const Main = ({ section, setSection }) => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | )
16 | }
17 |
18 | export default Main
19 |
--------------------------------------------------------------------------------
/components/methods/mobile.js:
--------------------------------------------------------------------------------
1 | import { Grid, Box, Text, Link } from 'theme-ui'
2 | import { Row, Column } from '@carbonplan/components'
3 | import Intro from './intro.js'
4 | import Section from './section.js'
5 | import TOC from './toc.js'
6 |
7 | const Mobile = ({ section, setSection }) => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | export default Mobile
49 |
--------------------------------------------------------------------------------
/components/methods/section.js:
--------------------------------------------------------------------------------
1 | import { Box } from 'theme-ui'
2 | import Sources from '../../methods/sources.md'
3 | import Metrics from '../../methods/metrics.md'
4 | import Feedback from '../../methods/feedback.md'
5 | import Projects from '../../methods/projects.md'
6 | import Terms from '../../methods/terms.md'
7 |
8 | const Section = ({ section }) => {
9 | return (
10 |
21 |
22 | {section == 'sources' && }
23 | {section == 'metrics' && }
24 | {section == 'feedback' && }
25 | {section == 'projects' && }
26 | {section == 'terms' && }
27 |
28 |
29 | )
30 | }
31 |
32 | export default Section
33 |
--------------------------------------------------------------------------------
/components/methods/table.js:
--------------------------------------------------------------------------------
1 | import { Box, Grid } from 'theme-ui'
2 | import { Row, Column } from '@carbonplan/components'
3 | import { Check } from '@carbonplan/icons'
4 | import Squares from '../projects/report/graphics/squares'
5 |
6 | const Table = ({ one, two, three, type, children }) => {
7 | let width
8 | if (type == 'icons') width = '50px'
9 | if (type == 'squares') width = '100px'
10 |
11 | const TableRow = ({ children, final }) => {
12 | return (
13 |
25 | {children}
26 |
27 | )
28 | }
29 |
30 | let starts, widths
31 | if (type === 'icons') {
32 | starts = [1, 2]
33 | widths = [1, 5]
34 | }
35 | if (type === 'squares') {
36 | starts = [1, 3]
37 | widths = [2, 4]
38 | }
39 |
40 | return (
41 |
46 |
47 |
48 | {type == 'icons' && (
49 |
52 | )}
53 | {type == 'squares' && (
54 |
55 |
56 |
57 | )}
58 |
59 |
60 |
69 | {three}
70 |
71 |
72 |
73 | {type == 'squares' && (
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
91 | {two}
92 |
93 |
94 |
95 |
96 |
97 | {type == 'icons' && }
98 | {type == 'squares' && (
99 |
100 |
101 |
102 | )}
103 |
104 |
105 |
114 | {one}
115 |
116 |
117 |
118 |
119 | )}
120 |
121 | )
122 | }
123 |
124 | export default Table
125 |
--------------------------------------------------------------------------------
/components/methods/toc.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { Box, Grid, Link, Divider } from 'theme-ui'
3 | import { default as NextLink } from 'next/link'
4 | import { Expander } from '@carbonplan/components'
5 |
6 | const sections = ['Sources', 'Metrics', 'Projects', 'Feedback', 'Terms']
7 |
8 | const TOC = ({ section, setSection }) => {
9 | const [expandedMetrics, setExpandedMetrics] = useState(false)
10 | const [expandedPrograms, setExpandedPrograms] = useState(false)
11 |
12 | const toggleMetrics = (e) => {
13 | setExpandedMetrics(!expandedMetrics)
14 | }
15 |
16 | const togglePrograms = (e) => {
17 | setExpandedPrograms(!expandedPrograms)
18 | }
19 |
20 | const style = (current) => {
21 | if (current == section) {
22 | return {
23 | textDecoration: 'none',
24 | fontFamily: 'heading',
25 | textTransform: 'uppercase',
26 | letterSpacing: 'smallcaps',
27 | pb: ['2px'],
28 | fontSize: [1, 2, 2, 3],
29 | mr: ['2px', 4, 0, 0],
30 | borderColor: 'primary',
31 | borderStyle: 'solid',
32 | borderWidth: '0px',
33 | borderBottomWidth: '1px',
34 | '@media (hover: hover) and (pointer: fine)': {
35 | '&:hover': {
36 | borderColor: 'secondary',
37 | color: 'secondary',
38 | },
39 | },
40 | '@media (hover: none) and (pointer: coarse)': {
41 | '&:hover': {
42 | color: 'primary',
43 | },
44 | },
45 | }
46 | } else {
47 | return {
48 | textDecoration: 'none',
49 | fontSize: [1, 2, 2, 3],
50 | mr: ['2px', 4, 0, 0],
51 | pb: ['2px'],
52 | fontFamily: 'heading',
53 | textTransform: 'uppercase',
54 | letterSpacing: 'smallcaps',
55 | borderColor: 'background',
56 | borderStyle: 'solid',
57 | borderWidth: '0px',
58 | borderBottomWidth: '1px',
59 | '@media (hover: hover) and (pointer: fine)': {
60 | '&:hover': {
61 | color: 'secondary',
62 | },
63 | },
64 | '@media (hover: none) and (pointer: coarse)': {
65 | '&:hover': {
66 | color: 'primary',
67 | },
68 | },
69 | }
70 | }
71 | }
72 |
73 | return (
74 |
75 |
84 | {sections.map((d, i) => {
85 | return (
86 |
95 | setSection(d.toLowerCase())}
98 | >
99 | {d}
100 |
101 |
102 | )
103 | })}
104 |
105 |
106 | )
107 | }
108 |
109 | export default TOC
110 |
--------------------------------------------------------------------------------
/components/notice.js:
--------------------------------------------------------------------------------
1 | import { Box } from 'theme-ui'
2 | import { Link } from '@carbonplan/components'
3 |
4 | const Notice = () => {
5 | return (
6 | <>
7 |
22 | This resource is no longer maintained or updated, read our{' '}
23 |
27 | post
28 |
29 | .
30 |
31 | `solid 1px ${colors.muted}`,
41 | pb: [3],
42 | }}
43 | >
44 | This resource is no longer maintained or updated, read our{' '}
45 |
49 | post
50 |
51 | .
52 |
53 | >
54 | )
55 | }
56 |
57 | export default Notice
58 |
--------------------------------------------------------------------------------
/components/projects/count.js:
--------------------------------------------------------------------------------
1 | import { Box, Text } from 'theme-ui'
2 |
3 | const Count = ({ value }) => {
4 | return (
5 |
17 |
25 | {value}
26 |
27 |
28 | )
29 | }
30 |
31 | export default Count
32 |
--------------------------------------------------------------------------------
/components/projects/index.js:
--------------------------------------------------------------------------------
1 | import { memo } from 'react'
2 | import { Box, Grid, Divider } from 'theme-ui'
3 | import { useMedia } from 'react-use'
4 | import { FadeIn } from '@carbonplan/components'
5 | import Report from './report'
6 | import Top from './top'
7 |
8 | const List = ({
9 | filters,
10 | setFilters,
11 | filtered,
12 | data,
13 | setHighlighted,
14 | tooltips,
15 | setTooltips,
16 | }) => {
17 | const isWide = useMedia('screen and (min-width: 72em)')
18 |
19 | return (
20 |
21 |
27 | {filtered.init && (
28 |
29 | {isWide && filtered.count > 0 && (
30 |
35 |
36 | {data
37 | .filter((d) => filtered[d.id])
38 | .filter((d, i) => i % 2 == 0)
39 | .map((d) => (
40 |
46 | ))}
47 |
48 |
49 | {data
50 | .filter((d) => filtered[d.id])
51 | .filter((d, i) => i % 2 == 1)
52 | .map((d) => (
53 |
59 | ))}
60 |
61 |
62 | )}
63 | {!isWide && filtered.count > 0 && (
64 |
65 | {data
66 | .filter((d) => filtered[d.id])
67 | .map((d) => (
68 |
74 | ))}
75 |
76 | )}
77 | {filtered.count == 0 && filtered.init == true && (
78 |
86 |
97 | No results found
98 |
99 |
105 | Please try changing the settings in the filter panel and try
106 | again.
107 |
108 |
109 | )}
110 |
111 | )}
112 |
113 | )
114 | }
115 |
116 | export default memo(List)
117 |
--------------------------------------------------------------------------------
/components/projects/report/graphics/bar.js:
--------------------------------------------------------------------------------
1 | import { Box } from 'theme-ui'
2 | import { scaleLinear, scaleLog } from 'd3-scale'
3 | import { useThemeUI } from 'theme-ui'
4 |
5 | const Bar = ({ tag, data, scale }) => {
6 | const context = useThemeUI()
7 | const theme = context.theme
8 |
9 | const x = scale.type == 'log' ? scaleLog() : scaleLinear()
10 | const width = x.domain([scale.min, scale.max]).range([0, 90])(data)
11 |
12 | return (
13 |
18 |
36 |
37 | )
38 | }
39 |
40 | export default Bar
41 |
--------------------------------------------------------------------------------
/components/projects/report/graphics/mechanism.js:
--------------------------------------------------------------------------------
1 | import { Box, Text } from 'theme-ui'
2 | import { useThemeUI } from 'theme-ui'
3 |
4 | const Mechanism = ({ tag, value }) => {
5 | const context = useThemeUI()
6 | const theme = context.theme
7 |
8 | return (
9 |
14 |
61 |
62 | )
63 | }
64 |
65 | export default Mechanism
66 |
--------------------------------------------------------------------------------
/components/projects/report/graphics/rating.js:
--------------------------------------------------------------------------------
1 | import { Box } from 'theme-ui'
2 | import { Check } from '@carbonplan/icons'
3 |
4 | const Rating = ({ sx, value }) => {
5 | return (
6 |
7 | {[0, 1, 2, 3, 4].map((d, i) => (
8 |
9 |
24 |
25 | ))}
26 |
27 | )
28 | }
29 |
30 | export default Rating
31 |
--------------------------------------------------------------------------------
/components/projects/report/graphics/scales.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | volume: {
3 | type: 'log',
4 | min: 2,
5 | max: 100000000,
6 | },
7 | negativity: {
8 | type: 'linear',
9 | min: 0,
10 | max: 1,
11 | },
12 | permanence: {
13 | type: 'log',
14 | min: 10,
15 | max: 1000,
16 | },
17 | price: {
18 | type: 'log',
19 | min: 3,
20 | max: 3000,
21 | },
22 | }
23 |
--------------------------------------------------------------------------------
/components/projects/report/graphics/squares.js:
--------------------------------------------------------------------------------
1 | import { Box, Text } from 'theme-ui'
2 | import { useThemeUI } from 'theme-ui'
3 |
4 | const Squares = ({ color, data, height }) => {
5 | const context = useThemeUI()
6 | const theme = context.theme
7 |
8 | return (
9 |
10 |
17 | = 0 ? 1 : 0.2 }}
20 | x='0'
21 | y='0'
22 | width='25'
23 | height='12'
24 | />
25 | = 1 ? 1 : 0.2 }}
28 | x='33'
29 | y='0'
30 | width='25'
31 | height='12'
32 | />
33 | = 2 ? 1 : 0.2 }}
36 | x='65'
37 | y='0'
38 | width='25'
39 | height='12'
40 | />
41 |
42 |
43 | )
44 | }
45 |
46 | export default Squares
47 |
--------------------------------------------------------------------------------
/components/projects/report/index.js:
--------------------------------------------------------------------------------
1 | import { memo, useState } from 'react'
2 | import AnimateHeight from 'react-animate-height'
3 | import {
4 | useColorMode,
5 | useThemeUI,
6 | Divider,
7 | Box,
8 | Flex,
9 | Grid,
10 | Link,
11 | } from 'theme-ui'
12 | import { Expander, Tag } from '@carbonplan/components'
13 | import Metric from './metric'
14 | import Share from './share'
15 |
16 | const showMetrics = [
17 | 'mechanism',
18 | 'volume',
19 | 'negativity',
20 | 'permanence',
21 | 'price',
22 | 'additionality',
23 | 'specificity',
24 | ]
25 |
26 | const Report = ({ data, setHighlighted, tooltips, embed }) => {
27 | const [expanded, setExpanded] = useState(embed ? true : false)
28 | const { theme } = useThemeUI()
29 | const [colorMode] = useColorMode()
30 |
31 | let {
32 | id,
33 | applicant,
34 | name,
35 | description,
36 | source,
37 | documentation,
38 | tags,
39 | metrics,
40 | location,
41 | rating,
42 | notes,
43 | } = data
44 |
45 | metrics = showMetrics.map((metric) => {
46 | return metrics.filter((m) => m.name == metric)[0]
47 | })
48 |
49 | metrics = [
50 | {
51 | name: 'rating',
52 | value: rating,
53 | units: '',
54 | notes: '',
55 | comment: '',
56 | },
57 | ].concat(metrics)
58 |
59 | const ml = embed ? [0, 0, 0] : [0, 0, '24px', '36px']
60 | const pl = embed ? [0, 0, 0] : [0, 0, '24px', '36px']
61 |
62 | const arrow = {
63 | ml: [1],
64 | fontSize: [4],
65 | position: 'relative',
66 | top: '4px',
67 | display: 'inline-block',
68 | textDecoration: 'none',
69 | lineHeight: 0,
70 | zIndex: '-1',
71 | '&:active': {
72 | color: 'primary',
73 | },
74 | '&:hover': {
75 | color: 'primary',
76 | },
77 | }
78 |
79 | return (
80 | {
82 | if (!embed) setExpanded(!expanded)
83 | }}
84 | onMouseEnter={() => {
85 | if (setHighlighted) setHighlighted(id)
86 | }}
87 | onMouseLeave={() => {
88 | if (setHighlighted) setHighlighted(null)
89 | }}
90 | sx={{
91 | borderStyle: 'solid',
92 | borderColor: 'muted',
93 | borderWidth: '0px',
94 | borderBottomWidth: embed
95 | ? ['0px', '0px', '0px']
96 | : ['1px', '1px', '0px'],
97 | borderLeftWidth: embed ? [0, 0, 0] : ['0px', '0px', '1px'],
98 | borderRadius: '0px',
99 | cursor: embed ? 'default' : 'pointer',
100 | pl: embed ? [0, 0, 0] : [0, 0, 0],
101 | pr: [0],
102 | pt: embed ? [0, 0, 0] : ['0px', '0px', '12px'],
103 | my: [embed ? 0 : 3],
104 | mb: embed ? [0, 0, 0] : [3, 3, '24px'],
105 | transition: 'border-color 0.15s',
106 | '&:hover': {
107 | borderColor: ['muted', 'muted', 'secondary'],
108 | },
109 | '@media (hover: hover) and (pointer: fine)': {
110 | '&:hover > #container > #box > #flex > #text > #expander': {
111 | stroke: 'primary',
112 | },
113 | },
114 | '@media (hover: none) and (pointer: coarse)': {
115 | '#container > #box > #flex > #text > #expander': {
116 | stroke: expanded ? 'primary' : 'secondary',
117 | },
118 | },
119 | }}
120 | >
121 |
130 |
131 |
139 |
148 | {!embed && (
149 |
159 | )}
160 | {applicant}
161 | {!embed && (
162 |
172 | )}
173 |
174 |
175 |
176 |
183 | {tags.map((tag, i) => (
184 |
194 | {tag}
195 |
196 | ))}
197 |
198 |
199 |
200 | {description}
201 |
202 |
212 | {id.includes('STRP') & (parseInt(id.split('STRP')[1]) <= 24)
213 | ? 'Stripe 2020'
214 | : ''}
215 | {id.includes('STRP') &
216 | (parseInt(id.split('STRP')[1]) > 24 &&
217 | parseInt(id.split('STRP')[1]) <= 50)
218 | ? 'Stripe Spring 2021'
219 | : ''}
220 | {id.includes('STRP') &
221 | (parseInt(id.split('STRP')[1]) > 50 &&
222 | parseInt(id.split('STRP')[1]) <= 61)
223 | ? 'Stripe Fall 2021'
224 | : ''}
225 | {id.includes('MSFT') ? 'Microsoft 2021' : ''}
226 |
227 | /
228 |
229 | {location.name}
230 |
231 |
236 | {expanded && (
237 |
238 |
239 |
240 | {metrics.map((metric) => (
241 |
248 | ))}
249 |
250 | e.stopPropagation()}
252 | sx={{
253 | pl: pl,
254 | fontSize: [1, 1, 1, 2],
255 | pt: [3],
256 | cursor: 'default',
257 | }}
258 | >
259 | {!(notes === '') && (
260 |
267 | Note: {notes}
268 |
269 | )}
270 |
271 |
272 |
282 | Proposal
283 | ↗
284 |
285 |
286 | {!(documentation.name === '') && (
287 |
288 | e.stopPropagation()}
290 | sx={{
291 | textDecoration: 'none',
292 | color: 'secondary',
293 | '&:hover': {
294 | color: 'primary',
295 | },
296 | }}
297 | href={documentation.url}
298 | >
299 | {documentation.name}
300 | ↗
301 |
302 |
303 | )}
304 |
305 |
308 |
312 | ↑
313 |
314 |
315 | `}
317 | label='Embed'
318 | >
319 |
330 |
372 |
373 |
374 |
375 |
376 |
377 |
378 | )}
379 |
380 | {
382 | if (expanded) e.stopPropagation()
383 | }}
384 | sx={{ height: ['16px'], cursor: 'default' }}
385 | />
386 |
387 | )
388 | }
389 |
390 | export default memo(Report)
391 |
--------------------------------------------------------------------------------
/components/projects/report/metric/desktop.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import AnimateHeight from 'react-animate-height'
3 | import { useThemeUI, Box, Divider, Grid, Text } from 'theme-ui'
4 | import { Expander } from '@carbonplan/components'
5 | import { Check } from '@carbonplan/icons'
6 | import Bar from '../graphics/bar'
7 | import Squares from '../graphics/squares'
8 | import Rating from '../graphics/rating'
9 | import Mechanism from '../graphics/mechanism'
10 | import TooltipToggle from '../../../tooltip/toggle'
11 | import TooltipDescription from '../../../tooltip/description'
12 | import scales from '../graphics/scales'
13 |
14 | const sx = {
15 | comment: {
16 | fontFamily: 'faux',
17 | fontSize: [1, 1, 1, 2],
18 | color: 'secondary',
19 | letterSpacing: 'faux',
20 | lineHeight: 'small',
21 | mt: [0],
22 | mb: [2],
23 | maxWidth: '450px',
24 | wordBreak: 'break-word',
25 | },
26 | value: {
27 | fontFamily: 'mono',
28 | fontSize: [3],
29 | textAlign: 'right',
30 | mt: ['6px'],
31 | },
32 | label: {
33 | fontFamily: 'mono',
34 | fontSize: [2, 2, 2, 3],
35 | mt: ['8px', '8px', '8px', '6px'],
36 | textTransform: 'capitalize',
37 | },
38 | units: {
39 | fontFamily: 'mono',
40 | color: 'secondary',
41 | fontSize: [1],
42 | ml: [2],
43 | textTransform: 'normal',
44 | },
45 | rating: {
46 | display: 'inline-block',
47 | ml: [3],
48 | fontSize: ['18px'],
49 | },
50 | }
51 |
52 | const MetricDesktop = ({
53 | metric,
54 | toggle,
55 | expanded,
56 | hasDetails,
57 | hasUnits,
58 | tag,
59 | format,
60 | parse,
61 | duration,
62 | tooltips,
63 | embed,
64 | }) => {
65 | const { theme } = useThemeUI()
66 | const [tooltip, setTooltip] = useState(false)
67 |
68 | return (
69 |
70 | e.stopPropagation()}
72 | sx={{
73 | cursor: hasDetails ? 'pointer' : 'initial',
74 | pointerEvents: 'all',
75 | '&:hover > #grid > #container > #expander': {
76 | fill: 'primary',
77 | stroke: 'primary',
78 | },
79 | pt: [1],
80 | pb: ['6px'],
81 | pl: embed ? [2, 2, 2] : [0, 0, '24px', '38px'],
82 | }}
83 | >
84 |
85 |
92 | {format(metric.name, metric.value)}
93 |
94 |
95 | {metric.name == 'mechanism' && (
96 |
97 | )}
98 | {metric.name == 'volume' && (
99 |
100 | )}
101 | {metric.name == 'permanence' && (
102 |
107 | )}
108 | {metric.name == 'negativity' && (
109 |
114 | )}
115 | {metric.name == 'price' && (
116 |
117 | )}
118 | {metric.name == 'additionality' && (
119 |
120 |
121 |
122 | )}
123 | {metric.name == 'specificity' && (
124 |
125 |
126 |
127 | )}
128 | {metric.name == 'rating' && (
129 |
130 |
134 |
135 | )}
136 |
137 |
138 |
145 | {metric.name}
146 |
147 | {tooltips && (
148 |
155 |
160 |
161 | )}
162 |
163 |
164 | {(metric.rating === 1 ||
165 | (metric.name === 'additionality' && metric.value === 2)) && (
166 |
175 | )}
176 | {metric.rating === 0 && }
177 |
178 | {hasDetails && (
179 |
180 |
185 |
186 | )}
187 |
188 |
189 | e.stopPropagation()}>
190 |
203 |
204 |
209 | {expanded && (
210 | e.stopPropagation()}
217 | >
218 |
224 | {metric.notes && (
225 |
226 |
236 | NOTES
237 |
238 |
244 | {metric.notes}
245 |
246 |
247 | )}
248 |
253 | {metric.comment && (
254 |
255 |
265 | COMMENT
266 |
267 |
273 | {metric.comment}
274 |
275 |
276 | )}
277 |
278 |
279 | )}
280 | {!expanded && }
281 |
282 |
290 |
291 | )
292 | }
293 |
294 | export default MetricDesktop
295 |
--------------------------------------------------------------------------------
/components/projects/report/metric/index.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { Box } from 'theme-ui'
3 | import { format as _format } from 'd3-format'
4 | import { useMeasure } from 'react-use'
5 | import MetricMobile from './mobile'
6 | import MetricDesktop from './desktop'
7 |
8 | const Metric = ({ metric, tag, tooltips, embed }) => {
9 | const [expanded, setExpanded] = useState(false)
10 |
11 | const [ref, { width }] = useMeasure()
12 |
13 | const toggle = (e) => {
14 | e.stopPropagation()
15 | setExpanded(!expanded)
16 | }
17 |
18 | const length =
19 | (metric.comment ? metric.comment.length : 0) +
20 | (metric.notes ? metric.notes.length : 0)
21 | const duration = Math.min(Math.max(length / 2, 100), 200) * 0.75
22 |
23 | const hasUnits = metric.units != ''
24 | const hasDetails = metric.notes !== '' || metric.comment !== ''
25 |
26 | const format = (key, value, mobile = false) => {
27 | if (value == 'N/A') return 'N/A'
28 | if (key == 'additionality') return ''
29 | if (key == 'specificity') return ''
30 | if (key == 'rating') return ''
31 | if (key == 'permanence' && value == 1000) return '1000+'
32 | if (mobile) {
33 | if (key == 'mechanism' && value == 0) return 'REMOVAL'
34 | if (key == 'mechanism' && value == 1) return 'AVOIDED'
35 | if (key == 'mechanism' && value == 3) return 'N/A'
36 | }
37 | if (!mobile) {
38 | if (key == 'mechanism' && value == 0) return 'RMV'
39 | if (key == 'mechanism' && value == 1) return 'AVD'
40 | if (key == 'mechanism' && value == 3) return 'N/A'
41 | }
42 | if (key == 'mechanism' && value == 2) return 'BOTH'
43 | else if (key == 'price') {
44 | if (parseFloat(value) < 1000) {
45 | return '$' + _format('.3~s')(parseFloat(value).toFixed(0))
46 | } else {
47 | return '$' + _format('.2~s')(parseFloat(value).toFixed(0))
48 | }
49 | } else if (key == 'negativity') return parseFloat(value).toFixed(2)
50 | else if (key == 'volume') {
51 | if (value < 1000) return value
52 | else if (value >= 1000 && value < 1000000)
53 | return Math.round(value / 1000) + 'k'
54 | else if (value >= 1000000) return Math.round(value / 1000000) + 'M'
55 | } else return value
56 | }
57 |
58 | return (
59 |
60 | {!embed && (
61 |
62 |
63 |
74 |
75 |
76 |
87 |
88 |
89 | )}
90 | {embed && width < 400 && (
91 |
102 | )}
103 | {embed && width >= 400 && (
104 |
116 | )}
117 |
118 | )
119 | }
120 |
121 | export default Metric
122 |
--------------------------------------------------------------------------------
/components/projects/report/metric/mobile.js:
--------------------------------------------------------------------------------
1 | import AnimateHeight from 'react-animate-height'
2 | import { useThemeUI, Box, Grid, Divider, Text } from 'theme-ui'
3 | import { Expander } from '@carbonplan/components'
4 | import { Check } from '@carbonplan/icons'
5 | import Rating from '../graphics/rating'
6 | import Squares from '../graphics/squares'
7 |
8 | const sx = {
9 | comment: {
10 | fontFamily: 'faux',
11 | fontSize: [1],
12 | color: 'secondary',
13 | letterSpacing: 'faux',
14 | lineHeight: 'small',
15 | mt: [0],
16 | mb: [2],
17 | wordBreak: 'break-word',
18 | },
19 | value: {
20 | fontFamily: 'mono',
21 | fontSize: [3],
22 | textAlign: 'right',
23 | mt: ['5px'],
24 | },
25 | label: {
26 | fontFamily: 'mono',
27 | fontSize: [2],
28 | mt: ['6px'],
29 | textTransform: 'capitalize',
30 | },
31 | units: {
32 | fontFamily: 'mono',
33 | color: 'secondary',
34 | fontSize: [1],
35 | ml: [2],
36 | textTransform: 'normal',
37 | },
38 | rating: {
39 | display: 'inline-block',
40 | ml: [3],
41 | fontSize: ['18px'],
42 | },
43 | }
44 |
45 | const MetricMobile = ({
46 | metric,
47 | toggle,
48 | expanded,
49 | hasDetails,
50 | hasUnits,
51 | tag,
52 | format,
53 | duration,
54 | }) => {
55 | const { theme } = useThemeUI()
56 |
57 | return (
58 |
59 | e.stopPropagation()}
61 | sx={{
62 | cursor: hasDetails ? 'pointer' : 'default',
63 | pointerEvents: 'all',
64 | '#grid > #container > #expander': {
65 | stroke: expanded ? 'primary' : 'secondary',
66 | },
67 | '@media (hover: hover) and (pointer: fine)': {
68 | '&:hover > #grid > #container #expander': {
69 | stroke: 'primary',
70 | },
71 | },
72 | pt: [2],
73 | pb: [3],
74 | }}
75 | >
76 |
77 |
78 |
79 | {metric.name}
80 |
81 | {hasUnits && (
82 |
83 | {metric.units}
84 |
85 | )}
86 |
87 | {(metric.rating === 1 ||
88 | (metric.name === 'additionality' && metric.value === 2)) && (
89 |
100 | )}
101 | {!(
102 | metric.rating === 1 ||
103 | (metric.name === 'additionality' && metric.value === 2)
104 | ) && }
105 | {hasDetails && (
106 |
107 |
112 |
113 | )}
114 |
115 |
124 | {format(metric.name, metric.value, true)}
125 |
126 |
127 | {metric.name == 'mechanism' && (
128 |
136 | {metric.removal && metric.avoided ? 'REMOVAL + AVOIDED' : ''}
137 | {metric.removal && !metric.avoided ? 'REMOVAL ONLY' : ''}
138 | {!metric.removal && metric.avoided ? 'AVOIDED ONLY' : ''}
139 |
140 | )}
141 | {metric.name == 'additionality' && (
142 |
147 | )}
148 | {metric.name == 'specificity' && (
149 |
154 | )}
155 | {metric.name == 'rating' && (
156 |
160 | )}
161 |
162 |
163 |
168 | {expanded && (
169 | {
172 | e.stopPropagation()
173 | toggle(e)
174 | }}
175 | >
176 |
182 | {metric.notes && (
183 |
184 |
194 | NOTES
195 |
196 |
202 | {metric.notes}
203 |
204 |
205 | )}
206 |
211 | {metric.comment && (
212 |
213 |
223 | COMMENT
224 |
225 |
231 | {metric.comment}
232 |
233 |
234 | )}
235 |
236 |
237 | )}
238 | {!expanded && }
239 |
240 |
241 |
242 | )
243 | }
244 |
245 | export default MetricMobile
246 |
--------------------------------------------------------------------------------
/components/projects/report/share.js:
--------------------------------------------------------------------------------
1 | import { useThemeUI, Box } from 'theme-ui'
2 | import { useRef, useState } from 'react'
3 |
4 | const Share = ({ value, label, children }) => {
5 | const [copied, setCopied] = useState(false)
6 | const [tick, setTick] = useState(null)
7 |
8 | const { theme } = useThemeUI()
9 |
10 | const copy = () => {
11 | const blank = document.createElement('textarea')
12 | document.body.appendChild(blank)
13 | blank.value = value
14 | blank.select()
15 | document.execCommand('copy')
16 | document.body.removeChild(blank)
17 | if (tick) clearTimeout(tick)
18 | setCopied(true)
19 | const timeout = setTimeout(() => {
20 | setCopied(false)
21 | }, 1000)
22 | setTick(timeout)
23 | }
24 |
25 | return (
26 |
27 | {
29 | e.stopPropagation()
30 | copy()
31 | }}
32 | sx={{
33 | cursor: 'pointer',
34 | textDecoration: 'none',
35 | display: 'inline-block',
36 | ml: [2],
37 | color: copied ? 'text' : 'secondary',
38 | stroke: copied ? theme.colors.text : theme.colors.secondary,
39 | '&:hover': {
40 | color: 'text',
41 | stroke: theme.colors.text,
42 | },
43 | }}
44 | >
45 | {copied ? 'Copied!' : label}
46 | {children}
47 |
48 |
49 | )
50 | }
51 |
52 | export default Share
53 |
--------------------------------------------------------------------------------
/components/projects/top.js:
--------------------------------------------------------------------------------
1 | import { Flex, Box } from 'theme-ui'
2 | import AnimateHeight from 'react-animate-height'
3 | import { Badge, Toggle, Dimmer, Icons, FadeIn } from '@carbonplan/components'
4 | import { format } from 'd3-format'
5 | import sx from '../styles'
6 |
7 | const Top = ({ data, filtered, tooltips, setTooltips }) => {
8 | return (
9 |
30 |
31 |
39 | Total
40 |
41 |
42 | {filtered.init && (
43 |
44 |
45 | {String(data.length).padStart(3, '0')}
46 |
47 |
48 | )}
49 |
50 |
58 | Filtered
59 |
60 |
61 | {filtered.init && (
62 |
63 |
64 | {String(filtered.count).padStart(3, '0')}
65 |
66 |
67 | )}
68 |
69 |
77 | Volume
78 |
79 |
80 | {filtered.init && (
81 |
82 |
83 | {format('.2~s')(
84 | data
85 | .filter((d) => filtered[d.id])
86 | .map((d) => d.metrics[1].value)
87 | .reduce((a, b) => a + b, 0),
88 | )}
89 |
90 |
91 | )}
92 |
93 |
94 |
95 | )
96 | }
97 |
98 | function capitalize(s) {
99 | if (typeof s !== 'string') return ''
100 | return s.charAt(0).toUpperCase() + s.slice(1)
101 | }
102 |
103 | export default Top
104 |
--------------------------------------------------------------------------------
/components/sidebar/axis.js:
--------------------------------------------------------------------------------
1 | import { useRef, useState, useEffect, useCallback } from 'react'
2 | import { Box } from 'theme-ui'
3 | import { format } from 'd3-format'
4 | import { FadeIn } from '@carbonplan/components'
5 | import { Chart, Plot, Ticks, TickLabels } from '@carbonplan/charts'
6 | import Brush from './brush'
7 | import Points from './points'
8 |
9 | const Axis = ({
10 | x,
11 | y,
12 | highlighted,
13 | filtered,
14 | data,
15 | label,
16 | setBounds,
17 | ticks,
18 | }) => {
19 | const [width, setWidth] = useState(null)
20 |
21 | const chart = useCallback((chartNode) => {
22 | if (chartNode != null) {
23 | setWidth(chartNode.clientWidth)
24 | }
25 | })
26 |
27 | const getWidth = () => {
28 | if (chart.clientWidth) return chart.clientWidth
29 | }
30 |
31 | useEffect(() => {
32 | const resizeListener = () => {
33 | setWidth(getWidth())
34 | }
35 | window.addEventListener('resize', resizeListener)
36 | return () => {
37 | window.removeEventListener('resize', resizeListener)
38 | }
39 | }, [])
40 |
41 | return (
42 |
43 |
52 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | )
68 | }
69 |
70 | export default Axis
71 |
--------------------------------------------------------------------------------
/components/sidebar/brush.js:
--------------------------------------------------------------------------------
1 | import { memo, useEffect, useRef } from 'react'
2 | import { Box } from 'theme-ui'
3 | import { select } from 'd3-selection'
4 | import { brushX } from 'd3-brush'
5 | import { useChart } from '@carbonplan/charts'
6 |
7 | function update(event, x, setBounds, label) {
8 | if (event.selection[0] == event.selection[1]) {
9 | setBounds((bounds) => {
10 | return { ...bounds, [label]: [] }
11 | })
12 | } else {
13 | setBounds((bounds) => {
14 | return {
15 | ...bounds,
16 | [label]: [x.invert(event.selection[0]), x.invert(event.selection[1])],
17 | }
18 | })
19 | }
20 | }
21 |
22 | const Brush = ({ label, setBounds }) => {
23 | const brush = useRef(null)
24 | const { x } = useChart()
25 |
26 | useEffect(() => {
27 | if (brush.current != null) {
28 | select(brush.current).call(
29 | brushX()
30 | .extent([
31 | [0, 0],
32 | [100, 100],
33 | ])
34 | .on('start brush', (e) => update(e, x, setBounds, label)),
35 | )
36 | }
37 | }, [])
38 |
39 | return
40 | }
41 |
42 | export default memo(Brush)
43 |
--------------------------------------------------------------------------------
/components/sidebar/field.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { Box } from 'theme-ui'
3 | import { Row, Column } from '@carbonplan/components'
4 | import TooltipToggle from '../tooltip/toggle'
5 | import TooltipDescription from '../tooltip/description'
6 | import sx from '../styles'
7 |
8 | const Field = ({ label, displayLabel, children, tooltips }) => {
9 | const [value, setValue] = useState(false)
10 |
11 | return (
12 | <>
13 |
14 |
15 | {displayLabel}
16 |
17 |
18 |
19 |
24 |
25 | {children}
26 |
27 |
32 |
33 |
34 |
35 | >
36 | )
37 | }
38 |
39 | export default Field
40 |
--------------------------------------------------------------------------------
/components/sidebar/index.js:
--------------------------------------------------------------------------------
1 | import { memo, useState, useEffect } from 'react'
2 | import { Box, Input, Styled, Grid, Text, Divider, Link } from 'theme-ui'
3 | import { default as NextLink } from 'next/link'
4 | import { Tray } from '@carbonplan/components'
5 | import Search from './search'
6 | import Metadata from './metadata'
7 | import Metrics from './metrics'
8 | import Mobile from './mobile'
9 | import Notice from '../notice'
10 |
11 | const Sidebar = ({
12 | highlighted,
13 | filtered,
14 | data,
15 | filters,
16 | setFilters,
17 | bounds,
18 | setBounds,
19 | tooltips,
20 | settingsExpanded,
21 | }) => {
22 | function setSearch(value) {
23 | setFilters((filters) => {
24 | return { ...filters, search: value }
25 | })
26 | }
27 |
28 | return (
29 | <>
30 |
46 |
47 |
57 | CDR Database
58 |
59 |
69 |
70 | These are reports on public Carbon Dioxide Removal project
71 | proposals. Built for transparency. Download as a{' '}
72 |
76 | JSON
77 | {' '}
78 | or{' '}
79 |
83 | CSV
84 | {' '}
85 | (licensed as{' '}
86 |
87 | CC-BY
88 |
89 | ). Read our{' '}
90 |
91 | methods
92 |
93 | .
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
107 |
108 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
128 |
129 | >
130 | )
131 | }
132 |
133 | export default Sidebar
134 |
--------------------------------------------------------------------------------
/components/sidebar/metadata.js:
--------------------------------------------------------------------------------
1 | import { memo, useMemo, useCallback } from 'react'
2 | import { Box } from 'theme-ui'
3 | import { Filter, Tag } from '@carbonplan/components'
4 | import RatingPicker from './rating-picker'
5 | import Field from './field'
6 |
7 | const colors = {
8 | dac: 'purple',
9 | forests: 'green',
10 | mineralization: 'grey',
11 | biomass: 'yellow',
12 | ocean: 'teal',
13 | soil: 'orange',
14 | }
15 |
16 | const categories = [
17 | 'forests',
18 | 'soil',
19 | 'biomass',
20 | 'ocean',
21 | 'mineralization',
22 | 'dac',
23 | ]
24 | const years = ['2020', '2021']
25 | const sources = ['stripe', 'microsoft']
26 |
27 | const mechanisms = ['removal', 'avoided']
28 |
29 | const filterGroups = {
30 | categories,
31 | sources,
32 | years,
33 | mechanisms,
34 | }
35 |
36 | const useFilterGroups = (filters) => {
37 | return useMemo(() => {
38 | const result = {}
39 | for (const group in filterGroups) {
40 | result[group] = filterGroups[group].reduce((obj, key) => {
41 | obj[key] = filters[key]
42 | return obj
43 | }, {})
44 | }
45 |
46 | return result
47 | }, [filters])
48 | }
49 | const Metadata = ({ filters: combinedFilters, setFilters, tooltips }) => {
50 | const filters = useFilterGroups(combinedFilters)
51 |
52 | const toggleOption = useCallback((updatedFilters) => {
53 | setFilters((filters) => {
54 | return { ...filters, ...updatedFilters }
55 | })
56 | })
57 |
58 | const setRating = useCallback((value) => {
59 | setFilters((filters) => {
60 | return { ...filters, ['rating']: value }
61 | })
62 | })
63 |
64 | return (
65 |
66 |
67 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
86 |
91 |
92 |
93 |
94 |
95 |
96 | )
97 | }
98 |
99 | export default memo(Metadata)
100 |
--------------------------------------------------------------------------------
/components/sidebar/metric.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Box, Grid, Text } from 'theme-ui'
3 | import { format } from 'd3-format'
4 | import { Row, Column, Expander } from '@carbonplan/components'
5 | import Axis from './axis'
6 | import TooltipToggle from '../tooltip/toggle'
7 | import TooltipDescription from '../tooltip/description'
8 | import sx from '../styles'
9 |
10 | const Metric = ({
11 | x,
12 | y,
13 | highlighted,
14 | filtered,
15 | data,
16 | units,
17 | label,
18 | setBounds,
19 | bounds,
20 | ticks,
21 | tooltips,
22 | tooltipLabel,
23 | }) => {
24 | const [tooltip, setTooltip] = useState(false)
25 | const [expanded, setExpanded] = useState(false)
26 |
27 | const onClick = () => {
28 | if (expanded) {
29 | setExpanded(false)
30 | setBounds((bounds) => {
31 | return {
32 | ...bounds,
33 | [label]: [],
34 | }
35 | })
36 | } else {
37 | setExpanded(true)
38 | }
39 | }
40 |
41 | return (
42 |
43 |
44 |
45 | #expander': {
54 | stroke: 'primary',
55 | },
56 | '&:hover > #label': {
57 | color: 'primary',
58 | },
59 | '&:hover > #label > #units': {
60 | color: 'secondary',
61 | },
62 | }}
63 | onClick={onClick}
64 | >
65 |
75 |
83 | {label}
84 |
95 | {units}
96 |
97 |
98 |
99 |
100 | {
101 |
102 | {expanded && (
103 |
115 | {!isNaN(bounds[0]) && format('.3~s')(bounds[0].toFixed(0))}
116 | {!isNaN(bounds[0]) && ' - '}
117 | {!isNaN(bounds[0]) && format('.3~s')(bounds[1].toFixed(0))}
118 | {isNaN(bounds[0]) && tooltips && 'drag to filter'}
119 |
120 | )}
121 |
122 |
127 |
128 |
129 | }
130 |
131 |
137 | {expanded && (
138 |
148 | )}
149 |
150 | )
151 | }
152 |
153 | export default Metric
154 |
--------------------------------------------------------------------------------
/components/sidebar/metrics.js:
--------------------------------------------------------------------------------
1 | import { memo, useState } from 'react'
2 | import { Box } from 'theme-ui'
3 | import { scaleOrdinal, scaleLog } from 'd3-scale'
4 | import Metric from './metric'
5 | import sx from '../styles'
6 |
7 | const threshold = (d, min, max) => {
8 | return Math.max(Math.min(d, max), min)
9 | }
10 |
11 | const Metrics = ({
12 | highlighted,
13 | filtered,
14 | data,
15 | bounds,
16 | setBounds,
17 | tooltips,
18 | }) => {
19 | return (
20 |
21 |
22 | Filter by metrics
23 |
24 | {
30 | return { ...d, value: threshold(d.value, 10, 1000000) }
31 | })}
32 | label='volume'
33 | tooltipLabel='volumeFilter'
34 | units='tCO₂'
35 | setBounds={setBounds}
36 | bounds={bounds.volume}
37 | ticks={[10, 100, 1000, 10000, 100000, 1000000]}
38 | tooltips={tooltips}
39 | />
40 |
41 | d.value !== 'N/A')
48 | .map((d) => {
49 | return { ...d, value: threshold(d.value, 1, 1000) }
50 | })}
51 | label='permanence'
52 | tooltipLabel='permanenceFilter'
53 | units='years'
54 | setBounds={setBounds}
55 | bounds={bounds.permanence}
56 | ticks={[1, 10, 100, 1000]}
57 | tooltips={tooltips}
58 | />
59 |
60 |
61 | )
62 | }
63 |
64 | export default memo(Metrics)
65 |
--------------------------------------------------------------------------------
/components/sidebar/mobile.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { useThemeUI, Box, Text } from 'theme-ui'
3 | import { Tag } from '@carbonplan/components'
4 | import RatingPicker from './rating-picker'
5 |
6 | const colors = {
7 | dac: 'purple',
8 | forests: 'green',
9 | mineralization: 'grey',
10 | biomass: 'yellow',
11 | ocean: 'teal',
12 | soil: 'orange',
13 | }
14 |
15 | const categories = [
16 | 'forests',
17 | 'soil',
18 | 'biomass',
19 | 'ocean',
20 | 'mineralization',
21 | 'dac',
22 | ]
23 |
24 | const Mobile = ({ filters, setFilters, expanded }) => {
25 | const { theme } = useThemeUI()
26 |
27 | function toggleOption(value) {
28 | setFilters((filters) => {
29 | return { ...filters, [value]: !filters[value] }
30 | })
31 | }
32 |
33 | function toggleOptionUnique(value, list) {
34 | let updated = {}
35 | list.forEach((d) => {
36 | updated[d] = value === d ? true : false
37 | })
38 | setFilters((filters) => {
39 | return {
40 | ...filters,
41 | ...updated,
42 | }
43 | })
44 | }
45 |
46 | function toggleAll(list) {
47 | let updated = {}
48 | if (isAll(list)) {
49 | list.forEach((d) => {
50 | updated[d] = false
51 | })
52 | } else {
53 | list.forEach((d) => {
54 | updated[d] = true
55 | })
56 | }
57 |
58 | setFilters((filters) => {
59 | return {
60 | ...filters,
61 | ...updated,
62 | }
63 | })
64 | }
65 |
66 | function setRating(value) {
67 | setFilters((filters) => {
68 | return { ...filters, ['rating']: value }
69 | })
70 | }
71 |
72 | function isAll(list) {
73 | let check = 0
74 | list.forEach((d) => {
75 | if (filters[d]) check += 1
76 | })
77 | return check == categories.length
78 | }
79 |
80 | return (
81 |
89 |
101 |
102 |
103 | CATEGORY
104 |
105 |
106 | {categories.map((d) => (
107 | toggleOption(d)}
113 | onDoubleClick={() => toggleOptionUnique(d, categories)}
114 | >
115 | {d}
116 |
117 | ))}
118 | toggleAll(categories)}
122 | >
123 | All
124 |
125 |
126 |
127 | RATING
128 |
129 |
130 |
131 |
132 |
133 | )
134 | }
135 |
136 | export default Mobile
137 |
--------------------------------------------------------------------------------
/components/sidebar/points.js:
--------------------------------------------------------------------------------
1 | import { Box } from 'theme-ui'
2 | import { memo } from 'react'
3 | import { mix } from '@theme-ui/color'
4 | import { useChart, Circle } from '@carbonplan/charts'
5 |
6 | const colors = {
7 | dac: 'purple',
8 | forests: 'green',
9 | mineralization: 'grey',
10 | biomass: 'yellow',
11 | ocean: 'teal',
12 | soil: 'orange',
13 | }
14 |
15 | const tagsToIndex = {
16 | forests: 5,
17 | soil: 4,
18 | biomass: 3,
19 | ocean: 2,
20 | mineralization: 1,
21 | dac: 0,
22 | }
23 |
24 | const Points = ({ data, filtered, highlighted, x, y }) => {
25 | return data
26 | .sort((a, b) => {
27 | if (filtered[a.id] && !filtered[b.id]) return 1
28 | if (!filtered[a.id] && filtered[b.id]) return -1
29 | else return 0
30 | })
31 | .map((d) => {
32 | return (
33 |
45 | )
46 | })
47 | }
48 |
49 | export default memo(Points)
50 |
--------------------------------------------------------------------------------
/components/sidebar/rating-picker.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { Box } from 'theme-ui'
3 | import { Check } from '@carbonplan/icons'
4 |
5 | const RatingPicker = ({ value, setValue }) => {
6 | const [hover, setHover] = useState(0)
7 |
8 | return (
9 |
10 | {[0, 1, 2, 3, 4].map((d, i) => (
11 | setHover(d + 1)}
14 | onMouseLeave={() => setHover(0)}
15 | onClick={() => setValue(d + 1)}
16 | sx={{ display: 'inline-block' }}
17 | >
18 | 0 && d < hover) ? 1 : 0.3,
28 | transition: '0.15s',
29 | '&: hover': {
30 | stroke: 'primary',
31 | strokeWidth: '2px',
32 | },
33 | }}
34 | />
35 |
36 | ))}
37 |
38 | )
39 | }
40 |
41 | export default RatingPicker
42 |
--------------------------------------------------------------------------------
/components/sidebar/search.js:
--------------------------------------------------------------------------------
1 | import { memo, useState, useEffect, useRef } from 'react'
2 | import { Box, Grid, Text, Input } from 'theme-ui'
3 | import Field from './field'
4 | import sx from '../styles'
5 |
6 | const Search = ({ setSearch, tooltips }) => {
7 | const [value, setValue] = useState('')
8 | const [hasFocus, setFocus] = useState(false)
9 | const inputRef = useRef(null)
10 |
11 | useEffect(() => {
12 | function handler(event) {
13 | const { key, keyCode, metaKey } = event
14 | if (key === '/' && metaKey) {
15 | if (!hasFocus) inputRef.current.focus()
16 | }
17 | }
18 | document.addEventListener('keydown', handler)
19 | return () => {
20 | document.removeEventListener('keydown', handler)
21 | }
22 | }, [])
23 |
24 | return (
25 |
26 |
27 |
28 | {
33 | setValue(e.currentTarget.value)
34 | setSearch(e.currentTarget.value)
35 | }}
36 | onFocus={() => setFocus(true)}
37 | onBlur={() => setFocus(false)}
38 | sx={{
39 | fontSize: [1],
40 | height: '24px',
41 | width: '90%',
42 | pt: [2],
43 | pb: [3],
44 | pl: [0],
45 | pr: [0],
46 | mt: ['3px'],
47 | fontFamily: 'mono',
48 | borderRadius: '0px',
49 | borderWidth: '0px',
50 | textAlign: 'left',
51 | display: 'inline-block',
52 | ':focus-visible': {
53 | outline: 'none !important',
54 | background: 'none !important',
55 | },
56 | }}
57 | value={value}
58 | />
59 |
60 |
61 |
62 | )
63 | }
64 |
65 | export default memo(Search)
66 |
--------------------------------------------------------------------------------
/components/styles.js:
--------------------------------------------------------------------------------
1 | const sx = {
2 | label: {
3 | fontFamily: 'mono',
4 | letterSpacing: 'mono',
5 | fontSize: [1, 1, 1, 2],
6 | textTransform: 'uppercase',
7 | color: 'secondary',
8 | mt: ['3px'],
9 | userSelect: 'none',
10 | },
11 | }
12 |
13 | export default sx
14 |
--------------------------------------------------------------------------------
/components/tooltip/description.js:
--------------------------------------------------------------------------------
1 | import { Box } from 'theme-ui'
2 | import AnimateHeight from 'react-animate-height'
3 | import glossary from '../../glossary'
4 |
5 | const TooltipDescription = ({ label, value, tooltips, ml, sx }) => {
6 | return (
7 |
8 |
13 | {tooltips && value && (
14 |
15 |
22 | {glossary[label]}
23 |
24 |
25 | )}
26 |
27 |
28 | )
29 | }
30 |
31 | export default TooltipDescription
32 |
--------------------------------------------------------------------------------
/components/tooltip/toggle.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import { Box } from 'theme-ui'
3 | import { Info } from '@carbonplan/icons'
4 |
5 | const TooltipToggle = ({ tooltips, value, setValue }) => {
6 | useEffect(() => {
7 | if (!tooltips) setValue(false)
8 | }, [tooltips])
9 |
10 | return (
11 |
12 | #tooltip-toggle': {
20 | stroke: 'primary',
21 | },
22 | },
23 | }}
24 | onClick={(e) => {
25 | e.stopPropagation()
26 | setValue(!value)
27 | }}
28 | >
29 |
38 |
39 |
40 | )
41 | }
42 |
43 | export default TooltipToggle
44 |
--------------------------------------------------------------------------------
/glossary.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | categoryFilter:
3 | 'Click to toggle whether to show projects from the given category. You can also double click to only include that category. Every project gets a primary category based on its carbon removal mechanism. Most projects also have a secondary category, which is included in searches.',
4 | search:
5 | 'Type here to search the database. Searches include the project applicant, project location, all category tags, and any project-specific keywords. Search results also depend on other filter selections below. You can type ⌘ + / to focus the search bar without clicking. ',
6 | sourceFilter:
7 | 'Click to filter based on the project source. Our database is sourced from public project proposals submitted to various entities, such as corporate purchasers of carbon removal. ',
8 | permanence:
9 | 'The duration in years over which carbon storage can be reasonably assured. For biological systems this duration is typically limited by physical and socioeconomic risks, whereas for chemical or geological systems the duration is effectively permanent.',
10 | permanenceFilter:
11 | 'This visual filter shows the permanence of each project. If you expand it using the plus, you can click and drag horizontally on the resulting chart to select a range to filter projects by. Values show the duration over which carbon storage can be reasonably assured.',
12 | volume:
13 | 'The total volume in tCO₂ of carbon removal and/or avoided emissions achieved by the project. We interpret these as gross volumes. Calculting net benefits requires incorporating negativity to account for project emissions.',
14 | volumeFilter:
15 | 'This visual filter shows the volume of each project. If you expand it using the plus, you can click and drag horizontally on the resulting chart to select a range to filter projects by. Values show the total volume in tCO₂ of carbon removal and/or avoided emissions claimed by the project. Calculting net benefits requires incorporating negativity to account for project emissions.',
16 | rating:
17 | 'A single rating to reflect our overall confidence in project claims on a scale from 1 to 5. Validation of mechanism, volume, negativity, or permanence, or a perfect score on additionality, each add 1 to the total rating. A minimum score on additionality subtracts 1 from the total rating. Rating does not necessarily reflect project quality, but rather our ability to validate key information.',
18 | yearFilter:
19 | 'Click to filter based on the year in the which the project was proposed. Most projects are proposed as part of annual or semi-annual procurement process.',
20 | ratingFilter:
21 | 'Click to filter projects by setting a minimum rating. All projects with a rating as high or higher will be shown. The rating for each project reflects our overall confidence in project claims on a scale from 1 to 5. Validation of mechanism, volume, negativity, or permanence, or a perfect score on additionality, each add 1 to the total rating. A minimum score on additionality subtracts 1 from the total rating. Rating does not necessarily reflect project quality, but rather our ability to validate key information.',
22 | mechanism:
23 | 'Does the project remove carbon dioxide from the atmosphere (RMV), avoid emissions that would have otherwise occurred (AVD), or both (BOTH)? Projects that only offer carbon storage without removal or avoided emissions are indicated with N/A. While all can have positive climate benefits, the difference matters for accounting purposes.',
24 | mechanismRating:
25 | 'Click to filter projects based on whether they remove carbon dioxide from the atmosphere (REMOVAL) or avoid emissions that would have otherwise occurred (AVOIDED). Projects that only store carbon dioxide without removing it from the atmosphere or avoiding emissions are shown for either selection. While all can have positive climate benefits, the differences matters for accounting purposes.',
26 | negativity:
27 | 'Negativity reflects the emissions intensity of different carbon removal solutions, and we define it as 1 minus the ratio of gross project emissions to gross climate benefits.',
28 | additionality:
29 | 'Additionality refers to the causal relationship between the funds a climate project seeks and the climate benefits it claims. The default score is 2/3, indicating a project could be additional. 1/3 indicates we found a red flag, or a lack of specificity. 3/3 indicates confidence that a project is likely additional, which we additionally indicate with a checkmark.',
30 | specificity:
31 | 'Specificity reflects access to detailed information in project proposals, publications, or other public materials. The default score is 2/3, indicating a project could be additional. 1/3 indicates critical project data could not be obtained. 3/3 indicates extensive detail on project implementation or planning.',
32 | price:
33 | 'Price is taken directly from project offerings, and is expressed in $/net tCO₂. Price depends on several other factors, including volume. As reported, price does not reflect project duration.',
34 | }
35 |
--------------------------------------------------------------------------------
/methods/components/projects.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Box, Link, Styled } from 'theme-ui'
3 | import collection from '../../data/methods'
4 | import unified from 'unified'
5 | import markdown from 'remark-parse'
6 | import remark2rehype from 'remark-rehype'
7 | import rehype2react from 'rehype-react'
8 |
9 | const processor = unified()
10 | .use(markdown)
11 | .use(remark2rehype)
12 | .use(rehype2react, {
13 | createElement: React.createElement,
14 | components: {
15 | a: Link,
16 | },
17 | })
18 |
19 | const Projects = () => {
20 | return (
21 |
22 | {collection.methods
23 | .sort((a, b) => {
24 | return a.applicant.localeCompare(b.applicant)
25 | })
26 | .filter((d) => {
27 | return !(d.content === '')
28 | })
29 | .map((d) => {
30 | return (
31 |
32 |
42 | {d.applicant}
43 |
44 |
51 | {d.id}
52 |
53 |
54 | {processor.processSync(d.content).result}
55 |
56 |
57 | )
58 | })}
59 |
60 | )
61 | }
62 |
63 | export default Projects
64 |
--------------------------------------------------------------------------------
/methods/feedback.md:
--------------------------------------------------------------------------------
1 | # Feedback?
2 |
3 | All our content is publicaly available and hosted on GitHub, and we welcome feedback on all aspects of our work.
4 |
5 | If you spot an issue with our website or want to request a new feature for this dashboard, please open an [issue](https://github.com/carbonplan/cdr-database) on GitHub.
6 |
7 | If you identify a general issue with our metrics or our methods, please open an [issue](https://github.com/carbonplan/cdr-database) on GitHub or send us an email at [feedback@carbonplan.org](mailto:feedback@carbonplan.org). We are committed to updating and improving our methods and metrics over time, and will gladly consider suggestions or engage with potential collaborators.
8 |
9 | If you identify a specific issue with a project analysis or report, please open an [issue](https://github.com/carbonplan/cdr-database) on GitHub or send us an email at [feedback@carbonplan.org](mailto:feedback@carbonplan.org). Our reports reflect our independent analysis of individual projects based on publicly available materials. While we are committed to listening to all feedback on the content of individual reports, we may or may not choose to modify our reports upon request. That said, if you directly represent a project, and we are unable to make changes that adequately address your concern, we will consider posting a comment attributed to you alongside the report on your project.
10 |
--------------------------------------------------------------------------------
/methods/metrics.md:
--------------------------------------------------------------------------------
1 | import Table from '../components/methods/table'
2 | import InlineCheck from '../components/methods/inline-check'
3 |
4 | export const meta = {
5 | revised: '05-26-2021',
6 | }
7 |
8 | # Metrics
9 |
10 | Our goal is to harmonize metrics across a range of project categories, bringing standards and shared concepts to a complex and nascent space.
11 |
12 | Values for each metric are based on information provided in public project proposals and our own research. We are actively developing better methods for metric calibration based on independent data and models.
13 |
14 | The mark next to each metric shows our validation. We assigned projects a when we could validate a claim with reasonable confidence. Where we didn’t feel confident about validating claims, we left the entry blank, generally erring on the side of caution and respectfulness. The absence of a should not necessarily be interpreted as a criticism of the project, though it may indicate concerns that could be resolved with more information. As we develop better methods, and as we learn more about projects, we expect to be able to validate more claims with confidence, and we may introduce new metrics. We also include “notes” and “comments”, with notes addressing information provided by projects directly and comments explaining our evaluation or interpretation of project information.
15 |
16 | It is important to note that our validation is dependent on access to specific project information, as well as relevant science and data. Validation may therefore reflect underlying biases, such as geographic inequities in publicly accessible science and data, or differences in the information solicited from projects by specific RFPs.
17 |
18 | Validation on metrics is only provided for mechanism, volume, negativity, and permanence. We do not provide validation on cost. In some ways price is self-evident because the price of the offering is set directly by the project. When expressed in terms of $/tCO₂, however, price also reflects uncertainty in volume. Because we validate volume separately, we do not attempt to specifically validate the price.
19 |
20 | For additionality and specificity we provide a qualitative score from 1 to 3, along with comments. Both of these scores are contextual. Additionality is less important when funding decisions are oriented around encouraging innovation, rather than carbon offsetting. For specificity, our scores should be interpreted in light of the fact that all projects in our database have provided a baseline level of information, and achieved a significant degree of transparency, by making materials public.
21 |
22 | Across all metrics, our evaluation is primarily focused on validating whether claims are plausible and consistent with the best available science and data. We also strive, through our metrics, to harmonize concepts and quantities across a wide diversity of projects. This process is more straightforward for methods based on established physical or biological processes, but can pose particular challenges for proprietary industrial practices or inherently variable physical properties.
23 |
24 | After analyzing each metric separately for validation, we reflect our overall confidence in project claims with a single score on a scale from 1 to 5. Validation of mechanism, volume, negativity, or permanence, or a perfect score on additionality, each add 1 to the total.
25 |
26 | In the remainder of this document, we explain each metric and the overall project rating in detail.
27 |
28 | ## Mechanism
29 |
30 | A project’s mechanism is its fundamental interaction with the global carbon cycle. A project that removes CO₂ from the atmosphere will feature a downward arrow on the left-hand side of the metric and the abbreviation CDR. A project that reduces or avoids carbon dioxide emissions — for example, by storing it in a more permanent form — will feature a closed circle on the right-hand side of the metric and the abbreviation AVD. In the case where a proposal offers only carbon storage (but not avoided emissions or carbon removal), we indicate the mechanism as N/A.
31 |
32 | While we only consider projects in this database that have the potential to be part of a carbon removal system, some of these projects are, in their current form, only responsible for avoided emissions. For example, utilization of CO₂ for storage in building materials where the CO₂ is currently sourced from an industrial waste stream currently constitutes an avoided emissions process, but would constitute carbon removal if its CO₂ were sourced from direct air capture or biological sources.
33 |
34 | In general, a project’s mechanism should be well-specified. The challenge of validation can be more significant, however, when a project involves early-stage technologies or references a broad variety of potential approaches without specifics about which ones are used.
35 |
36 | In the case where a proposal offers only one type of carbon benefit (e.g. only carbon removal) from a project that produces both carbon removal and avoided emissions, mechanism reflects the mechanisms of the project as a whole.
37 |
38 |
42 |
43 | ## Volume
44 |
45 | Volume estimates almost all come directly from project applications, and are stated in metric tCO₂. In a few cases, volume is specified in a way that can’t easily be compared with other projects, and can potentially be normalized.
46 |
47 | Volume estimates are complicated by time scale because the volume for different projects and project categories reflect different time scales of carbon removal. Projects tend to fall into one of two categories: those that are directly estimating the amount of carbon removed through some process on an annual basis (e.g. direct air capture), and those that are performing a one-time procurement of a material that will contribute to carbon removal or storage over some potentially unknown time horizon (e.g. procurement of minerals for enhanced weathering or procurement of biomass to produce biochar). Forests projects pose a challenge because they may report an annual or project lifetime depending on the context. In these cases, we simply report as faithfully as possible what projects claim (and why), but caution should be exercised when comparing volume estimates for these projects.
48 |
49 | If there is uncertainty about whether the offered volume is a gross or net volume, we conservatively interpret it to be a gross volume and attempt validation accordingly.
50 |
51 | We use one of two approaches to validate volume.
52 |
53 | First, where possible, we perform an independent calculation based on third-party information to replicate the project’s claimed volume number, or at least show that it falls within a plausible range — for example, by consulting yield tables for forest projects, or published sorbent properties for direct air capture. Over time, we will develop more sophisticated methods and models for these calculations, which will in turn require more specific information from projects for validation.
54 |
55 | In other cases, where the volume is primarily driven by procurement of materials for an industrial process, we try to assess whether there is enough information in public materials to show an internally consistent calculation that yields the given volume number. But this is not a verification of the ability for the project to procure the necessary materials. Rather, it’s a demonstration of internal consistency.
56 |
57 | In many cases we were unable to validate volume through an independent calculation or using project information, either because we lacked sufficient detail to parameterize an otherwise well-specific proposal, or because we were unable to replicate broad claims with simple models. Where possible, we indicated in our comments what additional information would be useful. A lack of validation on volume should not necessarily be taken as criticism, and we hope to improve in this ability over time.
58 |
59 | Key details needed to validate forest project volumes include project area, forest type, claimed standing stock, and claimed growth rate. For forest projects that include timber production, harvesting cycles must also be clearly communicated. For soil projects, key details include project area, specificity around interventions claimed to produce climate benefits, and a clear methodology for modeling or measuring changes in soil organic carbon. For biomass projects, key details include feedstock, annual production capacity, and claimed rate of sequestration per unit of transformed biomass. For DAC projects, key details needed include sorbent or solvent technology, project removal capacity, and clarity around the fate of captured CO2. For mineralization projects, key details needed include exchange material(s), claimed exchange rate(s), and some combination of claimed time bounds or exchange material volume bounds. Ocean projects are diverse and analyzed on a case by case basis.
60 |
61 |
65 |
66 | ## Negativity
67 |
68 | Negativity reflects the emissions intensity of different carbon removal solutions, and we define it as 1 minus the ratio of gross project emissions to gross climate benefits, including carbon removal and storage. Calculating negativity depends on a life cycle assessment that quantifies project emissions and climate benefits. If emissions are low relative to the climate benefits, this metric will approach 1.
69 |
70 | As discussed above under mechanism, the concept of “climate benefits” is complicated because some projects directly remove CO₂ (and thus contribute “gross removal”) but others primarily avoid and store CO₂ emissions (and thus contribute “gross storage”). For example, a project that mineralizes CO₂ sourced from industrial waste streams is primarily avoiding emissions, rather than directly removing CO₂. We call the CO₂ it mineralizes its “gross storage.” Similarly, a project that produces biochar from biogenic materials is not directly removing CO₂ from the atmosphere, but is rather avoiding biogenic CO₂ emissions. We would calculate the carbon embedded in biochar as the project’s “gross storage” for the purposes of the negativity metric.
71 |
72 | When projects report negativity based on volume that also includes avoided emissions due to other practices that were prevented (e.g. prevented use of alternative building materials), we recompute the ratio, if possible, to only reflect the carbon removal or carbon storage component.
73 |
74 | Estimating negativity requires a life cycle assessment to quantify emissions sources and sinks. This can be an abstract 'per ton' estimate based on parameters of the technology, or based on data from an instantiated project.
75 |
76 | To evaluate negativity, we analyze publicly provided life cycle assessment information. Where possible, we extract explicit gross project emissions, and gross volumes of carbon dioxide either removed or stored, and then compute the ratio. When projects simply report a ‘per ton’ ratio, we report that instead. If boundary conditions and emission sources are specified, we summarize them.
77 |
78 | For projects with an additional avoided removal component above and beyond the primary carbon removal or storage mechanism (e.g. emissions avoided due to less use of an alternative building material), we do not include those avoided emissions when calculating ratios.
79 |
80 | For forest projects, for consistency, we compute negativity over the project lifetime. Often these projects have emissions associated with the first year, but carbon removal potential that persists across a much longer duration.
81 |
82 | We express no judgment on this metric when we don't know enough about the project's process to independently confirm a claim.
83 |
84 |
88 |
89 | ## Permanence
90 |
91 | We consider the permanence of a project the duration over which durable carbon storage can be reasonably assured, in years.
92 |
93 | Our evaluation of permanence is largely categorical, with different considerations and heuristics for natural solutions (e.g. soil and forests), solutions that involve geological, mineral, or other physical storage materials.
94 |
95 | For all projects, when a range is provided, we report the minimum. All times are adjusted so that they are relative to the most recent project documents. Permanence claims equal to or greater than 1000 were recorded as 1000+.
96 |
97 | For soil and forest projects, we separately examine physical and socioeconomic risks. Physical risks include wildfire, drought, and natural disaster. Socioeconomic risks include breaches of contract, bankruptcy, and other factors which prevent projects from continuing to provide climate benefits. When explicit contract terms are described, we consider the contract duration the duration of permanence, even if it is lower than the number suggested by the physical risks.
98 |
99 | We consider 30 years or less a plausible maximum duration of permanence for projects managing socioeconomic risks, especially involving forests and soil. While somewhat arbitrary, 30 years offers a useful threshold because it is the maximum term length of residential mortgages in the United States, and therefore represents an upper-bound on the length of private contracts in a real-world financial market.
100 |
101 | Building materials are intermediate, as the plausible duration of permanence depends on the material (e.g. concrete vs laminated wood). While concrete can be considered similarly permanent as mineralization, wood products are likely bounded by a range of 50-100 years (Hepburn et al., 2019; Lippke et al., 2005).
102 |
103 |
107 |
108 | ## Price
109 |
110 | Project price is taken directly from public project offerings. Some prices have been modified for accounting consistency, for example, by averaging a range or ensuring that within a category prices are expressed in relation to climate benefits expected over the entire project duration (e.g. for forest projects).
111 |
112 |
113 | Prices are expressed per metric tCO₂ and thus, similar to volume, reflect project lifetimes, some of which are one year, some longer. We did not attempt to validate this metric because the price at which a project offers its product speaks for itself, at least in terms of total price. The stated price in terms of $/tCO₂ depends on several other factors, however, including projects’ estimated volume. As reported, the stated price does not reflect project duration, i.e. the reality that a ton of permanently removed carbon is a meaningfully different product than a ton of temporarily removed carbon. This makes permanence another important attribute to include in the [comparison of relative project prices](https://carbonplan.org/research/permanence-calculator).
114 |
115 | We are working with collaborators on building domain-specific open source models to assist with price estimation, e.g. for [direct air capture](https://carbonplan.org/research/dac-calculator) and mineralization.
116 |
117 | ## Additionality
118 |
119 | Additionality refers to the causal relationship between the funds a climate project seeks and the climate benefits it claims. Establishing a connection depends on a counterfactual scenario that asks what would happen in the absence of funding. If the project will generate climate benefits but is feasible if and only if it receives payment, then the project is additional. If the project will likely generate climate benefits without the funding, then it is non-additional.
120 |
121 | Evaluating additionality is critical but difficult because the counterfactual scenario can only be estimated, not observed — there is no “control” earth. Additionality is also dynamic: it depends on highly contextual information that can change over time, such as price spreads between different commodities.
122 |
123 | We examine projects for contextual information around the likelihood of additionality and provide values on a three point scale. Projects with high marginal costs or technologies without commercial markets tend to have higher values. Projects with benefits that have already been promised to other parties, or where non-climate co-benefits justify their economics, have lower values.
124 |
125 | To some extent, a high additionality score is contingent on high specificity (see below). If a project lacks specificity and there is not a categorical reason to think otherwise, the project will likely also receive a low additionality score.
126 |
127 | Different organizations might have different priorities for project selection. Low values on Additionality are not a concern when the purpose of funding is to advance technology, but should be a primary consideration if the goal is to claim an emissions offset.
128 |
129 |
135 |
136 | ## Specificity
137 |
138 | Specificity reflects whether there is enough detailed information in project proposals, publications, or other public materials for us to validate our metrics. All entries in this database are based on projects’ public proposals, which provide a baseline level of information and transparency. Our specificity metric is intended to indicate when the available information goes above and beyond that baseline.
139 |
140 | The utility of different information depends on the project category, but can include: specific project parameters (locations, practices), specific technology components, peer-reviewed publications, third-party verifications, and any datasets or models used for project quantification or life cycle assessment.
141 |
142 | In considering specificity, we appreciate there can be sensitivity around proprietary and private information, including intellectual property for technology, or information about land owners and existing business practices. A low value on specificity does not necessarily mean a carbon removal strategy cannot be effective, but it does indicate the importance of accurately measuring and verifying project performance.
143 |
144 | In general, we encourage sharing as many details as possible. We believe that making specific project information more public and transparent can help the field develop new and better solutions, as well as help organizations make better decisions when selecting projects.
145 |
146 |
152 |
153 | ## Rating
154 |
155 | After analyzing each metric separately for validation, we reflect our overall confidence in project claims with a single rating on a scale from 1 to 5. Validation of mechanism, volume, negativity, or permanence each add 1 point to the total. For additionality, which is scored on a three point scale, we subtract 1 point from the total if the project receives a 1/3 and we add 1 point to the total if the project receives a 3/3, and otherwise do not change it. The lowest rating a project can receive is 1.
156 |
157 | Price is not reflected in this rating because we do not attempt to validate it. Specificity is not reflected explicitly, but is embedded in our ability to validate or score the other metrics.
158 |
159 | Rating does not necessarily reflect project quality, but rather our ability to validate key information. Furthermore, as with validation of the individual metrics, a low rating should not necessarily be interpreted as a criticism of the project, though it may indicate concerns that could be resolved with more information. As we develop better methods for validation, we expect to be able to refine our ratings.
160 |
--------------------------------------------------------------------------------
/methods/projects.md:
--------------------------------------------------------------------------------
1 | import Projects from './components/projects'
2 |
3 | export const meta = {
4 | revised: '05-26-2020',
5 | }
6 |
7 | # Projects
8 |
9 | Here we provide any additional notes for projects we have analyzed. Projects are listed by applicant and ID, and sorted by applicant. All analysis and evaluation is based on information provided in public project proposals, publications, and our own research. We do not exhaustively list methods for every metric for every project, but rather highlight specific considerations or calculations. Methods may be minimal or absent if substantial information is provided in the notes and comments of our reports.
10 |
11 | Here and elsewhere, our primary focus is analyzing and independently validating scientific plausibility and credibility. Our hope is that sharing data and insights can benefit the field as a whole, and complement the work of the inventors, scientists, entrepreneurs, and activists who are making climate solutions a reality.
12 |
13 |
14 |
15 | ## References
16 |
17 | Here we list all references used that we either used directly for project analysis or that helped inform our approach more broadly.
18 |
19 | Ahimana & Maghembe (1987) Growth and biomass production by young Eucalyptus tereticornis under agroforestry at Morogoro, Tanzania, Forest Ecology and Management, [DOI](https://doi.org/10.1016/0378-1127)
20 |
21 | Bittig et al. (2019) A BGC-Argo Guide: Planning, Deployment, Data Handling and Usage, Frontiers in Marine Science, [DOI](https://doi.org/10.3389/fmars.2019.00502)
22 |
23 | Campbell et al. (2018) Potential carbon storage in biochar made from logging residue: Basic principles and Southern Oregon case studies, PLOS One, [DOI](https://doi.org/10.1371/journal.pone.0203475)
24 |
25 | Fargione et al. (2018) Natural climate solutions for the United States, Science Advances, [DOI](https://doi.org/10.1126/sciadv.aat1869)
26 |
27 | Friday, J. B. (2010) Farm and forestry production and marketing profile for koa (Acacia koa), Elevitch, C. R. (ed). Specialty Crops for Pacific Island Agroforestry, Permanent Agriculture Resources.
28 |
29 | Fuss et al. (2018) Negative emissions—Part 2: Costs, potentials and side effects, Environmental Research Letters, [DOI](https://doi.org/10.1088/1748-9326/aabf9f)
30 |
31 | Gunnarsson et al. (2018) The rapid and cost-effective capture and subsurface mineral storage of carbon and sulfur at the CarbFix2 site, International Journal of Greenhouse Gas Control, [DOI](https://doi.org/10.1016/j.ijggc.2018.08.014)
32 |
33 | Harmon et al (2020) Release of coarse woody detritus-related carbon: a synthesis across forest biomes, Carbon Balance and Management, [DOI](https://doi.org/10.1186/s13021-019-0136-6)
34 |
35 | Hepburn et al. (2019) The technological and economic prospects for CO2 utilization and removal, Nature, [DOI](https://doi.org/10.1038/s41586-019-1681-6)
36 |
37 | Keith et al. (2018) A Process for Capturing CO2 from the Atmosphere, Joule, [DOI](https://10.1016/j.joule.2018.05.006.)
38 |
39 | Kelemen et al. (2018) In situ carbon mineralization in ultramafic rocks: Natural processes and possible engineered methods, Energy Procedia, [DOI](https://doi.org/10.1016/j.egypro.2018.07.013)
40 |
41 | Keleman et al. (2020) Engineered carbon mineralization in ultramafic rocks for CO2 removal from air: Review and new insights, Chemical Geology, [DOI](https://doi.org/10.1016/j.chemgeo.2020.119628)
42 |
43 | Kelland et al. (2020) Increased yield and CO2 sequestration potential with the C4 cereal Sorghum bicolor cultivated in basaltic rock dust‐amended agricultural soil, Global Change Biology, [DOI](https://doi.org/10.1111/gcb.15089)
44 |
45 | Krause-Jensen et al. (2018) Sequestration of macroalgal carbon: the elephant in the Blue Carbon room, Biology Letters, [DOI](https://doi.org/10.1098/rsbl.2018.0236)
46 |
47 | Krause-Jensen & Duarte (2016) Substantial role of macroalgae in marine carbon sequestration, Nature Geoscience, [DOI](https://doi.org/10.1038/ngeo2790)
48 |
49 | La Plante et al. (2021) Saline Water-Based Mineralization Pathway for Gigatonne-Scale CO2 Management, ACS Sustainable Chemistry & Engineering, [DOI](https://doi.org/10.1021/acssuschemeng.0c08561)
50 |
51 | Lippke et al. (2011) Life cycle impacts of forest management and wood utilization on carbon mitigation: knowns and unknowns, Carbon Management, Carbon Management, [DOI](https://doi.org/10.4155/CMT.11.24)
52 |
53 | Macreadie et al. (2019) The future of Blue Carbon science, Nature Communication, [DOI](https://doi.org/10.1038/s41467-019-11693-w)
54 |
55 | Magnani et al. (2007) The human footprint in the carbon cycle of temperate and boreal forests, Nature, [DOI](https://doi.org/10.1038/nature05847)
56 |
57 | McQueen et al. (2020) Ambient weathering of magnesium oxide for CO2 removal from air, Nature Communication, [DOI](https://doi.org/10.1038/s41467-020-16510-3)
58 |
59 | McQueen et al. (2021) A review of direct air capture (DAC): scaling up commercial technologies and innovating for the future, Progress in Energy, [DOI](https://doi.org/10.1088/2516-1083/abf1ce)
60 |
61 | Monkman & MacDonald (2017) On carbon dioxide utilization as a means to improve the sustainability of ready-mixed concrete, Journal of Cleaner Production, [DOI](https://doi.org/10.1016/j.jclepro.2017.08.194)
62 |
63 | Montserrat et al. (2017) Olivine Dissolution in Seawater: Implications for CO2 Sequestration through Enhanced Weathering in Coastal Environments, Environmental Science & Technology, [DOI](https://doi.org/10.1021/acs.est.6b05942)
64 |
65 | Mulligan et al. (2020) CarbonShot: Federal Policy Options for Carbon Removal in the United States, World Resources Institute, [DOI](https://www.wri.org/publication/carbonshot-federal-policy-options-for-carbon-removal-in-the-united-states)
66 |
67 | NAS (National Academies of Sciences, Engineering, and Medicine) (2018) Negative Emissions Technologies and Reliable Sequestration: A Research Agenda, [DOI](https://doi.org/10.17226/25259)
68 |
69 | Pan et al. (2011) A Large and Persistent Carbon Sink in the World's Forests, Science, [DOI](https://doi.org/10.1126/science.1201609)
70 |
71 | Piñeiro et al. (2010) Pathways of Grazing Effects on Soil Organic Carbon and Nitrogen,
72 | Rangeland Ecology & Management, [DOI](https://doi.org/10.2111/08-255.1)
73 |
74 | Poeplau & Don (2015) Carbon sequestration in agricultural soils via cultivation of cover crops
75 | – A meta-analysis, Agriculture, Ecosystems and Environment, [DOI](http://doi.org/10.1016/j.agee.2014.10.024)
76 |
77 | Renforth & Henderson (2017) Assessing ocean alkalinity for carbon sequestration, Review of Geophysics, [DOI](https://doi.org/10.1002/2016RG000533)
78 |
79 | Sanderman & Baldrock (2010) Accounting for soil carbon sequestration in national inventories: a soil scientist’s perspective, Environmental Research Letters, [DOI](http://doi.org/10.1088/1748-9326/5/3/034003)
80 |
81 | Sanz-Peŕez et al. (2016) Direct Capture of CO2 from Ambient Air, Chemical Reviews, [DOI](https://doi.org/10.1021/acs.chemrev.6b00173)
82 |
83 | Schmidt et al. (2018) Pyrogenic carbon capture and storage, Global Change Biology Bioenergy, [DOI](https://doi.org/10.1111/gcbb.12553)
84 |
85 | Smith et al. (2005) Methods for calculating forest ecosystem and harvested carbon with standard estimates for forest types of the United States
86 |
87 | Smith et al. (2019) How to measure, report and verify soil carbon change to realize the potential of soil carbon sequestration for atmospheric greenhouse gas removal, Global Change Biology, [DOI](https://doi.org/10.1111/gcb.14815)
88 |
89 | Snæbjörnsdóttir et al. (2020) Carbon dioxide storage through mineral carbonation, Nature Reviews Earth and Environment, [DOI](http://doi.org/10.1038/s43017-019-0011-8)
90 |
91 | Sohngen & Mendelsohn (2003) An Optimal Control Model of Forest Carbon Sequestration, American Journal of Agricultural Economics, [DOI](http://hdl.handle.net/10.1111/1467-8276.00133)
92 |
93 | Smith et al. (2015) Biophysical and economic limits to negative CO2 emissions, Nature Climate Change, [DOI](https://doi.org/10.1038/nclimate2870)
94 |
95 | Sperow (2016) Estimating carbon sequestration potential on U.S. agricultural topsoils, Soil & Tillage Research, [DOI](http://doi.org/10.1016/j.still.2015.09.006)
96 |
97 | Spokas (2010) Review of the stability of biochar in soils: predictability of O:C molar ratios, Carbon Management, [DOI](https://doi.org/10.4155/CMT.10.32)
98 |
99 | Swan et al. (2015) COMET-Planner: Carbon and Greenhouse Gas Evaluation for NRCS Conservation Practice Planning, USDA Natural Resources Conservation Service
100 |
101 | Werner et al. (2018) Biogeochemical potential of biomass pyrolysis systems for limiting global warming to 1.5 ◦C, Environmental Research Letters, [DOI](https://doi.org/10.1088/1748-9326/aabb0e)
102 |
103 | Williamson et al. (2012) Ocean fertilization for geoengineering: A review of effectiveness, environmental impacts and emerging governance, Process Safety and Environmental Protection, [DOI](http://doi.org/10.1016/j.psep.2012.10.007)
104 |
105 | Winistorfer et al. (2005) Energy Consumption And Greenhouse Gas Emissions Related To The Use, Maintence, And Disposal Of A Residential Structure, Wood and Fiber Science
106 |
107 | Ximenes et al. (2017) The decay of engineered wood products and paper excavated from landfills in Australia, Waste Management, [DOI](https://doi.org/10.1016/j.wasman.2017.11.035)
108 |
109 | Yen & Lee (2011) Comparing aboveground carbon sequestration between moso bamboo (Phyllostachys heterocycla) and China fir (Cunninghamia lanceolata) forests based on the allometric model, Forest Ecology and Management, [DOI](https://doi.org/10.1016/j.foreco.2010.12.015)
110 |
111 | Zeng (2008) Carbon sequestration via wood burial, Carbon Balance and Management, [DOI](https://doi.org/10.1186/1750-0680-3-1)
112 |
--------------------------------------------------------------------------------
/methods/sources.md:
--------------------------------------------------------------------------------
1 | export const meta = {
2 | revised: '05-26-2020',
3 | }
4 |
5 | # Sources
6 |
7 | We develop this database by analyzing public proposals for carbon dioxide removal.
8 |
9 | Thus far, we have obtained proposals from calls for carbon dioxide removal procurement, e.g. from corporations looking to purchase carbon removal. In principle we could obtain proposals from other sources (e.g. directly from projects or public processes), and we may explore this in the future. Repeated proposals from the same applicant are listed and analyzed separately.
10 |
11 | While all proposals in this database claim to offer carbon removal, we do our best to surface the mechanisms underlying the proposal. Some projects exclusively perform carbon removal. Some include a component of avoided emissions. Some are exclusively avoided emissions at the moment, but could be part of a carbon removal system in the future. (Read our [explainer](https://carbonplan.org/research/carbon-removal-mechanisms) for more on the difference between removal and avoided emissions).
12 |
13 | Our analysis is based exclusively on public materials, including project proposals, public websites, registry listings, published literature, etc. In some cases we obtain early access to public proposals to get an early start on our analysis and make our reports as timely as possible. In cases where projects provided confidential information to prospective buyers alongside their public proposals, we avoid exposure to confidential information to the best of our ability, and do not incorporate it into our reports.
14 |
15 | Below is a brief overview of our current primary sources.
16 |
17 | ## Stripe Fall 2021
18 |
19 | In Fall 2021, Stripe ran a second round of [carbon removal purchases](https://stripe.com/newsroom/news/fall-21-carbon-removal-purchases). They made a call for proposals, solicited applications, evaluated them, and selected winners for their Fall 2021 purchase. All public materials are available [on GitHub](https://github.com/stripe/carbon-removal-source-materials). They received 11 submissions. We included 11 (100%) for our analyses.
20 |
21 | Read [our blog post](https://carbonplan.org/blog/stripe-2021-additions) for a summary of our evaluation of these projects.
22 |
23 | ## Stripe Spring 2021
24 |
25 | In Spring 2021, Stripe ran a new round of [carbon removal purchases](https://stripe.com/newsroom/news/spring-21-carbon-removal-purchases). CarbonPlan collaborated with Stripe to design a new, open source [carbon removal application](https://github.com/carbonplan/carbon-removal-application). They made a call for proposals, solicited applications, evaluated them, and selected winners for their Spring 2021 purchase. All public materials are available [on GitHub](https://github.com/stripe/carbon-removal-source-materials). They received 26 submissions. We included 23 (88%) for our analyses.
26 |
27 | We excluded one proposal that did not interact with the carbon cycle, and two proposals that presented broad theoretical pathways for carbon removal rather than a concrete and immediate offer.
28 |
29 | Read [our article](https://carbonplan.org/research/stripe-2021-insights) on insights from evaluating these projects.
30 |
31 | ## Microsoft 2021
32 |
33 | In early 2020, Microsoft announced a [commitment to purchase carbon removal](https://blogs.microsoft.com/blog/2020/01/16/microsoft-will-be-carbon-negative-by-2030/). They made a [call for proposals](https://blogs.microsoft.com/on-the-issues/2020/07/21/carbon-negative-transform-to-net-zero/), solicited applications, evaluated them, and [selected winners](https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE4MDlc) for their 2021 purchase. All public materials are available as a [CSV file](https://app.powerbi.com/view?r=eyJrIjoiOGM2MGFlNGYtMGNlNy00YzY5LWEyMTAtOTA0ODEyNzEzYTczIiwidCI6ImMxMzZlZWMwLWZlOTItNDVlMC1iZWFlLTQ2OTg0OTczZTIzMiIsImMiOjF9). They received 189 submissions. We included 162 (86%) for our analysis, due to incomplete or missing data.
34 |
35 | We excluded 9 proposals that specified 0 tCO₂ offered volume, which made the project invalid as a proposal for carbon removal from the perspective of our analysis. We excluded 16 proposals that did not provide enough data to tell what activities led to the claimed carbon benefits. Some of these proposals indicated more complete information in confidential attachments which we did not view. We also excluded one renewable energy proposal, and one apparent double submission.
36 |
37 | Microsoft’s RFP did not systematically surface life cycle analysis or price data in public sections of the application process. Therefore, most proposals from this source have N/As for negativity and price.
38 |
39 | Read [our article](https://carbonplan.org/research/microsoft-2021-insights) on insights from evaluating these projects.
40 |
41 | ## Stripe 2020
42 |
43 | In the spring of 2020 Stripe announced a [commitment to purchase carbon removal](https://stripe.com/blog/negative-emissions-commitment). They made a call for proposals, solicited applications using [this form](https://github.com/stripe/negative-emissions-source-materials/blob/master/project_applicaton_template.pdf), evaluated them, and selected winners for their 2020 purchase. All public materials are available [on Github](https://github.com/stripe/negative-emissions-source-materials). They received 24 submissions. We included all 24 (100%) for our analyses.
44 |
45 | Read [our article](https://carbonplan.org/research/stripe-2020-insights) on insights from evaluating these projects.
46 |
--------------------------------------------------------------------------------
/methods/terms.md:
--------------------------------------------------------------------------------
1 | # Terms
2 |
3 | The contents of this database are made available as [JSON](/research/cdr-database/projects.json) and [CSV](/research/cdr-database/projects.csv) under a [CC-BY 4.0 International license](https://creativecommons.org/licenses/by/4.0/), which means that you may share or adapt the data so long as you provide attribution (see the [full license](https://creativecommons.org/licenses/by/4.0/) for details). We are interested in how this database is used so we would additionally appreciate it if anyone planning to use it contacted us at [feedback@carbonplan.org](mailto:feedback@carbonplan.org). The code powering this website is released under an [MIT license](https://github.com/carbonplan/cdr-database/blob/main/LICENSE).
4 |
5 | Please cite this database as:
6 |
7 | F Chay, D Cullenward, J Hamman, J Freeman (2021) “CDR Database” CarbonPlan [doi:10.5281/zenodo.5715460](https://doi.org/10.5281/zenodo.5715460)
8 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const isDev =
2 | process.env.VERCEL_ENV === 'preview' || process.env.NODE_ENV === 'development'
3 |
4 | const slug = require('rehype-slug')
5 |
6 | const withMDX = require('@next/mdx')({
7 | extension: /\.mdx?$/,
8 | options: {
9 | rehypePlugins: [slug],
10 | },
11 | })
12 |
13 | module.exports = withMDX({
14 | pageExtensions: ['jsx', 'js', 'md', 'mdx'],
15 | assetPrefix: isDev ? '' : 'https://cdr-database.carbonplan.org',
16 | })
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cdr-database",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "next -p 5000",
9 | "build": "next build",
10 | "start": "next start -p 5000",
11 | "format": "npm run format-js && npm run format-md",
12 | "format-js": "prettier --write glossary.js theme.js '{methods,components,lib,pages}/**/*.js'",
13 | "format-md": "prettier --write README.md 'methods/**/*.md' --parser mdx"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/carbonplan/cdr-database.git"
18 | },
19 | "author": "",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/carbonplan/cdr-database/issues"
23 | },
24 | "homepage": "https://github.com/carbonplan/cdr-database#readme",
25 | "dependencies": {
26 | "@babel/core": "^7.15.0",
27 | "@carbonplan/charts": "^2.0.0",
28 | "@carbonplan/components": "^11.7.2",
29 | "@carbonplan/icons": "^1.0.0",
30 | "@carbonplan/theme": "^7.0.0",
31 | "@mdx-js/loader": "^1.6.22",
32 | "@next/mdx": "^11.0.1",
33 | "d3-brush": "^3.0.0",
34 | "d3-format": "^3.1.0",
35 | "d3-scale": "^4.0.2",
36 | "d3-selection": "^3.0.0",
37 | "jsonwebtoken": "^8.5.1",
38 | "next": "^12.0.7",
39 | "react": "^17.0.2",
40 | "react-animate-height": "^2.0.23",
41 | "react-dom": "^17.0.2",
42 | "react-use": "^17.2.4",
43 | "rehype-react": "^6.2.1",
44 | "rehype-slug": "^4.0.1",
45 | "remark-parse": "^9.0.0",
46 | "remark-rehype": "^8.0.0",
47 | "safe-compare": "^1.1.4",
48 | "swr": "^0.5.6",
49 | "theme-ui": "^0.12.1",
50 | "unified": "^9.2.0"
51 | },
52 | "devDependencies": {
53 | "prettier": "2.3.2"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/pages/404.js:
--------------------------------------------------------------------------------
1 | import { Custom404 } from '@carbonplan/components'
2 |
3 | export default Custom404
4 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Script from 'next/script'
3 | import { ThemeProvider } from 'theme-ui'
4 | import '@carbonplan/components/fonts.css'
5 | import '@carbonplan/components/globals.css'
6 | import theme from '../theme'
7 |
8 | const App = ({ Component, pageProps }) => {
9 | return (
10 |
11 | {process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' && (
12 |
17 | )}
18 |
19 |
20 | )
21 | }
22 |
23 | export default App
24 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Main, NextScript, Head } from 'next/document'
2 | import { InitializeColorMode } from 'theme-ui'
3 |
4 | class MyDocument extends Document {
5 | render() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | )
16 | }
17 | }
18 |
19 | export default MyDocument
20 |
--------------------------------------------------------------------------------
/pages/research/cdr-database/embed.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { useRouter } from 'next/router'
3 | import { useColorMode, Grid, Flex, Container, Box, Text } from 'theme-ui'
4 | import { Layout } from '@carbonplan/components'
5 | import Report from '../../../components/projects/report'
6 | import collection from '../../../data/projects'
7 |
8 | function Embed() {
9 | const [colorMode, setColorMode] = useColorMode()
10 | const [project, setProject] = useState(null)
11 | const [missing, setMissing] = useState(false)
12 |
13 | const router = useRouter()
14 |
15 | useEffect(() => {
16 | const { id, theme } = router.query
17 | if (theme) {
18 | if (theme === 'light') setColorMode(theme)
19 | if (theme === 'dark') setColorMode(theme)
20 | }
21 | if (id) {
22 | const project = collection.projects.filter((d) => {
23 | return d.id == id
24 | })[0]
25 | if (project) {
26 | setMissing(false)
27 | setProject(project)
28 | } else {
29 | setMissing(true)
30 | setProject(id)
31 | }
32 | }
33 | }, [router])
34 |
35 | return (
36 |
37 |
38 | {project && missing == false && (
39 |
45 | )}
46 | {missing == true && (
47 |
48 |
57 | Project '{project}' not found
58 |
59 |
66 | Try double checking your URL and try again.
67 |
68 |
69 | )}
70 |
71 |
72 | )
73 | }
74 |
75 | export default Embed
76 |
--------------------------------------------------------------------------------
/pages/research/cdr-database/index.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Grid, Flex, Container, Box, Text } from 'theme-ui'
3 | import { useRouter } from 'next/router'
4 | import { Layout, Guide } from '@carbonplan/components'
5 | import Main from '../../../components/main'
6 | import Notice from '../../../components/notice'
7 | import collection from '../../../data/projects'
8 |
9 | const selectMetric = (d, name) => {
10 | return d.metrics.filter((m) => m.name == name)[0].value
11 | }
12 |
13 | function Index() {
14 | const projects = collection.projects.sort((a, b) =>
15 | a.applicant.localeCompare(b.applicant),
16 | )
17 | const metrics = {
18 | volume: projects.map((d) => ({
19 | id: d.id,
20 | tag: d.tags[0],
21 | value: selectMetric(d, 'volume'),
22 | })),
23 | permanence: projects.map((d) => ({
24 | id: d.id,
25 | tag: d.tags[0],
26 | value: selectMetric(d, 'permanence'),
27 | })),
28 | }
29 |
30 | const [settingsExpanded, setSettingsExpanded] = useState(false)
31 |
32 | return (
33 | <>
34 |
35 |
36 |
37 | setSettingsExpanded(!settingsExpanded),
44 | }}
45 | title={'CDR Database – CarbonPlan'}
46 | description={'Public database of reports on carbon removal projects.'}
47 | card={'https://images.carbonplan.org/social/cdr-database.png'}
48 | nav={'research'}
49 | >
50 |
51 |
56 |
57 | >
58 | )
59 | }
60 |
61 | export default Index
62 |
--------------------------------------------------------------------------------
/pages/research/cdr-database/methods/index.js:
--------------------------------------------------------------------------------
1 | import { Box } from 'theme-ui'
2 | import { useState, useEffect } from 'react'
3 | import { Layout, Guide } from '@carbonplan/components'
4 | import Main from '../../../../components/methods/main'
5 | import Notice from '../../../../components/notice'
6 |
7 | function Methods(props) {
8 | const [section, setSection] = useState('sources')
9 |
10 | useEffect(() => {
11 | window.scrollTo(0, 0)
12 | }, [section])
13 |
14 | return (
15 | <>
16 |
17 |
18 |
19 |
29 |
30 |
31 |
32 | >
33 | )
34 | }
35 |
36 | export default Methods
37 |
--------------------------------------------------------------------------------
/pages/research/cdr-database/project.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { useRouter } from 'next/router'
3 | import { Grid, Flex, Divider, Container, Box, Text, Link } from 'theme-ui'
4 | import { default as NextLink } from 'next/link'
5 | import {
6 | Layout,
7 | Button,
8 | FadeIn,
9 | Row,
10 | Column,
11 | Guide,
12 | } from '@carbonplan/components'
13 | import { Left } from '@carbonplan/icons'
14 | import Notice from '../../../components/notice'
15 | import Report from '../../../components/projects/report'
16 | import collection from '../../../data/projects'
17 |
18 | const selectMetric = (d, name) => {
19 | return d.metrics.filter((m) => m.name == name)[0].value
20 | }
21 |
22 | const Project = () => {
23 | const [project, setProject] = useState(null)
24 | const [missing, setMissing] = useState(false)
25 |
26 | const router = useRouter()
27 |
28 | useEffect(() => {
29 | const { id } = router.query
30 | if (id) {
31 | const project = collection.projects.filter((d) => {
32 | return d.id == id
33 | })[0]
34 | if (project) {
35 | setMissing(false)
36 | setProject(project)
37 | } else {
38 | setMissing(true)
39 | setProject(id)
40 | }
41 | }
42 | }, [router])
43 |
44 | return (
45 | <>
46 |
47 |
48 |
49 |
58 |
59 |
60 |
61 |
62 | }
67 | >
68 | Back
69 |
70 | `solid 1px ${colors.muted}`,
73 | mt: [4],
74 | display: ['inherit', 'inherit', 'none', 'none'],
75 | }}
76 | >
77 |
78 |
79 |
89 | CDR Project
90 |
91 |
92 |
93 |
103 | This is an entry from our database of reports on carbon
104 | dioxide removal project proposals. It represents our
105 | evaluation of a project based on publicly available materials.
106 |
107 |
108 |
109 |
110 | To learn more about our reports, return to the main{' '}
111 |
112 | database
113 | {' '}
114 | or read our{' '}
115 |
116 | methods
117 |
118 | .
119 |
120 |
126 |
127 |
128 |
129 |
134 |
151 |
152 |
157 |
167 | {project && missing == false && (
168 |
169 |
175 |
176 | )}
177 | {missing == true && (
178 |
179 |
180 |
190 | Project '{project}' not found
191 |
192 |
199 | Try double checking your URL and try again.
200 |
201 |
202 |
203 | )}
204 |
205 |
206 |
207 |
208 | >
209 | )
210 | }
211 |
212 | export default Project
213 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | click
2 | gspread
3 | oauth2client
4 | pandas
5 | carbonplan_data
6 | intake
7 |
--------------------------------------------------------------------------------
/scripts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carbonplan/cdr-database/08fa4d9a010dab5d3cff5d03397a3e2b1b1375a6/scripts/__init__.py
--------------------------------------------------------------------------------
/scripts/build_projects.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import copy
3 | import json
4 |
5 | import click
6 | import msft2021
7 | import pandas as pd
8 | import strp2020
9 | import strp2021q1
10 | import strp2021q4
11 | from carbonplan_data.metadata import get_cf_global_attrs
12 |
13 | VERSION = '1.2.1'
14 |
15 |
16 | def write_projects(project_collection, output):
17 | collection = copy.deepcopy(project_collection)
18 | for project in collection['projects']:
19 | del project['methods']
20 |
21 | with open('data/' + output, "w") as f:
22 | f.write('module.exports=' + json.dumps(collection))
23 |
24 |
25 | def write_methods(project_collection, output):
26 | methods = []
27 | for project in project_collection['projects']:
28 | tmp = {}
29 | tmp['type'] = 'Methods'
30 | tmp['id'] = project['id']
31 | tmp['applicant'] = project['applicant']
32 | tmp['content'] = project['methods']
33 | methods.append(tmp)
34 |
35 | collection = {}
36 | collection['type'] = 'MethodsCollection'
37 | collection['methods'] = methods
38 |
39 | with open('data/' + output, "w") as f:
40 | f.write('module.exports=' + json.dumps(collection))
41 |
42 |
43 | def write_numbers(project_collection, output):
44 | collection = copy.deepcopy(project_collection)
45 | for project in collection['projects']:
46 | project['source'] = project['source']['id']
47 | del project['methods']
48 | del project['description']
49 | del project['documentation']
50 | del project['revisions']
51 | del project['notes']
52 | del project['type']
53 | del project['keywords']
54 | for metric in project['metrics']:
55 | del metric['comment']
56 | del metric['notes']
57 | del metric['units']
58 | del metric['type']
59 |
60 | with open('data/' + output, "w") as f:
61 | f.write('module.exports=' + json.dumps(collection))
62 |
63 |
64 | def write_csv(collection, output):
65 | projects = collection['projects']
66 | df = pd.DataFrame()
67 |
68 | df['id'] = [d['id'] for d in projects]
69 | df['applicant'] = [d['applicant'] for d in projects]
70 | df['location'] = [d['location']['name'] for d in projects]
71 | df['description'] = [d['description'] for d in projects]
72 | df['tags'] = [','.join(d['tags']) for d in projects]
73 |
74 | df['source'] = [d['source']['name'] for d in projects]
75 | df['source_id'] = [d['source']['id'] for d in projects]
76 | df['source_date'] = [d['source']['date'] for d in projects]
77 | df['source_url'] = [d['source']['url'] for d in projects]
78 | df['source_license'] = [d['source']['license'] for d in projects]
79 |
80 | df['documentation'] = [d['documentation']['url'] for d in projects]
81 |
82 | df['mechanism'] = [d['metrics'][0]['value'] for d in projects]
83 | df['mechanism_rating'] = [d['metrics'][0]['rating'] for d in projects]
84 | df['mechanism_notes'] = [d['metrics'][0]['notes'] for d in projects]
85 | df['mechanism_comment'] = [d['metrics'][0]['comment'] for d in projects]
86 |
87 | df['volume'] = [d['metrics'][1]['value'] for d in projects]
88 | df['volume_rating'] = [d['metrics'][1]['rating'] for d in projects]
89 | df['volume_notes'] = [d['metrics'][1]['notes'] for d in projects]
90 | df['volume_comment'] = [d['metrics'][1]['comment'] for d in projects]
91 |
92 | df['negativity'] = [d['metrics'][2]['value'] for d in projects]
93 | df['negativity_rating'] = [d['metrics'][2]['rating'] for d in projects]
94 | df['negativity_notes'] = [d['metrics'][2]['notes'] for d in projects]
95 | df['negativity_comment'] = [d['metrics'][2]['comment'] for d in projects]
96 |
97 | df['permanence'] = [d['metrics'][3]['value'] for d in projects]
98 | df['permanence_rating'] = [d['metrics'][3]['rating'] for d in projects]
99 | df['permanence_notes'] = [d['metrics'][3]['notes'] for d in projects]
100 | df['permanence_comment'] = [d['metrics'][3]['comment'] for d in projects]
101 |
102 | df['additionality'] = [d['metrics'][4]['value'] for d in projects]
103 | df['additionality_notes'] = [d['metrics'][4]['notes'] for d in projects]
104 | df['additionality_comment'] = [d['metrics'][4]['comment'] for d in projects]
105 |
106 | df['price'] = [d['metrics'][5]['value'] for d in projects]
107 | df['price_notes'] = [d['metrics'][5]['notes'] for d in projects]
108 | df['price_comment'] = [d['metrics'][5]['comment'] for d in projects]
109 |
110 | df['specificity'] = [d['metrics'][6]['value'] for d in projects]
111 | df['specificity_notes'] = [d['metrics'][6]['notes'] for d in projects]
112 | df['specificity_comment'] = [d['metrics'][6]['comment'] for d in projects]
113 |
114 | df['rating'] = [d['rating'] for d in projects]
115 |
116 | df['revisions'] = [d['revisions'] for d in projects]
117 |
118 | df['notes'] = [d['notes'] for d in projects]
119 |
120 | fname = 'public/research/cdr-database/' + output
121 | with open(fname, 'w') as f:
122 | f.write('# carbonplan / cdr-database\n')
123 | for key, val in collection['metadata'].items():
124 | f.write(f'# {key}: {val}\n')
125 | df.to_csv(f, index=False)
126 |
127 |
128 | def write_json(collection, output):
129 | with open('public/research/cdr-database/' + output, "w") as f:
130 | f.write(json.dumps(collection))
131 |
132 |
133 | @click.command()
134 | @click.argument('sources', nargs=-1)
135 | @click.option('--output-projects', default='projects.js', show_default=True)
136 | @click.option('--output-methods', default='methods.js', show_default=True)
137 | @click.option('--output-numbers', default='numbers.js', show_default=True)
138 | @click.option('--output-csv', default='projects.csv', show_default=True)
139 | @click.option('--output-json', default='projects.json', show_default=True)
140 | def main(sources, output_projects, output_methods, output_numbers, output_csv, output_json):
141 | projects = []
142 | metadata = get_cf_global_attrs(license='CC-BY-4.0', version=VERSION)
143 |
144 | if 'strp2020' in sources:
145 | projects.extend(strp2020.make_projects())
146 | if 'strp2021q1' in sources:
147 | projects.extend(strp2021q1.make_projects())
148 | if 'strp2021q4' in sources:
149 | projects.extend(strp2021q4.make_projects())
150 | if 'msft2021' in sources:
151 | projects.extend(msft2021.make_projects())
152 |
153 | project_collection = {
154 | "type": "ProjectCollection",
155 | "metadata": metadata,
156 | "projects": projects,
157 | }
158 |
159 | write_projects(project_collection, output_projects)
160 | write_methods(project_collection, output_methods)
161 | # write_numbers(project_collection, output_numbers)
162 | write_csv(project_collection, output_csv)
163 | write_json(project_collection, output_json)
164 |
165 |
166 | if __name__ == "__main__":
167 | main()
168 |
--------------------------------------------------------------------------------
/scripts/msft2021.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from utils import get_sheet, make_metric, make_project, maybe_float
4 |
5 |
6 | def make_projects():
7 | data = get_sheet("Sheet1", "Microsoft reports 0.1 [internal]").loc[:189]
8 |
9 | metrics = [
10 | "mechanism",
11 | "volume",
12 | "negativity",
13 | "permanence",
14 | "additionality",
15 | "price",
16 | "specificity",
17 | ]
18 |
19 | metric_keys = [
20 | "name",
21 | "value",
22 | "units",
23 | "notes",
24 | "comment",
25 | "rating",
26 | ]
27 |
28 | tag_keys = data.columns.levels[0][data.columns.levels[0].str.startswith("tag")]
29 | tag_keys = [(t, "") for t in tag_keys]
30 |
31 | data = data.rename(columns={'net removal volume offered to MSFT': 'volume'})
32 |
33 | projects = []
34 | for i, row in data.iterrows():
35 | project = make_project(row[("id", "")])
36 | tags = row[tag_keys].to_list()
37 | if "" in tags:
38 | tags.remove("")
39 | tags = [t.lower().strip() for t in tags]
40 | project["tags"].extend(tags)
41 | project["id"] = row[("id", "")]
42 | project["applicant"] = row[("applicant", "")]
43 | project["description"] = row[("description", "")]
44 | project["rating"] = row[("rating", "")]
45 | project["keywords"] = row[("keywords", "")]
46 | project["methods"] = row[("methods", "")]
47 | project["location"] = {
48 | "name": row[("location", "name")],
49 | }
50 | project["source"] = {
51 | "name": row[("source", "name")],
52 | "id": row[("source", "id")],
53 | "date": row[("source", "date")],
54 | "license": row[("source", "license")],
55 | "url": row[("source", "url")],
56 | }
57 | project["revisions"] = json.loads(row[("revisions", "")])
58 | project["documentation"] = {
59 | "name": row[("documentation", "name")],
60 | "url": row[("documentation", "url")],
61 | }
62 | project["notes"] = row[("notes", "")]
63 | for name in metrics:
64 | m = make_metric(name)
65 | for key in metric_keys:
66 | try:
67 | val = row[(name, key)]
68 | except KeyError:
69 | continue
70 | else:
71 | if val:
72 | m[key] = maybe_float(val)
73 | elif key in ['value', 'rating']:
74 | m[key] = -9999
75 |
76 | project["metrics"].append(m)
77 | if row[('flag', '')] != 'x':
78 | projects.append(project)
79 |
80 | return projects
81 |
--------------------------------------------------------------------------------
/scripts/msft2021.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carbonplan/cdr-database/08fa4d9a010dab5d3cff5d03397a3e2b1b1375a6/scripts/msft2021.pyc
--------------------------------------------------------------------------------
/scripts/strp2020.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from utils import get_sheet, make_metric, make_project, maybe_float
4 |
5 |
6 | def make_projects():
7 | data = get_sheet("Sheet1", "Stripe reports 0.1 [internal]").loc[:23]
8 |
9 | metrics = [
10 | "mechanism",
11 | "volume",
12 | "negativity",
13 | "permanence",
14 | "additionality",
15 | "price",
16 | "specificity",
17 | ]
18 |
19 | metric_keys = [
20 | "name",
21 | "value",
22 | "units",
23 | "notes",
24 | "comment",
25 | "rating",
26 | ]
27 |
28 | tag_keys = data.columns.levels[0][data.columns.levels[0].str.startswith("tag")]
29 | tag_keys = [(t, "") for t in tag_keys]
30 |
31 | projects = []
32 | for i, row in data.iterrows():
33 | project = make_project(row[("id", "")])
34 | tags = row[tag_keys].to_list()
35 | if "" in tags:
36 | tags.remove("")
37 | tags = [t.lower().strip() for t in tags]
38 | project["tags"].extend(tags)
39 | project["id"] = row[("id", "")]
40 | project["applicant"] = row[("applicant", "")]
41 | project["description"] = row[("description", "")]
42 | project["rating"] = row[("rating", "")]
43 | project["keywords"] = row[("keywords", "")]
44 | project["methods"] = row[("methods", "")]
45 | project["location"] = {
46 | "name": row[("location", "name")],
47 | }
48 | project["source"] = {
49 | "name": row[("source", "name")],
50 | "id": row[("source", "id")],
51 | "date": row[("source", "date")],
52 | "license": row[("source", "license")],
53 | "url": row[("source", "url")],
54 | }
55 | project["documentation"] = {
56 | "name": row[("documentation", "name")],
57 | "url": row[("documentation", "url")],
58 | }
59 | project["revisions"] = json.loads(row[("revisions", "")])
60 | project["notes"] = row[("notes", "")]
61 | for name in metrics:
62 | m = make_metric(name)
63 | for key in metric_keys:
64 | try:
65 | val = row[(name, key)]
66 | except KeyError:
67 | continue
68 | else:
69 | m[key] = maybe_float(val)
70 |
71 | project["metrics"].append(m)
72 | projects.append(project)
73 |
74 | return projects
75 |
--------------------------------------------------------------------------------
/scripts/strp2021q1.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from utils import get_sheet, make_metric, make_project, maybe_float
4 |
5 |
6 | def make_projects():
7 | data = get_sheet("Sheet1", "Stripe reports Q1 2021 2.0 [internal]").loc[:189]
8 |
9 | metrics = [
10 | "mechanism",
11 | "volume",
12 | "negativity",
13 | "permanence",
14 | "additionality",
15 | "price",
16 | "specificity",
17 | ]
18 |
19 | metric_keys = [
20 | "name",
21 | "value",
22 | "units",
23 | "notes",
24 | "comment",
25 | "rating",
26 | ]
27 |
28 | tag_keys = data.columns.levels[0][data.columns.levels[0].str.startswith("tag")]
29 | tag_keys = [(t, "") for t in tag_keys]
30 |
31 | projects = []
32 | for i, row in data.iterrows():
33 | project = make_project(row[("id", "")])
34 | tags = row[tag_keys].to_list()
35 | if "" in tags:
36 | tags.remove("")
37 | tags = [t.lower().strip() for t in tags]
38 | project["tags"].extend(tags)
39 | project["id"] = row[("id", "")]
40 | project["applicant"] = row[("applicant", "")]
41 | project["description"] = row[("description", "")]
42 | project["rating"] = row[("rating", "")]
43 | project["keywords"] = row[("keywords", "")]
44 | project["methods"] = row[("methods", "")]
45 | project["location"] = {
46 | "name": row[("location", "name")],
47 | }
48 | project["source"] = {
49 | "name": row[("source", "name")],
50 | "id": row[("source", "id")],
51 | "date": row[("source", "date")],
52 | "license": row[("source", "license")],
53 | "url": row[("source", "url")],
54 | }
55 | project["revisions"] = json.loads(row[("revisions", "")])
56 | project["documentation"] = {
57 | "name": row[("documentation", "name")],
58 | "url": row[("documentation", "url")],
59 | }
60 | project["notes"] = row[("notes", "")]
61 | for name in metrics:
62 | m = make_metric(name)
63 | for key in metric_keys:
64 | try:
65 | val = row[(name, key)]
66 | except KeyError:
67 | continue
68 | else:
69 | if val:
70 | m[key] = maybe_float(val)
71 | elif key in ['value', 'rating']:
72 | m[key] = -9999
73 |
74 | project["metrics"].append(m)
75 | if row[('flag', '')] != 'x':
76 | projects.append(project)
77 |
78 | return projects
79 |
--------------------------------------------------------------------------------
/scripts/strp2021q4.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from utils import get_sheet, make_metric, make_project, maybe_float
4 |
5 |
6 | def make_projects():
7 | data = get_sheet("Sheet1", "Stripe reports fall 2021 [internal]").loc[:189]
8 |
9 | metrics = [
10 | "mechanism",
11 | "volume",
12 | "negativity",
13 | "permanence",
14 | "additionality",
15 | "price",
16 | "specificity",
17 | ]
18 |
19 | metric_keys = [
20 | "name",
21 | "value",
22 | "units",
23 | "notes",
24 | "comment",
25 | "rating",
26 | ]
27 |
28 | tag_keys = data.columns.levels[0][data.columns.levels[0].str.startswith("tag")]
29 | tag_keys = [(t, "") for t in tag_keys]
30 |
31 | projects = []
32 | for i, row in data.iterrows():
33 | project = make_project(row[("id", "")])
34 | tags = row[tag_keys].to_list()
35 | if "" in tags:
36 | tags.remove("")
37 | tags = [t.lower().strip() for t in tags]
38 | project["tags"].extend(tags)
39 | project["id"] = row[("id", "")]
40 | project["applicant"] = row[("applicant", "")]
41 | project["description"] = row[("description", "")]
42 | project["rating"] = row[("rating", "")]
43 | project["keywords"] = row[("keywords", "")]
44 | project["methods"] = row[("methods", "")]
45 | project["location"] = {
46 | "name": row[("location", "name")],
47 | }
48 | project["source"] = {
49 | "name": row[("source", "name")],
50 | "id": row[("source", "id")],
51 | "date": row[("source", "date")],
52 | "license": row[("source", "license")],
53 | "url": row[("source", "url")],
54 | }
55 | project["revisions"] = json.loads(row[("revisions", "")])
56 | project["documentation"] = {
57 | "name": row[("documentation", "name")],
58 | "url": row[("documentation", "url")],
59 | }
60 | project["notes"] = row[("notes", "")]
61 | for name in metrics:
62 | m = make_metric(name)
63 | for key in metric_keys:
64 | try:
65 | val = row[(name, key)]
66 | except KeyError:
67 | continue
68 | else:
69 | if val:
70 | m[key] = maybe_float(val)
71 | elif key in ['value', 'rating']:
72 | m[key] = -9999
73 |
74 | project["metrics"].append(m)
75 | if row[('flag', '')] != 'x':
76 | projects.append(project)
77 |
78 | return projects
79 |
--------------------------------------------------------------------------------
/scripts/utils.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 |
3 | import gspread
4 | from oauth2client.service_account import ServiceAccountCredentials
5 | from pandas import DataFrame, MultiIndex
6 |
7 | SECRET_FILE = pathlib.Path(__file__).parents[1] / 'secrets/google-sheets-key.json'
8 |
9 |
10 | def ffill(data):
11 | """
12 | helper function to forward fill column labels
13 | """
14 | last = data[0]
15 | new = []
16 | for line in data:
17 | if line:
18 | new.append(line)
19 | last = line
20 | else:
21 | new.append(last)
22 | return new
23 |
24 |
25 | def get_sheet(sheet, doc):
26 | """
27 | helper function to open a specific google sheet
28 | """
29 | scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
30 |
31 | credentials = ServiceAccountCredentials.from_json_keyfile_name(SECRET_FILE, scope)
32 |
33 | gc = gspread.authorize(credentials)
34 | wks = gc.open(doc)
35 | sheet = wks.worksheet(sheet)
36 | data = sheet.get_all_values()
37 | h1 = ffill(data[0])
38 |
39 | # remove extra whitespace
40 | h1 = [k.strip() for k in h1]
41 | h2 = [k.strip() for k in data[1]]
42 |
43 | # create a multiindex
44 | columns = MultiIndex.from_tuples(zip(h1, h2))
45 |
46 | # populate the dataframe
47 | df = DataFrame(data[2:], columns=columns)
48 | return df
49 |
50 |
51 | def make_project(id):
52 | """
53 | return a template project
54 | """
55 | return {
56 | "type": "Project",
57 | "metrics": [],
58 | "tags": [],
59 | "id": id,
60 | "description": "",
61 | "applicant": "",
62 | }
63 |
64 |
65 | def make_metric(name):
66 | """
67 | return a template metric
68 | """
69 | return {
70 | "type": "Metric",
71 | "name": name,
72 | "value": "",
73 | "units": "",
74 | "rating": "",
75 | "notes": "",
76 | "comment": "",
77 | }
78 |
79 |
80 | def maybe_float(value):
81 | new = value.replace("$", "").replace(",", "")
82 | try:
83 | return float(new)
84 | except Exception:
85 | return value.strip()
86 |
--------------------------------------------------------------------------------
/scripts/utils.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carbonplan/cdr-database/08fa4d9a010dab5d3cff5d03397a3e2b1b1375a6/scripts/utils.pyc
--------------------------------------------------------------------------------
/secrets/google-sheets-key.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carbonplan/cdr-database/08fa4d9a010dab5d3cff5d03397a3e2b1b1375a6/secrets/google-sheets-key.json
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 100
3 |
--------------------------------------------------------------------------------
/theme.js:
--------------------------------------------------------------------------------
1 | import base from '@carbonplan/theme'
2 |
3 | export default {
4 | ...base,
5 | tags: {
6 | dac: 'purple',
7 | forests: 'green',
8 | mineralization: 'grey',
9 | biomass: 'yellow',
10 | burial: 'yellow',
11 | ocean: 'teal',
12 | alkalinity: 'teal',
13 | soil: 'orange',
14 | reforestation: 'green',
15 | 'redd+': 'green',
16 | biochar: 'yellow',
17 | storage: 'yellow',
18 | injection: 'grey',
19 | phytoplankton: 'green',
20 | ifm: 'green',
21 | agroforestry: 'green',
22 | concrete: 'grey',
23 | aggregate: 'grey',
24 | olivine: 'grey',
25 | basalt: 'grey',
26 | asphalt: 'grey',
27 | wood: 'yellow',
28 | farming: 'orange',
29 | grazing: 'orange',
30 | conversion: 'orange',
31 | macroalgae: 'green',
32 | wetlands: 'green',
33 | insulation: 'yellow',
34 | restoration: 'orange',
35 | },
36 | }
37 |
--------------------------------------------------------------------------------