diff --git a/404.html b/404.html index 97af38882..bf2ae847c 100644 --- a/404.html +++ b/404.html @@ -5,13 +5,13 @@ Page Not Found | Geo Garden Club - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/e96f37e1.6e364f0d.js b/assets/js/e96f37e1.6e364f0d.js deleted file mode 100644 index f919af9c9..000000000 --- a/assets/js/e96f37e1.6e364f0d.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkgeogardenclub_github_io=self.webpackChunkgeogardenclub_github_io||[]).push([[909],{3905:(e,t,a)=>{a.d(t,{Zo:()=>p,kt:()=>c});var r=a(7294);function n(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,r)}return a}function l(e){for(var t=1;t=0||(n[a]=e[a]);return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(n[a]=e[a])}return n}var s=r.createContext({}),d=function(e){var t=r.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):l(l({},t),e)),a},p=function(e){var t=d(e.components);return r.createElement(s.Provider,{value:t},e.children)},h="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},g=r.forwardRef((function(e,t){var a=e.components,n=e.mdxType,i=e.originalType,s=e.parentName,p=o(e,["components","mdxType","originalType","parentName"]),h=d(a),g=n,c=h["".concat(s,".").concat(g)]||h[g]||m[g]||i;return a?r.createElement(c,l(l({ref:t},p),{},{components:a})):r.createElement(c,l({ref:t},p))}));function c(e,t){var a=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var i=a.length,l=new Array(i);l[0]=g;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[h]="string"==typeof e?e:n,l[1]=o;for(var d=2;d{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>d});var r=a(7462),n=(a(7294),a(3905));const i={hide_table_of_contents:!1},l="Badges",o={unversionedId:"develop/alpha-release/badges",id:"develop/alpha-release/badges",title:"Badges",description:"Goals",source:"@site/docs/develop/alpha-release/badges.md",sourceDirName:"develop/alpha-release",slug:"/develop/alpha-release/badges",permalink:"/docs/develop/alpha-release/badges",draft:!1,tags:[],version:"current",frontMatter:{hide_table_of_contents:!1},sidebar:"developSidebar",previous:{title:"Coding Standards",permalink:"/docs/develop/alpha-release/coding-standards"},next:{title:"GGC Input Fields",permalink:"/docs/develop/alpha-release/input-fields"}},s={},d=[{value:"Goals",id:"goals",level:2},{value:"Design principles",id:"design-principles",level:2},{value:"Types",id:"types",level:3},{value:"Levels",id:"levels",level:3},{value:"Verification (i.e. badge processing)",id:"verification-ie-badge-processing",level:3},{value:"Observation tags",id:"observation-tags",level:3},{value:"Implementation hints",id:"implementation-hints",level:3},{value:"Garden badges",id:"garden-badges",level:2},{value:"Pesticide free",id:"pesticide-free",level:3},{value:"General Criteria",id:"general-criteria",level:4},{value:"Observation tags",id:"observation-tags-1",level:4},{value:"Implementation notes",id:"implementation-notes",level:4},{value:"Pollinator Friendly",id:"pollinator-friendly",level:3},{value:"General Criteria",id:"general-criteria-1",level:4},{value:"Observation tags",id:"observation-tags-2",level:4},{value:"Implementation notes",id:"implementation-notes-1",level:4},{value:"Sustainable Soil",id:"sustainable-soil",level:3},{value:"General Criteria",id:"general-criteria-2",level:4},{value:"Observation tags",id:"observation-tags-3",level:4},{value:"Implementation notes",id:"implementation-notes-2",level:4},{value:"Water Smart",id:"water-smart",level:3},{value:"General Criteria",id:"general-criteria-3",level:4},{value:"Observation tags",id:"observation-tags-4",level:4},{value:"Implementation notes",id:"implementation-notes-3",level:4},{value:"Gardener badges",id:"gardener-badges",level:2},{value:"Community Cultivator",id:"community-cultivator",level:3},{value:"General Criteria",id:"general-criteria-4",level:4},{value:"Observation tags:",id:"observation-tags-5",level:4},{value:"Implementation notes",id:"implementation-notes-4",level:4},{value:"Compost Champion",id:"compost-champion",level:3},{value:"General Criteria",id:"general-criteria-5",level:4},{value:"Observation tags",id:"observation-tags-6",level:4},{value:"Implementation notes",id:"implementation-notes-5",level:4},{value:"Crop Whisperer",id:"crop-whisperer",level:3},{value:"General Criteria",id:"general-criteria-6",level:4},{value:"Observation tags",id:"observation-tags-7",level:4},{value:"Implementation notes",id:"implementation-notes-6",level:4},{value:"Greenhouse grower",id:"greenhouse-grower",level:3},{value:"General Criteria",id:"general-criteria-7",level:4},{value:"Observation tags",id:"observation-tags-8",level:4},{value:"Implementation notes",id:"implementation-notes-7",level:4},{value:"Permaculture Pro",id:"permaculture-pro",level:3},{value:"General Criteria",id:"general-criteria-8",level:4},{value:"Observation tags",id:"observation-tags-9",level:4},{value:"Implementation notes",id:"implementation-notes-8",level:4},{value:"Vermiculturalist",id:"vermiculturalist",level:3},{value:"General Criteria",id:"general-criteria-9",level:4},{value:"Observation tags:",id:"observation-tags-10",level:4},{value:"Implementation notes",id:"implementation-notes-9",level:4},{value:"Seed Saver",id:"seed-saver",level:3},{value:"General Criteria",id:"general-criteria-10",level:4},{value:"Observation tags:",id:"observation-tags-11",level:4},{value:"Implementation notes",id:"implementation-notes-10",level:4},{value:"Post-Alpha badges",id:"post-alpha-badges",level:2},{value:"Chapter Chair",id:"chapter-chair",level:3},{value:"General Criteria",id:"general-criteria-11",level:4},{value:"Connected Community",id:"connected-community",level:3},{value:"General Criteria",id:"general-criteria-12",level:4},{value:"Climate Victors",id:"climate-victors",level:3},{value:"General Criteria",id:"general-criteria-13",level:4},{value:"Pesticide Resistors",id:"pesticide-resistors",level:3},{value:"General Criteria",id:"general-criteria-14",level:4},{value:"Seed Sharers",id:"seed-sharers",level:3},{value:"General Criteria:",id:"general-criteria-15",level:4},{value:"Climate Victory",id:"climate-victory",level:3},{value:"General Criteria",id:"general-criteria-16",level:4},{value:"Observation tags",id:"observation-tags-12",level:4},{value:"Master gardener",id:"master-gardener",level:3},{value:"General Criteria",id:"general-criteria-17",level:4},{value:"Observation tags",id:"observation-tags-13",level:4},{value:"Bee Buddy",id:"bee-buddy",level:3},{value:"General Criteria",id:"general-criteria-18",level:4},{value:"Observation tags",id:"observation-tags-14",level:4},{value:"Aquaponics Ace",id:"aquaponics-ace",level:3},{value:"General Criteria",id:"general-criteria-19",level:4},{value:"Observation tags",id:"observation-tags-15",level:4},{value:"Herbalist Hero",id:"herbalist-hero",level:3},{value:"General Criteria",id:"general-criteria-20",level:4},{value:"Observation tags:",id:"observation-tags-16",level:4},{value:"Educator Extraordinaire",id:"educator-extraordinaire",level:3},{value:"General Criteria",id:"general-criteria-21",level:4},{value:"Observation tags:",id:"observation-tags-17",level:4},{value:"Orchard Orchestrator",id:"orchard-orchestrator",level:3},{value:"General Criteria",id:"general-criteria-22",level:4}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,n.kt)(h,(0,r.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"badges"},"Badges"),(0,n.kt)("h2",{id:"goals"},"Goals"),(0,n.kt)("p",null,"The alpha release will implement a badge system for gardens and gardeners. This badge system is designed to accomplish the following goals:"),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("em",{parentName:"li"},"Foster user engagement and enjoyment through a game mechanic that publicizes achievements by gardens and gardeners.")," Gardeners should find it fun to accumulate badges that are associated with their profile and their garden(s)."),(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("em",{parentName:"li"},"Foster a community of practice by helping gardeners connect with others with similar interests and/or greater expertise with respect to a specific gardening topic.")," For example, if a user is interested in vermiculture, the badge system provides a mechanism for them to find other gardeners who already have experience in this area. "),(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("em",{parentName:"li"},"Provide a useful, compact representation of garden and gardener characteristics."),' The app provides "summary" cards for gardens and gardeners. Users should find the presence (and/or absence) of badges helpful in forming a high level understanding of these entities.'),(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("em",{parentName:"li"},"Provide a mechanism that identifies ways to improve gardening practices."),' The badge system makes visible the practices that are important to the GGC mission of food resiliency and sustainable gardening, such as seed saving, composting, and water conservation. This means that a simple heuristic for "getting better at gardening" is to simply "get more badges".')),(0,n.kt)("admonition",{title:"What about Chapters?",type:"info"},(0,n.kt)("p",{parentName:"admonition"},"The alpha release will not implement Chapter-level badges for two reasons:"),(0,n.kt)("ol",{parentName:"admonition"},(0,n.kt)("li",{parentName:"ol"},'The primary goals for Chapter-level badges are: (a) encouraging members of a Chapter via "peer pressure" to conform to certain best practices, and (b) making possible Chapter "leaderboards" so that Chapters can assess their capabilities relative to other chapters. Neither of these are important for the alpha release where we will focus on a single Chapter with a relatively small number of members. '),(0,n.kt)("li",{parentName:"ol"},"We will implement garden and gardener-based badge processing within each client, which is simple, scalable, and (hopefully) efficient. Implementing a Chapter-level badge system at scale will require Firebase cloud functions. These functions require specialized knowledge to implement correctly."))),(0,n.kt)("h2",{id:"design-principles"},"Design principles"),(0,n.kt)("h3",{id:"types"},"Types"),(0,n.kt)("p",null,"The alpha release will implement two types of badges: garden badges and gardener badges. Garden badges reflect the characteristics of a garden across one or more years. Gardener badges reflect characteristics of a gardener across all of the gardens with which they are associated. "),(0,n.kt)("h3",{id:"levels"},"Levels"),(0,n.kt)("p",null,"Each badge can be achieved at three levels of increasing sophistication and/or expertise. Level 1 badges are relatively easy to achieve. Level 2 and Level 3 badges indicate increasing levels of expertise or accomplishment with respect to the badge subject. "),(0,n.kt)("p",null,"Levels will be visually represented by 1-3 stars along the left side of the badge. Here`s an example:"),(0,n.kt)("img",{style:{borderStyle:"solid"},width:"300px",src:"/img/develop/alpha-release/badges/badge-examples.png"}),(0,n.kt)("h3",{id:"verification-ie-badge-processing"},"Verification (i.e. badge processing)"),(0,n.kt)("p",null,'Verification of badges can be done in the following ways: "via attestation", "via observation", or "via planting". Depending upon the badge and/or level, one or more of these verification approaches might be required.'),(0,n.kt)("p",null,'"Via attestation" means that the Gardener has simply attested that they (or their garden) adheres to certain practices. This is implemented as an "Attestation" section in the Garden and Gardener forms. For example, when creating or updating a Garden, the gardener can simply check a box to attest that the garden is pesticide-free. Gardeners are on the honor system to attest only to practices that they believe to be true.'),(0,n.kt)("p",null,'"Via Observation" requires the Gardener to post one or more Observations with one or more badge-specific tags in a single Garden. '),(0,n.kt)("p",null,'"Via Planting" requires the Gardener to have created Planting data in a single Garden that helps to satisfy the criteria for a badge.'),(0,n.kt)("admonition",{title:"Alpha release badge processing is client-side only",type:"warning"},(0,n.kt)("p",{parentName:"admonition"},"As the above indicates, for the alpha release, badge processing occurs on the client-side, and is triggered by updates to garden, gardener, observation, or planting documents."),(0,n.kt)("p",{parentName:"admonition"},'The current criteria are designed so that they can be assessed via either WithCoreData or WithGardenData. See the Implementation Notes section associated with each badge for an indication of which "With" widget can be used.'),(0,n.kt)("p",{parentName:"admonition"},"There are many ways we could define the criteria for a badge. The criteria we choose must align with the alpha release design constraints. If a criteria turns out to be too expensive to verify via client-side processing, then we should change the criteria, not change the design.")),(0,n.kt)("h3",{id:"observation-tags"},"Observation tags"),(0,n.kt)("p",null,"Many badges require the posting of (public) Observations to provide evidence for a specific practice. To implement badge processing, the system needs to be able to identify Observations that are intended to support achievement of a particular badge. This will be done by the user attaching one or more pre-defined tags to an Observation. Some practices (i.e. cover crops) can help the user achieve multiple badges, so the tag labels are not designed to indicate any particular badge. "),(0,n.kt)("p",null,'The system will "take the user\'s word" for the appropriateness of the tags to the Observation. We hope that the public nature of these observations will prevent users from misusing this process.'),(0,n.kt)("h3",{id:"implementation-hints"},"Implementation hints"),(0,n.kt)("p",null,"Badge processing has the following general implementation characteristics:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},'Triggered as part of "mutation" of Gardener, Garden, Planting, and Observation entities.'),(0,n.kt)("li",{parentName:"ul"},"During submit() processing, the BadgeProcessor is called with the Garden, Chapter, and User collections, plus the entities about to be mutated. It then calls a function for each Badge, passing it this data. Each badge-specific function has its own function returns the set of BadgeInstances to be created and deleted. ")),(0,n.kt)("p",null,"Badge implementation also involves the creation of the Badges page. This page should provide a description of each Badge, the criteria required to obtain each level, and chips to indicate the Gardens (or Gardeners) that currently hold the badge."),(0,n.kt)("p",null,'Badge implementation will also require updates to the Garden and Gardener entities and mutation processing in order to support the various "attestation" checkboxes. Each attestation can be implemented as an optional boolean field in the Garden or Gardener entity.'),(0,n.kt)("h2",{id:"garden-badges"},"Garden badges"),(0,n.kt)("p",null,"Here are proposals for the alpha release garden badges."),(0,n.kt)("h3",{id:"pesticide-free"},"Pesticide free"),(0,n.kt)("h4",{id:"general-criteria"},"General Criteria"),(0,n.kt)("p",null,"No pesticides are used in this garden."),(0,n.kt)("h4",{id:"observation-tags-1"},"Observation tags"),(0,n.kt)("p",null,"N/A"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is pesticide free. ",(0,n.kt)("br",null)," b. There is Planting data for this garden for a single calendar year.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is pesticide free. ",(0,n.kt)("br",null)," b. There is Planting data for this garden for exactly two calendar years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is pesticide free. ",(0,n.kt)("br",null)," b. There is Planting data for this garden for three or more calendar years.")))),(0,n.kt)("h4",{id:"implementation-notes"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Garden or Planting mutation. "),(0,n.kt)("p",null,"Requires WithGardenData. "),(0,n.kt)("h3",{id:"pollinator-friendly"},"Pollinator Friendly"),(0,n.kt)("h4",{id:"general-criteria-1"},"General Criteria"),(0,n.kt)("p",null,'The garden has pollinator-friendly practices such as: (1) Using a wide variety of plants that bloom from early spring into late fall, (2) Avoiding modern hybrid flowers, especially those with "doubled" flowers, (3) Eliminating pesticides whenever possible, (4) Including larval host plants in your landscape, (5) Creating a damp salt lick for butterflies and bees, (6) Leaving dead trees, or at least an occasional dead limb, in order to provide essential nesting sites for native bees, and (7) Adding to nectar resources by providing a hummingbird feeder.'),(0,n.kt)("h4",{id:"observation-tags-2"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#DitchChemicals"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Habitat"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Hummingbirds"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#LarvalHostPlants"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#NativeBees"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#NativePlants"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#PesticideFree"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SaltLick"),"."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices within a single calendar year.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices for two calendar years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices for three or more calendar years.")))),(0,n.kt)("h4",{id:"implementation-notes-1"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"sustainable-soil"},"Sustainable Soil"),(0,n.kt)("h4",{id:"general-criteria-2"},"General Criteria"),(0,n.kt)("p",null,"Garden soil has been improved by using sheet mulch, compost, and/or cover crops."),(0,n.kt)("h4",{id:"observation-tags-3"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Compost"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#CoverCrops"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SheetMulch"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Mulch"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#CropRotation")),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices within a single calendar year.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices for two calendar years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices for three or more calendar years.")))),(0,n.kt)("h4",{id:"implementation-notes-2"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"water-smart"},"Water Smart"),(0,n.kt)("h4",{id:"general-criteria-3"},"General Criteria"),(0,n.kt)("p",null,"The garden involves water conservation practices, including: (1) collecting and using rainwater; (2) drip irrigation or soaker hoses, or (3) timers to water during cooler parts of day to minimize water use."),(0,n.kt)("h4",{id:"observation-tags-4"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#DripIrrigation"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Rainwater"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#WaterTimer"),"."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices within a single calendar year.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for two calendar years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for three or more calendar years.")))),(0,n.kt)("h4",{id:"implementation-notes-3"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h2",{id:"gardener-badges"},"Gardener badges"),(0,n.kt)("p",null,"Here are proposals for the alpha release Gardener badges."),(0,n.kt)("h3",{id:"community-cultivator"},"Community Cultivator"),(0,n.kt)("h4",{id:"general-criteria-4"},"General Criteria"),(0,n.kt)("p",null,"The gardener has demonstrated experience with community and/or school gardening."),(0,n.kt)("h4",{id:"observation-tags-5"},"Observation tags:"),(0,n.kt)("p",null,"N/A"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},'a. The gardener is associated with exactly one garden which has the "Community or School Garden" attestation.')),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},'a. The gardener is associated with exactly two gardens that have the "Community or School Garden" attestation.')),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},'a. The gardener is associated with three or more gardens that have the "Community or School Garden" attestation.')))),(0,n.kt)("h4",{id:"implementation-notes-4"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Garden and Gardener mutation."),(0,n.kt)("p",null,"Requires WithCoreData."),(0,n.kt)("h3",{id:"compost-champion"},"Compost Champion"),(0,n.kt)("h4",{id:"general-criteria-5"},"General Criteria"),(0,n.kt)("p",null,"The gardener has experience composting in a gardens."),(0,n.kt)("h4",{id:"observation-tags-6"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Compost"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#CompostTea"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Hugelkulture"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Vermiculture"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Worms"),". "),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener has posted Observations indicating at least one of the practices for a single calendar year in a garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener has posted Observations indicating at least one of the practices for two calendar years in a single garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener has posted Observations indicating at least one of the practices for three or more calendar years in a single garden.")))),(0,n.kt)("h4",{id:"implementation-notes-5"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("p",null,'Note that the gardener cannot get to levels 2 or 3 by "switching" among different gardens. The postings must be from the same garden. This means WithGardenData is enough to evaluate the criteria.'),(0,n.kt)("p",null,'Also, the gardener must make the Observations themselves. They can\'t "passively" obtain the badge because someone else in the Garden made Observations with the appropriate tags.'),(0,n.kt)("h3",{id:"crop-whisperer"},"Crop Whisperer"),(0,n.kt)("h4",{id:"general-criteria-6"},"General Criteria"),(0,n.kt)("p",null,"The gardener has demonstrated expertise in growing a specific crop in a single garden. "),(0,n.kt)("admonition",{title:"Multiple Badge Alert!",type:"info"},(0,n.kt)("p",{parentName:"admonition"},'Unlike other badges, this badge is crop-specific, and so a gardener can earn multiple Crop Whisperer badges ("Bean Whisperer", "Cucumber Whisperer")')),(0,n.kt)("h4",{id:"observation-tags-7"},"Observation tags"),(0,n.kt)("p",null,"N/A"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Plantings for exactly three different varieties of the same crop. ",(0,n.kt)("br",null)," b. At least two outcomes were awarded at least three stars in at least one Planting.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Plantings for exactly four different varieties of the same crop. ",(0,n.kt)("br",null)," b. At least two outcomes were awarded at least three stars in at least one Planting.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Plantings for at least five different varieties of the same crop. ",(0,n.kt)("br",null)," b. At least two outcomes were awarded at least three stars in at least one Planting.")))),(0,n.kt)("h4",{id:"implementation-notes-6"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Planting mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"greenhouse-grower"},"Greenhouse grower"),(0,n.kt)("h4",{id:"general-criteria-7"},"General Criteria"),(0,n.kt)("p",null,"The gardener has experience growing plants successfully in a greenhouse."),(0,n.kt)("h4",{id:"observation-tags-8"},"Observation tags"),(0,n.kt)("p",null,"N/A."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There is a single Planting in a single Garden that was started in a greenhouse that survived to harvest and was awarded at least three stars for at least one outcomes.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are two Plantings in a single Garden that were started in a greenhouse that survived to harvest and were awarded at least three stars for at least one outcomes.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are three Plantings in a single Garden that were started in a greenhouse that survived to harvest and were awarded at least three stars for at least one outcomes.")))),(0,n.kt)("h4",{id:"implementation-notes-7"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Planting mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"permaculture-pro"},"Permaculture Pro"),(0,n.kt)("h4",{id:"general-criteria-8"},"General Criteria"),(0,n.kt)("p",null,"The gardener has completed a Permaculture workshop to learn about the philosophy of permaculture and is also associated with garden(s) that have achieved permaculture-related badges"),(0,n.kt)("h4",{id:"observation-tags-9"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#PesticideFree"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SustainableSoil"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#WaterSmart"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#PollinatorFriendly")),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener has attested in their profile that they have completed a permaculture workshop. ",(0,n.kt)("br",null)," b. There are Observations indicating at least one of the practices within a single calendar year.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener has attested in their profile that they have completed a permaculture workshop. ",(0,n.kt)("br",null)," b. There are Observations indicating at least one of the practices for exactly two calendar years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener has attested in their profile that they have completed a permaculture workshop. ",(0,n.kt)("br",null)," b. There are Observations indicating at least one of the practices for three or more calendar years.")))),(0,n.kt)("h4",{id:"implementation-notes-8"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Garden and Observation mutations."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"vermiculturalist"},"Vermiculturalist"),(0,n.kt)("h4",{id:"general-criteria-9"},"General Criteria"),(0,n.kt)("p",null,"The gardener has experience with vermiculture (the controlled growing of worms) and vermicomposting (the use of worms to produce compost)."),(0,n.kt)("h4",{id:"observation-tags-10"},"Observation tags:"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#CompostTea"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Vermiculture"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Worms"),". "),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices within a single calendar year in a single Garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for two calendar years in a single Garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for three or more calendar years in a single Garden.")))),(0,n.kt)("h4",{id:"implementation-notes-9"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutations."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"seed-saver"},"Seed Saver"),(0,n.kt)("h4",{id:"general-criteria-10"},"General Criteria"),(0,n.kt)("p",null,"The gardener has demonstrated experience with seed saving practices, including: (1) Harvesting seeds from plants, (2) Drying seeds, (3) Storing seeds, (4) Germinating seeds, (5) Providing seeds to other members of the community."),(0,n.kt)("h4",{id:"observation-tags-11"},"Observation tags:"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#SeedSaving"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SeedSharing")),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices within a single calendar year in a single Garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for two calendar years in a single Garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for three or more calendar years in a single Garden.")))),(0,n.kt)("h4",{id:"implementation-notes-10"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutations."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h2",{id:"post-alpha-badges"},"Post-Alpha badges"),(0,n.kt)("p",null,"Here are some proposals for badges that we could add after the alpha release. I have not edited these descriptions to conform to the latest design principles."),(0,n.kt)("h3",{id:"chapter-chair"},"Chapter Chair"),(0,n.kt)("h4",{id:"general-criteria-11"},"General Criteria"),(0,n.kt)("p",null,"The gardener is serving as a Chair for the Chapter."),(0,n.kt)("p",null,"Note that GGC System Admins are responsible to designating which member(s) of a Chapter are the Chair(s). When they do this designation, they set a flag in the member`s profile indicating that they are currently a Chapter Chair and what date they started being Chair."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for one or two years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for three or four years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for five or more years.")))),(0,n.kt)("h3",{id:"connected-community"},"Connected Community"),(0,n.kt)("h4",{id:"general-criteria-12"},"General Criteria"),(0,n.kt)("p",null,"The chapter has demonstrated a commitment to building a community of practice."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 100 gardeners in the chapter.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 250 gardeners in the chapter.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 500 gardeners in the chapter.")))),(0,n.kt)("h3",{id:"climate-victors"},"Climate Victors"),(0,n.kt)("h4",{id:"general-criteria-13"},"General Criteria"),(0,n.kt)("p",null,"The chapter has demonstrated a commitment to creating Climate Victory Gardens."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 50% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 75% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 90% of the chapter gardens have achieved the badge.")))),(0,n.kt)("h3",{id:"pesticide-resistors"},"Pesticide Resistors"),(0,n.kt)("h4",{id:"general-criteria-14"},"General Criteria"),(0,n.kt)("p",null,"The chapter has demonstrated a commitment to avoiding the use of pesticides in their gardens."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 50% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 75% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 90% of the chapter gardens have achieved the badge.")))),(0,n.kt)("h3",{id:"seed-sharers"},"Seed Sharers"),(0,n.kt)("h4",{id:"general-criteria-15"},"General Criteria:"),(0,n.kt)("p",null,"The chapter has demonstrated a commitment to seed sharing."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 50% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 75% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 90% of the chapter gardens have achieved the badge.")))),(0,n.kt)("h3",{id:"climate-victory"},"Climate Victory"),(0,n.kt)("h4",{id:"general-criteria-16"},"General Criteria"),(0,n.kt)("p",null,"A Climate Victory Garden has been added to ",(0,n.kt)("a",{parentName:"p",href:"https://www.greenamerica.org/climate-victory-gardens"},"Green America`s database")," and the garden implements one or more of the following practices: (1) grow food, (2) cover soils, (3) compost, (4) ditch chemicals, and (5) encourage biodiversity."),(0,n.kt)("h4",{id:"observation-tags-12"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Biodiversity"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Compost"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#CoverCrops"),",",(0,n.kt)("inlineCode",{parentName:"p"},"#DitchChemicals"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#PesticideFree"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#PollinatorFriendly"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SheetMulch"),"."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is in the Green America database. ",(0,n.kt)("br",null)," b. There are Observations associated with this garden for at least two of the associated tags.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is in the Green America database. ",(0,n.kt)("br",null)," b. There are Observations associated with this garden for at least five of the associated tags.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is in the Green America database. ",(0,n.kt)("br",null)," b. There are Observations associated with this garden for at least five of the associated tags in at least two different calendar years.")))),(0,n.kt)("h3",{id:"master-gardener"},"Master gardener"),(0,n.kt)("h4",{id:"general-criteria-17"},"General Criteria"),(0,n.kt)("p",null,"The gardener has completed a master gardener program."),(0,n.kt)("admonition",{title:"Shucks",type:"warning"},(0,n.kt)("p",{parentName:"admonition"},"I cannot think of a simple way to award more than one star. Ideas?")),(0,n.kt)("h4",{id:"observation-tags-13"},"Observation tags"),(0,n.kt)("p",null,"N/A"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener attests in their profile to having received a Master Gardener certification.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"(Not yet available)")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"(Not yet available)")))),(0,n.kt)("h3",{id:"bee-buddy"},"Bee Buddy"),(0,n.kt)("h4",{id:"general-criteria-18"},"General Criteria"),(0,n.kt)("p",null,"The gardener has experience caring for bees."),(0,n.kt)("h4",{id:"observation-tags-14"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Beekeeping"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Beekeeper")),(0,n.kt)("h3",{id:"aquaponics-ace"},"Aquaponics Ace"),(0,n.kt)("h4",{id:"general-criteria-19"},"General Criteria"),(0,n.kt)("p",null,"The gardener has demonstrated experience with aquaponics."),(0,n.kt)("h4",{id:"observation-tags-15"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Aquaponics"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#FishAndPlants"),","),(0,n.kt)("h3",{id:"herbalist-hero"},"Herbalist Hero"),(0,n.kt)("h4",{id:"general-criteria-20"},"General Criteria"),(0,n.kt)("p",null,"The gardener has grown medicinal herbs and created remedies from them."),(0,n.kt)("h4",{id:"observation-tags-16"},"Observation tags:"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Herbalist"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#HerbalRemedy"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#PlantMedicine")),(0,n.kt)("h3",{id:"educator-extraordinaire"},"Educator Extraordinaire"),(0,n.kt)("h4",{id:"general-criteria-21"},"General Criteria"),(0,n.kt)("p",null,"The gardener has provided educational experiences such as leading workshops, writing articles, or working as a garden educator in schools."),(0,n.kt)("h4",{id:"observation-tags-17"},"Observation tags:"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#InspireAndTeach"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SkillSharing"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#CommunityWorkshop")),(0,n.kt)("h3",{id:"orchard-orchestrator"},"Orchard Orchestrator"),(0,n.kt)("h4",{id:"general-criteria-22"},"General Criteria"),(0,n.kt)("p",null,"The gardener has demonstrated experience with orchard management."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/e96f37e1.8690653f.js b/assets/js/e96f37e1.8690653f.js new file mode 100644 index 000000000..28d830e93 --- /dev/null +++ b/assets/js/e96f37e1.8690653f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkgeogardenclub_github_io=self.webpackChunkgeogardenclub_github_io||[]).push([[909],{3905:(e,t,a)=>{a.d(t,{Zo:()=>p,kt:()=>c});var r=a(7294);function n(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,r)}return a}function l(e){for(var t=1;t=0||(n[a]=e[a]);return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(n[a]=e[a])}return n}var s=r.createContext({}),d=function(e){var t=r.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):l(l({},t),e)),a},p=function(e){var t=d(e.components);return r.createElement(s.Provider,{value:t},e.children)},h="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},g=r.forwardRef((function(e,t){var a=e.components,n=e.mdxType,i=e.originalType,s=e.parentName,p=o(e,["components","mdxType","originalType","parentName"]),h=d(a),g=n,c=h["".concat(s,".").concat(g)]||h[g]||m[g]||i;return a?r.createElement(c,l(l({ref:t},p),{},{components:a})):r.createElement(c,l({ref:t},p))}));function c(e,t){var a=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var i=a.length,l=new Array(i);l[0]=g;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[h]="string"==typeof e?e:n,l[1]=o;for(var d=2;d{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>d});var r=a(7462),n=(a(7294),a(3905));const i={hide_table_of_contents:!1},l="Badges",o={unversionedId:"develop/alpha-release/badges",id:"develop/alpha-release/badges",title:"Badges",description:"Goals",source:"@site/docs/develop/alpha-release/badges.md",sourceDirName:"develop/alpha-release",slug:"/develop/alpha-release/badges",permalink:"/docs/develop/alpha-release/badges",draft:!1,tags:[],version:"current",frontMatter:{hide_table_of_contents:!1},sidebar:"developSidebar",previous:{title:"Coding Standards",permalink:"/docs/develop/alpha-release/coding-standards"},next:{title:"GGC Input Fields",permalink:"/docs/develop/alpha-release/input-fields"}},s={},d=[{value:"Goals",id:"goals",level:2},{value:"Design principles",id:"design-principles",level:2},{value:"Types",id:"types",level:3},{value:"Levels",id:"levels",level:3},{value:"Verification (i.e. badge processing)",id:"verification-ie-badge-processing",level:3},{value:"Observation tags",id:"observation-tags",level:3},{value:"Implementation hints",id:"implementation-hints",level:3},{value:"Garden badges",id:"garden-badges",level:2},{value:"Pesticide free",id:"pesticide-free",level:3},{value:"General Criteria",id:"general-criteria",level:4},{value:"Observation tags",id:"observation-tags-1",level:4},{value:"Implementation notes",id:"implementation-notes",level:4},{value:"Pollinator Friendly",id:"pollinator-friendly",level:3},{value:"General Criteria",id:"general-criteria-1",level:4},{value:"Observation tags",id:"observation-tags-2",level:4},{value:"Implementation notes",id:"implementation-notes-1",level:4},{value:"Sustainable Soil",id:"sustainable-soil",level:3},{value:"General Criteria",id:"general-criteria-2",level:4},{value:"Observation tags",id:"observation-tags-3",level:4},{value:"Implementation notes",id:"implementation-notes-2",level:4},{value:"Water Smart",id:"water-smart",level:3},{value:"General Criteria",id:"general-criteria-3",level:4},{value:"Observation tags",id:"observation-tags-4",level:4},{value:"Implementation notes",id:"implementation-notes-3",level:4},{value:"Gardener badges",id:"gardener-badges",level:2},{value:"Community Cultivator",id:"community-cultivator",level:3},{value:"General Criteria",id:"general-criteria-4",level:4},{value:"Observation tags:",id:"observation-tags-5",level:4},{value:"Implementation notes",id:"implementation-notes-4",level:4},{value:"Compost Champion",id:"compost-champion",level:3},{value:"General Criteria",id:"general-criteria-5",level:4},{value:"Observation tags",id:"observation-tags-6",level:4},{value:"Implementation notes",id:"implementation-notes-5",level:4},{value:"Crop Whisperer",id:"crop-whisperer",level:3},{value:"General Criteria",id:"general-criteria-6",level:4},{value:"Observation tags",id:"observation-tags-7",level:4},{value:"Implementation notes",id:"implementation-notes-6",level:4},{value:"Greenhouse grower",id:"greenhouse-grower",level:3},{value:"General Criteria",id:"general-criteria-7",level:4},{value:"Observation tags",id:"observation-tags-8",level:4},{value:"Implementation notes",id:"implementation-notes-7",level:4},{value:"Permaculture Pro",id:"permaculture-pro",level:3},{value:"General Criteria",id:"general-criteria-8",level:4},{value:"Observation tags",id:"observation-tags-9",level:4},{value:"Implementation notes",id:"implementation-notes-8",level:4},{value:"Vermiculturalist",id:"vermiculturalist",level:3},{value:"General Criteria",id:"general-criteria-9",level:4},{value:"Observation tags:",id:"observation-tags-10",level:4},{value:"Implementation notes",id:"implementation-notes-9",level:4},{value:"Seed Saver",id:"seed-saver",level:3},{value:"General Criteria",id:"general-criteria-10",level:4},{value:"Observation tags:",id:"observation-tags-11",level:4},{value:"Implementation notes",id:"implementation-notes-10",level:4},{value:"Post-Alpha badges",id:"post-alpha-badges",level:2},{value:"Chapter Chair",id:"chapter-chair",level:3},{value:"General Criteria",id:"general-criteria-11",level:4},{value:"Connected Community",id:"connected-community",level:3},{value:"General Criteria",id:"general-criteria-12",level:4},{value:"Climate Victors",id:"climate-victors",level:3},{value:"General Criteria",id:"general-criteria-13",level:4},{value:"Pesticide Resistors",id:"pesticide-resistors",level:3},{value:"General Criteria",id:"general-criteria-14",level:4},{value:"Seed Sharers",id:"seed-sharers",level:3},{value:"General Criteria:",id:"general-criteria-15",level:4},{value:"Climate Victory",id:"climate-victory",level:3},{value:"General Criteria",id:"general-criteria-16",level:4},{value:"Observation tags",id:"observation-tags-12",level:4},{value:"Master gardener",id:"master-gardener",level:3},{value:"General Criteria",id:"general-criteria-17",level:4},{value:"Observation tags",id:"observation-tags-13",level:4},{value:"Bee Buddy",id:"bee-buddy",level:3},{value:"General Criteria",id:"general-criteria-18",level:4},{value:"Observation tags",id:"observation-tags-14",level:4},{value:"Aquaponics Ace",id:"aquaponics-ace",level:3},{value:"General Criteria",id:"general-criteria-19",level:4},{value:"Observation tags",id:"observation-tags-15",level:4},{value:"Herbalist Hero",id:"herbalist-hero",level:3},{value:"General Criteria",id:"general-criteria-20",level:4},{value:"Observation tags:",id:"observation-tags-16",level:4},{value:"Educator Extraordinaire",id:"educator-extraordinaire",level:3},{value:"General Criteria",id:"general-criteria-21",level:4},{value:"Observation tags:",id:"observation-tags-17",level:4},{value:"Orchard Orchestrator",id:"orchard-orchestrator",level:3},{value:"General Criteria",id:"general-criteria-22",level:4}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,n.kt)(h,(0,r.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"badges"},"Badges"),(0,n.kt)("h2",{id:"goals"},"Goals"),(0,n.kt)("p",null,"The alpha release will implement a badge system for gardens and gardeners. This badge system is designed to accomplish the following goals:"),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("em",{parentName:"li"},"Foster user engagement and enjoyment through a game mechanic that publicizes achievements by gardens and gardeners.")," Gardeners should find it fun to accumulate badges that are associated with their profile and their garden(s)."),(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("em",{parentName:"li"},"Foster a community of practice by helping gardeners connect with others with similar interests and/or greater expertise with respect to a specific gardening topic.")," For example, if a user is interested in vermiculture, the badge system provides a mechanism for them to find other gardeners who already have experience in this area. "),(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("em",{parentName:"li"},"Provide a useful, compact representation of garden and gardener characteristics."),' The app provides "summary" cards for gardens and gardeners. Users should find the presence (and/or absence) of badges helpful in forming a high level understanding of these entities.'),(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("em",{parentName:"li"},"Provide a mechanism that identifies ways to improve gardening practices."),' The badge system makes visible the practices that are important to the GGC mission of food resiliency and sustainable gardening, such as seed saving, composting, and water conservation. This means that a simple heuristic for "getting better at gardening" is to simply "get more badges".')),(0,n.kt)("admonition",{title:"What about Chapters?",type:"info"},(0,n.kt)("p",{parentName:"admonition"},"The alpha release will not implement Chapter-level badges for two reasons:"),(0,n.kt)("ol",{parentName:"admonition"},(0,n.kt)("li",{parentName:"ol"},'The primary goals for Chapter-level badges are: (a) encouraging members of a Chapter via "peer pressure" to conform to certain best practices, and (b) making possible Chapter "leaderboards" so that Chapters can assess their capabilities relative to other chapters. Neither of these are important for the alpha release where we will focus on a single Chapter with a relatively small number of members. '),(0,n.kt)("li",{parentName:"ol"},"We will implement garden and gardener-based badge processing within each client, which is simple, scalable, and (hopefully) efficient. Implementing a Chapter-level badge system at scale will require Firebase cloud functions. These functions require specialized knowledge to implement correctly."))),(0,n.kt)("h2",{id:"design-principles"},"Design principles"),(0,n.kt)("h3",{id:"types"},"Types"),(0,n.kt)("p",null,"The alpha release will implement two types of badges: garden badges and gardener badges. Garden badges reflect the characteristics of a garden across one or more years. Gardener badges reflect characteristics of a gardener across all of the gardens with which they are associated. "),(0,n.kt)("h3",{id:"levels"},"Levels"),(0,n.kt)("p",null,"Each badge can be achieved at three levels of increasing sophistication and/or expertise. Level 1 badges are relatively easy to achieve. Level 2 and Level 3 badges indicate increasing levels of expertise or accomplishment with respect to the badge subject. "),(0,n.kt)("p",null,"Levels will be visually represented by 1-3 stars along the left side of the badge. Here`s an example:"),(0,n.kt)("img",{style:{borderStyle:"solid"},width:"300px",src:"/img/develop/alpha-release/badges/badge-examples.png"}),(0,n.kt)("h3",{id:"verification-ie-badge-processing"},"Verification (i.e. badge processing)"),(0,n.kt)("p",null,'Verification of badges can be done in the following ways: "via attestation", "via observation", or "via planting". Depending upon the badge and/or level, one or more of these verification approaches might be required.'),(0,n.kt)("p",null,'"Via attestation" means that the Gardener (owner) has simply attested that they (or their garden) adheres to certain practices. This is implemented as an "Attestation" section in the Garden and Gardener forms. For example, when creating or updating a Garden, the gardener (owner) can simply check a box to attest that the garden is pesticide-free. Gardeners are on the honor system to attest only to practices that they believe to be true.'),(0,n.kt)("p",null,'"Via Observation" requires the Gardener (owner or editor) to post one or more Observations with one or more badge-specific tags in a single Garden. '),(0,n.kt)("p",null,'"Via Planting" requires the Gardener (owner) to have created Planting data in a single Garden that helps to satisfy the criteria for a badge.'),(0,n.kt)("admonition",{title:"Alpha release badge processing is client-side only",type:"warning"},(0,n.kt)("p",{parentName:"admonition"},"As the above indicates, for the alpha release, badge processing occurs on the client-side, and is triggered by updates to garden, gardener, observation, or planting documents."),(0,n.kt)("p",{parentName:"admonition"},'The current criteria are designed so that they can be assessed via either WithCoreData or WithGardenData. See the Implementation Notes section associated with each badge for an indication of which "With" widget can be used.'),(0,n.kt)("p",{parentName:"admonition"},"There are many ways we could define the criteria for a badge. The criteria we choose must align with the alpha release design constraints. If a criteria turns out to be too expensive to verify via client-side processing, then we should change the criteria, not change the design.")),(0,n.kt)("h3",{id:"observation-tags"},"Observation tags"),(0,n.kt)("p",null,"Many badges require the posting of (public) Observations to provide evidence for a specific practice. To implement badge processing, the system needs to be able to identify Observations that are intended to support achievement of a particular badge. This will be done by the user attaching one or more pre-defined tags to an Observation. Some practices (i.e. cover crops) can help the user achieve multiple badges, so the tag labels are not designed to indicate any particular badge. "),(0,n.kt)("p",null,'The system will "take the user\'s word" for the appropriateness of the tags to the Observation. We hope that the public nature of these observations will prevent users from misusing this process.'),(0,n.kt)("h3",{id:"implementation-hints"},"Implementation hints"),(0,n.kt)("p",null,"Badge processing has the following general implementation characteristics:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},'Triggered as part of "mutation" of Gardener, Garden, Planting, and Observation entities.'),(0,n.kt)("li",{parentName:"ul"},"During submit() processing, the BadgeProcessor is called with the Garden, Chapter, and User collections, plus the entities about to be mutated. It then calls a function for each Badge, passing it this data. Each badge-specific function has its own function returns the set of BadgeInstances to be created and deleted. ")),(0,n.kt)("p",null,"Badge implementation also involves the creation of the Badges page. This page should provide a description of each Badge, the criteria required to obtain each level, and chips to indicate the Gardens (or Gardeners) that currently hold the badge."),(0,n.kt)("p",null,'Badge implementation will also require updates to the Garden and Gardener entities and mutation processing in order to support the various "attestation" checkboxes. Each attestation can be implemented as an optional boolean field in the Garden or Gardener entity.'),(0,n.kt)("h2",{id:"garden-badges"},"Garden badges"),(0,n.kt)("p",null,"Here are proposals for the alpha release garden badges."),(0,n.kt)("h3",{id:"pesticide-free"},"Pesticide free"),(0,n.kt)("h4",{id:"general-criteria"},"General Criteria"),(0,n.kt)("p",null,"No pesticides are used in this garden."),(0,n.kt)("h4",{id:"observation-tags-1"},"Observation tags"),(0,n.kt)("p",null,"N/A"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is pesticide free. ",(0,n.kt)("br",null)," b. There is Planting data for this garden for a single calendar year.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is pesticide free. ",(0,n.kt)("br",null)," b. There is Planting data for this garden for exactly two calendar years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is pesticide free. ",(0,n.kt)("br",null)," b. There is Planting data for this garden for three or more calendar years.")))),(0,n.kt)("h4",{id:"implementation-notes"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Garden or Planting mutation. "),(0,n.kt)("p",null,"Requires WithGardenData. "),(0,n.kt)("h3",{id:"pollinator-friendly"},"Pollinator Friendly"),(0,n.kt)("h4",{id:"general-criteria-1"},"General Criteria"),(0,n.kt)("p",null,'The garden has pollinator-friendly practices such as: (1) Using a wide variety of plants that bloom from early spring into late fall, (2) Avoiding modern hybrid flowers, especially those with "doubled" flowers, (3) Eliminating pesticides whenever possible, (4) Including larval host plants in your landscape, (5) Creating a damp salt lick for butterflies and bees, (6) Leaving dead trees, or at least an occasional dead limb, in order to provide essential nesting sites for native bees, and (7) Adding to nectar resources by providing a hummingbird feeder.'),(0,n.kt)("h4",{id:"observation-tags-2"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#DitchChemicals"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Habitat"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Hummingbirds"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#LarvalHostPlants"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#NativeBees"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#NativePlants"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#PesticideFree"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SaltLick"),"."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices within a single calendar year.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices for two calendar years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices for three or more calendar years.")))),(0,n.kt)("h4",{id:"implementation-notes-1"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"sustainable-soil"},"Sustainable Soil"),(0,n.kt)("h4",{id:"general-criteria-2"},"General Criteria"),(0,n.kt)("p",null,"Garden soil has been improved by using sheet mulch, compost, and/or cover crops."),(0,n.kt)("h4",{id:"observation-tags-3"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Compost"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#CoverCrops"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SheetMulch"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Mulch"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#CropRotation")),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices within a single calendar year.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices for two calendar years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least three of the practices for three or more calendar years.")))),(0,n.kt)("h4",{id:"implementation-notes-2"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"water-smart"},"Water Smart"),(0,n.kt)("h4",{id:"general-criteria-3"},"General Criteria"),(0,n.kt)("p",null,"The garden involves water conservation practices, including: (1) collecting and using rainwater; (2) drip irrigation or soaker hoses, or (3) timers to water during cooler parts of day to minimize water use."),(0,n.kt)("h4",{id:"observation-tags-4"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#DripIrrigation"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Rainwater"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#WaterTimer"),"."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices within a single calendar year.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for two calendar years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for three or more calendar years.")))),(0,n.kt)("h4",{id:"implementation-notes-3"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h2",{id:"gardener-badges"},"Gardener badges"),(0,n.kt)("p",null,"Here are proposals for the alpha release Gardener badges."),(0,n.kt)("h3",{id:"community-cultivator"},"Community Cultivator"),(0,n.kt)("h4",{id:"general-criteria-4"},"General Criteria"),(0,n.kt)("p",null,"The gardener has demonstrated experience with community and/or school gardening."),(0,n.kt)("h4",{id:"observation-tags-5"},"Observation tags:"),(0,n.kt)("p",null,"N/A"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},'a. The gardener is associated with exactly one garden which has the "Community or School Garden" attestation.')),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},'a. The gardener is associated with exactly two gardens that have the "Community or School Garden" attestation.')),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},'a. The gardener is associated with three or more gardens that have the "Community or School Garden" attestation.')))),(0,n.kt)("h4",{id:"implementation-notes-4"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Garden and Gardener mutation."),(0,n.kt)("p",null,"Requires WithCoreData."),(0,n.kt)("h3",{id:"compost-champion"},"Compost Champion"),(0,n.kt)("h4",{id:"general-criteria-5"},"General Criteria"),(0,n.kt)("p",null,"The gardener has experience composting in a gardens."),(0,n.kt)("h4",{id:"observation-tags-6"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Compost"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#CompostTea"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Hugelkulture"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Vermiculture"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Worms"),". "),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener (owner or editor) has posted Observations indicating at least one of the practices for a single calendar year in a garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener (owner or editor) has posted Observations indicating at least one of the practices for two calendar years in a single garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener (owner or editor) has posted Observations indicating at least one of the practices for three or more calendar years in a single garden.")))),(0,n.kt)("h4",{id:"implementation-notes-5"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("p",null,'Note that the gardener cannot get to levels 2 or 3 by "switching" among different gardens. The postings must be from the same garden. This means WithGardenData is enough to evaluate the criteria.'),(0,n.kt)("p",null,'Also, the gardener must make the Observations themselves. They can\'t "passively" obtain the badge because someone else in the Garden made Observations with the appropriate tags.'),(0,n.kt)("h3",{id:"crop-whisperer"},"Crop Whisperer"),(0,n.kt)("h4",{id:"general-criteria-6"},"General Criteria"),(0,n.kt)("p",null,"The gardener has demonstrated expertise in growing a specific crop in a single garden. "),(0,n.kt)("admonition",{title:"Multiple Badge Alert!",type:"info"},(0,n.kt)("p",{parentName:"admonition"},'Unlike other badges, this badge is crop-specific, and so a gardener can earn multiple Crop Whisperer badges ("Bean Whisperer", "Cucumber Whisperer")')),(0,n.kt)("h4",{id:"observation-tags-7"},"Observation tags"),(0,n.kt)("p",null,"N/A"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Plantings for exactly three different varieties of the same crop. ",(0,n.kt)("br",null)," b. At least two outcomes were awarded at least three stars in at least one Planting.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Plantings for exactly four different varieties of the same crop. ",(0,n.kt)("br",null)," b. At least two outcomes were awarded at least three stars in at least one Planting.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Plantings for at least five different varieties of the same crop. ",(0,n.kt)("br",null)," b. At least two outcomes were awarded at least three stars in at least one Planting.")))),(0,n.kt)("h4",{id:"implementation-notes-6"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Planting mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"greenhouse-grower"},"Greenhouse grower"),(0,n.kt)("h4",{id:"general-criteria-7"},"General Criteria"),(0,n.kt)("p",null,"The gardener has experience growing plants successfully in a greenhouse."),(0,n.kt)("h4",{id:"observation-tags-8"},"Observation tags"),(0,n.kt)("p",null,"N/A."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There is a single Planting in a single Garden that was started in a greenhouse that survived to harvest and was awarded at least three stars for at least one outcomes.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are two Plantings in a single Garden that were started in a greenhouse that survived to harvest and were awarded at least three stars for at least one outcomes.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are three Plantings in a single Garden that were started in a greenhouse that survived to harvest and were awarded at least three stars for at least one outcomes.")))),(0,n.kt)("h4",{id:"implementation-notes-7"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Planting mutation."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"permaculture-pro"},"Permaculture Pro"),(0,n.kt)("h4",{id:"general-criteria-8"},"General Criteria"),(0,n.kt)("p",null,"The gardener has completed a Permaculture workshop to learn about the philosophy of permaculture and is also associated with garden(s) that have achieved permaculture-related badges"),(0,n.kt)("h4",{id:"observation-tags-9"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#PesticideFree"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SustainableSoil"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#WaterSmart"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#PollinatorFriendly")),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener (owner or editor) has attested in their profile that they have completed a permaculture workshop. ",(0,n.kt)("br",null)," b. There are Observations indicating at least one of the practices within a single calendar year.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener (owner or editor) has attested in their profile that they have completed a permaculture workshop. ",(0,n.kt)("br",null)," b. There are Observations indicating at least one of the practices for exactly two calendar years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener (owner or editor) has attested in their profile that they have completed a permaculture workshop. ",(0,n.kt)("br",null)," b. There are Observations indicating at least one of the practices for three or more calendar years.")))),(0,n.kt)("h4",{id:"implementation-notes-8"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Garden and Observation mutations."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"vermiculturalist"},"Vermiculturalist"),(0,n.kt)("h4",{id:"general-criteria-9"},"General Criteria"),(0,n.kt)("p",null,"The gardener has experience with vermiculture (the controlled growing of worms) and vermicomposting (the use of worms to produce compost)."),(0,n.kt)("h4",{id:"observation-tags-10"},"Observation tags:"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#CompostTea"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Vermiculture"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Worms"),". "),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices within a single calendar year in a single Garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for two calendar years in a single Garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for three or more calendar years in a single Garden.")))),(0,n.kt)("h4",{id:"implementation-notes-9"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutations."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h3",{id:"seed-saver"},"Seed Saver"),(0,n.kt)("h4",{id:"general-criteria-10"},"General Criteria"),(0,n.kt)("p",null,"The gardener has demonstrated experience with seed saving practices, including: (1) Harvesting seeds from plants, (2) Drying seeds, (3) Storing seeds, (4) Germinating seeds, (5) Providing seeds to other members of the community."),(0,n.kt)("h4",{id:"observation-tags-11"},"Observation tags:"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#SeedSaving"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SeedSharing")),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices within a single calendar year in a single Garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for two calendar years in a single Garden.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. There are Observations indicating at least one of the practices for three or more calendar years in a single Garden.")))),(0,n.kt)("h4",{id:"implementation-notes-10"},"Implementation notes"),(0,n.kt)("p",null,"Triggered as part of Observation mutations."),(0,n.kt)("p",null,"Requires WithGardenData."),(0,n.kt)("h2",{id:"post-alpha-badges"},"Post-Alpha badges"),(0,n.kt)("p",null,"Here are some proposals for badges that we could add after the alpha release. I have not edited these descriptions to conform to the latest design principles."),(0,n.kt)("h3",{id:"chapter-chair"},"Chapter Chair"),(0,n.kt)("h4",{id:"general-criteria-11"},"General Criteria"),(0,n.kt)("p",null,"The gardener is serving as a Chair for the Chapter."),(0,n.kt)("p",null,"Note that GGC System Admins are responsible to designating which member(s) of a Chapter are the Chair(s). When they do this designation, they set a flag in the member`s profile indicating that they are currently a Chapter Chair and what date they started being Chair."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for one or two years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for three or four years.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for five or more years.")))),(0,n.kt)("h3",{id:"connected-community"},"Connected Community"),(0,n.kt)("h4",{id:"general-criteria-12"},"General Criteria"),(0,n.kt)("p",null,"The chapter has demonstrated a commitment to building a community of practice."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 100 gardeners in the chapter.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 250 gardeners in the chapter.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 500 gardeners in the chapter.")))),(0,n.kt)("h3",{id:"climate-victors"},"Climate Victors"),(0,n.kt)("h4",{id:"general-criteria-13"},"General Criteria"),(0,n.kt)("p",null,"The chapter has demonstrated a commitment to creating Climate Victory Gardens."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 50% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 75% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 90% of the chapter gardens have achieved the badge.")))),(0,n.kt)("h3",{id:"pesticide-resistors"},"Pesticide Resistors"),(0,n.kt)("h4",{id:"general-criteria-14"},"General Criteria"),(0,n.kt)("p",null,"The chapter has demonstrated a commitment to avoiding the use of pesticides in their gardens."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 50% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 75% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 90% of the chapter gardens have achieved the badge.")))),(0,n.kt)("h3",{id:"seed-sharers"},"Seed Sharers"),(0,n.kt)("h4",{id:"general-criteria-15"},"General Criteria:"),(0,n.kt)("p",null,"The chapter has demonstrated a commitment to seed sharing."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 50% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 75% of the chapter gardens have achieved the badge.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. At least 90% of the chapter gardens have achieved the badge.")))),(0,n.kt)("h3",{id:"climate-victory"},"Climate Victory"),(0,n.kt)("h4",{id:"general-criteria-16"},"General Criteria"),(0,n.kt)("p",null,"A Climate Victory Garden has been added to ",(0,n.kt)("a",{parentName:"p",href:"https://www.greenamerica.org/climate-victory-gardens"},"Green America`s database")," and the garden implements one or more of the following practices: (1) grow food, (2) cover soils, (3) compost, (4) ditch chemicals, and (5) encourage biodiversity."),(0,n.kt)("h4",{id:"observation-tags-12"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Biodiversity"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Compost"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#CoverCrops"),",",(0,n.kt)("inlineCode",{parentName:"p"},"#DitchChemicals"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#PesticideFree"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#PollinatorFriendly"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SheetMulch"),"."),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is in the Green America database. ",(0,n.kt)("br",null)," b. There are Observations associated with this garden for at least two of the associated tags.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is in the Green America database. ",(0,n.kt)("br",null)," b. There are Observations associated with this garden for at least five of the associated tags.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The user has attested that the Garden is in the Green America database. ",(0,n.kt)("br",null)," b. There are Observations associated with this garden for at least five of the associated tags in at least two different calendar years.")))),(0,n.kt)("h3",{id:"master-gardener"},"Master gardener"),(0,n.kt)("h4",{id:"general-criteria-17"},"General Criteria"),(0,n.kt)("p",null,"The gardener has completed a master gardener program."),(0,n.kt)("admonition",{title:"Shucks",type:"warning"},(0,n.kt)("p",{parentName:"admonition"},"I cannot think of a simple way to award more than one star. Ideas?")),(0,n.kt)("h4",{id:"observation-tags-13"},"Observation tags"),(0,n.kt)("p",null,"N/A"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:null},"Level"),(0,n.kt)("th",{parentName:"tr",align:null},"Verification"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"1"),(0,n.kt)("td",{parentName:"tr",align:null},"a. The gardener attests in their profile to having received a Master Gardener certification.")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"2"),(0,n.kt)("td",{parentName:"tr",align:null},"(Not yet available)")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:null},"3"),(0,n.kt)("td",{parentName:"tr",align:null},"(Not yet available)")))),(0,n.kt)("h3",{id:"bee-buddy"},"Bee Buddy"),(0,n.kt)("h4",{id:"general-criteria-18"},"General Criteria"),(0,n.kt)("p",null,"The gardener has experience caring for bees."),(0,n.kt)("h4",{id:"observation-tags-14"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Beekeeping"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#Beekeeper")),(0,n.kt)("h3",{id:"aquaponics-ace"},"Aquaponics Ace"),(0,n.kt)("h4",{id:"general-criteria-19"},"General Criteria"),(0,n.kt)("p",null,"The gardener has demonstrated experience with aquaponics."),(0,n.kt)("h4",{id:"observation-tags-15"},"Observation tags"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Aquaponics"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#FishAndPlants"),","),(0,n.kt)("h3",{id:"herbalist-hero"},"Herbalist Hero"),(0,n.kt)("h4",{id:"general-criteria-20"},"General Criteria"),(0,n.kt)("p",null,"The gardener has grown medicinal herbs and created remedies from them."),(0,n.kt)("h4",{id:"observation-tags-16"},"Observation tags:"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#Herbalist"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#HerbalRemedy"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#PlantMedicine")),(0,n.kt)("h3",{id:"educator-extraordinaire"},"Educator Extraordinaire"),(0,n.kt)("h4",{id:"general-criteria-21"},"General Criteria"),(0,n.kt)("p",null,"The gardener has provided educational experiences such as leading workshops, writing articles, or working as a garden educator in schools."),(0,n.kt)("h4",{id:"observation-tags-17"},"Observation tags:"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"#InspireAndTeach"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#SkillSharing"),", ",(0,n.kt)("inlineCode",{parentName:"p"},"#CommunityWorkshop")),(0,n.kt)("h3",{id:"orchard-orchestrator"},"Orchard Orchestrator"),(0,n.kt)("h4",{id:"general-criteria-22"},"General Criteria"),(0,n.kt)("p",null,"The gardener has demonstrated experience with orchard management."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.efcdcca6.js b/assets/js/runtime~main.444b2a7d.js similarity index 64% rename from assets/js/runtime~main.efcdcca6.js rename to assets/js/runtime~main.444b2a7d.js index c9106f6e9..a2dcc4d54 100644 --- a/assets/js/runtime~main.efcdcca6.js +++ b/assets/js/runtime~main.444b2a7d.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,t,r,f,d={},c={};function o(e){var a=c[e];if(void 0!==a)return a.exports;var t=c[e]={exports:{}};return d[e].call(t.exports,t,t.exports,o),t.exports}o.m=d,e=[],o.O=(a,t,r,f)=>{if(!t){var d=1/0;for(i=0;i=f)&&Object.keys(o.O).every((e=>o.O[e](t[b])))?t.splice(b--,1):(c=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[t,r,f]},o.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return o.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var f=Object.create(null);o.r(f);var d={};a=a||[null,t({}),t([]),t(t)];for(var c=2&r&&e;"object"==typeof c&&!~a.indexOf(c);c=t(c))Object.getOwnPropertyNames(c).forEach((a=>d[a]=()=>e[a]));return d.default=()=>e,o.d(f,d),f},o.d=(e,a)=>{for(var t in a)o.o(a,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((a,t)=>(o.f[t](e,a),a)),[])),o.u=e=>"assets/js/"+({4:"4550e3f5",14:"4a78ad51",53:"935f2afb",57:"c03baef0",85:"1f391b9e",89:"a6aa9e1f",103:"ccc49370",195:"c4f5d8e4",265:"906ac375",277:"8175d4ae",306:"356ab24d",328:"8c887cbc",371:"e82ae094",378:"7fda5be5",414:"393be207",420:"6741c1a9",477:"b2f554cd",490:"850fe7ba",492:"5a8d1bd9",514:"1be78505",533:"b2b675dd",535:"814f3328",549:"1506d638",608:"9e4087bc",616:"d0e791fc",688:"f638b2a7",799:"954aa590",814:"95f4d37c",866:"11f6a8a1",878:"fe0b396c",888:"ee91fb74",909:"e96f37e1",918:"17896441",946:"d75a4d02",949:"0ffd5edc",968:"47fd9058",974:"af21c641",994:"6490a4b3"}[e]||e)+"."+{4:"7ad67dca",14:"1e786691",53:"44caf202",57:"017eb2b2",85:"2adfe008",89:"704a8d1c",103:"8c5c8077",195:"e035c206",265:"2b12e2b9",277:"72e652da",306:"51a190b2",328:"7df3361c",371:"6e321dbd",378:"24b074e7",412:"b078622e",414:"635a33ac",420:"d133cf79",477:"55e1a35c",490:"f82eebb2",492:"48e43781",506:"9e3b469b",514:"073db173",533:"160181bc",535:"89791684",549:"2be4de9d",608:"8fa1b199",616:"4f89fef9",688:"39d0c585",799:"9b74545f",814:"76530f14",866:"489ff8b9",878:"48452069",888:"9e17b1c0",909:"6e364f0d",918:"340030a3",946:"fab0bb26",949:"3cd9bedc",968:"89ff45b2",972:"c3865fc5",974:"b178bb8c",994:"8dabe684"}[e]+".js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),r={},f="geogardenclub-github-io:",o.l=(e,a,t,d)=>{if(r[e])r[e].push(a);else{var c,b;if(void 0!==t)for(var n=document.getElementsByTagName("script"),i=0;i{c.onerror=c.onload=null,clearTimeout(s);var f=r[e];if(delete r[e],c.parentNode&&c.parentNode.removeChild(c),f&&f.forEach((e=>e(t))),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=l.bind(null,c.onerror),c.onload=l.bind(null,c.onload),b&&document.head.appendChild(c)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.p="/",o.gca=function(e){return e={17896441:"918","4550e3f5":"4","4a78ad51":"14","935f2afb":"53",c03baef0:"57","1f391b9e":"85",a6aa9e1f:"89",ccc49370:"103",c4f5d8e4:"195","906ac375":"265","8175d4ae":"277","356ab24d":"306","8c887cbc":"328",e82ae094:"371","7fda5be5":"378","393be207":"414","6741c1a9":"420",b2f554cd:"477","850fe7ba":"490","5a8d1bd9":"492","1be78505":"514",b2b675dd:"533","814f3328":"535","1506d638":"549","9e4087bc":"608",d0e791fc:"616",f638b2a7:"688","954aa590":"799","95f4d37c":"814","11f6a8a1":"866",fe0b396c:"878",ee91fb74:"888",e96f37e1:"909",d75a4d02:"946","0ffd5edc":"949","47fd9058":"968",af21c641:"974","6490a4b3":"994"}[e]||e,o.p+o.u(e)},(()=>{var e={303:0,532:0};o.f.j=(a,t)=>{var r=o.o(e,a)?e[a]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(303|532)$/.test(a))e[a]=0;else{var f=new Promise(((t,f)=>r=e[a]=[t,f]));t.push(r[2]=f);var d=o.p+o.u(a),c=new Error;o.l(d,(t=>{if(o.o(e,a)&&(0!==(r=e[a])&&(e[a]=void 0),r)){var f=t&&("load"===t.type?"missing":t.type),d=t&&t.target&&t.target.src;c.message="Loading chunk "+a+" failed.\n("+f+": "+d+")",c.name="ChunkLoadError",c.type=f,c.request=d,r[1](c)}}),"chunk-"+a,a)}},o.O.j=a=>0===e[a];var a=(a,t)=>{var r,f,d=t[0],c=t[1],b=t[2],n=0;if(d.some((a=>0!==e[a]))){for(r in c)o.o(c,r)&&(o.m[r]=c[r]);if(b)var i=b(o)}for(a&&a(t);n{"use strict";var e,a,t,r,d,f={},c={};function o(e){var a=c[e];if(void 0!==a)return a.exports;var t=c[e]={exports:{}};return f[e].call(t.exports,t,t.exports,o),t.exports}o.m=f,e=[],o.O=(a,t,r,d)=>{if(!t){var f=1/0;for(i=0;i=d)&&Object.keys(o.O).every((e=>o.O[e](t[b])))?t.splice(b--,1):(c=!1,d0&&e[i-1][2]>d;i--)e[i]=e[i-1];e[i]=[t,r,d]},o.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return o.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var d=Object.create(null);o.r(d);var f={};a=a||[null,t({}),t([]),t(t)];for(var c=2&r&&e;"object"==typeof c&&!~a.indexOf(c);c=t(c))Object.getOwnPropertyNames(c).forEach((a=>f[a]=()=>e[a]));return f.default=()=>e,o.d(d,f),d},o.d=(e,a)=>{for(var t in a)o.o(a,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((a,t)=>(o.f[t](e,a),a)),[])),o.u=e=>"assets/js/"+({4:"4550e3f5",14:"4a78ad51",53:"935f2afb",57:"c03baef0",85:"1f391b9e",89:"a6aa9e1f",103:"ccc49370",195:"c4f5d8e4",265:"906ac375",277:"8175d4ae",306:"356ab24d",328:"8c887cbc",371:"e82ae094",378:"7fda5be5",414:"393be207",420:"6741c1a9",477:"b2f554cd",490:"850fe7ba",492:"5a8d1bd9",514:"1be78505",533:"b2b675dd",535:"814f3328",549:"1506d638",608:"9e4087bc",616:"d0e791fc",688:"f638b2a7",799:"954aa590",814:"95f4d37c",866:"11f6a8a1",878:"fe0b396c",888:"ee91fb74",909:"e96f37e1",918:"17896441",946:"d75a4d02",949:"0ffd5edc",968:"47fd9058",974:"af21c641",994:"6490a4b3"}[e]||e)+"."+{4:"7ad67dca",14:"1e786691",53:"44caf202",57:"017eb2b2",85:"2adfe008",89:"704a8d1c",103:"8c5c8077",195:"e035c206",265:"2b12e2b9",277:"72e652da",306:"51a190b2",328:"7df3361c",371:"6e321dbd",378:"24b074e7",412:"b078622e",414:"635a33ac",420:"d133cf79",477:"55e1a35c",490:"f82eebb2",492:"48e43781",506:"9e3b469b",514:"073db173",533:"160181bc",535:"89791684",549:"2be4de9d",608:"8fa1b199",616:"4f89fef9",688:"39d0c585",799:"9b74545f",814:"76530f14",866:"489ff8b9",878:"48452069",888:"9e17b1c0",909:"8690653f",918:"340030a3",946:"fab0bb26",949:"3cd9bedc",968:"89ff45b2",972:"c3865fc5",974:"b178bb8c",994:"8dabe684"}[e]+".js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),r={},d="geogardenclub-github-io:",o.l=(e,a,t,f)=>{if(r[e])r[e].push(a);else{var c,b;if(void 0!==t)for(var n=document.getElementsByTagName("script"),i=0;i{c.onerror=c.onload=null,clearTimeout(s);var d=r[e];if(delete r[e],c.parentNode&&c.parentNode.removeChild(c),d&&d.forEach((e=>e(t))),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=l.bind(null,c.onerror),c.onload=l.bind(null,c.onload),b&&document.head.appendChild(c)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.p="/",o.gca=function(e){return e={17896441:"918","4550e3f5":"4","4a78ad51":"14","935f2afb":"53",c03baef0:"57","1f391b9e":"85",a6aa9e1f:"89",ccc49370:"103",c4f5d8e4:"195","906ac375":"265","8175d4ae":"277","356ab24d":"306","8c887cbc":"328",e82ae094:"371","7fda5be5":"378","393be207":"414","6741c1a9":"420",b2f554cd:"477","850fe7ba":"490","5a8d1bd9":"492","1be78505":"514",b2b675dd:"533","814f3328":"535","1506d638":"549","9e4087bc":"608",d0e791fc:"616",f638b2a7:"688","954aa590":"799","95f4d37c":"814","11f6a8a1":"866",fe0b396c:"878",ee91fb74:"888",e96f37e1:"909",d75a4d02:"946","0ffd5edc":"949","47fd9058":"968",af21c641:"974","6490a4b3":"994"}[e]||e,o.p+o.u(e)},(()=>{var e={303:0,532:0};o.f.j=(a,t)=>{var r=o.o(e,a)?e[a]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(303|532)$/.test(a))e[a]=0;else{var d=new Promise(((t,d)=>r=e[a]=[t,d]));t.push(r[2]=d);var f=o.p+o.u(a),c=new Error;o.l(f,(t=>{if(o.o(e,a)&&(0!==(r=e[a])&&(e[a]=void 0),r)){var d=t&&("load"===t.type?"missing":t.type),f=t&&t.target&&t.target.src;c.message="Loading chunk "+a+" failed.\n("+d+": "+f+")",c.name="ChunkLoadError",c.type=d,c.request=f,r[1](c)}}),"chunk-"+a,a)}},o.O.j=a=>0===e[a];var a=(a,t)=>{var r,d,f=t[0],c=t[1],b=t[2],n=0;if(f.some((a=>0!==e[a]))){for(r in c)o.o(c,r)&&(o.m[r]=c[r]);if(b)var i=b(o)}for(a&&a(t);n Blog | Geo Garden Club - +

· One min read

Welcome to the Geo Garden Club site!

We have decided to rebrand the "Agile Garden Club" project as "Geo Garden Club". We feel that "Geo" represents the spirit of the project better than "Agile", and besides, it's less characters to type.

- + \ No newline at end of file diff --git a/blog/2023/02/10/welcome.html b/blog/2023/02/10/welcome.html index 609033fcf..78bb57356 100644 --- a/blog/2023/02/10/welcome.html +++ b/blog/2023/02/10/welcome.html @@ -5,13 +5,13 @@ Welcome, Geo Garden Club! Aloha, Agile Garden Club! | Geo Garden Club - +

Welcome, Geo Garden Club! Aloha, Agile Garden Club!

· One min read

Welcome to the Geo Garden Club site!

We have decided to rebrand the "Agile Garden Club" project as "Geo Garden Club". We feel that "Geo" represents the spirit of the project better than "Agile", and besides, it's less characters to type.

- + \ No newline at end of file diff --git a/blog/archive.html b/blog/archive.html index 0dabf758c..c20516774 100644 --- a/blog/archive.html +++ b/blog/archive.html @@ -5,13 +5,13 @@ Archive | Geo Garden Club - + - + \ No newline at end of file diff --git a/docs/develop.html b/docs/develop.html index 369d60f87..6119afa3c 100644 --- a/docs/develop.html +++ b/docs/develop.html @@ -5,13 +5,13 @@ Welcome to the GGC Developers Guide | Geo Garden Club - +

Welcome to the GGC Developers Guide

Welcome to the Developer's Guide for the Geo Garden Club project.

Please note that the Developer's Guide consists of all pages in the /docs/develop subdirectory of the GGC website. The Developer's Guide is "partially hidden", in that these pages do not appear in the sidebar of the pages discoverable from the home page of the site, and the robots.txt file discourages crawlers from retrieving this set of pages. So, in general, to access these pages, you need to be given the URL (https://geogardenclub.com/docs/develop) by a member of the project.

That said, this hidden-ness is only partial because:

  1. If you share the URL with others, then they can access these pages. There is no access control. Please use restraint.
  2. The GitHub geogardenclub.github.io repository containing the sources for these pages is a public repository. (GGC,LLC is not yet rich enough to pay $4/seat/month for GitHub Teams.) Thus, anyone who cares to poke around in that repo can discover all of these pages.

After some thought, I have concluded that the benefits of using Docusaurus for the GGC Developer Guide outweigh the risks involved with the possibility of architectural and design information "leaking" to competitors. But, I am noting this issue up front so that everyone adding information to these pages should consider the impact of that information being exposed to outsiders at some future date.

- + \ No newline at end of file diff --git a/docs/develop/alpha-release/architecture.html b/docs/develop/alpha-release/architecture.html index 0b3a0c5ae..74649841d 100644 --- a/docs/develop/alpha-release/architecture.html +++ b/docs/develop/alpha-release/architecture.html @@ -5,13 +5,13 @@ Architecture | Geo Garden Club - +

Architecture

The alpha release currently conforms (most of the time) to the architectural approach advocated by Andreas Bizzotto which he calls the "Riverpod Architecture". If you are not familiar with this approach, it's worth spending a few minutes reading through his description, which is available as a set of readings in the architecture module in my mobile application development course.

Client-server architecture perspective

To begin, the ggc_app can be viewed as a simple client-server application: there is a central back-end server (in our case, Firestore) that communicates with front-end clients (in our case, the Flutter ggc_app application):

Layered application architecture perspective

In the above diagram, the client app is structured as four layers. This layering is strict, in that each layer communicates only with the layer above and below it.

Repository Layer. This bottom-most layer implements generic code for communication with Firebase: querying collections for documents; adding, deleting, and modifying documents, and so forth. In this layer, data is represented in JSON format (for entities) or binary format (for images).

Data Layer. The data layer implements "feature-specific" communication with Firebase. For example, the Data Layer code for the Chapter feature implements classes that queries the Chapter collection in appropriate ways. In this layer, data is still represented as JSON or binary.

Domain Layer. The domain layer implements code to translate between the data representations used at the data layer (i.e. JSON and Binary) and the data representations used at the Presentation Layer (i.e. Dart classes for entities and collections). The domain layer also implements the "business logic" of the application as discussed in the Collections and business logic section.

Presentation Layer. The presentation layer implements the Flutter-based user interface. All of the classes at the presentation layer are Widgets. GGC divides UI classes into two types: "Screens" and "Views". Screens implement a "top-level" page: they return a Scaffold Widget and can be routed to. Views are "components": they are the building blocks for Screens and can potentially appear in multiple Screens.

Directory structure perspective

There is a relatively straightforward correspondence between the above layers and the directory structure in the ggc_app repository. The top-level of the repo is more or less like any Flutter app. Here is a semi-annotated version of most of the top-level files and directories:

ggc_app/
.github/ # CI GitHub Actions
android/
assets/
ios/
lib/ # Source code here
linux/
macos/
stories/ # Monarch stories here.
test/
web/
windows/
analysis_options.yaml
build_runner.sh # Useful if you change data model.
lakos.sh # Build a diagram of the architecture
pubspec.yaml
run_monarch.sh # Run the Monarch UI Story system

The lib/ directory is where most of the action is. Here's a semi-annotated perspective of some the top-level of the lib/ directory:

lib/
features/ # Feature-based organization for Data, Domain, and Presentation layers
repositories/ # Implements the "Repository" layer
main.dart # Main entry point
router.dart # Implements routes using go_router
theme_data.dart # Implements a theme using FlexColorScheme

Finally, here's a look inside the features/ directory:

features/
authentication/ # Authentication using firebase_ui_auth.
/presentation # Implementation only requires Widgets.
chapter/ # Implementation of Chapter feature
domain/ # Chapter and ChapterCollection
data/ # Firebase interface
presentation/ # ChapterScreen, ChapterCardView, etc.
crop/
discussion/
garden/
gardener/
home/
observation/
:
drawer_view.dart # DrawerView appears in many feature Screens.
with_core_data.dart # Hide asynchronous database retrieval from UI
with_garden_data.dart # Hide asynchronous database retrieval from UI
with_monarch_data.dart # Mock the DB for Monarch.

Each feature can have one or more of the following subdirectories: domain/, data/, and presentation/. The authentication feature only requires a presentation/ subdirectory, while the chapter feature requires all three.

The features/ directory also contains a few top-level files containing "cross-cutting" code that applies to many features.

The "With" widgets

One complicated issue to address in a client-server architecture is the asynchronous nature of client-server communication. In other words, when the client needs data from the server, it makes a request that can take time to complete, and may not complete successfully. The client UI should not simply "freeze" during this time, and should "fail gracefully" if the request does not complete successfully.

Making things even more complicated is the desire for modern UIs to be "reactive". This means that if the server's database content changes (for example, one user creates a new garden), then all the other clients currently connected should see their UI automatically refresh with updated information (for example, the number of gardens in the Chapter should increase by one for all other users.)

Making things yet more complicated in GGC is the desire to support a "Storybook" style design system like Monarch, in which individual Views as well as entire Screens can be displayed with sample data values without requiring a database connection.

In the alpha release, we address all of these issues through a design pattern that starts with a set of widgets which we will call the "With" widgets. Currently, there are three: WithCoreData, WithGardenData and WithMonarchData, but there will be others (WithObservationData, WithSeedData, etc.) as we continue to build out the system.

In this design pattern, all of the data that a Widget needs to build a user interface component can always be found somewhere within three "top-level" client-side classes: ChapterCollection, GardenCollection, and UserCollection.

In addition, UI widgets come in two basic flavors, "Screens" and "Views". Screens are a kind of "top-level" UI widget which must take responsibility for building the ChapterCollection, GardenCollection, and UserCollection classes. They accomplish this by invoking a "With" widget. Views are always a "child" of a Screen widget, and must be passed ChapterCollection, GardenCollection, and UserCollection instances from their parent Widget. So, database access always happens at the Screen-level, and from then on the Views all receive locally cached data.

To support the use of Monarch, each Screen is implemented by two Widgets: the Widget that calls (for example) WithCoreData in its build method, and then invokes an "Internal" widget with populated instances of ChapterCollection, GardenCollection, and UserCollection. The two-widget structure is necessary in order to support Monarch storybooks, as will be demonstrated later below.

Let's see a simple example of the use of WithCoreData:

class ChaptersScreen extends StatelessWidget {
const ChaptersScreen({
super.key,
});


Widget build(BuildContext context) {
return WithCoreData(whenCoreData: (
{required ChapterCollection chapters,
required GardenCollection gardens,
required UserCollection users}) {
return ChaptersScreenInternal(
chapters: chapters, gardens: gardens, users: users);
});
}
}

In a nutshell, when the ChaptersScreen widget's build method is called, it will call WithCoreData. WithCoreData will retrieve the "core" Chapter, Garden, and User data from Firebase (the first time it is called during a session---after that, the local Riverpod cache of the documents will be used). Note that core data includes documents from a variety of Firebase collections, including chapters, gardens, users, crops, badges.

While this retrieval process is going on, this "With" widget will display the CircularProgressIndicator widget. Once all of the core data is successfully retrieved, then the ChaptersScreenInternal widget's build method will called and passed these fully populated collection class instances, and the intended screen UI will appear. If an error occurs during database retrieval, the "With" widget will display a generic error page.

The net effect is that the UI code is insulated from technicalities resulting from the asynchronous nature of data retrieval. It just wraps the code for the "happy path" inside a "With" widget and proceeds. For example, here's an elided version of the "Internal" widget:

class ChaptersScreenInternal extends StatelessWidget {
const ChaptersScreenInternal({
Key? key,
required this.chapters,
required this.gardens,
required this.users,
}) : super(key: key);
final ChapterCollection chapters;
final GardenCollection gardens;
final UserCollection users;


Widget build(BuildContext context) {
return Scaffold(
drawer: DrawerView(currentUser: users.currentUser),
appBar: AppBar(
title: Text('Chapters (${chapters.size()})'),
actions: [HelpButton(routeName: AppRoute.chapters.name)],
),
body: ListView(children: [...]),
bottomNavigationBar: BottomNavigationBar(...),
);
}
}

What's nice is that ChaptersScreenInternal is a StatelessWidget that gets passed three collections: Chapters, Gardens, and Users, and this is all the data that it (or any of its component Views) needs to render the Screen.

As you look through the code, you will see that Screen widgets generally follow this design pattern: a "top-level" Widget that calls WithCoreData, along with a callback that calls the corresponding "Internal" widget with the three collection classes (and potentially some other data).

WithMonarchData

The decomposition of a Screen into a top-level widget and an internal widget is an important design pattern in ggc_app because it makes it easy to implement Monarch stories. You can do this by writing a story that first calls WithMonarchData, and then calls the "Internal" widget with the collections created by WithMonarchData.

For example:

Widget showChaptersScreen() {
return WithMonarchData(whenMonarchData: (
{required ChapterCollection chapters,
required GardenCollection gardens,
required UserCollection users}) {
return ChaptersScreenInternal(
chapters: chapters, gardens: gardens, users: users);
});
}

The difference between WithCoreData and WithMonarchData is that WithCoreData builds the Chapters, Gardens, and Users collections by accessing Firestore, while WithMonarchData builds the Chapters, Gardens, and Users collections from sample data stored in the assets/monarch directory.

What makes Monarch so useful for UI development is that it makes it really easy to display a UI component in different states. For example, here is an example of displaying the Drawer UI component with data from two different users (one with a profile picture, one who does not):

Widget showDrawer() {
return WithMonarchData(whenMonarchData: (
{required ChapterCollection chapters,
required GardenCollection gardens,
required UserCollection users}) {
return DrawerView(currentUser: users.getUser('jennacorindeane@gmail.com'));
});
}

Widget showDrawer2() {
return WithMonarchData(whenMonarchData: (
{required ChapterCollection chapters,
required GardenCollection gardens,
required UserCollection users}) {
return DrawerView(currentUser: users.getUser('johnson@hawaii.edu'));
});
}

To view these two states using the emulator, you would have to login and logout multiple times.

danger

These "Screen" and "View" design patterns in ggc_app have some important constraints:

  1. Only Screen widgets call a "With" widget. All View widgets should be passed the Chapter, User, and Garden collections from their parents.
  2. Neither Screen nor View widgets call Riverpod providers. All of the Riverpod providers are called within the "With" widgets.

WithGardenData, etc

WithCoreData is responsible for retrieving "core" data, which means the data that is necessary to build the initial set of Screens that the user sees after logging in. We don't want to retrieve all of the data that the user might ever want to see immediately upon logging in, as that might require the UI to pause for several-to-many seconds, degrading the user experience. Instead, upon logging in, only the minimum "core" data is retrieved from the database so that the wait time is minimal.

Now, consider the situation where the user wants to navigate to the Garden Details screen. This screen will require (among other things) all of the Planting data associated with that specific garden. To retrieve additional data beyond the core data, we will provide additional "With" widgets, of which WithGardenData is an example.

Here's an example invocation of WithGardenData:

class GardenDetailsScreen extends StatelessWidget {
const GardenDetailsScreen({Key? key, required this.gardenID})
: super(key: key);

final String gardenID;

Widget build(BuildContext context) {
return WithGardenData(
gardenID: gardenID,
whenGardenData: (
{required ChapterCollection chapters,
required GardenCollection gardens,
required UserCollection users}) {
return GardenDetailsScreenInternal(gardenID: gardenID,
chapters: chapters, gardens: gardens, users: users);
});
}
}

Notice that WithGardenData takes two arguments, a gardenID (used to determine which garden's detailed data to retrieve), plus the standard callback that will be passed filled out instances of ChapterCollection, GardenCollection, and UserCollection. For convenience, the GardenDetailsScreenInternal widget is passed the gardenID as well.

It is important to note that "extended" With widgets like WithGardenData call WithCoreData internally, so the resulting collection instances include all of the core data, plus (in this case) the garden details data. As a result, the client code never needs to nest multiple With widgets.

Due to the wonders of Riverpod, data is cached and reactive. The user can navigate away from this garden and return to it later and the system will build the collections from local copies of the data. Even better, Riverpod will keep its local copies in sync with Firebase, so that if other users add data, the current user will see the updates when they redisplay the page.

- + \ No newline at end of file diff --git a/docs/develop/alpha-release/badges.html b/docs/develop/alpha-release/badges.html index 5b8d38c09..41731b5dc 100644 --- a/docs/develop/alpha-release/badges.html +++ b/docs/develop/alpha-release/badges.html @@ -5,13 +5,13 @@ Badges | Geo Garden Club - +
-

Badges

Goals

The alpha release will implement a badge system for gardens and gardeners. This badge system is designed to accomplish the following goals:

  1. Foster user engagement and enjoyment through a game mechanic that publicizes achievements by gardens and gardeners. Gardeners should find it fun to accumulate badges that are associated with their profile and their garden(s).
  2. Foster a community of practice by helping gardeners connect with others with similar interests and/or greater expertise with respect to a specific gardening topic. For example, if a user is interested in vermiculture, the badge system provides a mechanism for them to find other gardeners who already have experience in this area.
  3. Provide a useful, compact representation of garden and gardener characteristics. The app provides "summary" cards for gardens and gardeners. Users should find the presence (and/or absence) of badges helpful in forming a high level understanding of these entities.
  4. Provide a mechanism that identifies ways to improve gardening practices. The badge system makes visible the practices that are important to the GGC mission of food resiliency and sustainable gardening, such as seed saving, composting, and water conservation. This means that a simple heuristic for "getting better at gardening" is to simply "get more badges".
What about Chapters?

The alpha release will not implement Chapter-level badges for two reasons:

  1. The primary goals for Chapter-level badges are: (a) encouraging members of a Chapter via "peer pressure" to conform to certain best practices, and (b) making possible Chapter "leaderboards" so that Chapters can assess their capabilities relative to other chapters. Neither of these are important for the alpha release where we will focus on a single Chapter with a relatively small number of members.
  2. We will implement garden and gardener-based badge processing within each client, which is simple, scalable, and (hopefully) efficient. Implementing a Chapter-level badge system at scale will require Firebase cloud functions. These functions require specialized knowledge to implement correctly.

Design principles

Types

The alpha release will implement two types of badges: garden badges and gardener badges. Garden badges reflect the characteristics of a garden across one or more years. Gardener badges reflect characteristics of a gardener across all of the gardens with which they are associated.

Levels

Each badge can be achieved at three levels of increasing sophistication and/or expertise. Level 1 badges are relatively easy to achieve. Level 2 and Level 3 badges indicate increasing levels of expertise or accomplishment with respect to the badge subject.

Levels will be visually represented by 1-3 stars along the left side of the badge. Here`s an example:

Verification (i.e. badge processing)

Verification of badges can be done in the following ways: "via attestation", "via observation", or "via planting". Depending upon the badge and/or level, one or more of these verification approaches might be required.

"Via attestation" means that the Gardener has simply attested that they (or their garden) adheres to certain practices. This is implemented as an "Attestation" section in the Garden and Gardener forms. For example, when creating or updating a Garden, the gardener can simply check a box to attest that the garden is pesticide-free. Gardeners are on the honor system to attest only to practices that they believe to be true.

"Via Observation" requires the Gardener to post one or more Observations with one or more badge-specific tags in a single Garden.

"Via Planting" requires the Gardener to have created Planting data in a single Garden that helps to satisfy the criteria for a badge.

Alpha release badge processing is client-side only

As the above indicates, for the alpha release, badge processing occurs on the client-side, and is triggered by updates to garden, gardener, observation, or planting documents.

The current criteria are designed so that they can be assessed via either WithCoreData or WithGardenData. See the Implementation Notes section associated with each badge for an indication of which "With" widget can be used.

There are many ways we could define the criteria for a badge. The criteria we choose must align with the alpha release design constraints. If a criteria turns out to be too expensive to verify via client-side processing, then we should change the criteria, not change the design.

Observation tags

Many badges require the posting of (public) Observations to provide evidence for a specific practice. To implement badge processing, the system needs to be able to identify Observations that are intended to support achievement of a particular badge. This will be done by the user attaching one or more pre-defined tags to an Observation. Some practices (i.e. cover crops) can help the user achieve multiple badges, so the tag labels are not designed to indicate any particular badge.

The system will "take the user's word" for the appropriateness of the tags to the Observation. We hope that the public nature of these observations will prevent users from misusing this process.

Implementation hints

Badge processing has the following general implementation characteristics:

  • Triggered as part of "mutation" of Gardener, Garden, Planting, and Observation entities.
  • During submit() processing, the BadgeProcessor is called with the Garden, Chapter, and User collections, plus the entities about to be mutated. It then calls a function for each Badge, passing it this data. Each badge-specific function has its own function returns the set of BadgeInstances to be created and deleted.

Badge implementation also involves the creation of the Badges page. This page should provide a description of each Badge, the criteria required to obtain each level, and chips to indicate the Gardens (or Gardeners) that currently hold the badge.

Badge implementation will also require updates to the Garden and Gardener entities and mutation processing in order to support the various "attestation" checkboxes. Each attestation can be implemented as an optional boolean field in the Garden or Gardener entity.

Garden badges

Here are proposals for the alpha release garden badges.

Pesticide free

General Criteria

No pesticides are used in this garden.

Observation tags

N/A

LevelVerification
1a. The user has attested that the Garden is pesticide free.
b. There is Planting data for this garden for a single calendar year.
2a. The user has attested that the Garden is pesticide free.
b. There is Planting data for this garden for exactly two calendar years.
3a. The user has attested that the Garden is pesticide free.
b. There is Planting data for this garden for three or more calendar years.

Implementation notes

Triggered as part of Garden or Planting mutation.

Requires WithGardenData.

Pollinator Friendly

General Criteria

The garden has pollinator-friendly practices such as: (1) Using a wide variety of plants that bloom from early spring into late fall, (2) Avoiding modern hybrid flowers, especially those with "doubled" flowers, (3) Eliminating pesticides whenever possible, (4) Including larval host plants in your landscape, (5) Creating a damp salt lick for butterflies and bees, (6) Leaving dead trees, or at least an occasional dead limb, in order to provide essential nesting sites for native bees, and (7) Adding to nectar resources by providing a hummingbird feeder.

Observation tags

#DitchChemicals, #Habitat, #Hummingbirds, #LarvalHostPlants, #NativeBees, #NativePlants, #PesticideFree, #SaltLick.

LevelVerification
1a. There are Observations indicating at least three of the practices within a single calendar year.
2a. There are Observations indicating at least three of the practices for two calendar years.
3a. There are Observations indicating at least three of the practices for three or more calendar years.

Implementation notes

Triggered as part of Observation mutation.

Requires WithGardenData.

Sustainable Soil

General Criteria

Garden soil has been improved by using sheet mulch, compost, and/or cover crops.

Observation tags

#Compost, #CoverCrops, #SheetMulch, #Mulch, #CropRotation

LevelVerification
1a. There are Observations indicating at least three of the practices within a single calendar year.
2a. There are Observations indicating at least three of the practices for two calendar years.
3a. There are Observations indicating at least three of the practices for three or more calendar years.

Implementation notes

Triggered as part of Observation mutation.

Requires WithGardenData.

Water Smart

General Criteria

The garden involves water conservation practices, including: (1) collecting and using rainwater; (2) drip irrigation or soaker hoses, or (3) timers to water during cooler parts of day to minimize water use.

Observation tags

#DripIrrigation, #Rainwater, #WaterTimer.

LevelVerification
1a. There are Observations indicating at least one of the practices within a single calendar year.
2a. There are Observations indicating at least one of the practices for two calendar years.
3a. There are Observations indicating at least one of the practices for three or more calendar years.

Implementation notes

Triggered as part of Observation mutation.

Requires WithGardenData.

Gardener badges

Here are proposals for the alpha release Gardener badges.

Community Cultivator

General Criteria

The gardener has demonstrated experience with community and/or school gardening.

Observation tags:

N/A

LevelVerification
1a. The gardener is associated with exactly one garden which has the "Community or School Garden" attestation.
2a. The gardener is associated with exactly two gardens that have the "Community or School Garden" attestation.
3a. The gardener is associated with three or more gardens that have the "Community or School Garden" attestation.

Implementation notes

Triggered as part of Garden and Gardener mutation.

Requires WithCoreData.

Compost Champion

General Criteria

The gardener has experience composting in a gardens.

Observation tags

#Compost, #CompostTea, #Hugelkulture, #Vermiculture, #Worms.

LevelVerification
1a. The gardener has posted Observations indicating at least one of the practices for a single calendar year in a garden.
2a. The gardener has posted Observations indicating at least one of the practices for two calendar years in a single garden.
3a. The gardener has posted Observations indicating at least one of the practices for three or more calendar years in a single garden.

Implementation notes

Triggered as part of Observation mutation.

Requires WithGardenData.

Note that the gardener cannot get to levels 2 or 3 by "switching" among different gardens. The postings must be from the same garden. This means WithGardenData is enough to evaluate the criteria.

Also, the gardener must make the Observations themselves. They can't "passively" obtain the badge because someone else in the Garden made Observations with the appropriate tags.

Crop Whisperer

General Criteria

The gardener has demonstrated expertise in growing a specific crop in a single garden.

Multiple Badge Alert!

Unlike other badges, this badge is crop-specific, and so a gardener can earn multiple Crop Whisperer badges ("Bean Whisperer", "Cucumber Whisperer")

Observation tags

N/A

LevelVerification
1a. There are Plantings for exactly three different varieties of the same crop.
b. At least two outcomes were awarded at least three stars in at least one Planting.
2a. There are Plantings for exactly four different varieties of the same crop.
b. At least two outcomes were awarded at least three stars in at least one Planting.
3a. There are Plantings for at least five different varieties of the same crop.
b. At least two outcomes were awarded at least three stars in at least one Planting.

Implementation notes

Triggered as part of Planting mutation.

Requires WithGardenData.

Greenhouse grower

General Criteria

The gardener has experience growing plants successfully in a greenhouse.

Observation tags

N/A.

LevelVerification
1a. There is a single Planting in a single Garden that was started in a greenhouse that survived to harvest and was awarded at least three stars for at least one outcomes.
2a. There are two Plantings in a single Garden that were started in a greenhouse that survived to harvest and were awarded at least three stars for at least one outcomes.
3a. There are three Plantings in a single Garden that were started in a greenhouse that survived to harvest and were awarded at least three stars for at least one outcomes.

Implementation notes

Triggered as part of Planting mutation.

Requires WithGardenData.

Permaculture Pro

General Criteria

The gardener has completed a Permaculture workshop to learn about the philosophy of permaculture and is also associated with garden(s) that have achieved permaculture-related badges

Observation tags

#PesticideFree, #SustainableSoil, #WaterSmart, #PollinatorFriendly

LevelVerification
1a. The gardener has attested in their profile that they have completed a permaculture workshop.
b. There are Observations indicating at least one of the practices within a single calendar year.
2a. The gardener has attested in their profile that they have completed a permaculture workshop.
b. There are Observations indicating at least one of the practices for exactly two calendar years.
3a. The gardener has attested in their profile that they have completed a permaculture workshop.
b. There are Observations indicating at least one of the practices for three or more calendar years.

Implementation notes

Triggered as part of Garden and Observation mutations.

Requires WithGardenData.

Vermiculturalist

General Criteria

The gardener has experience with vermiculture (the controlled growing of worms) and vermicomposting (the use of worms to produce compost).

Observation tags:

#CompostTea, #Vermiculture, #Worms.

LevelVerification
1a. There are Observations indicating at least one of the practices within a single calendar year in a single Garden.
2a. There are Observations indicating at least one of the practices for two calendar years in a single Garden.
3a. There are Observations indicating at least one of the practices for three or more calendar years in a single Garden.

Implementation notes

Triggered as part of Observation mutations.

Requires WithGardenData.

Seed Saver

General Criteria

The gardener has demonstrated experience with seed saving practices, including: (1) Harvesting seeds from plants, (2) Drying seeds, (3) Storing seeds, (4) Germinating seeds, (5) Providing seeds to other members of the community.

Observation tags:

#SeedSaving, #SeedSharing

LevelVerification
1a. There are Observations indicating at least one of the practices within a single calendar year in a single Garden.
2a. There are Observations indicating at least one of the practices for two calendar years in a single Garden.
3a. There are Observations indicating at least one of the practices for three or more calendar years in a single Garden.

Implementation notes

Triggered as part of Observation mutations.

Requires WithGardenData.

Post-Alpha badges

Here are some proposals for badges that we could add after the alpha release. I have not edited these descriptions to conform to the latest design principles.

Chapter Chair

General Criteria

The gardener is serving as a Chair for the Chapter.

Note that GGC System Admins are responsible to designating which member(s) of a Chapter are the Chair(s). When they do this designation, they set a flag in the member`s profile indicating that they are currently a Chapter Chair and what date they started being Chair.

LevelVerification
1a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for one or two years.
2a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for three or four years.
3a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for five or more years.

Connected Community

General Criteria

The chapter has demonstrated a commitment to building a community of practice.

LevelVerification
1a. At least 100 gardeners in the chapter.
2a. At least 250 gardeners in the chapter.
3a. At least 500 gardeners in the chapter.

Climate Victors

General Criteria

The chapter has demonstrated a commitment to creating Climate Victory Gardens.

LevelVerification
1a. At least 50% of the chapter gardens have achieved the badge.
2a. At least 75% of the chapter gardens have achieved the badge.
3a. At least 90% of the chapter gardens have achieved the badge.

Pesticide Resistors

General Criteria

The chapter has demonstrated a commitment to avoiding the use of pesticides in their gardens.

LevelVerification
1a. At least 50% of the chapter gardens have achieved the badge.
2a. At least 75% of the chapter gardens have achieved the badge.
3a. At least 90% of the chapter gardens have achieved the badge.

Seed Sharers

General Criteria:

The chapter has demonstrated a commitment to seed sharing.

LevelVerification
1a. At least 50% of the chapter gardens have achieved the badge.
2a. At least 75% of the chapter gardens have achieved the badge.
3a. At least 90% of the chapter gardens have achieved the badge.

Climate Victory

General Criteria

A Climate Victory Garden has been added to Green America`s database and the garden implements one or more of the following practices: (1) grow food, (2) cover soils, (3) compost, (4) ditch chemicals, and (5) encourage biodiversity.

Observation tags

#Biodiversity, #Compost, #CoverCrops,#DitchChemicals, #PesticideFree, #PollinatorFriendly, #SheetMulch.

LevelVerification
1a. The user has attested that the Garden is in the Green America database.
b. There are Observations associated with this garden for at least two of the associated tags.
2a. The user has attested that the Garden is in the Green America database.
b. There are Observations associated with this garden for at least five of the associated tags.
3a. The user has attested that the Garden is in the Green America database.
b. There are Observations associated with this garden for at least five of the associated tags in at least two different calendar years.

Master gardener

General Criteria

The gardener has completed a master gardener program.

Shucks

I cannot think of a simple way to award more than one star. Ideas?

Observation tags

N/A

LevelVerification
1a. The gardener attests in their profile to having received a Master Gardener certification.
2(Not yet available)
3(Not yet available)

Bee Buddy

General Criteria

The gardener has experience caring for bees.

Observation tags

#Beekeeping, #Beekeeper

Aquaponics Ace

General Criteria

The gardener has demonstrated experience with aquaponics.

Observation tags

#Aquaponics, #FishAndPlants,

Herbalist Hero

General Criteria

The gardener has grown medicinal herbs and created remedies from them.

Observation tags:

#Herbalist, #HerbalRemedy, #PlantMedicine

Educator Extraordinaire

General Criteria

The gardener has provided educational experiences such as leading workshops, writing articles, or working as a garden educator in schools.

Observation tags:

#InspireAndTeach, #SkillSharing, #CommunityWorkshop

Orchard Orchestrator

General Criteria

The gardener has demonstrated experience with orchard management.

- +

Badges

Goals

The alpha release will implement a badge system for gardens and gardeners. This badge system is designed to accomplish the following goals:

  1. Foster user engagement and enjoyment through a game mechanic that publicizes achievements by gardens and gardeners. Gardeners should find it fun to accumulate badges that are associated with their profile and their garden(s).
  2. Foster a community of practice by helping gardeners connect with others with similar interests and/or greater expertise with respect to a specific gardening topic. For example, if a user is interested in vermiculture, the badge system provides a mechanism for them to find other gardeners who already have experience in this area.
  3. Provide a useful, compact representation of garden and gardener characteristics. The app provides "summary" cards for gardens and gardeners. Users should find the presence (and/or absence) of badges helpful in forming a high level understanding of these entities.
  4. Provide a mechanism that identifies ways to improve gardening practices. The badge system makes visible the practices that are important to the GGC mission of food resiliency and sustainable gardening, such as seed saving, composting, and water conservation. This means that a simple heuristic for "getting better at gardening" is to simply "get more badges".
What about Chapters?

The alpha release will not implement Chapter-level badges for two reasons:

  1. The primary goals for Chapter-level badges are: (a) encouraging members of a Chapter via "peer pressure" to conform to certain best practices, and (b) making possible Chapter "leaderboards" so that Chapters can assess their capabilities relative to other chapters. Neither of these are important for the alpha release where we will focus on a single Chapter with a relatively small number of members.
  2. We will implement garden and gardener-based badge processing within each client, which is simple, scalable, and (hopefully) efficient. Implementing a Chapter-level badge system at scale will require Firebase cloud functions. These functions require specialized knowledge to implement correctly.

Design principles

Types

The alpha release will implement two types of badges: garden badges and gardener badges. Garden badges reflect the characteristics of a garden across one or more years. Gardener badges reflect characteristics of a gardener across all of the gardens with which they are associated.

Levels

Each badge can be achieved at three levels of increasing sophistication and/or expertise. Level 1 badges are relatively easy to achieve. Level 2 and Level 3 badges indicate increasing levels of expertise or accomplishment with respect to the badge subject.

Levels will be visually represented by 1-3 stars along the left side of the badge. Here`s an example:

Verification (i.e. badge processing)

Verification of badges can be done in the following ways: "via attestation", "via observation", or "via planting". Depending upon the badge and/or level, one or more of these verification approaches might be required.

"Via attestation" means that the Gardener (owner) has simply attested that they (or their garden) adheres to certain practices. This is implemented as an "Attestation" section in the Garden and Gardener forms. For example, when creating or updating a Garden, the gardener (owner) can simply check a box to attest that the garden is pesticide-free. Gardeners are on the honor system to attest only to practices that they believe to be true.

"Via Observation" requires the Gardener (owner or editor) to post one or more Observations with one or more badge-specific tags in a single Garden.

"Via Planting" requires the Gardener (owner) to have created Planting data in a single Garden that helps to satisfy the criteria for a badge.

Alpha release badge processing is client-side only

As the above indicates, for the alpha release, badge processing occurs on the client-side, and is triggered by updates to garden, gardener, observation, or planting documents.

The current criteria are designed so that they can be assessed via either WithCoreData or WithGardenData. See the Implementation Notes section associated with each badge for an indication of which "With" widget can be used.

There are many ways we could define the criteria for a badge. The criteria we choose must align with the alpha release design constraints. If a criteria turns out to be too expensive to verify via client-side processing, then we should change the criteria, not change the design.

Observation tags

Many badges require the posting of (public) Observations to provide evidence for a specific practice. To implement badge processing, the system needs to be able to identify Observations that are intended to support achievement of a particular badge. This will be done by the user attaching one or more pre-defined tags to an Observation. Some practices (i.e. cover crops) can help the user achieve multiple badges, so the tag labels are not designed to indicate any particular badge.

The system will "take the user's word" for the appropriateness of the tags to the Observation. We hope that the public nature of these observations will prevent users from misusing this process.

Implementation hints

Badge processing has the following general implementation characteristics:

  • Triggered as part of "mutation" of Gardener, Garden, Planting, and Observation entities.
  • During submit() processing, the BadgeProcessor is called with the Garden, Chapter, and User collections, plus the entities about to be mutated. It then calls a function for each Badge, passing it this data. Each badge-specific function has its own function returns the set of BadgeInstances to be created and deleted.

Badge implementation also involves the creation of the Badges page. This page should provide a description of each Badge, the criteria required to obtain each level, and chips to indicate the Gardens (or Gardeners) that currently hold the badge.

Badge implementation will also require updates to the Garden and Gardener entities and mutation processing in order to support the various "attestation" checkboxes. Each attestation can be implemented as an optional boolean field in the Garden or Gardener entity.

Garden badges

Here are proposals for the alpha release garden badges.

Pesticide free

General Criteria

No pesticides are used in this garden.

Observation tags

N/A

LevelVerification
1a. The user has attested that the Garden is pesticide free.
b. There is Planting data for this garden for a single calendar year.
2a. The user has attested that the Garden is pesticide free.
b. There is Planting data for this garden for exactly two calendar years.
3a. The user has attested that the Garden is pesticide free.
b. There is Planting data for this garden for three or more calendar years.

Implementation notes

Triggered as part of Garden or Planting mutation.

Requires WithGardenData.

Pollinator Friendly

General Criteria

The garden has pollinator-friendly practices such as: (1) Using a wide variety of plants that bloom from early spring into late fall, (2) Avoiding modern hybrid flowers, especially those with "doubled" flowers, (3) Eliminating pesticides whenever possible, (4) Including larval host plants in your landscape, (5) Creating a damp salt lick for butterflies and bees, (6) Leaving dead trees, or at least an occasional dead limb, in order to provide essential nesting sites for native bees, and (7) Adding to nectar resources by providing a hummingbird feeder.

Observation tags

#DitchChemicals, #Habitat, #Hummingbirds, #LarvalHostPlants, #NativeBees, #NativePlants, #PesticideFree, #SaltLick.

LevelVerification
1a. There are Observations indicating at least three of the practices within a single calendar year.
2a. There are Observations indicating at least three of the practices for two calendar years.
3a. There are Observations indicating at least three of the practices for three or more calendar years.

Implementation notes

Triggered as part of Observation mutation.

Requires WithGardenData.

Sustainable Soil

General Criteria

Garden soil has been improved by using sheet mulch, compost, and/or cover crops.

Observation tags

#Compost, #CoverCrops, #SheetMulch, #Mulch, #CropRotation

LevelVerification
1a. There are Observations indicating at least three of the practices within a single calendar year.
2a. There are Observations indicating at least three of the practices for two calendar years.
3a. There are Observations indicating at least three of the practices for three or more calendar years.

Implementation notes

Triggered as part of Observation mutation.

Requires WithGardenData.

Water Smart

General Criteria

The garden involves water conservation practices, including: (1) collecting and using rainwater; (2) drip irrigation or soaker hoses, or (3) timers to water during cooler parts of day to minimize water use.

Observation tags

#DripIrrigation, #Rainwater, #WaterTimer.

LevelVerification
1a. There are Observations indicating at least one of the practices within a single calendar year.
2a. There are Observations indicating at least one of the practices for two calendar years.
3a. There are Observations indicating at least one of the practices for three or more calendar years.

Implementation notes

Triggered as part of Observation mutation.

Requires WithGardenData.

Gardener badges

Here are proposals for the alpha release Gardener badges.

Community Cultivator

General Criteria

The gardener has demonstrated experience with community and/or school gardening.

Observation tags:

N/A

LevelVerification
1a. The gardener is associated with exactly one garden which has the "Community or School Garden" attestation.
2a. The gardener is associated with exactly two gardens that have the "Community or School Garden" attestation.
3a. The gardener is associated with three or more gardens that have the "Community or School Garden" attestation.

Implementation notes

Triggered as part of Garden and Gardener mutation.

Requires WithCoreData.

Compost Champion

General Criteria

The gardener has experience composting in a gardens.

Observation tags

#Compost, #CompostTea, #Hugelkulture, #Vermiculture, #Worms.

LevelVerification
1a. The gardener (owner or editor) has posted Observations indicating at least one of the practices for a single calendar year in a garden.
2a. The gardener (owner or editor) has posted Observations indicating at least one of the practices for two calendar years in a single garden.
3a. The gardener (owner or editor) has posted Observations indicating at least one of the practices for three or more calendar years in a single garden.

Implementation notes

Triggered as part of Observation mutation.

Requires WithGardenData.

Note that the gardener cannot get to levels 2 or 3 by "switching" among different gardens. The postings must be from the same garden. This means WithGardenData is enough to evaluate the criteria.

Also, the gardener must make the Observations themselves. They can't "passively" obtain the badge because someone else in the Garden made Observations with the appropriate tags.

Crop Whisperer

General Criteria

The gardener has demonstrated expertise in growing a specific crop in a single garden.

Multiple Badge Alert!

Unlike other badges, this badge is crop-specific, and so a gardener can earn multiple Crop Whisperer badges ("Bean Whisperer", "Cucumber Whisperer")

Observation tags

N/A

LevelVerification
1a. There are Plantings for exactly three different varieties of the same crop.
b. At least two outcomes were awarded at least three stars in at least one Planting.
2a. There are Plantings for exactly four different varieties of the same crop.
b. At least two outcomes were awarded at least three stars in at least one Planting.
3a. There are Plantings for at least five different varieties of the same crop.
b. At least two outcomes were awarded at least three stars in at least one Planting.

Implementation notes

Triggered as part of Planting mutation.

Requires WithGardenData.

Greenhouse grower

General Criteria

The gardener has experience growing plants successfully in a greenhouse.

Observation tags

N/A.

LevelVerification
1a. There is a single Planting in a single Garden that was started in a greenhouse that survived to harvest and was awarded at least three stars for at least one outcomes.
2a. There are two Plantings in a single Garden that were started in a greenhouse that survived to harvest and were awarded at least three stars for at least one outcomes.
3a. There are three Plantings in a single Garden that were started in a greenhouse that survived to harvest and were awarded at least three stars for at least one outcomes.

Implementation notes

Triggered as part of Planting mutation.

Requires WithGardenData.

Permaculture Pro

General Criteria

The gardener has completed a Permaculture workshop to learn about the philosophy of permaculture and is also associated with garden(s) that have achieved permaculture-related badges

Observation tags

#PesticideFree, #SustainableSoil, #WaterSmart, #PollinatorFriendly

LevelVerification
1a. The gardener (owner or editor) has attested in their profile that they have completed a permaculture workshop.
b. There are Observations indicating at least one of the practices within a single calendar year.
2a. The gardener (owner or editor) has attested in their profile that they have completed a permaculture workshop.
b. There are Observations indicating at least one of the practices for exactly two calendar years.
3a. The gardener (owner or editor) has attested in their profile that they have completed a permaculture workshop.
b. There are Observations indicating at least one of the practices for three or more calendar years.

Implementation notes

Triggered as part of Garden and Observation mutations.

Requires WithGardenData.

Vermiculturalist

General Criteria

The gardener has experience with vermiculture (the controlled growing of worms) and vermicomposting (the use of worms to produce compost).

Observation tags:

#CompostTea, #Vermiculture, #Worms.

LevelVerification
1a. There are Observations indicating at least one of the practices within a single calendar year in a single Garden.
2a. There are Observations indicating at least one of the practices for two calendar years in a single Garden.
3a. There are Observations indicating at least one of the practices for three or more calendar years in a single Garden.

Implementation notes

Triggered as part of Observation mutations.

Requires WithGardenData.

Seed Saver

General Criteria

The gardener has demonstrated experience with seed saving practices, including: (1) Harvesting seeds from plants, (2) Drying seeds, (3) Storing seeds, (4) Germinating seeds, (5) Providing seeds to other members of the community.

Observation tags:

#SeedSaving, #SeedSharing

LevelVerification
1a. There are Observations indicating at least one of the practices within a single calendar year in a single Garden.
2a. There are Observations indicating at least one of the practices for two calendar years in a single Garden.
3a. There are Observations indicating at least one of the practices for three or more calendar years in a single Garden.

Implementation notes

Triggered as part of Observation mutations.

Requires WithGardenData.

Post-Alpha badges

Here are some proposals for badges that we could add after the alpha release. I have not edited these descriptions to conform to the latest design principles.

Chapter Chair

General Criteria

The gardener is serving as a Chair for the Chapter.

Note that GGC System Admins are responsible to designating which member(s) of a Chapter are the Chair(s). When they do this designation, they set a flag in the member`s profile indicating that they are currently a Chapter Chair and what date they started being Chair.

LevelVerification
1a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for one or two years.
2a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for three or four years.
3a. The gardener is currently the Chapter Chair, and has served as a Chapter Chair for five or more years.

Connected Community

General Criteria

The chapter has demonstrated a commitment to building a community of practice.

LevelVerification
1a. At least 100 gardeners in the chapter.
2a. At least 250 gardeners in the chapter.
3a. At least 500 gardeners in the chapter.

Climate Victors

General Criteria

The chapter has demonstrated a commitment to creating Climate Victory Gardens.

LevelVerification
1a. At least 50% of the chapter gardens have achieved the badge.
2a. At least 75% of the chapter gardens have achieved the badge.
3a. At least 90% of the chapter gardens have achieved the badge.

Pesticide Resistors

General Criteria

The chapter has demonstrated a commitment to avoiding the use of pesticides in their gardens.

LevelVerification
1a. At least 50% of the chapter gardens have achieved the badge.
2a. At least 75% of the chapter gardens have achieved the badge.
3a. At least 90% of the chapter gardens have achieved the badge.

Seed Sharers

General Criteria:

The chapter has demonstrated a commitment to seed sharing.

LevelVerification
1a. At least 50% of the chapter gardens have achieved the badge.
2a. At least 75% of the chapter gardens have achieved the badge.
3a. At least 90% of the chapter gardens have achieved the badge.

Climate Victory

General Criteria

A Climate Victory Garden has been added to Green America`s database and the garden implements one or more of the following practices: (1) grow food, (2) cover soils, (3) compost, (4) ditch chemicals, and (5) encourage biodiversity.

Observation tags

#Biodiversity, #Compost, #CoverCrops,#DitchChemicals, #PesticideFree, #PollinatorFriendly, #SheetMulch.

LevelVerification
1a. The user has attested that the Garden is in the Green America database.
b. There are Observations associated with this garden for at least two of the associated tags.
2a. The user has attested that the Garden is in the Green America database.
b. There are Observations associated with this garden for at least five of the associated tags.
3a. The user has attested that the Garden is in the Green America database.
b. There are Observations associated with this garden for at least five of the associated tags in at least two different calendar years.

Master gardener

General Criteria

The gardener has completed a master gardener program.

Shucks

I cannot think of a simple way to award more than one star. Ideas?

Observation tags

N/A

LevelVerification
1a. The gardener attests in their profile to having received a Master Gardener certification.
2(Not yet available)
3(Not yet available)

Bee Buddy

General Criteria

The gardener has experience caring for bees.

Observation tags

#Beekeeping, #Beekeeper

Aquaponics Ace

General Criteria

The gardener has demonstrated experience with aquaponics.

Observation tags

#Aquaponics, #FishAndPlants,

Herbalist Hero

General Criteria

The gardener has grown medicinal herbs and created remedies from them.

Observation tags:

#Herbalist, #HerbalRemedy, #PlantMedicine

Educator Extraordinaire

General Criteria

The gardener has provided educational experiences such as leading workshops, writing articles, or working as a garden educator in schools.

Observation tags:

#InspireAndTeach, #SkillSharing, #CommunityWorkshop

Orchard Orchestrator

General Criteria

The gardener has demonstrated experience with orchard management.

+ \ No newline at end of file diff --git a/docs/develop/alpha-release/coding-standards.html b/docs/develop/alpha-release/coding-standards.html index c29f377f9..5a41d45a6 100644 --- a/docs/develop/alpha-release/coding-standards.html +++ b/docs/develop/alpha-release/coding-standards.html @@ -5,13 +5,13 @@ Coding Standards | Geo Garden Club - +

Coding Standards

This page documents our evolving set of best practices for GGC development.

Coding standards apply to main branch only

The following standards apply only to code that you are about to merge into the main branch. You may want to violate these standards temporarily during initial development of a feature in your non-main branch. That's OK.

Delete debugging/unused code

Often during development, you will insert debugging statements to help diagnose a problem. For example:

home_screen_observations_view.dart
 Widget build(BuildContext context) {
// logger.d('HomeScreenObservationsView.build $chapters');
// logger.d('HomeScreenObservationsView.build ${chapters.observations}');
// logger.d('HomeScreenObservationsView.build ${chapters.observations.size()}');
// logger.d('current user: ${users.currentUser}');
// logger.d('current gardener: ${gardens.gardeners.getGardener(users.currentUserID)}');
// List<String> chapterNames = chapters.getChapterNames();
List<Observation> observations = chapters.observations.getAllObservations();

Or, you might try one way to implement a feature, but eventually decide upon another way. For example:

home_screen_observations_view.dart
child: ListView(
// children: observations
// .map((observation) => InstagramCard(
// observation: observation,
// chapterName: chapterNames[0],
// observations: chapters.observations,
// tags: chapters.tags,
// users: users))
// .toList()));
children: observations.map((observation) => ObservationCard(observation: observation, chapters: chapters, gardens: gardens, users: users)).toList()));

Rather than comment out debugging or unused code, please delete it prior to merging into main. Deleting this code improves the signal-to-noise ratio for future readers. In the case of debugging statements, it is easy to re-insert them later if needed (and often, you will want to inspect different values later, so the commented lines aren't helpful).

If you are concerned about deleting potentially valuable code, then feel free to copy the file into the graveyard/ directory prior to deleting the commented out code.

I can think of one possible exception to this rule: you are debating between two alternative implementations, and you want others to experiment by commenting out one alternative and then the other. But in all the cases I can think of, we have decided these kinds of issues via screen shots rather than code.

Don't inline multi-statement callbacks

We want to keep code modular and avoid deeply indented code. Deeply indented code is more difficult to read, and (because it's deeply indented) more cognitively demanding to understand.

One good heuristic to avoid deeply indented code is to not inline multi-line callbacks. For example, consider the following implementation of a PopupMenuButton:

task_card.dart
                PopupMenuButton(
initialValue: _selectedMenu,
onSelected: (SampleItem result) {
setState(() {
_selectedMenu = result;
});
switch (result) {
case SampleItem.editTask:
context.pushNamed(AppRoute.editTask.name,
pathParameters: {'taskID': widget.task.taskID});
break;
case SampleItem.gardenDetails:
context.goNamed(AppRoute.gardenDetails.name,
pathParameters: {
'gardenID': widget.task.gardenID
});
break;
// case SampleItem.editPlanting:
// context.pushNamed(AppRoute.planting.name,
// pathParameters: {
// 'gardenID': widget.task.gardenID,
// 'plantingID': widget.task.plantingID
// });
// break;
}
},
itemBuilder: (BuildContext context) => popupMenuItems),

In this case, some of the code is indented 34 spaces, using up almost half of the allotted 80 character line width.

To avoid this situation, notice that the onSelected: argument is an inline callback, which could be easily rewritten as a local function:

void _onSelected(SampleItem result) {
setState(() => _selectedMenu = result);
switch (result) {
case SampleItem.editTask:
context.pushNamed(AppRoute.editTask.name,
pathParameters: {'taskID': widget.task.taskID});
break;
case SampleItem.gardenDetails:
context.goNamed(AppRoute.gardenDetails.name,
pathParameters: {'gardenID': widget.task.gardenID});
break;
}
}

And then provided as the callback value as follows:

                  PopupMenuButton(
initialValue: _selectedMenu,
onSelected: _onSelected,
itemBuilder: (BuildContext context) => popupMenuItems),

This rewrite makes it easier to understand the PopupMenuButton invocation (because it is now only four lines long) as well as the onSelected callback (because it now has access to almost the full 80 character line width).

This PopupMenuButton code snippet is also useful because it illustrates the situation in which inlining a callback is appropriate! This is when the callback is a one-liner, such as the argument to the itemBuilder: parameter.

Don't inline form field definitions

Another situation that often leads to deeply indented code is when form field definitions are inline. For example, consider the first 30 lines of this call to FormBuilder:

add_outcome_screen.dart
                       FormBuilder(
child: Column(
children: [
Text('Outcome for $plantingName.',
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 5),
const Text(
'Please rate the following on a scale of 1 to 5, with 5 being the best.'),
const SizedBox(height: 10),
FormBuilderSlider(
key: _germinationFieldKey,
name: 'Germination',
min: 1,
max: 5,
divisions: 4,
decoration: ggcInputDecoration(
label: 'Germination',
required: true,
hintText: ''),
initialValue: _germinationValue,
valueTransformer: (value) {
return value!.toInt();
},
onChanged: (value) {
setState(() {
_germinationValue = value!;
});
},
),

This is hard to read due to all the indentation, and it also has the potential to create very long form definitions. In this case, the complete call to FormBuilder is 200 lines long!

To create more readable code, and also to create opportunities for reuse, define form fields as widgets in the lib/common/input-fields directory. For example, here is the definition for a text field that allows the user to name (or rename) their garden:

garden_text_field.dart
class GardenTextField extends StatelessWidget {
const GardenTextField(
{super.key, required this.gardens, this.onTap, this.currName});

final GardenCollection gardens;
final void Function(String value)? onTap;
final String? currName;


Widget build(BuildContext context) {
String fieldName = 'Garden Name';
return FieldPadding(
child: FormBuilderTextField(
name: fieldName,
key: FieldKey.gardenTextField,
decoration: ggcInputDecoration(
label: fieldName,
required: true,
hintText: '4-20 chars, alphanumeric/spaces, unique',
),
initialValue: currName,
validator: FormBuilderValidators.compose([
GgcValidators.validName(),
GgcValidators.uniqueGardenName(gardens, currName)
])),
);
}
}

Now you can use it in a call to FormBuilder, where the total number of lines in the definition will typically be only a few more than the total number of fields:

add_garden_screen.dart
  FormBuilder(
key: _formKey,
child: Column(
children: [
GardenTextField(gardens: widget.gardens),
const SingleImagePicker(required: false),
EditorsTextField(users: widget.users),
FormButtons(onSubmit: onSubmit, onCancel: onCancel),
],
),
);

As a bonus, GardenTextField is used in both the AddGarden form and the EditGarden Form, which avoids duplicate code.

Also note that the onSubmit: and onCancel: callbacks are not inlined, conforming to the prior coding standard.

Avoid deep indentation

The prior two coding standards should significantly reduce the depth of indentation, but there may be other situations which result in deeply indented code.

As a heuristic, if indentation exceeds 5 or 6 levels, think about creating local functions to encapsulate semantically meaningful units of functionality, and then invoking them instead of inlining all of the code.

Use AsyncValue

When your code interacts with the database (or some other external service), you have two choices:

  1. Use our with widgets to retrieve the appropriate data. This choice is appropriate when your goal is to provide read-only access to data that resides in the database.
  2. Write asynchronous code to manipulate the contents of the database (i.e. when your goal is to update or delete data in the database).

The Flutterverse is filled with articles and example code on how to accomplish (2). For GGC, we will use the "Riverpod" approach, which involves:

  1. Define a Riverpod provider (using the @riverpod annotation) to perform the manipulation.
  2. Handle the resulting AsyncValue's three possible states: loading, error, data.

There is a non-trivial learning curve associated with this approach, but now that I've done it, I am confident that the return is worth the investment. Aside from a straightforward way to handle the three asynchronous states, Riverpod providers also provide caching, which can sometimes be very helpful.

It is hard to provide simple "before and after" code samples, because the situations involving data manipulation are so diverse. So, instead, here are some hopefully useful readings to get you started:

One thing not covered in these readings is how to handle the situation where you need to make multiple asynchronous calls. In the long term, we will probably want to implement Firestore transactions when relevant. In the short term, the AllData provider illustrates a design pattern for making multiple asynchronous requests and providing a single AsyncValue to the caller representing loading (i.e. at least one of the calls have not completed), error (for at least one of the calls), and success (all calls completed successfully). What AllData doesn't do is "roll back" when some of the calls succeed and others fail. But that's probably OK for the alpha release.

Observe GGC data mutation design pattern

"Data mutation" refers to creating, updating, and deleting entities from the database. In some cases, mutating one entity (i.e. deleting a Garden) requires the implicit mutation of many other entities (i.e. deleting the Garden's associated Beds, Plantings, Observations, Outcomes, and Tasks).

Accomplishing a data mutation involves a complex interaction between the front-end user interface and the back-end database. There are many potential ways to accomplish this interaction, but we will follow a design pattern documented by Andrea Bizzotti in his various Code With Andrea tutorials, with some additional customizations to suit our own GGC architecture.

At the time of writing, these design patterns have been implemented only for creating, updating, and deleting Garden entities. The CreateGardenScreen and MutateGardenController classes currently illustrate our data mutation design pattern.

Here is a walkthrough of some of the Garden code to illustrate the basic ideas of this design pattern.

1. The data mutation widget

A "Data mutation widget" (for example, UpdateGardenScreen) presents a user interface for performing a data mutation. The actual UI component displayed at any moment in time by the widget is determined by an associated controller (for example, MutateGardenController). The controller indicates which of four UI components to present: (1) an initial UI component (typically a form), (2) a loading indicator UI component (while waiting for an asynchronous action to complete, (3) a "success" component (displayed if the asynchronous action completes successfully) or (4) an error UI component (displayed if the asynchronous completes with an error).

Here's an excerpt of UpdateGardenScreen illustrating the basic way in which the controller controls the UI state of the screen:

lib/features/garden/presentation/update_garden_screen.dart
  AsyncValue asyncUpdate = ref.watch(mutateGardenControllerProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Update Garden'),
actions: [HelpButton(routeName: AppRoute.updateGarden.name)],
),
body: asyncUpdate.when(
data: (_) => updateGardenForm(),
loading: () => const GgcLoadingIndicator(),
error: (e, st) => GgcError(e.toString(), st.toString())));
}

2. The onSubmit() method

If the initial UI component is a form, then it should have an async onSubmit() callback method. This method typically involves a sequence of three phases. The first phase checks that the form field values pass any validation criteria. If so, the second phase creates domain model entities as indicated by the form values. The third phase calls the appropriate mutate controller method, passing it the domain entities and an onSuccess() callback, which tells the controller which page to go to if the data mutation is successful.

Here's an example:

lib/features/garden/presentation/edit_garden_screen.dart
   onSubmit() async {
// 1. Check that form fields are valid.
bool isValid = _formKey.currentState?.saveAndValidate() ?? false;
if (!isValid) return;
// 2. Create domain objects to send to controller.
String name = FieldKey.gardenTextField.currentState?.value;
List<dynamic> xFiles =
FieldKey.singleImagePicker.currentState?.value ?? [];
String editorsString =
FieldKey.editorsTextField.currentState?.value ?? '';
Garden garden = gardens.getGarden(gardenID);
List<String> updatedEditorUserIDs = users.parseUsernames(editorsString);
List<Editor> editorsToAdd = gardens.editors.makeNewEditors(
gardenID: gardenID,
chapterID: garden.chapterID,
gardenerIDs: updatedEditorUserIDs);
List<Editor> editorsToDelete = gardens.editors.getEditors(gardenID);
// Only update Editors collection if the field has changed.
if (gardens.editors.sameEditorList(editorsToAdd, editorsToDelete)) {
editorsToAdd = [];
editorsToDelete = [];
}
String profilePictureUrl = (xFiles.isNotEmpty && xFiles[0] is XFile)
? await ImageStorage.cropAndUploadImage(
xFile: xFiles[0], entityID: gardenID, context: context)
: garden.profilePicture;
Garden updatedGarden = Garden(
gardenID: gardenID,
name: name,
profilePicture: profilePictureUrl,
chapterID: chapters.currentChapterID,
cropIDs: garden.cropIDs,
sharedSeedIDs: garden.sharedSeedIDs,
lastUpdate: DateTime.now(),
ownerID: users.currentUserID,
pictures: []);
// 3. Use controller to invoke updates on database.
ref.read(mutateGardenControllerProvider.notifier).updateGarden(
garden: updatedGarden,
editorsToAdd: editorsToAdd,
editorsToDelete: editorsToDelete,
onSuccess: () {
context.pop();
GlobalSnackBar.show('Garden "$name" updated.');
},
);
}
Don't pass Collection classes to the controller method

To maintain separation of concerns, the values passed to mutate controller methods should be individual domain entities (i.e. Garden, Editor), lists of domain entities (i.e. List<Garden>, List<Editor>), or primitive types (String, int, etc). Don't pass collections (i.e. GardenCollection, EditorCollection). Use these collection classes within the onSubmit() method to determine the domain entities to pass.

3. Mutate controller create, update, delete methods

The Mutate Controller class typically implements create, update, and delete methods to handle the associated mutation. These methods will often need to make multiple asynchronous calls to the backend database. To do this efficiently, and also to provide atomicity, the controller should use the Firestore batched write facility.

Here is an example from MutateGardenController for creating a new Garden. Note that both the Garden and Editor databases are mutated:

lib/features/garden/presentation/mutate_garden_controller.dart
 Future<void> createGarden({
required Garden garden,
required List<Editor> editors,
required VoidCallback onSuccess,
}) async {
state = const AsyncLoading();
AsyncValue nextState = const AsyncLoading();
GardenDatabase gardenDatabase = ref.watch(gardenDatabaseProvider);
EditorDatabase editorDatabase = ref.watch(editorDatabaseProvider);
final WriteBatch batch = FirebaseFirestore.instance.batch();
gardenDatabase.setGardenBatch(batch, garden);
editorDatabase.addEditorsBatch(batch, editors);
await batch
.commit()
.then((_) => nextState = const AsyncValue.data(null))
.catchError((e, st) => nextState = AsyncValue.error(e, st));
if (mounted) {
state = nextState;
}
if (!state.hasError) {
onSuccess();
}
}

Following the CodeWithAndrea guidelines, this method first sets the controller state to AsyncLoading. Then it gets the databases of interest, creates a batch variable, and adds mutations to that batch variable by passing it into the appropriate methods in the variable database classes. Finally, it invokes the batch.commit() method to do all of the mutations at once, and either sets the state to AsyncData() if everything went well or AsyncError() if a problem occurred. A nice feature of batched writes is that they are performed as a transaction---either all of the writes succeed, or none of them do.

4. Database methods

The final part of this coding standard involves the appropriate definition of database methods. As shown above, database methods should be written to accept a batch parameter, and result in that parameter being updated with additional operations to perform. Here is an example:

"lib/features/garden/data/editor_database.dart
 void createEditorsBatch(WriteBatch batch, List<Editor> editors) {
for (Editor editor in editors) {
_service.setDataBatch(
batch: batch,
path: FirestorePath.editor(editor.editorID),
data: editor.toJson());
}
}

A template for the controller class

There is some boilerplate code for controllers. To make it a little easier to create new controllers, here is a template. See the TODO comments for places where code needs to be added, and replace all occurrences of "TEMPLATE" by the entity being controller (i.e. Garden, User, Task, etc).

Note that we'll use "create" rather than "add" to conform to the CRUD acronym. This means that the associated screens should be changed from "AddX" to "CreateX".

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'mutate_TEMPLATE_controller.g.dart';


class MutateTEMPLATEController extends _$MutateTEMPLATEController {
bool mounted = true;


FutureOr<void> build() {
ref.onDispose(() => mounted = false);
state = const AsyncData(null);
}

Future<void> createTEMPLATE({
/// TODO: Pass in domain object here.
required VoidCallback onSuccess,
}) async {
state = const AsyncLoading();
AsyncValue nextState = const AsyncLoading();
// TODO: Watch the appropriate database instances here.
final WriteBatch batch = FirebaseFirestore.instance.batch();
// TODO: Invoke the database batch methods here.
await batch
.commit()
.then((_) => nextState = const AsyncValue.data(null))
.catchError((e, st) => nextState = AsyncValue.error(e, st));
if (mounted) {
state = nextState;
}
if (!state.hasError) {
onSuccess();
}
}


Future<void> updateTEMPLATE({
/// TODO: Pass in domain data here
required VoidCallback onSuccess,
}) async {
state = const AsyncLoading();
AsyncValue nextState = const AsyncLoading();
/// TODO: ref.watch the appropriate databases here.
final WriteBatch batch = FirebaseFirestore.instance.batch();
/// TODO: Invoke the appropriate database batch methods here.
await batch
.commit()
.then((_) => nextState = const AsyncValue.data(null))
.catchError((e, st) => nextState = AsyncValue.error(e, st));
if (mounted) {
state = nextState;
}
if (!state.hasError) {
onSuccess();
}
}

Future<void> deleteTEMPLATE({
/// TODO: Pass in the appropriate domain objects here
required VoidCallback onSuccess,
}) async {
state = const AsyncLoading();
AsyncValue nextState = const AsyncLoading();
/// TODO: Watch the appropriate databases here.
final WriteBatch batch = FirebaseFirestore.instance.batch();
/// TODO: Invoke the appropriate database batch methods here.
await batch
.commit()
.then((_) => nextState = const AsyncValue.data(null))
.catchError((e, st) => nextState = AsyncValue.error(e, st));
if (mounted) {
state = nextState;
}
if (!state.hasError) {
onSuccess();
}
}
}
Caveats and gotchas

This design pattern is "fresh off the presses", which reveals a few issues:

  1. Batched writes are limited to 500 operations. Our current database organization will result in exceeding that limit for gardens of reasonable size (i.e. hundreds of plantings). This means we really need to reorganize the database to use subcollections. Then, for example, deleting a garden will delete all of its associated plantings in one batch operation.
  2. Collection classes shouldn't access the database methods at all. We should remove those methods.
  3. Remove database fields from collection classes. We should access databases using Riverpod provider variables.
  4. Database methods should return Futures, and not implement then() or catchError() clauses.
  5. WithGarden now provides access to Observations, Tasks, and Outcomes. The "extended" WithGarden widgets might no longer be necessary.

There are a set of issues labeled "Technical Debt" that divide up the work of implementing this design pattern into discrete chunks of work.

Don't write media-adaptive code

For the alpha release, we are not going to optimize layout for different screen sizes. So, please do not (for example) use MediaQuery to adjust values for different screen sizes. For example:

double width = MediaQuery.of(context).size.width;
if (!widget.readOnly) {
// compensate for the checkbox
width = width - 50;
}
if (width > 400) {
// horizontal mode so remove more.
width = width - 110;
}

The reason for this is to avoid: (a) investing time into writing code that we might abandon later once we decide on a comprehensive approach to screen-dependent layout, and (b) an inconsistent UI that is sometimes adaptive and sometimes not.

For more information on this issue, see:

If you need to adjust the screen size for some other reason, that's OK.

Use named routes

Use named routing. For example, write this:

onPressed: () =>
context.pushNamed(AppRoute.editObservation.name, pathParameters: {'observationID': widget.observation.observationID, 'gardenID': widget.observation.gardenID}),

Not this:

onPressed: () =>
context.push('/editObservation/${widget.observation.observationID}/${widget.observation.gardenID}');

The reason is that if you change the path, you will have to change all the links to that path. If you use named routing, you only have to change the path in one place.

Prefer widgets to helper methods

It is possible to create "helper" functions that return widgets, such as:

lib/common/functions/make_fab.dart
FloatingActionButton makeFAB(String route, BuildContext context) {
return FloatingActionButton(
onPressed: () {
context.pushNamed(route);
},
child: const Icon(Icons.add),
);
}

FloatingActionButton makeFABWithParameters(
String route, Map<String, String> pathParameters, BuildContext context) {
return FloatingActionButton(
onPressed: () {
context.pushNamed(route, pathParameters: pathParameters);
},
child: const Icon(Icons.add),
);
}

There are several reasons why it is better to create widgets than helper methods, as is explained here:

In this case, here's what the stateless widget version would look like:

lib/common/widgets/ggc_fab.dart
class GgcFAB extends StatelessWidget {
const GgcFAB(
{super.key, required this.route, this.pathParameters = const {}});

final String route;
final Map<String, String> pathParameters;


Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
context.pushNamed(route, pathParameters: pathParameters);
},
child: const Icon(Icons.add),
);
}
}

The following code illustrates the very minimal differences in how they are called:

  Widget? getFloatingActionButton(BuildContext context, int selectedIndex) {
if (selectedIndex == 0) {
return GgcFAB(route: AppRoute.createPlanting.name);
}
if (selectedIndex == 3) {
return makeFABWithParameters(AppRoute.createGardenTask.name,
{'gardenID': widget.gardenID}, context);
}
return null;
}

It gets a little nicer if you convert to the stateless widget approach entirely, since you can tighten up the return type and remove the context argument:

  GgcFAB? getFloatingActionButton(int selectedIndex) {
if (selectedIndex == 0) {
return GgcFAB(route: AppRoute.createPlanting.name);
}
if (selectedIndex == 3) {
return GgcFAB(route: AppRoute.createGardenTask.name,
pathParameters: {'gardenID': widget.gardenID});
}
return null;
}

Don't repeat titles

The title should appear in the scaffold. It does not need to be repeated in the body:

Prefer late to dummy field values

Sometimes you need to create an entity that has required fields before you know what those fields are. It is tempting to create a "dummy" entity with clearly incorrect values and then overwrite the fields once you know what the correct values are. For example, here's some code from TaskCard:

    Planting updatedPlanting = Planting(
plantingID: 'plantingID',
chapterID: 'chapterID',
gardenID: 'gardenID',
cropID: 'cropID',
cropName: 'cropName',
lastUpdate: DateTime.now());
switch (task.taskType) {
case 'sow':
updatedPlanting = planting.copyWith(
startDate: completedDate, lastUpdate: DateTime.now());
break;
case 'transplant':
updatedPlanting = planting.copyWith(
transplantDate: completedDate, lastUpdate: DateTime.now());
break;
case 'firstHarvest':
updatedPlanting = planting.copyWith(
firstHarvestDate: completedDate, lastUpdate: DateTime.now());
break;
case 'endHarvest':
updatedPlanting = planting.copyWith(
endHarvestDate: completedDate, lastUpdate: DateTime.now());
break;
case 'pull':
updatedPlanting = planting.copyWith(
pullDate: completedDate, lastUpdate: DateTime.now());
break;
case 'other':
// TODO: implement other what do we do if they are finishing a non planting task?
break;
}

You can make the code shorter, and communicate your intent more clearly, by using the late keyword:

    late Planting updatedPlanting;
switch (task.taskType) {
case 'sow':
updatedPlanting = planting.copyWith(
startDate: completedDate, lastUpdate: DateTime.now());
break;
case 'transplant':
updatedPlanting = planting.copyWith(
transplantDate: completedDate, lastUpdate: DateTime.now());
break;
case 'firstHarvest':
updatedPlanting = planting.copyWith(
firstHarvestDate: completedDate, lastUpdate: DateTime.now());
break;
case 'endHarvest':
updatedPlanting = planting.copyWith(
endHarvestDate: completedDate, lastUpdate: DateTime.now());
break;
case 'pull':
updatedPlanting = planting.copyWith(
pullDate: completedDate, lastUpdate: DateTime.now());
break;
case 'other':
// TODO: implement other what do we do if they are finishing a non planting task?
break;
}

A more important reason to use late is that if you fail to initialize the entity, you will get a runtime error that clearly indicates the problem, rather than a runtime error that initially seems unrelated (i.e. failure to find a chapterID).

- + \ No newline at end of file diff --git a/docs/develop/alpha-release/data-model.html b/docs/develop/alpha-release/data-model.html index 2f7573c59..763d867cd 100644 --- a/docs/develop/alpha-release/data-model.html +++ b/docs/develop/alpha-release/data-model.html @@ -5,13 +5,13 @@ Data Model | Geo Garden Club - +

Data Model

This page documents the data model intended to satisfy the alpha release requirements.

Entities

In this document, "entity" refers to the fundamental forms of persistent data objects. Each entity is defined as a set of typed fields.

Entities are persisted through a set of Firebase collections. In general, each entity is a document that is stored in a corresponding collection: all of the Chapter entity documents are stored in a Firebase collection called Chapters, all of the Gardener entity documents are stored in a Firebase collection called Gardeners. Unfortunately, the "News" entity documents are stored in a Firebase collection called "Newss" (with two s's at the end) so that the entity and collection name are different.

In the ggc_app application, there are Dart "domain" classes that mirror these Firebase collections, so there is a Dart class called "Chapter", a Dart class called "ChapterCollection", and so forth.

To facilitate the design description, each field of an entity will be documented with one of the following "variants" R, O, or D:

VariantDescription
RRequired: The field value is stored as an explicit value in each document of the entity's collection, and all documents have a value for this field.
OOptional: The field value may or may not exist in a given document associated with the collection.

Finally, the following documentation includes example documents (JSON objects) generated from the DataModelMigrator application.

Chapter

The Chapter entity contains the following fields:

FieldTypeR/ODescription
chapterIDStringRA unique ID with the format chapter-<chapterNum>
nameStringRThe name of the chapter, such as "Whatcom-WA"
zipCodesList<ZipCode>RThe zip codes associated with the chapter, derived from the ChapterZipMap.
profilePictureStringRThe path to a profile picture for this chapter
picturesList<String>OThe paths for additional pictures of this chapter.
lastUpdateDateTimeRA DateTime instance that timestamps the last update.

Here is an example of a Chapter collection document from the migrated data:

  {
"chapterID": "chapter-001",
"name": "Whatcom-WA",
"profilePicture": "/img/chapters/bellingham/bellingham-chapter-map.png",
"pictures": [
"/img/chapters/bellingham/chapter-007.jpg"
],
"zipcodes": [
"98225",
"98226",
"98227",
"98228",
"98229"
],
"lastUpdate": "2023-03-19T12:19:14.164090"
}

User

The User entity represents all of the people who have created an account with the system.

Note that not all Gardeners are users: commercial seed vendors won't generally have an account on the system.

Currently all Users are also Gardeners, though the design does not require this. In future, there may also be Users who are not Gardeners.

Every user is associated with a unique email address, which is their UserID.

For the alpha release, the data model does not include information about the subscriptions, payments, credit card, etc associated with a gardener.

Each User entity provides the following information:

FieldTypeR/ODescription
userIDUserIDRA unique ID corresponding to the email address associated with this user.
chapterIDChapterIDRThe chapterID associated with this Gardener.
nameStringRThe users name. The user name is normally not provided in the UI.
usernameStringRThe username is what is normally used to identify the user in the UI.
imagePathStringOA path to the image to be associated with this user.
lastUpdateDateTimeRThe DateTime object indicating the last update.

To illustrate, here is an example document from the Gardener collection:

 {
"userID": "johnson@hawaii.edu",
"chapterID": "chapter-001",
"name": "Philip Johnson",
"username": "@fiveoclockphil",
"imagePath": "",
"lastUpdate": "2023-04-01T00:00:00.000Z"
}

Gardener

The Gardener entity is designed to represent two distinct classes of gardeners in GGC: (1) "normal" home gardeners and (2) commercial seed vendors.

The benefit of having the Gardener entity represent both "normal" gardeners as well as commercial seed vendors is that it results in a uniform mechanism in the app to support "seed providers": any Gardener (which can either be a normal home gardener or a commercial seed vendor) grows a Garden which contains Plantings which (may or may not) produce seeds that are available within the Chapter.

This does create some UI complexity, in that commercial seed vendors are not intended to be "Chapter members" in the normal sense. There is a boolean isVendor field that can be used to maintain two local caches of Gardener collections: one containing all of the "normal" home gardeners, and one containing the "vendor" gardeners.

Each Gardener entity provides the following information:

FieldTypeR/ODescription
gardenerIDGardenerIDRA unique ID corresponding to the email address of this user.
chapterIDChapterIDRThe chapterID associated with this Gardener.
isVendorboolRA flag indicating whether this entity instance represents a vendor (if true) or a home gardener (if false)
vendorNameStringOIf isVendor, then this string is present and specifies the full vendor name.
vendorShortNameStringOIf isVendor, then this string is present and specifies a short vendor name.
vendorURLStringOIf isVendor, then this string is present and specifies a URL to the vendor site.
masterGardenerbooleanOtrue if this gardener is a Master Gardener. (This is an example "badge". There could be many others.)
lastUpdateDateTimeRThe DateTime object indicating the last update.

To illustrate, here is an example document from the Gardener collection:

  {
"gardenerID": "jennacorindeane@gmail.com",
"chapterID": "chapter-001",
"isMasterGardener": true,
"isVendor": false,
"vendorName": "",
"vendorShortName": "",
"vendorURL": "",
"lastUpdate": "2023-03-19T12:19:14.164836"
}

Garden

The Garden entity represents a plot of land (or maybe even just some pots) that can hold Plantings over one or more years.

The Garden entity contains the following fields:

FieldTypeR/O/IDescription
gardenIDGardenIDRA unique ID with the format garden-<chapterNum>-<gardenNum>. Each <gardenNum> is unique within a Chapter and starts at 100.
chapterIDChapterIDRThe ChapterID.
nameStringRThe name of the Chapter. This should normally be unique within a Chapter.
ownerIDGardenerIDRThe single Gardener who "owns" this Garden, which gives them full management rights. This ID corresponds to their email.
profilePictureStringThe path to an image to be used as the profile picture for this garden.
picturesList<String>A list of image paths.
isVendorboolRIf true, then this is a commercial garden, not a home garden.
picturesList<Pictures>O(Public) Pictures of this garden.
climateVictoryGardenbooleanOAn example "badge" associated with this garden.
lastUpdateDateTimeRThe last update timestamp.

Here is an example Garden document:

  {
"gardenID": "garden-001-102",
"chapterID": "chapter-001",
"name": "Kale is for Kids",
"ownerID": "jbeck913360@hotmail.com",
"profilePicture": "/img/gardens/45ght3cf/garden-001.jpg",
"pictures": [
"/img/gardens/45ght3cf/garden-007-birds-eye-view.jpg",
"/img/gardens/45ght3cf/garden-002.jpg"
],
"isVendor": false,
"isClimateVictoryGarden": false,
"lastUpdate": "2023-03-20T15:45:56.856468"
}

Editor

In the alpha release, the access control capability enables a Gardener to allow another Chapter member to edit one of their gardens. (There is no implementation of a "viewer", who can see more of someone else's garden than a normal Chapter member.)

This capability is implemented by the Editor entity, which implements a mapping between a Garden and a Gardener:

FieldTypeR/ODescription
editorIDStringRA unique ID with the format editor-<chapterNum>-<editorNum>
chapterIDChapterIDRThe ChapterID.
gardenIDGardenIDRThe garden for which editor access is being granted.
gardenerIDGardenerIDRThe gardener who is obtaining editor access to the above garden.
lastUpdateDateTimeRA DateTime instance that timestamps the last update.

Here is an example Editor document:

{
"editorID": "editor-001-001",
"gardenID": "garden-001-101",
"chapterID": "chapter-001",
"gardenerID": "jbeck913360@hotmail.com",
"lastUpdate": "2023-03-20T15:45:56.856359"
}

Bed

Each Garden consists of a number of Beds.

The Bed entity has the following conceptual structure.

FieldTypeR/ODescription
bedIDBedIDRA unique ID with the format bed-<chapterNum>-<gardenNum>-<bedNum>. BedNums are unique within a Chapter and Garden and start at 200.
chapterIDChapterIDRThe ChapterID.
gardenIDGardenIDRThe garden associated with this Bed.
nameStringRThe name associated with this Bed.
lastUpdateDateTimeRA DateTime instance that timestamps the last update.

Here is an example Bed document:

 {
"bedID": "bed-001-102-215",
"chapterID": "chapter-001",
"gardenID": "garden-001-102",
"name": "15",
"lastUpdate": "2023-03-20T15:45:56.856565"
}

Planting

A Planting is represents a set of plants of the same variety or crop, planted in a single bed, all with the same approximate timings (i.e. planting, transplanting, harvesting, etc.). If the same variety or crop is planted in two different beds, then this must be represented by two Planting instances. (Alternatively, you could define an additional, "virtual" Bed that conceptually represents the contents of two physical beds and put a single Planting in it.)

It is common during the garden planning process to first design the garden at the "crop" level, and then later refine the plan by specifying a specific variety of each crop. To support this incremental design process, the Planting entity only requires a Crop to be specified.

The Planting entity has the following conceptual structure.

FieldTypeR/ODescription
plantingIDPlantingIDRA unique ID with the format planting-<chapterNum>-<gardenNum>-<plantingNum>. PlantingNums are unique within a Chapter and Garden and start at 1000.
chapterIDChapterIDRThe ChapterID.
gardenIDGardenIDRThe garden associated with this Bed.
cropIDCropIDRThe Crop associated with this Planting.
cropNameStringRThe name associated with the above CropID.
yearNumberOThe year associated with a Garden. Not required when this Planting is associated with a vendor Garden.
bedIDBedIDOThe BedID.
varietyIDVarietyIDOThe VarietyID.
varietyNameStringOThe name associated with the above VarietyID.
outcomeIDOutcomeIDOThe outcomes associated with this planting.
seedIDSeedIDOThe seed that was used to create this planting.
startDateDateTimeOWhen the plant was started.
transplantDateDateTimeOWhen the plant was transplanted from greenhouse to bed (if that happened.)
firstHarvestDateDateTimeOWhen the plant first produced food.
endHarvestDateDateTimeOWhen the plant last produced food.
pullDateDateTimeOWhen the plant was pulled from the garden.
usedGreenhousebooleanOIf the planting was started in a greenhouse. Defaults to false.
isVendorbooleanOIf this planting is associated with a commercial seed grower. Defaults to false.
hasSeedsbooleanOIf this planting produced seeds. Defaults to false.
seedsAvailablebooleanOIf this planting produced seeds that the Gardener can provide to others. Defaults to false.
lastUpdateDateTimeRA DateTime instance that timestamps the last update.

Here is an example Planting document:

 {
"plantingID": "planting-001-101-1034",
"chapterID": "chapter-001",
"gardenID": "garden-001-101",
"cropID": "crop-001-544",
"cropName": "Tomatillo",
"year": 2023,
"bedID": "bed-001-101-218",
"varietyID": "variety-001-904",
"varietyName": "De Milpa",
"outcomeID": null,
"startDate": "2023-03-10T00:00:00.000",
"transplantDate": "2023-05-01T00:00:00.000",
"firstHarvestDate": null,
"endHarvestDate": null,
"pullDate": "2023-08-31T00:00:00.000",
"seedID": "seed-001-105-1048-103",
"usedGreenhouse": true,
"isVendor": false,
"hasSeeds": false,
"seedsAvailable": false,
"lastUpdate": "2023-03-20T15:45:56.872599"
}

Variety

Variety is a specific kind of Crop which has seeds. For example, a seed packet such as "Tomato (Sun Gold)" specifies the crop ("Tomato") and the Variety ("Sun Gold").

It is possible for multiple gardeners (either home or commercial) to produce Seeds of the same Variety.

The Variety entity has the following conceptual structure.

FieldTypeR/ODescription
varietyIDVarietyIDRA unique ID with the format variety-chapterNum-varietyNum. VarietyNums are unique within a Chapter and start at 900.
chapterIDChapterIDRThe ChapterID.
cropIDCropIDRThe Crop associated with this Variety.
cropNameStringRThe name associated with the above CropID.
nameStringRThe name associated with this Variety.
lastUpdateDateTimeRA DateTime instance that timestamps the last update.

Here is a sample Variety document:

 {
"varietyID": "variety-001-923",
"chapterID": "chapter-001",
"cropID": "crop-001-534",
"cropName": "Radicchio",
"name": "Pasqualino",
"lastUpdate": "2023-03-20T15:45:56.858247"
}

Crop

Crop specifies a type of plant independent of its Variety. For example, "Tomato" is a Crop.

Each Variety is associated with a single Crop.

The Crop entity has the following conceptual structure.

FieldTypeR/ODescription
cropIDCropIDRA unique ID with the format crop-<chapterNum>-<cropNum>. CropNums are unique within a Chapter and start at 500.
chapterIDChapterIDRThe ChapterID.
familyIDFamilyIDRThe plant Family associated with this Crop.
nameStringRThe name associated with this Crop.
lastUpdateDateTimeRA DateTime instance that timestamps the last update.

Here is an example Crop document:

{
"cropID": "crop-001-503",
"chapterID": "chapter-001",
"familyID": "family-411",
"name": "Asparagus",
"lastUpdate": "2023-03-20T15:45:56.857232"
}

Family

Family specifies the botanical family associated with one or more Crops (and implicitly, Varieties). For example, the "Nightshade" family groups together Tomatoes, Potatoes, and Peppers. Family data is useful during garden planning to facilitate planning issues including crop rotation and companion planting.

The Family entity is one of the few "global" collections in GGC. In other words, it does not include a ChapterID; every Chapter will download this collection in its entirety. (Which is not a hardship, there are only around a dozen Family documents.)

The Family entity has the following conceptual structure.

FieldTypeR/ODescription
familyIDFamilyIDRA unique ID with the format family-<familyNum>. FamilyNums are unique and start at 400.
formalStringRThe formal name associated with this Family.
commonStringRThe common name associated with this Family.
examplesStringRA documentation string providing examples of Crops within this Family.
lastUpdateDateTimeRA DateTime instance that timestamps the last update.

Note that computing the cropIDs or varietyIDs associated with a familyID requires specifying a chapterID.

Here is an example Family document:

{
"familyID": "family-406",
"formal": "Fabaaceae",
"common": "Legume",
"examples": "bean, pea, peanuts",
"lastUpdate": "2023-03-20T15:45:56.856873"
}

Outcome

Outcome data is gardener-supplied information about the result of a single planting. We want to specify results of a planting that is useful and actionable for gardeners, that captures the most important properties of a planting, that is relatively easy to provide, and that is specified in sufficient detail that we can create meaningful aggregations of outcome data for crops and varieties.

To support these requirements, we define five outcome types: germination, yield, flavor, pest and disease resistance, and appearance. Each planting can receive a "grade" for each of these outcome types on a five point scale. The following table presents the definitions for each scale value for each outcome type.

12345
GerminationFailure. No seeds germinated.Poor. Approximately a quarter of the seeds germinated.OK. Approximately half of the seeds germinated.Good. Approximately 3/4 of the seeds germinatedOutstanding. 90% or more of the seeds germinated.
YieldNone. The planting died and/or did not yield any food.Minimal. The planting yielded significantly less food than expected.OK. The planting yielded the expected amount of food.Good. The planting yielded somewhat more food than expected.Outstanding. The planting yielded significantly more food than expected.
FlavorBad. Not worth eating.Bland. Worth eating, but only a little.OK. Expected level of flavor.Good. Better than OK flavor, enjoyable to eat.Outstanding. Can't imagine it tasting better.
Pest and disease resistanceExtremely poor. 90% or more of the plantings have damage.Poor. More than half of the plantings have damage.OK. No more than a quarter of plantings have damage.Good. Only a few plantings have damage.Outstanding. No observable damage.
AppearanceAlmost all ugly. 90% or more of the crop is ugly.Mostly ugly. Over 50% of the crop is ugly.Mostly OK. Over 50% of the crop is OK.Mostly beautiful. Over 50% of the crop is beautiful.Almost all beautiful. 90% or more of the crop is beautiful.

The Family entity has the following conceptual structure.

FieldTypeR/ODescription
outcomeIDOutcomeIDRA unique ID with the format outcome-<chapterNum>-<gardenNum>-<outcomeNum>. OutcomeNums are equal to the PlantingNum of the Planting associated with this Outcome.
chapterIDChapterIDRThe ChapterID.
cropIDCropIDRThe Crop associated with this Outcome.
varietyIDVarietyIDRThe Variety associated with this Outcome.
plantingIDPlantingIDRThe Planting associated with this Outcome.
yearNumberRThe year associated with this Outcome.
appearancenumberOThe appearance outcome value, if available.
flavornumberOThe flavor outcome value, if available.
germinationnumberOThe germination outcome value, if available.
resistancenumberOThe resistance outcome value, if available.
yieldnumberOThe yield outcome value, if available.
lastUpdateDateTimeRA DateTime instance that timestamps the last update.

By design, an outcomeID's numerical suffix will always be the same as its associated plantingID.

Here is an example of an Outcome document:

{
"outcomeID": "outcome-001-101-1039",
"chapterID": "chapter-001",
"gardenID": "garden-001-101",
"cropID": "crop-001-546",
"varietyID": "variety-001-861",
"plantingID": "planting-001-101-1039",
"year": 2022,
"germination": 3,
"yield": 5,
"flavor": 5,
"resistance": 4,
"appearance": 5,
"lastUpdate": "2023-03-20T15:45:56.873320"
}

Seed

The ability to save and share seeds within a Chapter is a significant core value proposition for GGC.

Creating an effective UX for seed saving and sharing means (among other things) that we need to represent seeds explicitly within the data model. By "seed", we don't mean each individual, tiny seed. We mean the set of all seeds harvested from a planting in a garden in a particular season. We won't represent a "count" of the number of seeds available, as that seems too onerous. Instead, we'll just provide a flag (seedsAvailable) associated with a Planting that a Gardener can use to indicate that there exist (some number of) seeds to share.

Our data model enables us to represent both seeds that are locally produced by gardeners as well as seeds that are produced by vendors. One benefit of our design is the ability to represent the "provenance" of a seed. As a simple example:

PlantingOrigin of the seeds for this Planting
Bean (Scarlet Runner), "Alderwood" garden (2023)Bean (Scarlet Runner), "Alderwood" garden (2022)
Bean (Scarlet Runner), "Alderwood" garden (2022)Bean (Scarlet Runner), "Kale is for Kids" garden (2021)
Bean (Scarlet Runner), "Kale is for Kids" garden (2021)Bean (Scarlet Runner), "Johnny's Seeds" garden (vendor)
Bean (Scarlet Runner), "Johnny's Seeds" garden (vendor)unknown

In other words, our data model can represent a "chain" of Plantings, in which one Planting produces Seeds which are used to grow a subsequent Planting. When you add in the ability for a gardener to inspect this chain, and even learn about the history and observations of any of the Plantings in the chain, it becomes apparent that this has the potential to be an interesting resource for seed saving and sharing.

The Seed entity has the following conceptual structure:

FieldTypeR/ODescription
seedIDSeedIDRA unique ID with the format seed-<chapterNum>-<gardenNum>-<plantingNum>-<seedNum>. SeedNums are unique within a Chapter and Garden and start at 000.
chapterIDChapterIDRThe ChapterID.
gardenIDGardenIDRThe GardenID.
plantingIDPlantingIDRThe PlantingID.
cropIDCropIDRThe CropID.
varietyIDVarietyIDRThe VarietyID
gardenNameStringRThe name of the Garden associated with the above GardenID.
cropNameStringRThe name of the Crop associated with the above CropID.
varietyNameStringRThe name of the Variety associated with the above VarietyID.
seedsAvailableboolRThis field is true if this Seed is currently available for sharing.
lastUpdateDateTimeRA DateTime instance that timestamps the last update.

Here is an example Seed document:

{
"seedID": "seed-001-115-1035-104",
"chapterID": "chapter-001",
"gardenID": "garden-001-115",
"plantingID": "planting-001-115-1035",
"cropID": "crop-001-524",
"varietyID": "variety-001-905",
"gardenName": "Unknown vendor",
"cropName": "Lettuce",
"varietyName": "Mix",
"seedsAvailable": true,
"lastUpdate": "2023-03-20T15:45:56.891813"
}

In general, a Garden associated with a vendor will have a single Planting instance for each Variety that they offer. This Planting instance will have a single Seed instance, with seedsAvailable set to true. In reality, a vendor may or may not have seeds in stock for a given Variety at any given time. And, in reality, a vendor will produce their seeds from growing plants each year. But, we will not represent these "realities" about vendor gardens and seeds in our data model, at least for the alpha release.

Observation

An observation is a note (and, typically, a picture) taken by a gardener regarding a planting at a specific point in time.

The Observation entity has the following conceptual structure.

FieldTypeR/ODescription
observationIDObservationIDRA unique ID with the format observation-<chapterNum>-<gardenNum>-<observationNum>. ObservationNums are unique within a Chapter and Garden and start at 700.
chapterIDChapterIDRThe ChapterID.
gardenIDGardenIDRThe Garden associated with this Observation.
gardenNameStringRThe Garden name associated with the above GardenID.
plantingIDPlantingIDRThe Planting associated with this Observation.
cropIDCropIDRThe Crop associated with this Observation.
cropNameStringRThe name associated with the above CropID.
varietyIDVarietyIDRThe VarietyID.
varietyNameStringRThe name associated with the above VarietyID.
observationDateDateTimeRThe time and date associated with this Observation.
tagsList<String>RA list of strings that tag this Observation.
descriptionStringRA textual description of this Observation.
pictureStringOA string that can be used to retrieve the picture associated with this Observation. Or the empty string.
lastUpdateDateTimeRA DateTime instance that timestamps the last update.

Here is an example Observation document:

  {
"observationID": "observation-001-101-707",
"chapterID": "chapter-001",
"gardenID": "garden-001-101",
"plantingID": "planting-001-101-1044",
"cropID": "crop-001-529",
"varietyID": "variety-001-812",
"gardenName": "Alderwood",
"cropName": "Pea",
"varietyName": "Sugar Snap",
"observationDate": "2022-05-20T00:00:00.000",
"tags": [
"phenology",
"first flower"
],
"description": "First pea flower! Peas looking very happy.",
"picture": "observation-004.jpg",
"lastUpdate": "2023-04-01T00:00:00.000Z"
}

If we provide a specific set of tags, rather than allow a gardener to enter free text, then the tag system will be much more useful. Here is a proposal for an initial set of tags:

TagDescription
PestThis observation is useful because: (a) the gardener might choose to rotate beds for this Planting in future seasons, and other gardeners will find it useful to know in real time that the pest is present in their community.
First Harvest, Last HarvestThese observations are useful to the gardener because it provides more detailed guidance on how long a particular PlantID needs to actually be in a bed. Community members also will find this of use in their own garden planning.
First Frost, Last FrostThese seem like they could be useful for planning purposes in future years to decide when it's safe to have certain plants in the garden They could also be used to validate weather station data against the actual climate situation in the garden. Interestingly, if we want to get chapter-wide info on frost dates, we might have to communicate that one gardener observed a frost event to all the other gardeners in the chapter and ask them to confirm/deny frost in their garden (this disambiguates the "no frost" from "no data" situation.)
DiseaseDifferent than pest, which is animal specific. Disease might be leaf curl, wilts etc.
CompanionI think it would be interesting to see examples of plants benefitting from each other. For example, plants vining on each other, providing shade, protecting from pests.
TechniqueJessie once brought up that she would love to see examples of how other gardeners trellis/support plants and I think there could be good learning from sharing of these systems. That makes me think about planting strategies in general. I just planted potatoes for the first time and went online to see how best to do that. There are many ways to plant potatoes. Unknown if there are ways more suited for my climate or the type of potato I planted. I could imagine using the app to peruse pictures of potato planting strategies, perhaps even filtering by variety, to get support for this process.
First leaf, First bud, First Flower, First seedThese phenology tags make patterns in climate change obvious as well as provide insight into how two garden's climates might differ. For example, Jessie and I both have the same kind of raspberry. Knowing if her raspberry flowers/fruits/leafs out etc earlier or later than mine can explain to me other differences in our garden's performance.
AestheticIn addition to the above "useful" tags, we also think it's important to have a final category of tag that indicates an observation that the gardener thinks is interesting even if it's not particularly actionable. (These observations can be filtered out by other gardeners if they don't want to see them.)

In addition, we might want to have tags that provide "meta" information:

TagDescription
PublicIndicate if an observation can appear on the public page.
Help wantedIndicates if the Observation describes an issue or problem for which the gardener needs help. For example, "What is this pest?"

Task

A task is a todo/reminder for the gardener. There are two types of tasks:

  1. A task generated from a planting, such as transplant or first harvest. Eventually, GeoGardenClub will generate these tasks automatically when a new planting is created.
  2. A task created by the gardener, such as Weed bed 1 or Water bed 2.

The Task entity has the following conceptual structure.

FieldTypeR/ODescription
taskIDTaskIDRA unique identifier for this Task with the format task-<chapterNum>-<gardenNum>-<plantingNum>-taskNum. If the task is created by the gardener the <plantingNum> is 0000.
chapterIDChapterIDRThe ChapterID.
gardenIDGardenIDRThe Garden associated with this Task.
taskTypeTaskTypeRThe type of task. There are 6 task types: sow, transplant, firstHarvest, endHarvest, pull, and other.
descriptionStringOA description of the task. This is used for other tasks.
dueDateDateTimeRThe date the task is due.
cropIDCropIDOThe CropID associated with this Task. This is used for planting generated tasks.
cropNameStringOThe name of the crop associated with this Task. This is used for planting generated tasks.
bedIDBedIDOThe BedID associated with this Task. This is used for planting generated tasks.
varietyIDVarietyIDOThe VarietyID associated with this Task. This is used for planting generated tasks.
varietyNameStringOThe name of the variety associated with this Task. This is used for planting generated tasks.
lastUpdateDateTimeRThe date the task was last updated.

Here's an example planting generated task.

{
"taskID": "task-001-101-1068-006",
"chapterID": "chapter-001",
"gardenID": "garden-001-101",
"taskType": "transplant",
"description": "",
"dueDate": "2023-07-25T00:00:00.000",
"cropID": "crop-001-516",
"cropName": "Dill",
"bedID": "bed-001-101-208",
"varietyID": "variety-001-848",
"varietyName": "Goldkrone",
"lastUpdate": "2023-04-01T00:00:00.000Z"
}

and a gardener generated task.

{
"taskID": "task-001-101-0000-008",
"chapterID": "chapter-001",
"gardenID": "garden-001-101",
"taskType": "other",
"description": "Weed bed 1",
"dueDate": "2023-07-25T00:00:00.000",
"cropID": "",
"cropName": "",
"bedID": "",
"varietyID": "",
"varietyName": "",
"lastUpdate": "2023-04-01T00:00:00.000Z"
}

Collections and business logic

As noted above, each entity is represented in the ggc_app as a Dart class, and made persistent as a document in Firebase.

Groups of entity instances of the same type are also represented in the ggc_app as a Dart class, and made persistent as a collection in Firebase. So, for example, in ggc_app, there is a Dart class called "Chapter" (to represent individual instances of that entity) and a Dart class called "ChapterCollection" (to manage a set of Chapter instances). On the Firebase side, there is a collection called Chapters, and each document in that collection has the same structure as the corresponding Dart class. We use freezed to support the translation between the Dart class instance for an entity and its persistent representation as a Firebase document in JSON format.

That said, not all Collections in the ggc_app are created equally! Consider the following typical query:

"What are the names of the crops that have been planted by johnson@hawaii.edu?"

The answer to this query involves finding all the Gardens owned by johnson@hawaii.edu, then retrieving all of the Plantings associated with those Gardens, then building a set of Crop entities from those Plantings, then mapping over that set of Crop entities to build a list of crop names, then sorting that list of names into alphabetical order, and finally returning that list.

In this case, three different collections (Gardens, Plantings, and Crops) must be manipulated to satisfy the query. Other queries could require the manipulation of even more collections.

These kinds of queries represent the "business logic" of the application. In ggc_app, we want to follow the software engineering best practice of "separating business logic from user interface logic". To do that, ggc_app defines three "top-level" collections: UserCollection, GardenCollection, and ChapterCollection. Whenever possible, the UI can simply call a method on one of those top-level collections to obtain the data to present in the UI. So, if a UI component needs to present a list of crop names planted by a user, it can simply call users.getCrops(userID). The getCrops() method takes care of accessing all of the additional collections to obtain the desired data.

To make this more concrete, here are a sampling of the methods associated with the ggc_app "top-level" collections.

ChapterCollection

Method signatureReturn value
List<String> getChapterIDs()All chapter IDs
List<String> getAssociatedUserIDs(String chapterID)All users in this chapter
List<String> getChapterNames()Chapter names
String getChapterIDFromName(String name)(Since chapter names are unique.)

UserCollection

Method signatureReturn value
User getUser(String userID)Return the User entity
bool areUserNames(List<String> userNames)Verify the list of usernames
int getNumNews(userID)Number of news items for this user.
List<String> getAssociatedGardenNames(userID)Gardens associated with this user.

GardenCollection

Method signatureReturn value
List<Garden> getGardens({String? userID, String? chapterID})The gardens associated with either the user or the chapter.
String getOwnerUserID(String gardenID)The garden owner.
List<String> getEditorUserIDs(String gardenID)The garden editors.
bool _userIsAssociated(String gardenID, String userID)Is this user an owner or editor of this garden
void setGarden(Garden garden)Update the Firebase document associated with this garden.

When doing UI design, if you find yourself writing more than a couple of lines of code to produce the data for display, you should consider whether this code should be made "business logic" and provided as a method in either the ChapterCollection, GardenCollection, or UserCollection.

Other Data Model issues

Privacy

Our goals for GGC create a very particular and constrained approach to "privacy".

On the one hand, we want to preserve certain types of privacy:

  • Users can pick a "username" which is used in postings so that they do not have to reveal their real name.
  • The system does not reveal the precise location of gardens.
  • Users can tag an observation and/or photo as "private", and in that case it will not be visible to others.

On the other hand, we want to facilitate the creation of a community of practice, which is accomplished by making many aspects of garden planning and management public to all members of a chapter.

A significant goal for the alpha release is to test the hypothesis that it is not problematic for users to share this kind of information with others.

A common approach to privacy is to make sharing "opt-in". In other words, your data is private unless you explicitly agree to share it. One concern with this approach is that if we allow some users to make their garden info private, it creates an "information asymmetry", where some users get to exploit the experiences of others while not offering up their experiences in return. That seems corrosive to the morale of the chapter and impedes the creation of a community of practice. It seems better to test the hypothesis that there is enough value from sharing to make it mandatory (outside the "privacy" mechanisms listed above.)

IDs

In NoSQL databases, each document is automatically provided upon creation with a unique string called a "docID" which looks something like this: tghHU4CVf.

In the GGC data model, there is a docID field, but it is ignored by the application. Instead, the application relies on the fact that each document has a ID field whose name and values are based on the associated collection. So, the Gardener collection has a field named "gardenerID" and the value of that field will be a string with the prefix gardener- and a suffix that consists of two numbers: the chapterID associated with the gardener and a number that uniquely identifies the gardener within the chapter. For example, gardener-001-301.

In my prior development experience, having a "user friendly" ID field for NoSQL documents improves the developer experience in two ways:

  • It is a little easier to remember that garden-001-101 is Jenna's garden than tghHU4CVf is Jenna's garden.
  • Certain bugs are easier to identity. For example, if a field named "gardenerIDs" contains the value "crop-001-503".

One design problem with explicitly creating and managing ID fields in this way is ensuring that they are unique. While the database itself can trivially ensure that it always provides a unique randomly generated docID for each document, our design requires clients to create the gardenID, plantingID, etc. locally and hope that there is not already a document with this ID in the database.

With FireBase, it is possible to create a security rule to prevent documents with duplicate field values. Thus, we can have clients create IDs, and in the event that there is a collision (i.e. a document with the same ID for that field already exists), then the client request will fail with an error.

We believe this "error due to pre-existing ID" situation to be a very unlikely scenario, because the GGC unique IDs are crafted to be as "local as possible". For example, when creating a Planting, the unique ID includes the chapter and garden IDs. This means that a collision is only possible if two gardeners try to create a Planting for the same Garden in the same Chapter at "almost" the exact same time. As a result of this design of GGC IDs, we expect collisions to rarely, if ever, occur in practice. If they do, then the above Firebase security rule will prevent the document creation request from succeeding. In the UI, we will catch this failure and ask the user to retry.

In addition, this Alpha release data model implements a simple numbering convention to further improve the human readability of the unique IDs. The idea is to begin numbering entity documents of a given type at a different number. Here is the numbering system we are using:

Starting NumberEntities
000Chapter, Editor, Seed
100Garden
200Bed
400Family
500Crop
900Variety
1000Planting, Outcome

So, for example, a plantingID looks like this: planting-001-101-1034, and (if you recall the numbering convention), you can decode the ID as Chapter (0xx) followed by Garden (1xx) followed by the Planting (1xxx). Similarly, a bedID looks like bed-001-102-215 which is a Chapter (0xx) followed by a Garden (1xx) followed by a Bed (2xx).

Note that there is no implementation problem with an Entity having so many documents that the IDs eventually cross over into the next category. For example, there is definitely the possibility of more than 100 Chapters, at which point there could be a chapterNum of 101, which would be the same as a gardenNum. That doesn't create any conflicts or problems internally in the system: unique IDs do not depend upon entities "staying in" their starting range.

The goal of this numbering convention is simply to make the alpha release database documents slightly easier to understand while the relative numbers of Chapters, Gardens, Crops, etc are low. Once we have hundreds of Chapters and tens of thousands of Gardeners, we will have outgrown the use of this simple partitioning to understand the data.

Note that Firebase recommends against creating documentIDs with lexicographically close ranges. However, this recommendation applies only to situations with high levels of reads or writes. Even at scale, GGC will not be experience "high" levels of reads or writes (from a database point of view), so I am hopeful we can implement this numbering scheme, at least for the Alpha Release. (If necessary, we could easily migrate to a randomized string for IDs in future if this actually becomes an database bottleneck.)

Normalization and caching

A best practice for relational database design is "normalization", which means that a value should only occur in one place at a time. Normalization has a number of virtues, such as making updates and deletions more efficient and less error prone. But normalization has a substantial cost: queries can become very complicated, involving complex "joins" of data from a variety of tables.

The GGC app has the following design considerations that impact on the issue of normalization:

  • Updates and deletions are rare. GGC is mostly an "additive" database. While deletions and updates can occur, they are relatively rare and it's OK if they are "expensive" in time.
  • Reads are common, and we need local caches for certain kinds of Chapter data, and all of the user's Garden-related data.
  • In general, Gardeners do not access data outside their Chapter.

As a result of these design considerations, GGC collections are designed to facilitate caching by including chapterID and gardenID fields whenever relevant.

We also "denormalize" by occasionally providing "redundant" fields in a collection's documents. For example, in some cases a document will include a cropName field even though it already has a cropID field. We do this to simplify the developer experience: it simplifies construction of the UI by reducing the number of collection lookups, and it makes the contents of the database easier to understand and debug.

Local-first, caching, and disconnected operation

We intend to have a "local-first" approach to data. In other words, there will be a local cache of relevant collections on each user's device, so that Firebase queries will be minimized. I am not sure yet whether the local cache will be automatically synced in the background with the global store, or whether we will need to implement a "pull down to refresh" mode. I think either approach would be OK for the purposes of the alpha release.

On the other hand, our approach to IDs makes completely disconnected operation problematic. Actually, it goes beyond that: imagine two gardeners working in the same garden in disconnected mode. The opportunities for problematic data entry are present even if we moved to more traditional forms of document IDs.

For that reason, at least for the alpha release, we will require an internet connection in order to make the app fully functional. It should be straightforward to allow the app to work based on the locally cached data when disconnected, but when that occurs, provide some indication that certain operations will not be available until an internet connection is re-established.

- + \ No newline at end of file diff --git a/docs/develop/alpha-release/input-fields.html b/docs/develop/alpha-release/input-fields.html index 8bff2e05f..5b289bad1 100644 --- a/docs/develop/alpha-release/input-fields.html +++ b/docs/develop/alpha-release/input-fields.html @@ -5,13 +5,13 @@ GGC Input Fields | Geo Garden Club - +

GGC Input Fields

Motivation

GGC uses the Flutter Form Builder package to support data collection from gardeners. Flutter Form Builder simplifies form-based data collection by reducing the code needed to: (a) build a form, (b) validate fields, (c) react to changes, and (d) collect final user input.

While this is great, Flutter Form Builder does not, by itself, accomplish two additional important design goals for GGC:

  • Provide specialized widgets for commonly used GGC data input fields. For example, a dropdown displaying the names of all gardens associated with this user; and
  • Provide a single location for specifying the look-and-feel for input fields. We want to minimize the amount of duplicated code (and hopefully eliminate look-and-feel code) when creating a form to collect data in a screen.

There is a third design goal as well. GGC sometimes wants to use input fields outside the context of a "form"---i.e. a context in which data is gathered but not made available to the system until a "Submit" button is pressed. For example, the Outcome screen has input fields to select a garden, crop, and/or variety, and as these fields are manipulated by the user, the screen immediately refreshes to show Outcome data filtered by the values of the input fields. There is no "Submit" button in this screen, and so some of the Flutter Form Builder mechanisms are not used. The third design goal is:

  • Support both in-form and outside-form contexts without having to create two separate Garden dropdown widgets (for example).

To support these three design goals, GGC provides a set of custom input fields (in the lib/features/common/input-fields directory). We call these "GGC Input Fields" to distinguish them from "Form Builder Input Fields".

The goal of this page is to document how GGC Input Fields are created and used in order to facilitate their future evolution.

Background: Form Builder Input Fields

A good overview of Form Builder Input Fields and their use is available in the Flutter Form Builder Readme. As noted in the Parameters section, there are several attributes that all Form Builder Input Fields support. In many cases, a GGC Input Field will provide a value for these standard attributes:

Form Builder Input Field AttributeGGC Input Field Value
nameThe input field name, i.e. "New Garden Name", "Garden Dropdown", etc.
initialValueNot typically needed.
enabledSame default (true)
decorationProvided: implements standard border, icons, and styles across all GGC input fields
validatorProvided as needed. For example, the "New Garden Name" input field will validate that the provided string does not match any other garden name (case-insensitive, spaces and special characters removed).
onChangedMade available in case input field is used outside of a form
valueTransformerA function might be provided for some GGC Input Fields, not sure yet.

Let's look at a simple Form using Form Builder, which displays two text fields ("Email" and "Password") and a "Login" button.

final _formKey = GlobalKey<FormBuilderState>();

FormBuilder(
key: _formKey,
child: Column(
children: [
FormBuilderTextField(
key: _emailFieldKey,
name: 'email',
decoration: const InputDecoration(labelText: 'Email'),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(),
FormBuilderValidators.email(),
]),
),
const SizedBox(height: 10),
FormBuilderTextField(
name: 'password',
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(),
]),
),
MaterialButton(
color: Theme.of(context).colorScheme.secondary,
onPressed: () {
if (_formKey.currentState?.saveAndValidate() ?? false) {
debugPrint('validation succeeded');
debugPrint(_formKey.currentState?.value.toString());
} else {
debugPrint('validation failed');
}
},
child: const Text('Login'),
)
],
),
),

Form Builder provides pre-defined input fields for the following types of input controllers: Checkbox, Radio Button, Date Picker, Dropdown, Slider, Toggle, and Text Field. In addition, the Form Builder Extra Fields package provides input controllers for: Color Picker, Rating, Searchable Dropdown, Signature Pad, Spinnable Number Selector, and Text Field with Auto-Complete.

If you want to build a custom field, there is a set of Example Custom Fields, as well as two how-to articles: Building a Custom Field with FormBuilder Flutter Package and Turn any widget into a Form Input.

Custom Field Example

Here's a simple example of a custom field, built inline:

FormBuilderField<String?>(
name: 'name',
builder: (FormFieldState field) {
return Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return _kOptions.where((String option) {
return option.contains(textEditingValue.text.toLowerCase());
});
},
onSelected: (String selection) {
field.didChange(selection);
},
);
},
autovalidateMode: AutovalidateMode.always,
validator: (valueCandidate) {
if (valueCandidate?.isEmpty ?? true) {
return 'This field is required.';
}
return null;
},
),

FormBuilderField has two required fields: name, and builder. There are many optional fields, including: onSaved, initialValue, autovalidateMode, decoration, enabled, validator, valueTransformer, onChanged, and onReset.

The above example has the required fields plus two fields to implement validation. The field can return either a String or null.

GGC Input Fields

The above section provides a brief introduction to generic Form Builder and Input Fields. Here is how we are building GGC-specific abstractions to address the three design requirements.

Predefined Field Keys

One assumption we can make in GGC is that a given GGC form (i.e. GardenDropdown, CropDropdown, TitleField, etc) appears only once in any given form. That means we can reduce the amount of code required to build a form by predefining field keys. We do this in the FieldKey class, which contains a set of static fields that are initialized to a field key:

/// The FieldKey associated with each GGC Input Field type.
/// This assumes each GGC Input Field type can occur only once in a form.
class FieldKey {
static GlobalKey<FormBuilderFieldState<FormBuilderField<dynamic>, dynamic>>
gardenDropdown = GlobalKey<FormBuilderFieldState>();
static GlobalKey<FormBuilderFieldState<FormBuilderField<dynamic>, dynamic>>
gardenTextField = GlobalKey<FormBuilderFieldState>();
}

This means that when you build a form using the GGC Input Fields, you do not have to create or pass a field key to an input field, as the input field will use one of these values according to the input field type. Then, in the submit callback, you can retrieve the input field value this way:

String gardenID = FieldKey.gardenDropdown.currentState?.value;

GGC Input fields in forms

Using a GGC Input Field in a form is really easy. For example, here is how to add a required dropdown where the user must specify a Garden:

GardenDropdown(gardens: widget.gardens, chapters: widget.chapters, required: true);

As implied above, the value that you can retrieve from this dropdown in the submit callback is the gardenID. From this, you can easily get the garden name (or any other garden details). For example:

String gardenID = FieldKey.gardenDropdown.currentState?.value;
String gardenName = widget.gardens.getGarden(gardenID).name;

GGC Input field outside of forms

We can also use the GardenDropdown Input Field in a non-form context. For example, in the Outcomes screen accessible from the Drawer, there is a Garden dropdown such that the displayed outcomes update immediately each time a new garden is selected.

To do this, the Outcomes screen must provide an onTap function which is called each time the dropdown is manipulated. Here's is how the GardenDropdown can be called to provide this functionality:

GardenDropdown(
gardens: widget.gardens,
chapters: widget.chapters,
gardenID: gardenID,
initialValue: gardenID ?? 'All',
addAll: true,
enabled: widget.gardenID == null,
onTap: (value) => setState(() {
gardenID = (value == 'All') ? null : value;
cropID = null;
varietyID = null;
}),
);

In this situation, we pass in an onTap method that calls setState() to update local state variables for gardenID, cropID, and varietyID. This forces a rebuild of the screen with those new state values, which in turn recomputes the outcomes to be displayed.

This example illustrates how GardenDropdown achieves the three design goals:

  • It is specialized for a given GGC entity. The client just passes in the gardens and chapters collection instances and GardenDropdown does the work of extracting garden names and IDs and building the dropdown object.
  • The invocation of the GardenDropdown has no "look-and-feel" code associated with it. All of the decoration and theme data is internal.
  • The GardenDropdown can be used both within a form (where the data is extracted using a FormKey) or outside a form (where the data is extracted using an onTap callback).

(More documentation to come)

- + \ No newline at end of file diff --git a/docs/develop/alpha-release/installation.html b/docs/develop/alpha-release/installation.html index 798683485..ac0163a34 100644 --- a/docs/develop/alpha-release/installation.html +++ b/docs/develop/alpha-release/installation.html @@ -5,13 +5,13 @@ Installation | Geo Garden Club - +

Installation

Flutter

Follow the Flutter Installation instructions.

The Flutterpalooza module has some additional documentation.

It is important that you are able to run flutter doctor without error:

% flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[] Flutter (Channel stable, 3.10.0, on macOS 13.3.1 22E772610a darwin-arm64, locale en-US)
[] Android toolchain - develop for Android devices (Android SDK version 33.0.1)
[] Xcode - develop for iOS and macOS (Xcode 14.3)
[] Chrome - develop for the web
[] Android Studio (version 2021.3)
[] IntelliJ IDEA Ultimate Edition (version 2023.1)
[] Connected device (3 available)
[] Network resources

• No issues found!

XCode 14.3 configuration

To my great dismay, ggc_app does not build for the iOS simulator using XCode 14.3 without some additional configuration. The only way I have found to get the ggc_app to run on the iOS simulator is by installing the libarclite library manually into XCode. Here are the steps:

  1. Open the Terminal app and go to the XCode library folder:
cd /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/
  1. Allow the Terminal app to create directories in "protected" areas such Applications/. To do this, go to System Preferences > Security & Privacy > Privacy > Full Disk Access, and add the Terminal app to the list of apps that have full disk access by toggling the radio button next to the Terminal app. You will need to enter your password to make this change. Afterwards, the Terminal app will need to restart for these changes to take effect.

  2. Add the libarclite files to the XCode library folder:

sudo mkdir arc
cd arc
sudo git clone https://github.com/kamyarelyasi/Libarclite-Files.git .
sudo chmod +x *
Additional shenanigans

The above information should be enough for you to proceed. However, I want to document some additional details in case they become relevant in the future.

First, after doing the above, when trying to deploy to the iOS Simulator, I got an XCode error that CFBundleVersion was invalid. I fixed this (and related errors) by editing the ios/Runner/Info.plist file manually, and setting both CFBundleVersion and CFBundleShortVersionString to "1". Since this file is version controlled, and does not appear to be affected by running pod install etc, I don't think you need to worry about it.

Second, this approach to resolving XCode 14.3 problems was found here. What you see is that the above instructions only resolving building, but not "archiving". Additional steps are provided to support archiving. Since I don't know anything about archiving, I didn't do this additional step, but I want to note it while I am thing about it.

Finally, in case we need to look at these changes more closely in the future, the commit is here.

ggc_app

To install the app, first clone the sources from https://github.com/geogardenclub/ggc_app.

Next, cd into the ggc_app directory and run flutter pub get. For example:

% flutter pub get
Running "flutter pub get" in ggc_app...
Resolving dependencies... (1.4s)
_fe_analyzer_shared 58.0.0 (59.0.0 available)
analyzer 5.10.0 (5.11.1 available)
async 2.10.0 (2.11.0 available)
build_daemon 3.1.1 (4.0.0 available)
build_runner 2.3.3 (2.4.1 available)
characters 1.2.1 (1.3.0 available)
collection 1.17.0 (1.17.1 available)
flex_color_scheme 7.0.3 (7.0.4 available)
flutter_form_builder 7.8.0 (8.0.0 available)
flutter_riverpod 2.3.5 (2.3.6 available)
flutter_svg 1.1.6 (2.0.5 available)
go_router 6.5.7 (6.5.8 available)
intl 0.17.0 (0.18.1 available)
js 0.6.5 (0.6.7 available)
matcher 0.12.13 (0.12.15 available)
material_color_utilities 0.2.0 (0.3.0 available)
meta 1.8.0 (1.9.1 available)
monarch 3.0.1 (3.4.0 available)
path 1.8.2 (1.8.3 available)
path_provider_windows 2.1.5 (2.1.6 available)
petitparser 5.1.0 (5.4.0 available)
riverpod 2.3.5 (2.3.6 available)
source_span 1.9.1 (1.10.0 available)
sqflite 2.2.6 (2.2.7 available)
sqflite_common 2.4.3 (2.4.4 available)
synchronized 3.0.1 (3.1.0 available)
test_api 0.4.16 (0.5.2 available)
vm_service 11.3.0 (11.4.0 available)
win32 3.1.4 (4.1.3 available)
xml 6.2.2 (6.3.0 available)
Got dependencies!

Next, to check that the ggc_app actually runs in your environment, the simplest thing to do is to invoke flutter run and select Chrome:

% flutter run
Multiple devices found:
macOS (desktop) • macos • darwin-arm64 • macOS 13.3.1 22E261 darwin-arm64
Chrome (web) • chrome • web-javascript • Google Chrome 112.0.5615.137
[1]: macOS (macos)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"): 2
Launching lib/main.dart on Chrome in debug mode...
Waiting for connection from debug service on Chrome... 16.5s
This app is linked to the debug service: ws://127.0.0.1:58007/FT3-VNs7AGk=/ws
Debug service listening on ws://127.0.0.1:58007/FT3-VNs7AGk=/ws

💪 Running with sound null safety 💪

🔥 To hot restart changes while running, press "r" or "R".
For a more detailed help message, press "h". To quit, press "q".

An Observatory debugger and profiler on Chrome is available at: http://127.0.0.1:58007/FT3-VNs7AGk=
WARNING: found an existing <meta name="viewport"> tag. Flutter Web uses its own viewport configuration for better compatibility with
Flutter. This tag will be replaced.
The Flutter DevTools debugger and profiler on Chrome is available at: http://127.0.0.1:9100?uri=http://127.0.0.1:58007/FT3-VNs7AGk=

If all goes well, you should see a window similar to the following appear:

At this point, you can login as one of the existing users to make sure communication with Firebase is working correctly. Contact Philip for credentials.

Editor

There are three good choices for your Editor: Visual Studio, Android Studio, or IntelliJ IDEA Ultimate (with the Dart and Flutter plugins, which makes it almost equivalent to Android Studio).

With IntelliJ IDEA Ultimate, after bringing up the project, you should see a run toolbar at the top which gives you (on a Mac) the option of opening the iOS simulator:

After opening the simulator, it should appear and you should be able to emulate the system on an iOS device:

It takes a couple of minutes to do all of the XCode shenanigans the first time you run it, but eventually you should see something like the following:

As before, consult with Philip for login credentials.

Monarch

According to their home page, Monarch is a "tool for building Flutter widgets in isolation. It makes it easy to build, test and debug complex UIs." Monarch is basically a Flutter port of React Storybook, which is tremendously popular in React UI development.

I have begun using Monarch and believe it will be very helpful for GGC UI development.

Follow the Monarch installation instructions to install the tool.

Then, invoke monarch run --reload hot-restart (or, for less typing, the ./run_monarch.sh shell script).

You will see the Monarch UI appear, which enables you to view all of the GGC UI elements individually, and (where useful) in different states:

Note that you need to manually select our theme (currently, "Green Theme: Light"). Monarch defaults to the Material Light theme when it is first invoked.

For the design and development of basic UI elements, Monarch appears to be faster, easier, and more efficient than running the iOS simulator. Creating Monarch stories also creates an easy to browse "catalog" of UI elements which are far easier to review than paging through the emulated system to get to the correct state.

- + \ No newline at end of file diff --git a/docs/develop/alpha-release/onboarding.html b/docs/develop/alpha-release/onboarding.html index b678da5d8..521e8b20c 100644 --- a/docs/develop/alpha-release/onboarding.html +++ b/docs/develop/alpha-release/onboarding.html @@ -5,13 +5,13 @@ Onboarding | Geo Garden Club - +

Onboarding

Welcome, new GGC Alpha Release developer! This page provides a checklist of things required to get started developing our technology.

Site access

You will need access to the following:

  • The GGC Discord server. Request an invite from Philip or Jenna.
  • The GGC GitHub organization. Please send Philip your GitHub username and he will invite you.
  • The ggc_app Firebase project. Please send Philip your gmail account name and he will add you.

Proficiency in Dart and Flutter

We assume that you already have basic proficiency in Dart and Flutter. If you are not sure of your proficiency, then we recommend that you work through the Dartapalooza and Flutterpalooza modules of Philip's mobile application development course.

Assignment of rights

Before you can contribute code to this project, you will need to sign a document that assigns the ownership of the code you contribute to Geo Garden Club, LLC. Please contact Philip or Jenna for details on how to do this.

Developer workflow

We use a basic process for development:

  • Tasks are specified in a GitHub project board. The project board for GGC is available at: https://github.com/orgs/geogardenclub/projects/1/views/1

  • Code is developed using a branch-and-merge model. Please name the branch "issue-XXX", where XXX is the issue number associated with the task associated with your coding.

  • If you are making a trivial fix, feel free to commit directly to the main branch.

  • We run a CI task to ensure that all code committed to the main branch passes dart analyze without triggering warnings or errors.

- + \ No newline at end of file diff --git a/docs/develop/alpha-release/requirements.html b/docs/develop/alpha-release/requirements.html index df8a24746..73e00e9fe 100644 --- a/docs/develop/alpha-release/requirements.html +++ b/docs/develop/alpha-release/requirements.html @@ -5,13 +5,13 @@ Requirements | Geo Garden Club - +

Requirements

The Core Value Propositions

The goal of the alpha release is to provide an app through Apple's TestFlight to an alpha test community that will use the app without charge in return for providing us with feedback on the strengths and weaknesses of the system. The alpha release is intended to partially test our business model by implementing what we hypothesize to be the "core value propositions" (CVPs) for GGC. Core value propositions are the minimal set of capabilities that we must provide to users in order for them to find the application to be of acceptable value (i.e. they will want to use the app to support their gardening practices.)

Core Value Propositions come in two flavors:

  • Unique value propositions. These are capabilities that, to our knowledge, are not available in any other app.
  • Non-unique value propositions. These are capabilities that may be available in some form in other apps, but which we have to provide in our app in order for the app to be of minimally acceptable value.

For each of the following four CVPs, we will indicate which aspects are unique, and which are non-unique.

1. Effective support for chapters.

All users are associated with a single chapter based upon their zip code. For the alpha release, only a single chapter (Whatcom-WA) will be supported.

The concept of a chapter, and an associated geographic boundary for shared data, is a unique value proposition of GGC. To our knowledge, no other garden planning app has this capability.

Effective support means:

  • Users find the geographic boundary of their chapter to be useful,
  • Users are comfortable sharing data only within the boundaries of their chapter.

Design implications include:

  • Upon initial signin, the user must enter a zip code. This is used to identify their chapter. One cannot edit their zip code once entered in order to prevent users from "skipping around" to different chapters. For the alpha release, the only valid zip codes are those associated with Whatcom-WA.
  • We will implement a Firebase collection that maps zip codes to chapter names. Zip codes are initially mapped to county names, so there will exist a chapter for every zip code. The alpha release will not require this collection.

2. Effective support for garden planning.

The alpha release provides the ability for users to easily define one or more gardens. Each garden consists of a number of beds. Beds contain plantings. Plantings consist of crops or varieties, plus several important dates and (potentially) observations.

Garden planning is not, in general, a unique value proposition of GGC. There are many garden planning apps. Our goal is to be competitive with any other garden planning app with respect to the user experience. In addition, there are a couple of value propositions unique to GGC due to Chapters.

Effective support means:

  • Users find that GGC has an above threshold feature set for garden planning.
  • In general, users find GGC to be as easy (or hopefully easier) to use for planning than whatever they were doing before.
  • If some aspect of GGC planning is more complicated, then the additional complexity has a positive return on investment for users.

Design implications include:

  • Ability to define and represent gardens, beds, seeds, plantings, crops, varieties, dates, observations.
  • Useful representations for the plan, such as a timeline view and/or calendar view.
  • The ability to specify a planting in general terms as a crop during planning, then later narrow it to a specific variety by specifying a seed.
  • Some ability to view other garden plans (or aspects) in the chapter. (This is a unique value proposition.)
  • The ability to review historical outcome data (and perhaps other prior experiences with the crop or variety) from other chapter members in order to improve seed selection and other aspects of planning. (unique value proposition)
  • Some ability to see garden plans for the season of interest under development by other members. (unique value proposition)

3. Effective support for garden management.

The alpha release supports garden management. We use "management" to refer to features of the app that are used during the actual growing season, as opposed to "planning", which refer to features that are used prior to the actual growing season.

There are other garden planning apps that support garden management. However, the ability of GGC to provide information about garden management from other users in the chapter should make garden management a significant value proposition for GGC.

Effective support means:

  • Users find that GGC has an above threshold feature set for garden management.
  • Users find GGC to be as easy (or easier) to use for management than whatever they were doing before.
  • If there are aspects of GGC garden management that are more complicated, then the additional complexity has a positive return on investment for users.

Design implications include:

  • A To Do list that shows upcoming activities or events, generated from the planting data.
  • Notifications of interesting events or observations in other gardens in the chapter, provided via a "Feed" or some other page. (unique value proposition)
  • Ability to allow a group of users to collaboratively manage a single garden. (unique value proposition)

4. Effective support for a community of practice.

The alpha release provides mechanisms that facilitates the formation of a "community of practice" within a chapter.

If successful, the ability of GGC to facilitate the creation of communities of practice would be a unique value proposition.

Effective support means:

  • Users obtain demonstrable value from interactions with other chapter members and access to their garden plans and management.
  • Users do not feel negatively about the information sharing designed into the app.

Design implications include:

  • The ability to message other chapter member(s) (either as a "broadcast" message or a DM).
  • The ability to take pictures, annotate them, and share with other chapter members.
  • The ability to access aggregate chapter outcome data about crops and varietals.
  • The ability to see information about other gardens, including the beds and plantings, both current and historical.
  • The ability to indicate that you have seeds for a particular varietal that you are willing to share in the community.

Features outside scope

To clarify what will be in the alpha release, it is also useful to clarify what will not be in this release. The excluded features include:

  • Chapters other than Whatcom-WA.
  • Allowing a user to be a member of multiple chapters.
  • Sharing of data beyond the members of a chapter.
  • A "public", web-based view of a garden that can be shared to anyone with the URL.
  • Climate data.
  • Chapter Chairs, who can moderate, promote, and otherwise manage the chapter.
  • Hashtags, and the ability to filter gardens, plantings, observations, etc by the hashtag.
  • Disconnected operation. The alpha release will require an internet connection to enable full capabilities. It might implement a local cache so it would be possible to provide read-only access to the garden plans and data when not connected to the internet.
- + \ No newline at end of file diff --git a/docs/develop/alpha-release/scripts.html b/docs/develop/alpha-release/scripts.html index a6588833d..773c7b2b5 100644 --- a/docs/develop/alpha-release/scripts.html +++ b/docs/develop/alpha-release/scripts.html @@ -5,13 +5,13 @@ Scripts | Geo Garden Club - + - + \ No newline at end of file diff --git a/docs/develop/beyond-alpha/chapterzipmap.html b/docs/develop/beyond-alpha/chapterzipmap.html index ad0876954..725be5b6a 100644 --- a/docs/develop/beyond-alpha/chapterzipmap.html +++ b/docs/develop/beyond-alpha/chapterzipmap.html @@ -5,13 +5,13 @@ ChapterZipMap | Geo Garden Club - +

ChapterZipMap

In GGC 1.0, the geographic region associated with a chapter will be defined through a collection called "ChapterZipMap" that maps US zip codes to their corresponding chapter name and chapterID, where the chapter name is constructed from the county and state associated with the zip code. For example, the zip code "96822" maps to the chapter named "Honolulu-HI". We want chapter names to be unique, and so we add the state to the county name because there are many county names that occur in more than one state (i.e. 31 states have a "Washington" county). This collection also defines the chapterID. A portion of the documents in the collection might look like this:

zipcodechapterNamechapterID
"96822""Honolulu-HI""Chapter-023"
"96734""Honolulu-HI""Chapter-023"
"98225""Whatcom-WA""Chapter-013"
"98226""Whatcom-WA""Chapter-013"

The ChapterZipMap collection is pre-constructed and loaded into the database, which means that app is pre-initialized with the names of all chapters possible in the United States, and defines a chapter for every one of its geographic regions. We can also extend this collection to define chapters for other countries in future.

Here is an example excerpt of the JSON file for initializing the ChapterZipMap collection:

[
{
"zipcode": "96822",
"chapterName": "Honolulu-HI",
"chapterID": "Chapter-023"
},
{
"zipcode": "96734",
"chapterName": "Honolulu-HI",
"chapterID": "Chapter-023"
},
{
"zipcode": "98225",
"chapterName": "Whatcom-WA",
"chapterID": "Chapter-013"
},
{
"zipcode": "98226",
"chapterName": "Whatcom-WA",
"chapterID": "Chapter-013"
}
]
- + \ No newline at end of file diff --git a/docs/develop/mockup/chatgpt-feedback.html b/docs/develop/mockup/chatgpt-feedback.html index 6e9f35a41..20028f5cc 100644 --- a/docs/develop/mockup/chatgpt-feedback.html +++ b/docs/develop/mockup/chatgpt-feedback.html @@ -5,13 +5,13 @@ ChatGPT feedback | Geo Garden Club - +

ChatGPT feedback

Just for fun, I had a conversation with ChatGPT in April, 2023 about how to design a gardening app. Much of its responses were uninsightful, but it did come up with some banging ideas for badges.

Design a program to address food resiliency by support communities of practice among home gardeners.

Sure, here's a high-level design for a program that could support communities of practice among home gardeners and help improve food resiliency:

  1. Build a platform for home gardeners to connect with each other and share knowledge and resources. The platform could be a website or mobile app that allows users to create profiles, post photos and descriptions of their gardens, and ask and answer questions.

  2. Develop a system for categorizing and tagging posts so that users can easily find information relevant to their interests and needs. For example, users could tag posts with the types of plants they're growing, the climate and soil conditions in their region, and the pests or diseases they're dealing with.

  3. Recruit experienced gardeners to serve as mentors and moderators on the platform. These individuals could offer advice and guidance to less experienced gardeners, help answer questions, and keep discussions on track.

  4. Host regular online events, such as webinars or Q&A sessions, where users can learn from experts in the field and connect with each other in real time.

  5. Develop partnerships with local organizations and businesses to provide resources and support to the community. For example, the program could partner with a local nursery to offer discounts on seedlings or with a food bank to distribute surplus produce grown by community members.

  6. Encourage users to share their experiences and successes with others in the community. This could include posting photos of their gardens or sharing recipes using the produce they've grown.

  7. Provide incentives for users to participate in the community. For example, users could earn badges or points for contributing to discussions or answering questions, which could be redeemed for prizes such as gardening tools or gift cards.

By implementing these strategies, the program could help create a vibrant and supportive community of home gardeners who are working together to improve food resiliency in their local communities.

Now implement that design in Flutter.

As an AI language model, I cannot implement code. However, I can give you an overview of what a Flutter app that implements the design for supporting communities of practice among home gardeners might look like.

Firstly, the app would have a home screen with different sections for users to explore. The main sections could be:

  1. News Feed: This section would show the latest posts and updates from other gardeners in the community.

  2. Gardening Tips: This section would provide users with a collection of gardening tips and tricks. Users could also submit their own tips and tricks to share with the community.

  3. Q&A: This section would allow users to ask questions about gardening and get answers from experienced gardeners in the community. Users could also answer questions and earn badges for their contributions.

  4. Events: This section would display upcoming online events, such as webinars or Q&A sessions, where users can learn from experts in the field and connect with each other in real-time.

  5. Resources: This section would provide users with access to gardening resources such as articles, videos, and tutorials.

  6. Profile: This section would allow users to create and edit their profile, view their activity history, and connect with other gardeners.

The app would also have a search function that would allow users to search for posts, users, or resources based on keywords or tags. The app could also have a messaging feature that would allow users to communicate with each other privately.

In terms of implementing the design in Flutter, the app could use Flutter's built-in widgets to create the UI elements for the different sections of the app. For example, a ListView widget could be used to display posts in the News Feed section, while a GridView widget could be used to display resources in the Resources section.

Flutter's routing system could be used to navigate between different screens in the app, such as from the News Feed to the Q&A section. The app could also use Flutter's networking libraries to fetch data from a backend server, such as the latest posts from other gardeners.

Overall, Flutter provides a lot of powerful tools and features that can be used to implement a community-building app for home gardeners.

Make the design more oriented toward experienced gardeners.

Sure, here's an updated design that is more oriented towards experienced gardeners:

  1. Expert Insights: This section would provide in-depth articles, videos, and tutorials on advanced gardening topics such as soil science, crop rotation, and pest management. Users could also submit their own content to share with the community.

  2. Peer-to-Peer Mentoring: This section would allow experienced gardeners to mentor other gardeners in the community. Users could request a mentor or be matched with a mentor based on their interests and needs. Mentors could provide personalized advice and support to mentees through private messaging or video calls.

  3. Advanced Q&A: This section would allow users to ask and answer advanced gardening questions. Questions could be categorized by topics such as greenhouse gardening, hydroponics, or permaculture.

  4. Plant Database: This section would provide a comprehensive database of different plant varieties, including information on growth requirements, pest resistance, and culinary uses. Users could also contribute to the database by adding new plant varieties or updating existing entries.

  5. Events: This section would display upcoming events, such as conferences or workshops, where experienced gardeners can connect and learn from each other.

  6. Community Projects: This section would highlight community gardening projects, such as urban farms or community gardens, where experienced gardeners can share their skills and knowledge with others.

  7. Profile: This section would allow users to create and edit their profile, view their activity history, and connect with other experienced gardeners in the community.

The app could also have a badge system that rewards users for their contributions to the community. For example, users could earn badges for answering questions, contributing to the plant database, or mentoring other gardeners.

In terms of implementation in Flutter, the app could use more advanced UI elements such as graphs and charts to display data and statistics about plant growth and health. The app could also use more advanced networking libraries to fetch and display data from external databases or APIs.

Overall, this updated design is more focused on advanced gardening topics and provides opportunities for experienced gardeners to share their expertise with others in the community.

Provide examples of badges that would be appropriate for experienced gardeners.

(Editors note: I was so enamored of the initial responses that I prompted ChatGPT three more times for additional ideas. The final set are collapsed into this single reply.)

Sure, here are some examples of badges that would be appropriate for experienced gardeners in the app:

  1. Master Gardener: This badge could be earned by users who have completed a master gardening program or have equivalent experience and knowledge in the field.

  2. Plant Whisperer: This badge could be earned by users who have demonstrated exceptional knowledge and skills in the care and maintenance of plants.

  3. Community Builder: This badge could be earned by users who have contributed significantly to the growth and development of the gardening community, such as by organizing events or mentoring other gardeners.

  4. Pesticide-Free Gardener: This badge could be earned by users who have demonstrated a commitment to using natural and organic methods of pest control in their gardening practices.

  5. Greenhouse Guru: This badge could be earned by users who have demonstrated exceptional knowledge and skills in greenhouse gardening, such as by successfully growing a wide variety of plants in a greenhouse environment.

  6. Permaculture Pro: This badge could be earned by users who have demonstrated exceptional knowledge and skills in permaculture gardening, such as by successfully implementing permaculture principles in their garden or community project.

  7. Soil Scientist: This badge could be earned by users who have demonstrated exceptional knowledge and skills in soil science, such as by successfully improving soil quality and fertility in their garden.

  8. Seed Saving Specialist: This badge could be earned by users who have demonstrated exceptional knowledge and skills in saving and storing seeds, such as by successfully preserving heirloom varieties of plants.

  9. Composting Champion: This badge could be earned by users who have demonstrated exceptional knowledge and skills in composting, such as by successfully producing high-quality compost for use in their garden.

  10. Food Forest Pioneer: This badge could be earned by users who have successfully implemented food forest principles in their garden or community project, such as by creating a diverse and self-sustaining ecosystem that produces a variety of edible crops.

  11. Irrigation Expert: This badge could be earned by users who have demonstrated exceptional knowledge and skills in irrigation, such as by successfully designing and implementing an efficient irrigation system in their garden.

  12. Edible Landscaper: This badge could be earned by users who have successfully integrated edible plants into their landscaping, such as by creating an aesthetically pleasing and productive garden that provides both beauty and food.

  13. Beekeeper: This badge could be earned by users who have successfully kept and cared for bees, such as by producing honey or pollinating crops in their garden or community project.

  14. Farmer's Market Vendor: This badge could be earned by users who have successfully sold their garden produce at a farmer's market or other local venue, such as by developing a profitable and sustainable small-scale farming business.

  15. Vermiculture Master: This badge could be earned by users who have demonstrated exceptional knowledge and skills in vermicomposting, such as by successfully raising and caring for worms to produce high-quality compost.

  16. Orchardist: This badge could be earned by users who have successfully maintained and harvested from an orchard, such as by growing fruit trees and managing pests and diseases.

  17. Aquaponics Ace: This badge could be earned by users who have successfully implemented and maintained an aquaponics system, such as by raising fish and using their waste to fertilize plants.

  18. Foraging Pro: This badge could be earned by users who have demonstrated exceptional knowledge and skills in foraging for wild edible plants, such as by safely and sustainably harvesting a variety of wild foods.

  19. Herbalist: This badge could be earned by users who have demonstrated exceptional knowledge and skills in growing, harvesting, and using medicinal herbs, such as by successfully creating herbal remedies for various ailments.

  20. Garden Photographer: This badge could be earned by users who have demonstrated exceptional skills in capturing the beauty of their garden through photography, such as by sharing high-quality and visually appealing images of their plants and landscapes.

  21. Garden Writer: This badge could be earned by users who have demonstrated exceptional skills in writing about gardening, such as by creating engaging and informative blog posts or articles about various gardening topics.

  22. Heirloom Hero: This badge could be earned by users who have demonstrated exceptional knowledge and skills in growing and preserving heirloom varieties of plants, such as by successfully saving seeds and sharing them with the community.

  23. Landscape Designer: This badge could be earned by users who have demonstrated exceptional skills in designing and creating beautiful and functional gardens, such as by successfully incorporating different elements like paths, water features, and seating areas.

  24. Garden Educator: This badge could be earned by users who have demonstrated exceptional skills in educating others about gardening, such as by creating and delivering engaging and informative workshops or courses.

  25. Pollinator Protector: This badge could be earned by users who have demonstrated exceptional knowledge and skills in creating pollinator-friendly habitats in their garden or community project, such as by incorporating native plants, providing shelter and nesting sites, and avoiding the use of pesticides.

  26. Garden Artist: This badge could be earned by users who have demonstrated exceptional skills in creating art inspired by their garden or using garden materials, such as by producing paintings, sculptures, or installations.

  27. Fruit Tree Pruner: This badge could be earned by users who have demonstrated exceptional knowledge and skills in pruning fruit trees, such as by maintaining the trees' health and maximizing their fruit production.

  28. Sustainable Gardener: This badge could be earned by users who have demonstrated exceptional commitment to sustainable gardening practices, such as by reducing waste, conserving water, and using organic methods of pest control.

These badges would recognize users who have demonstrated exceptional knowledge and skills in various aspects of gardening and would encourage users to continue learning and sharing their expertise with the community.

- + \ No newline at end of file diff --git a/docs/develop/mockup/customer-feedback.html b/docs/develop/mockup/customer-feedback.html index 721164556..24f94e902 100644 --- a/docs/develop/mockup/customer-feedback.html +++ b/docs/develop/mockup/customer-feedback.html @@ -5,13 +5,13 @@ Customer feedback | Geo Garden Club - +

Customer feedback

Following the Lean Startup principle of "validated learning", we performed an evaluation study of the technology innovations present in the mockup with 24 experienced gardeners during the summer and fall of 2022. We report on the results of that study in the following 15 minute video (or 7 minutes, if you run the video at 2x speed):

If you just want to click through the slides, here they are:

The net of the customer feedback phase is that we found no compelling evidence to abandon the project.

- + \ No newline at end of file diff --git a/docs/develop/mockup/design.html b/docs/develop/mockup/design.html index 62fc8ed1a..8015d9a3a 100644 --- a/docs/develop/mockup/design.html +++ b/docs/develop/mockup/design.html @@ -5,13 +5,13 @@ Design and implementation | Geo Garden Club - +

Design and implementation

We built a simple single page web application using React. The goal of the system was to provide an "executable mockup" to show potential customers our vision of some of the features that would be made available in our production technology.

This system used real data collected from two gardens. You can view the mockups for each garden here:

- + \ No newline at end of file diff --git a/docs/develop/mockup/entrepreneur-feedback.html b/docs/develop/mockup/entrepreneur-feedback.html index 753fe042c..2b8cf224e 100644 --- a/docs/develop/mockup/entrepreneur-feedback.html +++ b/docs/develop/mockup/entrepreneur-feedback.html @@ -5,7 +5,7 @@ Entrepreneur feedback | Geo Garden Club - + @@ -14,7 +14,7 @@ Philip


Hi Philip,

I'm happy I could contribute some ideas. I suspected you were already doing many of those things :-) Jenna - It's nice to meet you.

Comments are below.

We plan to offer at least 6-9 months free use of the app for new users---essentially allowing folks to use it for one gardening season without cost. Our idea is that after that time, they will have entered a significant amount of their own gardening data into the app, resulting in a subtle kind of "lock in".

That makes sense. You can do this for Apple App Store and Google Play apps via promo codes. Make sure to create a paid app, and then use promo codes because you can't convert a free app to a paid app.

If we find too many people quitting after six months, then we could go to a free version with ads, but I'd really like to avoid ads for a variety of reasons if we can.

I'm also not a fan of using ads, but some companies don't have any other revenue model. I think AGC probably does if you can build a good premium service. For a subscription service you really need to concentrate on providing ongoing and ever-improving services such as Garden Kumu, gamification (e.g. contests, challenges, badges) and locale specific guidance from AGC experts and senior chapter members.

I suggest providing strong incentives for senior chapter members to share their knowledge. In addition to recognition for status via badges you might want to provide things like coupons for Kokua Coop, Leahi Health, etc. You may also want to (quietly) waive or discount their subscription fees.

You are so right that "serious gardener" doesn't mean "no fun required gardener"! We've thought about a variety of game mechanics (badges, contests, etc). Another angle is citizen science: there are various climate and environmental groups that have campaigns where gardeners could provide useful observations about plant growth.

That is a good idea. I would ask if you can list them as partners. It would be nice to have a list of collaborating green companies / nonprofits on your site. This lends credibility.

Your suggestion to make it social is also spot on. One primary goal of the app is to foster a high quality "community of practice" around gardening in a given geographic region. The ability to message others within the app and easily share gardening data is going to be key.

I agree. People often underestimate the effort in putting a community together on a proprietary platform. It can be done, but it's a lot of work both from a technology and a community growth / management perspective. The devil is in the details.

Regarding photos, the app will support taking photos (tagged to the garden and plant varietal) that can be shared. In fact, one thing we learned is that if we provide a kind of "Garden Instagram" within the app, then some folks who aren't even gardeners said they would subscribe to the app simply to see what their friends were doing in their gardens.

That is a great idea. Lots of people are interested in gardening but can't participate because they don't have land and / or time.

We also know (from Jenna's personal experience) that gardeners like to share pictures of their gardens and particularly beautiful/interesting plants. So, having a garden-specific "feed" seems like a fun and attractive feature. We didn't present this idea because we believe that by itself, it's not enough to make the application viable. We first have to nail the ability to support gardeners in planning and execution of their garden first. If we can succeed there, then the Garden Instagram feature will be icing on the top.

That makes sense. The main reason companies like this fail is simply failure to launch. They fail to scope an MVP that addresses the 20% of their ideas that are critical to early success.

Yes, we agree about the opportunities for partnering and sharing. We think the app can informally facilitate lots of cooperation: some folks could (for example) coordinate their succession planting with each other so that everyone always has (for example) a good supply of lettuce. There is also a huge opportunity for seed sharing. This is exciting, because locally grown seeds are often going to be better adapted to local conditions (natural selection!). In fact, Jenna is presenting our idea to a seed saving/sharing organization in the near future.

True. Communities in rural Japan are very good at this. They share seeds, they give excess produce to neighbors, and just generally function as a unified community gardening engine.

And, yes, our target demographic is Millenials and Gen Z. Thankfully, Jenna is a millennial.

👍

We eventually need to get someone even younger on the team.

:-) My 9 you is a very serious gardener. He has been trained well by grandma. Noa knows how to plant seeds with the right spacing and depth for a given plant, handle watering, fertilizing, cultivating, etc. They can start young!

Thank you for "Let me know if there is anything else I can do to help." Actually, I already have something in mind. I've been getting up to speed with Flutter and am now more-or-less competent with creating CRUD apps using Riverpod and Firebase and pushing them out to TestFlight. I want to make some more progress on an initial MVP, but at some point I would like to ask you to take a look at the UI and code and let me know where we're going terribly, terribly wrong. :) Probably early summer. I propose to take you out to lunch to discuss the results of the code review.

No problem. I've personally been doing SwiftUI work, but we have three excellent Flutter developers at Ikayzo. I've also been doing lots of app design / branding and UXD work (examples from our flamboyant Niftia customer attached.)


From: Jenna Deane

Hi E1,

Thanks for the feedback and tips! You mentioned how in rural Japan gardeners operate as a "unified community gardening engine" and that is exactly what this technology aims to do. Do I need to plan a trip to Japan to research? Obviously yes. ;)

I appreciated the emphasis on gamification - contests, badges, incentives for good recording of growing data and how that can intersect with community building. I have Spotify and love their "Wrapped" year in review. Getting interesting stats about your growing habits could be a fun element to add down the line (once we have growing habits to aggregate data from!) and could be provided in a shareable format.

I love the cross generational passing of skills between your child and his grandma! I really enjoyed working with kids in school gardens and often it was the grandparents who were most engaged with the garden. It makes me think - this technology could help the youth document the gardening practices and of their elders. What important data to record!

Very cool Niftia design! So sleek and who doesn't love a peacock? Made me want to visit the site to learn more. NFTs!

Thanks again, Jenna


Hi Jenna,

Thanks for the feedback and tips! You mentioned how in rural Japan gardeners operate as a "unified community gardening engine" and that is exactly what this technology aims to do. Do I need to plan a trip to Japan to research? Obviously yes. ;)

