strom-research 1.18.0-beta.12 → 1.18.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.cs.md CHANGED
@@ -7,7 +7,7 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
7
7
 
8
8
  ## 1.18.0
9
9
 
10
- *Připravuje se: bety do 1.18.0-beta.12. Oddíl bude úplný s vydáním.*
10
+ *Připravuje se: bety do 1.18.0-beta.13. Oddíl bude úplný s vydáním.*
11
11
 
12
12
  - **Fotky z telefonu nastojato.** Fotka otočená podle údaje EXIF se čte tak, jak se má dívat, nastojato; místa na ní
13
13
  vyznačená (výřezy, poloviny dvoustrany, uložená část) jsou místa obrázku, jak se ukazuje. Fotka sama zůstává, jak
@@ -21,7 +21,8 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
21
21
  které jmenuje, i s tím, kdo ze stromu to může být. Nic se nedomýšlí: jméno, které v dokumentu není, vypadne — i
22
22
  příjmení, nikdy se nebere od otce ani z jiné rodiny —, co nejde přečíst, je označené; druhý čtenář může číst naslepo.
23
23
  Dokument na úředním formuláři, jako rodný list, si své osoby ponechá — dítě i oba rodiče s jejich podílem —, a dokument
24
- přečtený dřív je po aktualizaci ukáže, aniž by se četl znovu.
24
+ přečtený dřív je po aktualizaci ukáže, aniž by se četl znovu. U každé osoby a události je i místo na stránce, kde
25
+ stojí, takže aplikace Strom ukáže výřez každé z nich.
25
26
  Do stromu se z toho nic nezapíše samo. Dokument už přečtený to o sobě řekne, takže se omylem nečte podruhé. Dokument
26
27
  čtený znovu se ukazuje pod názvem svého souboru, nikdy pod kódem. Dokument poslaný z aplikace Strom najde agenta
27
28
  stejně jako terminál — i nainstalovaného do vlastní složky, přes npm nebo Homebrew — místo hlášení, že agent není
package/CHANGELOG.de.md CHANGED
@@ -7,7 +7,7 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
7
7
 
8
8
  ## 1.18.0
9
9
 
10
- *In Vorbereitung: die Betas bis 1.18.0-beta.12. Der Abschnitt ist mit dem Release vollständig.*
10
+ *In Vorbereitung: die Betas bis 1.18.0-beta.13. Der Abschnitt ist mit dem Release vollständig.*
11
11
 
12
12
  - **Handyfotos aufrecht.** Ein Foto, das nach seiner EXIF-Ausrichtung gedreht ist, wird gelesen, wie es gesehen
