From 484017fc18bf8725150200ec3f768fcc7d5a1cd5 Mon Sep 17 00:00:00 2001 From: funkpower Date: Sat, 19 Sep 2026 19:06:57 -0400 Subject: [PATCH] vocabmerge: a subject scheme, supplied rather than compiled in --- crates/bibclean/README.md | 53 ++++- crates/bibclean/src/lib.rs | 1 + crates/bibclean/src/scheme.rs | 304 ++++++++++++++++++++++++++++ crates/bibclean/src/vocab.rs | 70 +++++++ parity/cases/subjects/cases.ndjson | 67 ++++++ parity/golden/subjects/cases.ndjson | 67 ++++++ parity/oracle/subjects.py | 103 ++++++++++ parity/src/bin/regen.rs | 15 +- parity/tests/subjects.rs | 123 +++++++++++ 9 files changed, 801 insertions(+), 2 deletions(-) create mode 100644 crates/bibclean/src/scheme.rs create mode 100644 parity/cases/subjects/cases.ndjson create mode 100644 parity/golden/subjects/cases.ndjson create mode 100644 parity/oracle/subjects.py create mode 100644 parity/tests/subjects.rs diff --git a/crates/bibclean/README.md b/crates/bibclean/README.md index fdf94b6..601ef3f 100644 --- a/crates/bibclean/README.md +++ b/crates/bibclean/README.md @@ -251,7 +251,7 @@ One heading per line — an `id`, a `name`, and the `count` of works under it: | `--rules ` | Vocabulary specific to this collection | | `--man` | Write this page as a manual page, in roff | -The rules file is JSON, and today holds one thing: +The rules file is JSON and holds two things. ```json {"device_prefixes": ["alexskobo"]} @@ -261,6 +261,57 @@ An e-reader stamps its own name onto every book it touches. The generic device names are built in; one named after its owner is a fact about a single collection, so it is supplied rather than compiled in. +### A subject scheme + +The other thing is a closed list of subjects to file under. A catalogue that +lets anything be a subject ends up with forty thousand of them and no way to +browse; a scheme is the other approach. + +```json +{ + "scheme": { + "subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Fiction"], + "aliases": {"Philosophie": "Philosophy", "Church History": "History"}, + "drop": ["General", "Humanities", "Non-Fiction"], + "parents": {"Phenomenology": "Philosophy"}, + "blocks_fiction": ["Biography"], + "fiction_genres": ["Fiction"] + } +} +``` + +Which subjects a collection uses is the most collection-specific thing there +is — a theology library and a science library agree on none of them — so it +arrives in the file rather than compiled in. + +With one, every heading nothing else has already claimed gets an answer: a +spelling the scheme knows is a merge onto the spelling it uses, and a heading +the scheme does not have at all is a delete. + +``` +$ vocabmerge --kind tag --rules scheme.json tags.ndjson +{"kind":"tag","action":"merge","keep":"Philosophy","drop":"Philosophie",…,"method":"scheme_alias","confidence":0.95,"evidence":"the scheme files \"Philosophie\" under \"Philosophy\""} +{"kind":"tag","action":"delete","keep":null,"drop":"General",…,"method":"scheme_absent","confidence":0.8,"evidence":"\"General\" is not in the scheme, on 88 books"} +{"kind":"tag","action":"delete","keep":null,"drop":"Underwater Basketry",…,"method":"scheme_absent","confidence":0.8,"evidence":"\"Underwater Basketry\" is not in the scheme, on 1 books"} +{"kind":"tag","action":"merge","keep":"History","drop":"Church History",…,"method":"scheme_alias","confidence":0.95,"evidence":"the scheme files \"Church History\" under \"History\""} +``` + +The deletes are the lower confidence of the two on purpose: an alias is a +statement the scheme makes, while an absence only says the scheme does not +list it — which may equally mean the scheme is short a heading. + +Four rules the scheme applies, for anything using it as a library: + +- **Parents come along.** Phenomenology files under Philosophy and + Phenomenology, so a shelf browsed by the parent still finds the child. +- **Order is scheme order**, not the order the headings arrived in. +- **A study is not a novel.** A work already under a heading in + `blocks_fiction` — or whose title says "Companion to", "A Life of", "Diary", + "Very Short Introduction" — loses the `fiction_genres`. A historical novel + may still be both. +- **A book tagged only with headings the scheme rejects has no subject yet**, + which is what a catalogue built from filenames looks like. + ### Exit codes | Code | Meaning | diff --git a/crates/bibclean/src/lib.rs b/crates/bibclean/src/lib.rs index ccf8212..8a2538f 100644 --- a/crates/bibclean/src/lib.rs +++ b/crates/bibclean/src/lib.rs @@ -17,6 +17,7 @@ pub mod fix; pub mod isbn; pub mod matching; pub mod reasons; +pub mod scheme; pub mod slug; pub mod vocab; diff --git a/crates/bibclean/src/scheme.rs b/crates/bibclean/src/scheme.rs new file mode 100644 index 0000000..30c3baa --- /dev/null +++ b/crates/bibclean/src/scheme.rs @@ -0,0 +1,304 @@ +//! A closed subject scheme, and what it says about a heading. +//! +//! A catalogue that lets anything be a subject ends up with forty thousand of +//! them and no way to browse. A scheme is the other approach: a fixed list of +//! headings, everything else mapped onto one or dropped. +//! +//! The scheme itself is supplied rather than compiled in. Which subjects a +//! collection uses, and what its parents are, is the most collection-specific +//! thing there is — a theology library and a science library agree on nothing +//! here — so it arrives as a file and this only knows what to do with one. + +use std::collections::HashMap; +use std::sync::LazyLock; + +use fancy_regex::Regex; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Titles that say the book is *about* somebody rather than by them, or is a +/// study rather than a story. +/// +/// General reference rather than collection-specific: a Companion is a +/// Companion on any shelf. +static STUDY_TITLE: LazyLock = LazyLock::new(|| { + Regex::new(r"(?i)\b(companion to|guidebook to|very short introduction|letters of|collected letters|memoirs?\b|autobiograph|\bdiary\b|\bessays\b|\blectures\b|\bspeeches\b|a life of|biography of)\b") + .expect("a valid pattern") +}); + +/// A subject scheme: what the headings are, and how they relate. +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct Scheme { + /// Every heading the scheme allows, in the order they should be listed. + #[serde(default)] + pub subjects: Vec, + /// Spellings that mean one of the subjects: alias to subject. + #[serde(default)] + pub aliases: HashMap, + /// Headings too broad or too incidental to keep, whatever they say. + #[serde(default)] + pub drop: Vec, + /// Which subject each subject sits under, child to parent. + #[serde(default)] + pub parents: HashMap, + /// Subjects that mean the work is a study, so it cannot also be fiction. + #[serde(default)] + pub blocks_fiction: Vec, + /// The genres a study blocks. + #[serde(default)] + pub fiction_genres: Vec, +} + +impl Scheme { + /// Whether the scheme says anything at all. + pub fn is_empty(&self) -> bool { + self.subjects.is_empty() + } + + /// The subject a heading means, or `None` if it is not one. + /// + /// Exact first, then an alias. A heading on the drop list is not a + /// subject however it is spelled, and the brackets a catalogue wraps its + /// own annotations in are stripped before the comparison. + pub fn canonical(&self, name: &str) -> Option<&str> { + let name = name.trim(); + if name.is_empty() { + return None; + } + let lower = name.to_lowercase(); + let bare = lower.trim_matches(|c: char| "*[]".contains(c)); + + if self.drop.iter().any(|drop| { + let drop = drop.to_lowercase(); + let drop = drop.trim_matches(|c: char| "*[]".contains(c)); + drop == lower || drop == bare + }) { + return None; + } + if let Some(found) = self.subjects.iter().find(|s| s.to_lowercase() == lower) { + return Some(found); + } + self.aliases + .iter() + .find(|(alias, _)| alias.to_lowercase() == lower) + .map(|(_, subject)| subject.as_str()) + } + + /// Every chosen subject, with the parent of each carried alongside it. + /// + /// Phenomenology becomes Philosophy and Phenomenology, so a shelf browsed + /// by the parent still finds the child. Returned in scheme order, which + /// is the order a reader expects to see them listed. + pub fn with_parents(&self, subjects: &[String]) -> Vec { + let mut chosen: Vec<&str> = Vec::new(); + for subject in subjects { + if !self.subjects.iter().any(|s| s == subject) { + continue; + } + chosen.push(subject); + if let Some(parent) = self.parents.get(subject) { + chosen.push(parent); + } + } + self.in_order(&chosen) + } + + /// Drop the fiction genres where the work is plainly a study. + /// + /// A historical novel may be fiction and history at once. A life of + /// somebody, a Companion, or a writer's diary may not be a novel. + pub fn without_impossible_fiction( + &self, + subjects: &[String], + title: Option<&str>, + ) -> Vec { + let study = title.is_some_and(|title| STUDY_TITLE.is_match(title).unwrap_or(false)); + let blocked = subjects + .iter() + .any(|s| self.blocks_fiction.iter().any(|block| block == s)); + + let kept: Vec<&str> = subjects + .iter() + .map(String::as_str) + .filter(|subject| { + !(study || blocked) || !self.fiction_genres.iter().any(|genre| genre == subject) + }) + .collect(); + self.in_order(&kept) + } + + /// The subjects a book should carry: the chosen ones, their parents, and + /// nothing the title rules out. + pub fn compose(&self, subjects: &[String], title: Option<&str>) -> Vec { + self.without_impossible_fiction(&self.with_parents(subjects), title) + } + + /// Whether a book has no real subject yet. + /// + /// Untagged, or tagged only with headings the scheme does not recognise — + /// which is what a catalogue built from filenames looks like. + pub fn needs_subject(&self, existing: &[String]) -> bool { + !existing.iter().any(|tag| self.canonical(tag).is_some()) + } + + /// The given headings, deduplicated and in scheme order. + fn in_order(&self, chosen: &[&str]) -> Vec { + self.subjects + .iter() + .filter(|subject| chosen.contains(&subject.as_str())) + .cloned() + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn scheme() -> Scheme { + Scheme { + subjects: [ + "Philosophy", + "Phenomenology", + "History", + "Biography", + "Fiction", + "Science Fiction", + ] + .iter() + .map(|s| s.to_string()) + .collect(), + aliases: [("Philosophie", "Philosophy"), ("SF", "Science Fiction")] + .iter() + .map(|(a, b)| (a.to_string(), b.to_string())) + .collect(), + drop: ["General", "Humanities", "Non-Fiction"] + .iter() + .map(|s| s.to_string()) + .collect(), + parents: [("Phenomenology", "Philosophy")] + .iter() + .map(|(a, b)| (a.to_string(), b.to_string())) + .collect(), + blocks_fiction: vec!["Biography".into()], + fiction_genres: vec!["Fiction".into(), "Science Fiction".into()], + } + } + + fn subjects(names: &[&str]) -> Vec { + names.iter().map(|s| s.to_string()).collect() + } + + #[test] + fn a_heading_in_the_scheme_is_itself() { + assert_eq!(scheme().canonical("Philosophy"), Some("Philosophy")); + assert_eq!(scheme().canonical("philosophy"), Some("Philosophy")); + } + + #[test] + fn an_alias_is_the_subject_it_means() { + assert_eq!(scheme().canonical("Philosophie"), Some("Philosophy")); + assert_eq!(scheme().canonical("sf"), Some("Science Fiction")); + } + + #[test] + fn a_heading_on_the_drop_list_is_not_a_subject() { + assert_eq!(scheme().canonical("General"), None); + // Including inside the brackets a catalogue wraps its own notes in. + assert_eq!(scheme().canonical("[General]"), None); + assert_eq!(scheme().canonical("*Humanities*"), None); + } + + #[test] + fn a_heading_the_scheme_never_heard_of_is_not_a_subject() { + assert_eq!(scheme().canonical("Underwater Basketry"), None); + assert_eq!(scheme().canonical(""), None); + assert_eq!(scheme().canonical(" "), None); + } + + #[test] + fn a_child_carries_its_parent() { + assert_eq!( + scheme().with_parents(&subjects(&["Phenomenology"])), + ["Philosophy", "Phenomenology"] + ); + } + + #[test] + fn subjects_come_back_in_scheme_order_however_they_went_in() { + assert_eq!( + scheme().with_parents(&subjects(&["History", "Philosophy"])), + ["Philosophy", "History"] + ); + } + + #[test] + fn a_heading_outside_the_scheme_is_dropped_rather_than_carried() { + assert_eq!( + scheme().with_parents(&subjects(&["Basketry"])), + Vec::::new() + ); + } + + #[test] + fn a_biography_is_not_also_a_novel() { + assert_eq!( + scheme().without_impossible_fiction(&subjects(&["Biography", "Fiction"]), None), + ["Biography"] + ); + } + + #[test] + fn a_title_that_says_study_rules_out_fiction_on_its_own() { + for title in [ + "The Cambridge Companion to Nietzsche", + "Logic: A Very Short Introduction", + "A Life of Wrangel", + "The Diary of a Writer", + ] { + assert_eq!( + scheme() + .without_impossible_fiction(&subjects(&["Fiction", "History"]), Some(title)), + ["History"], + "{title}" + ); + } + } + + #[test] + fn a_historical_novel_may_still_be_both() { + assert_eq!( + scheme().without_impossible_fiction( + &subjects(&["Fiction", "History"]), + Some("War and Peace") + ), + ["History", "Fiction"] + ); + } + + #[test] + fn composing_does_the_parents_and_the_fiction_rule_at_once() { + assert_eq!( + scheme().compose( + &subjects(&["Phenomenology", "Fiction"]), + Some("A Life of Husserl") + ), + ["Philosophy", "Phenomenology"] + ); + } + + #[test] + fn a_book_tagged_only_with_rubbish_still_needs_a_subject() { + assert!(scheme().needs_subject(&[])); + assert!(scheme().needs_subject(&subjects(&["General", "ebook"]))); + assert!(!scheme().needs_subject(&subjects(&["General", "Philosophy"]))); + } + + #[test] + fn an_empty_scheme_says_nothing_about_anything() { + let empty = Scheme::default(); + assert!(empty.is_empty()); + assert_eq!(empty.canonical("Philosophy"), None); + assert!(empty.needs_subject(&subjects(&["Philosophy"]))); + } +} diff --git a/crates/bibclean/src/vocab.rs b/crates/bibclean/src/vocab.rs index c180ed8..8becc40 100644 --- a/crates/bibclean/src/vocab.rs +++ b/crates/bibclean/src/vocab.rs @@ -163,6 +163,13 @@ pub struct Rules { /// A device named after its owner belongs here and not in the program. #[serde(default)] pub device_prefixes: Vec, + /// The closed subject scheme this collection files under, if it has one. + /// + /// Which subjects a collection uses is the most collection-specific thing + /// there is — a theology library and a science library agree on none of + /// them — so it arrives here rather than being compiled in. + #[serde(default)] + pub scheme: crate::scheme::Scheme, } /// Every proposal a list of headings suggests. @@ -179,6 +186,68 @@ pub fn cluster(kind: Kind, items: &[Item], rules: &Rules) -> Vec { } if kind == Kind::Tag { out.extend(junk_deletes(items, &grouped, rules)); + out.extend(scheme_proposals(items, &out, &rules.scheme)); + } + out +} + +/// What a subject scheme says about the headings nothing else has claimed. +/// +/// A heading the scheme spells differently is a merge onto the spelling it +/// uses; a heading the scheme does not have at all is a delete. Headings +/// already spoken for by a near-duplicate group or by the junk rules are left +/// alone, so nothing gets two proposals. +fn scheme_proposals( + items: &[Item], + already: &[Proposal], + scheme: &crate::scheme::Scheme, +) -> Vec { + if scheme.is_empty() { + return Vec::new(); + } + let spoken_for: Vec = already.iter().map(|proposal| proposal.drop_id).collect(); + let mut out = Vec::new(); + + for item in items { + if spoken_for.contains(&item.id) { + continue; + } + match scheme.canonical(&item.name) { + // Already the scheme's own spelling: nothing to say. + Some(subject) if subject == item.name => {} + Some(subject) => { + let keep = items.iter().find(|other| other.name == subject); + out.push(Proposal { + kind: Kind::Tag.name().to_string(), + action: "merge".to_string(), + keep: Some(subject.to_string()), + drop: item.name.clone(), + keep_id: keep.map(|keep| keep.id), + drop_id: item.id, + keep_count: keep.map_or(0, |keep| keep.count), + drop_count: item.count, + method: "scheme_alias".to_string(), + confidence: 0.95, + evidence: format!("the scheme files {:?} under {subject:?}", item.name), + }); + } + None => out.push(Proposal { + kind: Kind::Tag.name().to_string(), + action: "delete".to_string(), + keep: None, + drop: item.name.clone(), + keep_id: None, + drop_id: item.id, + keep_count: 0, + drop_count: item.count, + method: "scheme_absent".to_string(), + confidence: 0.8, + evidence: format!( + "{:?} is not in the scheme, on {} books", + item.name, item.count + ), + }), + } } out } @@ -730,6 +799,7 @@ mod tests { fn a_device_named_after_its_owner_comes_from_the_rules() { let rules = Rules { device_prefixes: vec!["alexskobo".into()], + ..Rules::default() }; assert_eq!(junk_reason("[alexskobo] to read", &Rules::default()), None); assert_eq!( diff --git a/parity/cases/subjects/cases.ndjson b/parity/cases/subjects/cases.ndjson new file mode 100644 index 0000000..f891cd4 --- /dev/null +++ b/parity/cases/subjects/cases.ndjson @@ -0,0 +1,67 @@ +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-0", "kind": "canonical", "tag": "Philosophy"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-1", "kind": "canonical", "tag": "philosophy"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-2", "kind": "canonical", "tag": "PHILOSOPHY"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-3", "kind": "canonical", "tag": " Philosophy "} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-4", "kind": "canonical", "tag": "Philosophie"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-5", "kind": "canonical", "tag": "SF"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-6", "kind": "canonical", "tag": "sf"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-7", "kind": "canonical", "tag": "Sci-Fi"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-8", "kind": "canonical", "tag": "General"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-9", "kind": "canonical", "tag": "general"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-10", "kind": "canonical", "tag": "[General]"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-11", "kind": "canonical", "tag": "*Misc*"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-12", "kind": "canonical", "tag": "Humanities"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-13", "kind": "canonical", "tag": "ebook"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-14", "kind": "canonical", "tag": "Underwater Basketry"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-15", "kind": "canonical", "tag": ""} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-16", "kind": "canonical", "tag": " "} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-17", "kind": "canonical", "tag": "Church History"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-18", "kind": "canonical", "tag": "Lit Crit"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-19", "kind": "canonical", "tag": "[Philosophy]"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-20", "kind": "canonical", "tag": "*Philosophy*"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-21", "kind": "canonical", "tag": "Non-Fiction"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-22", "kind": "canonical", "tag": "nonfiction"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-23", "kind": "canonical", "tag": "Theology."} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "canonical-24", "kind": "canonical", "tag": "Fiction"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "parents-25", "kind": "parents", "subjects": ["Phenomenology"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "parents-26", "kind": "parents", "subjects": ["Philosophy"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "parents-27", "kind": "parents", "subjects": ["History", "Philosophy"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "parents-28", "kind": "parents", "subjects": ["Basketry"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "parents-29", "kind": "parents", "subjects": ["Literary Criticism"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "parents-30", "kind": "parents", "subjects": ["Phenomenology", "Literary Criticism"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "parents-31", "kind": "parents", "subjects": []} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "parents-32", "kind": "parents", "subjects": ["Fiction", "Science Fiction"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "parents-33", "kind": "parents", "subjects": ["Philosophy", "Philosophy"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "parents-34", "kind": "parents", "subjects": ["Fantasy", "Phenomenology"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-35", "kind": "fiction", "subjects": ["Biography", "Fiction"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-36", "kind": "fiction", "subjects": ["Fiction", "History"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-37", "kind": "fiction", "subjects": ["Fiction", "History"], "title": "War and Peace"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-38", "kind": "fiction", "subjects": ["Fiction", "History"], "title": "The Cambridge Companion to Nietzsche"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-39", "kind": "fiction", "subjects": ["Fiction", "History"], "title": "Logic: A Very Short Introduction"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-40", "kind": "fiction", "subjects": ["Fiction", "History"], "title": "A Life of Wrangel"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-41", "kind": "fiction", "subjects": ["Fiction", "History"], "title": "The Diary of a Writer"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-42", "kind": "fiction", "subjects": ["Fiction", "History"], "title": "Collected Letters of Paul"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-43", "kind": "fiction", "subjects": ["Fiction", "History"], "title": "Memoirs of a Geisha"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-44", "kind": "fiction", "subjects": ["Fiction", "History"], "title": "Essays in Idleness"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-45", "kind": "fiction", "subjects": ["Science Fiction", "Fantasy", "Biography"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-46", "kind": "fiction", "subjects": ["Literary Criticism", "Fantasy"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-47", "kind": "fiction", "subjects": ["Fiction"], "title": "An Autobiography"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-48", "kind": "fiction", "subjects": ["Fiction"], "title": "Lectures on Ethics"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-49", "kind": "fiction", "subjects": ["Fiction"], "title": "Speeches"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-50", "kind": "fiction", "subjects": ["Fiction"], "title": "The Guidebook to Kant"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "fiction-51", "kind": "fiction", "subjects": ["Fiction"], "title": "Ordinary Title"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "compose-52", "kind": "compose", "subjects": ["Phenomenology", "Fiction"], "title": "A Life of Husserl"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "compose-53", "kind": "compose", "subjects": ["Phenomenology", "Fiction"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "compose-54", "kind": "compose", "subjects": ["Literary Criticism", "Fiction"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "compose-55", "kind": "compose", "subjects": ["Basketry", "Fiction"], "title": "A Life of Someone"} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "compose-56", "kind": "compose", "subjects": []} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "needs-57", "kind": "needs", "existing": []} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "needs-58", "kind": "needs", "existing": ["General"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "needs-59", "kind": "needs", "existing": ["General", "ebook"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "needs-60", "kind": "needs", "existing": ["Philosophy"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "needs-61", "kind": "needs", "existing": ["General", "Philosophy"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "needs-62", "kind": "needs", "existing": ["Underwater Basketry"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "needs-63", "kind": "needs", "existing": ["SF"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "needs-64", "kind": "needs", "existing": ["sf", "General"]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "needs-65", "kind": "needs", "existing": [""]} +{"scheme": {"subjects": ["Philosophy", "Phenomenology", "Theology", "History", "Biography", "Literary Criticism", "Fiction", "Science Fiction", "Fantasy"], "aliases": {"Philosophie": "Philosophy", "SF": "Science Fiction", "Sci-Fi": "Science Fiction", "Church History": "History", "Lit Crit": "Literary Criticism"}, "drop": ["General", "Humanities", "Non-Fiction", "Nonfiction", "ebook", "book", "[General]", "*Misc*"], "parents": {"Phenomenology": "Philosophy", "Literary Criticism": "History"}, "blocks_fiction": ["Biography", "Literary Criticism"], "fiction_genres": ["Fiction", "Science Fiction", "Fantasy"]}, "name": "needs-66", "kind": "needs", "existing": ["[General]"]} diff --git a/parity/golden/subjects/cases.ndjson b/parity/golden/subjects/cases.ndjson new file mode 100644 index 0000000..2dc9d64 --- /dev/null +++ b/parity/golden/subjects/cases.ndjson @@ -0,0 +1,67 @@ +{"kind": "canonical", "name": "canonical-0", "subject": "Philosophy"} +{"kind": "canonical", "name": "canonical-1", "subject": "Philosophy"} +{"kind": "canonical", "name": "canonical-2", "subject": "Philosophy"} +{"kind": "canonical", "name": "canonical-3", "subject": "Philosophy"} +{"kind": "canonical", "name": "canonical-4", "subject": "Philosophy"} +{"kind": "canonical", "name": "canonical-5", "subject": "Science Fiction"} +{"kind": "canonical", "name": "canonical-6", "subject": "Science Fiction"} +{"kind": "canonical", "name": "canonical-7", "subject": "Science Fiction"} +{"kind": "canonical", "name": "canonical-8", "subject": null} +{"kind": "canonical", "name": "canonical-9", "subject": null} +{"kind": "canonical", "name": "canonical-10", "subject": null} +{"kind": "canonical", "name": "canonical-11", "subject": null} +{"kind": "canonical", "name": "canonical-12", "subject": null} +{"kind": "canonical", "name": "canonical-13", "subject": null} +{"kind": "canonical", "name": "canonical-14", "subject": null} +{"kind": "canonical", "name": "canonical-15", "subject": null} +{"kind": "canonical", "name": "canonical-16", "subject": null} +{"kind": "canonical", "name": "canonical-17", "subject": "History"} +{"kind": "canonical", "name": "canonical-18", "subject": "Literary Criticism"} +{"kind": "canonical", "name": "canonical-19", "subject": null} +{"kind": "canonical", "name": "canonical-20", "subject": null} +{"kind": "canonical", "name": "canonical-21", "subject": null} +{"kind": "canonical", "name": "canonical-22", "subject": null} +{"kind": "canonical", "name": "canonical-23", "subject": null} +{"kind": "canonical", "name": "canonical-24", "subject": "Fiction"} +{"kind": "parents", "name": "parents-25", "subjects": ["Philosophy", "Phenomenology"]} +{"kind": "parents", "name": "parents-26", "subjects": ["Philosophy"]} +{"kind": "parents", "name": "parents-27", "subjects": ["Philosophy", "History"]} +{"kind": "parents", "name": "parents-28", "subjects": []} +{"kind": "parents", "name": "parents-29", "subjects": ["History", "Literary Criticism"]} +{"kind": "parents", "name": "parents-30", "subjects": ["Philosophy", "Phenomenology", "History", "Literary Criticism"]} +{"kind": "parents", "name": "parents-31", "subjects": []} +{"kind": "parents", "name": "parents-32", "subjects": ["Fiction", "Science Fiction"]} +{"kind": "parents", "name": "parents-33", "subjects": ["Philosophy"]} +{"kind": "parents", "name": "parents-34", "subjects": ["Philosophy", "Phenomenology", "Fantasy"]} +{"kind": "fiction", "name": "fiction-35", "subjects": ["Biography"]} +{"kind": "fiction", "name": "fiction-36", "subjects": ["History", "Fiction"]} +{"kind": "fiction", "name": "fiction-37", "subjects": ["History", "Fiction"]} +{"kind": "fiction", "name": "fiction-38", "subjects": ["History"]} +{"kind": "fiction", "name": "fiction-39", "subjects": ["History"]} +{"kind": "fiction", "name": "fiction-40", "subjects": ["History"]} +{"kind": "fiction", "name": "fiction-41", "subjects": ["History"]} +{"kind": "fiction", "name": "fiction-42", "subjects": ["History"]} +{"kind": "fiction", "name": "fiction-43", "subjects": ["History"]} +{"kind": "fiction", "name": "fiction-44", "subjects": ["History"]} +{"kind": "fiction", "name": "fiction-45", "subjects": ["Biography"]} +{"kind": "fiction", "name": "fiction-46", "subjects": ["Literary Criticism"]} +{"kind": "fiction", "name": "fiction-47", "subjects": ["Fiction"]} +{"kind": "fiction", "name": "fiction-48", "subjects": []} +{"kind": "fiction", "name": "fiction-49", "subjects": []} +{"kind": "fiction", "name": "fiction-50", "subjects": []} +{"kind": "fiction", "name": "fiction-51", "subjects": ["Fiction"]} +{"kind": "compose", "name": "compose-52", "subjects": ["Philosophy", "Phenomenology"]} +{"kind": "compose", "name": "compose-53", "subjects": ["Philosophy", "Phenomenology", "Fiction"]} +{"kind": "compose", "name": "compose-54", "subjects": ["History", "Literary Criticism"]} +{"kind": "compose", "name": "compose-55", "subjects": []} +{"kind": "compose", "name": "compose-56", "subjects": []} +{"kind": "needs", "name": "needs-57", "needs": true} +{"kind": "needs", "name": "needs-58", "needs": true} +{"kind": "needs", "name": "needs-59", "needs": true} +{"kind": "needs", "name": "needs-60", "needs": false} +{"kind": "needs", "name": "needs-61", "needs": false} +{"kind": "needs", "name": "needs-62", "needs": true} +{"kind": "needs", "name": "needs-63", "needs": false} +{"kind": "needs", "name": "needs-64", "needs": false} +{"kind": "needs", "name": "needs-65", "needs": true} +{"kind": "needs", "name": "needs-66", "needs": true} diff --git a/parity/oracle/subjects.py b/parity/oracle/subjects.py new file mode 100644 index 0000000..2e17ea1 --- /dev/null +++ b/parity/oracle/subjects.py @@ -0,0 +1,103 @@ +"""Apply a subject scheme the way the book pipeline did, for comparison. + +The four functions below are the originals, copied out of the subjects module +verbatim and with the module-level scheme lifted into an argument — importing +it opens a database connection and loads a chat client, and neither decides +anything. What decides is the arithmetic of the scheme, which is what this +compares. + + python3 oracle/subjects.py < cases.ndjson + +One case per line, carrying its own scheme. Writes one JSON answer per line. +""" + +from __future__ import annotations + +import json +import re +import sys + +_STUDY_TITLE_RE = re.compile( + r"\b(companion to|guidebook to|very short introduction|" + r"letters of|collected letters|memoirs?\b|autobiograph|" + r"\bdiary\b|\bessays\b|\blectures\b|\bspeeches\b|" + r"a life of|biography of)\b", + re.IGNORECASE, +) + + +def canonical_tag(name, scheme): + subject_lower = {s.lower(): s for s in scheme["subjects"]} + alias_lower = {k.lower(): v for k, v in scheme["aliases"].items()} + drop_lower = {d.lower().strip("*[]") for d in scheme["drop"]} + t = (name or "").strip() + if not t: + return None + low = t.lower() + if low in drop_lower or low.strip("*[]") in drop_lower: + return None + if low in subject_lower: + return subject_lower[low] + if low in alias_lower: + return alias_lower[low] + return None + + +def expand_parents(subjects, scheme): + subject_set = frozenset(scheme["subjects"]) + chosen = set() + for s in subjects: + if s not in subject_set: + continue + chosen.add(s) + parent = scheme["parents"].get(s) + if parent: + chosen.add(parent) + return [s for s in scheme["subjects"] if s in chosen] + + +def sanitize_subjects(subjects, scheme, title=None): + blocks = frozenset(scheme["blocks_fiction"]) + genres = frozenset(scheme["fiction_genres"]) + chosen = set(subjects) + study = bool(title and _STUDY_TITLE_RE.search(title)) + if chosen & blocks or study: + chosen -= genres + return [s for s in scheme["subjects"] if s in chosen] + + +def compose_tags(subjects, scheme, title=None): + return sanitize_subjects(expand_parents(subjects, scheme), scheme, title=title) + + +def needs_classify(existing, scheme): + subject_set = frozenset(scheme["subjects"]) + for raw in existing: + c = canonical_tag(raw, scheme) + if c and c in subject_set: + return False + return True + + +def answer(case): + scheme, kind = case["scheme"], case["kind"] + if kind == "canonical": + return {"subject": canonical_tag(case["tag"], scheme)} + if kind == "parents": + return {"subjects": expand_parents(case["subjects"], scheme)} + if kind == "fiction": + return {"subjects": sanitize_subjects(case["subjects"], scheme, title=case.get("title"))} + if kind == "compose": + return {"subjects": compose_tags(case["subjects"], scheme, title=case.get("title"))} + if kind == "needs": + return {"needs": needs_classify(case["existing"], scheme)} + raise SystemExit(f"no such case: {kind}") + + +for line in sys.stdin: + if not line.strip(): + continue + case = json.loads(line) + out = {"name": case["name"], "kind": case["kind"]} + out.update(answer(case)) + sys.stdout.write(json.dumps(out, sort_keys=True) + "\n") diff --git a/parity/src/bin/regen.rs b/parity/src/bin/regen.rs index 202cedd..b18d07b 100644 --- a/parity/src/bin/regen.rs +++ b/parity/src/bin/regen.rs @@ -19,7 +19,7 @@ type Written = Result, String>; type Job = (&'static str, fn() -> Written); fn main() -> ExitCode { - let jobs: [Job; 18] = [ + let jobs: [Job; 19] = [ ("tokens", tokens), ("unescape", unescape), ("chunkmd", chunkmd), @@ -38,6 +38,7 @@ fn main() -> ExitCode { ("avplan", avplan), ("pdfgutter", pdfgutter), ("seriesof", seriesof), + ("subjects", subjects), ]; for (name, job) in jobs { @@ -220,6 +221,18 @@ fn cueseg() -> Written { Ok(written) } +fn subjects() -> Written { + let dir = parity::golden("subjects"); + std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?; + + let cases = parity::dir().join("cases/subjects/cases.ndjson"); + let text = std::fs::read_to_string(&cases).map_err(|e| e.to_string())?; + let answers = parity::run_oracle("subjects.py", &text)?; + let path = dir.join("cases.ndjson"); + std::fs::write(&path, answers).map_err(|e| e.to_string())?; + Ok(vec![path.display().to_string()]) +} + fn seriesof() -> Written { let dir = parity::golden("seriesof"); std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?; diff --git a/parity/tests/subjects.rs b/parity/tests/subjects.rs new file mode 100644 index 0000000..1b05d11 --- /dev/null +++ b/parity/tests/subjects.rs @@ -0,0 +1,123 @@ +//! Every judgement a subject scheme makes must match the Python exactly. +//! +//! A scheme decides what a shelf can be browsed by. A heading mapped onto the +//! wrong subject files a book where nobody will look for it, and a fiction +//! genre left on a biography puts a life of somebody in among the novels. + +use bibclean::scheme::Scheme; +use serde_json::Value; + +fn records(text: &str) -> Vec { + text.lines() + .filter(|line| !line.trim().is_empty()) + .map(|line| serde_json::from_str(line).expect("valid JSON")) + .collect() +} + +fn cases() -> Vec { + let text = std::fs::read_to_string(parity::dir().join("cases/subjects/cases.ndjson")) + .expect("a readable case file"); + records(&text) +} + +fn strings(value: &Value) -> Vec { + value + .as_array() + .expect("an array") + .iter() + .map(|item| item.as_str().expect("a string").to_string()) + .collect() +} + +#[test] +fn every_judgement_matches_python() { + let golden = records(&parity::read_golden_text("subjects/cases.ndjson")); + let cases = cases(); + assert_eq!(cases.len(), golden.len(), "case count moved — regenerate"); + + let mut wrong: Vec = Vec::new(); + for (case, theirs) in cases.iter().zip(&golden) { + let name = case["name"].as_str().expect("a name"); + let kind = case["kind"].as_str().expect("a kind"); + assert_eq!(theirs["name"].as_str(), Some(name), "cases out of order"); + + // The scheme travels with the case, because a scheme is exactly the + // thing that is not compiled in. + let scheme: Scheme = + serde_json::from_value(case["scheme"].clone()).expect("a readable scheme"); + let title = case["title"].as_str(); + + let mut note = |field: &str, ours: String, theirs: &Value| { + wrong.push(format!("{name}.{field}: python {theirs}, rust {ours}")); + }; + + match kind { + "canonical" => { + let ours = scheme.canonical(case["tag"].as_str().expect("a tag")); + if ours != theirs["subject"].as_str() { + note("subject", format!("{ours:?}"), &theirs["subject"]); + } + } + "parents" => { + let ours = scheme.with_parents(&strings(&case["subjects"])); + if ours != strings(&theirs["subjects"]) { + note("subjects", format!("{ours:?}"), &theirs["subjects"]); + } + } + "fiction" => { + let ours = scheme.without_impossible_fiction(&strings(&case["subjects"]), title); + if ours != strings(&theirs["subjects"]) { + note("subjects", format!("{ours:?}"), &theirs["subjects"]); + } + } + "compose" => { + let ours = scheme.compose(&strings(&case["subjects"]), title); + if ours != strings(&theirs["subjects"]) { + note("subjects", format!("{ours:?}"), &theirs["subjects"]); + } + } + "needs" => { + let ours = scheme.needs_subject(&strings(&case["existing"])); + if Some(ours) != theirs["needs"].as_bool() { + note("needs", ours.to_string(), &theirs["needs"]); + } + } + other => panic!("nothing answers a {other} case"), + } + } + + assert!( + wrong.is_empty(), + "{} judgements differ:\n{}", + wrong.len(), + wrong + .iter() + .take(20) + .map(|line| line.chars().take(200).collect::()) + .collect::>() + .join("\n") + ); +} + +/// Every question has to be asked, or the file proves less than it looks like +/// it does. +#[test] +fn every_kind_of_case_is_present() { + let cases = cases(); + for kind in ["canonical", "parents", "fiction", "compose", "needs"] { + assert!( + cases.iter().any(|case| case["kind"] == kind), + "no {kind} case" + ); + } +} + +/// Catches a golden file drifting away from a Python that has moved on. +#[test] +#[cfg(feature = "python")] +fn the_golden_file_still_matches_live_python() { + let cases = std::fs::read_to_string(parity::dir().join("cases/subjects/cases.ndjson")) + .expect("a readable case file"); + let live = parity::run_oracle("subjects.py", &cases).expect("the oracle runs"); + assert_eq!(live, parity::read_golden_text("subjects/cases.ndjson")); +}