Skip to content

Repair a claim that does not derive

Use this when a rule should derive a row and does not, and you want to know which fact is missing. gnd why-not names the first step of the rule that left no bindings, and the values that reached it. You add that fact and ask again, until the row derives and gnd witness proves it.

Files on GitHub

The claim

An answer says Ada Byrne said quotation q-17. The model certifies a quotation as said by a person when three facts hold: the quotation appears in a work, the person wrote that work, and an editor reviewed it.

certified(q, p) <- quote(q, w), authored(w, p), reviewed(w, r)

The population records where each quotation appears, and nothing else. Copy the example:

python -c "import gnd, pathlib, shutil; shutil.copytree(pathlib.Path(gnd.__file__).parent / 'examples/quoted_claims', 'quoted_claims', dirs_exist_ok=True)"
gnd validate quoted_claims/model.orm.json
OK: quoted_claims/model.orm.json is a valid GND ORM model

gnd check passes: the population covers the claim's fact type, asserts none of its rows, and none derive. That is consistent, and it does not tell you why the claim is missing.

gnd check quoted_claims/model.orm.json quoted_claims/gap.pop.json --format json \
  | python -c "import json, sys; r = json.load(sys.stdin); print(r['exit_code'], [d['verdict'] for d in r['derivations']])"
0 ['pass']

Ask why the claim is not derived

gnd why-not quoted_claims/model.orm.json quoted_claims/gap.pop.json \
  --fact-type certified --row quoted_claims/claim.json | python -m json.tool
{
    "clauses": [
        {
            "blocker": {
                "asserted_not_read": false,
                "derivation": "base",
                "fact_type": "authored",
                "ground": true,
                "index": 1,
                "kind": "atom",
                "near_miss_count": 0,
                "near_miss_truncated": false,
                "near_misses": "redacted",
                "roles": {
                    "person": {
                        "bound": true,
                        "variable": "$p"
                    },
                    "work": {
                        "bound": true,
                        "variable": "$w"
                    }
                },
                "sample_count": 1,
                "samples": "redacted"
            },
            "clause_index": 0,
            "descent": null,
            "descent_stopped": "not-derived",
            "steps": [
                {
                    "count": 1,
                    "fact_type": "quote",
                    "index": 0,
                    "kind": "atom"
                },
                {
                    "count": 0,
                    "fact_type": "authored",
                    "index": 1,
                    "kind": "atom"
                }
            ],
            "steps_after_blocker": "not-evaluated"
        }
    ],
    "exit_code": 0,
    "fact_type": "certified",
    "mode": "row",
    "order": "declared",
    "reason": null,
    "schema_version": "gnd-why-not/2",
    "status": "blocked",
    "unavailable": null,
    "view": "redacted",
    "work": {
        "binding_cells": 3,
        "joins": 1,
        "pre_joins": 2,
        "probes": 4
    }
}

The rule's first step found one binding, and the second, authored, found none. Both of its roles are bound, so the missing fact is fully determined. The default view hides the values. Ask for them with --raw:

gnd why-not quoted_claims/model.orm.json quoted_claims/gap.pop.json \
  --fact-type certified --row quoted_claims/claim.json --raw \
  | python -c "import json, sys; b = json.load(sys.stdin)['clauses'][0]['blocker']; print(b['fact_type'], b['samples'])"
authored [{'person': 'Ada Byrne', 'work': 'Field Notes'}]

Add the first fact and ask again

The missing fact is authored(Field Notes, Ada Byrne). Add it and ask again:

python -c "
import json
p = json.load(open('quoted_claims/gap.pop.json'))
p['facts']['authored'].append({'work': 'Field Notes', 'person': 'Ada Byrne'})
json.dump(p, open('quoted_claims/once.pop.json', 'w'))
"
gnd why-not quoted_claims/model.orm.json quoted_claims/once.pop.json \
  --fact-type certified --row quoted_claims/claim.json --raw \
  | python -c "import json, sys; b = json.load(sys.stdin)['clauses'][0]['blocker']; print(b['fact_type'], b['roles'], b['samples'])"
