Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f53fa27
setting linterts #12
TToJlkoBHuK Mar 21, 2026
497784a
setting Cmakefile + test #12
TToJlkoBHuK Mar 21, 2026
712a386
add struct current and update card and calc #13
TToJlkoBHuK Mar 21, 2026
dfe6d79
realiazation algorithm #14
TToJlkoBHuK Mar 21, 2026
6d9beee
test algorithm #15
TToJlkoBHuK Mar 21, 2026
527be6c
CI C code #16
TToJlkoBHuK Mar 21, 2026
54d4976
udate c-linter #16
TToJlkoBHuK Mar 21, 2026
e528e1c
update CI #16
TToJlkoBHuK Mar 21, 2026
70fd9ce
update CI #16
TToJlkoBHuK Mar 21, 2026
610cb8e
update CI #16
TToJlkoBHuK Mar 21, 2026
7c8bd8c
delete C-linter #16
TToJlkoBHuK Mar 21, 2026
8106da0
update lint.yml #16
TToJlkoBHuK Mar 21, 2026
063b61d
linter go #21
TToJlkoBHuK Mar 21, 2026
c5134ea
временное отключение линтера Go #21
TToJlkoBHuK Mar 21, 2026
032b264
временное отключение Go #21
TToJlkoBHuK Mar 21, 2026
a1113ac
feat(algorithm): implement SM-2 spaced repetition core logic
ilindan-dev Mar 31, 2026
3bd6cda
test(algorithm): setup Google Test framework and add test suite
ilindan-dev Mar 31, 2026
b05da82
style(algorithm): apply strict clang-format and clang-tidy rules
ilindan-dev Mar 31, 2026
5705bea
ci: configure GitHub Actions for C/C++ linting
ilindan-dev Mar 31, 2026
97e274b
ci: configure Go and Python linting pipelines
ilindan-dev Mar 31, 2026
ee683ef
ci: fix go version for linter and exclude build dir from clang-format
ilindan-dev Mar 31, 2026
378c4a3
ci: fix go version for linter to 1.24
ilindan-dev Mar 31, 2026
021d6c3
ci: add test integration for C code
ilindan-dev Mar 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/lint-c.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: C/C++ CI

on:
pull_request:
branches: [ "dev", "main" ]
paths:
- 'algorithm/**'

jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install clang-tools and cmake
run: sudo apt-get update && sudo apt-get install -y clang-format clang-tidy cmake

- name: Configure CMake and generate compile_commands.json
run: cmake -B ./algorithm/build -S ./algorithm

- name: Check C/C++ formatting (clang-format)
run: |
find ./algorithm/src ./algorithm/include ./algorithm/tests -type f \( -name "*.c" -o -name "*.cpp" -o -name "*.h" \) | xargs clang-format --Werror --dry-run

- name: Run C/C++ linter (clang-tidy)
run: |
find ./algorithm/src ./algorithm/tests -type f \( -name "*.c" -o -name "*.cpp" \) | xargs clang-tidy -p ./algorithm/build

- name: Build C/C++ project
run: cmake --build ./algorithm/build

- name: Run tests (CTest)
working-directory: ./algorithm/build
run: ctest --output-on-failure
23 changes: 23 additions & 0 deletions .github/workflows/lint-go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Go Lint

on:
pull_request:
branches: [ "dev", "main" ]
paths:
- 'backend/**'

jobs:
lint-go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v6
with:
go-version: '1.24'
check-latest: true

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v6
with:
working-directory: ./backend
21 changes: 21 additions & 0 deletions .github/workflows/lint-python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Python Lint

on:
pull_request:
branches: [ "dev", "main" ]
paths:
- 'parser/**'

jobs:
lint-python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Ruff
run: pip install ruff

- name: Run Ruff check and format
run: |
ruff check ./parser
ruff format --check ./parser
43 changes: 0 additions & 43 deletions .github/workflows/lint.yml

This file was deleted.

4 changes: 1 addition & 3 deletions algorithm/.clang-format
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
---
Language: Cpp
BasedOnStyle: Google

Expand All @@ -9,5 +8,4 @@ UseTab: Never

PointerAlignment: Left

SortIncludes: true
---
SortIncludes: true
10 changes: 4 additions & 6 deletions algorithm/.clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@
Checks: >
-*,
bugprone-*,
cert-*,
clang-analyzer-*,
misc-*,
performance-*,
portability-*,
readability-*,
-readability-identifier-length,
-readability-magic-numbers,
-bugprone-easily-swappable-parameters

WarningsAsErrors: '*'

HeaderFilterRegex: '.*'

CheckOptions:
- key: readability-braces-around-statements.ShortStatementLines
value: '1'
---
readability-braces-around-statements.ShortStatementLines: 1
...
41 changes: 36 additions & 5 deletions algorithm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.14)

project(SpacedRepetition C)
project(SpacedRepetition C CXX)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_C_STANDARD_REQUIRED True)
set(CMAKE_CXX_STANDARD_REQUIRED True)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -Werror")
endif()

include_directories(include)

