A third pass at the same design, after your own pushback — confirm before any build runs.
Frank's answer to UN (2026-09-18, UN-e) named seven concerns with the v2 design: it breaks the enrichment process; lite enrichment is an expensive low-value step; other languages cannot reuse the cluster learning structure; learners will want lite converted to full, so the work is done twice; the database is so complicated that changes become hard; lite content is hard to translate to other L1 languages; data redundancy. Read together they reject two parts of v2: the lite card (with its enrichment_level state, its umbrella container and its “relations-lite” pass) and the four per-language side tables. v3 keeps the one part they do not reject, the linking table, and drops everything else.
| Concern | v3 |
|---|---|
| 1. Breaks the enrichment process | No new state and no new pass. The only pipeline change is that S4 writes link rows instead of arrays, after a parity check, and the judge runs over four kinds instead of two. |
| 2. Lite enrichment is expensive and low value | There is no lite enrichment. A target is enriched fully, once, or not at all. |
| 3. Other languages cannot reuse the structure | The link table is keyed by target language today. Kinds become a lookup table. Nothing in the shape is Indonesian-specific. |
| 4. Lite to full means double work | No lite, so no second pass over the same target. |
| 5. The database is too complicated to change | One existing table gains seven columns and a lookup table. Four inline arrays retire. No side tables. |
| 6. Lite content is hard to translate to other L1s | The only new text is the meaning and note per mention, stored the way every other text in the estate is stored today. |
| 7. Redundancy | Content lives once, on the full card. The link row repeats a short text string, not content. The arrays, which today repeat both, retire. |
erDiagram
CT1_LEMMA ||--o{ CT2_SENSE : "has meanings"
CT2_SENSE ||--o{ CT3_FORM : "has forms (unchanged, one parent)"
CT2_SENSE ||--o{ RELATION_LINK : "mentions (t073, extended)"
RELATION_LINK }o--o| CT2_TARGET : "a full card in the catalog, or nothing yet"
RELATION_LINK }o--|| RELATION_KIND : "kind, from a lookup table"
RELATION_LINK {
bigint id PK "existing"
text l2_language "existing; ind today; lets another target language reuse the table"
uuid source_card_id FK "existing; the owning CT2 sense"
text relation_field "existing; similar_expressions | opposite_expressions | informal_variants | related_compounds"
text target_text_norm "existing; the item's text as authored, normalized"
uuid target_card_id FK "existing; nullable; the full CT2 card when one exists"
text decision_code "existing; NA_NO_SENSE_MATCH | NA_NOT_IN_ESTATE when there is no card"
int ordinal "NEW; order within its section"
text register "NEW; formal | informal | slang | neutral, of the pairing, authored"
bool is_idiom "NEW"
text tag_raw "NEW; jenis as authored"
jsonb gloss "NEW; the meaning next to this sense, {eng, ind} (LB-a)"
jsonb note "NEW; the context note, {eng, ind} (LB-a)"
jsonb provenance "NEW; opaque, stored whole"
}
CT2_TARGET {
uuid card_id PK "an existing full sense or phrase card; nothing else"
}
RELATION_KIND {
text kind PK "the four kinds today"
jsonb section_label "per learner language"
int display_order
}
1. CT1 is a lemma — the root word a card's meanings belong to.
2. CT2 is the learnable card — a specific meaning a learner can actually study.
3. CT3 is a form of one specific sense — unchanged by this design.
4. A link row is not a card — it just points at a full card, when one exists, or at nothing yet.
select relation_field, count(*) as rows, count(*) filter (where target_card_id is not null) as linked, count(*) filter (where target_card_id is null) as absent, count(distinct target_text_norm) as distinct_texts, count(distinct source_card_id) as distinct_source_senses from t073_relation_sense_links group by relation_field order by relation_field;
| Kind | Rows | Linked | Absent | Distinct texts | Distinct source senses |
|---|---|---|---|---|---|
| similar_expressions | 30,649 | 10,493 | 20,156 | 18,377 | 9,395 |
| opposite_expressions | 8,954 | 3,526 | 5,428 | 4,368 | 4,023 |
| informal_variants | 0 | 0 | 0 | 0 | 0 |
| related_compounds | 0 | 0 | 0 | 0 | 0 |
select relation_field, decision_code, count(*) from t073_relation_sense_links where target_card_id is null group by relation_field, decision_code order by relation_field, decision_code;
| Kind | Reason | Count |
|---|---|---|
| similar_expressions | NA_NO_SENSE_MATCH | 271 |
| similar_expressions | NA_NOT_IN_ESTATE | 19,885 |
| opposite_expressions | NA_NO_SENSE_MATCH | 579 |
| opposite_expressions | NA_NOT_IN_ESTATE | 4,849 |
select count(distinct target_text_norm) as total_distinct_texts, count(distinct source_card_id) as total_distinct_senses, count(*) as total_rows from t073_relation_sense_links;
select sum(case when jsonb_typeof(similar_expressions)='array' then jsonb_array_length(similar_expressions) else 0 end) as similar_n, sum(case when jsonb_typeof(opposite_expressions)='array' then jsonb_array_length(opposite_expressions) else 0 end) as opposite_n, sum(case when jsonb_typeof(informal_variants)='array' then jsonb_array_length(informal_variants) else 0 end) as informal_n, sum(case when jsonb_typeof(related_compounds)='array' then jsonb_array_length(related_compounds) else 0 end) as related_n from v_senses where l2_language='ind' and card_tier=2 and card_archetype='vocabulary';
| Kind | Text | Target | Register | Is idiom | Eng meaning | Ind paraphrase |
|---|---|---|---|---|---|---|
| informal | mam | None yet — not yet judged | to be authored | to be authored | baby talk for 'eat' used with small children at home | to be authored |
| informal | mamam | None yet — not yet judged | to be authored | to be authored | cute child language for 'eat', common when coaxing toddlers | to be authored |
| informal | mkn | None yet — not yet judged | to be authored | to be authored | texting shorthand for 'makan' in chats | to be authored |
| opposite | berpuasa | None yet — not in the estate | to be authored | No | to fast (abstain from eating) | to be authored |
| opposite | menahan lapar | None yet — not in the estate | to be authored | No | to hold back hunger (not eat) | to be authored |
| related compound | kasih makan | None yet — not yet judged | to be authored | to be authored | to give food to a person or animal | memberi makan pada orang atau hewan |
| related compound | kereta makan | None yet — not yet judged | to be authored | to be authored | dining car | gerbong kereta api yang dipakai sebagai tempat makan; restorasi |
| related compound | makan malam | None yet — not yet judged | to be authored | to be authored | to have dinner | makan pada malam hari |
| related compound | makan pagi | None yet — not yet judged | to be authored | to be authored | to have breakfast | makan pada waktu pagi; menyarap |
| related compound | makan siang | None yet — not yet judged | to be authored | to be authored | to have lunch | makan pada waktu siang hari |
| related compound | makan sirih | None yet — not yet judged | to be authored | to be authored | the habit of chewing betel leaf | kebiasaan mengunyah daun sirih |
| related compound | mencari makan | None yet — not yet judged | to be authored | to be authored | to earn a living | mencari nafkah |
| related compound | nafsu makan | None yet — not yet judged | to be authored | to be authored | the feeling of wanting to eat | rasa ingin makan |
| related compound | rumah makan | None yet — not yet judged | to be authored | to be authored | restaurant | kedai tempat makan (menjual makanan) |
| related compound | sudah makan? | None yet — not yet judged | to be authored | to be authored | a friendly greeting asking if you have eaten | sapaan akrab untuk tanya sudah makan |
| similar | bersantap | None yet — not in the estate | to be authored | No | to dine | to be authored |
| similar | melahap | None yet — not in the estate | to be authored | No | to devour | to be authored |
| similar | menikmati makanan | None yet — not in the estate | to be authored | No | to enjoy a meal | to be authored |
| similar | menyantap | None yet — not in the estate | to be authored | No | to partake (eat formally) | to be authored |
| Kind | Text | Target | Register | Is idiom | Eng meaning | Ind paraphrase |
|---|---|---|---|---|---|---|
| informal | gak makan | None yet — not yet judged | to be authored | to be authored | colloquial for 'rem tidak makan' in chat and speech | to be authored |
| opposite | macet | Existing full card | to be authored | No | to jam, to get stuck | to be authored |
| opposite | rem blong | None yet — not in the estate | to be authored | No | brakes fail | to be authored |
| opposite | tidak berfungsi | None yet — not in the estate | to be authored | No | to not function | to be authored |
| similar | bekerja sebagaimana mestinya | None yet — not in the estate | to be authored | No | to work as it should | to be authored |
| similar | berfungsi | None yet — not in the estate | to be authored | No | to function | to be authored |
| similar | berjalan | None yet — no matching sense | to be authored | No | to run properly | to be authored |
| similar | bisa | None yet — no matching sense | to be authored | No | to work, can function | to be authored |
| Text | Target |
|---|---|
| bekerja sebagaimana mestinya | None yet — not in the estate |
| berfungsi | None yet — not in the estate |
| berjalan | None yet — no matching sense |
| berpuasa | None yet — not in the estate |
| bersantap | None yet — not in the estate |
| bisa | None yet — no matching sense |
| gak makan | None yet — not yet judged |
| kasih makan | None yet — not yet judged |
| kereta makan | None yet — not yet judged |
| macet | Existing full card |
| makan malam | None yet — not yet judged |
| makan pagi | None yet — not yet judged |
| makan siang | None yet — not yet judged |
| makan sirih | None yet — not yet judged |
| mam | None yet — not yet judged |
| mamam | None yet — not yet judged |
| melahap | None yet — not in the estate |
| menahan lapar | None yet — not in the estate |
| mencari makan | None yet — not yet judged |
| menikmati makanan | None yet — not in the estate |
| menyantap | None yet — not in the estate |
| mkn | None yet — not yet judged |
| nafsu makan | None yet — not yet judged |
| rem blong | None yet — not in the estate |
| rumah makan | None yet — not yet judged |
| sudah makan? | None yet — not yet judged |
| tidak berfungsi | None yet — not in the estate |
Link-row table: 23 of 81 cells exist today — the rest are “to be authored”. (The distinct-target table has no missing cells to count: under v3 a target is either an existing full card or nothing yet — there is no partial/lite state to measure.)
An item with a linked full card: kind label, the text, the meaning next to this sense, phonetics, sound, examples, Learn, and the plus (add to deck) — the same as any item renders today.
An item with no card yet: kind label, the text, the meaning next to this sense. The plus queues full enrichment through the existing path; the chip shows pending until the card lands.