From 0c332e0ceeafcf8466d65dcefacfe8eb3a998857 Mon Sep 17 00:00:00 2001 From: justinsilvestre Date: Mon, 6 Nov 2023 17:38:22 +0100 Subject: [PATCH] Setup basic component relations in KanjisenseFigure --- app/lib/dic/componentsDictionary.yml | 3 + app/lib/dic/kanjijumpSpecificVariants.ts | 3 +- prisma/kanjisense/componentMeanings.ts | 3 +- prisma/kanjisense/getFigureMeaningsText.ts | 70 ++++- .../seedKanjisenseFigureRelation.ts | 41 +-- prisma/kanjisense/seedKanjisenseFigures.ts | 296 +++++++++++++----- .../kanjisense/seedKanjisenseVariantGroups.ts | 27 +- prisma/schema.prisma | 111 +++---- 8 files changed, 374 insertions(+), 180 deletions(-) diff --git a/app/lib/dic/componentsDictionary.yml b/app/lib/dic/componentsDictionary.yml index 247eeee18..d17ca2ed1 100644 --- a/app/lib/dic/componentsDictionary.yml +++ b/app/lib/dic/componentsDictionary.yml @@ -3503,6 +3503,9 @@ GWS-U5BFD-G: 軗: mnemonic: beat standin: 撃 +𭃌: + mnemonic: engrave + reference: 契 ####### 丱: historical: tufts of hair diff --git a/app/lib/dic/kanjijumpSpecificVariants.ts b/app/lib/dic/kanjijumpSpecificVariants.ts index bb616a095..9ee1f8071 100644 --- a/app/lib/dic/kanjijumpSpecificVariants.ts +++ b/app/lib/dic/kanjijumpSpecificVariants.ts @@ -207,5 +207,6 @@ export const kanjijumpSpecificVariants = [ ["徹", "GWS-U3054E-JV"], ["GWS-U7230-G", "爰"], ["歹", "歺", "𣦵"], - ["GWS-U300EE", "𦥯"] + ["GWS-U300EE", "𦥯"], + ["臘","﨟","臈"], ] diff --git a/prisma/kanjisense/componentMeanings.ts b/prisma/kanjisense/componentMeanings.ts index c30e3346e..5afd92972 100644 --- a/prisma/kanjisense/componentMeanings.ts +++ b/prisma/kanjisense/componentMeanings.ts @@ -28,6 +28,7 @@ export async function shouldComponentBeAssignedMeaning( variantGroupId, }: { id: string; + /* includes self */ directUses: string[]; variantGroupId: string | null; }, @@ -48,7 +49,7 @@ export async function shouldComponentBeAssignedMeaning( directUses, ); const isAtomic = false; - const minimumUsesInPriorityCandidates = isAtomic ? 1 : 2; + const minimumUsesInPriorityCandidates = isAtomic ? 2 : 3; return Boolean( usesInPriorityCandidates.size >= minimumUsesInPriorityCandidates, diff --git a/prisma/kanjisense/getFigureMeaningsText.ts b/prisma/kanjisense/getFigureMeaningsText.ts index fad06ffad..9a35d5c5b 100644 --- a/prisma/kanjisense/getFigureMeaningsText.ts +++ b/prisma/kanjisense/getFigureMeaningsText.ts @@ -1,25 +1,73 @@ -import { PrismaClient } from "@prisma/client"; +import { KanjisenseFigureRelation, PrismaClient } from "@prisma/client"; const RADICAL_ENTRY_REGEX = /radical \(no\.|radical number/; export async function getFigureMeaningsText( prisma: PrismaClient, - figureId: string, + figure: KanjisenseFigureRelation, + componentsDictionaryEntry: ComponentMeaning | null, ) { - const unihanDefinition = prisma.unihan15.findUnique({ + const figureId = figure.id; + const unihanDefinitionLookup = prisma.unihan15.findUnique({ where: { id: figureId }, select: { kDefinition: true }, }); - const kanjidicEnglish = prisma.kanjidicEntry.findUnique({ + const kanjidicEnglishLookup = prisma.kanjidicEntry.findUnique({ where: { id: figureId }, select: { definitions: true }, }); - return { - unihanDefinitionText: - (await unihanDefinition)?.kDefinition?.join("; ") || null, - kanjidicEnglish: - (await kanjidicEnglish)?.definitions?.filter( - (e) => !RADICAL_ENTRY_REGEX.test(e), - ) || [], + const mnemonicKeywords = componentsDictionaryEntry; + const historicalKeyword = + mnemonicKeywords?.historical && mnemonicKeywords.historical !== "(various)" + ? mnemonicKeywords.historical + : null; + let mnemonicSource = ""; + if (mnemonicKeywords?.reference) + mnemonicSource = ` {{cf. ${mnemonicKeywords.reference}}}`; + else if (mnemonicKeywords?.standin) + mnemonicSource = ` {{via ${mnemonicKeywords.standin}}}`; + + const unihanDefinitionText = + (await unihanDefinitionLookup)?.kDefinition?.join("; ") || null; + const kanjidicEnglish = + (await kanjidicEnglishLookup)?.definitions?.filter( + (e) => !RADICAL_ENTRY_REGEX.test(e), + ) || []; + const mnemonicKeyword = mnemonicKeywords?.mnemonic + ? [mnemonicKeywords.mnemonic, mnemonicSource].join("") + : null; + const historicalKeywordOrDefinition = + (historicalKeyword || + kanjidicEnglish?.[0] || + unihanDefinitionText?.split("; ")?.[0]) ?? + null; + + const meaning = { + unihanDefinitionText, + kanjidicEnglish, + keyword: historicalKeywordOrDefinition || mnemonicKeyword, + mnemonicKeyword: !historicalKeywordOrDefinition ? mnemonicKeyword : null, }; + + if ( + !meaning.unihanDefinitionText && + !meaning.kanjidicEnglish.length && + !meaning.keyword && + !meaning.mnemonicKeyword + ) + return null; + return meaning; +} + +export interface ComponentMeaning { + /** historical meaning */ + historical?: string; + /** mnemonic keyword, if historical meaning is absent or different */ + mnemonic?: string; + /** for this component's mnemonic keyword, it borrows the meaning of a common kanji containing it. */ + standin?: string; + /** this component derives its mnemonic keyword from a common kanji using it. */ + reference?: string; + /** for grouping components by meaning */ + tag?: string | null; } diff --git a/prisma/kanjisense/seedKanjisenseFigureRelation.ts b/prisma/kanjisense/seedKanjisenseFigureRelation.ts index 7745f7591..2886b66a4 100644 --- a/prisma/kanjisense/seedKanjisenseFigureRelation.ts +++ b/prisma/kanjisense/seedKanjisenseFigureRelation.ts @@ -16,7 +16,6 @@ import { registerSeeded } from "../seedUtils"; import { inBatchesOf } from "./inBatchesOf"; - export async function seedKanjisenseFigureRelation( prisma: PrismaClient, force = false, @@ -123,12 +122,14 @@ export async function seedKanjisenseFigureRelation( }, ); + await prisma.kanjisenseFigure.deleteMany({}); await prisma.kanjisenseFigureRelation.deleteMany({}); await inBatchesOf(1000, [...dbInput.values()], async (batch) => { await prisma.kanjisenseFigureRelation.createMany({ data: batch.map((r) => ({ id: r.id, idsText: r.idsText, + selectedIdsComponents: r.selectedIdsComponents, directUses: [...r.directUses], listsAsComponent: [...r.listsAsComponent], isPriorityCandidate: r.isPriorityCandidate, @@ -155,12 +156,13 @@ class CreateFigureRelationInput { constructor( id: string, idsText: string, + selectedIdsComponents: string[], isPriorityCandidate: boolean, variantGroupId: string | null, ) { this.id = id; this.idsText = idsText; - this.selectedIdsComponents = []; + this.selectedIdsComponents = selectedIdsComponents; this.directUses = new Set(); this.listsAsComponent = new Set(); this.isPriorityCandidate = isPriorityCandidate; @@ -179,7 +181,16 @@ async function analyzeFiguresRelations( ) { for (const figureId of figureIds) { const cached = cache.get(figureId); - const idsText = cached?.idsText ?? patchedIds.getIds(figureId); + const idsText = cached?.idsText ?? (await patchedIds.getIds(figureId)); + const ids = parseIds(figureId, idsText); + const jLocaleIndex = ids.locales["J"]; + if (verbose && !jLocaleIndex && ids.sequences.length > 1) { + console.log(`Arbitrarily choosing first sequence for ${figureId}`); + } + const selectedIdsComponents = getComponentsFromIds( + ids.sequences[jLocaleIndex ?? 0], + ).filter((c) => c !== figureId); + const variantGroupId = cached?.variantGroupId ?? variantGroups.find((v) => v.includes(figureId))?.[0] ?? @@ -189,7 +200,8 @@ async function analyzeFiguresRelations( cached || new CreateFigureRelationInput( figureId, - await idsText, + idsText, + selectedIdsComponents, options.isPriority, variantGroupId, ); @@ -199,22 +211,11 @@ async function analyzeFiguresRelations( } if (!cached) { - const ids = parseIds(figureId, await idsText); - const jLocaleIndex = ids.locales["J"]; - if (verbose && !jLocaleIndex && ids.sequences.length > 1) { - console.log(`Arbitrarily choosing first sequence for ${figureId}`); - } - - const components = getComponentsFromIds( - ids.sequences[jLocaleIndex ?? 0], - ).filter((c) => c !== figureId); - - if (components.length > 1) { - figureRelation.selectedIdsComponents = components; + if (selectedIdsComponents.length > 1) { await analyzeFiguresRelations( prisma, variantGroups, - components, + selectedIdsComponents, cache, patchedIds, { @@ -226,14 +227,16 @@ async function analyzeFiguresRelations( }, ); } - for (const componentKey of components) { + for (const componentKey of selectedIdsComponents) { cache.get(componentKey)!.directUses.add(figureId); } } } } -/** returns figure keys */ +/** returns figure keys for NON-ATOMIC components, + * and empty array for atomic components + */ function getComponentsFromIds(ids: ParseIds.IDS): string[] { return ParseIds.flatten(ids).map((component) => { const key = component.type === "html" ? component.code : component.char; diff --git a/prisma/kanjisense/seedKanjisenseFigures.ts b/prisma/kanjisense/seedKanjisenseFigures.ts index 17992611e..e1a217978 100644 --- a/prisma/kanjisense/seedKanjisenseFigures.ts +++ b/prisma/kanjisense/seedKanjisenseFigures.ts @@ -1,20 +1,20 @@ import { readFileSync } from "node:fs"; -import { - KanjisenseFigure, - KanjisenseFigureRelation, - PrismaClient, -} from "@prisma/client"; +import { KanjisenseFigureRelation, PrismaClient } from "@prisma/client"; import yaml from "yaml"; import { baseKanjiSet } from "~/lib/baseKanji"; import { files } from "~/lib/files.server"; +import { getFigureById } from "~/models/getFigureById.server"; import { registerSeeded } from "../seedUtils"; import { shouldComponentBeAssignedMeaning } from "./componentMeanings"; import { getBaseKanjiVariantGroups } from "./getBaseKanjiVariantGroups"; -import { getFigureMeaningsText } from "./getFigureMeaningsText"; +import { + ComponentMeaning, + getFigureMeaningsText, +} from "./getFigureMeaningsText"; // import { getStandaloneCharacters } from "./getStandaloneCharacters"; import { getListsMembership } from "./seedKanjisenseFigureRelation"; @@ -57,8 +57,21 @@ export async function seedKanjisenseFigures( const nonPriorityCharacters = await prisma.kanjisenseFigureRelation.findMany({ where: { - id: { notIn: priorityCharacters.map((c) => c.id) }, - directUses: { isEmpty: true }, + OR: [ + { + id: { notIn: priorityCharacters.map((c) => c.id) }, + directUses: { isEmpty: true }, + }, + { + id: { + in: Object.values(baseKanjiVariantsGroups).flatMap((g) => + g.variants.filter( + (v) => !priorityCharacters.some((c) => c.id === v), + ), + ), + }, + }, + ], }, }); const allStandaloneCharacters = priorityCharacters.concat( @@ -69,10 +82,11 @@ export async function seedKanjisenseFigures( where: { directUses: { isEmpty: false }, id: { not: { in: allStandaloneCharacters.map((c) => c.id) } }, - variantGroupId: { not: null }, + // variantGroupId: { not: null }, }, }); + console.log("Checking for figures needing meaning assignment..."); const meaningfulComponents: KanjisenseFigureRelation[] = []; const meaninglessComponents: KanjisenseFigureRelation[] = []; for (const figure of componentFiguresPotentiallyNeedingMeaningAssignment) { @@ -83,22 +97,98 @@ export async function seedKanjisenseFigures( if (shouldBeAssignedMeaning) meaningfulComponents.push(figure); else meaninglessComponents.push(figure); } + const remainingMeaninglessComponents = + await prisma.kanjisenseFigureRelation.findMany({ + where: { + id: { + notIn: [ + ...meaningfulComponents.map((c) => c.id), + ...meaninglessComponents.map((c) => c.id), + ...allStandaloneCharacters.map((c) => c.id), + ], + }, + }, + }); + remainingMeaninglessComponents.push(...meaninglessComponents); + + const allFiguresCount = + meaningfulComponents.length + + meaninglessComponents.length + + allStandaloneCharacters.length; + if (allFiguresCount !== (await prisma.kanjisenseFigureRelation.count())) { + const unnaccountedForFigures = + await prisma.kanjisenseFigureRelation.findMany({ + where: { + id: { + notIn: [ + ...meaningfulComponents.map((c) => c.id), + ...meaninglessComponents.map((c) => c.id), + ...allStandaloneCharacters.map((c) => c.id), + ], + }, + }, + }); + console.log(unnaccountedForFigures); + throw new Error( + `allFiguresCount (${allFiguresCount}) !== await prisma.kanjisenseFigureRelation.count() (${await prisma.kanjisenseFigureRelation.count()})`, + ); + } const priorityFiguresMeanings = new Map< string, Awaited> >(); + console.log("preparing meanings for priority figures"); for (const figure of [ ...allStandaloneCharacters, ...meaningfulComponents, ]) { - const primaryVariantId = - baseKanjiVariantsGroups[figure.id]?.id ?? figure.id; - priorityFiguresMeanings.set( - figure.id, - await getFigureMeaningsText(prisma, primaryVariantId), + const primaryVariantId = figure.variantGroupId || figure.id; + + let meaning = await getFigureMeaningsText( + prisma, + figure, + componentsDictionary[primaryVariantId] || null, ); + if (!meaning) { + const variants = + ( + await prisma.kanjisenseVariantGroup.findUnique({ + where: { id: primaryVariantId }, + select: { variants: true }, + }) + )?.variants ?? []; + const componentMeaning = componentsDictionary[primaryVariantId]; + for (const variant of variants) { + const variantFigure = await getFigureById(prisma, variant); + meaning = await getFigureMeaningsText( + prisma, + variantFigure, + componentMeaning || null, + ); + if (meaning) { + console.warn( + `meaning not found for priority figure variant ${variantFigure.id}, using variant ${variant}, ${meaning.kanjidicEnglish} // ${meaning.unihanDefinitionText}`, + ); + break; + } + } + if (!meaning) + throw new Error( + `meaning not found for any variants of priority figure ${ + figure.id + } having ${getVariantsMessage(variants)}`, + ); + if (!meaning.keyword) { + throw new Error( + `keyword not found for priority figure ${ + figure.id + } having ${getVariantsMessage(variants)}`, + ); + } + } + priorityFiguresMeanings.set(figure.id, meaning); } const allAozoraCharacterFrequencies = Object.fromEntries( @@ -117,11 +207,15 @@ export async function seedKanjisenseFigures( for (const figure of [...priorityCharacters, ...meaningfulComponents]) { const id = figure.id; const meaning = priorityFiguresMeanings.get(figure.variantGroupId ?? id); - if (!meaning) throw new Error(`meaning not found for ${id}`); + if (!meaning) console.error(`meaning not found for ${id}`); + + if (!meaning?.keyword) + console.error(`keyword not found for priority figure ${id}`); + const createFigureInput = getCreateFigureInput( figure, - meaning, - componentsDictionary, + meaning?.keyword ?? "[MISSING]", + meaning?.mnemonicKeyword ?? null, allAozoraCharacterFrequencies[id]?.appearances, true, ); @@ -131,15 +225,21 @@ export async function seedKanjisenseFigures( console.log("preparing non-priority figures..."); for (const figure of [...nonPriorityCharacters, ...meaninglessComponents]) { const id = figure.id; - const meaning = priorityFiguresMeanings.get(figure.variantGroupId ?? id); - if (!meaning) throw new Error(`meaning not found for ${id}`); const appearances = 0; - + const meaning = await getFigureMeaningsText( + prisma, + figure, + componentsDictionary[figure.variantGroupId ?? figure.id] || null, + ); + const keyword = + meaning?.kanjidicEnglish?.[0] || + meaning?.unihanDefinitionText?.split("; ")?.[0] || + "[UNNAMED FIGURE]"; const createFigureInput = getCreateFigureInput( figure, - meaning, - componentsDictionary, + keyword, + null, appearances, false, ); @@ -148,7 +248,7 @@ export async function seedKanjisenseFigures( console.log("creating entries..."); - await prisma.kanjisenseFigureMeaning.deleteMany({}); + await prisma.kanjisenseComponent.deleteMany({}); await prisma.kanjisenseFigure.deleteMany({}); console.log("seeding figures"); @@ -175,6 +275,40 @@ export async function seedKanjisenseFigures( })), }); + console.log("building components trees"); + const { componentsTreesInput, componentsToUses } = + await getAllComponentsTrees(dbInput.keys(), prisma); + + console.log("connecting components trees entries"); + for (const [id, componentsTree] of componentsTreesInput.entries()) { + const figureUsesAsComponent = componentsToUses.get(id); + try { + await prisma.kanjisenseFigure.update({ + where: { id }, + data: { + componentsTree: componentsTree.map((c) => c.toJSON()), + asComponent: figureUsesAsComponent?.size + ? { + create: { + uses: { + connect: Array.from( + figureUsesAsComponent, + (parentId) => ({ + id: parentId, + }), + ), + }, + }, + } + : undefined, + }, + }); + } catch (e) { + console.log({ id, componentsTree, figureUsesAsComponent }); + throw e; + } + } + console.log("connecting top-level components"); console.log("registering active sound marks"); @@ -197,55 +331,70 @@ interface CreateKanjisenseFigureInput { } class ComponentUse { - parent: KanjisenseFigure; - component: KanjisenseFigure; - - constructor(parent: KanjisenseFigure, component: KanjisenseFigure) { + constructor( + public parent: string, + public component: string, + ) { this.parent = parent; this.component = component; } toJSON() { - return [this.parent.id, this.component.id]; + return [this.parent, this.component]; } } -const componentsTreeCache = new Map(); +function getVariantsMessage(variants: string[]) { + return variants.length ? `variants: [${variants.join(", ")}]` : "no variants"; +} + +async function getAllComponentsTrees( + dbInput: IterableIterator, + prisma: PrismaClient, +) { + const componentsTreesInput = new Map(); + const componentsToUses = new Map>(); + const charactersToComponents = new Map>(); + const getSelectedIdsComponentsCache = new Map(); + for (const parent of dbInput) { + const componentsTree = await getComponentsTree(parent, async (id) => { + if (getSelectedIdsComponentsCache.has(id)) + return getSelectedIdsComponentsCache.get(id)!; + + const relation = await prisma.kanjisenseFigureRelation.findUnique({ + where: { id }, + select: { selectedIdsComponents: true }, + }); + if (!relation) throw new Error(`figure ${id} not found`); + getSelectedIdsComponentsCache.set(id, relation.selectedIdsComponents); + return relation.selectedIdsComponents; + }); + for (const { component } of componentsTree) { + if (!componentsToUses.has(component)) + componentsToUses.set(component, new Set()); + componentsToUses.get(component)!.add(parent); + if (!charactersToComponents.has(parent)) + charactersToComponents.set(parent, new Set()); + charactersToComponents.get(parent)!.add(component); + } + componentsTreesInput.set(parent, componentsTree); + } + return { componentsTreesInput, componentsToUses }; +} function getCreateFigureInput( figure: KanjisenseFigureRelation, - meaning: Awaited>, - componentsDictionary: Record, + keyword: string, + mnemonicKeyword: string | null, aozoraAppearances: number, isPriority: boolean, ) { const figureId = figure.id; - const primaryVariantId = figure.variantGroupId ?? figureId; - const mnemonicKeywords = componentsDictionary[primaryVariantId]; - const historicalKeyword = - mnemonicKeywords?.historical && mnemonicKeywords.historical !== "(various)" - ? mnemonicKeywords.historical - : null; - let mnemonicSource = ""; - if (mnemonicKeywords?.reference) - mnemonicSource = ` {{cf. ${mnemonicKeywords.reference}}}`; - else if (mnemonicKeywords?.standin) - mnemonicSource = ` {{via ${mnemonicKeywords.standin}}}`; - - const keyword = - (historicalKeyword || - meaning.kanjidicEnglish?.[0] || - meaning.unihanDefinitionText?.split(";")?.[0]) ?? - null; - if (!keyword) console.error(`no keyword for ${figureId}`); - return { id: figureId, - keyword: keyword ?? "[MISSING]", - mnemonicKeyword: mnemonicKeywords?.mnemonic - ? [mnemonicKeywords.mnemonic, mnemonicSource].join("") - : null, + keyword: keyword, + mnemonicKeyword: mnemonicKeyword, isPriority, listsAsComponent: figure.listsAsComponent, // refine? listsAsCharacter: [...getListsMembership(figureId)], @@ -255,39 +404,28 @@ function getCreateFigureInput( } export async function getComponentsTree( - figure: KanjisenseFigure & { relation: KanjisenseFigureRelation }, - getFigure: ( - id: string, - ) => Promise, + figureId: string, + getSelectedIdsComponents: (id: string) => Promise, + componentsTreeCache = new Map(), ) { - if (componentsTreeCache.has(figure.id)) - return componentsTreeCache.get(figure.id)!; + if (componentsTreeCache.has(figureId)) + return componentsTreeCache.get(figureId)!; const componentsTree: ComponentUse[] = []; - for (const componentKey of figure.relation.selectedIdsComponents) { - if (componentKey === figure.id) continue; // prevent infinite loop for atomic figures - const component = await getFigure(componentKey); + for (const componentId of await getSelectedIdsComponents(figureId)) { + if (componentId === figureId) continue; // prevent infinite loop for atomic figures componentsTree.push( - new ComponentUse(figure, component), - ...(await getComponentsTree(component, getFigure)), + new ComponentUse(figureId, componentId), + ...(await getComponentsTree( + componentId, + getSelectedIdsComponents, + componentsTreeCache, + )), ); } - componentsTreeCache.set(figure.id, componentsTree); + componentsTreeCache.set(figureId, componentsTree); return componentsTree; } - -interface ComponentMeaning { - /** historical meaning */ - historical?: string; - /** mnemonic keyword, if historical meaning is absent or different */ - mnemonic?: string; - /** for this component's mnemonic keyword, it borrows the meaning of a common kanji containing it. */ - standin?: string; - /** this component derives its mnemonic keyword from a common kanji using it. */ - reference?: string; - /** for grouping components by meaning */ - tag?: string | null; -} diff --git a/prisma/kanjisense/seedKanjisenseVariantGroups.ts b/prisma/kanjisense/seedKanjisenseVariantGroups.ts index 79bec57e4..9be48e0d9 100644 --- a/prisma/kanjisense/seedKanjisenseVariantGroups.ts +++ b/prisma/kanjisense/seedKanjisenseVariantGroups.ts @@ -40,16 +40,27 @@ export async function seedKanjisenseVariantGroups( // build "base variant groups" by aggregating kdb-OLD-variants of base kanji let baseVariantGroups: string[][] = []; - for (const baseChar of baseKanji) { - const oldVariants = await prisma.kanjiDbVariant.findMany({ - where: { base: baseChar, variantType: KanjiDbVariantType.OldStyle }, - }); - if (oldVariants.length) { + const oldVariants = ( + await prisma.kanjiDbVariant.findMany({ + where: { + base: { in: [...baseKanji] }, + variantType: KanjiDbVariantType.OldStyle, + }, + }) + ).reduce((baseToVariants, { base, variant }) => { + const variants = baseToVariants.get(base) || []; + variants.push(variant); + baseToVariants.set(base, variants); + return baseToVariants; + }, new Map()); + + for (const [baseChar, variants] of oldVariants) { + if (variants) { const variantGroup = [ baseChar, - ...oldVariants - .filter((o) => o.variant !== baseChar) - .map(({ variant }) => variant), + ...variants + .filter((variant) => variant !== baseChar) + .map((variant) => variant), ]; baseVariantGroups = mergeVariants(baseVariantGroups, [variantGroup]); } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a2be12023..708382f35 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -96,85 +96,68 @@ model ScriptinAozoraFrequency { model KanjisenseVariantGroup { id String @id variants String[] + // built after variants are set KanjisenseFigure KanjisenseFigure[] } model KanjisenseFigureRelation { - id String @id - variantGroupId String? - directUses String[] - listsAsComponent String[] - isPriorityCandidate Boolean - + id String @id + variantGroupId String? + directUses String[] + listsAsComponent String[] + isPriorityCandidate Boolean idsText String selectedIdsComponents String[] - - figure KanjisenseFigure? + figure KanjisenseFigure? } model KanjisenseSoundMark { character String @id chain Json - // ditch "active"? activeSoundMark String } model KanjisenseFigure { - id String @id - keyword String - mnemonicKeyword String? - isPriority Boolean - listsAsComponent String[] - listsAsCharacter String[] - - aozoraAppearances Int - + id String @id + keyword String + isPriority Boolean + listsAsComponent String[] + listsAsCharacter String[] + aozoraAppearances Int activeSoundMarkValue String? - - variantGroupId String? - variantGroup KanjisenseVariantGroup? @relation(fields: [variantGroupId], references: [id]) - - componentsTree Json? - - meaningfulUses KanjisenseComponentUse[] @relation("meaningfulUses") - topLevelComponents KanjisenseComponentUse[] @relation("topLevelComponents") - - ShuowenImage ShuowenImage? @relation(fields: [shuowenImageId], references: [id]) - shuowenImageId String? - KvgJson KvgJson? @relation(fields: [kvgJsonId], references: [id]) - kvgJsonId String? - - relation KanjisenseFigureRelation @relation(fields: [id], references: [id]) - meaning KanjisenseFigureMeaning? + variantGroupId String? + componentsTree Json? + shuowenImageId String? + kvgJsonId String? + mnemonicKeyword String? + asComponent KanjisenseComponent? @relation("asComponent") + allComponents KanjisenseComponent[] @relation("allComponents") + meaningfulUses KanjisenseComponentUse[] @relation("meaningfulUses") + firstClassComponents KanjisenseComponentUse[] @relation("firstClassComponents") + relation KanjisenseFigureRelation @relation(fields: [id], references: [id]) + KvgJson KvgJson? @relation(fields: [kvgJsonId], references: [id]) + ShuowenImage ShuowenImage? @relation(fields: [shuowenImageId], references: [id]) + variantGroup KanjisenseVariantGroup? @relation(fields: [variantGroupId], references: [id]) + meaning KanjisenseFigureMeaning? +} + +model KanjisenseComponent { + id String @id + componentFigure KanjisenseFigure @relation("asComponent", fields: [id], references: [id], onDelete: Cascade) + uses KanjisenseFigure[] @relation("allComponents") } model KanjisenseComponentUse { - component KanjisenseFigure @relation(fields: [componentId], references: [id], name: "meaningfulUses") componentId String - parent KanjisenseFigure @relation(fields: [parentId], references: [id], name: "topLevelComponents") parentId String - - tag KanjisenseComponentUseTag - - id String @id -} - -enum KanjisenseComponentUseTag { - // used within this parent character, but not common enough to have a meaning in Kanjijump - directMeaningless - // should not happen - directMeaninglessSound - // used within a "directMeaningless" component of this parent character - meaningfulIndirect - // used within a "directMeaningless component" of this parent character, serving as sound mark - meaningfulIndirectSound - // used directly as a sound mark within this parent character - sound + tag KanjisenseComponentUseTag + id String @id + component KanjisenseFigure @relation("meaningfulUses", fields: [componentId], references: [id]) + parent KanjisenseFigure @relation("firstClassComponents", fields: [parentId], references: [id]) } model KanjisenseFigureReading { - id String @id - + id String @id guangyunYunjing String? guangyunInferredKanOn String? guangyunInferredGoOn String? @@ -189,16 +172,14 @@ model KanjisenseFigureReading { } model KanjisenseFigureMeaning { - id String @id - figure KanjisenseFigure @relation(fields: [id], references: [id]) - - kanjidicEnglish String[] - unihanDefinition String? - + id String @id + unihanDefinition String? kanjisenseHistorical String? kanjisenseMnemonic String? kanjisenseStandin String? kanjisenseReference String? + kanjidicEnglish String[] + figure KanjisenseFigure @relation(fields: [id], references: [id], onDelete: Cascade) } model KvgJson { @@ -238,6 +219,14 @@ model Note { user User @relation(fields: [userId], references: [id], onDelete: Cascade) } +enum KanjisenseComponentUseTag { + directMeaningless + directMeaninglessSound + meaningfulIndirect + meaningfulIndirectSound + sound +} + enum KanjiDbVariantType { VariationSelectorVariant CjkviVariant