Value level metadata

  • define_spec() gains three tables: value_levels (one row per value of a variable), where_clauses (one row per compared value) and external_codelists (one row per dictionary codelist). They sit between comments and study in the argument list, so calls that passed study or standard positionally must be updated.
  • write_define() emits def:ValueListDef, def:ValueListRef, def:WhereClauseDef, def:WhereClauseRef and RangeCheck/CheckValue, and read_define() reads them back. The write/read round trip of the shipped example is lossless, and a document carrying value level metadata validates against the real CDISC Define-XML 2.1 schema.
  • check_define() checks every value against the rows its where clause selects, so a Length or codelist declared for VSSTRESN where VSTESTCD = "SYSBP" is compared with the systolic rows only. Its result gains a value column, NA for a variable-level finding.
  • New findings: codelist_external, where_clause_not_evaluable, value_absent.
  • A def:WhereClauseDef whose RangeCheck names a variable that will have no ItemDef is refused by define_spec(), before a tag is written.

External codelist references

  • CodeList holding one ExternalCodeList with Dictionary, Version, href and ref - MedDRA, WHODrug, a CDISC CT release, ISO 3166 - written and read.
  • Values behind an external codelist cannot be compared with the dictionary, which is not shipped; check_define() says so (codelist_external) rather than passing them silently.

Reading real documents

  • Which dataset and variable a value list or where clause belongs to is now taken from the reference graph (ItemGroupDef -> ItemRef -> ItemDef -> def:ValueListRef) rather than from the text of the OIDs. Documents that use a different OID scheme, such as def:ValueListDef OID="VL.AETERM" with no dataset component, now read correctly.
  • An empty CheckValue survives: DSSCAT NE "" means “is not blank” and is a real condition.
  • Fixed: a document whose codelists are all ExternalCodeList produced a column-less table and failed to read.
  • Fixed: SASFieldName on a value level ItemDef was the value key, which can be longer than 8 characters or contain a dot and is then schema-invalid. It is now the parent variable, which is the SAS column the value lives in.
  • Fixed: def:Class was emitted with no Name when a dataset had no class. Name is required, so the element is now left out instead.
  • define_spec() now rejects duplicate variables$order within a dataset (ItemRef/@OrderNumber must be unique within an ItemGroupDef, UC-IGD-2) and any missing value in a column that maps to an attribute the schema requires.
  • check_define() refuses a data list with a missing name instead of failing with “missing value where TRUE/FALSE needed”.

Still out of scope

  • Analysis results metadata (ARM). Unchanged judgement: it is a separate model with its own namespace and conformance rules, and half of it is worse than none. Neither written nor read.
  • Annotated-CRF and supplemental document leaves (def:AnnotatedCRF, def:SupplementalDoc, def:DocumentRef, def:PDFPageRef), Alias, SignificantDigits, ItemDef-level RangeCheck, more than one def:Standard, and more than one FormalExpression per MethodDef. See README “Scope and limits” for the full list and what happens to each on a round trip.
  • Initial development version.
  • define_spec() builds and validates a specification from five plain data frames (datasets, variables, codelists, methods, comments), aggregating every problem into a single error.
  • write_define() emits Define-XML 2.1 with xml2; the output for the shipped example validates against the CDISC define2-1-0.xsd schema.
  • read_define() parses Define-XML 2.1 back into a specification; the write/read round trip is lossless and asserted in the test suite.
  • check_define() compares a specification against real datasets and returns a tibble of discrepancies: missing or undocumented datasets and variables, type disagreements, lengths shorter than the longest observed value, and values absent from a declared codelist.