diff --git "a/claims/1791_brugui\303\250re.jsonl" "b/claims/1791_brugui\303\250re.jsonl" index cee23bd..cce74eb 100644 --- "a/claims/1791_brugui\303\250re.jsonl" +++ "b/claims/1791_brugui\303\250re.jsonl" @@ -6,7 +6,7 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1791_bruguière:0/children/2:usage", "kind": "usage", "path": "0/children/2", "printedAttribution": "as-record", "source": "1791_bruguière", "spelling": "mollusca", "subject": "mollusca", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1791_bruguière:0/children/2:placement", "kind": "placement", "parent": "vermes", "path": "0/children/2", "position": 2, "printedAttribution": "as-record", "rank": "Order", "rankAsPrinted": "Order", "source": "1791_bruguière", "subject": "mollusca", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1791_bruguière:0/children/3:usage", "kind": "usage", "notes": "This (or possibly Bruguière 1789, a copy of which is not available) is the first assignment of a Linnaean rank to Echinodermata, and therefor the first formal use and often cited as the taxon's authority. Klein 1778 (a reprint of Klein 1734) is an earlier contender without a rank.\n", "path": "0/children/3", "printedAttribution": "as-record", "source": "1791_bruguière", "spelling": "echinodermata", "subject": "echinodermata", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1791_bruguière:0/children/3:placement", "kind": "placement", "notes": "This (or possibly Bruguière 1789, a copy of which is not available) is the first assignment of a Linnaean rank to Echinodermata, and therefor the first formal use and often cited as the taxon's authority. Klein 1778 (a reprint of Klein 1734) is an earlier contender without a rank.\n", "parent": "vermes", "path": "0/children/3", "position": 3, "printedAttribution": "as-record", "rank": "Unranked", "rankAsPrinted": "Order", "source": "1791_bruguière", "subject": "echinodermata", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1791_bruguière:0/children/3:placement", "kind": "placement", "notes": "This (or possibly Bruguière 1789, a copy of which is not available) is the first assignment of a Linnaean rank to Echinodermata, and therefor the first formal use and often cited as the taxon's authority. Klein 1778 (a reprint of Klein 1734) is an earlier contender without a rank.\n", "parent": "vermes", "path": "0/children/3", "position": 3, "printedAttribution": "as-record", "rank": null, "rankAsPrinted": "Order", "source": "1791_bruguière", "subject": "echinodermata", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1791_bruguière:0/children/3/children/0:usage", "kind": "usage", "path": "0/children/3/children/0", "printedAttribution": "as-record", "source": "1791_bruguière", "spelling": "asterias", "subject": "asterias", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1791_bruguière:0/children/3/children/0:placement", "kind": "placement", "parent": "echinodermata", "path": "0/children/3/children/0", "position": 0, "printedAttribution": "as-record", "rank": "genus", "source": "1791_bruguière", "subject": "asterias", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1791_bruguière:0/children/3/children/1:usage", "kind": "usage", "path": "0/children/3/children/1", "printedAttribution": "as-record", "source": "1791_bruguière", "spelling": "echinus", "subject": "echinus", "tree": "taxonomy"} diff --git a/claims/1798_cuvier.jsonl b/claims/1798_cuvier.jsonl index 88f0a38..bb3bd2f 100644 --- a/claims/1798_cuvier.jsonl +++ b/claims/1798_cuvier.jsonl @@ -74,7 +74,7 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1798_cuvier:0/children/7:usage", "kind": "usage", "notes": "zoophyta-class", "path": "0/children/7", "printedAttribution": "as-record", "source": "1798_cuvier", "spelling": "zoophytes-class", "subject": "zoophytes-class", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1798_cuvier:0/children/7:placement", "kind": "placement", "notes": "zoophyta-class", "parent": "animalia", "path": "0/children/7", "position": 7, "printedAttribution": "as-record", "rank": "Class", "source": "1798_cuvier", "subject": "zoophytes-class", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1798_cuvier:0/children/7/children/0:usage", "kind": "usage", "notes": "Chapter II, Des Zoophytes qui ont une enveloppe cortace ou calcaire,\nun organe respiratoire intérieur distinct,etsouvent despieds\nrétracuiles nombreux; ou des ÉCHINODERMES.\n(These are zoophytes that have a leathery or calcareous outer\novering, a distinct internal respiratory organ, and often numerous\nretractable feet; or Echinoderms.)\n", "path": "0/children/7/children/0", "printedAttribution": "as-record", "source": "1798_cuvier", "spelling": "échinodermes", "subject": "échinodermes", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1798_cuvier:0/children/7/children/0:placement", "kind": "placement", "notes": "Chapter II, Des Zoophytes qui ont une enveloppe cortace ou calcaire,\nun organe respiratoire intérieur distinct,etsouvent despieds\nrétracuiles nombreux; ou des ÉCHINODERMES.\n(These are zoophytes that have a leathery or calcareous outer\novering, a distinct internal respiratory organ, and often numerous\nretractable feet; or Echinoderms.)\n", "parent": "zoophytes-class", "path": "0/children/7/children/0", "position": 0, "printedAttribution": "as-record", "rank": "Unranked", "rankAsPrinted": "Order", "source": "1798_cuvier", "subject": "échinodermes", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1798_cuvier:0/children/7/children/0:placement", "kind": "placement", "notes": "Chapter II, Des Zoophytes qui ont une enveloppe cortace ou calcaire,\nun organe respiratoire intérieur distinct,etsouvent despieds\nrétracuiles nombreux; ou des ÉCHINODERMES.\n(These are zoophytes that have a leathery or calcareous outer\novering, a distinct internal respiratory organ, and often numerous\nretractable feet; or Echinoderms.)\n", "parent": "zoophytes-class", "path": "0/children/7/children/0", "position": 0, "printedAttribution": "as-record", "rank": null, "rankAsPrinted": "Order", "source": "1798_cuvier", "subject": "échinodermes", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1798_cuvier:0/children/7/children/0/children/0:usage", "kind": "usage", "notes": "I les holothuries", "path": "0/children/7/children/0/children/0", "printedAttribution": "as-record", "source": "1798_cuvier", "spelling": "holothuria", "subject": "holothuria", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1798_cuvier:0/children/7/children/0/children/0:placement", "kind": "placement", "notes": "I les holothuries", "parent": "échinodermes", "path": "0/children/7/children/0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "genus", "source": "1798_cuvier", "subject": "holothuria", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1798_cuvier:0/children/7/children/0/children/0/children/0:usage", "kind": "usage", "notes": "L'holothurie timide (H. tubulosa. Lin.)", "path": "0/children/7/children/0/children/0/children/0", "printedAttribution": "as-record", "source": "1798_cuvier", "spelling": "tubulosa_gmelin_1791a", "subject": "tubulosa_gmelin_1791a", "tree": "taxonomy"} diff --git a/claims/1825i_gray.j.e.jsonl b/claims/1825i_gray.j.e.jsonl index 9ab0d29..f6a6cdd 100644 --- a/claims/1825i_gray.j.e.jsonl +++ b/claims/1825i_gray.j.e.jsonl @@ -1,6 +1,6 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "root", "form": "taxon", "id": "1825i_gray.j.e:0:usage", "kind": "usage", "path": "0", "printedAttribution": "as-record", "source": "1825i_gray.j.e", "spelling": "radiata", "subject": "radiata", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1825i_gray.j.e:0/children/0:usage", "kind": "usage", "notes": "Quinarian \"Normal Group\"; determination of Linnaean rank deferred.", "path": "0/children/0", "printedAttribution": "as-record", "source": "1825i_gray.j.e", "spelling": "echinodermata", "subject": "echinodermata", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1825i_gray.j.e:0/children/0:placement", "kind": "placement", "notes": "Quinarian \"Normal Group\"; determination of Linnaean rank deferred.", "parent": "radiata", "path": "0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "Unranked", "rankAsPrinted": null, "source": "1825i_gray.j.e", "subject": "echinodermata", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1825i_gray.j.e:0/children/0:placement", "kind": "placement", "notes": "Quinarian \"Normal Group\"; determination of Linnaean rank deferred.", "parent": "radiata", "path": "0/children/0", "position": 0, "printedAttribution": "as-record", "rank": null, "rankAsPrinted": null, "source": "1825i_gray.j.e", "subject": "echinodermata", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1825i_gray.j.e:0/children/0/children/0:usage", "kind": "usage", "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1825i_gray.j.e", "spelling": "echinida", "subject": "echinida", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1825i_gray.j.e:0/children/0/children/0:placement", "kind": "placement", "parent": "echinodermata", "path": "0/children/0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "Class", "source": "1825i_gray.j.e", "subject": "echinida", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "synonyms", "form": "taxon", "id": "1825i_gray.j.e:0/children/0/children/0/synonyms/0:usage", "kind": "usage", "path": "0/children/0/children/0/synonyms/0", "printedAttribution": "as-record", "source": "1825i_gray.j.e", "spelling": "echinides", "subject": "echinides", "tree": "taxonomy"} diff --git a/claims/1837_hisinger.jsonl b/claims/1837_hisinger.jsonl index 588d9dc..59343bc 100644 --- a/claims/1837_hisinger.jsonl +++ b/claims/1837_hisinger.jsonl @@ -1,6 +1,6 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "root", "form": "taxon", "id": "1837_hisinger:0:usage", "kind": "usage", "path": "0", "printedAttribution": "as-record", "source": "1837_hisinger", "spelling": "radiata-subkingdom", "subject": "radiata-subkingdom", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "citesSource": "1830_cuvier", "form": "taxon", "id": "1837_hisinger:0/children/0:usage", "kind": "usage", "notes": "\"Cuv.\" is given as the 1829 edition of Le Règne Animal in the cited authors list, although note that Echinoderms were covered in Volum III which was delayed until 1830.\n", "path": "0/children/0", "printed": {"citedAs": "A. ECHINODERMATA. Cuv."}, "source": "1837_hisinger", "spelling": "echinodermata", "subject": "echinodermata", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "citesSource": "1830_cuvier", "id": "1837_hisinger:0/children/0:placement", "kind": "placement", "notes": "\"Cuv.\" is given as the 1829 edition of Le Règne Animal in the cited authors list, although note that Echinoderms were covered in Volum III which was delayed until 1830.\n", "parent": "radiata-subkingdom", "path": "0/children/0", "position": 0, "printed": {"citedAs": "A. ECHINODERMATA. Cuv."}, "rank": "Unranked", "rankAsPrinted": "Class", "source": "1837_hisinger", "subject": "echinodermata", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "citesSource": "1830_cuvier", "id": "1837_hisinger:0/children/0:placement", "kind": "placement", "notes": "\"Cuv.\" is given as the 1829 edition of Le Règne Animal in the cited authors list, although note that Echinoderms were covered in Volum III which was delayed until 1830.\n", "parent": "radiata-subkingdom", "path": "0/children/0", "position": 0, "printed": {"citedAs": "A. ECHINODERMATA. Cuv."}, "rank": null, "rankAsPrinted": "Class", "source": "1837_hisinger", "subject": "echinodermata", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1837_hisinger:0/children/0/children/0:usage", "kind": "usage", "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1837_hisinger", "spelling": "stelleridea", "subject": "stelleridea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1837_hisinger:0/children/0/children/0:placement", "kind": "placement", "parent": "echinodermata", "path": "0/children/0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "Family", "source": "1837_hisinger", "subject": "stelleridea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1837_hisinger:0/children/0/children/0/children/0:usage", "kind": "usage", "path": "0/children/0/children/0/children/0", "printedAttribution": "as-record", "source": "1837_hisinger", "spelling": "asterias", "subject": "asterias", "tree": "taxonomy"} diff --git a/claims/1847_hall.jsonl b/claims/1847_hall.jsonl index 3624f0e..0171cd9 100644 --- a/claims/1847_hall.jsonl +++ b/claims/1847_hall.jsonl @@ -49,7 +49,7 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1847_hall:0/children/0/children/5/children/0:placement", "kind": "placement", "parent": "glyptocrinus", "path": "0/children/0/children/5/children/0", "position": 0, "printedAttribution": "as-record", "rank": "species", "source": "1847_hall", "subject": "decadactylus_hall_1847", "tree": "taxonomy"} {"actKind": "new", "audit": {"coverageKind": "newTaxa", "state": "unaudited"}, "id": "1847_hall:0/children/0/children/5/children/0:act", "kind": "act", "path": "0/children/0/children/5/children/0", "printedAttribution": "as-record", "source": "1847_hall", "subject": "decadactylus_hall_1847", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "citesSource": "1846b_buch", "form": "taxon", "id": "1847_hall:0/children/1:usage", "kind": "usage", "notes": "English translation of von Buch explicitly cited on page 89.\nTrenton Limestone\n", "pages": 89, "path": "0/children/1", "source": "1847_hall", "spelling": "cystidea", "subject": "cystidea", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "citesSource": "1846b_buch", "id": "1847_hall:0/children/1:placement", "kind": "placement", "notes": "English translation of von Buch explicitly cited on page 89.\nTrenton Limestone\n", "pages": 89, "parent": "crinoidea", "path": "0/children/1", "position": 1, "rank": "Unranked", "rankAsPrinted": "Family", "source": "1847_hall", "subject": "cystidea", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "citesSource": "1846b_buch", "id": "1847_hall:0/children/1:placement", "kind": "placement", "notes": "English translation of von Buch explicitly cited on page 89.\nTrenton Limestone\n", "pages": 89, "parent": "crinoidea", "path": "0/children/1", "position": 1, "rank": null, "rankAsPrinted": "Family", "source": "1847_hall", "subject": "cystidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1847_hall:0/children/1/children/0:usage", "kind": "usage", "path": "0/children/1/children/0", "printedAttribution": "as-record", "source": "1847_hall", "spelling": "echino-encrinites", "subject": "echino-encrinites", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1847_hall:0/children/1/children/0:placement", "kind": "placement", "parent": "cystidea", "path": "0/children/1/children/0", "position": 0, "printedAttribution": "as-record", "rank": "genus", "source": "1847_hall", "subject": "echino-encrinites", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "synonyms", "citedIllustrations": [{"figures": [[1, 5]], "plate": 2}], "citedPages": [[185, 192]], "form": "taxon", "id": "1847_hall:0/children/1/children/0/synonyms/0:usage", "kind": "usage", "notes": "volume ii; specimen has since been lost", "path": "0/children/1/children/0/synonyms/0", "printed": {"auth": ["von Meyer"], "in": ["Karstner"], "year": 1826}, "source": "1847_hall", "spelling": "echino-encrinites", "subject": "echino-encrinites", "tree": "taxonomy"} diff --git a/claims/1848_leuckart.jsonl b/claims/1848_leuckart.jsonl index d24463e..540ea5c 100644 --- a/claims/1848_leuckart.jsonl +++ b/claims/1848_leuckart.jsonl @@ -3,7 +3,7 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1848_leuckart:0/children/0:placement", "kind": "placement", "parent": "echinodermata", "path": "0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "Subphylum", "rankAsPrinted": "Class", "source": "1848_leuckart", "subject": "pelmatozoa", "tree": "taxonomy"} {"actKind": "new", "audit": {"coverageKind": "newTaxa", "state": "unaudited"}, "id": "1848_leuckart:0/children/0:act", "kind": "act", "path": "0/children/0", "printedAttribution": "as-record", "source": "1848_leuckart", "subject": "pelmatozoa", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1848_leuckart:0/children/0/children/0:usage", "kind": "usage", "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1848_leuckart", "spelling": "cystoidea", "subject": "cystoidea", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1848_leuckart:0/children/0/children/0:placement", "kind": "placement", "parent": "pelmatozoa", "path": "0/children/0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "Unranked", "rankAsPrinted": "Order", "source": "1848_leuckart", "subject": "cystoidea", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1848_leuckart:0/children/0/children/0:placement", "kind": "placement", "parent": "pelmatozoa", "path": "0/children/0/children/0", "position": 0, "printedAttribution": "as-record", "rank": null, "rankAsPrinted": "Order", "source": "1848_leuckart", "subject": "cystoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1848_leuckart:0/children/0/children/1:usage", "kind": "usage", "path": "0/children/0/children/1", "printedAttribution": "as-record", "source": "1848_leuckart", "spelling": "crinoidea", "subject": "crinoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1848_leuckart:0/children/0/children/1:placement", "kind": "placement", "parent": "pelmatozoa", "path": "0/children/0/children/1", "position": 1, "printedAttribution": "as-record", "rank": "Class", "rankAsPrinted": "Order", "source": "1848_leuckart", "subject": "crinoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1848_leuckart:0/children/1:usage", "kind": "usage", "path": "0/children/1", "printedAttribution": "as-record", "source": "1848_leuckart", "spelling": "actinozoa", "subject": "actinozoa", "tree": "taxonomy"} diff --git a/claims/1848b_forbes.jsonl b/claims/1848b_forbes.jsonl index 707d4d7..226b744 100644 --- a/claims/1848b_forbes.jsonl +++ b/claims/1848b_forbes.jsonl @@ -1,6 +1,6 @@ {"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "axis": "root", "form": "taxon", "id": "1848b_forbes:0:usage", "kind": "usage", "notes": "At times Echinodermata is also called an Order, but as Cystideae and Crinoideae are called Orders several times, Class seems most correct.\n", "pages": 523, "path": "0", "printed": {"citedAs": "There has been much difference of opinion among naturalists respecting the true zoological position of the Cystideæ . Even the great class or type within which they should be ranked has not been agreed upon by all ; for some have regarded them as Mollusca, others as Echinoderms.\n"}, "source": "1848b_forbes", "spelling": "echinodermata", "subject": "echinodermata", "tree": "taxonomy"} {"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "citesSource": "1845_buch", "form": "taxon", "id": "1848b_forbes:0/children/0:usage", "kind": "usage", "notes": "Von Buch ... \"Uber Cystideen\" published at Berlin in 1845\n", "pages": 483, "path": "0/children/0", "printed": {"citedAs": "the Cystideæ, an order of radiated animals"}, "source": "1848b_forbes", "spelling": "cystideae", "subject": "cystideae", "tree": "taxonomy"} -{"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "citesSource": "1845_buch", "id": "1848b_forbes:0/children/0:placement", "kind": "placement", "notes": "Von Buch ... \"Uber Cystideen\" published at Berlin in 1845\n", "pages": 483, "parent": "echinodermata", "path": "0/children/0", "position": 0, "printed": {"citedAs": "the Cystideæ, an order of radiated animals"}, "rank": "Unranked", "rankAsPrinted": "Order", "source": "1848b_forbes", "subject": "cystideae", "tree": "taxonomy"} +{"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "citesSource": "1845_buch", "id": "1848b_forbes:0/children/0:placement", "kind": "placement", "notes": "Von Buch ... \"Uber Cystideen\" published at Berlin in 1845\n", "pages": 483, "parent": "echinodermata", "path": "0/children/0", "position": 0, "printed": {"citedAs": "the Cystideæ, an order of radiated animals"}, "rank": null, "rankAsPrinted": "Order", "source": "1848b_forbes", "subject": "cystideae", "tree": "taxonomy"} {"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "form": "taxon", "id": "1848b_forbes:0/children/0/children/0:usage", "kind": "usage", "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1848b_forbes", "spelling": "pseudocrinites", "subject": "pseudocrinites", "tree": "taxonomy"} {"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "id": "1848b_forbes:0/children/0/children/0:placement", "kind": "placement", "parent": "cystideae", "path": "0/children/0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "genus", "source": "1848b_forbes", "subject": "pseudocrinites", "tree": "taxonomy"} {"actKind": "emended", "audit": {"state": "partial"}, "id": "1848b_forbes:0/children/0/children/0:act", "kind": "act", "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1848b_forbes", "subject": "pseudocrinites", "tree": "taxonomy"} diff --git a/claims/1852_hall.jsonl b/claims/1852_hall.jsonl index 9a25146..37eeb36 100644 --- a/claims/1852_hall.jsonl +++ b/claims/1852_hall.jsonl @@ -169,7 +169,7 @@ {"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "id": "1852_hall:1/children/0/children/1/children/14:placement", "kind": "placement", "notes": "Niagara Group. Errata and additions; no species defined.", "parent": "crinoidea", "path": "1/children/0/children/1/children/14", "position": 14, "printedAttribution": "as-record", "rank": "genus", "source": "1852_hall", "subject": "calceocrinus", "tree": "taxonomy"} {"actKind": "new", "audit": {"coverage": "all", "coverageKind": "newTaxa", "state": "partial"}, "id": "1852_hall:1/children/0/children/1/children/14:act", "kind": "act", "notes": "Niagara Group. Errata and additions; no species defined.", "path": "1/children/0/children/1/children/14", "printedAttribution": "as-record", "source": "1852_hall", "subject": "calceocrinus", "tree": "taxonomy"} {"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "editorial": {"basis": "Hall only mentions orders on page 233, noting von Buch's definition of Cystidea as \"forming a distinct order of Echinoderms, inferior to the Crinoids.\" He does not appear to adopt the rank Order himself, referring to both cystids and \"true crinoids\" as families in various places.\n", "inferred": ["rank"]}, "form": "taxon", "id": "1852_hall:1/children/0/children/2:usage", "kind": "usage", "pages": 233, "path": "1/children/0/children/2", "printedAttribution": "as-record", "source": "1852_hall", "spelling": "cystideae", "subject": "cystideae", "tree": "taxonomy"} -{"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "editorial": {"basis": "Hall only mentions orders on page 233, noting von Buch's definition of Cystidea as \"forming a distinct order of Echinoderms, inferior to the Crinoids.\" He does not appear to adopt the rank Order himself, referring to both cystids and \"true crinoids\" as families in various places.\n", "inferred": ["rank"]}, "id": "1852_hall:1/children/0/children/2:placement", "kind": "placement", "pages": 233, "parent": "crinoidea", "path": "1/children/0/children/2", "position": 2, "printedAttribution": "as-record", "rank": "Unranked", "rankAsPrinted": "Family", "source": "1852_hall", "subject": "cystideae", "tree": "taxonomy"} +{"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "editorial": {"basis": "Hall only mentions orders on page 233, noting von Buch's definition of Cystidea as \"forming a distinct order of Echinoderms, inferior to the Crinoids.\" He does not appear to adopt the rank Order himself, referring to both cystids and \"true crinoids\" as families in various places.\n", "inferred": ["rank"]}, "id": "1852_hall:1/children/0/children/2:placement", "kind": "placement", "pages": 233, "parent": "crinoidea", "path": "1/children/0/children/2", "position": 2, "printedAttribution": "as-record", "rank": null, "rankAsPrinted": "Family", "source": "1852_hall", "subject": "cystideae", "tree": "taxonomy"} {"audit": {"state": "partial"}, "basis": "Hall only mentions orders on page 233, noting von Buch's definition of Cystidea as \"forming a distinct order of Echinoderms, inferior to the Crinoids.\" He does not appear to adopt the rank Order himself, referring to both cystids and \"true crinoids\" as families in various places.\n", "editorial": {"basis": "Hall only mentions orders on page 233, noting von Buch's definition of Cystidea as \"forming a distinct order of Echinoderms, inferior to the Crinoids.\" He does not appear to adopt the rank Order himself, referring to both cystids and \"true crinoids\" as families in various places.\n", "inferred": ["rank"]}, "id": "1852_hall:1/children/0/children/2:editorial", "inferred": ["rank"], "kind": "editorial", "pages": 233, "path": "1/children/0/children/2", "printedAttribution": "as-record", "source": "1852_hall", "subject": "cystideae", "tree": "taxonomy"} {"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "axis": "synonyms", "form": "taxon", "id": "1852_hall:1/children/0/children/2/synonyms/0:usage", "kind": "usage", "notes": "On page 223: \"The Cystidea of Von Buch, properly so called,\"\n", "path": "1/children/0/children/2/synonyms/0", "printedAttribution": "as-record", "source": "1852_hall", "spelling": "cystidea", "subject": "cystidea", "tree": "taxonomy"} {"audit": {"coverage": "partly", "coverageKind": "synonymy", "state": "partial"}, "id": "1852_hall:1/children/0/children/2/synonyms/0:acceptance", "kind": "acceptance", "notes": "On page 223: \"The Cystidea of Von Buch, properly so called,\"\n", "path": "1/children/0/children/2/synonyms/0", "printedAttribution": "as-record", "source": "1852_hall", "stance": "accepts", "subject": "cystidea", "tree": "taxonomy", "under": "cystideae"} diff --git "a/claims/1854_m\303\274ller.j.p.jsonl" "b/claims/1854_m\303\274ller.j.p.jsonl" index 14daab6..8da532b 100644 --- "a/claims/1854_m\303\274ller.j.p.jsonl" +++ "b/claims/1854_m\303\274ller.j.p.jsonl" @@ -2,11 +2,11 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1854_müller.j.p:0/children/0:usage", "kind": "usage", "path": "0/children/0", "printedAttribution": "none", "source": "1854_müller.j.p", "spelling": "crinoiden", "subject": "crinoiden", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0:placement", "kind": "placement", "parent": "echinodermen", "path": "0/children/0", "position": 0, "printedAttribution": "none", "rank": "Family", "rankAsPrinted": null, "source": "1854_müller.j.p", "subject": "crinoiden", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1854_müller.j.p:0/children/0/children/0:usage", "kind": "usage", "path": "0/children/0/children/0", "printed": {"citedAs": "the group of the Cystideen of L. v. Buch"}, "source": "1854_müller.j.p", "spelling": "cystideen", "subject": "cystideen", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0/children/0:placement", "kind": "placement", "parent": "crinoiden", "path": "0/children/0/children/0", "position": 0, "printed": {"citedAs": "the group of the Cystideen of L. v. Buch"}, "rank": "Unranked", "rankAsPrinted": null, "source": "1854_müller.j.p", "subject": "cystideen", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0/children/0:placement", "kind": "placement", "parent": "crinoiden", "path": "0/children/0/children/0", "position": 0, "printed": {"citedAs": "the group of the Cystideen of L. v. Buch"}, "rank": null, "rankAsPrinted": null, "source": "1854_müller.j.p", "subject": "cystideen", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "openTaxon", "id": "1854_müller.j.p:0/children/0/children/0/children/0:usage", "kind": "usage", "notes": "Rhombifera, but not named.", "path": "0/children/0/children/0/children/0", "placeholder": "unnamed", "printedAttribution": "as-record", "source": "1854_müller.j.p", "spelling": "cystideae-unnamed-group_müller.j.p_1854", "subject": "cystideae-unnamed-group_müller.j.p_1854", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0/children/0/children/0:placement", "kind": "placement", "notes": "Rhombifera, but not named.", "parent": "cystideen", "path": "0/children/0/children/0/children/0", "placeholder": "unnamed", "position": 0, "printedAttribution": "as-record", "rank": "Unranked", "source": "1854_müller.j.p", "subject": "cystideae-unnamed-group_müller.j.p_1854", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0/children/0/children/0:placement", "kind": "placement", "notes": "Rhombifera, but not named.", "parent": "cystideen", "path": "0/children/0/children/0/children/0", "placeholder": "unnamed", "position": 0, "printedAttribution": "as-record", "rank": null, "source": "1854_müller.j.p", "subject": "cystideae-unnamed-group_müller.j.p_1854", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "openTaxon", "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/0:usage", "kind": "usage", "notes": "Hemicosmitida, but not named.", "path": "0/children/0/children/0/children/0/children/0", "placeholder": "unnamed", "printedAttribution": "as-record", "source": "1854_müller.j.p", "spelling": "cystideae-unnamed-subgroup-1_müller.j.p_1854", "subject": "cystideae-unnamed-subgroup-1_müller.j.p_1854", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/0:placement", "kind": "placement", "notes": "Hemicosmitida, but not named.", "parent": "cystideae-unnamed-group_müller.j.p_1854", "parentPlaceholder": "unnamed", "path": "0/children/0/children/0/children/0/children/0", "placeholder": "unnamed", "position": 0, "printedAttribution": "as-record", "rank": "Unranked", "source": "1854_müller.j.p", "subject": "cystideae-unnamed-subgroup-1_müller.j.p_1854", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/0:placement", "kind": "placement", "notes": "Hemicosmitida, but not named.", "parent": "cystideae-unnamed-group_müller.j.p_1854", "parentPlaceholder": "unnamed", "path": "0/children/0/children/0/children/0/children/0", "placeholder": "unnamed", "position": 0, "printedAttribution": "as-record", "rank": null, "source": "1854_müller.j.p", "subject": "cystideae-unnamed-subgroup-1_müller.j.p_1854", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/0/children/0:usage", "kind": "usage", "path": "0/children/0/children/0/children/0/children/0/children/0", "printedAttribution": "as-record", "source": "1854_müller.j.p", "spelling": "hemicosmites", "subject": "hemicosmites", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/0/children/0:placement", "kind": "placement", "parent": "cystideae-unnamed-subgroup-1_müller.j.p_1854", "parentPlaceholder": "unnamed", "path": "0/children/0/children/0/children/0/children/0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "genus", "source": "1854_müller.j.p", "subject": "hemicosmites", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/0/children/1:usage", "kind": "usage", "path": "0/children/0/children/0/children/0/children/0/children/1", "printedAttribution": "as-record", "source": "1854_müller.j.p", "spelling": "caryocrinus", "subject": "caryocrinus", "tree": "taxonomy"} @@ -31,7 +31,7 @@ {"audit": {"coverageKind": "synonymy", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/1/children/4/synonyms/0:acceptance", "kind": "acceptance", "parents": ["caryocystites"], "path": "0/children/0/children/0/children/0/children/1/children/4/synonyms/0", "printedAttribution": "as-record", "source": "1854_müller.j.p", "stance": "accepts", "subject": "testudinarius_hisinger_1837", "tree": "taxonomy", "under": "testudinarius_hisinger_1837"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "parents", "form": "taxon", "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/1/children/4/synonyms/0/parents/0:usage", "kind": "usage", "path": "0/children/0/children/0/children/0/children/1/children/4/synonyms/0/parents/0", "printedAttribution": "as-record", "source": "1854_müller.j.p", "spelling": "caryocystites", "subject": "caryocystites", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "openTaxon", "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/2:usage", "kind": "usage", "notes": "Glyptocystitida, but not named.", "path": "0/children/0/children/0/children/0/children/2", "placeholder": "unnamed", "printedAttribution": "as-record", "source": "1854_müller.j.p", "spelling": "cystideae-unnamed-subgroup-2_müller.j.p_1854", "subject": "cystideae-unnamed-subgroup-2_müller.j.p_1854", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/2:placement", "kind": "placement", "notes": "Glyptocystitida, but not named.", "parent": "cystideae-unnamed-group_müller.j.p_1854", "parentPlaceholder": "unnamed", "path": "0/children/0/children/0/children/0/children/2", "placeholder": "unnamed", "position": 2, "printedAttribution": "as-record", "rank": "Unranked", "source": "1854_müller.j.p", "subject": "cystideae-unnamed-subgroup-2_müller.j.p_1854", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/2:placement", "kind": "placement", "notes": "Glyptocystitida, but not named.", "parent": "cystideae-unnamed-group_müller.j.p_1854", "parentPlaceholder": "unnamed", "path": "0/children/0/children/0/children/0/children/2", "placeholder": "unnamed", "position": 2, "printedAttribution": "as-record", "rank": null, "source": "1854_müller.j.p", "subject": "cystideae-unnamed-subgroup-2_müller.j.p_1854", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/2/children/0:usage", "kind": "usage", "path": "0/children/0/children/0/children/0/children/2/children/0", "printedAttribution": "as-record", "source": "1854_müller.j.p", "spelling": "echinoencrinus", "subject": "echinoencrinus", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/2/children/0:placement", "kind": "placement", "parent": "cystideae-unnamed-subgroup-2_müller.j.p_1854", "parentPlaceholder": "unnamed", "path": "0/children/0/children/0/children/0/children/2/children/0", "position": 0, "printedAttribution": "as-record", "rank": "genus", "source": "1854_müller.j.p", "subject": "echinoencrinus", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1854_müller.j.p:0/children/0/children/0/children/0/children/2/children/0/children/0:usage", "kind": "usage", "path": "0/children/0/children/0/children/0/children/2/children/0/children/0", "printedAttribution": "as-record", "source": "1854_müller.j.p", "spelling": "angulosus_pander_1830", "subject": "angulosus_pander_1830", "tree": "taxonomy"} diff --git a/claims/1854a_billings.jsonl b/claims/1854a_billings.jsonl index 74f0875..0385b18 100644 --- a/claims/1854a_billings.jsonl +++ b/claims/1854a_billings.jsonl @@ -1,6 +1,6 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "root", "form": "taxon", "id": "1854a_billings:0:usage", "kind": "usage", "pages": 215, "path": "0", "printed": {"citedAs": "Echinodermata"}, "printedAttribution": "none", "rankAsPrinted": null, "source": "1854a_billings", "spelling": "echinodermata", "subject": "echinodermata", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "citesSource": "1845_buch", "form": "taxon", "id": "1854a_billings:0/children/0:usage", "kind": "usage", "pages": 215, "path": "0/children/0", "printed": {"citedAs": "The Cystidea were first set apart as a separate order of the Echinodermata by the late illustrious geologist, Leopold Von Buch, in a memoir which appeared in 1845 in the Transactions of the Royal Academy of Sciences of Berlin, and afterwards in 1846 translated and published in the Journal of the Geological Society of London. \n"}, "source": "1854a_billings", "spelling": "cystidea", "subject": "cystidea", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "citesSource": "1845_buch", "id": "1854a_billings:0/children/0:placement", "kind": "placement", "pages": 215, "parent": "echinodermata", "path": "0/children/0", "position": 0, "printed": {"citedAs": "The Cystidea were first set apart as a separate order of the Echinodermata by the late illustrious geologist, Leopold Von Buch, in a memoir which appeared in 1845 in the Transactions of the Royal Academy of Sciences of Berlin, and afterwards in 1846 translated and published in the Journal of the Geological Society of London. \n"}, "rank": "Unranked", "rankAsPrinted": "Order", "source": "1854a_billings", "subject": "cystidea", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "citesSource": "1845_buch", "id": "1854a_billings:0/children/0:placement", "kind": "placement", "pages": 215, "parent": "echinodermata", "path": "0/children/0", "position": 0, "printed": {"citedAs": "The Cystidea were first set apart as a separate order of the Echinodermata by the late illustrious geologist, Leopold Von Buch, in a memoir which appeared in 1845 in the Transactions of the Royal Academy of Sciences of Berlin, and afterwards in 1846 translated and published in the Journal of the Geological Society of London. \n"}, "rank": null, "rankAsPrinted": "Order", "source": "1854a_billings", "subject": "cystidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1854a_billings:0/children/0/children/0:usage", "kind": "usage", "pages": 215, "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1854a_billings", "spelling": "glyptocystites", "subject": "glyptocystites", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854a_billings:0/children/0/children/0:placement", "kind": "placement", "pages": 215, "parent": "cystidea", "path": "0/children/0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "genus", "source": "1854a_billings", "subject": "glyptocystites", "tree": "taxonomy"} {"actKind": "new", "audit": {"coverageKind": "newTaxa", "state": "unaudited"}, "id": "1854a_billings:0/children/0/children/0:act", "kind": "act", "pages": 215, "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1854a_billings", "subject": "glyptocystites", "tree": "taxonomy"} diff --git a/claims/1854b_billings.jsonl b/claims/1854b_billings.jsonl index 67acf8d..cc7a542 100644 --- a/claims/1854b_billings.jsonl +++ b/claims/1854b_billings.jsonl @@ -1,6 +1,6 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "root", "form": "taxon", "id": "1854b_billings:0:usage", "kind": "usage", "path": "0", "printedAttribution": "as-record", "rankAsPrinted": null, "source": "1854b_billings", "spelling": "echinodermata", "subject": "echinodermata", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1854b_billings:0/children/0:usage", "kind": "usage", "path": "0/children/0", "printedAttribution": "as-record", "source": "1854b_billings", "spelling": "cystidea", "subject": "cystidea", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854b_billings:0/children/0:placement", "kind": "placement", "parent": "echinodermata", "path": "0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "Unranked", "rankAsPrinted": "Order", "source": "1854b_billings", "subject": "cystidea", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854b_billings:0/children/0:placement", "kind": "placement", "parent": "echinodermata", "path": "0/children/0", "position": 0, "printedAttribution": "as-record", "rank": null, "rankAsPrinted": "Order", "source": "1854b_billings", "subject": "cystidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1854b_billings:0/children/0/children/0:usage", "kind": "usage", "pages": 251, "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1854b_billings", "spelling": "pleurocystites", "subject": "pleurocystites", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1854b_billings:0/children/0/children/0:placement", "kind": "placement", "pages": 251, "parent": "cystidea", "path": "0/children/0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "genus", "source": "1854b_billings", "subject": "pleurocystites", "tree": "taxonomy"} {"actKind": "new", "audit": {"coverageKind": "newTaxa", "state": "unaudited"}, "id": "1854b_billings:0/children/0/children/0:act", "kind": "act", "pages": 251, "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1854b_billings", "subject": "pleurocystites", "tree": "taxonomy"} diff --git a/claims/1854c_billings.jsonl b/claims/1854c_billings.jsonl index 115b5d1..a34450b 100644 --- a/claims/1854c_billings.jsonl +++ b/claims/1854c_billings.jsonl @@ -1,6 +1,6 @@ {"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "axis": "root", "form": "taxon", "id": "1854c_billings:0:usage", "kind": "usage", "path": "0", "printedAttribution": "as-record", "rankAsPrinted": null, "source": "1854c_billings", "spelling": "echinodermata", "subject": "echinodermata", "tree": "taxonomy"} {"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "form": "taxon", "id": "1854c_billings:0/children/0:usage", "kind": "usage", "path": "0/children/0", "printedAttribution": "as-record", "source": "1854c_billings", "spelling": "cystidea", "subject": "cystidea", "tree": "taxonomy"} -{"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "id": "1854c_billings:0/children/0:placement", "kind": "placement", "parent": "echinodermata", "path": "0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "Unranked", "rankAsPrinted": "Order", "source": "1854c_billings", "subject": "cystidea", "tree": "taxonomy"} +{"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "id": "1854c_billings:0/children/0:placement", "kind": "placement", "parent": "echinodermata", "path": "0/children/0", "position": 0, "printedAttribution": "as-record", "rank": null, "rankAsPrinted": "Order", "source": "1854c_billings", "subject": "cystidea", "tree": "taxonomy"} {"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "form": "taxon", "id": "1854c_billings:0/children/0/children/0:usage", "kind": "usage", "notes": "First given on page 268, formal definition starts on page 269.", "pages": 269, "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1854c_billings", "spelling": "comarocystites", "subject": "comarocystites", "tree": "taxonomy"} {"audit": {"coverage": "partly", "coverageKind": "skeleton", "state": "partial"}, "id": "1854c_billings:0/children/0/children/0:placement", "kind": "placement", "notes": "First given on page 268, formal definition starts on page 269.", "pages": 269, "parent": "cystidea", "path": "0/children/0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "genus", "source": "1854c_billings", "subject": "comarocystites", "tree": "taxonomy"} {"actKind": "new", "audit": {"coverage": "all", "coverageKind": "newTaxa", "state": "partial"}, "id": "1854c_billings:0/children/0/children/0:act", "kind": "act", "notes": "First given on page 268, formal definition starts on page 269.", "pages": 269, "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1854c_billings", "subject": "comarocystites", "tree": "taxonomy"} diff --git a/claims/1857_billings.jsonl b/claims/1857_billings.jsonl index 86f3960..2bc16a7 100644 --- a/claims/1857_billings.jsonl +++ b/claims/1857_billings.jsonl @@ -1,6 +1,6 @@ {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "axis": "root", "form": "taxon", "id": "1857_billings:0:usage", "kind": "usage", "pages": 256, "path": "0", "printed": {"citedAs": "Sub-kingdom, RADIATA"}, "printedAttribution": "none", "source": "1857_billings", "spelling": "radiata", "subject": "radiata", "tree": "taxonomy"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "form": "taxon", "id": "1857_billings:0/children/0:usage", "kind": "usage", "pages": 256, "path": "0/children/0", "printed": {"citedAs": "Class, ECHINODERMATA"}, "printedAttribution": "none", "source": "1857_billings", "spelling": "echinodermata", "subject": "echinodermata", "tree": "taxonomy"} -{"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "id": "1857_billings:0/children/0:placement", "kind": "placement", "pages": 256, "parent": "radiata", "path": "0/children/0", "position": 0, "printed": {"citedAs": "Class, ECHINODERMATA"}, "printedAttribution": "none", "rank": "Unranked", "rankAsPrinted": "Class", "source": "1857_billings", "subject": "echinodermata", "tree": "taxonomy"} +{"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "id": "1857_billings:0/children/0:placement", "kind": "placement", "pages": 256, "parent": "radiata", "path": "0/children/0", "position": 0, "printed": {"citedAs": "Class, ECHINODERMATA"}, "printedAttribution": "none", "rank": null, "rankAsPrinted": "Class", "source": "1857_billings", "subject": "echinodermata", "tree": "taxonomy"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "form": "taxon", "id": "1857_billings:0/children/0/children/0:usage", "kind": "usage", "pages": 256, "path": "0/children/0/children/0", "printed": {"citedAs": "Order, CRINOIDEA"}, "printedAttribution": "none", "source": "1857_billings", "spelling": "crinoidea", "subject": "crinoidea", "tree": "taxonomy"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "id": "1857_billings:0/children/0/children/0:placement", "kind": "placement", "pages": 256, "parent": "echinodermata", "path": "0/children/0/children/0", "position": 0, "printed": {"citedAs": "Order, CRINOIDEA"}, "printedAttribution": "none", "rank": "Class", "rankAsPrinted": "Order", "source": "1857_billings", "subject": "crinoidea", "tree": "taxonomy"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "form": "taxon", "id": "1857_billings:0/children/0/children/0/children/0:usage", "kind": "usage", "path": "0/children/0/children/0/children/0", "printedAttribution": "as-record", "source": "1857_billings", "spelling": "glyptocrinus", "subject": "glyptocrinus", "tree": "taxonomy"} @@ -102,7 +102,7 @@ {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "id": "1857_billings:0/children/0/children/0/children/8/children/0:placement", "kind": "placement", "parent": "porocrinus", "path": "0/children/0/children/0/children/8/children/0", "position": 0, "printedAttribution": "as-record", "rank": "species", "source": "1857_billings", "subject": "conicus_billings_1857_porocrinus", "tree": "taxonomy"} {"actKind": "new", "audit": {"coverage": "all", "coverageKind": "newTaxa", "state": "partial"}, "id": "1857_billings:0/children/0/children/0/children/8/children/0:act", "kind": "act", "path": "0/children/0/children/0/children/8/children/0", "printedAttribution": "as-record", "source": "1857_billings", "subject": "conicus_billings_1857_porocrinus", "tree": "taxonomy"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "form": "taxon", "id": "1857_billings:0/children/0/children/1:usage", "kind": "usage", "pages": 280, "path": "0/children/0/children/1", "printed": {"citedAs": "Order CYSTIDEÆ."}, "source": "1857_billings", "spelling": "cystideae", "subject": "cystideae", "tree": "taxonomy"} -{"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "id": "1857_billings:0/children/0/children/1:placement", "kind": "placement", "pages": 280, "parent": "echinodermata", "path": "0/children/0/children/1", "position": 1, "printed": {"citedAs": "Order CYSTIDEÆ."}, "rank": "Unranked", "rankAsPrinted": "Order", "source": "1857_billings", "subject": "cystideae", "tree": "taxonomy"} +{"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "id": "1857_billings:0/children/0/children/1:placement", "kind": "placement", "pages": 280, "parent": "echinodermata", "path": "0/children/0/children/1", "position": 1, "printed": {"citedAs": "Order CYSTIDEÆ."}, "rank": null, "rankAsPrinted": "Order", "source": "1857_billings", "subject": "cystideae", "tree": "taxonomy"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "form": "taxon", "id": "1857_billings:0/children/0/children/1/children/0:usage", "kind": "usage", "path": "0/children/0/children/1/children/0", "printedAttribution": "as-record", "source": "1857_billings", "spelling": "glyptocystites", "subject": "glyptocystites", "tree": "taxonomy"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "id": "1857_billings:0/children/0/children/1/children/0:placement", "kind": "placement", "parent": "cystideae", "path": "0/children/0/children/1/children/0", "position": 0, "printedAttribution": "as-record", "rank": "genus", "source": "1857_billings", "subject": "glyptocystites", "tree": "taxonomy"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "form": "taxon", "id": "1857_billings:0/children/0/children/1/children/0/children/0:usage", "kind": "usage", "path": "0/children/0/children/1/children/0/children/0", "printedAttribution": "as-record", "source": "1857_billings", "spelling": "multiporus_billings_1854", "subject": "multiporus_billings_1854", "tree": "taxonomy"} diff --git a/claims/1858_salter_billings.jsonl b/claims/1858_salter_billings.jsonl index 7e7780d..3d271ff 100644 --- a/claims/1858_salter_billings.jsonl +++ b/claims/1858_salter_billings.jsonl @@ -1,6 +1,6 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "root", "form": "taxon", "id": "1858_salter_billings:0:usage", "kind": "usage", "path": "0", "printedAttribution": "as-record", "rankAsPrinted": null, "source": "1858_salter_billings", "spelling": "echinodermata", "subject": "echinodermata", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1858_salter_billings:0/children/0:usage", "kind": "usage", "path": "0/children/0", "printedAttribution": "as-record", "source": "1858_salter_billings", "spelling": "cystideae", "subject": "cystideae", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1858_salter_billings:0/children/0:placement", "kind": "placement", "parent": "echinodermata", "path": "0/children/0", "position": 0, "printedAttribution": "as-record", "rank": "Unranked", "rankAsPrinted": null, "source": "1858_salter_billings", "subject": "cystideae", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1858_salter_billings:0/children/0:placement", "kind": "placement", "parent": "echinodermata", "path": "0/children/0", "position": 0, "printedAttribution": "as-record", "rank": null, "rankAsPrinted": null, "source": "1858_salter_billings", "subject": "cystideae", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1858_salter_billings:0/children/0/children/0:usage", "kind": "usage", "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1858_salter_billings", "spelling": "cyclocystoides", "subject": "cyclocystoides", "tree": "taxonomy"} {"altPlacements": ["asteriadae-order"], "audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1858_salter_billings:0/children/0/children/0:placement", "kind": "placement", "parent": "cystideae", "path": "0/children/0/children/0", "position": 0, "printedAttribution": "as-record", "provisional": true, "rank": "genus", "source": "1858_salter_billings", "subject": "cyclocystoides", "tree": "taxonomy"} {"actKind": "new", "audit": {"coverageKind": "newTaxa", "state": "unaudited"}, "id": "1858_salter_billings:0/children/0/children/0:act", "kind": "act", "path": "0/children/0/children/0", "printedAttribution": "as-record", "source": "1858_salter_billings", "subject": "cyclocystoides", "tree": "taxonomy"} diff --git a/claims/1900_bather.jsonl b/claims/1900_bather.jsonl index c174750..27a0683 100644 --- a/claims/1900_bather.jsonl +++ b/claims/1900_bather.jsonl @@ -22,7 +22,7 @@ {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "axis": "synonyms", "citesAuctt": true, "form": "taxon", "id": "1900_bather:0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/synonyms/0:usage", "kind": "usage", "path": "0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/synonyms/0", "printed": {"citedAs": "(= CRINOIDEA, sensu lato Auctt.)."}, "sensu": "lato", "source": "1900_bather", "spelling": "crinoidea", "subject": "crinoidea", "tree": "taxonomy"} {"audit": {"coverage": "partly", "coverageKind": "synonymy", "state": "partial"}, "citesAuctt": true, "id": "1900_bather:0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/synonyms/0:acceptance", "kind": "acceptance", "path": "0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/synonyms/0", "printed": {"citedAs": "(= CRINOIDEA, sensu lato Auctt.)."}, "source": "1900_bather", "stance": "accepts", "subject": "crinoidea", "tree": "taxonomy", "under": "pelmatozoa"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "form": "taxon", "id": "1900_bather:0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0:usage", "kind": "usage", "pages": 39, "path": "0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0", "printed": {"citedAs": "CLASS I. CYSTIDEA, Von Buch (1844)."}, "source": "1900_bather", "spelling": "cystidea", "subject": "cystidea", "tree": "taxonomy"} -{"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "id": "1900_bather:0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0:placement", "kind": "placement", "pages": 39, "parent": "pelmatozoa", "path": "0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0", "position": 0, "printed": {"citedAs": "CLASS I. CYSTIDEA, Von Buch (1844)."}, "rank": "Unranked", "rankAsPrinted": "Class", "source": "1900_bather", "subject": "cystidea", "tree": "taxonomy"} +{"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "id": "1900_bather:0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0:placement", "kind": "placement", "pages": 39, "parent": "pelmatozoa", "path": "0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0", "position": 0, "printed": {"citedAs": "CLASS I. CYSTIDEA, Von Buch (1844)."}, "rank": null, "rankAsPrinted": "Class", "source": "1900_bather", "subject": "cystidea", "tree": "taxonomy"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "axis": "children", "form": "taxon", "id": "1900_bather:0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0/children/0:usage", "kind": "usage", "pages": 43, "path": "0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0/children/0", "printed": {"citedAs": "ORDER 1. Amphoridea, Haeckel (1896, pars)."}, "source": "1900_bather", "spelling": "amphoridea", "subject": "amphoridea", "tree": "taxonomy"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "id": "1900_bather:0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0/children/0:placement", "kind": "placement", "pages": 43, "parent": "cystidea", "path": "0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0/children/0", "position": 0, "printed": {"citedAs": "ORDER 1. Amphoridea, Haeckel (1896, pars)."}, "rank": "Class", "rankAsPrinted": "Order", "source": "1900_bather", "subject": "amphoridea", "tree": "taxonomy"} {"audit": {"coverage": "all", "coverageKind": "skeleton", "state": "partial"}, "axis": "synonyms", "citesSource": "1896a_haeckel", "form": "synonyms", "id": "1900_bather:0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0/children/0/synonyms/0:usage", "kind": "usage", "ownName": true, "path": "0/children/0/children/0/children/1/children/0/children/0/children/0/children/0/children/0/children/0/synonyms/0", "printed": {"citedAs": "Haeckel (1896, pars)"}, "source": "1900_bather", "subject": "amphoridea", "tree": "taxonomy"} diff --git a/claims/1963_brown.i.a.jsonl b/claims/1963_brown.i.a.jsonl index fdf69a4..2dc6509 100644 --- a/claims/1963_brown.i.a.jsonl +++ b/claims/1963_brown.i.a.jsonl @@ -2,7 +2,7 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1963_brown.i.a:0/children/0:usage", "kind": "usage", "pages": 388, "path": "0/children/0", "printed": {"citedAs": "Subphylum PELMATOZOA."}, "printedAttribution": "none", "source": "1963_brown.i.a", "spelling": "pelmatozoa", "subject": "pelmatozoa", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1963_brown.i.a:0/children/0:placement", "kind": "placement", "pages": 388, "parent": "echinodermata", "path": "0/children/0", "position": 0, "printed": {"citedAs": "Subphylum PELMATOZOA."}, "printedAttribution": "none", "rank": "Subphylum", "rankAsPrinted": "Subphylum", "source": "1963_brown.i.a", "subject": "pelmatozoa", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "citesSource": "1846c_buch", "editorial": {"basis": "von Buch's paper was first published as a monograph in 1845, but is often cited as a journal article from 1846. I am assuming that the German article is intended, although it actually reached print after the English translation (1846b_buch).\n", "inferred": ["authority"]}, "form": "taxon", "id": "1963_brown.i.a:0/children/0/children/0:usage", "kind": "usage", "pages": 388, "path": "0/children/0/children/0", "printed": {"citedAs": "Class CYSTOIDEA von Buch, 1846."}, "source": "1963_brown.i.a", "spelling": "cystoidea", "subject": "cystoidea", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "citesSource": "1846c_buch", "editorial": {"basis": "von Buch's paper was first published as a monograph in 1845, but is often cited as a journal article from 1846. I am assuming that the German article is intended, although it actually reached print after the English translation (1846b_buch).\n", "inferred": ["authority"]}, "id": "1963_brown.i.a:0/children/0/children/0:placement", "kind": "placement", "pages": 388, "parent": "pelmatozoa", "path": "0/children/0/children/0", "position": 0, "printed": {"citedAs": "Class CYSTOIDEA von Buch, 1846."}, "rank": "Unranked", "rankAsPrinted": "Class", "source": "1963_brown.i.a", "subject": "cystoidea", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "citesSource": "1846c_buch", "editorial": {"basis": "von Buch's paper was first published as a monograph in 1845, but is often cited as a journal article from 1846. I am assuming that the German article is intended, although it actually reached print after the English translation (1846b_buch).\n", "inferred": ["authority"]}, "id": "1963_brown.i.a:0/children/0/children/0:placement", "kind": "placement", "pages": 388, "parent": "pelmatozoa", "path": "0/children/0/children/0", "position": 0, "printed": {"citedAs": "Class CYSTOIDEA von Buch, 1846."}, "rank": null, "rankAsPrinted": "Class", "source": "1963_brown.i.a", "subject": "cystoidea", "tree": "taxonomy"} {"audit": {"state": "unaudited"}, "basis": "von Buch's paper was first published as a monograph in 1845, but is often cited as a journal article from 1846. I am assuming that the German article is intended, although it actually reached print after the English translation (1846b_buch).\n", "citesSource": "1846c_buch", "editorial": {"basis": "von Buch's paper was first published as a monograph in 1845, but is often cited as a journal article from 1846. I am assuming that the German article is intended, although it actually reached print after the English translation (1846b_buch).\n", "inferred": ["authority"]}, "id": "1963_brown.i.a:0/children/0/children/0:editorial", "inferred": ["authority"], "kind": "editorial", "pages": 388, "path": "0/children/0/children/0", "printed": {"citedAs": "Class CYSTOIDEA von Buch, 1846."}, "source": "1963_brown.i.a", "subject": "cystoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "corrected": {"citesSource": "1854_müller.j.p", "printed": {"citedAs": "Order DIPLOPORITA J. Müller, 1954."}}, "editorial": {"basis": "The year \"1954\" is used in the paper but clearly an error.", "corrections": {"authority": {"auth": null, "source": "1854_müller.j.p", "year": null}}}, "form": "taxon", "id": "1963_brown.i.a:0/children/0/children/0/children/0:usage", "kind": "usage", "pages": 388, "path": "0/children/0/children/0/children/0", "printed": {"auth": ["müller.j.p"], "citedAs": "Order DIPLOPORITA J. Müller, 1954.", "year": 1954}, "printedErrors": ["auth", "year"], "source": "1963_brown.i.a", "spelling": "diploporita", "subject": "diploporita", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "corrected": {"citesSource": "1854_müller.j.p", "printed": {"citedAs": "Order DIPLOPORITA J. Müller, 1954."}}, "editorial": {"basis": "The year \"1954\" is used in the paper but clearly an error.", "corrections": {"authority": {"auth": null, "source": "1854_müller.j.p", "year": null}}}, "id": "1963_brown.i.a:0/children/0/children/0/children/0:placement", "kind": "placement", "pages": 388, "parent": "cystoidea", "path": "0/children/0/children/0/children/0", "position": 0, "printed": {"auth": ["müller.j.p"], "citedAs": "Order DIPLOPORITA J. Müller, 1954.", "year": 1954}, "printedErrors": ["auth", "year"], "rank": "Group", "rankAsPrinted": "Order", "source": "1963_brown.i.a", "subject": "diploporita", "tree": "taxonomy"} diff --git a/claims/1965_fell.jsonl b/claims/1965_fell.jsonl index 7217d0c..e3248c9 100644 --- a/claims/1965_fell.jsonl +++ b/claims/1965_fell.jsonl @@ -4,7 +4,7 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1965_fell:0/children/0/children/0:usage", "kind": "usage", "pages": 14, "path": "0/children/0/children/0", "printed": {"citedAs": "Class Carpoidea"}, "printedAttribution": "none", "source": "1965_fell", "spelling": "carpoidea", "subject": "carpoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1965_fell:0/children/0/children/0:placement", "kind": "placement", "pages": 14, "parent": "pelmatozoa", "path": "0/children/0/children/0", "position": 0, "printed": {"citedAs": "Class Carpoidea"}, "printedAttribution": "none", "rank": "Class", "rankAsPrinted": "Class", "source": "1965_fell", "subject": "carpoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1965_fell:0/children/0/children/1:usage", "kind": "usage", "pages": 14, "path": "0/children/0/children/1", "printed": {"citedAs": "Class Cystoidea"}, "printedAttribution": "none", "source": "1965_fell", "spelling": "cystoidea", "subject": "cystoidea", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1965_fell:0/children/0/children/1:placement", "kind": "placement", "pages": 14, "parent": "pelmatozoa", "path": "0/children/0/children/1", "position": 1, "printed": {"citedAs": "Class Cystoidea"}, "printedAttribution": "none", "rank": "Unranked", "rankAsPrinted": "Class", "source": "1965_fell", "subject": "cystoidea", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1965_fell:0/children/0/children/1:placement", "kind": "placement", "pages": 14, "parent": "pelmatozoa", "path": "0/children/0/children/1", "position": 1, "printed": {"citedAs": "Class Cystoidea"}, "printedAttribution": "none", "rank": null, "rankAsPrinted": "Class", "source": "1965_fell", "subject": "cystoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1965_fell:0/children/0/children/2:usage", "kind": "usage", "pages": 14, "path": "0/children/0/children/2", "printed": {"citedAs": "Class Eocrinoidea"}, "printedAttribution": "none", "source": "1965_fell", "spelling": "eocrinoidea", "subject": "eocrinoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1965_fell:0/children/0/children/2:placement", "kind": "placement", "pages": 14, "parent": "pelmatozoa", "path": "0/children/0/children/2", "position": 2, "printed": {"citedAs": "Class Eocrinoidea"}, "printedAttribution": "none", "rank": "Class", "rankAsPrinted": "Class", "source": "1965_fell", "subject": "eocrinoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1965_fell:0/children/0/children/3:usage", "kind": "usage", "pages": 14, "path": "0/children/0/children/3", "printed": {"citedAs": "Class Paracrinoidea"}, "printedAttribution": "none", "source": "1965_fell", "spelling": "paracrinoidea", "subject": "paracrinoidea", "tree": "taxonomy"} @@ -41,7 +41,7 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1965_fell:1/children/1:placement", "kind": "placement", "pages": 13, "parent": "echinodermata", "path": "1/children/1", "position": 1, "printed": {"citedAs": "Subphylum Crinozoa Matsumoto, 1929 (redefined)"}, "rank": "Subphylum", "rankAsPrinted": "Subphylum", "source": "1965_fell", "subject": "crinozoa", "tree": "taxonomy"} {"actKind": "emended", "audit": {"state": "unaudited"}, "id": "1965_fell:1/children/1:act", "kind": "act", "pages": 13, "path": "1/children/1", "printed": {"citedAs": "Subphylum Crinozoa Matsumoto, 1929 (redefined)"}, "source": "1965_fell", "subject": "crinozoa", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1965_fell:1/children/1/children/0:usage", "kind": "usage", "pages": 14, "path": "1/children/1/children/0", "printed": {"citedAs": "Class Cystoidea"}, "printedAttribution": "none", "source": "1965_fell", "spelling": "cystoidea", "subject": "cystoidea", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1965_fell:1/children/1/children/0:placement", "kind": "placement", "pages": 14, "parent": "crinozoa", "path": "1/children/1/children/0", "position": 0, "printed": {"citedAs": "Class Cystoidea"}, "printedAttribution": "none", "rank": "Unranked", "rankAsPrinted": "Class", "source": "1965_fell", "subject": "cystoidea", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1965_fell:1/children/1/children/0:placement", "kind": "placement", "pages": 14, "parent": "crinozoa", "path": "1/children/1/children/0", "position": 0, "printed": {"citedAs": "Class Cystoidea"}, "printedAttribution": "none", "rank": null, "rankAsPrinted": "Class", "source": "1965_fell", "subject": "cystoidea", "tree": "taxonomy"} {"actKind": "moved", "audit": {"state": "unaudited"}, "id": "1965_fell:1/children/1/children/0:act", "kind": "act", "movedFrom": "pelmatozoa", "pages": 14, "path": "1/children/1/children/0", "printed": {"citedAs": "Class Cystoidea"}, "printedAttribution": "none", "source": "1965_fell", "subject": "cystoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "declinedParent": "pelmatozoa", "id": "1965_fell:1/children/1/children/0:rejection", "kind": "rejection", "pages": 14, "path": "1/children/1/children/0", "printed": {"citedAs": "Class Cystoidea"}, "printedAttribution": "none", "source": "1965_fell", "subject": "cystoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "moved", "form": "taxon", "id": "1965_fell:1/children/1/children/0/moved:usage", "kind": "usage", "path": "1/children/1/children/0/moved", "printedAttribution": "as-record", "source": "1965_fell", "spelling": "pelmatozoa", "subject": "pelmatozoa", "tree": "taxonomy"} diff --git a/claims/1967a_ubaghs.jsonl b/claims/1967a_ubaghs.jsonl index 5fd6749..9e23565 100644 --- a/claims/1967a_ubaghs.jsonl +++ b/claims/1967a_ubaghs.jsonl @@ -15,7 +15,7 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1967a_ubaghs:0/children/1/children/1:usage", "kind": "usage", "path": "0/children/1/children/1", "printedAttribution": "as-record", "source": "1967a_ubaghs", "spelling": "paracrinoidea", "subject": "paracrinoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1967a_ubaghs:0/children/1/children/1:placement", "kind": "placement", "parent": "crinozoa", "path": "0/children/1/children/1", "position": 1, "printedAttribution": "as-record", "rank": "Class", "rankAsPrinted": "Class", "source": "1967a_ubaghs", "subject": "paracrinoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1967a_ubaghs:0/children/1/children/2:usage", "kind": "usage", "path": "0/children/1/children/2", "printedAttribution": "as-record", "source": "1967a_ubaghs", "spelling": "cystoidea", "subject": "cystoidea", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1967a_ubaghs:0/children/1/children/2:placement", "kind": "placement", "parent": "crinozoa", "path": "0/children/1/children/2", "position": 2, "printedAttribution": "as-record", "rank": "Unranked", "rankAsPrinted": "Class", "source": "1967a_ubaghs", "subject": "cystoidea", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1967a_ubaghs:0/children/1/children/2:placement", "kind": "placement", "parent": "crinozoa", "path": "0/children/1/children/2", "position": 2, "printedAttribution": "as-record", "rank": null, "rankAsPrinted": "Class", "source": "1967a_ubaghs", "subject": "cystoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1967a_ubaghs:0/children/1/children/3:usage", "kind": "usage", "path": "0/children/1/children/3", "printedAttribution": "as-record", "source": "1967a_ubaghs", "spelling": "blastoidea", "subject": "blastoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1967a_ubaghs:0/children/1/children/3:placement", "kind": "placement", "parent": "crinozoa", "path": "0/children/1/children/3", "position": 3, "printedAttribution": "as-record", "rank": "Class", "rankAsPrinted": "Class", "source": "1967a_ubaghs", "subject": "blastoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1967a_ubaghs:0/children/1/children/4:usage", "kind": "usage", "path": "0/children/1/children/4", "printedAttribution": "as-record", "source": "1967a_ubaghs", "spelling": "parablastoidea", "subject": "parablastoidea", "tree": "taxonomy"} diff --git a/claims/1975_kolata.jsonl b/claims/1975_kolata.jsonl index d4d42e4..0b1fd58 100644 --- a/claims/1975_kolata.jsonl +++ b/claims/1975_kolata.jsonl @@ -15,7 +15,7 @@ {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1975_kolata:0/children/1:usage", "kind": "usage", "pages": 15, "path": "0/children/1", "printed": {"citedAs": "Subphylum CRINOZOA Matsumoto, 1929"}, "source": "1975_kolata", "spelling": "crinozoa", "subject": "crinozoa", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1975_kolata:0/children/1:placement", "kind": "placement", "pages": 15, "parent": "echinodermata", "path": "0/children/1", "position": 1, "printed": {"citedAs": "Subphylum CRINOZOA Matsumoto, 1929"}, "rank": "Subphylum", "rankAsPrinted": "Subphylum", "source": "1975_kolata", "subject": "crinozoa", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "citesSource": "1846c_buch", "editorial": {"basis": "von Buch's paper was first published as a monograph in 1845, but is often cited as a journal article from 1846. I am assuming that the German article is intended, although it actually reached print after the English translation (1846b_buch).\n", "inferred": ["authority"]}, "form": "taxon", "id": "1975_kolata:0/children/1/children/0:usage", "kind": "usage", "pages": 15, "path": "0/children/1/children/0", "printed": {"citedAs": "Class CYSTOIDEA von Buch, 1846"}, "source": "1975_kolata", "spelling": "cystoidea", "subject": "cystoidea", "tree": "taxonomy"} -{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "citesSource": "1846c_buch", "editorial": {"basis": "von Buch's paper was first published as a monograph in 1845, but is often cited as a journal article from 1846. I am assuming that the German article is intended, although it actually reached print after the English translation (1846b_buch).\n", "inferred": ["authority"]}, "id": "1975_kolata:0/children/1/children/0:placement", "kind": "placement", "pages": 15, "parent": "crinozoa", "path": "0/children/1/children/0", "position": 0, "printed": {"citedAs": "Class CYSTOIDEA von Buch, 1846"}, "rank": "Unranked", "rankAsPrinted": "Class", "source": "1975_kolata", "subject": "cystoidea", "tree": "taxonomy"} +{"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "citesSource": "1846c_buch", "editorial": {"basis": "von Buch's paper was first published as a monograph in 1845, but is often cited as a journal article from 1846. I am assuming that the German article is intended, although it actually reached print after the English translation (1846b_buch).\n", "inferred": ["authority"]}, "id": "1975_kolata:0/children/1/children/0:placement", "kind": "placement", "pages": 15, "parent": "crinozoa", "path": "0/children/1/children/0", "position": 0, "printed": {"citedAs": "Class CYSTOIDEA von Buch, 1846"}, "rank": null, "rankAsPrinted": "Class", "source": "1975_kolata", "subject": "cystoidea", "tree": "taxonomy"} {"audit": {"state": "unaudited"}, "basis": "von Buch's paper was first published as a monograph in 1845, but is often cited as a journal article from 1846. I am assuming that the German article is intended, although it actually reached print after the English translation (1846b_buch).\n", "citesSource": "1846c_buch", "editorial": {"basis": "von Buch's paper was first published as a monograph in 1845, but is often cited as a journal article from 1846. I am assuming that the German article is intended, although it actually reached print after the English translation (1846b_buch).\n", "inferred": ["authority"]}, "id": "1975_kolata:0/children/1/children/0:editorial", "inferred": ["authority"], "kind": "editorial", "pages": 15, "path": "0/children/1/children/0", "printed": {"citedAs": "Class CYSTOIDEA von Buch, 1846"}, "source": "1975_kolata", "subject": "cystoidea", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "axis": "children", "form": "taxon", "id": "1975_kolata:0/children/1/children/0/children/0:usage", "kind": "usage", "pages": 15, "path": "0/children/1/children/0/children/0", "printed": {"citedAs": "Order RHOMBIFERA Zittel, 1879"}, "source": "1975_kolata", "spelling": "rhombifera_zittel_1879", "subject": "rhombifera_zittel_1879", "tree": "taxonomy"} {"audit": {"coverageKind": "skeleton", "state": "unaudited"}, "id": "1975_kolata:0/children/1/children/0/children/0:placement", "kind": "placement", "pages": 15, "parent": "cystoidea", "path": "0/children/1/children/0/children/0", "position": 0, "printed": {"citedAs": "Order RHOMBIFERA Zittel, 1879"}, "rank": "Group", "rankAsPrinted": "Order", "source": "1975_kolata", "subject": "rhombifera_zittel_1879", "tree": "taxonomy"} diff --git a/claims/names.json b/claims/names.json index d12f21e..d3c7959 100644 --- a/claims/names.json +++ b/claims/names.json @@ -11312,7 +11312,7 @@ "kind": "altSpellingOf", "name": "Cystidea", "of": "cystideen", - "rank": "Unranked" + "rank": null }, "cystideae": { "authority": { @@ -11329,7 +11329,7 @@ "kind": "altSpellingOf", "name": "Cystideae", "of": "cystideen", - "rank": "Unranked" + "rank": null }, "cystideae-unnamed-group_müller.j.p_1854": { "authority": { @@ -11344,7 +11344,7 @@ "kind": "placeholder", "name": null, "placeholder": "unnamed", - "rank": "Unranked" + "rank": null }, "cystideae-unnamed-subgroup-1_müller.j.p_1854": { "authority": { @@ -11359,7 +11359,7 @@ "kind": "placeholder", "name": null, "placeholder": "unnamed", - "rank": "Unranked" + "rank": null }, "cystideae-unnamed-subgroup-2_müller.j.p_1854": { "authority": { @@ -11374,7 +11374,7 @@ "kind": "placeholder", "name": null, "placeholder": "unnamed", - "rank": "Unranked" + "rank": null }, "cystideen": { "authority": { @@ -11391,7 +11391,7 @@ "kind": "primary", "lang": "DE", "name": "Cystideen", - "rank": "Unranked" + "rank": null }, "cystoidea": { "authority": { @@ -11408,7 +11408,7 @@ "kind": "altSpellingOf", "name": "Cystoidea", "of": "cystidea", - "rank": "Unranked" + "rank": null }, "cystostellaroidea": { "authority": { @@ -13495,7 +13495,7 @@ ], "kind": "primary", "name": "Echinodermata", - "rank": "Unranked" + "rank": null }, "echinodermata-class": { "authority": { @@ -23205,7 +23205,7 @@ "kind": "primary", "mineral": true, "name": "kalckballar", - "rank": "Unranked" + "rank": null }, "kaskaskiensis_hall_1858": { "authority": { @@ -45457,7 +45457,7 @@ "lang": "FR", "name": "Échinodermes", "of": "echinodermata", - "rank": "Unranked" + "rank": null }, "échinodermes-class": { "authority": { diff --git a/data/ranks.yaml b/data/ranks.yaml new file mode 100644 index 0000000..4a3ac77 --- /dev/null +++ b/data/ranks.yaml @@ -0,0 +1,112 @@ +# The rank table: the rank words a record or a node may give, and the +# loader's suffix lint over them (`phylohist/ranks.py`, `$defs/ranks`). +# +# `ranks` lists the formal ranks in order, as the data writes them: +# capitalised above the genus group, lower-case from the genus down. The +# informal words follow, flagged `informal`; `section` stays until roadmap +# C6. A word a source prints that is not here (Stage, Sub-branch) is added +# by the auditor. The schema's `rank` enum restates the names in this order +# (a test holds the two equal). A source that prints no rank word and no +# cladistic term is written `rank: null`. +ranks: +- name: Domain +- name: Kingdom +- name: Subkingdom +- name: Superphylum +- name: Phylum +- name: Subphylum +- name: Infraphylum +- name: Superclass +- name: Class +- name: Subclass +- name: Infraclass +- name: Parvclass +- name: Superorder +- name: Order +- name: Suborder +- name: Superfamily + endings: + - suffix: oidea + # The older ending; the Code's fourth edition (1999) settled -oidea. + - suffix: acea + before: 1999 +- name: Family + endings: + - suffix: idae +- name: Subfamily + endings: + - suffix: inae +- name: genus + group: genus +- name: subgenus + group: genus +- name: species + group: species +- name: subspecies + group: species +- name: variety + group: species +- name: Division + informal: true +- name: section + informal: true +- name: Branch + informal: true +- name: Group + informal: true +- name: Grade + informal: true +- name: Clade + informal: true +- name: Plesion + informal: true +- name: Scion + informal: true + +# The suffix lint checks a named record above the genus group, at a formal +# rank, whose authority year is `checkedFrom` or later (a work with no +# year, one in preparation, is not checked), at the rank it gives or +# borrows. A spelling, gender or vernacular variant is not checked; a +# Latin spelling and a rank variant are. Both directions: a name at a rank +# with `endings` ends in one of them (one with `before` only in a work +# before that year), and a name ending in one of `suffixes` is at one of +# its ranks. None for -oidea, which echinoderm classes end in. +checkedFrom: 1900 +suffixes: +- suffix: inae + ranks: [Subfamily] +- suffix: idae + ranks: [Family] +- suffix: acea + ranks: [Superfamily] +- suffix: ina + ranks: [Suborder] +- suffix: ida + ranks: [Order] +- suffix: zoa + ranks: [Class, Subphylum, Phylum, Subkingdom, Kingdom] + +# Names the lint passes at that rank only; the loader reports one that +# names no record, or only records at another rank. +exceptions: +- name: Disparida + rank: Subclass + note: Moore & Laudon 1943's -ida name, ranked above the order. +- name: Cladida + rank: Parvclass + note: Moore & Laudon 1943's -ida name, ranked above the order. +- name: Helicoplacida + rank: Subclass + note: Durham 1967 names a subclass with the ordinal ending. +- name: Polyplacida + rank: Subclass + note: Durham 1967 names a subclass with the ordinal ending. +- name: Gogiida + rank: Subclass + note: Parsley 2021 raises Broadhead's order to a subclass, keeping the name. +- name: Caryocystitida + rank: Superfamily + note: Jaekel 1918's -ida name, printed as a superfamily. +- name: Promopalaeasteraceae + rank: Superfamily + note: Schuchert 1914's -aceae, neither -oidea nor -acea. diff --git a/data/taxa.yaml b/data/taxa.yaml index f619bd1..d6a1213 100644 --- a/data/taxa.yaml +++ b/data/taxa.yaml @@ -3585,19 +3585,19 @@ cystideen: the 1846 English notice and translation. cystideae-unnamed-group_müller.j.p_1854: name: null - rank: Unranked + rank: null authority: source: 1854_müller.j.p notes: Rhombifera, no rank given but Diploporita defined as suborder cystideae-unnamed-subgroup-1_müller.j.p_1854: name: null - rank: Unranked + rank: null authority: source: 1854_müller.j.p notes: Hemicosmitida; rank unstated cystideae-unnamed-subgroup-2_müller.j.p_1854: name: null - rank: Unranked + rank: null authority: source: 1854_müller.j.p notes: Glyptocystitida; rank unstated diff --git a/docs/claims.md b/docs/claims.md index 3a4c98c..927b9d4 100644 --- a/docs/claims.md +++ b/docs/claims.md @@ -84,8 +84,8 @@ every `synonyms`, `non`, `removed`, `parents`, `or` and `type` entry. Fields add node's claims belong to the open form, so the compared taxon's statements do not include them; `history` lists the form under its own name, marked `compared`. -- `sensu`: `stricto`, `lato` or `emendato` when the node prints the - qualifier. +- `sensu`: `stricto`, `lato`, `emendato` or `extenso` when the node prints + the qualifier. - `designation`: the phrase the node prints for an open taxon, when it declares one (an `openTaxon` node only; the loader rejects it beside `taxon` or `mineralTaxon`). @@ -101,8 +101,10 @@ Emitted for every child under its parent in a taxonomy. Fields added: siblings, since the tree keeps the printed order. Under an unnamed clade node the parent is null and `parentPath` points at the node; when the named ancestor is further up, `parentPath` says so too. -- `rank`: the record's rank today. When the tree carries a printed rank - word in `citedAs` or `notes`, `rankAsPrinted` holds it; the rule that +- `rank`: the record's rank today, null for a record written `rank: null` + (its source prints no rank word and no cladistic term). When the tree + carries a printed rank word in `citedAs` or `notes`, `rankAsPrinted` + holds it; the rule that rank belongs to the tree node is G8, not yet in force. A node written `rank: null` (the source places the taxon with no rank word) carries `rankAsPrinted: null`; `rank` is the record's, as ever. @@ -499,8 +501,8 @@ a family's `type` or `type: null` may be written and is not counted. A genus is present when it carries `type`, a node or a null, or has a child marked `isType` (the older form of the same statement); only `type: null` writes a null. `skeleton` counts the named nodes above the species level, -genus and subgenus included (any rank but the species-level ones, so an -`Unranked` or `section` node counts, and a placeholder, a bin with no members of its own, +genus and subgenus included (any rank but the species-level ones, so a +node with no rank (`rank: null`) or a `section` node counts, and a placeholder, a bin with no members of its own, does not): `children` as a list or as a null is present, and only `children: null` writes a null. A genus with no `children` is a genus whose species are not yet entered, since a source often lists no species; a @@ -560,7 +562,8 @@ each entry's `name`, `type`, `subject`, `within`, `prefixes`, `otherNames` and `place` as present: the store reads it to split a catalog number a reader types into its holder and its bare number. `claims/names.json` has -one row per taxon record: name, rank, kind (`primary`; for a derivative +one row per taxon record: name, rank (null for a record written `rank: +null`), kind (`primary`; for a derivative the field naming its target, `altSpellingOf`, `altGenderOf`, `latinSpellingOf`, `vulgarSpellingOf` or `altRankOf`, as `phylohist/derivatives.py` lists them; `placeholder`), `mineral: true` on a @@ -663,7 +666,7 @@ blocks; the CLI (`phylohist `, `--style`), the MCP server | tool | answers | |---|---| -| `resolve_name(query, rank)` | which records a printed name can mean, with each record's variants (the same name at other ranks or spellings) and the count of sources with statements about it; empty is the closed-world answer. A mineral formation's name resolves like any named record and is marked `mineral`. Unnamed records (bins, open nomenclature) are never found by name: the words in their designation name other taxa. Keys are lowercase; every tool accepts a key in any case, or a printed name that resolves to one record ("Rhenopyrgus grayae", "Pyrgocystis (Rhenopyrgus) coronaeformis", "Pyrgocystis (Rhenopyrgus)"); a name that can mean several records is refused with the candidates | +| `resolve_name(query, rank)` | which records a printed name can mean, with each record's variants (the same name at other ranks or spellings) and the count of sources with statements about it; empty is the closed-world answer. `rank` restricts the candidates to one rank word, `unranked` to the records written `rank: null`. A mineral formation's name resolves like any named record and is marked `mineral`. Unnamed records (bins, open nomenclature) are never found by name: the words in their designation name other taxa. Keys are lowercase; every tool accepts a key in any case, or a printed name that resolves to one record ("Rhenopyrgus grayae", "Pyrgocystis (Rhenopyrgus) coronaeformis", "Pyrgocystis (Rhenopyrgus)"); a name that can mean several records is refused with the candidates | | `resolve_source(query)` | the sources a citation can mean ("Dehm 1961", "Holloway & Jell 1983", "Sumrall et al. 2013", "Fay 1967a"): key, citation, year, authors, entered or not; empty means no source in the corpus is that paper. Every `source` parameter accepts a key or the citation as the blocks print it; a citation that can mean several papers is refused with their keys | | `contents(source, record, depth, synonymy)` | a classification block of what a source places under a record; every source that places it when no source is given | | `placements(records, sources, years, …)` | the matrix: records as rows (variants folded) with their rank, sources as columns in year order, the parent each gives (a rejection marked "; not X"); the schemes measured in the header | diff --git a/drafts/1891_bell.f.j.yaml b/drafts/1891_bell.f.j.yaml index 72ab469..3ca38b7 100644 --- a/drafts/1891_bell.f.j.yaml +++ b/drafts/1891_bell.f.j.yaml @@ -75,7 +75,7 @@ notes: | Rank words "Stage", "Sub-branch" and "Sub-stage" have no value in schemas/phylogeny.yaml's rank enum (which does have Branch, Division, - Class, Group, Grade, Unranked among others); each is quoted in `notes` + Class, Group, Grade, Clade among others); each is quoted in `notes` on its own node instead of set via the node's `rank` field. See the review doc, section (d). diff --git a/notes/development/semantics-roadmap.md b/notes/development/semantics-roadmap.md index 2d795f0..e2659ad 100644 --- a/notes/development/semantics-roadmap.md +++ b/notes/development/semantics-roadmap.md @@ -1475,9 +1475,9 @@ differs from `name`. A record with `pages` spells its `rank` (the schema requires it; `null` says the protologue placed the taxon with no rank word), where a tree node leaves a genus or species implicit: a node with no `rank` implies a species when its name is lower-case, else a genus, and the record -must say the same. A `rank: null` node agrees with a record's `null` or -`Unranked`, and a node's `pages: null` (an inferred node) never agrees with -a record's pages. (`_report_protologue_mismatches`, in +must say the same. A `rank: null` node agrees with a record's `null`, and +a node's `pages: null` (an inferred node) never agrees with a record's +pages. (`_report_protologue_mismatches`, in `phylohist/loader/load.py`.) `designation` is among the checked fields (2026-10-05), declared on an `openTaxon` node or a marker. A section record with `pages` is checked the same way against the `sectionStart` marker @@ -1582,6 +1582,42 @@ Bather prints Pelmatozoa as "Sub-Phylum" in 1899 and "Grade" in 1900. Until G8 lands, the draft tree quotes each rank word in `notes`; whether Stage, Sub-branch and Sub-stage join the enum is the auditor's call. +Decided 2026-10-22 and built in the rank-table chunk (`data/ranks.yaml` +with `phylohist/ranks.py` over it; the exceptions and the lint's rules live +in the table, a test holds the schema enum to it): + +- `rank: null` means the source prints no rank word and no cladistic term + for the taxon. The code reads it as none (`None`), the claims and the + names index carry `rank: null`, and a null node agrees with a null + record. `Unranked` leaves the vocabulary: the three unnamed Müller 1854 + groups, whose notes say the rank is unstated, become `null`. A modern + unranked clade is `Clade`, as sources print it. `incertae sedis` is open + nomenclature, not a rank; such a placeholder may carry the rank of the + taxon it stands in for. +- The table is the vocabulary: the formal ranks in order, with the casing + the data writes (capitalised above the genus group, lower-case from + genus down), and a curated set of informal words flagged as such + (Division, Group, Grade, Branch, Plesion, Clade, Scion, and `section` + until C6 re-enters the fourteen `rank: section` records). The schema + enum is checked against the table; every comparison folds the case; a + word a source prints that the table lacks (Stage, Sub-branch) is added by + the auditor, never allowed open. +- The suffix lint applies to records above the genus group whose authority + year is 1900 or later, in both directions (a rank's expected ending, and + an ending's expected ranks); a name before 1900 is never checked. An + `altSpellingOf`, `altGenderOf` or `vulgarSpellingOf` record is skipped + (it borrows its rank and may be a vernacular form); a `latinSpellingOf` + record is checked at the rank it borrows, since that is where a name + introduced in the vernacular first meets the conventions, and a rank + variant (`altRankOf`) at its own rank, like any record (the eleven rank + variants that would warn are all pre-1900 families and classes of + Linnaeus, Klein and Lamarck). A superfamily accepts -acea before 1999 + and -oidea always; -oidea itself implies no rank, since echinoderm + classes end in it. + The exceptions live in the table, each with a note, and the loader errors + on one that names no record or a record at another rank, so the list + cannot go stale. + **G9. Coverage gaps are scope history, not errors.** Sources entered early capture less because the project's scope grew (skeleton first, then synonymy, material, occurrences, disarticulated plates). The audit state @@ -1706,7 +1742,7 @@ Fields fall into three classes, and only the third is nullable: null: infraspecific names are rare enough to be entered when present, so their absence says nothing. The null is also an error on a cited entry, in a draft, and on a node of a cladogram or other tree that is not a - taxonomy. `Unranked`, `section` and the other informal ranks count as + taxonomy. `Clade`, `section` and the other informal ranks count as above the species level. There is no `unused` form: the tree's own nulls say where it stops, so `skeleton` is never `na`. diff --git a/phylohist/blocks.py b/phylohist/blocks.py index 38c85b6..8cba660 100644 --- a/phylohist/blocks.py +++ b/phylohist/blocks.py @@ -36,7 +36,14 @@ 'substituted': 'nom. subst.', } -SENSU_MARKS = {'stricto': '(s. s.)', 'lato': '(s. l.)', 'emendato': '(s. em.)'} +# The mark a listing prints after a name for each `sensu` value the schema +# allows (a test holds the two equal). +SENSU_MARKS = { + 'stricto': '(s. s.)', + 'lato': '(s. l.)', + 'emendato': '(s. em.)', + 'extenso': '(s. ext.)', +} def auctt_words(value): diff --git a/phylohist/claims.py b/phylohist/claims.py index 433cf0c..2c3792b 100644 --- a/phylohist/claims.py +++ b/phylohist/claims.py @@ -939,8 +939,9 @@ def derived_coverage(roots): form of the statement) counts as present, and only `type: null` writes a null; `unused: [type]` is `na`. For `skeleton` every named node above the species level (genus and subgenus included) is counted (any rank but the - species-level ones; an `Unranked` or `section` node counts, a genus with - no `children` is one whose species are not yet entered); a node carrying + species-level ones; a node with no rank (`rank: null`) or a `section` + node counts, a genus with no `children` is one whose species are not yet + entered); a node carrying `children`, a list or a null, is present, and only `children: null` writes a null. It has no `unused` form and is never `na`.""" diff --git a/phylohist/cli.py b/phylohist/cli.py index 15c5ad8..c712c65 100644 --- a/phylohist/cli.py +++ b/phylohist/cli.py @@ -82,7 +82,7 @@ def add_parser(name, **kwargs): p = add_parser('resolve', help='records a printed name can refer to') p.add_argument('query') - p.add_argument('--rank') + p.add_argument('--rank', help='a rank word; "unranked" for a record whose source prints none') p = add_parser('source', help='the sources a citation can mean') p.add_argument('query') @@ -241,7 +241,7 @@ def _print_resolved(result, style): print('(no record in the corpus carries this name)') return for c in result: - rank = 'mineral' if c.get('mineral') else c['rank'] + rank = 'mineral' if c.get('mineral') else (c['rank'] or 'unranked') line = f'{c["key"]} {c["name"] or "[placeholder]"} ({rank}, {c["kind"]}' if c.get('of'): line += f', of {c["of"]}' diff --git a/phylohist/derivatives.py b/phylohist/derivatives.py index 7aaaec3..816c653 100644 --- a/phylohist/derivatives.py +++ b/phylohist/derivatives.py @@ -23,12 +23,16 @@ # borrows_rank: takes the target's rank and mineral flag. # lang: which record must carry `lang`: 'record' for a vernacular form, # 'target' for the Latin spelling of one, None otherwise. +# suffix_checked: the loader's suffix lint checks the record at its rank +# (`ranks.RankTable.suffix_problems`); a Latin spelling and a rank variant +# are names in their own right, another spelling, a gender form and a +# vernacular form are not. # words: how the variant relates to its target (" "). # reverse: how the target relates to the variant, read from the other end. # order: the resolver's candidate order (a primary record is 0, a # placeholder last). Kind = collections.namedtuple( - 'Kind', 'field relation borrows_name borrows_rank lang words reverse order' + 'Kind', 'field relation borrows_name borrows_rank lang suffix_checked words reverse order' ) # In precedence order, should a record ever carry two of the fields. @@ -39,6 +43,7 @@ borrows_name=False, borrows_rank=True, lang=None, + suffix_checked=False, words='spelling variant of', reverse='spelling variant of', order=2, @@ -49,6 +54,7 @@ borrows_name=False, borrows_rank=True, lang=None, + suffix_checked=False, words='gender variant of', reverse='gender variant of', order=2, @@ -59,6 +65,7 @@ borrows_name=False, borrows_rank=True, lang='target', + suffix_checked=True, words='Latin form of', reverse='vernacular form of', order=2, @@ -69,6 +76,7 @@ borrows_name=False, borrows_rank=True, lang='record', + suffix_checked=False, words='vernacular form of', reverse='Latin form of', order=3, @@ -79,6 +87,7 @@ borrows_name=True, borrows_rank=False, lang=None, + suffix_checked=True, words='same name at another rank as', reverse='same name at another rank as', order=1, diff --git a/phylohist/loader/corpus.py b/phylohist/loader/corpus.py index 50668ba..d48c7c2 100644 --- a/phylohist/loader/corpus.py +++ b/phylohist/loader/corpus.py @@ -1,6 +1,7 @@ """The records and trees of one load (`load.load`), handed explicitly to everything that looks a record up. Each load builds a corpus of its own.""" +from ..ranks import TABLE from .research import Author, Publication, Source, SourceAlias from .taxa import Section, Taxon @@ -8,17 +9,20 @@ class Corpus: """What one load builds: the author, publication, source, taxon and section records, each a dict from key to record in the order added; the - repository registry (`data/repositories.yaml`); the data files as loaded - (`data`); and ``{source_key: [root Tree]}`` (`roots`). A record is built - against the corpus it is added to and keeps no reference to it.""" + repository registry (`data/repositories.yaml`); the rank table (`ranks`, + a `ranks.RankTable`: the load's `ranks.yaml`, the repository's own for a + bare `Corpus()`); the data files as loaded (`data`); and ``{source_key: + [root Tree]}`` (`roots`). A record is built against the corpus it is + added to and keeps no reference to it.""" - def __init__(self, repositories=None): + def __init__(self, repositories=None, ranks=None): self.authors = {} self.publications = {} self.sources = {} self.taxa = {} self.sections = {} self.repositories = dict(repositories or {}) + self.ranks = ranks if ranks is not None else TABLE self.data = None self.roots = {} @@ -50,7 +54,7 @@ def copy(self): """A corpus whose dicts are its own, sharing the records and trees: what is added to the copy is not in this one. `data` is copied at its top level, `roots` with its lists.""" - other = Corpus(self.repositories) + other = Corpus(self.repositories, self.ranks) other.authors = dict(self.authors) other.publications = dict(self.publications) other.sources = dict(self.sources) diff --git a/phylohist/loader/io.py b/phylohist/loader/io.py index 36a217e..32744b4 100644 --- a/phylohist/loader/io.py +++ b/phylohist/loader/io.py @@ -24,6 +24,7 @@ 'sections', 'repositories', 'roles', + 'ranks', ) diff --git a/phylohist/loader/load.py b/phylohist/loader/load.py index 6f2679c..80e5c21 100644 --- a/phylohist/loader/load.py +++ b/phylohist/loader/load.py @@ -13,12 +13,13 @@ logged before it. """ +import collections import contextlib import json import logging from ..derivatives import kind_of -from ..ranks import GENUS_GROUP, SPECIES_GROUP, fold_rank, implied_rank, rank_order +from ..ranks import GENUS_GROUP, SPECIES_GROUP, RankTable, fold_rank, implied_rank, rank_order from . import checks, material, nomenclature from .corpus import Corpus from .io import DATA_DIR, TREE_DIR, LoadError, display_path, fail, load_files @@ -206,9 +207,6 @@ def _page_form(value): def _same_protologue_value(field, record_value, node_value): - if field == 'rank': - # A node with no rank word is the record's null or `Unranked`. - return record_value == node_value or (node_value is None and record_value == 'Unranked') if field == 'pages': # An inferred node's null has no page to agree with the record's. return node_value is not None and _page_form(record_value) == _page_form(node_value) @@ -339,6 +337,23 @@ def _report_rank_order(corpus): ) +def _report_rank_exceptions(corpus): + # An exception of the suffix lint (`ranks.yaml`) names a record at the + # rank it excuses; one that names none, or only records at another rank, + # is stale. + at_rank = collections.defaultdict(dict) + for taxon in corpus.taxa.values(): + if taxon.name is not None: + at_rank[taxon.name][fold_rank(taxon.rank)] = taxon.rank + for name, rank in corpus.ranks.exceptions: + found = at_rank.get(name) + if not found: + logger.error(f'ranks.yaml: exception {name} ({rank}) names no record') + elif fold_rank(rank) not in found: + at = ', '.join(sorted(r or 'no rank' for r in found.values())) + logger.error(f'ranks.yaml: exception {name} ({rank}) names a record at another rank ({at})') + + def _report_section_protologues(corpus): # A section record that gives `pages` must agree with the `sectionStart` # marker flagged `new` for it in its authority's trees, on the fields that @@ -460,6 +475,7 @@ def _load_trees(corpus): _report_missing_protologues(corpus) _report_protologue_mismatches(corpus) _report_rank_order(corpus) + _report_rank_exceptions(corpus) _report_section_protologues(corpus) _report_lapsus_records(corpus) _report_material(corpus) @@ -556,7 +572,7 @@ def load(drafts=False, tolerate=False, data_dir=DATA_DIR, fast=False): `load_files`'s: another directory shaped like `data/`, and libyaml.""" with collecting() as logged: data = load_files(drafts=drafts, data_dir=data_dir, fast=fast) - corpus = Corpus(data.get('repositories')) + corpus = Corpus(data.get('repositories'), RankTable(data['ranks'])) corpus.data = data _basic_load(corpus, 'authors', corpus.add_author) _basic_load(corpus, 'publications', corpus.add_publication) diff --git a/phylohist/loader/nomenclature.py b/phylohist/loader/nomenclature.py index 651dd8c..fd08bfe 100644 --- a/phylohist/loader/nomenclature.py +++ b/phylohist/loader/nomenclature.py @@ -9,9 +9,9 @@ ``(level, message)`` pairs, ``level`` being ``'error'`` or ``'warning'``; the caller adds the source key and, for a per-node check, the node's path. A check that needs a taxon record takes `taxa`, a callable from a taxon -key to an object with `name`, `rank` and `mineral` (a loaded corpus's `taxa.get` gives a -`Taxon`, which does), or `None` -for a key with no record; `record_lookup` builds one from the raw +key to an object with `name`, `rank` (`None` for a record with none) and +`mineral` (a loaded corpus's `taxa.get` gives a `Taxon`, which does), or +`None` for a key with no record; `record_lookup` builds one from the raw `data/taxa.yaml` for a caller that has not loaded the corpus. """ @@ -56,12 +56,12 @@ def _borrows_name(kind, data): def _raw_rank(records, key): """The rank `Taxon` would give the raw record: that of the record a - spelling borrows it from, else its own `rank` (`Unranked` for an explicit + spelling borrows it from, else its own `rank` (`None` for an explicit null), else a species when its name is lower-case and a genus otherwise; `None` when it has neither.""" data = _borrowed_from(records, key, _borrows_rank) if 'rank' in data: - return data['rank'] or 'Unranked' + return data['rank'] name = data.get('name') return None if name is None else implied_rank(name) @@ -93,6 +93,17 @@ def _is_species_level(record): return fold_rank(record.rank) in SPECIES_GROUP +def _level_known(record): + """Whether the record's level can be compared: it has a rank, or it is a + mineral, which has none and is no species-level name; a record written + `rank: null` says nothing about its level.""" + return record.rank is not None or record.mineral + + +def _rank_words(record): + return record.rank or ('mineral' if record.mineral else 'no rank') + + def compared_link(node): """`(sign, target)` for an open form's link, or `None` when the node has no `openTaxon` or no link (or both, which `compared_links` reports).""" @@ -105,8 +116,9 @@ def compared_link(node): def compared_links(node, taxa): """A `cf` or `aff` sits only on an `openTaxon` node, never both on one node; its target has a record that is named (not itself a placeholder), - and the open record and the target are both species-level or both not; - an error otherwise.""" + and the open record and the target are both species-level or both not + (a record written `rank: null` is not compared; a mineral, which has no + rank, is no species-level name); an error otherwise.""" signs = [sign for sign in SIGNS if sign in node] messages = [ ('error', f'`{sign}` on a node that is not an `openTaxon`') @@ -127,13 +139,13 @@ def compared_links(node, taxa): messages.append( ('error', f'`{sign}` target "{key}" is itself a placeholder (it has no name)') ) - elif own is not None and own.rank and target.rank: + elif own is not None and _level_known(own) and _level_known(target): if _is_species_level(own) != _is_species_level(target): messages.append( ( 'error', - f'`{sign}` target "{key}" ({target.rank}) is not at the rank level of the open ' - f'form "{node["openTaxon"]}" ({own.rank})', + f'`{sign}` target "{key}" ({_rank_words(target)}) is not at the rank level of the ' + f'open form "{node["openTaxon"]}" ({_rank_words(own)})', ), ) return messages @@ -141,12 +153,13 @@ def compared_links(node, taxa): def quoted_parent(node, taxa): """`quotedParent` quotes a species' genus, so the node's own record is - species-level; an error otherwise.""" + species-level; an error otherwise (a record written `rank: null` is not + reported; a mineral, which has no rank, is no species-level name).""" if not node.get('quotedParent'): return [] key = next((node[f] for f in _OWN_FIELDS if isinstance(node.get(f), str)), None) record = taxa(key) if key is not None else None - if record is None or not record.rank or _is_species_level(record): + if record is None or not _level_known(record) or _is_species_level(record): return [] return [('error', '`quotedParent` on a node that is not a species-level name')] diff --git a/phylohist/loader/taxa.py b/phylohist/loader/taxa.py index 3582083..1dd4571 100644 --- a/phylohist/loader/taxa.py +++ b/phylohist/loader/taxa.py @@ -9,14 +9,6 @@ logger = logging.getLogger(__name__) -NON_RANKS = { - 'Grade', - 'Branch', - 'Group', - 'Plesion', -} - - # The keys of an `authority` block that name an attribution, as against the # locators (`pages`, `illustrations`) a block may carry alone. ATTRIBUTION_KEYS = ('source', 'auctt', 'auth', 'year') @@ -172,10 +164,10 @@ def __init__(self, taxon_data, taxon_key, corpus): self._rank = self._data.get('rank') self._mineral = bool(self._data.get('mineral')) - if 'rank' in self._data and self._rank is None: - # An explicit null: the protologue placed the taxon with no rank word. - self._rank = 'Unranked' - if not self._rank: + # `rank: null` is no rank: the source prints no rank word and no + # cladistic term. Only a record that writes no `rank` takes one from + # its name. + if 'rank' not in self._data: if self._name is None: # The schema requires `rank` on a null-name record; this is the backstop. fail( @@ -221,7 +213,7 @@ def __init__(self, taxon_data, taxon_key, corpus): if not valid: logger.error(f'"{taxon_key}" not in expected set: {valid_set}') - self._check_rank() + self._check_rank(corpus.ranks) logger.debug(f' ...all authorities for "{taxon_key}" processed') @@ -263,7 +255,8 @@ def _check_expected_taxon(self, expected): else: expected_set = {expected} - expected_set.add(f'{expected}-{self.rank.lower()}') + if self.rank is not None: + expected_set.add(f'{expected}-{fold_rank(self.rank)}') if op := self.original_parent: expected_set = {e + f'_{op.lower()}' for e in expected_set} @@ -273,106 +266,15 @@ def _check_expected_taxon(self, expected): return True, expected_set return False, expected_set - def _check_rank(self): - if ( - self.mineral - or self.rank in NON_RANKS - or self.name.lower() == self.name - or self.key.endswith('-' + self.rank.lower()) - ): + def _check_rank(self, table): + """The suffix lint (`ranks.RankTable.suffix_problems`), each problem a + warning, on a record that is no mineral, at the rank it gives or + borrows, unless its kind is one the lint passes over + (`Kind.suffix_checked`).""" + if self._mineral or (self._kind is not None and not self._kind.suffix_checked): return - - for suffix, rank, exceptions in ( - ('inae', 'Subfamily', frozenset({'Placocystida'})), - ( - 'idae', - 'Family', - frozenset( - { - 'Crinoiden', - 'Crinoideen', - 'Cystideen', - 'Cystidea', - 'Cystideae', - 'Cystoidea', - 'Échinides', - 'Echinides', - 'Stelleridea', - 'Stellérides', - 'Stellerides', - 'Stelleridica', - 'Fistulides', - } - ), - ), - ): - if self.rank == rank and not self.name.endswith(suffix) and self.name not in exceptions: - logger.warning( - f'{self.name} of rank {rank} expected to end with suffix "{suffix}"', - ) - - # TODO: Verify that each exception is the expected rank - ida_classes = frozenset( - { - 'Acalephida', - 'Arachnida', - 'Echinida', - 'Fistulida', - 'Medusida', - 'Stellerida', - 'Zoanthida', - } - ) - ida_subclasses = frozenset({'Disparida', 'Helicoplacida', 'Polyplacida'}) - ida_parvclasses = frozenset({'Cladida'}) - ida_superfamilies = frozenset( - { - 'Aristocystitida', - 'Caryocystitida', - 'Glyptocystitida', - 'Glyptosphaeritida', - 'Hemicosmitida', - 'Protocrinitida', - 'Sphaeronitida', - } - ) - ida_subfamilies = frozenset({'Placocystida'}) - ida_exceptions = ( - ida_classes | ida_subclasses | ida_parvclasses | ida_superfamilies | ida_subfamilies - ) - - ina_exceptions = frozenset( - { - 'Carallina', - 'Corallina', - 'Craterina', - 'Funiculina', - 'Intestina', - 'Meandrina', - 'Palasterina', - 'Palaeasterina', - 'Tellina', - } - ) - - for suffix, ranks, exceptions in ( - ('inae', ('Subfamily',), frozenset({'Gallinae'})), - ('acea', ('Superfamily',), frozenset({'Crustacea', 'Testacea'})), - ('ina', ('Suborder',), ina_exceptions), - ('ida', ('Order',), ida_exceptions), - ( - 'zoa', - ('Class', 'Subphylum', 'Phylum', 'Subkingdom', 'Kingdom'), - frozenset({'Lithozoa'}), - ), - ): - if self.name.endswith(suffix) and self.rank not in ranks and self.name not in exceptions: - logger.warning( - f'{self.name} with suffix "{suffix}" expected to have one of ranks ' - f'{ranks} but has rank {self.rank} and is not in {exceptions}', - ) - - # TODO: More ranks, but they get increasingly inconsistent. + for message in table.suffix_problems(self._name, self._rank, self._authority.year): + logger.warning(message) def __str__(self): return f'{self.display_name} ({self._authority})' @@ -399,7 +301,9 @@ def display_name(self): @property def rank(self): - # TODO: Handle this properly, it's more complex than it looks + """The record's rank: its own `rank`, `None` for an explicit null (the + source prints none), the rank a spelling borrows, or a genus or species + read from a name that gives none.""" return self._rank @property @@ -426,10 +330,10 @@ def derivative_kind(self): @property def declared_rank(self): - """The `rank` the record writes, `None` when it writes none; `rank` - fills the gaps (a genus or species from the name, `Unranked` for a - null, a derivative's borrowed rank), and the protologue check reads - the record's own word.""" + """The `rank` the record writes, `None` when it writes none or writes a + null; `rank` fills the gaps (a genus or species from the name, a + derivative's borrowed rank), and the protologue check reads the + record's own word.""" return self._data.get('rank') @property diff --git a/phylohist/ranks.py b/phylohist/ranks.py index d36a5a3..f44ae47 100644 --- a/phylohist/ranks.py +++ b/phylohist/ranks.py @@ -1,56 +1,23 @@ """The ranks a record or a node may be given, and the groups of them the loader's checks and the query side both test a rank against. -`RANKS` is the schema's `rank` enum in its order and casing (a test holds -it there); every comparison reads a rank through `fold_rank`, and -`rank_order` sorts by the enum's order. Both sides import this module, -which imports nothing of either. What the informal ranks mean, and how -`Unranked` differs from a rank the source never printed, is not decided -here (roadmap G8). +`data/ranks.yaml` is the table: the rank words in order and as the data +writes them, the informal words flagged, the endings the loader's suffix +lint expects, and the names it lets pass. This module reads it once at +import for the constants both sides use (`RANKS`, the schema's `rank` enum +in its order and casing, held equal by a test; `SPECIES_GROUP`; +`GENUS_GROUP`); a load reads its own copy into a `RankTable`, which the lint +runs over. Every comparison reads a rank through `fold_rank`, and +`rank_order` sorts by the table's order. A record written `rank: null` +(the source prints no rank word and no cladistic term) has no rank: its +`rank` is `None`. """ -# The schema's `rank` enum, in its order and casing. -RANKS = ( - 'Domain', - 'Kingdom', - 'Subkingdom', - 'Superphylum', - 'Phylum', - 'Subphylum', - 'Infraphylum', - 'Superclass', - 'Class', - 'Subclass', - 'Infraclass', - 'Parvclass', - 'Superorder', - 'Order', - 'Suborder', - 'Superfamily', - 'Family', - 'Subfamily', - 'genus', - 'subgenus', - 'species', - 'subspecies', - 'variety', - 'Division', - 'section', - 'Branch', - 'Group', - 'Grade', - 'Clade', - 'Plesion', - 'Scion', - 'Unranked', -) +import pathlib -# The species group, whose names are combinations with a genus and whose -# types are specimens, and the genus group, whose types are species; folded. -SPECIES_GROUP = ('species', 'subspecies', 'variety') -GENUS_GROUP = ('genus', 'subgenus') +import yaml -_ORDER = {rank.lower(): index for index, rank in enumerate(RANKS)} +TABLE_PATH = pathlib.Path(__file__).resolve().parent.parent / 'data' / 'ranks.yaml' def fold_rank(rank): @@ -58,13 +25,84 @@ def fold_rank(rank): return (rank or '').lower() -def rank_order(rank): - """Where `rank` sorts: its place in `RANKS`, after all of them for a rank - outside them or none.""" - return _ORDER.get(fold_rank(rank), len(RANKS)) - - def implied_rank(name): """The rank a record or node that gives none takes from its name: a species when the name is lower-case, else a genus.""" return 'species' if name.islower() else 'genus' + + +class RankTable: + """A `ranks.yaml` document as the code reads it: `names` in order, + `informal` (folded), `exceptions` as `(name, rank)` pairs, the groups, and + the suffix lint's rules.""" + + def __init__(self, document): + self.document = document + entries = document['ranks'] + self.names = tuple(entry['name'] for entry in entries) + self._entries = {fold_rank(entry['name']): entry for entry in entries} + self.informal = frozenset(fold_rank(e['name']) for e in entries if e.get('informal')) + self._checked_from = document['checkedFrom'] + self._suffixes = tuple( + (rule['suffix'], tuple(rule['ranks']), frozenset(fold_rank(r) for r in rule['ranks'])) + for rule in document['suffixes'] + ) + self.exceptions = tuple((e['name'], e['rank']) for e in document['exceptions']) + self._excepted = {(name, fold_rank(rank)) for name, rank in self.exceptions} + + def group(self, group): + """The folded names of the `genus` or `species` group, in order.""" + return tuple(fold_rank(e['name']) for e in self._entries.values() if e.get('group') == group) + + def suffix_problems(self, name, rank, year): + """What the suffix lint says of a name at a rank in a work of `year`: + nothing for a work before `checkedFrom` or with no year, for no rank or + one outside the table, for an informal rank or the genus and species + groups, or for an exception; else, in both directions, the ending the + rank expects (an ending with `before` only in a work before that year) + and the ranks the name's ending expects.""" + if year is None or year < self._checked_from: + return [] + folded = fold_rank(rank) + entry = self._entries.get(folded) + if ( + entry is None + or entry.get('informal') + or entry.get('group') + or (name, folded) in self._excepted + ): + return [] + problems = [] + endings = [ + e['suffix'] for e in entry.get('endings', ()) if 'before' not in e or year < e['before'] + ] + if endings and not name.endswith(tuple(endings)): + expected = ' or '.join(f'"{suffix}"' for suffix in endings) + problems.append(f'{name} of rank {rank} expected to end with suffix {expected}') + for suffix, ranks, folded_ranks in self._suffixes: + if name.endswith(suffix) and folded not in folded_ranks: + problems.append( + f'{name} with suffix "{suffix}" expected to have one of ranks ' + f'({", ".join(ranks)}) but has rank {rank}' + ) + return problems + + +with open(TABLE_PATH) as _fd: + TABLE = RankTable(yaml.safe_load(_fd)) + +# The table's rank words, in its order and casing. +RANKS = TABLE.names + +# The species group, whose names are combinations with a genus and whose +# types are specimens, and the genus group, whose types are species; folded. +SPECIES_GROUP = TABLE.group('species') +GENUS_GROUP = TABLE.group('genus') + +_ORDER = {rank.lower(): index for index, rank in enumerate(RANKS)} + + +def rank_order(rank): + """Where `rank` sorts: its place in `RANKS`, after all of them for a rank + outside them or none.""" + return _ORDER.get(fold_rank(rank), len(RANKS)) diff --git a/phylohist/resolve.py b/phylohist/resolve.py index 2c36117..d90cb96 100644 --- a/phylohist/resolve.py +++ b/phylohist/resolve.py @@ -297,8 +297,9 @@ def resolve_name(self, query, rank=None): } if rank is not None: + # A record written `rank: null` has no rank word; `unranked` asks for it. wanted = fold_rank(rank) - keys = {k for k in keys if self.store._rank_of(k) == wanted} + keys = {k for k in keys if (self.store._rank_of(k) or 'unranked') == wanted} candidates = [self._candidate(k) for k in keys] candidates.sort( diff --git a/phylohist/tools.py b/phylohist/tools.py index 7b3f116..bb5d32f 100644 --- a/phylohist/tools.py +++ b/phylohist/tools.py @@ -118,7 +118,8 @@ def printed_forms(record, source=None, style='text'): 'name does not exist. An open form (a cf. or aff. record) says what it ' "is compared with. A mineral formation's name, under which a fossil was " 'once described, resolves like any named record and is marked "mineral". ' - 'Optionally restrict by rank word.' + 'Optionally restrict by rank word; "unranked" finds the records whose source ' + 'prints no rank.' ), 'resolve_source': ( 'The sources a citation can mean: "Dehm 1961", "Holloway & Jell ' @@ -308,7 +309,7 @@ def _spec(name, properties, required): 'resolve_name', { 'query': {'type': 'string', 'description': 'the printed name'}, - 'rank': {'type': 'string', 'description': 'optional rank word'}, + 'rank': {'type': 'string', 'description': 'optional rank word; "unranked" for none'}, }, ['query'], ), diff --git a/phylohist/words.py b/phylohist/words.py index cd74bb4..c89f6d1 100644 --- a/phylohist/words.py +++ b/phylohist/words.py @@ -413,15 +413,16 @@ def display(self, key, source=None, path=None): def placeholder_words(self, key, source=None, path=None): """A placeholder in the source's words: a bin by its rank ("Order - uncertain", "Unnamed family"), an open-nomenclature record by its - designation, or the words of its key ("Agelacrinites sp.").""" + uncertain", "Unnamed family", "Unnamed unranked" for a record written + `rank: null`), an open-nomenclature record by its designation, or the + words of its key ("Agelacrinites sp.").""" row = self.store.names.get(key) or {} kind = row.get('placeholder') rank = (row.get('rank') or '').lower() if kind == 'uncertain': return f'{rank.capitalize()} uncertain' if rank else 'Uncertain' if kind == 'unnamed': - return f'Unnamed {rank}' if rank else 'Unnamed' + return f'Unnamed {rank}' if rank else 'Unnamed unranked' if row.get('designation'): return row['designation'] words = [self._OPEN_WORDS.get(w, w) for w in key_stem(key).split('-')] diff --git a/schemas/phylogeny.yaml b/schemas/phylogeny.yaml index 32c3c80..e4976bf 100644 --- a/schemas/phylogeny.yaml +++ b/schemas/phylogeny.yaml @@ -423,6 +423,12 @@ $defs: - required: [source] - required: [auctt] rank: + description: | + The rank words of `data/ranks.yaml`, in its order and casing (a test + holds the two equal): the formal ranks, capitalised above the genus + group and lower-case from the genus down, then the informal words a + source prints in a rank's place. A record or node whose source prints + no rank word and no cladistic term writes `null`. type: string enum: - Domain @@ -456,7 +462,6 @@ $defs: - Clade - Plesion - Scion - - Unranked citationFields: $comment: | @@ -573,8 +578,9 @@ $defs: description: | Explicit on a record that gives its protologue page (`authority.pages`), where the tree node leaves a genus or - species implicit; `null` says the - protologue placed the taxon with no rank word. An unnamed record + species implicit; `null` says the protologue prints no rank word + and no cladistic term for the taxon, and the loader reads it as + no rank (a modern unranked clade is `Clade`). An unnamed record spells its rank, and a node giving no rank of its own under a genus, subgenus or species is at a rank below its parent's; a node written `rank: null` stands outside that order (the loader @@ -819,7 +825,8 @@ $defs: - type: "null" rank: description: | - `null`: the source places the taxon with no rank word. + `null`: the source prints no rank word and no cladistic term for + the taxon (one it calls a clade is `Clade`). oneOf: - $ref: "phylogeny#/$defs/rank" - type: "null" @@ -951,8 +958,8 @@ $defs: enum: [paraphyletic, polyphyletic] sensu: description: | - A printed "s. s." / "s. l." / "s. em." qualifying the name as this - source uses it. + A printed "s. s." / "s. l." / "s. em." / "s. ext." qualifying the + name as this source uses it. type: string enum: [stricto, lato, emendato, extenso] @@ -2157,6 +2164,82 @@ $defs: $ref: "#/$defs/role" notes: type: string + ranks: + description: | + `data/ranks.yaml`: the rank words in order and as the data writes + them, the informal ones flagged; the endings the loader's suffix lint + expects of a name at a rank and the ranks it expects of a name with + an ending; and the names it lets pass, each with why. Rank-valued + fields are plain strings, not `$defs/rank`, so that the census counts + only the data's ranks; a test holds them to the table's own names. + type: object + additionalProperties: false + required: [ranks, checkedFrom, suffixes, exceptions] + properties: + ranks: + type: array + minItems: 1 + items: + type: object + additionalProperties: false + required: [name] + properties: + name: + type: string + informal: + description: Not a rank of the Code's hierarchy; the suffix lint skips it. + type: boolean + group: + description: The Code's genus or species group; the suffix lint skips it. + type: string + enum: [genus, species] + endings: + description: The endings a name at this rank takes; one with `before` only in a work before that year. + type: array + minItems: 1 + items: + type: object + additionalProperties: false + required: [suffix] + properties: + suffix: + type: string + pattern: ^[a-z]+$ + before: + $ref: "#/$defs/publicationYear" + checkedFrom: + description: The authority year from which the suffix lint checks a name. + $ref: "#/$defs/publicationYear" + suffixes: + description: The ranks a name with an ending is expected to be at. + type: array + items: + type: object + additionalProperties: false + required: [suffix, ranks] + properties: + suffix: + type: string + pattern: ^[a-z]+$ + ranks: + type: array + minItems: 1 + items: + type: string + exceptions: + description: Names the lint passes at that rank only, each with why. + type: array + items: + type: object + additionalProperties: false + required: [name, rank, note] + properties: + name: + type: string + rank: + type: string + note: + type: string publicationYear: $comment: Linnaeus was born in 1707. The upper bound is fragile but helpful for now; apologies to the future. diff --git a/scripts/schema-usage.md b/scripts/schema-usage.md index 92d2235..d7a6691 100644 --- a/scripts/schema-usage.md +++ b/scripts/schema-usage.md @@ -352,6 +352,26 @@ property. | `regions/items/oneOf/1.value` | 2 | 100.0% | | `inferred` | 0 | 0.0% | +### `ranks` -- 1 instances in `data/` + +| property | data | data % | +|---|---|---| +| `ranks/items.name` | 31 | 100.0% | +| `ranks/items.informal` | 8 | 25.8% | +| `exceptions/items.name` | 7 | 100.0% | +| `exceptions/items.note` | 7 | 100.0% | +| `exceptions/items.rank` | 7 | 100.0% | +| `suffixes/items.ranks` | 6 | 100.0% | +| `suffixes/items.suffix` | 6 | 100.0% | +| `ranks/items.group` | 5 | 16.1% | +| `ranks/items.endings/items.suffix` | 4 | 100.0% | +| `ranks/items.endings` | 3 | 9.7% | +| `checkedFrom` | 1 | 100.0% | +| `exceptions` | 1 | 100.0% | +| `ranks` | 1 | 100.0% | +| `ranks/items.endings/items.before` | 1 | 25.0% | +| `suffixes` | 1 | 100.0% | + ### `relationalFields` -- 5784 instances in `data/` | property | data | data % | @@ -639,7 +659,7 @@ property. ### `phylogeny#/$defs/rank` -24 of 32 members used, 1515 occurrences. +23 of 31 members used, 1512 occurrences. | value | count | |---|---| @@ -663,13 +683,21 @@ property. | `'Kingdom'` | 6 | | `'Superorder'` | 4 | | `'Subkingdom'` | 3 | -| `'Unranked'` | 3 | | `'Plesion'` | 3 | | `'Branch'` | 2 | | `'Parvclass'` | 1 | **Never used (8):** `'Domain'`, `'Superphylum'`, `'Infraphylum'`, `'Superclass'`, `'Infraclass'`, `'subspecies'`, `'Clade'`, `'Scion'` +### `phylogeny#/$defs/ranks/properties/ranks/items/properties/group` + +2 of 2 members used, 5 occurrences. + +| value | count | +|---|---| +| `'species'` | 3 | +| `'genus'` | 2 | + ### `phylogeny#/$defs/repositories/additionalProperties/properties/subject` 2 of 4 members used, 15 occurrences. diff --git a/tests/corpus/README.md b/tests/corpus/README.md index 526234a..a710b17 100644 --- a/tests/corpus/README.md +++ b/tests/corpus/README.md @@ -1,7 +1,9 @@ # The test corpus A frozen slice of `data/` for the tests, taken at commit 34f21b5 (main, -11 October 2026): the seven record files whole, and 53 of the 220 +11 October 2026): the eight record files +whole (`ranks.yaml` copied when the rank table was added; its `exceptions` +are this corpus's own), and 53 of the 220 trees. It is not resynced with `data/`, since the tests must not depend on what the curated data says today. Edit it only to add the case a test needs or to migrate it alongside a schema change, and keep it loading diff --git a/tests/corpus/ranks.yaml b/tests/corpus/ranks.yaml new file mode 100644 index 0000000..df72ae1 --- /dev/null +++ b/tests/corpus/ranks.yaml @@ -0,0 +1,117 @@ +# The rank table: the rank words a record or a node may give, and the +# loader's suffix lint over them (`phylohist/ranks.py`, `$defs/ranks`). +# +# `ranks` lists the formal ranks in order, as the data writes them: +# capitalised above the genus group, lower-case from the genus down. The +# informal words follow, flagged `informal`; `section` stays until roadmap +# C6. A word a source prints that is not here (Stage, Sub-branch) is added +# by the auditor. The schema's `rank` enum restates the names in this order +# (a test holds the two equal). A source that prints no rank word and no +# cladistic term is written `rank: null`. +ranks: +- name: Domain +- name: Kingdom +- name: Subkingdom +- name: Superphylum +- name: Phylum +- name: Subphylum +- name: Infraphylum +- name: Superclass +- name: Class +- name: Subclass +- name: Infraclass +- name: Parvclass +- name: Superorder +- name: Order +- name: Suborder +- name: Superfamily + endings: + - suffix: oidea + # The older ending; the Code's fourth edition (1999) settled -oidea. + - suffix: acea + before: 1999 +- name: Family + endings: + - suffix: idae +- name: Subfamily + endings: + - suffix: inae +- name: genus + group: genus +- name: subgenus + group: genus +- name: species + group: species +- name: subspecies + group: species +- name: variety + group: species +- name: Division + informal: true +- name: section + informal: true +- name: Branch + informal: true +- name: Group + informal: true +- name: Grade + informal: true +- name: Clade + informal: true +- name: Plesion + informal: true +- name: Scion + informal: true + +# The suffix lint checks a named record above the genus group, at a formal +# rank, whose authority year is `checkedFrom` or later (a work with no +# year, one in preparation, is not checked), at the rank it gives or +# borrows. A spelling, gender or vernacular variant is not checked; a +# Latin spelling and a rank variant are. Both directions: a name at a rank +# with `endings` ends in one of them (one with `before` only in a work +# before that year), and a name ending in one of `suffixes` is at one of +# its ranks. None for -oidea, which echinoderm classes end in. +checkedFrom: 1900 +suffixes: +- suffix: inae + ranks: [Subfamily] +- suffix: idae + ranks: [Family] +- suffix: acea + ranks: [Superfamily] +- suffix: ina + ranks: [Suborder] +- suffix: ida + ranks: [Order] +- suffix: zoa + ranks: [Class, Subphylum, Phylum, Subkingdom, Kingdom] + +# Names the lint passes at that rank only; the loader reports one that +# names no record, or only records at another rank. +exceptions: +- name: Disparida + rank: Subclass + note: Moore & Laudon 1943's -ida name, ranked above the order. +- name: Cladida + rank: Parvclass + note: Moore & Laudon 1943's -ida name, ranked above the order. +- name: Helicoplacida + rank: Subclass + note: Durham 1967 names a subclass with the ordinal ending. +- name: Polyplacida + rank: Subclass + note: Durham 1967 names a subclass with the ordinal ending. +- name: Gogiida + rank: Subclass + note: Parsley 2021 raises Broadhead's order to a subclass, keeping the name. +- name: Caryocystitida + rank: Superfamily + note: Jaekel 1918's -ida name, printed as a superfamily. +- name: Promopalaeasteraceae + rank: Superfamily + note: Schuchert 1914's -aceae, neither -oidea nor -acea. +# This frozen corpus keeps Jaekel 1918's superfamily; `data/` has since +# re-entered it as an order. +- name: Hemicosmitida + rank: Superfamily + note: Jaekel 1918's -ida name at the superfamily. diff --git a/tests/corpus/taxa.yaml b/tests/corpus/taxa.yaml index 1677369..57a944f 100644 --- a/tests/corpus/taxa.yaml +++ b/tests/corpus/taxa.yaml @@ -3578,19 +3578,19 @@ cystideen: vulgarSpellingOf: cystidea-family cystideae-unnamed-group_müller.j.p_1854: name: null - rank: Unranked + rank: null authority: source: 1854_müller.j.p notes: Rhombifera, no rank given but Diploporita defined as suborder cystideae-unnamed-subgroup-1_müller.j.p_1854: name: null - rank: Unranked + rank: null authority: source: 1854_müller.j.p notes: Hemicosmitida; rank unstated cystideae-unnamed-subgroup-2_müller.j.p_1854: name: null - rank: Unranked + rank: null authority: source: 1854_müller.j.p notes: Glyptocystitida; rank unstated diff --git a/tests/test_load.py b/tests/test_load.py index db4d7f2..185191b 100644 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -21,9 +21,12 @@ import pytest -from phylohist import regenerate +from phylohist import ranks, regenerate EXPECTED_WARNINGS_PATH = pathlib.Path(__file__).parent / 'expected-warnings.txt' +# A fake set of record files carries the rank table with no exceptions, since +# it has no records for one to name. +EMPTY_TABLE = {**ranks.TABLE.document, 'exceptions': []} @pytest.mark.production @@ -133,7 +136,10 @@ def test_load_fails_on_logged_errors(monkeypatch): def broken(drafts=False, **_): logging.getLogger('phylohist.loader.taxa').error('a check fired') - return {k: {} for k in ('authors', 'publications', 'sources', 'taxa', 'sections', 'trees')} + return { + **{k: {} for k in ('authors', 'publications', 'sources', 'taxa', 'sections', 'trees')}, + 'ranks': EMPTY_TABLE, + } monkeypatch.setattr(loading, 'load_files', broken) with pytest.raises(LoadError, match='1 integrity error while loading'): @@ -283,6 +289,7 @@ def test_tree_without_a_source_record_is_skipped(monkeypatch): def orphan(drafts=False, **_): data = {k: {} for k in ('authors', 'publications', 'sources', 'taxa', 'sections')} + data['ranks'] = EMPTY_TABLE data['trees'] = {'9999_nobody': {'taxonomies': [{'taxon': 'cyathocystis'}]}} return data @@ -426,6 +433,11 @@ def test_an_unnamed_record_spells_its_rank(records, caplog): records.add_taxon(unnamed, 'testum-sp') assert 'Unnamed, unranked taxon testum-sp!' in _errors(caplog) assert 'testum-sp' not in records.taxa + # An explicit null is a rank statement: the source gives none. + with caplog.at_level(logging.ERROR, logger='phylohist'): + caplog.clear() + records.add_taxon({**unnamed, 'rank': None}, 'testum-sp') + assert _errors(caplog) == [] and records.taxa['testum-sp'].rank is None def test_a_record_naming_no_author_loads(records, caplog): @@ -488,6 +500,7 @@ def test_a_missing_record_ends_the_load_whatever_tolerate_says(monkeypatch, capl def broken(drafts=False, **_): logging.getLogger('phylohist.loader.taxa').error('a check fired') data = {k: {} for k in ('authors', 'publications', 'sources', 'sections', 'trees')} + data['ranks'] = EMPTY_TABLE data['taxa'] = {'testum': {'name': 'Testum', 'authority': {'source': '1899_nobody'}}} return data @@ -642,7 +655,7 @@ def test_a_vernacular_record_carries_lang(records, caplog): 'Taxon testen vulgar spelling of cyathocystidae has no `lang`', 'Taxon testidae Latin spelling of testeen, whose record has no `lang`', ] - assert records.taxa['testoidae'].rank == 'Unranked' + assert records.taxa['testoidae'].rank is None def test_the_schema_knows_every_derivative_kind(): @@ -667,14 +680,36 @@ def test_the_schema_knows_every_derivative_kind(): assert not check({'testen': {'name': 'Testen', 'vulgarSpellingOf': 'testa'}}) -def test_the_ranks_are_the_schemas(): - # `phylohist/ranks.py` restates the schema's `rank` enum for both sides - # of the package; the two must not drift apart. +def test_the_ranks_are_the_tables_and_the_schemas(): + # `data/ranks.yaml` is the vocabulary: `phylohist/ranks.py` reads it for + # both sides of the package, and the schema's `rank` enum restates it; + # the three must not drift apart. from phylohist import ranks from phylohist.loader import io + assert ranks.RANKS == ranks.TABLE.names assert ranks.RANKS == tuple(io.load_yaml(io.SCHEMA_PATH)['$defs']['rank']['enum']) - assert set(ranks.SPECIES_GROUP + ranks.GENUS_GROUP) <= set(ranks.RANKS) + assert len(set(ranks.RANKS)) == len(ranks.RANKS) and 'Unranked' not in ranks.RANKS + genus = ranks.RANKS.index('genus') + assert all(rank[0].isupper() for rank in ranks.RANKS[:genus]) + assert all(rank.islower() for rank in ranks.SPECIES_GROUP + ranks.GENUS_GROUP) + assert ranks.TABLE.informal == { + 'division', + 'section', + 'branch', + 'group', + 'grade', + 'clade', + 'plesion', + 'scion', + } + assert ranks.SPECIES_GROUP == ('species', 'subspecies', 'variety') + assert ranks.GENUS_GROUP == ('genus', 'subgenus') + table = ranks.TABLE.document + named = {r for rule in table['suffixes'] for r in rule['ranks']} | { + e['rank'] for e in table['exceptions'] + } + assert named <= set(ranks.RANKS) assert ranks.rank_order('variety') > ranks.rank_order('species') > ranks.rank_order('Genus') assert ranks.rank_order(None) == ranks.rank_order('Stage') == len(ranks.RANKS) assert (ranks.implied_rank('testa'), ranks.implied_rank('Testum')) == ('species', 'genus') @@ -708,5 +743,5 @@ def test_declared_rank_is_the_records_own_word(records): ) records.add_taxon({'name': 'Testvm', 'altSpellingOf': 'testum'}, 'testvm') assert (records.taxa['testum'].rank, records.taxa['testum'].declared_rank) == ('genus', None) - assert (records.taxa['testa'].rank, records.taxa['testa'].declared_rank) == ('Unranked', None) + assert (records.taxa['testa'].rank, records.taxa['testa'].declared_rank) == (None, None) assert (records.taxa['testvm'].rank, records.taxa['testvm'].declared_rank) == ('genus', None) diff --git a/tests/test_mineral_taxa.py b/tests/test_mineral_taxa.py index 51b0c72..365ca4c 100644 --- a/tests/test_mineral_taxa.py +++ b/tests/test_mineral_taxa.py @@ -104,7 +104,6 @@ def test_schema_accepts_a_mineral_record_with_a_null_rank(record): [ {'name': 'mineralum', 'mineral': True, **EARLIER}, {'name': 'mineralum', 'rank': 'species', 'mineral': True, **EARLIER}, - {'name': 'mineralum', 'rank': 'Unranked', 'mineral': True, **EARLIER}, {'name': 'mineralum', 'rank': None, 'mineral': 'yes', **EARLIER}, ], ) @@ -132,8 +131,8 @@ def test_schema_rejects_a_mineral_taxon_that_is_no_key(): # -- records ------------------------------------------------------------------ -def test_a_mineral_reads_as_unranked_and_mineral(records): - assert (records.taxa[MINERAL].rank, records.taxa[MINERAL].mineral) == ('Unranked', True) +def test_a_mineral_reads_as_no_rank_and_mineral(records): + assert (records.taxa[MINERAL].rank, records.taxa[MINERAL].mineral) == (None, True) assert (records.taxa[GENUS].mineral, records.taxa[UNRANKED].mineral) == (False, False) @@ -158,12 +157,14 @@ def test_a_mineral_key_takes_the_authority_suffix_as_a_species_does(records, cap def test_a_mineral_is_exempt_from_the_rank_suffix_warnings(records, caplog): - record = {'name': 'Lapida', 'rank': None, **EARLIER} + record = {'name': 'Lapida', 'rank': 'Class', 'authority': {'auth': [], 'year': 1950}} _, warned = _logged(caplog, logging.WARNING, lambda: records.add_taxon(dict(record), 'lapida')) - assert len(warned) == 1 and 'Lapida with suffix "ida"' in warned[0] - mineral = {**record, 'mineral': True} + assert warned == [ + 'Lapida with suffix "ida" expected to have one of ranks (Order) but has rank Class' + ] + mineral = {**record, 'rank': None, 'mineral': True} _, warned = _logged( - caplog, logging.WARNING, lambda: records.add_taxon(mineral, 'lapida_linnaeus_1758') + caplog, logging.WARNING, lambda: records.add_taxon(mineral, 'lapida_unknown_1950') ) assert warned == [] @@ -212,11 +213,11 @@ def test_mineral_taxon_beside_taxon_is_one_field_too_many(records, caplog): # -- the pairing check -------------------------------------------------------- -def test_record_lookup_carries_mineral_and_reads_an_explicit_null_as_unranked(): - assert LOOKUP(MINERAL) == ('mineralum', 'Unranked', True) - assert LOOKUP(SPELLING) == ('mineralun', 'Unranked', True) +def test_record_lookup_carries_mineral_and_reads_an_explicit_null_as_no_rank(): + assert LOOKUP(MINERAL) == ('mineralum', None, True) + assert LOOKUP(SPELLING) == ('mineralun', None, True) assert LOOKUP(GENUS) == ('Testum', 'genus', False) - assert LOOKUP(UNRANKED) == ('Testaceum', 'Unranked', False) + assert LOOKUP(UNRANKED) == ('Testaceum', None, False) assert LOOKUP('nothing') is None @@ -390,12 +391,18 @@ def test_a_mineral_synonym_yields_a_usage_and_an_acceptance(records): def test_the_names_index_marks_a_mineral_and_its_spellings(records): rows = names_index(records) mineral = rows[MINERAL] - assert (mineral['mineral'], mineral['rank'], mineral['kind']) == (True, 'Unranked', 'primary') + assert (mineral['mineral'], mineral['rank'], mineral['kind']) == (True, None, 'primary') spelling = rows[SPELLING] assert (spelling['mineral'], spelling['kind'], spelling['of']) == (True, 'altSpellingOf', MINERAL) assert 'mineral' not in rows[GENUS] and 'mineral' not in rows[UNRANKED] +def test_the_names_index_carries_a_null_rank(records): + rows = names_index(records) + assert rows[UNRANKED]['rank'] is None and rows[MINERAL]['rank'] is None + assert rows[GENUS]['rank'] == 'genus' + + # -- the store ---------------------------------------------------------------- HOMONYM = 'mineralum' @@ -404,21 +411,25 @@ def test_the_names_index_marks_a_mineral_and_its_spellings(records): @pytest.fixture(scope='module') def synthetic(corpus, tmp_path_factory): """A `ClaimStore` over a copy of `claims/` with `SOURCE`'s claims replaced - by those of a tree citing the mineral in a synonymy, and the names index - carrying the mineral, its spelling and a genus of the same name.""" + by those of a tree citing the mineral in a synonymy under a genus that + sits under a record written `rank: null`, and the names index carrying + the mineral, its spelling, that record and a genus of the mineral's + name.""" directory = tmp_path_factory.mktemp('claims') shutil.copytree(corpus.claims, directory, dirs_exist_ok=True) added = ( (MINERAL, MINERAL_RECORD), (SPELLING, RECORDS[SPELLING]), (GENUS, GENUS_RECORD), + (UNRANKED, RECORDS[UNRANKED]), (HOMONYM, {'name': 'Mineralum', 'rank': 'genus', **EARLIER}), ) records = corpus.records.copy() for key, record in added: records.add_taxon(dict(record), key) + genus = {'taxon': GENUS, 'synonyms': [{'mineralTaxon': MINERAL, 'citedAs': 'Mineralum'}]} tree = Tree( - {'taxon': GENUS, 'synonyms': [{'mineralTaxon': MINERAL, 'citedAs': 'Mineralum'}]}, + {'taxon': UNRANKED, 'children': [genus]}, {'source_key': SOURCE, 'type': 'taxonomy', 'position': 620}, corpus=records, ) @@ -436,12 +447,28 @@ def test_resolve_finds_a_mineral_and_keeps_it_apart_from_a_genus_of_the_name(syn candidates = {c['key']: c for c in synthetic.resolve_name('mineralum')} assert set(candidates) == {MINERAL, HOMONYM} mineral = candidates[MINERAL] - assert (mineral['mineral'], mineral['rank']) == (True, 'Unranked') + assert (mineral['mineral'], mineral['rank']) == (True, None) assert mineral['variants'] == [SPELLING] assert 'mineral' not in candidates[HOMONYM] assert MINERAL not in candidates[HOMONYM]['variants'] +def test_resolve_by_rank_unranked_finds_a_null_rank(synthetic): + # A record written `rank: null` has no rank word; `unranked` asks for it. + assert [c['key'] for c in synthetic.resolve_name('Testaceum', rank='unranked')] == [UNRANKED] + assert [c['key'] for c in synthetic.resolve_name('mineralum', rank='Unranked')] == [MINERAL] + assert [c['key'] for c in synthetic.resolve_name('mineralum', rank='genus')] == [HOMONYM] + assert synthetic.resolve_name('Testaceum', rank='') == [] + + +def test_a_null_rank_prints_no_rank_word(synthetic): + [block] = synthetic.contents(SOURCE, UNRANKED) + assert block['rendered'].splitlines()[1:] == [' Testaceum', ' Genus Testum'] + assert block['nodes'][0]['rank'] is None and 'rankWord' not in block['nodes'][0] + forms = synthetic.printed_forms(MINERAL) + assert forms['heading']['rank'] is None and forms['rendered'].splitlines()[0] == 'mineralum' + + def test_the_synonymy_block_lists_the_mineral_entry(synthetic): [block] = synthetic.contents(SOURCE, GENUS, synonymy=True) assert 'mineralum' in block['rendered'] @@ -453,7 +480,7 @@ def test_the_cli_prints_mineral_in_place_of_the_rank(capsys): candidate = { 'key': MINERAL, 'name': 'mineralum', - 'rank': 'Unranked', + 'rank': None, 'kind': 'primary', 'mineral': True, 'sourcesWithStatements': 1, diff --git a/tests/test_node_nulls.py b/tests/test_node_nulls.py index c4ab02c..8b47a73 100644 --- a/tests/test_node_nulls.py +++ b/tests/test_node_nulls.py @@ -589,7 +589,7 @@ def test_a_section_and_an_unranked_node_count(records, monkeypatch): tree = {'taxon': ORDER, 'children': None} assert _skeleton(records, _phylum(tree, {'taxon': SECTION}), 1131) == 'partly' assert _skeleton(records, _phylum(tree, {'taxon': SECTION, 'children': None}), 1132) == 'all' - monkeypatch.setattr(records.taxa[SECTION], '_rank', 'Unranked') + monkeypatch.setattr(records.taxa[SECTION], '_rank', None) assert _skeleton(records, _phylum(tree, {'taxon': SECTION}), 1133) == 'partly' assert _skeleton(records, _phylum(tree, {'taxon': SECTION, 'children': None}), 1134) == 'all' diff --git a/tests/test_protologue.py b/tests/test_protologue.py index f294dd1..b17e06b 100644 --- a/tests/test_protologue.py +++ b/tests/test_protologue.py @@ -120,10 +120,9 @@ def test_null_illustrations_on_the_node_equal_none_on_the_record(records, caplog ] -def test_an_unranked_node_equals_an_unranked_record(records, caplog): - assert _check(records, {'rank': 'Unranked'}, {'rank': None}, caplog=caplog)[0] == [] +def test_a_null_node_equals_a_null_record(records, caplog): assert _check(records, {'rank': None}, {'rank': None}, caplog=caplog)[0] == [] - assert records.taxa[KEY].rank == 'Unranked' + assert records.taxa[KEY].rank is None errors, _ = _check(records, {'rank': 'variety'}, {'rank': None}, caplog=caplog) assert errors == [f'taxa.yaml {KEY}: rank "variety" but its protologue node in {FILE} has none'] diff --git a/tests/test_rank_suffixes.py b/tests/test_rank_suffixes.py new file mode 100644 index 0000000..8a2ab07 --- /dev/null +++ b/tests/test_rank_suffixes.py @@ -0,0 +1,168 @@ +"""The suffix lint over the rank table (`data/ranks.yaml`, +`phylohist/ranks.py`): which records it checks, from which year, in which +directions, and the exceptions the table carries. + +Synthetic records are added to each test's copy of the loaded test corpus +(`records`); a credit with a year stands for the authority, since the +lint reads the year alone.""" + +import logging + +import pytest +import yaml + +from phylohist import ranks +from phylohist.derivatives import KINDS +from phylohist.loader.load import _report_rank_exceptions +from tests import support + +SOURCE = {'source': '1758_linnaeus'} +MODERN = {'auth': [], 'year': 1950} +SUFFIX_WORDS = ('expected to end with suffix', 'expected to have one of ranks') + + +def _warned(caplog, records, data, key): + with caplog.at_level(logging.WARNING, logger='phylohist'): + caplog.clear() + records.add_taxon(data, key) + return [ + r.getMessage() + for r in caplog.records + if r.levelno == logging.WARNING and any(w in r.getMessage() for w in SUFFIX_WORDS) + ] + + +def _errors(caplog): + return [r.getMessage() for r in caplog.records if r.levelno >= logging.ERROR] + + +def test_the_lint_checks_names_from_the_cut_year(records, caplog): + record = {'name': 'Testida', 'rank': 'Class'} + assert ( + _warned(caplog, records, {**record, 'authority': {'auth': [], 'year': 1899}}, 'testida') == [] + ) + assert _warned( + caplog, records, {**record, 'authority': {'auth': [], 'year': 1900}}, 'testida' + ) == ['Testida with suffix "ida" expected to have one of ranks (Order) but has rank Class'] + # No year, no check. + assert _warned(caplog, records, {**record, 'authority': {'auth': []}}, 'testida') == [] + + +def test_a_rank_expects_its_ending(records, caplog): + assert _warned( + caplog, records, {'name': 'Testa', 'rank': 'Family', 'authority': MODERN}, 'testa' + ) == ['Testa of rank Family expected to end with suffix "idae"'] + assert _warned( + caplog, records, {'name': 'Testidae', 'rank': 'Subfamily', 'authority': MODERN}, 'testidae' + ) == [ + 'Testidae of rank Subfamily expected to end with suffix "inae"', + 'Testidae with suffix "idae" expected to have one of ranks (Family) but has rank Subfamily', + ] + assert _warned( + caplog, records, {'name': 'Testozoa', 'rank': 'Order', 'authority': MODERN}, 'testozoa' + ) == [ + 'Testozoa with suffix "zoa" expected to have one of ranks ' + '(Class, Subphylum, Phylum, Subkingdom, Kingdom) but has rank Order' + ] + + +def test_a_superfamily_ends_in_acea_before_1999_and_oidea_always(records, caplog): + record = {'name': 'Testacea', 'rank': 'Superfamily'} + assert ( + _warned(caplog, records, {**record, 'authority': {'auth': [], 'year': 1998}}, 'testacea') == [] + ) + assert _warned( + caplog, records, {**record, 'authority': {'auth': [], 'year': 1999}}, 'testacea' + ) == ['Testacea of rank Superfamily expected to end with suffix "oidea"'] + assert _warned( + caplog, records, {'name': 'Testa', 'rank': 'Superfamily', 'authority': MODERN}, 'testa' + ) == ['Testa of rank Superfamily expected to end with suffix "oidea" or "acea"'] + # -oidea implies no rank: echinoderm classes end in it. + assert ( + _warned( + caplog, records, {'name': 'Testoidea', 'rank': 'Class', 'authority': MODERN}, 'testoidea' + ) + == [] + ) + + +@pytest.mark.parametrize('rank', ['Grade', 'genus', None]) +def test_an_informal_rank_the_genus_group_and_no_rank_are_not_checked(records, caplog, rank): + assert ( + _warned(caplog, records, {'name': 'Testida', 'rank': rank, 'authority': MODERN}, 'testida') + == [] + ) + + +@pytest.mark.parametrize('kind', KINDS, ids=[kind.field for kind in KINDS]) +def test_a_derivative_is_checked_by_its_kind(records, caplog, kind): + # A Latin spelling and a rank variant are names in their own right; a + # spelling, a gender form and a vernacular form are not. + records.add_taxon( + {'name': 'Testidae', 'rank': 'Family', 'lang': 'DE', 'authority': MODERN}, 'testidae' + ) + variant = {'name': 'Testida', kind.field: 'testidae'} + if kind.field == 'altRankOf': + variant['rank'] = 'Family' + if kind.lang == 'record': + variant['lang'] = 'DE' + warned = _warned(caplog, records, variant, 'testida') + assert bool(warned) == kind.suffix_checked + assert {k.field for k in KINDS if k.suffix_checked} == {'latinSpellingOf', 'altRankOf'} + + +def test_an_exception_passes_a_name_at_its_rank_only(records, caplog): + excepted = { + **ranks.TABLE.document, + 'exceptions': [{'name': 'Testida', 'rank': 'Class', 'note': 'x'}], + } + records.ranks = ranks.RankTable(excepted) + assert ( + _warned(caplog, records, {'name': 'Testida', 'rank': 'Class', 'authority': MODERN}, 'testida') + == [] + ) + assert ( + _warned( + caplog, records, {'name': 'Testida', 'rank': 'Subclass', 'authority': MODERN}, 'testida' + ) + != [] + ) + + +def test_a_stale_exception_is_an_error(records, caplog): + records.add_taxon({'name': 'Testida', 'rank': 'Class', 'authority': MODERN}, 'testida') + records.ranks = ranks.RankTable( + { + **ranks.TABLE.document, + 'exceptions': [ + {'name': 'Nonesuchida', 'rank': 'Class', 'note': 'x'}, + {'name': 'Testida', 'rank': 'Order', 'note': 'x'}, + {'name': 'Testida', 'rank': 'Class', 'note': 'x'}, + ], + } + ) + with caplog.at_level(logging.ERROR, logger='phylohist'): + _report_rank_exceptions(records) + assert _errors(caplog) == [ + 'ranks.yaml: exception Nonesuchida (Class) names no record', + 'ranks.yaml: exception Testida (Order) names a record at another rank (Class)', + ] + + +def test_the_corpora_share_the_vocabulary_and_keep_their_own_exceptions(corpus): + # The loaded corpus carries no suffix warning and its exceptions all + # name records; the frozen corpus keeps an exception `data/` has outgrown. + assert not [w for w in corpus.logged.warnings if any(s in w for s in SUFFIX_WORDS)] + assert ('Hemicosmitida', 'Superfamily') in corpus.records.ranks.exceptions + assert ('Hemicosmitida', 'Superfamily') not in ranks.TABLE.exceptions + live = yaml.safe_load(ranks.TABLE_PATH.read_text()) + frozen = yaml.safe_load((support.CORPUS / 'ranks.yaml').read_text()) + live.pop('exceptions'), frozen.pop('exceptions') + assert live == frozen + + +def test_a_null_rank_record_with_a_qualified_key_is_checked_without_a_rank_form(records, caplog): + with caplog.at_level(logging.ERROR, logger='phylohist'): + caplog.clear() + records.add_taxon({'name': 'Testa', 'rank': None, 'authority': SOURCE}, 'testa-x') + assert _errors(caplog) == ['"testa-x" not in expected set: {\'testa\'}'] diff --git a/tests/test_source_alias.py b/tests/test_source_alias.py index ba2bc9f..449e414 100644 --- a/tests/test_source_alias.py +++ b/tests/test_source_alias.py @@ -14,6 +14,7 @@ import pytest +from phylohist import ranks from phylohist.claims import manifest from phylohist.loader import LoadError, load from phylohist.loader.load import _load_sources @@ -141,6 +142,8 @@ def aliased(drafts=False, **_): 'publications': documents['publications'], 'taxa': {}, 'sections': {}, + # The rank table with no exceptions: the files hold no record for one. + 'ranks': {**ranks.TABLE.document, 'exceptions': []}, } data['sources'] = {TARGET: documents['sources'][TARGET], '1985_banks': {'aliasOf': TARGET}} data['trees'] = {'1985_banks': {'taxonomies': [{'taxon': 'cyathocystis'}]}} diff --git a/tests/test_tools.py b/tests/test_tools.py index 2872694..d5ad419 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -94,6 +94,12 @@ def test_resolve_absent_is_empty(store): assert store.resolve_name('') == [] +def test_an_unnamed_record_with_no_rank_reads_unnamed_unranked(store): + key = 'cystideae-unnamed-group_müller.j.p_1854' + assert store.rank(key) is None + assert store.words.display(key) == 'Unnamed unranked' + + def test_unnamed_records_are_not_found_by_name(store): # The words in a bin's key or an open-nomenclature designation name # other taxa; a prefix query must not sweep them in. @@ -762,6 +768,17 @@ def test_followed_acts_and_sensu_words(store): assert words.claim_words(usage) == 'cites the name sensu stricto' node = {'key': 'crinoidea', 'name': 'Crinoidea', 'rank': 'Class', 'sensu': 'stricto'} assert node_label(node).endswith('Crinoidea (s. s.)') + # Every `sensu` the schema allows has a mark; Bather 1900 prints "sensu + # extenso" on Blastoidea, and the listing used to fail on it. + from phylohist import blocks + from phylohist.loader import io + + schema = io.load_yaml(io.SCHEMA_PATH) + assert set(blocks.SENSU_MARKS) == set( + schema['$defs']['uncertaintyFields']['properties']['sensu']['enum'] + ) + node = {'key': 'blastoidea', 'name': 'Blastoidea', 'rank': 'Class', 'sensu': 'extenso'} + assert node_label(node).endswith('Blastoidea (s. ext.)') def test_material_words(store):