Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
6 changed files with 39 additions and 25 deletions
Showing only changes of commit 1afcbc1a83 - Show all commits
+2 -2
View File
@@ -69,8 +69,8 @@ replacement, and each removed path, column or flag.
- `geo.SE` and `geo.US`: five linked tables per country, `region`, `municipality`, - `geo.SE` and `geo.US`: five linked tables per country, `region`, `municipality`,
`locality`, `postal-code` and `street`, weighted by population and address counts `locality`, `postal-code` and `street`, weighted by population and address counts
and built from SCB, GeoNames, Trafikverket NVDB and the US Census Bureau, and an and built from SCB, GeoNames, Trafikverket NVDB and the US Census Bureau, and an
`address` record binding one street and reading its locality, postal code and region `address` record binding one locality and drawing its street and postal code inside
through it. `sv_SE.address` and `en_US.address` bind those records once, so it. `sv_SE.address` and `en_US.address` bind those records once, so
`en_US.address.street` no longer carries `name` and `suffix`, and a locale folder `en_US.address.street` no longer carries `name` and `suffix`, and a locale folder
loads only beside `geo`. loads only beside `geo`.
- `{date(from,to,'layout')}` and `{time('layout')}`: a second between two days, or - `{date(from,to,'layout')}` and `{time('layout')}`: a second between two days, or
+4 -5
View File
@@ -238,8 +238,7 @@ too, so a person's prefix never contradicts it. A `personnummer`'s birth number,
for a man, which no real person is ever given. for a man, which no real person is ever given.
A person of a chosen sex is assembled from the tables — `sex[f].first-name` beside A person of a chosen sex is assembled from the tables — `sex[f].first-name` beside
`last-name` — while a shipped `personnummer` draws its own sex, apart from any `person` `last-name`. Skatteverket's test series is also small: a personnummer
beside it. That test series is also small: a personnummer
is one of about 70,000 values, a day in 1930–2025 against the two birth numbers, so a is one of about 70,000 values, a day in 1930–2025 against the two birth numbers, so a
fixture past a few hundred rows repeats one and a `UNIQUE` column needs a category of fixture past a few hundred rows repeats one and a `UNIQUE` column needs a category of
your own. `sv_SE.date` and `en_US.date` are uniform over 1970-01-01 to 2029-12-31, your own. `sv_SE.date` and `en_US.date` are uniform over 1970-01-01 to 2029-12-31,
@@ -249,9 +248,9 @@ your own. `sv_SE.date` and `en_US.date` are uniform over 1970-01-01 to 2029-12-3
inside a sex. inside a sex.
A `geo` folder holds one tree per country under its alpha-2 code: five A `geo` folder holds one tree per country under its alpha-2 code: five
[linked tables](#linked-tables) named alike, and an `address` record binding one street and [linked tables](#linked-tables) named alike, and an `address` record binding one locality
reading its locality, postal code and region through it, which the locale's `address` and drawing its street and postal code inside it, which the locale's `address` binds once
binds once and prints whole. and prints whole.
| Table | `geo.SE` | `geo.US` | Weight | | Table | `geo.SE` | `geo.US` | Weight |
|-------|----------|----------|--------| |-------|----------|----------|--------|
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"format": "{street} {street-number}\n{postal-code} {locality}", "format": "{street} {street-number}\n{postal-code} {locality}",
"street": "{.street as s}{s.name}", "street": "{.locality as l}{l.street.name}",
"street-number": [ "street-number": [
"{int(1,9)}", "{int(1,9)}",
"{int(10,99)}", "{int(10,99)}",
@@ -9,6 +9,6 @@
{ "format": "{int(10,99)}{upper(1)}", "weight": 0.1 }, { "format": "{int(10,99)}{upper(1)}", "weight": 0.1 },
{ "format": "{int(100,999)}{upper(1)}", "weight": 0.05 } { "format": "{int(100,999)}{upper(1)}", "weight": 0.05 }
], ],
"postal-code": "{s..locality.postal-code.code}", "postal-code": "{l.postal-code.code}",
"locality": "{s..locality.name}" "locality": "{l.name}"
} }
+4 -4
View File
@@ -1,13 +1,13 @@
{ {
"format": "{street-number} {street}\n{locality}, {region} {postal-code}", "format": "{street-number} {street}\n{locality}, {region} {postal-code}",
"street": "{.street as s}{s.name}", "street": "{.locality as l}{l.street.name}",
"street-number": [ "street-number": [
"{int(10,99)}", "{int(10,99)}",
{ "format": "{int(100,999)}", "weight": 2 }, { "format": "{int(100,999)}", "weight": 2 },
{ "format": "{int(1000,9999)}", "weight": 0.5 }, { "format": "{int(1000,9999)}", "weight": 0.5 },
{ "format": "{int(10,99)}{int(100,999)}", "weight": 0.3 } { "format": "{int(10,99)}{int(100,999)}", "weight": 0.3 }
], ],
"locality": "{s..locality.name}", "locality": "{l.name}",
"region": "{s..locality..municipality..region.abbr}", "region": "{l..municipality..region.abbr}",
"postal-code": "{s..locality.postal-code.code}" "postal-code": "{l.postal-code.code}"
} }
+24 -9
View File
@@ -268,9 +268,10 @@ func TestShippedPersonNames(t *testing.T) {
seen := map[string]bool{} seen := map[string]bool{}
for i := 0; i < 2000; i++ { for i := 0; i < 2000; i++ {
seen[fake(t, f, locale+".sex[f].first-name")] = true seen[fake(t, f, locale+".sex[f].first-name")] = true
first, sex := fake(t, f, locale+".person.first"), fake(t, f, locale+".person.sex") got := fakeTemplate(t, f, `{/`+locale+`.person.first}|{/`+locale+`.person.sex}`)
if first == "" || sex == "" { first, sex, _ := strings.Cut(got, "|")
t.Fatalf("%s.person lacks a first name or a sex", locale) if v := fake(t, f, locale+".sex["+sex+"].first-name["+first+"]"); v != first {
t.Fatalf("%s: %q: %q is not a %s name", locale, got, first, sex)
} }
} }
if len(seen) < 300 || !seen["Anna"] && locale == "sv_SE" || !seen["Mary"] && locale == "en_US" { if len(seen) < 300 || !seen["Anna"] && locale == "sv_SE" || !seen["Mary"] && locale == "en_US" {
@@ -339,22 +340,36 @@ func TestShippedSwedishAddress(t *testing.T) {
// one address category are two addresses. // one address category are two addresses.
func TestShippedAddressIsOneNamedPick(t *testing.T) { func TestShippedAddressIsOneNamedPick(t *testing.T) {
f := newGenerator(t, "data", WithSeed(3)) f := newGenerator(t, "data", WithSeed(3))
for _, c := range []struct{ locale, country, template string }{ for _, c := range []struct{ locale, country, template, largest string }{
{"sv_SE", "SE", "{a.street}|{a.postal-code}|{a.locality}||{b.street}"}, {"sv_SE", "SE", "{a.street}|{a.postal-code}|{a.locality}||{b.street}", "Stockholm"},
{"en_US", "US", "{a.street}|{a.postal-code}|{a.locality}|{a.region}|{b.street}"}, {"en_US", "US", "{a.street}|{a.postal-code}|{a.locality}|{a.region}|{b.street}", "New York"},
} { } {
places := geoPlaces(t, c.country) places := geoPlaces(t, c.country)
apart := false apart, localities := false, map[string]int{}
for i := 0; i < 300; i++ { for i := 0; i < 2000; i++ {
got := fakeTemplate(t, f, `{/`+c.locale+`.address as a}{/`+c.locale+`.address as b}`+c.template) got := fakeTemplate(t, f, `{/`+c.locale+`.address as a}{/`+c.locale+`.address as b}`+c.template)
parts := strings.Split(got, "|") parts := strings.Split(got, "|")
if !places[strings.Join(parts[:4], "|")] { if !places[strings.Join(parts[:4], "|")] {
t.Fatalf("%s.address: %q is no street, postal code, locality and region of one place", c.locale, got) t.Fatalf("%s.address: %q is no street, postal code, locality and region of one place", c.locale, got)
} }
apart = apart || parts[0] != parts[4] apart = apart || parts[0] != parts[4]
localities[parts[2]]++
region := "{/" + c.locale + ".address.region}"
if c.country == "SE" {
region = ""
}
got = fakeTemplate(t, f, `{/`+c.locale+`.address.street}|{/`+c.locale+`.address.postal-code}|{/`+c.locale+`.address.locality}|`+region)
if !places[got] {
t.Fatalf("%s.address: field paths %q are no street, postal code, locality and region of one place", c.locale, got)
}
} }
if !apart { if !apart {
t.Errorf("%s: two names bound to address drew one street in 300 renders, want two addresses", c.locale) t.Errorf("%s: two names bound to address drew one street in 2000 renders, want two addresses", c.locale)
}
for l, n := range localities {
if n > localities[c.largest] {
t.Errorf("%s.address drew %s %d times and %s %d, want the locality weighted by population", c.locale, l, n, c.largest, localities[c.largest])
}
} }
} }
} }
+2 -2
View File
@@ -861,8 +861,8 @@ name is an error listing both keys; select by type instead.
A Luhn-valid personnummer over a random birth number may be a living person's; 238 and A Luhn-valid personnummer over a random birth number may be a living person's; 238 and
239 after any date are blocked from assignment, so the shipped `personnummer` and 239 after any date are blocked from assignment, so the shipped `personnummer` and
`samordningsnummer` use those. They sit in a `birth-number` table under `sex` rather `samordningsnummer` use those. They sit in a `birth-number` table under `sex` rather
than as a column of it: the render's shared draw of the family is what makes the number than as a column of it, so a pick of a sex can read the number, which `todo.md` item 28
and the name agree on sex, and `sex` stays one shape across locales instead of has `personnummer` do to agree with a person's sex, and `sex` stays one shape across locales instead of
collecting every sex-keyed id fact. A samordningsnummer's day, the birthday plus 60, is collecting every sex-keyed id fact. A samordningsnummer's day, the birthday plus 60, is
drawn from 61 to 88, valid in every month, rather than computed from the date drawn. drawn from 61 to 88, valid in every month, rather than computed from the date drawn.