- Score max changed from 100 to 60, rating max from 10 to 6 - Note scoring now awards actual rating points (delta-based) - Re-scoring only awards/deducts the difference - Rating history shows note_score entries with problem link - LLM prompt includes problem statement context for better evaluation - LLM scoring dimensions: 题意理解/思路算法/代码记录/踩坑反思 (15 each) - Minecraft-themed UI: 矿石鉴定, 探索笔记, 存入宝典, etc. - Fallback scoring adjusted for 60-point scale - Handle LLM markdown code fence wrapping in response Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
254 行
11 KiB
C++
254 行
11 KiB
C++
#include "csp/services/wrong_book_service.h"
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#include <chrono>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
namespace csp::services {
|
|
|
|
namespace {
|
|
|
|
void CheckSqlite(int rc, sqlite3* db, const char* what) {
|
|
if (rc == SQLITE_OK || rc == SQLITE_ROW || rc == SQLITE_DONE) return;
|
|
throw std::runtime_error(std::string(what) + ": " + sqlite3_errmsg(db));
|
|
}
|
|
|
|
int64_t NowSec() {
|
|
using namespace std::chrono;
|
|
return duration_cast<std::chrono::seconds>(
|
|
std::chrono::system_clock::now().time_since_epoch())
|
|
.count();
|
|
}
|
|
|
|
std::string ColText(sqlite3_stmt* stmt, int col) {
|
|
const unsigned char* txt = sqlite3_column_text(stmt, col);
|
|
return txt ? reinterpret_cast<const char*>(txt) : std::string();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::vector<WrongBookEntry> WrongBookService::ListByUser(int64_t user_id) {
|
|
sqlite3* db = db_.raw();
|
|
sqlite3_stmt* stmt = nullptr;
|
|
const char* sql =
|
|
"SELECT w.user_id,w.problem_id,w.last_submission_id,w.note,w.note_score,w.note_rating,w.note_feedback_md,w.note_images_json,w.note_scored_at,w.updated_at,p.title "
|
|
"FROM wrong_book w "
|
|
"JOIN problems p ON p.id=w.problem_id "
|
|
"WHERE w.user_id=? ORDER BY w.updated_at DESC";
|
|
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
|
|
"prepare wrong_book list");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
|
|
|
|
std::vector<WrongBookEntry> out;
|
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
WrongBookEntry e;
|
|
e.item.user_id = sqlite3_column_int64(stmt, 0);
|
|
e.item.problem_id = sqlite3_column_int64(stmt, 1);
|
|
if (sqlite3_column_type(stmt, 2) == SQLITE_NULL) {
|
|
e.item.last_submission_id = std::nullopt;
|
|
} else {
|
|
e.item.last_submission_id = sqlite3_column_int64(stmt, 2);
|
|
}
|
|
e.item.note = ColText(stmt, 3);
|
|
e.item.note_score = sqlite3_column_int(stmt, 4);
|
|
e.item.note_rating = sqlite3_column_int(stmt, 5);
|
|
e.item.note_feedback_md = ColText(stmt, 6);
|
|
e.item.note_images_json = ColText(stmt, 7);
|
|
e.item.note_scored_at = sqlite3_column_int64(stmt, 8);
|
|
e.item.updated_at = sqlite3_column_int64(stmt, 9);
|
|
e.problem_title = ColText(stmt, 10);
|
|
out.push_back(std::move(e));
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
return out;
|
|
}
|
|
|
|
void WrongBookService::UpsertNote(int64_t user_id,
|
|
int64_t problem_id,
|
|
const std::string& note) {
|
|
sqlite3* db = db_.raw();
|
|
sqlite3_stmt* stmt = nullptr;
|
|
const char* sql =
|
|
"INSERT INTO wrong_book(user_id,problem_id,last_submission_id,note,updated_at) "
|
|
"VALUES(?,?,?,?,?) "
|
|
"ON CONFLICT(user_id,problem_id) DO UPDATE SET note=excluded.note,updated_at=excluded.updated_at";
|
|
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
|
|
"prepare wrong_book upsert note");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
|
|
CheckSqlite(sqlite3_bind_null(stmt, 3), db, "bind last_submission_id");
|
|
CheckSqlite(sqlite3_bind_text(stmt, 4, note.c_str(), -1, SQLITE_TRANSIENT), db,
|
|
"bind note");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 5, NowSec()), db, "bind updated_at");
|
|
CheckSqlite(sqlite3_step(stmt), db, "wrong_book upsert note");
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
void WrongBookService::UpsertNoteScore(int64_t user_id,
|
|
int64_t problem_id,
|
|
int32_t note_score,
|
|
int32_t note_rating,
|
|
const std::string& note_feedback_md) {
|
|
sqlite3* db = db_.raw();
|
|
sqlite3_stmt* stmt = nullptr;
|
|
const char* sql =
|
|
"INSERT INTO wrong_book(user_id,problem_id,last_submission_id,note,updated_at,note_score,note_rating,note_feedback_md,note_scored_at) "
|
|
"VALUES(?,?,?,?,?,?,?,?,?) "
|
|
"ON CONFLICT(user_id,problem_id) DO UPDATE SET "
|
|
"note_score=excluded.note_score,"
|
|
"note_rating=excluded.note_rating,"
|
|
"note_feedback_md=excluded.note_feedback_md,"
|
|
"note_scored_at=excluded.note_scored_at,"
|
|
"updated_at=excluded.updated_at";
|
|
|
|
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
|
|
"prepare wrong_book upsert note score");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
|
|
CheckSqlite(sqlite3_bind_null(stmt, 3), db, "bind last_submission_id");
|
|
CheckSqlite(sqlite3_bind_text(stmt, 4, "", -1, SQLITE_TRANSIENT), db, "bind note");
|
|
const int64_t now = NowSec();
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 5, now), db, "bind updated_at");
|
|
CheckSqlite(sqlite3_bind_int(stmt, 6, note_score), db, "bind note_score");
|
|
CheckSqlite(sqlite3_bind_int(stmt, 7, note_rating), db, "bind note_rating");
|
|
CheckSqlite(sqlite3_bind_text(stmt, 8, note_feedback_md.c_str(), -1, SQLITE_TRANSIENT), db,
|
|
"bind note_feedback_md");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 9, now), db, "bind note_scored_at");
|
|
CheckSqlite(sqlite3_step(stmt), db, "wrong_book upsert note score");
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
|
|
void WrongBookService::UpsertBySubmission(int64_t user_id,
|
|
int64_t problem_id,
|
|
int64_t submission_id,
|
|
const std::string& note) {
|
|
sqlite3* db = db_.raw();
|
|
sqlite3_stmt* stmt = nullptr;
|
|
const char* sql =
|
|
"INSERT INTO wrong_book(user_id,problem_id,last_submission_id,note,updated_at) "
|
|
"VALUES(?,?,?,?,?) "
|
|
"ON CONFLICT(user_id,problem_id) DO UPDATE SET "
|
|
"last_submission_id=excluded.last_submission_id,"
|
|
"updated_at=excluded.updated_at,"
|
|
"note=CASE WHEN wrong_book.note='' THEN excluded.note ELSE wrong_book.note END";
|
|
|
|
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
|
|
"prepare wrong_book upsert by submission");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 3, submission_id), db,
|
|
"bind submission_id");
|
|
CheckSqlite(sqlite3_bind_text(stmt, 4, note.c_str(), -1, SQLITE_TRANSIENT), db,
|
|
"bind note");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 5, NowSec()), db, "bind updated_at");
|
|
CheckSqlite(sqlite3_step(stmt), db, "wrong_book upsert by submission");
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
std::string WrongBookService::GetNoteImagesJson(int64_t user_id, int64_t problem_id) {
|
|
sqlite3* db = db_.raw();
|
|
sqlite3_stmt* stmt = nullptr;
|
|
const char* sql = "SELECT note_images_json FROM wrong_book WHERE user_id=? AND problem_id=? LIMIT 1";
|
|
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db, "prepare wrong_book get note_images_json");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
|
|
std::string out = "[]";
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
out = ColText(stmt, 0);
|
|
if (out.empty()) out = "[]";
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
return out;
|
|
}
|
|
|
|
void WrongBookService::SetNoteImagesJson(int64_t user_id, int64_t problem_id, const std::string& note_images_json) {
|
|
sqlite3* db = db_.raw();
|
|
sqlite3_stmt* stmt = nullptr;
|
|
const char* sql =
|
|
"INSERT INTO wrong_book(user_id,problem_id,last_submission_id,note,updated_at,note_images_json) "
|
|
"VALUES(?,?,?,?,?,?) "
|
|
"ON CONFLICT(user_id,problem_id) DO UPDATE SET note_images_json=excluded.note_images_json,updated_at=excluded.updated_at";
|
|
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db, "prepare wrong_book set note_images_json");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
|
|
CheckSqlite(sqlite3_bind_null(stmt, 3), db, "bind last_submission_id");
|
|
CheckSqlite(sqlite3_bind_text(stmt, 4, "", -1, SQLITE_TRANSIENT), db, "bind note");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 5, NowSec()), db, "bind updated_at");
|
|
CheckSqlite(sqlite3_bind_text(stmt, 6, note_images_json.c_str(), -1, SQLITE_TRANSIENT), db, "bind note_images_json");
|
|
CheckSqlite(sqlite3_step(stmt), db, "wrong_book set note_images_json");
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
|
|
void WrongBookService::Remove(int64_t user_id, int64_t problem_id) {
|
|
sqlite3* db = db_.raw();
|
|
sqlite3_stmt* stmt = nullptr;
|
|
const char* sql = "DELETE FROM wrong_book WHERE user_id=? AND problem_id=?";
|
|
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
|
|
"prepare wrong_book delete");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
|
|
CheckSqlite(sqlite3_step(stmt), db, "wrong_book delete");
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
int32_t WrongBookService::GetNoteRating(int64_t user_id, int64_t problem_id) {
|
|
sqlite3* db = db_.raw();
|
|
sqlite3_stmt* stmt = nullptr;
|
|
const char* sql = "SELECT note_rating FROM wrong_book WHERE user_id=? AND problem_id=? LIMIT 1";
|
|
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
|
|
"prepare get note_rating");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
|
|
int32_t rating = 0;
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
rating = sqlite3_column_int(stmt, 0);
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
return rating;
|
|
}
|
|
|
|
void WrongBookService::AwardNoteRating(int64_t user_id, int64_t problem_id, int delta) {
|
|
sqlite3* db = db_.raw();
|
|
|
|
// Update user rating
|
|
{
|
|
sqlite3_stmt* stmt = nullptr;
|
|
const char* sql = "UPDATE users SET rating=rating+? WHERE id=?";
|
|
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
|
|
"prepare add note rating");
|
|
CheckSqlite(sqlite3_bind_int(stmt, 1, delta), db, "bind delta");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 2, user_id), db, "bind user_id");
|
|
CheckSqlite(sqlite3_step(stmt), db, "exec add note rating");
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
// Log to daily_task_logs for rating history visibility
|
|
{
|
|
sqlite3_stmt* stmt = nullptr;
|
|
const char* sql =
|
|
"INSERT INTO daily_task_logs(user_id,task_code,day_key,reward,created_at) "
|
|
"VALUES(?,?,?,?,?)";
|
|
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
|
|
"prepare note rating log");
|
|
const std::string task_code = "note_score_" + std::to_string(problem_id);
|
|
const int64_t now = NowSec();
|
|
// Use a unique day_key to avoid IGNORE conflicts
|
|
const std::string day_key = "note_" + std::to_string(now);
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
|
|
CheckSqlite(sqlite3_bind_text(stmt, 2, task_code.c_str(), -1, SQLITE_TRANSIENT), db,
|
|
"bind task_code");
|
|
CheckSqlite(sqlite3_bind_text(stmt, 3, day_key.c_str(), -1, SQLITE_TRANSIENT), db,
|
|
"bind day_key");
|
|
CheckSqlite(sqlite3_bind_int(stmt, 4, delta), db, "bind reward");
|
|
CheckSqlite(sqlite3_bind_int64(stmt, 5, now), db, "bind created_at");
|
|
CheckSqlite(sqlite3_step(stmt), db, "insert note rating log");
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
}
|
|
|
|
} // namespace csp::services
|