diff --git a/crates/weksa/src/context.rs b/crates/weksa/src/context.rs index 3dec960..70d875a 100644 --- a/crates/weksa/src/context.rs +++ b/crates/weksa/src/context.rs @@ -64,7 +64,7 @@ Fields: - predications: a list of {id, concept, frame, roles, features}; roles is a list of {role, filler}, filler a node id. - relations: a list of {relation, from, to, features}. - stance: {features}; the speaker's felt affect and restraint, never the affect the line displays. -Node ids are n1, n2, and so on, one per referent and predication. Every other id comes from the inventory listed below and must be live at its revision. Write each id in full, exactly as the inventory lines spell it, family prefix included: concept:7, never 7. An entity comes only from the entities line. A referent's concept is never null, even when it carries an entity: give the nearest concept id. Participants and audience list the node ids of referents, never an entity id: an entity speaker is a referent whose entity field holds it, and participants lists that referent's node id. +Node ids are n1, n2, and so on, one per referent and predication. Every other id comes from the inventory listed below and must be live at its revision. Write each id in full, exactly as the inventory lines spell it, family prefix included: concept:7, never 7. An entity comes only from the entities line. A referent's concept is never null, even when it carries an entity: give the nearest concept id. Participants and audience list the node ids of referents, never an entity id: an entity speaker is a referent whose entity field holds it, and participants lists that referent's node id. A predication holds each role at most once, and a feature list each feature at most once: two or more fillers of one role are one referent of kind group that fills the role, and each member is related to it by a member_of relation from the member to the group. A feature is {feature, value}; value is {\"Grade\": 0 to 1000}, {\"Option\": label} or {\"Flag\": true or false}, as the inventory lists the feature's kind."; /// The instructions a lowering prompt starts with: the lowerer's whole brief. diff --git a/crates/weksa/src/encode.rs b/crates/weksa/src/encode.rs index d6b22db..1ddabd3 100644 --- a/crates/weksa/src/encode.rs +++ b/crates/weksa/src/encode.rs @@ -474,6 +474,39 @@ mod tests { assert_eq!(PacketDraft::of(&made), draft); } + #[test] + fn coordinated_fillers_admit_as_one_group_referent() { + use crate::store::InventoryStore; + use crate::validate::{Refusal, validate}; + // Item original-ja:19 as measure-ja-2 recorded it: two patients, one role twice. + const REFUSED: &str = r#"{"confidence":720,"context":{"setting":null,"medium":null,"participants":["n1"],"audience":["n1"]},"discourse":{"speech_act":"speech_act:3","addressee":null,"addressee_consent":null,"features":[]},"referents":[{"id":"n1","kind":"referent_kind:1","concept":"concept:28","entity":"entity:keeper","features":[]},{"id":"n2","kind":"referent_kind:1","concept":"concept:31","entity":null,"features":[]},{"id":"n3","kind":"referent_kind:5","concept":"concept:123","entity":null,"features":[]},{"id":"n4","kind":"referent_kind:5","concept":"concept:123","entity":null,"features":[]}],"predications":[{"id":"n5","concept":"concept:26","frame":"frame:4","roles":[{"role":"role:1","filler":"n1"},{"role":"role:2","filler":"n3"},{"role":"role:2","filler":"n4"}],"features":[{"feature":"feature:1","value":{"Option":"positive"}},{"feature":"feature:2","value":{"Option":"present"}},{"feature":"feature:3","value":{"Option":"habitual"}}]},{"id":"n6","concept":"concept:196","frame":"frame:4","roles":[{"role":"role:1","filler":"n1"},{"role":"role:2","filler":"n5"}],"features":[{"feature":"feature:1","value":{"Option":"positive"}},{"feature":"feature:2","value":{"Option":"future"}},{"feature":"feature:4","value":{"Option":"desired"}}]}],"relations":[{"relation":"relation:11","from":"n2","to":"n3","features":[]},{"relation":"relation:11","from":"n1","to":"n4","features":[]}],"stance":{"features":[{"feature":"feature:18","value":{"Grade":600}},{"feature":"feature:11","value":{"Grade":450}},{"feature":"feature:17","value":{"Grade":250}}]}}"#; + const PRESCRIBED: &str = r#"{"confidence":720,"context":{"setting":null,"medium":null,"participants":["n1"],"audience":["n1"]},"discourse":{"speech_act":"speech_act:3","addressee":null,"addressee_consent":null,"features":[]},"referents":[{"id":"n1","kind":"referent_kind:1","concept":"concept:28","entity":"entity:keeper","features":[]},{"id":"n2","kind":"referent_kind:1","concept":"concept:31","entity":null,"features":[]},{"id":"n3","kind":"referent_kind:5","concept":"concept:123","entity":null,"features":[]},{"id":"n4","kind":"referent_kind:5","concept":"concept:123","entity":null,"features":[]},{"id":"n7","kind":"referent_kind:2","concept":"concept:123","entity":null,"features":[]}],"predications":[{"id":"n5","concept":"concept:26","frame":"frame:4","roles":[{"role":"role:1","filler":"n1"},{"role":"role:2","filler":"n7"}],"features":[{"feature":"feature:1","value":{"Option":"positive"}},{"feature":"feature:2","value":{"Option":"present"}},{"feature":"feature:3","value":{"Option":"habitual"}}]},{"id":"n6","concept":"concept:196","frame":"frame:4","roles":[{"role":"role:1","filler":"n1"},{"role":"role:2","filler":"n5"}],"features":[{"feature":"feature:1","value":{"Option":"positive"}},{"feature":"feature:2","value":{"Option":"future"}},{"feature":"feature:4","value":{"Option":"desired"}}]}],"relations":[{"relation":"relation:11","from":"n2","to":"n3","features":[]},{"relation":"relation:11","from":"n1","to":"n4","features":[]},{"relation":"relation:3","from":"n3","to":"n7","features":[]},{"relation":"relation:3","from":"n4","to":"n7","features":[]}],"stance":{"features":[{"feature":"feature:18","value":{"Grade":600}},{"feature":"feature:11","value":{"Grade":450}},{"feature":"feature:17","value":{"Grade":250}}]}}"#; + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../data/original/inventory.cc"); + let inventory = InventoryStore::open(&path).unwrap().inventory().unwrap(); + let item = CorpusItemId::parse("item:original-ja:19").unwrap(); + let encoder = encoder_id("m", "d"); + let packet = |answer: &str| { + PacketDraft::parse(answer) + .unwrap() + .into_packet(&item, 6, &encoder) + }; + assert_eq!( + validate(&packet(REFUSED), &inventory), + Err(vec![Refusal::V11DuplicateRole { + role: "role:2".parse().unwrap(), + predication: "n5".parse().unwrap(), + }]) + ); + assert_eq!(validate(&packet(PRESCRIBED), &inventory), Ok(())); + // The prompt names the shape the packet above admits, by gloss. + let gloss = |id: &str| inventory.entry(&id.parse().unwrap()).unwrap().gloss.clone(); + assert_eq!(gloss("referent_kind:2"), "group"); + assert_eq!(gloss("relation:3"), "member_of"); + let prompt = crate::context::ENCODE_INSTRUCTIONS; + assert!(prompt.contains("member_of") && prompt.contains("group")); + } + #[test] fn lowering_draft_parse() { let draft = LoweringDraft::parse("\n {\"text\":\"a\"} \n").unwrap(); diff --git a/docs/interlingua-standard.md b/docs/interlingua-standard.md index 548862c..f8e7937 100644 --- a/docs/interlingua-standard.md +++ b/docs/interlingua-standard.md @@ -252,6 +252,10 @@ pair a role id with a filler node (the inventory seeds `agent`, `patient`, aspect, polarity, modality and evidentiality are features with declared option sets, not fields. +A predication holds each role once (V11). Coordinated fillers ("A and B") are +one `group` referent filling the role, each member related to it by `member_of`, +from the member to the group. + ### `relations` Edges not owned by one predication. `relation` is a relation id (the inventory