add_library(spaced_rep STATIC
src/algorithm.c
)
add_library(spaced_rep STATIC src/algorithm.c)

include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
DOWNLOAD_EXTRACT_TIMESTAMP true
)

set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_algorithm.cpp")
add_executable(test_algorithm tests/test_algorithm.cpp)

target_link_libraries(test_algorithm
PRIVATE
spaced_rep
GTest::gtest_main
m
)

include(GoogleTest)
include(CTest)
gtest_discover_tests(test_algorithm)
endif()
42 changes: 42 additions & 0 deletions algorithm/include/algorithm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#ifndef ALGORITHM_H
#define ALGORITHM_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Current state of the flashcard (data from DB)
*/
typedef struct {
int64_t interval; /**< Current interval until the next review (in days) */
float easiness; /**< Easiness Factor (usually >= 1.3) */
int64_t repetitions; /**< Number of consecutive correct answers */
} CardState;

/**
* @brief Result of the spaced repetition calculation to be saved in the DB
*/
typedef struct {
int64_t next_interval; /**< Newly calculated interval (in days) */
float new_easiness; /**< Updated Easiness Factor */
int64_t repetitions; /**< Updated repetitions counter */
int32_t quality; /**< Answer quality grade (from 0 to 5) */
} ReviewResult;

/**
* @brief Calculates the next review parameters for a card based on similarity percentage.
* @param similarity_percent Cosine similarity percentage of the user's answer vs the reference (0.0
* - 100.0).
* @param current_state Struct containing the current interval, easiness, and repetitions.
* @return ReviewResult Updated data ready to be saved in the database.
*/
ReviewResult calculate_next_review(float similarity_percent, CardState current_state);

#ifdef __cplusplus
}
#endif

#endif
75 changes: 75 additions & 0 deletions algorithm/src/algorithm.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include "algorithm.h"

#include <math.h>

// --- SM-2 Algorithm Constants ---

// Thresholds for converting cosine similarity (0.0 - 100.0) to a quality grade (0 - 5)
static const float THRESHOLD_GRADE_1 = 20.0F;
static const float THRESHOLD_GRADE_2 = 40.0F;
static const float THRESHOLD_GRADE_3 = 60.0F;
static const float THRESHOLD_GRADE_4 = 80.0F;
static const float THRESHOLD_GRADE_5 = 95.0F;

// Base constraints and intervals (in days)
static const float MIN_EASINESS_FACTOR = 1.3F;
static const int64_t INTERVAL_FIRST_STEP = 1;
static const int64_t INTERVAL_SECOND_STEP = 6;

/**
* @brief Helper function: converts similarity percentage into a discrete grade from 0 to 5.
* Hidden from other translation units via the static modifier.
*/
static int32_t determine_quality_grade(const float similarity_percent) {
if (similarity_percent < THRESHOLD_GRADE_1)
return 0; // Complete blackout / completely incorrect
if (similarity_percent < THRESHOLD_GRADE_2)
return 1; // Incorrect, but remembered the correct answer
if (similarity_percent < THRESHOLD_GRADE_3)
return 2; // Incorrect, but the answer seemed familiar
if (similarity_percent < THRESHOLD_GRADE_4)
return 3; // Correct, but with significant difficulty
if (similarity_percent < THRESHOLD_GRADE_5) return 4; // Correct, after a slight hesitation
return 5; // Perfect and quick response
}

ReviewResult calculate_next_review(const float similarity_percent, const CardState current_state) {
ReviewResult result;

// Determine response quality
result.quality = determine_quality_grade(similarity_percent);

// Calculate new Easiness Factor (EF)
// Original SM-2 formula: EF' = EF + (0.1 - (5 - q) * (0.08 + (5 - q) * 0.02))
const float q_diff = 5.0F - (float)result.quality;
const float ef_modifier = 0.1F - (q_diff * (0.08F + (q_diff * 0.02F)));

result.new_easiness = current_state.easiness + ef_modifier;

// EF must not drop below the minimum threshold
if (result.new_easiness < MIN_EASINESS_FACTOR) {
result.new_easiness = MIN_EASINESS_FACTOR;
}

// Calculate repetitions counter and next interval
if (result.quality < 3) {
// Grades 0, 1, or 2 indicate forgetting. Reset progress.
result.repetitions = 0;
result.next_interval = INTERVAL_FIRST_STEP;
} else {
// Successful review (grades 3, 4, 5)
result.repetitions = current_state.repetitions + 1;

if (result.repetitions == 1) {
result.next_interval = INTERVAL_FIRST_STEP;
} else if (result.repetitions == 2) {
result.next_interval = INTERVAL_SECOND_STEP;
} else {
// Mathematically correct rounding instead of the (int)(... + 0.5F) hack
const float next_interval_calc = (float)current_state.interval * result.new_easiness;
result.next_interval = (int64_t)roundf(next_interval_calc);
}
}

return result;
}
9 changes: 9 additions & 0 deletions algorithm/tests/.clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
InheritParentConfig: true

Checks: >
-misra-*,
-cert-err58-cpp,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-type-vararg
...ы
Loading
Loading