Refactor code structure for improved readability and maintainability
Test Suite / test (24.15.0) (push) Has been cancelled

This commit is contained in:
Nils-Johan Gynther
2026-05-06 07:37:59 +02:00
parent e4f201ea36
commit 969dafdbc6
273 changed files with 11357 additions and 39 deletions
+12
View File
@@ -0,0 +1,12 @@
export interface AiModelInfo {
id: string;
name: string;
description: string;
model: string;
path: string;
trigger: string;
access: string;
}
export declare class AiController {
getModels(): AiModelInfo[];
}
+79
View File
@@ -0,0 +1,79 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AiController = void 0;
const common_1 = require("@nestjs/common");
const public_decorator_1 = require("../auth/decorators/public.decorator");
const ai_service_1 = require("./ai.service");
const RECEIPT_IMPORT_MODEL = 'mistral-small-2603';
let AiController = class AiController {
getModels() {
return [
{
id: 'receipt-pdf',
name: 'Kvittoimport — PDF-tolkning',
description: 'Extraherar varunamn, mängd och pris ur PDF-kvitton via textanalys.',
model: RECEIPT_IMPORT_MODEL,
path: '/import',
trigger: 'Vid uppladdning av PDF-kvitto',
access: 'Alla inloggade',
},
{
id: 'receipt-image',
name: 'Kvittoimport — Bildtolkning',
description: 'Extraherar varunamn, mängd och pris ur kvittofoton via bildanalys.',
model: RECEIPT_IMPORT_MODEL,
path: '/import',
trigger: 'Vid uppladdning av kvittobild (JPEG, PNG, WebP, HEIC)',
access: 'Alla inloggade',
},
{
id: 'receipt-category',
name: 'Kvittoimport — Kategorisuggestion',
description: 'För varor som inte matchas mot befintliga produkter visas ett AI-förslag på kategori som ledtråd.',
model: ai_service_1.AI_CATEGORIZATION_MODEL,
path: '/import',
trigger: 'Automatiskt efter kvittotolkning (om inga träffar hittas)',
access: 'Premium-användare + Admin',
},
{
id: 'product-suggest',
name: 'AI-kategorisering per produkt',
description: 'Ger ett AI-förslag på kategori för en enskild produkt med säkerhetsindikation (hög/medel/låg).',
model: ai_service_1.AI_CATEGORIZATION_MODEL,
path: '/admin/products',
trigger: 'Manuell — klick på "✨ Fråga AI" i produktlistan',
access: 'Admin',
},
{
id: 'product-bulk',
name: 'AI-bulk-kategorisering',
description: 'Analyserar alla okategoriserade produkter och presenterar förslag i ett bekräftelsemodal.',
model: ai_service_1.AI_CATEGORIZATION_MODEL,
path: '/admin/products',
trigger: 'Manuell — knappen "✨ AI-kategorisera okategoriserade"',
access: 'Admin',
},
];
}
};
exports.AiController = AiController;
__decorate([
(0, common_1.Get)('models'),
(0, public_decorator_1.Public)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Array)
], AiController.prototype, "getModels", null);
exports.AiController = AiController = __decorate([
(0, common_1.Controller)('ai')
], AiController);
//# sourceMappingURL=ai.controller.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ai.controller.js","sourceRoot":"","sources":["../../src/ai/ai.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,0EAA6D;AAC7D,6CAAuD;AAEvD,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAa3C,IAAM,YAAY,GAAlB,MAAM,YAAY;IAGvB,SAAS;QACP,OAAO;YACL;gBACE,EAAE,EAAE,aAAa;gBACjB,IAAI,EAAE,6BAA6B;gBACnC,WAAW,EAAE,oEAAoE;gBACjF,KAAK,EAAE,oBAAoB;gBAC3B,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,+BAA+B;gBACxC,MAAM,EAAE,gBAAgB;aACzB;YACD;gBACE,EAAE,EAAE,eAAe;gBACnB,IAAI,EAAE,6BAA6B;gBACnC,WAAW,EAAE,oEAAoE;gBACjF,KAAK,EAAE,oBAAoB;gBAC3B,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,uDAAuD;gBAChE,MAAM,EAAE,gBAAgB;aACzB;YACD;gBACE,EAAE,EAAE,kBAAkB;gBACtB,IAAI,EAAE,mCAAmC;gBACzC,WAAW,EAAE,mGAAmG;gBAChH,KAAK,EAAE,oCAAuB;gBAC9B,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,2DAA2D;gBACpE,MAAM,EAAE,2BAA2B;aACpC;YACD;gBACE,EAAE,EAAE,iBAAiB;gBACrB,IAAI,EAAE,+BAA+B;gBACrC,WAAW,EAAE,gGAAgG;gBAC7G,KAAK,EAAE,oCAAuB;gBAC9B,IAAI,EAAE,iBAAiB;gBACvB,OAAO,EAAE,iDAAiD;gBAC1D,MAAM,EAAE,OAAO;aAChB;YACD;gBACE,EAAE,EAAE,cAAc;gBAClB,IAAI,EAAE,wBAAwB;gBAC9B,WAAW,EAAE,2FAA2F;gBACxG,KAAK,EAAE,oCAAuB;gBAC9B,IAAI,EAAE,iBAAiB;gBACvB,OAAO,EAAE,uDAAuD;gBAChE,MAAM,EAAE,OAAO;aAChB;SACF,CAAC;IACJ,CAAC;CACF,CAAA;AApDY,oCAAY;AAGvB;IAFC,IAAA,YAAG,EAAC,QAAQ,CAAC;IACb,IAAA,yBAAM,GAAE;;;;6CAiDR;uBAnDU,YAAY;IADxB,IAAA,mBAAU,EAAC,IAAI,CAAC;GACJ,YAAY,CAoDxB"}
+2
View File
@@ -0,0 +1,2 @@
export declare class AiModule {
}
+23
View File
@@ -0,0 +1,23 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AiModule = void 0;
const common_1 = require("@nestjs/common");
const ai_service_1 = require("./ai.service");
const ai_controller_1 = require("./ai.controller");
let AiModule = class AiModule {
};
exports.AiModule = AiModule;
exports.AiModule = AiModule = __decorate([
(0, common_1.Module)({
controllers: [ai_controller_1.AiController],
providers: [ai_service_1.AiService],
exports: [ai_service_1.AiService],
})
], AiModule);
//# sourceMappingURL=ai.module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ai.module.js","sourceRoot":"","sources":["../../src/ai/ai.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,6CAAyC;AACzC,mDAA+C;AAOxC,IAAM,QAAQ,GAAd,MAAM,QAAQ;CAAG,CAAA;AAAX,4BAAQ;mBAAR,QAAQ;IALpB,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,4BAAY,CAAC;QAC3B,SAAS,EAAE,CAAC,sBAAS,CAAC;QACtB,OAAO,EAAE,CAAC,sBAAS,CAAC;KACrB,CAAC;GACW,QAAQ,CAAG"}
+14
View File
@@ -0,0 +1,14 @@
import { FlatCategory } from '../categories/categories.service';
export declare const AI_CATEGORIZATION_MODEL = "mistral-tiny";
export type CategorySuggestion = {
categoryId: number;
categoryName: string;
path: string;
confidence: 'high' | 'medium' | 'low';
usedFallback: boolean;
};
export declare class AiService {
private readonly logger;
suggestCategory(productName: string, categories: FlatCategory[]): Promise<CategorySuggestion>;
private fallbackToOvrigt;
}
+150
View File
@@ -0,0 +1,150 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var AiService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AiService = exports.AI_CATEGORIZATION_MODEL = void 0;
const common_1 = require("@nestjs/common");
const MISTRAL_API_URL = 'https://api.mistral.ai/v1/chat/completions';
exports.AI_CATEGORIZATION_MODEL = 'mistral-tiny';
const MODEL = exports.AI_CATEGORIZATION_MODEL;
let AiService = AiService_1 = class AiService {
constructor() {
this.logger = new common_1.Logger(AiService_1.name);
}
async suggestCategory(productName, categories) {
const apiKey = process.env.MISTRAL_API_KEY;
if (!apiKey) {
throw new common_1.ServiceUnavailableException('MISTRAL_API_KEY är inte konfigurerad i miljövariabler');
}
const categoryList = categories
.map((c) => `[${c.id}] ${c.path}`)
.join('\n');
const systemPrompt = `Du är ett kategoriseringssystem för en livsmedelsapp. Din uppgift är att hitta den mest lämpliga kategorin för en produkt.
Tillgängliga kategorier (format: [id] Sökväg):
${categoryList}
Regler:
1. Välj den mest specifika underkategorin som passar produkten.
2. Om ingen specifik kategori passar, välj en underkategori under "Övrigt" om möjligt.
3. Om ingen underkategori under "Övrigt" passar, välj "Övrigt" (den kategori vars sökväg är exakt "Övrigt").
4. Du MÅSTE alltid returnera ett svar — aldrig null eller tomt.
5. Svara ENDAST med giltig JSON i detta format: { "categoryId": <nummer>, "confidence": "high" | "medium" | "low" }
- "high": uppenbart matchande kategori
- "medium": trolig matchning
- "low": osäker, används fallback (Övrigt eller underkategori till Övrigt)`;
const userPrompt = `Produkt: "${productName}"`;
let raw = '';
const MAX_RETRIES = 3;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
const response = await fetch(MISTRAL_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: MODEL,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
max_tokens: 100,
temperature: 0.1,
response_format: { type: 'json_object' },
}),
});
if (response.status === 503 || response.status === 429) {
const err = await response.text();
this.logger.warn(`Mistral API ${response.status} (försök ${attempt}/${MAX_RETRIES}): ${err}`);
if (attempt < MAX_RETRIES) {
await new Promise((r) => setTimeout(r, attempt * 1500));
continue;
}
throw new common_1.ServiceUnavailableException('AI-tjänsten är tillfälligt otillgänglig, försök igen');
}
if (!response.ok) {
const err = await response.text();
this.logger.error(`Mistral API-fel: ${response.status} ${err}`);
throw new common_1.ServiceUnavailableException('AI-tjänsten svarade inte korrekt');
}
const data = await response.json();
raw = data.choices?.[0]?.message?.content ?? '';
break;
}
catch (err) {
if (err instanceof common_1.ServiceUnavailableException)
throw err;
this.logger.error(`Mistral fetch-fel (försök ${attempt}/${MAX_RETRIES}): ${String(err)}`);
if (attempt < MAX_RETRIES) {
await new Promise((r) => setTimeout(r, attempt * 1500));
continue;
}
throw new common_1.ServiceUnavailableException('Kunde inte nå AI-tjänsten');
}
}
let parsed;
try {
parsed = JSON.parse(raw);
}
catch {
this.logger.warn(`AI returnerade ogiltig JSON: ${raw}`);
return this.fallbackToOvrigt(categories);
}
const validId = typeof parsed.categoryId === 'number';
const matchedCategory = validId ? categories.find((c) => c.id === parsed.categoryId) : null;
if (!matchedCategory) {
this.logger.warn(`AI returnerade okänt categoryId ${parsed.categoryId}, använder fallback`);
return this.fallbackToOvrigt(categories);
}
const confidence = ['high', 'medium', 'low'].includes(parsed.confidence)
? parsed.confidence
: 'medium';
if (confidence === 'low') {
const l1Name = matchedCategory.path.split(' > ')[0];
const l1 = categories.find((c) => c.path === l1Name);
if (l1 && l1.id !== matchedCategory.id) {
this.logger.log(`AI-guardrail: ${confidence} konfidenspoäng → remappar "${matchedCategory.path}" → L1 "${l1.path}"`);
return {
categoryId: l1.id,
categoryName: l1.name,
path: l1.path,
confidence,
usedFallback: true,
};
}
}
return {
categoryId: matchedCategory.id,
categoryName: matchedCategory.name,
path: matchedCategory.path,
confidence,
usedFallback: confidence === 'low',
};
}
fallbackToOvrigt(categories) {
const ovrigt = categories.find((c) => c.path === 'Övrigt');
if (!ovrigt) {
const first = categories[0];
return { categoryId: first.id, categoryName: first.name, path: first.path, confidence: 'low', usedFallback: true };
}
return {
categoryId: ovrigt.id,
categoryName: ovrigt.name,
path: ovrigt.path,
confidence: 'low',
usedFallback: true,
};
}
};
exports.AiService = AiService;
exports.AiService = AiService = AiService_1 = __decorate([
(0, common_1.Injectable)()
], AiService);
//# sourceMappingURL=ai.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ai.service.js","sourceRoot":"","sources":["../../src/ai/ai.service.ts"],"names":[],"mappings":"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"}