//! `wai.delivery.opportunity` (extensions/opportunity-report.md): the committed corpus //! `live-conformance/opportunity/vectors.json` regenerates byte for byte from its seeds and is the //! only file there besides its README; every vector gets the outcome it states; every reason the //! extension adds and every code it reuses refuses a vector; §13 lists exactly the reasons the //! reference reports; and the worked example of §14: a channel's day of two owners, signed cues and //! program-as-run, a reported statement that under-reports one owner's breaks, and the check that //! shows it, computed and then recomputed from the public records alone. #![cfg(feature = "opportunity_report")] use std::collections::{BTreeMap, BTreeSet}; use std::path::PathBuf; use serde_json::{Value, json}; use wai::opportunity_report::{self as opp, Reason, corpus, scope}; fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } fn read(p: &str) -> String { std::fs::read_to_string(wai_dir().join(p)).unwrap_or_else(|e| panic!("{p}: {e}")) } fn committed() -> String { read("live-conformance/opportunity/vectors.json") } fn doc() -> Value { serde_json::from_str(&committed()).expect("the corpus is JSON") } #[test] fn the_committed_corpus_regenerates_from_its_seeds() { let generated = corpus::generate().expect("the corpus generates"); assert!(committed() == generated, "live-conformance/opportunity/vectors.json differs from `wai_opportunity_vectors gen`"); let mut files: Vec = std::fs::read_dir(wai_dir().join("live-conformance/opportunity")).unwrap().flatten().map(|e| e.file_name().to_string_lossy().into_owned()).collect(); files.sort(); assert_eq!(files, ["README.md", "vectors.json"]); } #[test] fn every_vector_gets_its_stated_outcome() { let fails = corpus::verify(&committed()); assert!(fails.is_empty(), "{}", fails.join("\n")); } /// Every reason this extension adds and every code it reuses refuses a vector, and the texts it /// delegates to refuse one each at their own places. #[test] fn the_corpus_covers_every_reason_and_case() { let d = doc(); let vectors = d["vectors"].as_array().unwrap(); let seen: BTreeSet<&str> = vectors.iter().filter_map(|v| v["outcome"]["reason"].as_str()).collect(); for r in Reason::ALL { assert!(seen.contains(r.code()), "nothing in the corpus is refused {r}"); } for r in Reason::REUSED { assert!(seen.contains(r), "nothing in the corpus is refused {r}"); } let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}"))["outcome"].clone(); assert_eq!(get("contract_offered")["reason"], "not_countersigned"); assert_eq!(get("run_aired_misstated")["at"], json!(["run", 0])); assert_eq!(get("room_event_signature")["at"].as_array().unwrap()[..3], [json!("room"), json!(0), json!("room")]); assert_eq!(get("check_report_refused")["at"], json!(["report", "report"])); assert_eq!(get("check_statement_refused")["at"], json!(["statement", "statement"])); for n in ["owner_a_day", "owner_b_day", "operator_day", "owner_a_runs_alone", "owner_b_two_rooms", "check_owner_b_under_reported", "check_owner_a_csv", "check_owner_b_without_statement", "check_owner_b_reports_a_programme"] { assert_eq!(get(n)["outcome"], "accept", "{n}"); } } /// §13's table names exactly the codes the reference adds, in its order. #[test] fn section_13_lists_exactly_the_reference_codes() { let text = read("extensions/opportunity-report.md"); let at = text.find("\n## 13. Reasons").expect("§13"); let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); let listed: Vec<&str> = text[at..end].lines().filter_map(|l| l.strip_prefix("| `")).filter_map(|l| l.split('`').next()).collect(); let codes: Vec<&str> = Reason::ALL.iter().map(|r| r.code()).collect(); assert_eq!(listed, codes); } fn line<'a>(o: &'a Value, scope_: &str, key: &str) -> &'a Value { o["lines"].as_array().unwrap().iter().find(|l| l["scope"] == scope_ && l["key"] == key).unwrap_or_else(|| panic!("no line {scope_} {key}")) } /// The worked example (§14). Owner B computes and signs its report of the day from the channel's /// two windows and the room's cues; its platform reports B's impressions per programme, and all /// but leaves out B's second documentary; B checks the report against it and against the /// operator's signed statement of the day. A party holding only the public records receives the /// check's document as text, verifies it, and recomputes the opportunities of every line from /// the attributed breaks and the contract alone. #[test] fn the_worked_example_shows_the_under_report_from_the_public_records() { let w = corpus::world().expect("the world"); let b = &w.reports["owner_b"]; // B's report, computed from the documents without it, is the corpus's, byte for byte. let (rep, report_doc) = opp::create(&w.pools.report_document(b, false), &corpus::key("owner-b"), corpus::pk("owner-b"), corpus::DAY16, corpus::DAY17).expect("B's report"); assert_eq!(rep.to_bytes(), b.report); // Four cued breaks in B's documentaries: 360 s, twelve slots (four, two, four, two). assert_eq!((rep.breaks, rep.break_ms, rep.slots), (4, 360_000, 12)); // The check of the platform's figures, beside the signed statement. let inputs = w.pools.check_document(b, "b_programmes", Some(&w.statements["owner_b"]), None); let (_, document) = opp::create_check(&inputs, &corpus::key("owner-b"), opp::FLOOR_PPM, opp::CEILING_PPM).expect("the check"); // A party holding only the public records: the document, as text. let text = serde_json::to_string(&document).unwrap(); let got: Value = serde_json::from_str(&text).unwrap(); let a = opp::verify_check(&got).expect("the check verifies"); let o = opp::check_json(&a); let period = line(&o, "period", ""); // Fourteen impressions reported against twenty-four the signed records prove, and revenue // below B's signed part. assert_eq!((period["impressions"].as_u64(), period["signed"]["impressions"].as_u64()), (Some(14), Some(24))); assert_eq!(period["fill_ppm"], 1_166_666); assert_eq!(period["flags"], json!(["impressions_below_signed", "revenue_below_signed"])); // The first documentary is in step with the day; the second, as many slots and two // impressions, falls far below it. let doc_1 = line(&o, "programme", "b/doc-1"); let doc_2 = line(&o, "programme", "b/doc-2"); assert_eq!((doc_1["slots"].as_u64(), doc_1["flags"].clone()), (Some(6), json!([]))); assert_eq!((doc_2["slots"].as_u64(), doc_2["fill_ppm"].as_u64(), doc_2["flags"].clone()), (Some(6), Some(333_333), json!(["below"]))); // Recomputed from the report's attributed breaks and the contract: each programme's breaks, // seconds and slots, over the breaks of the splits B shares in. let report = opp::verify(&report_doc).expect("the report verifies"); let r = opp::accepted_json(&report); let b_splits: BTreeSet = r["lines"].as_array().unwrap().iter().map(|l| l["split"].as_u64().unwrap()).collect(); let mut per: BTreeMap = BTreeMap::new(); for x in r["breaks"].as_array().unwrap() { if !b_splits.contains(&x["split"].as_u64().unwrap()) { continue; } let e = per.entry(x["asset"].as_str().unwrap_or("").to_string()).or_default(); e.0 += 1; e.1 += x["duration_ms"].as_u64().unwrap(); e.2 += x["slots"].as_u64().unwrap_or(0); } for p in ["b/doc-1", "b/doc-2"] { let l = line(&o, "programme", p); assert_eq!(per[p], (l["breaks"].as_u64().unwrap(), l["break_ms"].as_u64().unwrap(), l["slots"].as_u64().unwrap()), "{p}"); } // The signed figures, from the statement's own lines. let st = &a.statement.as_ref().expect("the statement").statement; assert_eq!(st.lines.iter().map(|l| l.impressions).sum::(), 24); assert_eq!(period["signed"]["revenue"].as_u64(), Some(st.total)); // The check's lines are the only figures that change with the reported statement: the same // report and statement beside owner A's fair CSV flag nothing. let d = doc(); let v = d["vectors"].as_array().unwrap().iter().find(|v| v["name"] == "check_owner_a_csv").expect("A's check vector"); let fair = opp::verify_check(&corpus::document(&d, v).expect("it reads")).expect("A's check"); assert!(fair.check.lines.iter().all(|l| l.flags == 0), "A's reported statement is what its records prove"); assert_eq!(fair.check.lines[0].scope, scope::PERIOD); } /// Owner A's day holds every kind of break §4 makes: a break inside the ad slot after A's first /// episode counts as cued and the slot is not counted again; the slot no cue covers is scheduled /// and unslotted; slots come from a section or from the policy. #[test] fn owner_a_holds_every_kind_of_break() { let w = corpus::world().expect("the world"); let a = opp::verify(&w.pools.report_document(&w.reports["owner_a"], true)).expect("A's report"); let mine: Vec<&opp::Break> = a.counted().collect(); let cued: Vec<&&opp::Break> = mine.iter().filter(|b| b.class == opp::class::CUED).collect(); let scheduled: Vec<&&opp::Break> = mine.iter().filter(|b| b.class == opp::class::SCHEDULED).collect(); assert_eq!(cued.len(), 4); assert_eq!(scheduled.len(), 1); assert_eq!((scheduled[0].slots, scheduled[0].asset.as_deref()), (None, Some("a/ep-2")), "the uncued ad slot, by the rule preceding"); let sources: Vec> = cued.iter().map(|b| b.slot_source).collect(); assert_eq!(sources, [Some("policy"), Some("section"), Some("section"), Some("section")]); assert_eq!(cued[2].asset.as_deref(), Some("a/ep-1"), "the break inside the ad slot, by the rule preceding"); let runs_alone = opp::verify(&w.pools.report_document(&w.reports["owner_a_runs_alone"], true)).expect("from the runs alone"); assert_eq!(runs_alone.counted().filter(|b| b.class == opp::class::SCHEDULED).count(), 2, "with no room, both ad slots are scheduled"); }