feat(bot): add configurable household assistant behavior

This commit is contained in:
2026-03-12 03:22:43 +04:00
parent 146f5294f4
commit 4e7400e908
22 changed files with 4127 additions and 96 deletions

View File

@@ -172,6 +172,16 @@ function repository(): HouseholdConfigurationRepository {
utilitiesReminderDay: input.utilitiesReminderDay ?? 3,
timezone: input.timezone ?? 'Asia/Tbilisi'
}),
getHouseholdAssistantConfig: async (householdId) => ({
householdId,
assistantContext: 'House in Kojori',
assistantTone: 'Playful'
}),
updateHouseholdAssistantConfig: async (input) => ({
householdId: input.householdId,
assistantContext: input.assistantContext ?? 'House in Kojori',
assistantTone: input.assistantTone ?? 'Playful'
}),
listHouseholdUtilityCategories: async () => [],
upsertHouseholdUtilityCategory: async (input) => ({
id: input.slug ?? 'utility-category-1',
@@ -269,6 +279,11 @@ describe('createMiniAppAdminService', () => {
utilitiesReminderDay: 3,
timezone: 'Asia/Tbilisi'
},
assistantConfig: {
householdId: 'household-1',
assistantContext: 'House in Kojori',
assistantTone: 'Playful'
},
topics: [
{
householdId: 'household-1',
@@ -322,6 +337,11 @@ describe('createMiniAppAdminService', () => {
utilitiesDueDay: 5,
utilitiesReminderDay: 4,
timezone: 'Asia/Tbilisi'
},
assistantConfig: {
householdId: 'household-1',
assistantContext: 'House in Kojori',
assistantTone: 'Playful'
}
})
})

View File

@@ -1,4 +1,5 @@
import type {
HouseholdAssistantConfigRecord,
HouseholdBillingSettingsRecord,
HouseholdConfigurationRepository,
HouseholdMemberAbsencePolicy,
@@ -29,6 +30,7 @@ export interface MiniAppAdminService {
| {
status: 'ok'
settings: HouseholdBillingSettingsRecord
assistantConfig: HouseholdAssistantConfigRecord
categories: readonly HouseholdUtilityCategoryRecord[]
members: readonly HouseholdMemberRecord[]
memberAbsencePolicies: readonly HouseholdMemberAbsencePolicyRecord[]
@@ -51,10 +53,13 @@ export interface MiniAppAdminService {
utilitiesDueDay: number
utilitiesReminderDay: number
timezone: string
assistantContext?: string
assistantTone?: string
}): Promise<
| {
status: 'ok'
settings: HouseholdBillingSettingsRecord
assistantConfig: HouseholdAssistantConfigRecord
}
| {
status: 'rejected'
@@ -210,6 +215,34 @@ function normalizeDisplayName(raw: string): string | null {
return trimmed.replace(/\s+/g, ' ')
}
function defaultAssistantConfig(householdId: string): HouseholdAssistantConfigRecord {
return {
householdId,
assistantContext: null,
assistantTone: null
}
}
function normalizeAssistantText(
raw: string | undefined,
maxLength: number
): string | null | undefined {
if (raw === undefined) {
return undefined
}
const trimmed = raw.trim()
if (trimmed.length === 0) {
return null
}
if (trimmed.length > maxLength) {
return null
}
return trimmed
}
export function createMiniAppAdminService(
repository: HouseholdConfigurationRepository
): MiniAppAdminService {
@@ -222,17 +255,22 @@ export function createMiniAppAdminService(
}
}
const [settings, categories, members, memberAbsencePolicies, topics] = await Promise.all([
repository.getHouseholdBillingSettings(input.householdId),
repository.listHouseholdUtilityCategories(input.householdId),
repository.listHouseholdMembers(input.householdId),
repository.listHouseholdMemberAbsencePolicies(input.householdId),
repository.listHouseholdTopicBindings(input.householdId)
])
const [settings, assistantConfig, categories, members, memberAbsencePolicies, topics] =
await Promise.all([
repository.getHouseholdBillingSettings(input.householdId),
repository.getHouseholdAssistantConfig
? repository.getHouseholdAssistantConfig(input.householdId)
: Promise.resolve(defaultAssistantConfig(input.householdId)),
repository.listHouseholdUtilityCategories(input.householdId),
repository.listHouseholdMembers(input.householdId),
repository.listHouseholdMemberAbsencePolicies(input.householdId),
repository.listHouseholdTopicBindings(input.householdId)
])
return {
status: 'ok',
settings,
assistantConfig,
categories,
members,
memberAbsencePolicies,
@@ -263,6 +301,23 @@ export function createMiniAppAdminService(
}
}
const assistantContext = normalizeAssistantText(input.assistantContext, 1200)
const assistantTone = normalizeAssistantText(input.assistantTone, 160)
if (
(input.assistantContext !== undefined &&
assistantContext === null &&
input.assistantContext.trim().length > 0) ||
(input.assistantTone !== undefined &&
assistantTone === null &&
input.assistantTone.trim().length > 0)
) {
return {
status: 'rejected',
reason: 'invalid_settings'
}
}
let rentAmountMinor: bigint | null | undefined
let rentCurrency: CurrencyCode | undefined
const settlementCurrency = input.settlementCurrency
@@ -291,38 +346,65 @@ export function createMiniAppAdminService(
rentCurrency = parseCurrency(input.rentCurrency ?? 'USD')
}
const settings = await repository.updateHouseholdBillingSettings({
householdId: input.householdId,
...(settlementCurrency
? {
settlementCurrency
}
: {}),
...(paymentBalanceAdjustmentPolicy
? {
paymentBalanceAdjustmentPolicy
}
: {}),
...(rentAmountMinor !== undefined
? {
rentAmountMinor
}
: {}),
...(rentCurrency
? {
rentCurrency
}
: {}),
rentDueDay: input.rentDueDay,
rentWarningDay: input.rentWarningDay,
utilitiesDueDay: input.utilitiesDueDay,
utilitiesReminderDay: input.utilitiesReminderDay,
timezone: input.timezone.trim()
})
const shouldUpdateAssistantConfig =
assistantContext !== undefined || assistantTone !== undefined
const [settings, nextAssistantConfig] = await Promise.all([
repository.updateHouseholdBillingSettings({
householdId: input.householdId,
...(settlementCurrency
? {
settlementCurrency
}
: {}),
...(paymentBalanceAdjustmentPolicy
? {
paymentBalanceAdjustmentPolicy
}
: {}),
...(rentAmountMinor !== undefined
? {
rentAmountMinor
}
: {}),
...(rentCurrency
? {
rentCurrency
}
: {}),
rentDueDay: input.rentDueDay,
rentWarningDay: input.rentWarningDay,
utilitiesDueDay: input.utilitiesDueDay,
utilitiesReminderDay: input.utilitiesReminderDay,
timezone: input.timezone.trim()
}),
repository.updateHouseholdAssistantConfig && shouldUpdateAssistantConfig
? repository.updateHouseholdAssistantConfig({
householdId: input.householdId,
...(assistantContext !== undefined
? {
assistantContext
}
: {}),
...(assistantTone !== undefined
? {
assistantTone
}
: {})
})
: repository.getHouseholdAssistantConfig
? repository.getHouseholdAssistantConfig(input.householdId)
: Promise.resolve({
householdId: input.householdId,
assistantContext: assistantContext ?? null,
assistantTone: assistantTone ?? null
})
])
return {
status: 'ok',
settings
settings,
assistantConfig: nextAssistantConfig
}
},