- Added package.json for project dependencies and scripts - Created setup_mariadb.sql for database setup and user creation - Documented project development plan in taskfile.txt - Implemented test_setup.sh for system requirements and project structure checks - Configured systemd service files for backend and frontend applications - Added ubu-deployment-commands.sh for deployment instructions on Ubu server
144 lines
19 KiB
Plaintext
144 lines
19 KiB
Plaintext
Udvikling af en AI-baseret tilbudsberegner
|
||
1. Domæne: Tømrerfaget som udgangspunkt
|
||
|
||
Først skal vi afgrænse domænet til tømrer- og snedkerarbejde, da tilbudsberegneren skal skræddersys til dette område. En tømrer-tilbudsberegner skal forstå de typiske opgaver en tømrer udfører – fx tagkonstruktioner, gulvlægning, montering af døre/vinduer, terrassebyggeri, isolering mv. Systemet bør have kendskab til almindelige enhedspriser og tidsforbrug for sådanne opgaver baseret på brancheerfaring (“erfaringspriser”). For eksempel kan timeprisen for en tømrer ligge omkring 260 kr/timen, gulvlægning ca. 110 kr/m², og et nyt tegltag omkring 1.300 kr/m² inkl. materialer
|
||
byggetilbud.dk
|
||
byggetilbud.dk
|
||
. Disse tal varierer naturligvis, men de illustrerer den type basisdata som AI’en skal have adgang til. Med andre ord skal domænemodellen vide, hvilke typer arbejdsopgaver og materialer der indgår i et tømrerprojekt, samt hvordan man typisk beregner prisen herfor.
|
||
2. Relevante elementer i et tømrer-tilbud
|
||
|
||
Et godt tilbud fra en tømrer indeholder en detaljeret oversigt over alle relevante omkostninger og forudsætninger. Den AI-baserede beregner bør som minimum dække følgende nøgleelementer:
|
||
|
||
Arbejdstid og løn – Estimering af det antal arbejdstimer, der kræves, samt enhedspris pr. time for tømrerarbejde. Dette ganges op til en samlet lønomkostning. (Fx 40 timer á 400 kr/t = 16.000 kr). AI’en skal kunne tage højde for medarbejdertyper og satser, inklusive evt. sociale bidrag.
|
||
|
||
Materialer – En liste over nødvendige materialer (træ, plader, tagsten, skruer osv.) med mængder og priser. AI’en bør lægge materialepriser sammen og lægge avance på materialer. Man kalkulerer ofte materialekost + et markup (fortjeneste) på fx 10-25%, afhængigt af markedet
|
||
reddit.com
|
||
reddit.com
|
||
.
|
||
|
||
Overhead/faste omkostninger – Indregn faste driftsudgifter som transport, administration, værktøjsslitage, forsikringer m.m. Typisk lægges et generelt overhead på (fx 5-15% af de direkte omkostninger) for at dække forretningens øvrige udgifter
|
||
buildern.com
|
||
. Disse indirekte omkostninger skal med for at sikre tilbuddet er økonomisk bæredygtigt.
|
||
|
||
Fortjeneste og buffer – Udover direkte omkostninger skal der lægges en fortjenstmargen (profit). Mange bruger en formel hvor totalprisen = arbejdsløn + materialer + overhead + profitmargin + evt. buffer
|
||
getjobber.com
|
||
. En buffer (kontingens) ~5-10% kan lægges til for uforudsete udgifter, så man ikke ender med underskud hvis noget går galt.
|
||
|
||
Arbejdsbeskrivelse og omfang – En klar tekstbeskrivelse af det arbejde, der udføres, og hvad der er inkluderet i prisen. Dette indebærer at AI’en formulerer hvilke opgaver der konkret udføres (f.eks. nedtagning af eksisterende tag, opsætning af stillads, oprydning/bortskaffelse, montering af nye materialer osv.). Sådanne beskrivelser findes ofte som standardtekster i tilbud, og AI’et kan generere dem baseret på opgaven. (Fx angive at prisen inkluderer nedrivning og bortskaffelse af gammelt tag, montering af lægter, undertag, tagsten, tagrender etc., hvis det er et tagprojekt
|
||
byggetilbud.dk
|
||
). Det er også vigtigt at nævne, hvis noget ikke er inkluderet (f.eks. “tilbuddet inkluderer ikke malerarbejde eller el-arbejde”).
|
||
|
||
Tidsplan og gyldighed – Ofte angives en forventet tidsramme for projektet (start- og slutdato eller varighed), samt hvor længe tilbuddet er gyldigt (f.eks. 30 dage). AI’en kan automatisk indsætte standardformuleringer om tidsplan og at prisen er gældende inden for en vis periode.
|
||
|
||
Eventuelle forbehold og betingelser – Angivelse af betalingsbetingelser (fx 30% forud, resten efter udført arbejde), garantier og forbehold (f.eks. vejrafhængige forsinkelser, kundens ansvar for tilladelser, osv.). Disse kan være skabelonagtige tekstbidder, som AI’en lærer at inkludere efter behov.
|
||
|
||
Ved at sikre at alle ovenstående elementer kommer med, vil det AI-genererede tilbud ligne et professionelt håndværkertilbud. AI’en skal både regne de hårde tal rigtigt ud og formidle det i et klart sprog. Summen af komponenterne (arbejde, materialer, overhead, profit, moms etc.) skal fremgå tydeligt, så kunden ser hvordan prisen er sammensat. Ifølge brancheanbefalinger for entreprenører bør man netop beregne totalprisen som summen af alle disse poster
|
||
getjobber.com
|
||
, hvilket tilbudsberegneren automatisk kan gøre.
|
||
3. Yderligere idéer og funktioner
|
||
|
||
Udover de oplistede punkter kan vi udvide funktionaliteten for at gøre tilbudsberegneren endnu bedre:
|
||
|
||
Interaktiv spørgeguide: I stedet for blot at tage én brugerinput og spytte et tilbud ud, kunne frontenden fungere som en lille chat, hvor AI’en stiller opklarende spørgsmål. F.eks. hvis en kunde skriver “Nyt tag på mit hus”, kan AI’en spørge til tagareal, type (tegl, stål, pap), husets højde m.v. Dette sikrer at den får alle nødvendige detaljer, før den beregner tilbuddet. En sådan dialogbaseret tilgang udnytter AI’ens styrke i naturlig sprogbehandling til at efterligne en rigtig samtale med en håndværker.
|
||
|
||
Flere fagområder: Selvom fokus er på tømrer, kan systemet designes fleksibelt, så man senere kan tilføje andre fag (murer, maler, elektriker osv.). Hvert fag kan have sin egen videnbase og prisdata, men dele samme platform. Arkitekturmæssigt kan man modulere det, så “begge hjemmesider” – dvs. en for hver fagkategori – kører på samme backend-skabelon men med hver sin database (se næste afsnit).
|
||
|
||
Automatisk format og PDF: AI’en kan generere tilbudsteksten, men man kan også integrere et PDF-genereringsmodul, så tilbuddet formateres pænt med firmanavn/logo, tilbudsnummer, dato osv., klar til at sende til kunden. Dette er mere præsentabelt end rå tekst.
|
||
|
||
Forbedring via feedback: Systemet kan indbygges med feedback-loop. Hvis brugeren justerer noget manuelt i det genererede tilbud (f.eks. retter prisen på en post), kan disse ændringer gemmes. Over tid kan man samle data om afvigelser mellem AI-forslag og faktisk accepterede tilbud, og således justere prisberegningerne. Dette kunne gøres ved at finetune modellen løbende med nye eksempler eller simpelthen ved at justere de underliggende prisdata.
|
||
|
||
Sikkerhed for forkerte svar: En udfordring ved generative sprogmodeller er, at de kan “hallucinere” – dvs. finde på ting, som ikke passer. For at undgå at AI’en giver teknisk forkerte eller økonomisk uforsvarlige svar, kan man lægge begrænsninger ind. F.eks. kan backend have regler: hvis AI’en foreslår en totalpris under summen af kendte omkostninger, skal den justeres eller advares. Man kan også have et log-system, så tilbud ikke sendes direkte til kunde uden en gennemgang.
|
||
|
||
Integration med eksisterende systemer: På sigt kan man koble tilbudsberegneren med firmaets CRM eller projektstyring. Fx hvis kunden accepterer tilbuddet, kan data sendes videre til en ordreseddel. AI’en kunne også bruge eksisterende kundedata (hvis kunden er i databasen) for at personalisere tilbuddet (f.eks. “dejligt at arbejde for dig igen” hvis det er en tidligere kunde).
|
||
|
||
Kort sagt handler det om at gøre løsningen nem at bruge og værdiskabende ud over blot at regne en pris. AI’en kan automatisere kedelige dele (udregning, tekstskrivning) og hjælpe med at standardisere tilbuddene, men samtidig bør systemet give mulighed for at mennesket kan finpudse og kontrollere inden afsendelse.
|
||
4. Simpelt systemdesign og teknologi
|
||
|
||
For at gøre systemet så brugervenligt og ”nemt” som muligt, følger vi en klassisk web-applikationsarkitektur med et frontend og et backend:
|
||
|
||
Frontend (React): En enkel webside/app hvor brugeren indtaster oplysninger om opgaven. Dette kan være et formular-baseret UI (felter som “Projekt type”, “Kvadratmeter”, “Beskrivelse” osv.) eller en chat-lignende grænseflade hvor man skriver i fri tekst. React er et godt valg da det er populært og kan skabe en interaktiv brugeroplevelse. Den sender brugerens input videre til backenden via API-kald. Frontenden kan også præsentere det færdige tilbud pænt og give mulighed for at rette eller bekræfte.
|
||
|
||
Backend (Node.js eller Python Flask): Backenden er hjernen, der håndterer AI-delen. Valget mellem Node og Flask afhænger af udviklerens præferencer – begge kan fungere fint. Node (med Express.js) vil være JavaScript hele vejen igennem (godt hvis man er komfortabel med JS), mens Python Flask kan være fordelagtigt hvis man vil drage nytte af Pytons AI/ML-økosystem. Begge kan kalde OpenAI’s API forholdsvis let (OpenAI tilbyder klientbiblioteker til både JS og Python).
|
||
|
||
Backendens hovedopgave er at modtage data fra frontend, sammenstille en prompt til OpenAI’s model og sende svaret tilbage. Prompten vil typisk bestå af en systembesked med instruktioner (f.eks. “Du er en erfaren tilbudsberegner for et tømrerfirma…”), efterfulgt af det dynamiske indhold: de indlæste prisdata/erfaringspriser og de specifikke brugerinput.
|
||
|
||
Backenden bør også håndtere logik som f.eks. opsplitning af brugerens input i dele (areal, materialevalg etc.), evt. beregninger for mængder, før det sendes til AI. Man kan vælge at lade AI’en selv udføre beregningerne i tekst (baseret på de oplysninger den har), men ofte er det sikrere at lave kritiske beregninger i koden. For eksempel kan backend beregne “antal kvm * pris pr. kvm” for materialer og give disse tal til modellen, som så fokuserer på at formulere tilbuddet pænt.
|
||
|
||
OpenAI API: Til selve AI-delen bruger vi OpenAI’s model (fx GPT-4 for bedste resultater, eller GPT-3.5 Turbo for lavere omkostning). Via API’et sender vi prompten og modtager det genererede tilbudstekst. Husk at sikre API-nøglen og overholde token-grænser (lange prompts med meget data kan være dyre, så optimer promptlængden).
|
||
|
||
Eksempel: Når brugeren fx har angivet “40 m² nyt klikgulv, inkl. fjernelse af gammelt gulv”, så vil backend slå op i prisdatabasen at gulvlægning ~110 kr/m²
|
||
byggetilbud.dk
|
||
, gulvafdækning/bortskaffelse måske ekstra, beregne en subtotalsum, og så lave en prompt til GPT som indeholder noget ala: “Opgave: 40 m2 klikgulv lægges… Materialer koster X kr, Arbejde Y timer… Skriv et tilbud.” Modellen returnerer en færdig formuleret tekst med prisberegning.
|
||
|
||
Testing og iteration: Fordi vi bruger et modulært design, kan man nemt teste de enkelte dele. Start med at teste API’et med nogle få eksempler manuelt (f.eks. via en curl eller Postman) for at se om GPT leverer fornuftige tilbud ud fra givne prompts. Justér prompt-engineering indtil tonen og formatet sidder i skabet. Derefter kan frontenden kobles på.
|
||
|
||
Valg af teknologistak (React/Node vs. React/Flask) kommer an på hvad du er mest tryg ved. Begge opfylder kravet om et “nemt” setup:
|
||
|
||
Node/Express har fordelen at alt er JavaScript (samme sprog i frontend og backend). Der findes også mange eksempler på at bruge OpenAI API i Node, samt npm-pakker til at integrere med databaser m.m.
|
||
|
||
Python/Flask er letvægt og Python er ofte brugt til AI-projekter. Du kan drage fordel af biblioteker som Pandas for at håndtere datafiler (erfaringspriser) eller LangChain for avanceret promptstyring, hvis det bliver relevant. Flask kan køre som en lille REST API-server.
|
||
|
||
Uanset valg, så hold arkitekturen simpel: en backend-route som f.eks. /api/generateQuote kan modtage en JSON med brugerens input, og returnere JSON med det færdige tilbud. Frontenden kalder denne og viser resultatet. Dette adskiller præsentation og AI-logik pænt, og gør det også nemt senere at udskifte modellen eller integrere nye interfaces (fx mobilapp) uden at røre ved AI-delen.
|
||
5. Database og løbende læring
|
||
|
||
For at opfylde ønsket om at systemet “bliver klogere” af filer og brugerinput, skal vi tænke databaser og videnslagring ind i designet. Her giver det mening med to typer databaser:
|
||
|
||
(A) Vidensdatabase (statiske data):
|
||
Denne database indeholder de statiske oplysninger som AI’en skal bruge – især erfaringspriser, enhedspriser, standard tekster og regler. Det kan implementeres på to måder (eller en kombination):
|
||
|
||
Struktureret database: En traditionel relationsdatabase (fx PostgreSQL/MySQL) med tabeller for priser. Fx en tabel priser med kolonner: opgave, enhed, pris_per_enhed. Her kunne der være en række for “klikgulv lægning – pris 110 kr/m2” osv.
|
||
byggetilbud.dk
|
||
. Backenden kan lave SQL-opslag baseret på brugerens valg (hvis opgaven matcher en række) og trække prisen. Struktureret data er nem at vedligeholde (man kan have et admin-interface eller uploade CSV-filer for at opdatere priser). Når nye “erfaringspriser” kommer til, kan de tilføjes her.
|
||
|
||
Ustruktureret vidensbase med embeddings: Alternativt eller supplerende kan man lagre viden i tekstform og bruge AI’en til at hente den relevante information. Her kommer vektordatabase og embeddings i spil. Idéen er at konvertere tekststykker (dokumenter, prisnoter, tidligere tilbud) til matematiske vektorer og gemme dem i en speciel database, hvor man kan søge efter lignende betydninger. Når brugeren så beskriver en opgave, konverteres denne beskrivelse også til en vektor, og man finder de nærmeste matches i vidensbasen
|
||
reddit.com
|
||
. På den måde kan systemet automatisk finde fx tidligere tilbud på lignende opgaver eller relevante prislister, og indsætte de oplysninger i prompten til GPT. Dette kaldes Retrieval-Augmented Generation (RAG) – modellen får ekstra kontekst fra sin “hukommelse” uden at man manuelt skal feede al viden hver gang. Fordelen er, at AI’en kan have adgang til meget mere data end der kan proppes direkte i dens prompt, fordi det først finder de relevante bidder og kun giver dem videre. Som en ekspert udtrykker det: denne metode lader GPT håndtere store vidensmængder ved lynhurtigt at hente relevante informationer frem, i stedet for at vi skal finetune eller paste alt ind hver gang
|
||
reddit.com
|
||
. Det gør det muligt at holde AI’ens viden opdateret om f.eks. nyeste materialer eller priser i branchen
|
||
myscale.com
|
||
.
|
||
|
||
Figur: Sådan kan en vektor-database udvide AI’ens hukommelse. Tekstdokumenter omdannes til embeddings (numeriske vektorer) og lagres. Når brugeren stiller et spørgsmål, laves også en embedding af dette, som sammenlignes med databasen for at finde matchende vidensbidder. De mest relevante tekststykker hentes og tilføjes som kontekst i prompten til AI-modellen
|
||
reddit.com
|
||
. Dette holder AI’ens svar faktabaserede og opdaterede.
|
||
|
||
I praksis kan man starte med en simpel struktureret database (det dækker meget). Hvis du senere vil implementere en egentlig vektor-database, findes der tjenester som Pinecone, Weaviate eller open source løsninger (f.eks. PostgreSQL med pgvector). Fordelen er, at du kan fodre systemet med hele dokumenter (filer med beskrivelser, tidligere tilbud, manualer) og den vil kunne “slå dem op” semantisk. Som MyScale-teamet beskriver, kan en vektorbaseret vidensbank give LLM’en adgang til domænespecifik, opdateret information og dermed forbedre nøjagtigheden af svarene markant
|
||
myscale.com
|
||
.
|
||
|
||
(B) Bruger/indsigts-database:
|
||
Den anden database er til selve applikationen og læring fra brugerinput. Her gemmes fx:
|
||
|
||
Historik af genererede tilbud – Hvad blev forespurgt og hvad svarede AI? Dette er nyttigt for revision (og debugging, hvis AI’en gav et dårligt svar). Man kan gemme prompt, svar, tidspunkt, bruger etc.
|
||
|
||
Kundedata – Hvis din tilbudsberegner skal bruges af flere kunder via en hjemmeside, kan du gemme kontaktoplysninger, så tilbuddet evt. kan tilpasses kunden (eller så kunden kan få tilsendt tilbuddet på email automatisk).
|
||
|
||
Feedback – Som nævnt under punkt 3 kan man registrere hvis en bruger ændrer noget i AI’ens forslag eller markerer et tilbud som acceptabelt/ikke acceptabelt. Disse metadata kan hjælpe med at træne systemet. For eksempel, hvis du på sigt vil fine-tune din sprogmodel til jeres firma, kan du bruge de akkumulerede tilbud som træningsdata (med passende format). Fine-tuning betyder, at man opdaterer modellens vægte med nye eksempler, så den mere præcist rammer firmaets tone og priser uden at skulle have alt som prompt hver gang
|
||
medium.com
|
||
. Men fine-tuning er bedst hvis man har nok data og behov for meget specialiserede outputs
|
||
medium.com
|
||
. Ofte vil en embeddings-baseret tilgang række, især fordi det er mere enkelt at vedligeholde løbende (du opdaterer bare din database, frem for at gen-træne hele AI-modellen).
|
||
|
||
Hvordan “bliver systemet klogere”?
|
||
Med ovenstående to databaser får vi en løsning hvor begge dele af systemet lærer:
|
||
|
||
Vidensdatabasen kan løbende udvides. Hver gang I har nye priser eller erfaringer (fx “opsætning af gipsvæg koster nu X kr/m²”), tilføjes det. AI’en vil herefter kunne tage højde for det i sine beregninger – enten fordi backenden fodrer de nye tal ind i prompten, eller fordi de nye tekster ligger i vektor-indexet og bliver fundet når relevant. Modellen trænes ikke i traditionel forstand, men den har adgang til mere og mere relevant info, hvilket øger kvaliteten af svarene over tid. Dette adresserer det klassiske problem at sprogmodeller ellers kun støtter sig til en statisk træningsdata og kan være uopdaterede
|
||
myscale.com
|
||
.
|
||
|
||
Samtidig kan I analysere data fra bruger/tilbuds-databasen for at justere systemet manuelt: fx hvis I ser at AI konsekvent overestimerer visse opgaver ift. konkurrenternes priser, kan I kalibrere ved at ændre prisdatabasen eller promptinstrukser (”hvis totalpris overstiger X, gør Y”). Denne menneskelige i loop justering er vigtig, især i starten mens I indsamler erfaringer med systemet.
|
||
|
||
Af sikkerhedsgrunde og kvalitetssikring bør AI’ens forslag altid gennemgås, især i begyndelsen. Men med en veludbygget vidensbase og klare promptinstruktioner vil systemet gradvist levere mere spot-on tilbud. Husk også at overholde GDPR hvis I gemmer kundeoplysninger i databasen, og generelt sikre jeres API-nøgler og data (backenden bør f.eks. validere input for at undgå misbrug).
|
||
|
||
Opsummering: I får mest ud af tilbudsberegneren ved at kombinere præcise data (fra jeres erfaringer) med AI’ens sproglige evner. Ved at have en robust data-backend og en enkel frontend kan AI’en automatisere tilbudsgivning på en måde, der sparer tid og giver kunderne hurtige, detaljerede tilbud. Teknisk set er React + Node/Flask en overskuelig vej, og integrationen med OpenAI API er relativt lige til. Den største investering er at forberede jeres vidensgrundlag – men når det først er på plads (om det så er via tabeller eller tekster), vil AI’en kunne skalere jeres tilbudsgivning og blive klogere jo mere den bruges. Med andre ord: start med det simple (få grunddata ind, lav en MVP), og udbyg gradvist med de avancerede funktioner som chat-dialog, læring af feedback, mm., efterhånden som I ser behovet. God fornøjelse med byggeriet af jeres AI-tilbudsberegner! 🚀
|
||
|
||
Kilder: De anvendte kilder inkluderer eksempler på prislister fra tømrerbranchen
|
||
byggetilbud.dk
|
||
byggetilbud.dk
|
||
, branchevejledninger om tilbudsberegning
|
||
getjobber.com
|
||
buildern.com
|
||
samt tekniske artikler om integration af sprogmodeller med vidensdatabaser
|
||
reddit.com
|
||
myscale.com
|
||
. Disse underbygger anbefalingerne for opbygning af systemet og vigtigheden af at kombinere korrekte prisdata med AI for at få præcise og troværdige tilbud. |