Absolutely. I recommend doing your research in Okinawa at the Club Med in Ishigaki. There are plenty of people with gardens in the vicinity. It will be rough, but someone has to do it.

I appreciated the emphasis on gamification - contests, badges, incentives for good recording of growing data and how that can intersect with community building. I have Spotify and love their "Wrapped" year in review. Getting interesting stats about your growing habits could be a fun element to add down the line (once we have growing habits to aggregate data from!) and could be provided in a shareable format.

That sounds great. I'd like to have that data for our community garden and grandma's gardens.

I love the cross generational passing of skills between your child and his grandma!

:-) It is a precious thing. She taught him all kinds of interesting things like how to find bamboo shoots that are the right size for digging up, and how to prepare and cook them. They are an amazing bright purple.

I really enjoyed working with kids in school gardens

That is a lot of fun, and it's a great opportunity to teach botany, chemistry, entomology and a dozen other things! Also, after spending all day on a computer, it's nice to just sink your hands into the land.

and often it was the grandparents who were most engaged with the garden.

That is certainly true in our family, and many of our boys' friends' families as well. They learn gardening from grandma and / or grandpa.

It makes me think - this technology could help the youth document the gardening practices and of their elders. What important data to record!

Absolutely. It is a great opportunity to preserve traditional gardening practices.