reviewed {'reviewer': {'bound': False, 'variable': '$r'}, 'work': {'bound': True, 'variable': '$w'}} [{'work': 'Field Notes'}]

Now the rule stops one step later, at reviewed. Its work role is bound, but no binding reaches reviewer: the payload cannot say who reviewed the work. That value has to come from a source, here extracted.json.

Add the second fact and prove the claim

python -c "
import json
p = json.load(open('quoted_claims/once.pop.json'))
reviewer = json.load(open('quoted_claims/extracted.json'))['reviewed']['reviewer']
p['facts']['reviewed'].append({'work': 'Field Notes', 'reviewer': reviewer})
json.dump(p, open('quoted_claims/twice.pop.json', 'w'))
"
gnd why-not quoted_claims/model.orm.json quoted_claims/twice.pop.json \
  --fact-type certified --row quoted_claims/claim.json \
  | python -c "import json, sys; print(json.load(sys.stdin)['status'])"
gnd witness quoted_claims/model.orm.json quoted_claims/twice.pop.json \
  --targets quoted_claims/claim.json \
  | python -c "import json, sys; print(json.load(sys.stdin)['results'][0]['status'])"
derived
proved

Keep the population consistent

The row now derives, but the population still covers that fact type and asserts no rows, so gnd check would report the derived extension incomplete. Assert the derived row, and the check passes again:

python -c "
import json
p = json.load(open('quoted_claims/twice.pop.json'))
p['facts']['certified'] = [{'quote': 'q-17', 'person': 'Ada Byrne'}]
json.dump(p, open('quoted_claims/repaired.pop.json', 'w'))
"
gnd check quoted_claims/model.orm.json quoted_claims/repaired.pop.json --format json \
  | python -c "import json, sys; r = json.load(sys.stdin); print(r['exit_code'], [d['verdict'] for d in r['derivations']])"
0 ['pass']

When the values are spelled differently

Sometimes the fact is there, spelled another way. In spelling-gap.pop.json someone recorded the work as FIELD NOTES. To GND that is a different value from Field Notes, so the claim is blocked at authored exactly as before. The answer's near-misses show the difference:

gnd why-not quoted_claims/model.orm.json quoted_claims/spelling-gap.pop.json \
  --fact-type certified --row quoted_claims/claim.json --raw \
  | python -c "import json, sys; b = json.load(sys.stdin)['clauses'][0]['blocker']; print(b['fact_type'], b['near_miss_count']); print(b['near_misses'])"
authored 1
[{'candidate': {'person': 'Ada Byrne', 'work': 'FIELD NOTES'}, 'roles': ['work'], 'sample': {'person': 'Ada Byrne', 'work': 'Field Notes'}}]

The binding for Field Notes met a row for FIELD NOTES by the same person. roles names the role that differs. The population also has Field notes by Bo Lin, which is not listed: its other bound role, person, differs too. Near-misses compare strings after case folding and removing punctuation and extra spaces; the why-not reference defines the rule.

Why-not does not decide that the two spellings name one work. If they do, correct the row to the spelling the rule expects, and ask again:

python -c "
import json
p = json.load(open('quoted_claims/spelling-gap.pop.json'))
for row in p['facts']['authored']:
    if row == {'work': 'FIELD NOTES', 'person': 'Ada Byrne'}:
        row['work'] = 'Field Notes'
json.dump(p, open('quoted_claims/respelled.pop.json', 'w'))
"
gnd why-not quoted_claims/model.orm.json quoted_claims/respelled.pop.json \
  --fact-type certified --row quoted_claims/claim.json --raw \
  | python -c "import json, sys; b = json.load(sys.stdin)['clauses'][0]['blocker']; print(b['fact_type'], b['near_miss_count'])"
reviewed 0

The claim now stops at reviewed, with no near-misses: that fact is missing, and you add it as in the previous section.

What the answer does not tell you

  • Why-not says which fact is missing, not whether it is true. Add a fact only when a source supports it.
  • A step with an unbound role needs a value from somewhere else, as reviewer did here.
  • A near-miss is a candidate, not a match. Correct a spelling only when you know both name the same thing.
  • Each answer covers the rule as written, in declared order. The why-not reference defines the steps, the exit codes and the limits.