NagaYu/signpost-label-quality
Signpost label quality Labels from accessibility trees, each one classed as good or as one of seven ways a label can fail to mean anything. It is built for the question that is left over after axe-core and Xcode's Accessibility Inspector have both passed: there is a name, but does the name identify this control? Repository: NagaYu/signpost-label-quality Code, evaluation, and the builder for this dataset https://github.com/NagaYu/signpost Model trained on it… See the full description on the dataset page: https://huggingface.co/datasets/NagaYu/signpost-label-quality.
Signpost label quality
Labels from accessibility trees, each one classed as good or as one of seven ways a label can fail to mean anything. It is built for the question that is left over after axe-core and Xcode's Accessibility Inspector have both passed: there is a name, but does the name identify this control?
Repository: NagaYu/signpost-label-quality
Configs
labels is the default config.
Data statement -- read this before quoting a number from it
The corpus is synthetic. Every screen in this build was composed by scripts/make_seed_corpus.py from a per-language vocabulary written for the project. No site was crawled and no app was decompiled, which is why the whole pipeline runs offline on a laptop and why there is no redistribution question to answer. Every row carries synthetic: true.
What that means for the numbers: a model evaluated on this data is being evaluated against a generator, and its score is partly a measurement of how learnable that generator is. Real screens are messier -- more abbreviations, more house style, more labels that are bad in ways nobody thought to plant. Take detection and accuracy figures computed here as an upper bound on a real codebase, not as an estimate of one. signpost/collect.py is the path for real pages, and it keeps the same redistribution discipline: label, role, minimal context, never a page body.
How the defects were planted. The clean corpus goes through signpost/inject.py, which corrupts a fixed share of labels (35% here) and records, for each one, the position, the class, the original string, and the strategy that produced it. Seven classes are planted: generic, role_echo, identifier, duplicate, verbose, missing and mismatch. A corrupted label is admitted to the hard set only if the corrupted string still passes every syntactic criterion -- specific, role-free, not an identifier, short enough -- so "syntactically valid but contextually wrong" is enforced by re-running the checks rather than by assertion. mismatch is generated five ways; one of them, confusable_action, draws from the same action lexicon a rule-based checker could use, and it is flagged as lexicon_derivable so nobody has to take a hard-set score at face value.
What the splits guarantee. The split is by origin -- site or app -- not by screen and not by row. Naming conventions belong to a codebase, so a per-screen split would let a model score well on a second page of a shop it had already seen. Train and test share no origin: 40 origins on the train side, 20 on the test side, checked by collect.assert_no_origin_leak before this file was written. The two sides are corrupted with different injection seeds, so a test defect is not a training defect relocated. The split is stratified by language, so no language lands entirely on one side.
What is not in the denominator. Before injection, every label passes the strict quality gate in signpost/quality.py; 0 of 2592 nodes (0.0%) were dropped as not demonstrably good. A node that was never corrupted but does not pass that gate is dropped rather than published as ok. This matters because the ok rows are the denominator of any over-flagging rate computed from this dataset, and a doubtful label counted as a good one would make every such rate -- ours included -- look better than it is.
What is not published. The label, the role, and clipped context. Page bodies, DOM, screenshots and full visible text stay local. The caps are enforced by collect.assert_no_bulk_text, which raises rather than truncates, and this build fails rather than shipping an oversized row.
Classes
Languages
Mismatch strategies
Fields
Limitations
- Synthetic throughout. See the data statement.
- The per-row
licenseandattributionfields are real fields carrying synthetic values: the generator assigns each origin a licence so that the provenance machinery is exercised end to end. For a corpus collected withsignpost/collect.pythey carry what the source actually said. - The class balance is a choice, not an observation. Most real screens have far fewer defects than 35% of labels.
duplicateis only definable within a screen, so a row taken out of its screen cannot be re-judged for that class.- Nothing here says what any person will experience. The classes point at documented guidelines (WCAG 2.2, Apple's and Google's own guidance) and the citations live in
signpost/cite.py. Signpost is not a replacement foraxe-core, for Xcode's Accessibility Inspector, or for review by people who use assistive technology; it narrows what a human has to look at.
Reproducing this build
python scripts/make_seed_corpus.py
python scripts/build_dataset.py --seed 0 --test-frac 0.30 --inject-rate 0.35Both are deterministic: same seed, same bytes.
Licence
The data is released under mit. Per-row licence, origin and attribution travel with each row.
