-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from Ulas-Scan/development
Development
- Loading branch information
Showing
23 changed files
with
996 additions
and
209 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package constants | ||
|
||
const ( | ||
PROMPT_SUMMARIZE = `Tolong summarize product reviews di bawah ini maksimal 5 kalimat, dan gunakan Bahasa Indonesia: | ||
Output: | ||
{ | ||
"summary": summary | ||
} | ||
` | ||
|
||
PROMPT_ANALYZE = ` Harap tentukan jumlah komentar di bawah ini yang menggambarkan kondisi baik, buruk, atau tidak memberikan informasi yang cukup di setiap aspek, yaitu: | ||
1. packaging atau pengemasan, | ||
2. delivery atau pengiriman, | ||
3. respon penjual atau respon admin, | ||
4. product condition atau kondisi produk. | ||
Pastikan bahwa total jumlah komentar tidak melebihi 100, tidak ada yang tumpang tindih, dan setiap komentar hanya diklasifikasikan ke satu kategori. Hanya perlu menampilkan jumlah komentar tanpa rincian komentar aslinya: | ||
jumlah = (komentar positif / (komentar positif + komentar negatif)) * 100 | ||
jumlah dalam format float atau bilangan desimal. | ||
PENTING: Output harus berupa JSON yang menyimpan score dalam bentuk desimal atau float bukan string dengan format sebagai berikut: | ||
{ | ||
"packaging": float(jumlah), | ||
"delivery": float(jumlah), | ||
"admin_response": float(jumlah), | ||
"product_condition": float(jumlah) | ||
} | ||
Contoh Output: | ||
{ | ||
"packaging": 12.34, | ||
"delivery": 56.78, | ||
"admin_response": 91.01, | ||
"product_condition": 11.12 | ||
} | ||
` | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
package controller | ||
|
||
import ( | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
|
||
"ulascan-be/dto" | ||
"ulascan-be/service" | ||
"ulascan-be/utils" | ||
|
||
"github.com/gin-gonic/gin" | ||
"github.com/google/uuid" | ||
) | ||
|
||
type ( | ||
MLController interface { | ||
GetSentimentAnalysisAndSummarization(ctx *gin.Context) | ||
} | ||
|
||
mlController struct { | ||
tokopediaService service.TokopediaService | ||
modelService service.ModelService | ||
geminiService service.GeminiService | ||
historyService service.HistoryService | ||
} | ||
) | ||
|
||
func NewMLController( | ||
ts service.TokopediaService, | ||
ms service.ModelService, | ||
gs service.GeminiService, | ||
hs service.HistoryService, | ||
) MLController { | ||
return &mlController{ | ||
tokopediaService: ts, | ||
modelService: ms, | ||
geminiService: gs, | ||
historyService: hs, | ||
} | ||
} | ||
|
||
func (c *mlController) GetSentimentAnalysisAndSummarization(ctx *gin.Context) { | ||
productUrl := ctx.Query("product_url") | ||
if productUrl == "" { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_REVIEWS, dto.ErrProductUrlMissing.Error(), nil) | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, res) | ||
return | ||
} | ||
|
||
parsedUrl, err := url.Parse(productUrl) | ||
if err != nil { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_REVIEWS, err.Error(), nil) | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, res) | ||
return | ||
} | ||
|
||
pathParts := strings.Split(parsedUrl.Path, "/") | ||
if len(pathParts) < 3 { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_REVIEWS, dto.ErrProductUrlWrongFormat.Error(), nil) | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, res) | ||
return | ||
} | ||
|
||
productReq := dto.GetProductRequest{ | ||
ShopDomain: pathParts[1], | ||
ProductKey: pathParts[2], | ||
ProductUrl: "https://www.tokopedia.com/" + pathParts[1] + "/" + pathParts[2], | ||
} | ||
|
||
product, err := c.tokopediaService.GetProduct(ctx, productReq) | ||
if err != nil { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_PRODUCT_ID, err.Error(), nil) | ||
ctx.JSON(http.StatusBadRequest, res) | ||
return | ||
} | ||
|
||
shopAvatar, err := c.tokopediaService.GetShopAvatar(ctx, productReq.ShopDomain) | ||
if err != nil { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_SHOP_AVATAR, err.Error(), nil) | ||
ctx.JSON(http.StatusBadRequest, res) | ||
return | ||
} | ||
|
||
// fmt.Println("=== PRODUCT ID ===") | ||
// fmt.Println(product) | ||
|
||
reviewsReq := dto.GetReviewsRequest{ | ||
ProductUrl: productReq.ProductUrl, | ||
ProductId: product.ProductId, | ||
} | ||
|
||
reviews, err := c.tokopediaService.GetReviews(ctx, reviewsReq) | ||
if err != nil { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_REVIEWS, err.Error(), nil) | ||
ctx.JSON(http.StatusBadRequest, res) | ||
return | ||
} | ||
|
||
// fmt.Println("=== REVIEWS ===") | ||
// fmt.Println(reviews) | ||
|
||
// Assuming `reviews` is a slice of a struct with `Message` and `Rating` fields | ||
statements := make([]string, len(reviews)) | ||
ratingSum := 0.0 | ||
|
||
for i, review := range reviews { | ||
statements[i] = review.Message | ||
ratingSum += float64(review.Rating) | ||
} | ||
|
||
var ratingAvg float64 | ||
if len(reviews) > 0 { | ||
ratingAvg = ratingSum / float64(len(reviews)) | ||
} else { | ||
ratingAvg = 0.0 // or handle the case where there are no reviews | ||
} | ||
|
||
predictReq := dto.PredictRequest{ | ||
Statements: statements, | ||
} | ||
|
||
// fmt.Println("=== PREDICT REQ ===") | ||
// fmt.Println(predictReq) | ||
|
||
predictResult, err := c.modelService.Predict(ctx, predictReq) | ||
if err != nil { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_PREDICT, err.Error(), nil) | ||
ctx.JSON(http.StatusBadRequest, res) | ||
return | ||
} | ||
|
||
var builder strings.Builder | ||
for _, review := range reviews { | ||
builder.WriteString(review.Message) | ||
builder.WriteString("\n") | ||
} | ||
concatenatedMessage := builder.String() | ||
|
||
analyzeResult, err := c.geminiService.Analyze(ctx, concatenatedMessage) | ||
if err != nil { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_ANALYZE, err.Error(), nil) | ||
ctx.JSON(http.StatusBadRequest, res) | ||
return | ||
} | ||
|
||
summarizeResult, err := c.geminiService.Summarize(ctx, concatenatedMessage) | ||
if err != nil { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_ANALYZE, err.Error(), nil) | ||
ctx.JSON(http.StatusBadRequest, res) | ||
return | ||
} | ||
|
||
userID, exists := ctx.Get("user_id") | ||
if exists { | ||
userIDStr, ok := userID.(string) | ||
if !ok { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_HISTORY, "Invalid user ID type", nil) | ||
ctx.JSON(http.StatusInternalServerError, res) | ||
return | ||
} | ||
|
||
userUUID, err := uuid.Parse(userIDStr) | ||
if err != nil { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_HISTORY, "Invalid user ID format", nil) | ||
ctx.JSON(http.StatusInternalServerError, res) | ||
return | ||
} | ||
|
||
history := dto.HistoryCreateRequest{ | ||
UserID: userUUID, | ||
ProductID: product.ProductId, | ||
Rating: len(reviews), | ||
Ulasan: predictResult.CountNegative + predictResult.CountPositive, | ||
Bintang: ratingAvg, | ||
URL: productReq.ProductUrl, | ||
ProductName: product.ProductName, | ||
PositiveCount: predictResult.CountPositive, | ||
NegativeCount: predictResult.CountNegative, | ||
Packaging: analyzeResult.Packaging, | ||
Delivery: analyzeResult.Delivery, | ||
AdminResponse: analyzeResult.AdminResponse, | ||
ProductCondition: analyzeResult.ProductCondition, | ||
Content: summarizeResult, | ||
} | ||
_, err = c.historyService.CreateHistory(ctx, history) | ||
if err != nil { | ||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_HISTORY, err.Error(), nil) | ||
ctx.JSON(http.StatusInternalServerError, res) | ||
return | ||
} | ||
} | ||
|
||
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_REVIEWS, dto.MLResult{ | ||
ProductName: product.ProductName, | ||
ProductDescription: product.ProductDescription, | ||
Rating: len(reviews), | ||
Ulasan: predictResult.CountNegative + predictResult.CountPositive, | ||
Bintang: ratingAvg, | ||
ImageUrls: product.ImageUrls, | ||
ShopName: product.ShopName, | ||
ShopAvatar: shopAvatar, | ||
CountNegative: predictResult.CountNegative, | ||
CountPositive: predictResult.CountPositive, | ||
Packaging: analyzeResult.Packaging, | ||
Delivery: analyzeResult.Delivery, | ||
AdminResponse: analyzeResult.AdminResponse, | ||
ProductCondition: analyzeResult.ProductCondition, | ||
Summary: summarizeResult, | ||
}) | ||
ctx.JSON(http.StatusOK, res) | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package dto | ||
|
||
import "errors" | ||
|
||
var ( | ||
ErrCreateHttpRequest = errors.New("failed to create http request") | ||
ErrSendsHttpRequest = errors.New("failed to sends http request") | ||
ErrReadHttpResponseBody = errors.New("failed to read http response body") | ||
ErrParseJson = errors.New("failed to parse response json") | ||
ErrNotOk = errors.New("received non-200 response code") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package dto | ||
|
||
const ( | ||
// Failed | ||
MESSAGE_FAILED_ANALYZE = "failed analyze" | ||
|
||
// Success | ||
// MESSAGE_SUCCESS_PREDICT = "success predict" | ||
) | ||
|
||
var ( | ||
// ErrMarshallJson = errors.New("failed to marshall request body json") | ||
) | ||
|
||
type AnalyzeResponse struct { | ||
Packaging float32 `json:"packaging"` | ||
Delivery float32 `json:"delivery"` | ||
AdminResponse float32 `json:"admin_response"` | ||
ProductCondition float32 `json:"product_condition"` | ||
} |
Oops, something went wrong.