13
13
  werden soll, aufrecht; die darauf markierten Stellen (Ausschnitte, die Hälften einer Doppelseite, ein gespeicherter
@@ -23,6 +23,8 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
23
23
  vom Vater oder aus einer anderen Familie genommen —, Unleserliches wird markiert; ein zweiter Leser kann blind lesen.
24
24
  Ein Dokument auf einem amtlichen Formular, etwa eine Geburtsurkunde, behält seine Personen — das Kind und beide
25
25
  Eltern mit ihrem Anteil —, und ein früher gelesenes Dokument zeigt sie nach dem Update, ohne erneut gelesen zu werden.
26
+ Jede Person und jedes Ereignis kommt mit der Stelle auf der Seite, an der es steht, so zeigt die Strom-App von jedem
27
+ einen eigenen Ausschnitt.
26
28
  In den Stammbaum gelangt nichts von selbst. Ein schon gelesenes Dokument sagt es, so wird es nicht versehentlich ein
27
29
  zweites Mal gelesen. Ein erneut gelesenes Dokument wird unter dem Namen seiner Datei gezeigt, nie unter einem Code.
28
30
  Ein aus der Strom-App gesendetes Dokument findet den Agenten wie das Terminal — auch einen in einem eigenen Ordner,
package/CHANGELOG.md CHANGED
@@ -7,7 +7,7 @@ The changes of each version in short. The full notes and the install lines are o
7
7
 
8
8
  ## 1.18.0
9
9
 
10
- *In preparation: the betas up to 1.18.0-beta.12. The section is complete with the release.*
10
+ *In preparation: the betas up to 1.18.0-beta.13. The section is complete with the release.*
11
11
 
12
12
  - **Phone photos upright.** A photo turned by its EXIF orientation is read as it is meant to be seen, upright; the
13
13
  places marked on it (excerpts, the halves of a spread, a cut-out part) are of the picture as it is shown. The
@@ -22,7 +22,8 @@ The changes of each version in short. The full notes and the install lines are o
22
22
  name the document does not have is left out — a surname too, never taken from a father or another family —, what
23
23
  cannot be read is marked; a second reader can read it blind. A
24
24
  document on a printed form, as a birth certificate, keeps its people — the child and both parents with their part in
25
- it —, and a document read before shows them after the update with nothing read again. Nothing of it enters the tree by itself. A document read
25
+ it —, and a document read before shows them after the update with nothing read again. Each person and event it names
26
+ comes with the place on the page where it stands, so the Strom app shows each its own excerpt. Nothing of it enters the tree by itself. A document read
26
27
  already says so, so it is not read a second time by mistake. A document read again is shown under the name of its
27
28
  file, never a code. A document sent from the Strom app finds the agent as the terminal does — one installed in a
28
29
  folder of its own, by npm or Homebrew too — instead of saying no agent is set up. Every agent the research works with
@@ -110,13 +110,15 @@ term: <a word or abbreviation as written> = <what it means, in ${into}> (term |
110
110
  — one line each, for the old words, formulas, abbreviations, occupations, and on a gravestone its symbols
111
111
  `}person: <ref> | <role> | ${blind ? "" : `<the role in ${into}> | `}written=<the name exactly as it stands in the transcript> | given=<the given
112
112
  name in today's form> | surname=<the surname in today's form> | sex=M|F|U | age=<as written> | occupation=<…> |
113
- residence=<…> — one line each person named; ref p1, p2, … in this entry; role: ${ROLES.join(", ")};
113
+ residence=<…> | region=x,y,w,h — one line each person named; ref p1, p2, … in this entry; role: ${ROLES.join(", ")};
114
114
  given and surname only turn the written name into today's form ("Joannes" → "Jan"): never a name the transcript
115
115
  does not have — no surname the entry does not write by the person (a child's is not taken from its father's);
116
116
  on a form (a certificate) whose fields hold a name apart, written= the words of its fields as written there;
117
- a name you could not read stays out; leave out what the entry does not say
117
+ a name you could not read stays out; leave out what the entry does not say; region: where the person's name
118
+ stands — its line, or its field on a form — as the entry's region, of the page's whole view (tight, not the entry)
118
119
  event: <BIRT | BAPM | MARR | DEAT | BURI | RESI | EVEN> | date=<as on the record, in numbers: 3.4.1846, or a year> |
119
- place=<the place as written> | persons=<ref>:<principal|father|mother|godparent|witness|groom|bride|deceased|other>,…
120
+ place=<the place as written> | persons=<ref>:<principal|father|mother|godparent|witness|groom|bride|deceased|other>,… |
121
+ region=x,y,w,h — where its date (and place) stand, as a person's region
120
122
  relation: parent <ref of the parent> <ref of the child> — or — spouse <ref> <ref>
121
123
  ${blind
122
124
  ? ""
@@ -260,12 +262,18 @@ export function parseDocReport(text) {
260
262
  const ref = /^p\d+$/iu.test(parts[0].trim()) ? parts.shift().trim().toLowerCase() : `p${entry.persons.length + 1}`;
261
263
  const role = parts[0] && !parts[0].includes("=") ? parts.shift().trim().toLowerCase() : "other";
262
264
  const roleText = parts[0] && !parts[0].includes("=") ? parts.shift().trim() : undefined;
263
- entry.persons.push({ ref, role, ...(roleText ? { roleText } : {}), fields: pairs(parts) });
265
+ const fields = pairs(parts);
266
+ const region = parseRegion(fields.region);
267
+ delete fields.region;
268
+ entry.persons.push({ ref, role, ...(roleText ? { roleText } : {}), ...(region ? { region } : {}), fields });
264
269
  }
265
270
  for (const e of fields.get("event") ?? []) {
266
271
  const parts = e.split(/\s*\|\s*/u);
267
272
  const type = parts[0] && !parts[0].includes("=") ? parts.shift().trim() : "EVEN";
268
- entry.events.push({ type, fields: pairs(parts) });
273
+ const fields = pairs(parts);
274
+ const region = parseRegion(fields.region);
275
+ delete fields.region;
276
+ entry.events.push({ type, ...(region ? { region } : {}), fields });
269
277
  }
270
278
  for (const r of fields.get("relation") ?? []) {
271
279
  const w = r.split(/[\s,]+/u).filter(Boolean);
@@ -574,6 +582,9 @@ export function checkEntry(raw, id, blind = false, form = false) {
574
582
  }
575
583
  if (uncertain.size)
576
584
  person.uncertain = [...uncertain];
585
+ const region = partOf(p.region, raw.region);
586
+ if (region)
587
+ person.region = region;
577
588
  persons.push(person);
578
589
  }
579
590
  const refs = new Set(persons.map((p) => p.ref));
@@ -603,6 +614,9 @@ export function checkEntry(raw, id, blind = false, form = false) {
603
614
  }
604
615
  if (uncertain.size)
605
616
  ev.uncertain = [...uncertain];
617
+ const region = partOf(e.region, raw.region);
618
+ if (region)
619
+ ev.region = region;
606
620
  events.push(ev);
607
621
  }
608
622
  const relations = [];
@@ -623,6 +637,24 @@ export function checkEntry(raw, id, blind = false, form = false) {
623
637
  entry.glossary = raw.terms;
624
638
  return { entry, checks };
625
639
  }
640
+ /** A name's or a date's line is no more than a quarter of the page. */
641
+ const PART_AREA = 0.25;
642
+ /** How far a part may lie out of its entry's region (a reader's regions are rough). */
643
+ const PART_SLACK = 0.05;
644
+ /**
645
+ * A person's or an event's region as a reader gave it, where it can be one: a part of the page no bigger than a
646
+ * quarter of it, that lies (roughly) in its entry's region; else none — the app shows the entry's region then.
647
+ */
648
+ function partOf(r, entry) {
649
+ if (!r || r[2] * r[3] > PART_AREA)
650
+ return undefined;
651
+ if (!entry)
652
+ return r;
653
+ const [x, y, w, h] = [entry[0] - PART_SLACK, entry[1] - PART_SLACK, entry[2] + 2 * PART_SLACK, entry[3] + 2 * PART_SLACK];
654
+ const across = Math.min(r[0] + r[2], x + w) - Math.max(r[0], x);
655
+ const down = Math.min(r[1] + r[3], y + h) - Math.max(r[1], y);
656
+ return across > 0 && down > 0 ? r : undefined;
657
+ }
626
658
  // ── two readers ───────────────────────────────────────────────────────────────────────────────────────────────────
627
659
  /** The hunks where two transcripts differ, word by word (their keys; the marks [?] are no difference). */
628
660
  export function wordDiff(a, b) {
@@ -720,6 +752,7 @@ export function compareReadings(first, blind) {
720
752
  used.set(e.page, k + 1);
721
753
  const b = other.get(e.page)?.[k];
722
754
  const variants = [];
755
+ const regions = new Map();
723
756
  if (!b)
724
757
  variants.push({ field: "entry", at: e.id, other: "" });
725
758
  else {
@@ -737,6 +770,9 @@ export function compareReadings(first, blind) {
737
770
  for (const f of ["given", "surname"])
738
771
  if (!same(p[f], q[f]))
739
772
  variants.push({ field: `${p.ref}.${f}`, at: p[f] ?? "", other: q[f] ?? "" });
773
+ // where the name stands: the first reader's, else the blind one's of the same person
774
+ if (!p.region && q.region)
775
+ regions.set(p, q.region);
740
776
  };
741
777
  const alone = [];
742
778
  for (const p of e.persons) {
@@ -775,6 +811,8 @@ export function compareReadings(first, blind) {
775
811
  variants.push({ field: ev.type, at: ev.date?.text ?? ev.type, other: "" });
776
812
  continue;
777
813
  }
814
+ if (!ev.region && q.region)
815
+ regions.set(ev, q.region);
778
816
  if ((ev.date?.gedcom ?? ev.date?.text) !== (q.date?.gedcom ?? q.date?.text) && !same(ev.date?.text, q.date?.text))
779
817
  variants.push({ field: `${ev.type}.date`, at: ev.date?.text ?? "", other: q.date?.text ?? "" });
780
818
  if (!same(ev.place?.text, q.place?.text))
@@ -784,7 +822,8 @@ export function compareReadings(first, blind) {
784
822
  const agree = variants.length ? "differ" : "same";
785
823
  if (variants.length)
786
824
  all = false;
787
- return { ...e, agree, ...(variants.length ? { variants } : {}) };
825
+ const placed = (x) => (regions.has(x) ? { ...x, region: regions.get(x) } : x);
826
+ return { ...e, persons: e.persons.map(placed), events: e.events.map(placed), agree, ...(variants.length ? { variants } : {}) };
788
827
  });
789
828
  // entries only the blind reader has
790
829
  for (const [page, list] of other)
@@ -1045,8 +1084,14 @@ export function buildResult(tree, s) {
1045
1084
  checks.push(...c.checks);
1046
1085
  // a region of a part asked (--region): of the whole page
1047
1086
  const page = s.pages.find((p) => p.n === e.page);
1048
- if (c.entry.region && page?.region)
1049
- c.entry.region = within(page.region, c.entry.region);
1087
+ if (page?.region) {
1088
+ const part = page.region;
1089
+ if (c.entry.region)
1090
+ c.entry.region = within(part, c.entry.region);
1091
+ for (const x of [...c.entry.persons, ...c.entry.events])
1092
+ if (x.region)
1093
+ x.region = within(part, x.region);
1094
+ }
1050
1095
  out.push(c.entry);
1051
1096
  }
1052
1097
  });
package/dist/core/tree.js CHANGED
@@ -27,7 +27,7 @@ import { lastBackup } from "./backup.js";
27
27
  export const TREE_FILE = "strom.json";
28
28
  // between releases the candidate being made (a bridge run from the sources says what it is: the Strom app goes by it);
29
29
  // at a release the release's own (package.json)
30
- export const VERSION = "1.18.0-beta.12";
30
+ export const VERSION = "1.18.0-beta.13";
31
31
  const GITIGNORE = `# Strom Research — local state and large files are not versioned
32
32
  .strom/
33
33
  /media/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strom-research",
3
- "version": "1.18.0-beta.12",
3
+ "version": "1.18.0-beta.13",
4
4
  "description": "Genealogical research toolkit for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {