<?php
namespace App\Controller\Api;
use App\Entity\AgeCategories;
use App\Entity\CardiometabolicProfiles;
use App\Entity\CardiovascularScore;
use App\Entity\Countries;
use App\Entity\CvdAdvices;
use App\Entity\CvdRisks;
use App\Entity\NonHdlCholesterol;
use App\Entity\ScoreRisk;
use App\Entity\SystolicBloodPresure;
use App\Entity\Users;
use App\Entity\UnitMeasureHba1cType;
use App\Entity\UnitMeasureConcentrationType;
use App\Helper\ConvertUnitMeasures;
use App\Helper\HealthHelper;
use App\Helper\LangHelper;
use App\Services\Gamification\ProfileGamification;
use App\Services\Health\CvdRiskPredictionBMI;
use App\Services\Health\CvdRiskPredictionLipids;
use App\Services\Health\GenCardioLipids;
use App\Services\Health\MenParameters;
use App\Services\Health\WomenParameters;
use App\Services\Notifications\FcmNotifications;
use App\Traits\ProfilesTrait;
use Doctrine\ORM\EntityManagerInterface;
use Dompdf\Dompdf;
use Dompdf\Options;
use Exception;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
class ProfilesController extends AbstractController
{
use ProfilesTrait;
use \App\Controller\Response;
protected TranslatorInterface $translator;
protected EntityManagerInterface $em;
public function __construct(TranslatorInterface $translator, EntityManagerInterface $em)
{
$this->translator = $translator;
$this->em = $em;
}
#[Route('/api/profiles/history',
name: 'app_api_profiles_history', methods: ["GET"])]
public function history(EntityManagerInterface $em): Response
{
$user = $this->getUser();
if (!$user or ! $user instanceof Users) {
$type = is_null($user) ? 'null' : get_class($user);
return $this->errorJsonResponse(['message' => "User not found or invalid ($type)"]);
}
$profiles = $em->getRepository(CardiometabolicProfiles::class)->findBy(['userId' => $user->getId(), 'masterProfile' => true], ['id' => 'DESC']);
return $this->json([
'status' => 'ok',
'data' => $profiles,
], 200, [], ['groups' => ["profile", "profiles:extended"]]);
}
protected function getHealthyScore(CardiometabolicProfiles $profile, $lang)
{
$ageCategory = $this->em->getRepository(AgeCategories::class)->getCategoryByAge($profile->getAge());
$country = $this->em->getRepository(Countries::class)->find($profile->getCountryId() ?? 175);
$cvdRisk = $this->em->getRepository(CvdRisks::class)->find($country->getRiskId() ?? 1);
$systolicBloodPresure = $this->em->getRepository(SystolicBloodPresure::class)
->getByValue($profile->getSystolicBloodPressure() ?? 0);
$nonHdlCholesterol = $this->em->getRepository(NonHdlCholesterol::class)
->getByValue($profile->getNonHdlCholesterol() ?? 0);
$criteria = [
'gender' => $profile->getGender(),
'smoking' => 0,
'systolicBloodPresureId' => $systolicBloodPresure ? $systolicBloodPresure->getId() : 0,
'ageCategoryId' => $ageCategory ? $ageCategory->getId() : 0,
'nonHdlCholesterolId' => $nonHdlCholesterol ? $nonHdlCholesterol->getId() : 0,
'cvdRiskId' => $cvdRisk ? $cvdRisk->getId() : null,
'removed' => 0,
];
$cvdScore = $this->em->getRepository(CardiovascularScore::class)->findOneBy($criteria);
if ($cvdScore) {
$scoreRisk = $this->em->getRepository(ScoreRisk::class)->find($cvdScore->getScoreRiskId());
}
$scoreRisk = HealthHelper::getScoreRisk($profile, $scoreRisk ?? null, $this->em);
$riskFlag = false;
if (!empty($scoreRisk)) {
if (in_array($scoreRisk->getId(), [2,3]) && // (high and very high) TODO: score risk should be enum
$profile->hasOneOrMoreSevereRiskConditions() &&
$this->getUser() && $this->getUser()->hasValidSubscription()
) $riskFlag = true;
return [
'risk_flag' => $riskFlag,
'score_risk' => $this->parseScoreRisk($scoreRisk, $lang),
'cvd_score' => HealthHelper::parseScoreToArray(HealthHelper::getScore($profile, $cvdScore ?? null, $scoreRisk))
];
}
return ['risk_flag' => $riskFlag];
}
#[Route('/api/cardiometabolic_profile/{userId}',
name: 'api_cardiometabolic_profile', methods: ["GET"])]
public function getCardiometabolicProfile($userId, Request $request, EntityManagerInterface $em)
{
try {
$user = $this->getUser(); // TOKEN
if (!$user or ! $user instanceof Users) {
$type = is_null($user) ? 'null' : get_class($user);
return $this->errorJsonResponse(['message' => "User not found or invalid ($type)"]);
}
/** @var CardiometabolicProfiles $profile */
$profile = $em->getRepository(CardiometabolicProfiles::class)->getLastByUser($user->getId());
$parsedAdvices = [];
if (!$profile) {
return $this->json([
'status' => 'ok',
'data' => null,
], 200, [], ['groups' => ["profile", "cvd_score:read"]]);
}
if ($profile) {
$country = $em->getRepository(Countries::class)->find($profile->getCountryId());
$advices = $em->getRepository(CvdAdvices::class)->filter(
LangHelper::getLangId($user->getLang()),
$profile->getSmoker(),
$profile->getImc() > 25,
true,
$profile->getDiabetesStatus(),
$user->hasValidSubscription(),
$profile->getCountryId()
);
// if no country advices, get all countries advices
if (count($advices) === 0) {
$advices = $em->getRepository(CvdAdvices::class)->filter(
LangHelper::getLangId($user->getLang()),
$profile->getSmoker(),
$profile->getImc() > 25,
true,
$profile->getDiabetesStatus(),
$user->hasValidSubscription()
);
}
foreach ($advices as $advice) {
$parsedAdvices[] = [
'id' => $advice->getId(),
'advice' => $this->parseAdvice($advice, $profile),
'title' => $advice->getTitle(),
'image' => $advice->getImage(),
'path' => $request->getUriForPath('/assets/images/advices/'),
];
}
if ($this->useFraminghamAlgorithm($country->getRiskId(), $profile) && $profile->getHdlCholesterol() > 0 && $profile->getTotalCholesterol() > 0 && $profile->getSystolicBloodPressure() > 0) {
$cvd_risk_prediction_using_lipids = (new GenCardioLipids(
$profile->getGender() == 1 ? new WomenParameters() : new MenParameters(),
$profile->getGender() == 1 ? 0 : 1,
$profile->getAge(),
$profile->getSystolicBloodPressure(),
$profile->getHypertensionTreatment(),
$profile->getSmoker(),
$profile->getDiabetes(),
$profile->getHdlCholesterol(),
$profile->getTotalCholesterol()
))->generateCvdRiskPredictionUsingLipids();
$cvd_risk_prediction_lipids = (new CvdRiskPredictionLipids())->calcGenCvdRiskPredictionLipids(
$profile->getGender() == 2 ? 1 : 0,
$profile->getAge(),
$profile->getSystolicBloodPressure(),
$profile->getHypertensionTreatment(),
$profile->getSmoker(),
$profile->getDiabetes(),
$profile->getHdlCholesterol(),
$profile->getTotalCholesterol()
);
}
if ($this->useFraminghamAlgorithm($country->getRiskId(), $profile) && $profile->getSystolicBloodPressure() > 0) {
$cvd_risk_prediction_bmi = (new CvdRiskPredictionBMI())->calcGenCvdRiskPredictionBMI(
$profile->getGender() == 2 ? 1 : 0,
$profile->getAge(),
$profile->getSystolicBloodPressure(),
$profile->getHypertensionTreatment(),
$profile->getSmoker(),
$profile->getDiabetes(),
$profile->getImc()
);
}
}
// ar trebui sa vina null obiectul score_risk daca una din aceste informatii este 0. "totalCholesterol": 0, "hdlCholesterol": 0, "nonHdlCholesterol": 0
if ($profile->getCvdScoreId() > 0 && $profile->getHdlCholesterol() > 0 && $profile->getTotalCholesterol() > 0 && $profile->getNonHdlCholesterol() > 0) {
$cvdScore = $em->getRepository(CardiovascularScore::class)->find($profile->getCvdScoreId());
if ($cvdScore) {
$scoreRisk = $em->getRepository(ScoreRisk::class)->find($cvdScore->getScoreRiskId());
}
}
$scoreRisk = HealthHelper::getScoreRisk($profile, $scoreRisk ?? null, $em);
return $this->json([
'status' => 'ok',
'data' => $profile,
'advices' => $parsedAdvices,
'score_risk' => $this->parseScoreRisk($scoreRisk, $user->getLang()),
'cvd_score' => HealthHelper::parseScoreToArray(HealthHelper::getScore($profile, $cvdScore ?? null, $scoreRisk)),
'params' => [
'weight_loss' => ConvertUnitMeasures::convertKgByUnitMeasureType($user->getUnitMeasure(), HealthHelper::weightLoss($profile->getHeight(), $profile->getWeight())),
'ldl_cholesterol_limit' => HealthHelper::getLdlLimit($profile),
],
'cvd_risk_prediction_using_lipids' => $cvd_risk_prediction_using_lipids ?? null,
'cvd_risk_prediction_bmi' => $cvd_risk_prediction_bmi ?? null,
'cvd_risk_prediction_lipids' => $cvd_risk_prediction_lipids ?? null,
'healthy_minutes' => $user->getHealthyMinutes(),
'user_points' => $user->getUserPoints(),
'healthiest_score' => $this->getHealthyScore($profile, $user->getLang())
], 200, [], ['groups' => ["profile", "cvd_score:read"]]);
} catch (Exception $e) {
return $this->errorJsonResponse(['message' => $e->getMessage()]);
}
}
#[Route('/api/cardiometabolic_profile',
name: 'api_cardiometabolic_profile_save', methods: ["POST"])]
public function saveCardiometaboliProfile(
Request $request,
EntityManagerInterface $em,
LoggerInterface $appLogger,
FcmNotifications $fcmNotification
)
{
try {
$content = $request->getContent();
# parse JSON to Array
$data = json_decode($content, true);
$user = $this->getUser(); // TOKEN
if (!$user or ! $user instanceof Users) {
$type = is_null($user) ? 'null' : get_class($user);
return $this->errorJsonResponse(['message' => "User not found or invalid ($type)"]);
}
$previousProfile = $em->getRepository(CardiometabolicProfiles::class)->getLastMasterByUser($user->getId());
$country = $em->getRepository(Countries::class)->find($data['country'] ?? 0);
$systolicBloodPresure = $em->getRepository(SystolicBloodPresure::class)->getByValue($data['systolic_blood_pressure'] ?? 0);
$ageCategory = $em->getRepository(AgeCategories::class)->getCategoryByAge($data['age'] ?? 0);
$nonHdlCholesterol = $em->getRepository(NonHdlCholesterol::class)->getByValue($data['non_hdl_cholesterol'] ?? 0);
$cvdRisk = $em->getRepository(CvdRisks::class)->find($country ? $country->getRiskId() : 0);
$cvdScore = null;
if ($data['total_cholesterol'] > 0 && $data['hdl_cholesterol'] > 0) {
$criteria = [
'gender' => $data['gender'] ?? 0,
'smoking' => $data['smoker'] ? 1 : 0,
'systolicBloodPresureId' => $systolicBloodPresure ? $systolicBloodPresure->getId() : 0,
'ageCategoryId' => $ageCategory ? $ageCategory->getId() : 0,
'nonHdlCholesterolId' => $nonHdlCholesterol ? $nonHdlCholesterol->getId() : 0,
'cvdRiskId' => $cvdRisk ? $cvdRisk->getId() : 0,
'removed' => 0,
];
$cvdScore = $em->getRepository(CardiovascularScore::class)->findOneBy($criteria);
}
$cardiometabolicProfile = $em
->getRepository(CardiometabolicProfiles::class)
->createFromData($data, $user->getId(), $country->getId(), $cvdScore);
if ($user->hasValidSubscription()) {
$gamification = new ProfileGamification(
$cardiometabolicProfile,
$previousProfile,
$em,
$appLogger,
$this->getParameter('fcm_api_key')
);
$gamification->saveRewards();
$em->flush();
$gamification->sendNotifications($fcmNotification);
}
return $this->jsonResponse([
'status' => 'ok',
'gender' => $data['gender'] ?? 0,
'smoking' => $data['smoker'] ?? 0,
'systolicBloodPresureId' => $systolicBloodPresure ? $systolicBloodPresure->getId() : 0,
'ageCategoryId' => $ageCategory ? $ageCategory->getId() : 0,
'nonHdlCholesterolId' => $nonHdlCholesterol ? $nonHdlCholesterol->getId() : 0,
'cvdRiskId' => $cvdRisk ? $cvdRisk->getId() : 0,
'removed' => 0,
]);
} catch (Exception $e) {
return $this->errorJsonResponse(['message' => $e->getMessage()]);
}
}
#[Route('/api/cardiometabolic_profile/pdf/{userId}',
name: 'api_cardiometabolic_profile_pdf', methods: ["GET"])]
public function getCardiometaboliProfilePdf($userId, EntityManagerInterface $em)
{
$user = $this->getUser(); // TOKEN
if (!$user or ! $user instanceof Users) {
$type = is_null($user) ? 'null' : get_class($user);
return $this->errorJsonResponse(['message' => "User not found or invalid ($type)"]);
}
//$user = $em->getRepository(Users::class)->find($userId);
/** @var CardiometabolicProfiles $profile */
$profile = $em->getRepository(CardiometabolicProfiles::class)->getLastByUser($user->getId());
$country = $em->getRepository(Countries::class)->find($profile->getCountryId());
$cvdScore = $em->getRepository(CardiovascularScore::class)->find($profile->getCvdScoreId() ?? 0);
$scoreRisk = $em->getRepository(ScoreRisk::class)->find($cvdScore?->getScoreRiskId() ?? 0);
$scoreRisk = HealthHelper::getScoreRisk($profile, $scoreRisk, $em);
// Configure Dompdf according to your needs
$pdfOptions = new Options();
$pdfOptions->set('defaultFont', 'Arial');
// Instantiate Dompdf with our options
$dompdf = new Dompdf($pdfOptions);
// Retrieve the HTML generated in our twig file
$html = $this->renderView('pdf/profile.html.twig', [
'logo' => base64_encode(file_get_contents(__DIR__ . '/../../../public/images/DAHNA_Powered-by-cardioscience_Logo.png')),
'lang' => $user->getLang(),
'has_valid_subscription' => $user->hasValidSubscription(),
'user' => [
'name' => $user->getFullname(),
'country' => $country->getCountryName(),
'sex' => HealthHelper::getSex($profile->getGender()),
'age' => $profile->getAge(),
'weight' => $profile->getWeight(),
'height' => $profile->getHeight(),
],
'profile' => [
'weight_loss' => HealthHelper::weightLoss($profile->getHeight(), $profile->getWeight()),
'bmi' => $profile->getImc(),
'score' => HealthHelper::getScore($profile, $cvdScore, $scoreRisk ?? null),
'risk' => $scoreRisk ? $scoreRisk->getScoreRisk() : null,
'risk_color' => $scoreRisk ? $scoreRisk->getColor() : '',
'systolic_blood_pressure' => $profile->getSystolicBloodPressure(),
'total_cholesterol' => $profile->getTotalCholesterol(true),
'hdl_cholesterol' => $profile->getHdlCholesterol(true),
'non_hdl_cholesterol' => $profile->getNonHdlCholesterol(true),
'myocardial_infarction' => $profile->getMyocardialInfarction() ? 'Yes' : 'No',
'angina_pectoris' => $profile->getAnginaPectoris() ? 'Yes' : 'No',
'smoker' => $profile->getSmoker() ? 'Yes' : 'No',
'hypertension' => $profile->getHypertensionTreatment() ? 'Yes' : 'No',
'diabetes' => $profile->getDiabetes() ? 'Yes' : 'No',
'hypertension_treatment' => $profile->getHypertensionTreatment() ? 'Yes' : 'No',
'coronary_artery_bypass' => $profile->getCoronaryArteryBypass() ? 'Yes' : 'No',
'arthritis' => $profile->getArthritis() ? 'Yes' : 'No',
'stroke' => $profile->getStroke() ? 'Yes' : 'No',
'angioplasty' => $profile->getAngioplasty() ? 'Yes' : 'No',
'hba1c' => $profile->getHba1c(true),
'fasting_blood_sugar' => $profile->getFastingBloodSugar(true),
'creatinine' => $profile->getCreatinine(true),
'egfr' => $profile->getEGFR(),
'urea' => $profile->getUrea(true),
'diabetes_diagnosis_year' => $profile->getDiabetesDiagnosisYear()
],
'reference_values' => [
'systolic_blood_pressure' => "100 - 200",
'total_cholesterol' =>
$profile->getUnitMeasureConcentration() == UnitMeasureConcentrationType::MG_PER_DL ?
"< 200" : "< 5.18",
'hdl_cholesterol' => $profile->getUnitMeasureConcentration() == UnitMeasureConcentrationType::MG_PER_DL ?
($profile->getGender() == 1 ? "> 50" : "> 40") :
($profile->getGender() == 1 ? "> 1.29" : "> 1.04"),
'non_hdl_cholesterol' => "-",
'hba1c' => $profile->getUnitMeasureHba1c() == UnitMeasureHba1cType::PERCENT ? "< 5.7" : "< 39",
'fasting_blood_sugar' => $profile->getUnitMeasureConcentration() == UnitMeasureConcentrationType::MG_PER_DL ?
"70 - 100" : "3.9 - 5.6",
'creatinine' => $profile->getUnitMeasureConcentration() == UnitMeasureConcentrationType::MG_PER_DL ?
($profile->getGender() == 1 ? "0.6 - 1.2" : "0.5 - 1.1") :
($profile->getGender() == 1 ? "> 53 - 106" : "44 - 97"),
'urea' => $profile->getUnitMeasureConcentration() == UnitMeasureConcentrationType::MG_PER_DL ?
"15 - 45" : "2.5 - 7.5",
'egfr' => ">90"
],
'unit_measures' => [
'systolic_blood_pressure' => "mm Hg",
'total_cholesterol' => $profile->getUnitMeasureConcentration()->value,
'hdl_cholesterol' => $profile->getUnitMeasureConcentration()->value,
'non_hdl_cholesterol' => $profile->getUnitMeasureConcentration()->value,
'hba1c' => $profile->getUnitMeasureHba1c()->value,
'fasting_blood_sugar' => $profile->getUnitMeasureConcentration()->value,
'creatinine' => $profile->getUnitMeasureConcentration()->value,
'urea' => $profile->getUnitMeasureConcentration()->value,
'egfr' => 'mL/min/1.73 m²',
]
]);
// Load HTML to Dompdf
$dompdf->loadHtml($html);
// (Optional) Setup the paper size and orientation 'portrait' or 'portrait'
$dompdf->setPaper('A4', 'portrait');
// Render the HTML as PDF
$dompdf->render();
// Output the generated PDF to Browser (force download)
$dompdf->stream("profil_cardiometabolic.pdf", [
"Attachment" => true,
]);
exit();
}
}