@ai-matrx/records 0.76.10 → 0.76.11
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.md +6 -0
- package/dist/templates/index.cjs +116 -12
- package/dist/templates/index.cjs.map +1 -1
- package/dist/templates/index.d.cts +15 -2
- package/dist/templates/index.d.ts +15 -2
- package/dist/templates/index.js +116 -12
- package/dist/templates/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/records
|
|
2
2
|
|
|
3
|
+
## 0.76.11
|
|
4
|
+
|
|
5
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
6
|
+
`git diff npm/records/v0.76.10..npm/records/v0.76.11 -- apps/shared/records`).
|
|
7
|
+
No source changes intended and no consumer action required.
|
|
8
|
+
|
|
3
9
|
## 0.76.10
|
|
4
10
|
|
|
5
11
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
package/dist/templates/index.cjs
CHANGED
|
@@ -104,6 +104,7 @@ __export(templates_exports, {
|
|
|
104
104
|
isSelfDeclaredField: () => isSelfDeclaredField,
|
|
105
105
|
isTimedField: () => isTimedField,
|
|
106
106
|
lastResortTrim: () => lastResortTrim,
|
|
107
|
+
meaningWords: () => meaningWords,
|
|
107
108
|
membersAddRowsSteps: () => membersAddRowsSteps,
|
|
108
109
|
mergeDescribeParts: () => mergeDescribeParts,
|
|
109
110
|
normalizeForPhones: () => normalizeForPhones,
|
|
@@ -133,6 +134,7 @@ __export(templates_exports, {
|
|
|
133
134
|
rowsFor: () => rowsFor,
|
|
134
135
|
ruleBreak: () => ruleBreak,
|
|
135
136
|
runTemplateDoor: () => runTemplateDoor,
|
|
137
|
+
sameMeaning: () => sameMeaning,
|
|
136
138
|
selfDeclaredFieldSpec: () => selfDeclaredFieldSpec,
|
|
137
139
|
sensitivityFloorOf: () => sensitivityFloorOf,
|
|
138
140
|
shellsPayload: () => shellsPayload,
|
|
@@ -4528,8 +4530,8 @@ function validateTemplate(raw, options = {}) {
|
|
|
4528
4530
|
for (const k of rule.filled ?? []) if (!filled(val(k))) say(at, `"${row.key}" is ${st}, so "${fieldOf(table, k)?.label ?? k}" is filled; it is empty.`);
|
|
4529
4531
|
for (const [k, v] of Object.entries(rule.equals ?? {})) {
|
|
4530
4532
|
const got = val(k) ?? null;
|
|
4531
|
-
const
|
|
4532
|
-
if (!
|
|
4533
|
+
const same2 = sameJson(got, v) || typeof v === "number" && parseNumeric(storeText(got) ?? "") !== null && qCmp(parseNumeric(storeText(got) ?? ""), parseNumeric(String(v))) === 0;
|
|
4534
|
+
if (!same2) say(at, `"${row.key}" is ${st}, so "${fieldOf(table, k)?.label ?? k}" is ${JSON.stringify(v)}; it is ${JSON.stringify(got)}.`);
|
|
4533
4535
|
}
|
|
4534
4536
|
}
|
|
4535
4537
|
};
|
|
@@ -5866,7 +5868,7 @@ ${e.body}`.matchAll(MERGE_TOKEN)].map((m) => m[1]));
|
|
|
5866
5868
|
}
|
|
5867
5869
|
}
|
|
5868
5870
|
const rows = t.rows;
|
|
5869
|
-
const
|
|
5871
|
+
const same2 = (x, y) => {
|
|
5870
5872
|
const a = storeText(x);
|
|
5871
5873
|
const b = storeText(y);
|
|
5872
5874
|
if (a === null || b === null) return false;
|
|
@@ -5884,7 +5886,7 @@ ${e.body}`.matchAll(MERGE_TOKEN)].map((m) => m[1]));
|
|
|
5884
5886
|
};
|
|
5885
5887
|
for (const sf of t.fields.filter((ff) => !isComputed(ff) && !isRelation(ff) && ff.key !== l.key)) {
|
|
5886
5888
|
const have = rows.filter((r) => filled(r.values?.[sf.key]));
|
|
5887
|
-
if (have.length >= 3 && have.every((r) =>
|
|
5889
|
+
if (have.length >= 3 && have.every((r) => same2(r.values?.[sf.key], far(r)))) {
|
|
5888
5890
|
say(`tables (${t.token}).fields (${sf.key})`, `"${sf.label}" repeats "${l.label}" on every row; drop it and use the lookup, which follows the linked record.`);
|
|
5889
5891
|
}
|
|
5890
5892
|
}
|
|
@@ -6569,6 +6571,33 @@ function describeField(f, table) {
|
|
|
6569
6571
|
};
|
|
6570
6572
|
}
|
|
6571
6573
|
|
|
6574
|
+
// src/templates/describe/meaning.ts
|
|
6575
|
+
var FILLER = /* @__PURE__ */ new Set(["of", "the", "a", "an", "for", "to", "in", "on", "and", "or", "your", "their", "is", "are", "was", "when", "what", "which", "with", "my", "our"]);
|
|
6576
|
+
function stemWord(w) {
|
|
6577
|
+
let s = w.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
6578
|
+
if (s.length > 4 && s.endsWith("ies")) s = s.slice(0, -3) + "y";
|
|
6579
|
+
else if (s.length > 4 && s.endsWith("es") && /(ch|sh|x|ss|s)es$/.test(s)) s = s.slice(0, -2);
|
|
6580
|
+
else if (s.length > 3 && s.endsWith("s") && !s.endsWith("ss")) s = s.slice(0, -1);
|
|
6581
|
+
if (s.length > 6 && s.endsWith("ation")) s = s.slice(0, -5);
|
|
6582
|
+
else if (s.length > 6 && s.endsWith("ion")) s = s.slice(0, -3);
|
|
6583
|
+
if (s.length > 4 && (s.endsWith("y") || s.endsWith("e"))) s = s.slice(0, -1);
|
|
6584
|
+
return s;
|
|
6585
|
+
}
|
|
6586
|
+
function meaningWords(text) {
|
|
6587
|
+
const words = text.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w.toLowerCase()).filter((w) => !FILLER.has(w)).map(stemWord).filter(Boolean);
|
|
6588
|
+
return [...new Set(words)].sort();
|
|
6589
|
+
}
|
|
6590
|
+
var same = (a, b) => a.length > 0 && a.length === b.length && a.every((w, i) => w === b[i]);
|
|
6591
|
+
function sameMeaning(a, b) {
|
|
6592
|
+
if (a.parityType !== b.parityType) return false;
|
|
6593
|
+
const ka = meaningWords(a.key);
|
|
6594
|
+
const kb = meaningWords(b.key);
|
|
6595
|
+
if (same(ka, kb)) return true;
|
|
6596
|
+
const la = a.label ? meaningWords(a.label) : [];
|
|
6597
|
+
const lb = b.label ? meaningWords(b.label) : [];
|
|
6598
|
+
return same(la, lb);
|
|
6599
|
+
}
|
|
6600
|
+
|
|
6572
6601
|
// src/templates/describe/merge.ts
|
|
6573
6602
|
var clone = (v) => v === void 0 ? v : JSON.parse(JSON.stringify(v));
|
|
6574
6603
|
var labelOfKey = (key) => {
|
|
@@ -6601,7 +6630,7 @@ function ensureSkeleton(t, p) {
|
|
|
6601
6630
|
t.fields.push({ key: "status", label: "Status", parityType: "select", required: true, choices: [...p.status.choices], default: p.status.default });
|
|
6602
6631
|
}
|
|
6603
6632
|
if (p.slot && !field(t, "slot")) t.fields.push({ key: "slot", label: "Visit time", parityType: "datetime", format: "datetime", required: true });
|
|
6604
|
-
for (const l of p.links) {
|
|
6633
|
+
for (const l of p.links ?? []) {
|
|
6605
6634
|
if (field(t, l.key)) continue;
|
|
6606
6635
|
t.fields.push({ key: l.key, label: l.label, parityType: "relation", relationTarget: l.target, ...l.many ? { multi: true } : { relationMax: 1 } });
|
|
6607
6636
|
}
|
|
@@ -6610,7 +6639,7 @@ function addExistingKeys(t, p, existing) {
|
|
|
6610
6639
|
if (!p.reuse) return;
|
|
6611
6640
|
const e = existing.find((x) => x.id === p.reuse.existing_table_id);
|
|
6612
6641
|
if (!e) return;
|
|
6613
|
-
for (const key of p.reuse.keepKeys) {
|
|
6642
|
+
for (const key of p.reuse.keepKeys ?? []) {
|
|
6614
6643
|
if (field(t, key)) continue;
|
|
6615
6644
|
const ef = e.fields.find((f) => f.key === key);
|
|
6616
6645
|
if (!ef) continue;
|
|
@@ -6618,7 +6647,9 @@ function addExistingKeys(t, p, existing) {
|
|
|
6618
6647
|
}
|
|
6619
6648
|
}
|
|
6620
6649
|
function addRequest(t, r) {
|
|
6621
|
-
if (field(t, r.key)) return;
|
|
6650
|
+
if (field(t, r.key)) return void 0;
|
|
6651
|
+
const twin = t.fields.find((f2) => sameMeaning(f2, r));
|
|
6652
|
+
if (twin) return twin.key;
|
|
6622
6653
|
const f = { key: r.key, label: r.label, parityType: r.parityType };
|
|
6623
6654
|
if (r.choices?.length) f.choices = [...r.choices];
|
|
6624
6655
|
if (r.askOfSubject) f.askOfSubject = true;
|
|
@@ -6626,6 +6657,7 @@ function addRequest(t, r) {
|
|
|
6626
6657
|
if (r.required) f.required = true;
|
|
6627
6658
|
if (r.help) f.help = r.help;
|
|
6628
6659
|
t.fields.push(f);
|
|
6660
|
+
return void 0;
|
|
6629
6661
|
}
|
|
6630
6662
|
var CHOICE = ["select", "multi_select", "list"];
|
|
6631
6663
|
var STAGE_KEY = /(^|_)(status|stage)$/;
|
|
@@ -6687,7 +6719,7 @@ function sharedBlocksFor(tables) {
|
|
|
6687
6719
|
function reusesOf(plan) {
|
|
6688
6720
|
return plan.tables.filter((t) => t.reuse).map((t) => ({ token: t.token, existing_table_id: t.reuse.existing_table_id }));
|
|
6689
6721
|
}
|
|
6690
|
-
function mergeDescribeParts(plan, tables, doors, views, existingTables = []) {
|
|
6722
|
+
function mergeDescribeParts(plan, tables, doors, views, existingTables = [], notes = []) {
|
|
6691
6723
|
const out = clone(Array.isArray(tables?.tables) ? tables.tables : []);
|
|
6692
6724
|
const byToken = new Map(out.map((t) => [t.token, t]));
|
|
6693
6725
|
for (const p of plan.tables) {
|
|
@@ -6700,10 +6732,25 @@ function mergeDescribeParts(plan, tables, doors, views, existingTables = []) {
|
|
|
6700
6732
|
ensureSkeleton(t, p);
|
|
6701
6733
|
addExistingKeys(t, p, existingTables);
|
|
6702
6734
|
}
|
|
6735
|
+
const repoint = /* @__PURE__ */ new Map();
|
|
6703
6736
|
for (const r of doors?.fieldRequests ?? []) {
|
|
6704
6737
|
const t = byToken.get(r.table);
|
|
6705
|
-
if (t)
|
|
6706
|
-
|
|
6738
|
+
if (!t) continue;
|
|
6739
|
+
const twin = addRequest(t, r);
|
|
6740
|
+
if (!twin) continue;
|
|
6741
|
+
if (!repoint.has(r.table)) repoint.set(r.table, /* @__PURE__ */ new Map());
|
|
6742
|
+
repoint.get(r.table).set(r.key, twin);
|
|
6743
|
+
notes.push(`tables.${r.table}.fields.${r.key}: dropped, it means the same as "${twin}" (questions now ask "${twin}")`);
|
|
6744
|
+
}
|
|
6745
|
+
const forms = clone(doors?.forms ?? []);
|
|
6746
|
+
const bookingOut = doors?.booking ? clone(doors.booking) : void 0;
|
|
6747
|
+
const repointQuestions = (table, qs) => {
|
|
6748
|
+
const m = repoint.get(table);
|
|
6749
|
+
if (!m || !Array.isArray(qs)) return;
|
|
6750
|
+
for (const q2 of qs) if (m.has(q2.fieldKey)) q2.fieldKey = m.get(q2.fieldKey);
|
|
6751
|
+
};
|
|
6752
|
+
for (const f of forms) repointQuestions(f.table, f.fields);
|
|
6753
|
+
if (bookingOut) repointQuestions(bookingOut.table, bookingOut.questions);
|
|
6707
6754
|
const spec = {
|
|
6708
6755
|
specVersion: 1,
|
|
6709
6756
|
version: 1,
|
|
@@ -6720,9 +6767,9 @@ function mergeDescribeParts(plan, tables, doors, views, existingTables = []) {
|
|
|
6720
6767
|
sharedBlocks: sharedBlocksFor(out),
|
|
6721
6768
|
relationships: clone(tables?.relationships ?? []),
|
|
6722
6769
|
views: mergeViews(plan, views, byToken),
|
|
6723
|
-
forms
|
|
6770
|
+
forms,
|
|
6724
6771
|
dimensions: [],
|
|
6725
|
-
extras:
|
|
6772
|
+
extras: bookingOut ? [bookingOut] : []
|
|
6726
6773
|
};
|
|
6727
6774
|
return spec;
|
|
6728
6775
|
}
|
|
@@ -6738,6 +6785,62 @@ var GOOD_ORDER = ["green", "teal", "blue", "violet", "slate", "amber", "orange",
|
|
|
6738
6785
|
var BAD_ORDER = ["red", "orange", "gray", "slate", "brown", "pink", "amber", "yellow", "violet", "blue", "teal"];
|
|
6739
6786
|
var DISTINCT_UP_TO = 7;
|
|
6740
6787
|
var lower = (s) => s.trim().toLowerCase();
|
|
6788
|
+
function linkedTables(spec, token) {
|
|
6789
|
+
const out = [];
|
|
6790
|
+
const add = (x) => {
|
|
6791
|
+
const t = spec.tables.find((y) => y.token === x);
|
|
6792
|
+
if (t && t.token !== token && !out.includes(t)) out.push(t);
|
|
6793
|
+
};
|
|
6794
|
+
const own = spec.tables.find((t) => t.token === token);
|
|
6795
|
+
for (const f of own?.fields ?? []) if (f.parityType === "relation" && f.relationTarget) add(f.relationTarget);
|
|
6796
|
+
for (const t of spec.tables) if (t.fields.some((f) => f.parityType === "relation" && f.relationTarget === token)) add(t.token);
|
|
6797
|
+
for (const r of spec.relationships ?? []) {
|
|
6798
|
+
if (r.fromTable === token) add(r.toTable);
|
|
6799
|
+
if (r.toTable === token) add(r.fromTable);
|
|
6800
|
+
}
|
|
6801
|
+
return out;
|
|
6802
|
+
}
|
|
6803
|
+
function fixMissingQuestionFields(spec, fixes) {
|
|
6804
|
+
const doors = [];
|
|
6805
|
+
for (const form of spec.forms) doors.push({ where: `forms.${form.token}`, table: form.table, questions: form.fields ?? [], set: (q2) => form.fields = q2 });
|
|
6806
|
+
for (const e of spec.extras ?? []) {
|
|
6807
|
+
if (e.kind !== "booking") continue;
|
|
6808
|
+
const b = e;
|
|
6809
|
+
doors.push({ where: `extras.${b.token}`, table: b.table, questions: b.questions ?? [], set: (q2) => b.questions = q2 });
|
|
6810
|
+
}
|
|
6811
|
+
for (const d of doors) {
|
|
6812
|
+
const t = spec.tables.find((x) => x.token === d.table);
|
|
6813
|
+
if (!t) continue;
|
|
6814
|
+
const kept = [];
|
|
6815
|
+
for (const q2 of d.questions) {
|
|
6816
|
+
if (t.fields.some((f) => f.key === q2.fieldKey)) {
|
|
6817
|
+
kept.push(q2);
|
|
6818
|
+
continue;
|
|
6819
|
+
}
|
|
6820
|
+
const source = linkedTables(spec, t.token).find((o) => o.fields.some((f) => f.key === q2.fieldKey));
|
|
6821
|
+
if (source) {
|
|
6822
|
+
const copy = clone2(source.fields.find((f) => f.key === q2.fieldKey));
|
|
6823
|
+
delete copy.required;
|
|
6824
|
+
delete copy.unique;
|
|
6825
|
+
const isDate2 = copy.parityType === "datetime";
|
|
6826
|
+
if (isDate2 && !copy.absoluteDates && /birth|dob|born/i.test(`${copy.key} ${copy.label}`)) {
|
|
6827
|
+
copy.absoluteDates = true;
|
|
6828
|
+
copy.historicalReason = copy.historicalReason?.trim() || "date of birth";
|
|
6829
|
+
}
|
|
6830
|
+
const hasSchedule = t.fields.some((f) => f.parityType === "datetime" && !f.absoluteDates);
|
|
6831
|
+
t.fields.push(copy);
|
|
6832
|
+
if (isDate2 && !copy.absoluteDates && hasSchedule && !t.before?.length && !t.datesIndependent?.trim()) {
|
|
6833
|
+
t.datesIndependent = `"${copy.label}" is the linked ${source.token}'s own date; it does not depend on the table's other dates.`;
|
|
6834
|
+
}
|
|
6835
|
+
fixes.push(`tables.${t.token}.fields.${q2.fieldKey}: copied from linked table "${source.token}" (${d.where} asks it)`);
|
|
6836
|
+
kept.push(q2);
|
|
6837
|
+
} else if (!q2.required) {
|
|
6838
|
+
fixes.push(`${d.where}.fields.${q2.fieldKey}: question dropped ("${t.token}" declares no such field and no linked table has it)`);
|
|
6839
|
+
} else kept.push(q2);
|
|
6840
|
+
}
|
|
6841
|
+
if (kept.length !== d.questions.length) d.set(kept);
|
|
6842
|
+
}
|
|
6843
|
+
}
|
|
6741
6844
|
function raiseSensitivity(spec, fixes) {
|
|
6742
6845
|
for (const t of spec.tables) {
|
|
6743
6846
|
for (const f of t.fields) {
|
|
@@ -6992,6 +7095,7 @@ function switchJob(spec, fixes) {
|
|
|
6992
7095
|
function autofixDescribeSpec(input, ctx = {}) {
|
|
6993
7096
|
const spec = describeSpec(clone2(input));
|
|
6994
7097
|
const autoFixes = [];
|
|
7098
|
+
fixMissingQuestionFields(spec, autoFixes);
|
|
6995
7099
|
raiseSensitivity(spec, autoFixes);
|
|
6996
7100
|
flagAskedOfSubject(spec, ctx, autoFixes);
|
|
6997
7101
|
fixBooking(spec, autoFixes);
|