Very cool Niftia design! So sleek and who doesn't love a peacock? Made me want to visit the site to learn more. NFTs!

Thank you. It was / is a fun project. I think our customer was unlucky with the timing given the precipitous fall in value, but hopefully they'll make a comeback. He is very upset that Stephen Colbert makes fun of NFTs every other night. I haven't told him Stephen is my favorite comedian.

Entrepreneur 2 (E2)

I had a zoom call with E2 this morning. They seemed pretty enthusiastic about our direction and didn't bring up any warning signs about our approach and process. They had a number of interesting thoughts which I will summarize as follows:

  1. Consider "local first". They are building apps that cache data locally first, and then later upload to the web. This can improve performance, but more profoundly, can provide for much more data privacy than a traditional "sync to the cloud immediately" approach.

  2. They think it's important for us to first ensure that we design an app that folks view as a "trusted friend"---that the data is safe, that it will be there for them later in a useful form. Only then will the "social" aspects become relevant.

  3. They made a very interesting comment---they've recently taken over a plot in a community garden, and wishes there was a way to communicate and coordinate with the other folks in that community. This is kind of like a "micro-chapter". I just had the idea that we might want to support hashtags for gardens. Then folks could add #makiki-community-garden (or whatever) to their garden and filter all the analyses by that hashtag. That would basically enable users to self-organize into smaller groups within a chapter.

