Internal Work Tool · Pricing, Matching, Personalized KakaoTalk
Code computes the amounts; the LLM only returns candidates and the fields to change
Pricing lived in Google Sheets formulas, and every proposal was reconciled by hand. When I moved those formulas into code, I added tests that compare each field against the sheet values for real products. In catalog product extraction and sales partner matching, the LLM only produces candidates. Registering and reviewing them is the staff's job. For personalized KakaoTalk, a CRM built by a colleague decides each seller's recommendations; I built the send automation that takes those targets and sends.
- I defined which fields the LLM may change. If the LLM back-calculates a request like "set seller earnings to 1000", other amounts shift too. So the 6 computed values, such as supply price and earnings, are blocked from the edit list. For such requests, it only points to the input values that need to change. If asked to change only the margin rate, it does not return the sale price.
- I ported the sheet formulas down to the rounding units. Partner supply price rounds up, and the fee rate is fixed in code. I added tests that compare each field for 2 real products in the sheet (a sample product at 30,000 won, among others). The tests also check two properties: changing the split ratio leaves total profit unchanged, and the two shares always sum to total profit.
- It looks for the original photo inside the PDF first. Pages go to the LLM one at a time, and it returns photo coordinates. If those coordinates overlap an original image embedded in the PDF with IoU (the ratio of the overlapping area of two boxes) above 0.5, the original is used. Backgrounds covering more than 80% of the page are dropped from the candidates, and the page is cropped only when no matching original exists. For products that appear on several pages, empty price fields are filled from each other.
- Excel still yields results when the LLM fails. If LLM parsing returns 0 items, the UI does not show an empty screen; a rule-based parser re-reads the same file. Images inside the file are attached to products in order. Icons under 750 bytes are not counted as product photos and are discarded.
- RAG can only add candidates, not create new ones. Rule scores are assigned by category, group-buy history, product requests, and proposals, in that order, and the results of a 4-sentence RAG query are added as a union. IDs returned by RAG are accepted only if they are in the existing partner pool. This logic used to live in n8n; I moved it into code so it could be tested with vitest.
- Automatic sends and UI sends use the same rules. The server assembler loads the same product-selection and template modules as the UI, so the same person never gets different products. A CRM lookup failure and 0 results are handled separately. Only on 0 results does it fill in popular products. The new-seller card is built only once. On 09-22, when a card was built for each person, the renderer threw 980 500 errors.
- Scheduled sends go out a little every minute, without overlapping. Per tick, it assembles up to 3 targets that get personalized recommendations and up to 30 without them. If 5 or more queued items have not gone out, that tick is skipped. Once the send window passes, nothing new starts, so sends do not pile up at night. To keep ticks from overlapping, each schedule has a lock row, and it can only be released with the token left when it was acquired.
- One account runs on only one PC. Checking the install token and marking it used are combined into a single conditional UPDATE, so two PCs cannot register with the same token at the same time. If the same Instagram account is on two PCs, the one that logged in more recently wins. The server's job-assignment function follows the same rule. The daily action volume is redrawn within a range every morning.
- 43 price-calculation test cases