feat(tag-vocab): NoteRepository — getTopUsedTags + getTagIdByName (#3 v0.2.3)

- getTopUsedTags(limit=20): top-N (count desc, id asc) + kebab-case 필터 + deleted_at 제외
- getTagIdByName(name): COLLATE NOCASE lookup
- AI+user source 통합 카운트 (Q1=C 결정)
- 단위 +7 cases (정렬, 필터, source 통합, deleted 제외, limit, getTagIdByName)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
altair823
2026-05-02 12:10:36 +09:00
parent 853ca39c0d
commit df8a53aec1
2 changed files with 103 additions and 0 deletions

View File

@@ -215,6 +215,40 @@ export class NoteRepository {
.run(nextRunAt, lastError.slice(0, 500), noteId);
}
/**
* v0.2.3 #3 — AI prompt 의 vocabulary 후보. 사용 빈도 높은 태그 top-N.
* source 무시 (AI+user 통합), kebab-case 통과한 것만 (한글/공백/대문자 제외).
* deleted_at IS NULL 만 (휴지통 노트 태그 제외).
*/
getTopUsedTags(limit = 20): string[] {
const rows = this.db
.prepare(
`SELECT t.name, COUNT(*) c
FROM tags t
JOIN note_tags nt ON nt.tag_id = t.id
JOIN notes n ON n.id = nt.note_id
WHERE n.deleted_at IS NULL
GROUP BY t.id
ORDER BY c DESC, t.id ASC
LIMIT ?`
)
.all(limit) as Array<{ name: string; c: number }>;
return rows
.map((r) => r.name)
.filter((n) => /^[a-z0-9-]+$/.test(n));
}
/**
* v0.2.3 #3 — vocab hit telemetry 의 tagId 확보용. updateAiResult 후 호출 보장.
* tags.name COLLATE NOCASE 라 case-insensitive lookup.
*/
getTagIdByName(name: string): number | null {
const row = this.db
.prepare(`SELECT id FROM tags WHERE name = ? COLLATE NOCASE LIMIT 1`)
.get(name) as { id: number } | undefined;
return row ? row.id : null;
}
updateUserAiFields(
id: string,
fields: { title?: string; summary?: string; tags?: string[] }