Entrepreneur 3 (E3)

GGC looks like a cool project, fun that you get to work with Jenna! It looks like an interesting niche, but getting a critical mass of users seems like it would be challenging in general because of the intersection of serious gardeners and the geographic span of chapters (walking & biking distance!) I would worry that it would be hard to get that combination in many places, since most apps / Internet tools thrive by having few if any geographic boundaries, but that is not possible for GGC (by design). I wonder if you could appeal to both novice & serious gardeners as a way to build community to get to critical mass, and perhaps explore the pathways by which a novice becomes serious (which I assume is a thing).

GGC Motivation page notes:

  • I would add something about using technology to introduce some of the benefits of community gardening to home gardens to the TL;DR section of the Mo, since that seems like the core problem GGC is attempting to solve.
  • I think Food Shift has shifted from the term "food deserts" to "food apartheid", since they are often the result of systemic racism, not just a natural feature of a landscape (though humans make deserts too! 😦
  • I see the Agile Garden Club name is still in the diagram in the Motivation page

Entrepreneur 4 (E4)

I want to try to recapture E4’s excellent napkin points. All I can remember so far this morning is:

  1. Avoid conflating “food resilience” (direct hit for GGC) with “food insecurity” (lots of structural barriers for GGC).
  2. Need more BIPOC representation.
  3. A hyperlocal orientation does not have to preclude other dimensions for data sharing.
  4. First page would be better written in terms of “assets” that GGC wants to help leverage better than they are currently, rather than the current “academic grant proposal introduction section” (which is the only way I know how to write first pages.)

Other points (by E4):

  1. Include social enterprises that run urban/community farms as part of their programming
  2. use tags for profiling gardening conditions, community types etc to make effective and appropriate global sharing
  3. ensure features in the platform to be able to easily share the harvest (there is significant food wasted at all stages of the food supply chain, starting with community farms).
  4. pair up with nonprofits and write grants. The license fee can be included in the grant
  5. not just BIPOCS but include people with lived experience of food insecurity in the leadership Btw I read research stating that asset framing language yields more funding (donations and grants) so maybe it works for academic grants, too.

Entrepreneur 5 (E5)

(E5 is a commercial nursery owner, and so has both entreprenurial and gardening experience.)

Aloha Philip,

I'm taking a look at the info.

In your outcome data section, the rating criteria is a great idea. Based on my personal experience, you may consider revising your ratings on pest and disease resistance. I've seldom seen garden plants without some damage. In commercial agriculture, with pests you generally do not attempt to control the pest unless it is causing significant economic damage, i.e. a few holes etc are not a reason. Only if the damage will significantly affect yields. Home gardeners should not expect fruits and veg to look like they do in the grocery store.

Outcome Appearance- as above they won't look like grocery store produce. Many places are now marketing ugly fruit and veg. It reduces food waste and cost. Some of the best tomatoes I've eaten have been ugly but the best tasting. Just a thought, my citrus is generally ugly due to insect damage on the skins, but the flavor is unaffected.

Seed saving/sharing- The vast majority of seeds have been crossbred for various attributes such as disease resistance, nematode resistance, etc.. The seeds from these plants may or maynot have the same characteristics. Commercial tomatoes were bred to withstand a 5 mile per hour impact for machine harvesting but they didn't bother to consider flavor as a criteria. Heirloom plants are open pollinated and are what we call pure line in that the seeds from the plants produce very similar to exactly the same genetically. So, sharing heirloom seeds is good but not hybrid seed.

I see that you have Charlie Reppun on your team. I don't really know him but did hang out with his brother when we lived on the big island.

I hope this was somewhat helpful.

- + \ No newline at end of file diff --git a/docs/develop/roadmap.html b/docs/develop/roadmap.html index 4f7505351..4cbe5b238 100644 --- a/docs/develop/roadmap.html +++ b/docs/develop/roadmap.html @@ -5,13 +5,13 @@ Roadmap | Geo Garden Club - +

Roadmap

The Geo Garden Club project is organized into the following subprojects. Note that future dates are estimates. Also note that the evaluation periods for the alpha and beta releases overlap in time with the development of the following (beta, 1.0) releases.

DatesSubprojectGoal
Summer 2021 - Fall 2022Mockup DevelopmentDesign and implement an executable mockup to illustrate design innovations.
Mockup Evaluation (Customers)Evaluate the business concept through interviews with experienced gardeners.
Mockup Evaluation (Entrepreneurs)Evaluate the business concept through interviews with entrepreneurs.
Fall 2022 - Spring 2024Alpha release developmentBuild a mobile application implementing the Core Value Propositions.
Spring 2024 - Winter 2024Alpha release evaluationEvaluate the ability of GGC to fulfill the Core Value Propositions through use of the Alpha version with 3-4 gardeners.
Beta release developmentBuild a second release incorporating improvements identified through Alpha release evaluation.
Spring 2025 - Winter 2025Beta release evaluationValidate commercial viability of project through deployment of a free version to (say) 100 gardeners in a single chapter (i.e. Whatcom-WA).
1.0 release developmentBuild the 1.0 release. Support multiple chapters and subscription-based payments.
Spring 20261.0 release deploymentGeneral release of the system to multiple chapters. Subscription payments enabled.
- + \ No newline at end of file diff --git a/docs/home/innovations.html b/docs/home/innovations.html index 8b5cf0edd..fb60b7ed0 100644 --- a/docs/home/innovations.html +++ b/docs/home/innovations.html @@ -5,13 +5,13 @@ Design Innovations | Geo Garden Club - +

Design Innovations

tl;dr

Geo Garden Club includes the following design innovations:

  1. Garden data is aggregrated within local geographic regions called "Chapters".
  2. Access control enables collaborative garden planning and management.
  3. Multi-year garden timelines facilitate experience-based improvement.
  4. Chapter timelines facilitate discovery of local "best practices".
  5. Notifications and observations provide context-specific chapter communication.
  6. Outcome data supports improvement within a single garden and across the chapter.
  7. Support for seed saving and seed sharing.
  8. The public view provides garden owners with controlled, public, read-only access.
Mockup screenshot alert

To illustrate our design innovations, this page uses excerpts from our web-based mockup.

See the Mobile App Sneak Peek page for screenshots of our mobile app, now under development!

1. Garden data is aggregated within local geographic regions called "Chapters".

Each GGC garden is associated with a "Chapter", which collects together a set of gardens that share the same geographic region and (mostly) similar climate. Just as important, the gardeners associated with a Chapter share the same geographic region: they are within walking (or biking) distance of each other.

Chapters are used by GGC to organize and limit the kinds of data sharing. Garden data is only shared within a Chapter. This means that data about plants, outcomes, and timing are all local to your garden's immediate geographical region.

We anticipate that a Chapter can be "viable" with as little as a few dozen members. By viable, we mean that the collective data gathered and shared among Chapter members is sufficient to improve decision making and garden improvement, and that communication among Chapter members succeeds in creating a local "community of practice".

On the other hand, we anticipate that if a Chapter grows beyond a few hundred members, then it might be advantageous to subdivide it into two smaller Chapters.

2. Access control enables collaborative garden planning and management.

Similar to other cloud-based document management systems, GGC enables collaborative access and management of garden data. Gardeners can be have one of three roles: "owner" (with full access to the garden, including the ability to add other gardeners, modify roles, and delete the garden), "editor" (allowing the gardener to add and edit data), and "viewer" (allowing read-access only).

3. Multi-year garden timelines facilitate experience-based improvement.

GGC is oriented to the needs of gardeners who want to improve their gardens over multiple seasons, and thus need to compare and contrast their efforts over multiple years. An important way to represent a garden is via a timeline, which specifies the contents of the garden for each year as well as important dates during the lifecycle of a planting (such as start date, first harvest, and pull date.)

The following image shows a timeline view of a portion of a garden in Bellingham, WA during 2022:

Timeline data can provide many insights, particularly when multiple years of garden data are available. For example, it is useful to rotate the crops planted in a bed each year in order to mitigate certain pests, diseases, and soil nutrient imbalances. The "Bed" timeline view makes it easy to review what plants have been in a particular bed over time:

Notice that GGC color codes each plant variety according to its family (i.e. pink for the Gourd Family, brown for the Legume family, etc). The above timeline illustrates how this gardener rotated crops in Bed 11 over the past three years, ensuring that different plant families were grown in the bed each successive year.

4. Chapter timelines facilitate discovery of local "best practices".

One way to improve garden productivity is by learning best practices in your local geographic region for the timing of planting. GGC Chapter Timelines provide a simple way to view timing data for your own garden, then compare it to timing data across the entire chapter.

In the example image above, we can see that this gardener has planted broccoli only during Week 16 (i.e. between April 15-21) and the latest they left their broccoli was Week 29 (July 22-30). The Chapter Timeline shows that there are gardeners in the Chapter who have planted broccoli as early as Week 7 and left the broccoli in the ground until the end of the year.

This chart alone is not enough information for the gardener to decide what to do, but it is enough information to start a conversation within the Chapter about the timing of broccoli if the gardener wants to change their practices.

5. Notifications and observations provide context-specific chapter communication.

GGC allows gardeners to make "observations" regarding a planting of a plant variety on a specific day.

Observations can include phenomena such as successful germination, first flower, first harvest, diseases, or pests.

Observations can be automatically converted into "Notifications", which are made available to other gardeners in the same chapter growing the same plant variety. For example, this Observation regarding Matina Tomatoes could produce a notification for other gardeners growing Matina Tomatoes in that chapter to inform them that leaf curl has been found to be a problem. This, in turn, could lead to communication between gardeners in this chapter if an effective approach to management of leaf curl for Matina Tomatoes is known.

6. Outcome data supports improvement within a single garden and across the chapter.

An important mechanism for improvement is assessment of outcomes: How well did a single planting do? And what insights can be gained from aggregating outcome data from multiple plantings during a single season, or multiple plantings over multiple seasons, or multiple plantings across the entire chapter?

Outcome data is always created with respect to a single planting. For example, this image shows a summary of a single planting of Rainbow Chard during 2021, including the outcome data that the gardener assigned to it.

Up to five outcome types can be associated with a planting: Appearance, Flavor, Germination, Resistance (to pests and/or disease), and Yield.

Every outcome type is assigned a value based on a five point scale: 1 is the worst, and five is the best. This image provides a visualization of outcome data using stars. In this case, Appearance was assigned 5 (the highest value), and Germination was assigned 2. If the gardener had chosen to not assign a value to one or more of the outcome types, then all the stars would be grey, indicating no outcome data of that type is available.

In order to combine outcome data together and produce meaningful results, it's crucial to define criteria for each numeric rating for each outcome type so that gardeners assign outcomes in a consistent manner. The following table provides the GGC criteria for assigning 1, 2, 3, 4, or 5 for each of the five outcome types.

Once outcome data exists for a set of plantings, then they can be combined to show the spectrum of outcomes associated with a plant variety (or crop) for the current garden or across all gardens in a chapter. GGC provides a visualization of the spectrum of outcome data as a horizontal stacked bar chart, where dark red is 1, light red is 2, grey is 3, light green is 4, and dark green is five. Here is an example for all of the Bean plant varieties:

So, the above chart reveals that bad Bean outcomes are unlikely but have still occurred for Appearance, Flavor, and Yield. Beans show uniformly good Resistance, and pretty good Germination.

Selecting subsets of years makes it possible to see how outcomes are distributed in time and if the distributions of outcomes are different depending upon the year.

7. Support for seed saving and sharing

We believe that an important step toward food resiliency is to develop local networks for seed production and sharing.

To that end, GGC enables gardeners to indicate whether or not they are saving seeds from a particular planting, and if so, whether they have enough seeds that they are willing to share them with the local chapter.

The planting card at left indicates that this gardener has both saved seeds from a specific planting of Lettuce, and they have enough seeds to share some with the Chapter.

Seed saving and sharing has another implication: when growing a plant for seeds, you will sometimes need to leave it in the garden after there is nothing more to harvest. So, in GGC, there is the ability to indicate and "End Harvest" date as well as an "End" (i.e. Pull) date.

We can see this in the planting card above, as well as in the timeline view for that planting of lettuce:

The timeline bar is blue from January to mid-June, indicating that this gardener was actively harvesting lettuce for that entire period. But from mid-June to mid-July, the timeline bar switches to green, indicating that there is no longer any harvest but the plant is still growing (in this case, to produce seed). Reference to the planting card reveals that the harvest ended on 6/15/22 and the lettuce was pulled on 7/20/22.

GGC can thus provide a new insight to gardeners: how long does it take not just to grow a seed to first harvest (which is typically provided on the seed packet) but also how long that plant yields harvest and, significantly, how long is required to yield seeds?

8. The public view provides garden owners with controlled, public, read-only access.

We are currently developing a mobile app that members of Geo Garden Club will use to view and enter garden data and communicate with other members of the Chapter associated with each Garden.

In addition, we will implement access control mechanisms so that owners of a garden can control which other members of GGC can interact with garden data.

The requirement to download and install a mobile app, join GGC, and obtain access from the owner in order to see garden data creates a fairly high barrier to garden data. While this might be necessary and appropriate for active participants in a garden, it also erects a "walled garden". What if a gardener simply wants to ask a question in the Reddit "vegetablegardening" group and needs to provide some details about their garden?

GGC allows the owner of each garden to enable a web-based "public view" of the garden (and its associated chapter). In fact, all of the images on this documentation page were taken from a public view. The public view is designed to allow the gardener to provide details about a garden without revealing its exact location or the identity of gardeners associated with it.

Here is an example of a portion of a public view which is available at https://agilegardenclub.com/public-garden/?name=45ght3cf

Garden owners opt-in to the public view, it is not enabled by default. While this site provides access to a couple of public views for documentation purposes, GGC will not provide a directory of public views, and so it is not likely that a person can find a public view without having been given the URL to it.

- + \ No newline at end of file diff --git a/docs/home/motivation.html b/docs/home/motivation.html index 36fc9e281..c16325659 100644 --- a/docs/home/motivation.html +++ b/docs/home/motivation.html @@ -5,13 +5,13 @@ Motivation | Geo Garden Club - +

Motivation

tl;dr

Food insecurity is an important problem in the U.S. and globally.

Home gardens are an important, underutilized resource for addressing food insecurity.

Geo Garden Club is designing and implementing collaborative technologies to improve the efficiency and effectiveness of home gardeners.

Food security, home gardens, and GGC

Food security, as defined by the United Nations’ Committee on World Food Security, means that all people, at all times, have physical, social, and economic access to sufficient, safe, and nutritious food that meets their food preferences and dietary needs for an active and healthy life. In the coming decades, food security will become an increasingly critical issue due to population growth in combination with climate change, the latter of which which will negatively impact agricultural water availability, arable land availability, and the diversity and distribution of agricultural plant, insect, and animal species (Kwasek, 2012).

Food insecurity is not only an issue for the distant future or for underdeveloped countries. In 2019, an estimated 1 in 8 Americans were food insecure, equating to over 38 million Americans, including almost 12 million children (Coleman-Jensen, 2019).

(Galhenia et al, 2013) provides evidence that home gardens can improve food security: "... Benefits of home gardens include enhancing food and nutritional security in many socio-economic and political situations, improving family health and human capacity, empowering women, promoting social justice and equity, and preserving indigenous knowledge and culture." In addition, "the most fundamental social benefit of home gardens stems from their direct contributions to household food security by increasing availability, accessibility, and utilization of food products". According to (Rai, 2020), home gardens can also strengthen numerous ecosystem serviecs, including plant biodiversity, microclimate, water runoff, urban soil restoration, and water quality. Finally, home gardens can play a significant role in combatting "food deserts", areas in which it is difficult to buy affordable or good-quality fresh food (Palar et al., 2019).

Community gardens are similar to home gardens in scale and the types of food products grown, but community gardens create and foster "communities of practice" with significant health consequences: In a study by (Alaimo et al, 2008), community gardeners consumed fruits and vegetables 5.7 times per day, compared with home gardeners (4.6 times per day) and nongardeners (3.9 times per day). Moreover, 56% of community gardeners met national recommendations to consume fruits and vegetables at least 5 times per day, compared with 37% of home gardeners and 25% of nongardeners.

A fundamental goal of Geo Garden Club (GGC) is to address food insecurity by increasing: (a) the numbers of home gardens (and home gardeners), (b) the productivity of home gardens, and (c) the ability of home gardens to improve human health. To accomplish this, we are designing technology to not just facilitate home garden planning and implementation, but also to facilitate the creation of local "communities of practice" for home gardening. If successful, GGC home gardeners will (among other things) reap the health benefits currently enjoyed by community gardeners.

Our target demographic: the "serious" gardener

We view food production as a spectrum of activities and levels of commitment, as shown in the following diagram:

On the far left side are "recreational" gardeners. These are people who are either just getting into gardening, and/or are relatively uncommitted to gardening. There are a variety of technologies (websites and applications) oriented to the needs of "recreational" gardeners.

On the right side are "farmers": those who make most or all of their living from growing food. Unlike gardeners, farmers cannot operate at a loss. There are also a variety of technologies available to support the needs of small scale farmers (i.e. "urban agriculture") as well as large scale farmers ("industrial agriculture").

We call our target demographic the "serious gardener": a gardener who hopes to grow significant amounts of food, to improve their garden on a season-by-season basis, and who is open to sharing their experiences with other gardeners and learning from other gardener's experiences. A serious gardener is not necessarily an "expert" gardener. In fact, one can be both a serious gardener and an absolute beginner! Serious gardeners are defined by intent, not skill level.

Improving a garden from year to year has multiple facets, including:

  • Better choice of plant varietals to improve yield or pest/environmental resistance
  • Better planning of bed contents (soil/amendments and plant varietals) and sequencing of planting to improve outcomes (yield, flavor, timing of harvest, reduced pests, etc.)
  • Better use of resources (i.e. growing season, bed size, water, nutrients)

There are two basic approaches used by a serious gardener to improve their garden:

  1. Individual experimentation and record keeping. A serious gardener tries to learn from their experience over multiple growing seasons. They may keep informal records to provide a more data-driven approach to improvement.

  2. Collective interaction with a "community of practice". Most serious gardeners develop some sort of informal community of fellow-minded gardeners to whom they discuss issues and share experiences in hopes of improving their collective garden experiences. Traditionally, these communities of practice took the form of garden clubs, such as the Garden Club of America. More recently, communities of practice can take the form of local Facebook groups, or even global forums like the Reddit r/vegetablegardening forum. Interaction with others can also increase the enjoyment of gardening and provides motivation.

Interestingly, this classification scheme reveals a technology gap: there is no technology designed to address the needs of gardeners who have more sophisticated goals than recreational gardeners, but who are not interested in running a business based on growing and selling food. Geo Garden Club is targeting this market and technology niche.

The impact of improved garden knowledge

Improving the ability of gardeners to learn effective gardening practices has been shown to facilitate participation in gardening. A study of the socio-behavioral drivers of growing produce at home (Grebitus, 2021) found that knowledge of gardening practices was a significant factor. "...increased knowledge leads to increased participation in home and community gardens. Hence, we need to educate future gardeners, to increase their knowledge and ability to participate safely in small-scale urban agriculture, as stressed by Kortright and Wakefield, who suggested that home food gardeners could be supported with regard to acquiring ecological gardening skills and to general learning opportunities. Lack of knowledge can increase the risk for those who are unaware of safe gardening practices, for example the risk of soil contaminants."

- + \ No newline at end of file diff --git a/docs/home/related-work.html b/docs/home/related-work.html index 2f31430a0..2118c65bf 100644 --- a/docs/home/related-work.html +++ b/docs/home/related-work.html @@ -5,13 +5,13 @@ Related work | Geo Garden Club - +

Related work

Garden Planning Tools

If you search for "garden planning tools" on the Internet, you'll find dozens of applications. Most of those are essentially "landscape architecture" tools for people who want to design the visual look of their (flower) gardens. This is an interesting design problem, but not the problem addressed by Geo Garden Club.

If you narrow the search to say, "vegetable garden planning tools", you'll still find many that focus on the visual look of the garden bed, but there are a few that focus on the kinds of issues of interest to GGC. Here are the most relevant applications we have found:

Name/URLUsersCost
GPGarden Planner500K+$29-$40/year
TSTerritorial Seed?$29-$40/year
VPVegPlotter?Free
GPPGarden Plan Pro20K+Free version (1 bed), $19.99 one time purchase, $1.99/month subscription
GMGarden Manager20K+Planner: $0. Coach: $6/mo, Coach+Online Library, webinars, members only chat forum: $7.5/mo
GIGrow It!700KOut of business (?)
PMPlants Map?Free plan, or $49-$99/year
SGSmart Gardener?$10/3 months; $30/year
GSGoogle Sheets?Free

Some general observations about garden planning tools:

  • Many of these sites focus primarily the needs of "recreational" or "beginner" gardeners, and/or focus on garden construction.
  • The social media integration for the some of the apps is questionable. Why "like" a picture of a plant?
  • The gardener-to-gardener communication channels are quite primitive, consisting of posting to Facebook or publishing journal entries.
  • Most tools have a very limited free tier, with a typical paid subscriber base at $1-$3/month.
  • Some tools tend to be underwritten by seed vendors, and so the planning tool is oriented toward marketing and seed sales.

As noted before, a popular tool for serious gardeners is a spreadsheet such as Google Sheets or Excel, perhaps in conjunction with a document editor (Google Docs or Word). This combination of tools is free and very flexible, but lacks any domain-specific functionality.

Urban Agriculture Tools

"Urban Agriculture" is a general term for cultivating, processing, and distributing food in or around urban areas. These tools are distinguished from home garden planner tools by a focus on more professional, market-oriented approach to small-scale farming.

Name/URLUsersCost
LFLiteFarm, wiki, github1000sFree, Open Source
COGCOG-Pro?$79-$159/year
VTVeggieTables?$89/year + $19/additional user
ASAgSquared SimpleFarm1000s$10/user/month
FBFarmbrite1000s$15-$30/month
TTend?$30-39/month
FSFarmStatistics?$20/year
ADAgritecture Designer120$30-80/month

Some general observations about urban agriculture tools:

  • These tools all emphasize (and provide support for) commercial, for-profit farming (albeit on a small scale).
  • Several focus on record-keeping required for organic certification.
  • Several focus on people management.
  • None have mechanisms to share data with neighboring farms.

Citizen Science technologies

There are several tools available to support citizen science as it relates to climate change:

Name/URLUsersCost
NNNature's Notebook1000sFree
SFSmartFin?Free

Our goal is for GGC to complement existing approaches to Citizen Science. We would like to work with these organizations to determine the best wa for GGC to collect data to augment current data sets and make them more valuable to researchers.

How does GGC fit in?

Analysis of the technology landscape reveals that there are basically two clusters of features: "Novice" features that are associated with the garden planning tools, and "professional" features that are associated with the urban agriculture tools.

The market niche for GGC is between these two areas:

  • "Beyond Novice". GGC gardeners have generally solved the "layout problem", and are interested in more sophisticated record keeping than is available in current garden planning tools.

  • "Non-professional". GGC gardeners do not require people management technology. In addition, in a professional setting, local data sharing could be undesirable to farmers as it might reveal competitive secrets. GGC gardeners are in a non-competitive environment where data sharing within the community has little downside.

- + \ No newline at end of file diff --git a/docs/home/sneak-peek.html b/docs/home/sneak-peek.html index ceedafa28..bf81b3bb6 100644 --- a/docs/home/sneak-peek.html +++ b/docs/home/sneak-peek.html @@ -5,13 +5,13 @@ Mobile App Sneak Peek | Geo Garden Club - +

Mobile App Sneak Peek

We are working on the alpha release of the GeoGardenClub mobile app, with an expected release date of early 2024. While the app is not yet ready for prime time, we thought it would be fun to show you some selected screen shots so you can get an idea of where we're heading.

Login

As with all mobile apps, you will be asked to sign in and/or register when you download the app.

Home (Tasks View)

After logging in, you will come to your home screen. The home screen has a bottom navigation bar providing four views: Tasks, Gardens, Chat, and Observations. Let's look at each of these in turn.

The Home Screen "Tasks" View provides a kind of "To Do" list. Most entries are automatically generated from your garden plan(s), although you can add Tasks manually if you wish.

Tasks in red are "overdue" according to your garden plan. If your garden isn't growing according to your current plan, you can easily correct the task date. Easily maintaining accurate records of important planting events (sowing, first harvest, pull date, etc) is a design goal of GGC.

Home (Garden Summary View)

The Home Screen "Gardens" View provides a summary of all of the gardens that you own or have been given access to by the owner.

You can click the "Details" button to get more information about a specific garden. (More below.)

Home (Chat View)

The Home Screen "Chat" View provides access to a set of system-managed Chat rooms to facilitate communication between gardeners.

Gardeners cannot create their own chat rooms. Instead, the system defines one Chat room for the Chapter, with access granted to all members of the Chapter. In addition, the system creates a chat room for each Garden in the Chapter, and access to each of those chat rooms is granted to the owners and editors of that garden.

Home (Observations View)

The Home Screen "Observations" View provides a kind of "Instagram-ish" scrolling list of photos made by yourself and other gardeners in the Chapter.

Observations allow gardeners to document interesting events in their garden, post questions to other gardeners in the chapter, or simply post beautiful pictures for all to enjoy.

Garden Details (Timeline View)

The Garden Details Screen "Timeline" View provides a perspective on the chronological ordering of plantings in your garden.

You can zoom in to display the garden at 6 month or 1 month views. Each planting can be in one of four phases: in the greenhouse, growing in a bed, available for harvest, or being left to produce seeds. Any of these phases are optional.

Garden Details (Filter View)

The Garden Details Screen "Filter" View provides a perspective on your garden over multiple years.

For example, this screenshot allows the gardener to review what varieties of beans they have planted over the past four years, and the timings associated with each of them.

There is interesting information here, from the range of times when beans were planted, to which years the beans were left to seed!

Garden Details (Outcomes View)

The Garden Details Screen "Outcomes" View provides access to the Outcome data associated with the plantings in your garden.

This can help you experiment and refine the varieties you choose to plant and the way you plant them in order to optimize one or more outcome measures.

Garden Details (Tasks View)

The Garden Details Screen "Tasks" View provides access to the tasks associated with this specific garden. (The Home Page Tasks View shows all of the tasks associated with all of your gardens.)

Chapter Summary Screen

The Chapter Summary Screen provides a summary of the gardens, gardeners, and other information associated with the Chapter.

Every GeoGardenClub user is a member of a Chapter. Chapters are organized based on a small number of adjacent zip codes.

By organizing users into chapters, we believe that it will be easier to share useful information with each other, and enable activities such as seed sharing that support food resilience.

Chapter Gardeners Screen

The Chapter Gardeners Screen provides a kind of "Directory" for the members of the chapter

For privacy purposes, users get to pick a unique "username" when they register, which is the only way they are identified to other gardeners in the Chapter. User can optionally provide a photo (which may or may not be a headshot).

Chapter Gardens Screen

Like the Chapter Gardeners Screen, the Chapter Gardens Screen also provides a kind of "Directory", but this is for the Gardens in the Chapter, not the Gardeners.

there's more to come...

We hope you enjoyed this sneak peek of the GGC app. We want you to know that we plan on providing a variety of other interesting features in the alpha release, such as Badges, Seeds, Chat Rooms, and Themes. Stay tuned!

- + \ No newline at end of file diff --git a/docs/home/team.html b/docs/home/team.html index 2b4158f08..bcd6a1a0d 100644 --- a/docs/home/team.html +++ b/docs/home/team.html @@ -5,13 +5,13 @@ The Team | Geo Garden Club - +

The Team

Jenna Deane

Jenna Deane has been a "serious" gardener and garden educator for over 15 years, and is currently Zero Waste Program Manager for Sustainable Connections in Bellingham, WA. Previously, she was the Education Program Manager at Common Threads Farm, where she supervised and trained over 20 Americorps volunteers each year to design and implement garden education programs in local schools. Prior to that, she was the Garden Coordinator at Adelante Spanish Immersion School in Redwood City, CA where she managed their school garden program. Jenna received a B.A. in Environmental Studies from Western Washington University.

Philip Johnson

Philip Johnson is a Professor of Information and Computer Sciences at the University of Hawaii. He has over 30 years of experience in software engineering research and education. He has co-founded two startups, participated in multiple business accelerator and incubator programs, and led a variety of sustainability-related research initiatives. Philip received B.S. degrees in Biology and Computer Science from the University of Michigan and a Ph.D. in Computer Science from the University of Massachusetts.

Carleton (Cam) Moore

Cam Moore is a Professor of Information and Computer Sciences at the University of Hawaii. His background includes extensive experience in both academia and industry. His prior industry experience includes software engineering positions at Lockheed Martin and Orincon and co-founder of a software startup. His academic experience includes ACUE certification in College Education. Cam received a B.S. degree in Electrical Engineering and Computer Science from the University of Colorado and a Ph.D. in Computer Science from the University of Hawaii.

Joseph Dane

Joseph Dane is a lawyer at Goodsill Anderson Quinn and Stifel in Honolulu, HI. He has advised business and non-profit organizations on tax matters and on corporate law questions of internal governance and structure. He has also advised local companies on issues involving intellectual property, copyright law, and general commercial contracting. Prior to becoming a lawyer, Joseph was a software engineer for NOAA and co-founded a software startup. Joseph received a B.S. in Physics from the University of Irvine, an M.S. in Computer Science from the University of Hawaii, and a J.D. from the University of Hawaii William Richardson School of Law.

Advisory Board

  • Mercedez Castro is a software engineer in Seattle, WA.

  • Charlie Reppun is a farmer and owner of Waianu Farm in Waiahole, Oahu.

  • Katie Amberg-Johnson is a Scientist at Schrodinger in New York City.

  • Jessie Beck is a home gardener in Bellingham, WA.

- + \ No newline at end of file diff --git a/index.html b/index.html index ac609aed2..c2e8b5725 100644 --- a/index.html +++ b/index.html @@ -5,13 +5,13 @@ Geo Garden Club | Geo Garden Club - +

Growing better gardens, gardeners, and communities, one plant at a time

Unlock insights from your personal experience

Home gardening over multiple seasons yields many useful insights. GGC provides new ways to gather and reflect on your home garden's history in order to improve your future gardening outcomes.

Unlock the collective wisdom of your community

GGC facilitates the creation and management of local "communities of practice" allowing members to more easily share garden outcome data and best practices with each other.

Improve local food production and practices

Home gardens are an important and underutilized resource for increasing community resilience, health, and emotional well-being. GGC provides new ways for home gardens and gardeners to create a "virtual" local community garden.

- + \ No newline at end of file diff --git a/markdown-page.html b/markdown-page.html index fcd075bf0..25a4b3ed5 100644 --- a/markdown-page.html +++ b/markdown-page.html @@ -5,13 +5,13 @@ Markdown page example | Geo Garden Club - + - + \ No newline at end of file