7#include "../../libs/json.hpp"
14using json = nlohmann::json;
49 std::srand(std::time(
nullptr));
50 int random = std::rand() % 10000;
51 std::string uuid = std::to_string(std::time(
nullptr)) +
"-" + std::to_string(random);
53 std::string configFile = tempDir +
"\\chainpage-" + uuid +
".json";
55 std::string configFile = tempDir +
"/chainpage-" + uuid +
".json";
64 std::ofstream file(configFile);
65 if (!file.is_open()) {
74 }
catch (
const std::exception& e) {
80 const std::string& subjectID)
const {
81 std::ostringstream cmd;
84 cmd <<
"pebl2 " << studyPath <<
"/tests/" <<
testName <<
"/" <<
testName <<
".pbl";
87 cmd <<
" -s " << subjectID;
119 : mParticipantCounter(1001), mUploadEnabled(false),
120 mLSLEnabled(false), mLSLStreamName(
"PEBL_{test}")
128 auto chain = std::make_shared<Chain>();
129 chain->mFilePath = path;
131 if (!chain->LoadFromJSON(path)) {
139 return SaveToJSON(mFilePath);
143 const std::string& name,
144 const std::string& description) {
145 auto chain = std::make_shared<Chain>();
146 chain->mFilePath = path;
148 chain->mDescription = description;
154 mItems.push_back(item);
158 if (index < 0 || index >
static_cast<int>(mItems.size())) {
162 mItems.insert(mItems.begin() + index, item);
166 if (index < 0 || index >=
static_cast<int>(mItems.size())) {
170 mItems.erase(mItems.begin() + index);
175 if (fromIndex < 0 || fromIndex >=
static_cast<int>(mItems.size())) {
179 if (toIndex < 0 || toIndex >=
static_cast<int>(mItems.size())) {
183 if (fromIndex == toIndex) {
191 mItems.erase(mItems.begin() + fromIndex);
194 if (toIndex > fromIndex) {
199 mItems.insert(mItems.begin() + toIndex, item);
205 if (index < 0 || index >=
static_cast<int>(mItems.size())) {
209 return &mItems[index];
213 if (index < 0 || index >=
static_cast<int>(mItems.size())) {
217 return &mItems[index];
225 result.
errors.push_back(
"Chain name is required");
228 if (mFilePath.empty()) {
229 result.
warnings.push_back(
"Chain file path not set");
233 if (mItems.empty()) {
234 result.
warnings.push_back(
"Chain has no items");
237 for (
size_t i = 0; i < mItems.size(); i++) {
239 std::string prefix =
"Item " + std::to_string(i + 1) +
": ";
243 if (item.
title.empty()) {
244 result.
warnings.push_back(prefix +
"Page has no title");
248 result.
warnings.push_back(prefix +
"Page has no content");
254 result.
errors.push_back(prefix +
"Test name is required");
258 if (study !=
nullptr) {
260 if (test ==
nullptr) {
261 result.
errors.push_back(prefix +
"Test not found in study: " + item.
testName);
265 result.
warnings.push_back(prefix +
"Test is marked as not included: " + item.
testName);
271 if (variant ==
nullptr) {
272 result.
errors.push_back(prefix +
"Parameter variant not found: " + item.
paramVariant);
279 bool langFound =
false;
280 for (
const auto& lang : availableLangs) {
286 if (!langFound && !availableLangs.empty()) {
287 result.
warnings.push_back(prefix +
"Language not found in translations: " + item.
language);
298bool Chain::LoadFromJSON(
const std::string& jsonPath) {
300 std::ifstream file(jsonPath);
301 if (!file.is_open()) {
309 mName = j.value(
"chain_name",
"");
310 mDescription = j.value(
"description",
"");
311 mParticipantCounter = j.value(
"participant_counter", 1001);
312 mUploadEnabled = j.value(
"upload_enabled",
false);
313 mLSLEnabled = j.value(
"lsl_enabled",
false);
314 mLSLStreamName = j.value(
"lsl_stream_name",
"PEBL_{test}");
318 if (j.contains(
"items") && j[
"items"].is_array()) {
319 for (
const auto& itemJson : j[
"items"]) {
320 std::string itemTypeStr = itemJson.value(
"item_type",
"instruction");
325 if (item.IsPageItem()) {
327 item.title = (itemJson.contains(
"title") && !itemJson[
"title"].is_null())
328 ? itemJson[
"title"].get<std::string>() :
"";
329 item.content = (itemJson.contains(
"content") && !itemJson[
"content"].is_null())
330 ? itemJson[
"content"].get<std::string>() :
"";
334 item.testName = (itemJson.contains(
"test_name") && !itemJson[
"test_name"].is_null())
335 ? itemJson[
"test_name"].get<std::string>() :
"";
336 item.paramVariant = (itemJson.contains(
"param_variant") && !itemJson[
"param_variant"].is_null())
337 ? itemJson[
"param_variant"].get<std::string>() :
"default";
338 item.language = (itemJson.contains(
"language") && !itemJson[
"language"].is_null())
339 ? itemJson[
"language"].get<std::string>() :
"";
341 if (itemJson.contains(
"random_group") && !itemJson[
"random_group"].is_null()) {
342 item.randomGroup = itemJson[
"random_group"].get<
int>();
343 }
else if (itemJson.contains(
"randomization_group") && !itemJson[
"randomization_group"].is_null()) {
344 item.randomGroup = itemJson[
"randomization_group"].get<
int>();
346 item.randomGroup = 0;
350 mItems.push_back(item);
356 }
catch (
const std::exception& e) {
361bool Chain::SaveToJSON(
const std::string& jsonPath) {
366 j[
"chain_name"] = mName;
367 j[
"description"] = mDescription;
368 j[
"participant_counter"] = mParticipantCounter;
369 j[
"upload_enabled"] = mUploadEnabled;
370 j[
"lsl_enabled"] = mLSLEnabled;
371 j[
"lsl_stream_name"] = mLSLStreamName;
374 json itemsArray = json::array();
375 for (
const auto& item : mItems) {
379 if (item.IsPageItem()) {
381 itemJson[
"title"] = item.title;
382 itemJson[
"content"] = item.content;
386 itemJson[
"test_name"] = item.testName;
387 itemJson[
"param_variant"] = item.paramVariant;
388 itemJson[
"language"] = item.language;
389 itemJson[
"random_group"] = item.randomGroup;
392 itemsArray.push_back(itemJson);
394 j[
"items"] = itemsArray;
397 std::ofstream file(jsonPath);
398 if (!file.is_open()) {
407 }
catch (
const std::exception& e) {
413 mParticipantCounter++;
std::string ItemTypeToString(ItemType type)
ItemType StringToItemType(const std::string &str)
std::string ItemTypeToString(ItemType type)
ItemType StringToItemType(const std::string &str)
void AddItem(const ChainItem &item)
ValidationResult Validate(const class Study *study=nullptr) const
void IncrementParticipantCounter()
static std::shared_ptr< Chain > LoadFromFile(const std::string &path)
ChainItem * GetItem(int index)
static std::shared_ptr< Chain > CreateNew(const std::string &path, const std::string &name, const std::string &description="")
bool RemoveItem(int index)
bool MoveItem(int fromIndex, int toIndex)
void InsertItem(int index, const ChainItem &item)
Test * GetTest(const std::string &testName)
const std::string & GetPath() const
std::string CreateChainPageConfig(const std::string &tempDir) const
std::string GetDisplayName() const
std::string BuildTestCommand(const std::string &studyPath, const std::string &subjectID) const
std::vector< std::string > warnings
std::vector< std::string > errors
std::vector< std::string > GetAvailableLanguages(const std::string &studyPath) const
const ParameterVariant * GetVariant(const std::string &variantName) const