diff --git a/app/models/Backend.scala b/app/models/Backend.scala index 3ff537ac..852e6f71 100644 --- a/app/models/Backend.scala +++ b/app/models/Backend.scala @@ -11,7 +11,15 @@ import esecuele.* import javax.inject.Inject import models.Helpers.* -import models.db.{IntervalsQuery, QAOTF, QLITAGG, QW2V, TargetsQuery} +import models.db.{ + AllByIdInColumnQuery, + ClinicalReportQuery, + IntervalsQuery, + QAOTF, + QLITAGG, + QW2V, + TargetsQuery +} import models.entities.Publication.* import models.entities.Associations.* import models.entities.Biosample.* @@ -640,53 +648,6 @@ class Backend @Inject() (implicit prioritisationFt.flatMap(identity) } - def getKnownDrugs( - queryString: String, - kv: Map[String, String], - sizeLimit: Option[Int], - cursor: Option[String] - ): Future[Option[KnownDrugs]] = { - - val pag = Pagination(0, sizeLimit.getOrElse(Pagination.sizeDefault)) - val sortByField = sort.FieldSort(field = "phase").desc() - val cbIndex = getIndexOrDefault("known_drugs") - - val mappedValues = - Seq(keyValue("index", cbIndex)) ++ kv.map(pair => keyValue(pair._1, pair._2)).toSeq - - logger.debug(s"querying known drugs", mappedValues*) - - val aggs = Seq( - cardinalityAgg("uniqueTargets", "targetId.raw"), - cardinalityAgg("uniqueDiseases", "diseaseId.raw"), - cardinalityAgg("uniqueDrugs", "drugId.raw"), - valueCountAgg("rowsCount", "drugId.raw") - ) - - esRetriever - .getByFreeQuery( - cbIndex, - queryString, - kv, - pag, - fromJsValue[KnownDrug], - aggs, - Some(sortByField), - Seq("ancestors", "descendants"), - cursor - ) - .map { - case (Seq(), _, _) => Some(KnownDrugs(0, 0, 0, 0, cursor, Seq())) - case (seq, agg, nextCursor) => - logger.trace(Json.prettyPrint(agg)) - val drugs = (agg \ "uniqueDrugs" \ "value").as[Long] - val diseases = (agg \ "uniqueDiseases" \ "value").as[Long] - val targets = (agg \ "uniqueTargets" \ "value").as[Long] - val rowsCount = (agg \ "rowsCount" \ "value").as[Long] - Some(KnownDrugs(drugs, diseases, targets, rowsCount, nextCursor, seq)) - } - } - def getEvidencesByVariantId( datasourceIds: Option[Seq[String]], variantId: String, @@ -792,6 +753,71 @@ class Backend @Inject() (implicit .map(_.mappedHits) } + def getClinicalTargetsByTarget(id: String): Future[ClinicalTargets] = + val tableName = getTableWithPrefixOrDefault(defaultOTSettings.clickhouse.clinicalTarget.name) + + val clinicalTargetQuery = AllByIdInColumnQuery(id, tableName, 0, Pagination.sizeMax, "targetId") + + logger.info(s"getting clinical target with id $id", keyValue("table", tableName)) + + dbRetriever + .executeQuery[ClinicalTarget, Query](clinicalTargetQuery.query) + .map { + case Seq() => + logger.info(s"no clinical target found for $id", keyValue("table", tableName)) + ClinicalTargets(0, IndexedSeq()) + case ct => + logger.info(s"clinical target found for $id ${ct.length}", keyValue("table", tableName)) + ClinicalTargets(ct.length, ct) + } + + def getClinicalIndicationsByDrug(id: String): Future[ClinicalIndications] = + val tableName = getTableWithPrefixOrDefault( + defaultOTSettings.clickhouse.clinicalIndication.drugTable.name + ) + + logger.info(s"getting clinical indications by the drug $id", keyValue("table", tableName)) + getClinicalIndications(id, tableName, "drugId") + + def getClinicalIndicationsByDisease(id: String): Future[ClinicalIndications] = + val tableName = getTableWithPrefixOrDefault( + defaultOTSettings.clickhouse.clinicalIndication.diseaseTable.name + ) + + logger.info(s"getting clinical indications by the disease $id", keyValue("table", tableName)) + getClinicalIndications(id, tableName, "diseaseId") + + def getClinicalReports(ids: Seq[String]): Future[IndexedSeq[ClinicalReport]] = { + val tableName = getTableWithPrefixOrDefault(defaultOTSettings.clickhouse.clinicalReport.name) + + val clinicalReportQuery = ClinicalReportQuery(ids, tableName, 0, Pagination.sizeMax) + + logger.info(s"getting clinical reports with ids $ids", keyValue("table", tableName)) + + dbRetriever + .executeQuery[ClinicalReport, Query](clinicalReportQuery.query) + } + + private def getClinicalIndications(id: String, + tableName: String, + columnName: String + ): Future[ClinicalIndications] = { + + val clinicalIndicationsQuery = + AllByIdInColumnQuery(id, tableName, 0, Pagination.sizeMax, columnName) + + dbRetriever + .executeQuery[ClinicalIndication, Query](clinicalIndicationsQuery.query) + .map { + case Seq() => + logger.info(s"no clinical indication found for $id in table $tableName") + ClinicalIndications(0, IndexedSeq()) + case cis => + logger.info(s"clinical indications found for $id in table $tableName: ${cis.length}") + ClinicalIndications(cis.length, cis) + } + } + def getPharmacogenomicsByDrug(id: String): Future[IndexedSeq[Pharmacogenomics]] = { val queryTerm: Map[String, String] = Map("drugs.drugId.keyword" -> id) getPharmacogenomics(id, queryTerm) @@ -908,7 +934,7 @@ class Backend @Inject() (implicit def getDrugs(ids: Seq[String]): Future[IndexedSeq[Drug]] = { val drugIndexName = getIndexOrDefault("drug") - logger.debug(s"querying drugs", keyValue("drug_ids", ids), keyValue("index", drugIndexName)) + logger.debug(s"querying drugs with ids: $ids", keyValue("index", drugIndexName)) val queryTerm = Map("id.keyword" -> ids) esRetriever @@ -931,16 +957,6 @@ class Backend @Inject() (implicit mechanismsOfActionRaw.map(i => Drug.mechanismOfActionRaw2MechanismOfAction(i.mappedHits)) } - def getIndications(ids: Seq[String]): Future[IndexedSeq[Indications]] = { - val index = getIndexOrDefault("drugIndications") - logger.debug(s"querying indications", keyValue("ids", ids), keyValue("index", index)) - val queryTerm = Map("id.keyword" -> ids) - - esRetriever - .getByIndexedQueryShould(index, queryTerm, Pagination.mkDefault, fromJsValue[Indications]) - .map(_.mappedHits) - } - def getDrugWarnings(id: String): Future[IndexedSeq[DrugWarning]] = { val indexName = getIndexOrDefault("drugWarnings") logger.debug(s"querying drug warnings", keyValue("id", id), keyValue("index", indexName)) @@ -968,7 +984,7 @@ class Backend @Inject() (implicit def getDiseases(ids: Seq[String]): Future[IndexedSeq[Disease]] = { val diseaseIndexName = getIndexOrDefault("disease") - logger.debug(s"querying diseases", keyValue("ids", ids), keyValue("index", diseaseIndexName)) + logger.debug(s"querying diseases with ids: $ids", keyValue("index", diseaseIndexName)) esRetriever.getByIds(diseaseIndexName, ids, fromJsValue[Disease]) } diff --git a/app/models/ClickhouseRetriever.scala b/app/models/ClickhouseRetriever.scala index cdccde18..105d0d09 100644 --- a/app/models/ClickhouseRetriever.scala +++ b/app/models/ClickhouseRetriever.scala @@ -73,7 +73,7 @@ class ClickhouseRetriever(config: OTSettings)(implicit case Failure(ex) => val qStr = qq.statements.mkString("\n") logger.error(s"executeQuery an exception was thrown ${ex.getMessage} with Query $qStr") - Vector.empty + Vector.empty // TODO: maybe we should return the error instead of an empty vector, to inform the user } } } diff --git a/app/models/GQLSchema.scala b/app/models/GQLSchema.scala index e27cd8bd..54ec762d 100644 --- a/app/models/GQLSchema.scala +++ b/app/models/GQLSchema.scala @@ -204,6 +204,21 @@ object GQLSchema { ) ctx.ctx.getCredibleSets(credSetQueryArgs, ctx.arg(pageArg)) } + ), + Field( + "clinicalReport", + OptionType(clinicalReportImp), + description = Some(""), + arguments = clinicalReportId :: Nil, + resolve = ctx => clinicalReportFetcher.deferOpt(ctx.arg(clinicalReportId)) + ), + Field( + "clinicalReports", + ListType(clinicalReportImp), + description = Some(""), + arguments = clinicalReportIds :: Nil, + complexity = Some(complexityCalculator(clinicalReportIds)), + resolve = ctx => clinicalReportFetcher.deferSeqOpt(ctx.arg(clinicalReportIds)) ) ) ) diff --git a/app/models/db/AllByIdInColumnQuery.scala b/app/models/db/AllByIdInColumnQuery.scala new file mode 100644 index 00000000..fc73af00 --- /dev/null +++ b/app/models/db/AllByIdInColumnQuery.scala @@ -0,0 +1,29 @@ +package models.db + +import esecuele.Column.{column, literal} +import esecuele.{Column, Format, From, Functions, Limit, OrderBy, PreWhere, Query, Select, Settings} +import utils.OTLogging + +case class AllByIdInColumnQuery(id: String, + tableName: String, + offset: Int, + size: Int, + columnName: String +) extends Queryable + with OTLogging { + + override val query: Query = + Query( + Select( + Column.star :: Nil + ), + From(column(tableName)), + PreWhere( + Functions.in(column(columnName), literal(id)) + ), + OrderBy(column("id") :: Nil), + Limit(offset, size), + Format("JSONEachRow"), + Settings(Map("output_format_json_escape_forward_slashes" -> "0")) + ) +} diff --git a/app/models/db/ClinicalReportQuery.scala b/app/models/db/ClinicalReportQuery.scala new file mode 100644 index 00000000..73342930 --- /dev/null +++ b/app/models/db/ClinicalReportQuery.scala @@ -0,0 +1,39 @@ +package models.db + +import esecuele.Column.{column, literal} +import esecuele.{ + Column, + Format, + From, + Functions, + Limit, + OrderBy, + PreWhere, + Query, + Select, + Settings, + Where +} +import utils.OTLogging + +case class ClinicalReportQuery(ids: Seq[String], tableName: String, offset: Int, size: Int) + extends Queryable + with OTLogging { + + private val conditional = Where( + Functions.in(column("id"), Functions.set(ids.map(literal).toSeq)) + ) + + override val query: Query = + Query( + Select( + Column.star :: Nil + ), + From(column(tableName)), + conditional, + OrderBy(column("id") :: Nil), + Limit(offset, size), + Format("JSONEachRow"), + Settings(Map("output_format_json_escape_forward_slashes" -> "0")) + ) +} diff --git a/app/models/entities/ClinicalDiseaseListItem.scala b/app/models/entities/ClinicalDiseaseListItem.scala new file mode 100644 index 00000000..4d658565 --- /dev/null +++ b/app/models/entities/ClinicalDiseaseListItem.scala @@ -0,0 +1,3 @@ +package models.entities + +case class ClinicalDiseaseListItem(diseaseFromSource: String, diseaseId: String) diff --git a/app/models/entities/ClinicalIndication.scala b/app/models/entities/ClinicalIndication.scala new file mode 100644 index 00000000..3294b366 --- /dev/null +++ b/app/models/entities/ClinicalIndication.scala @@ -0,0 +1,21 @@ +package models.entities + +import play.api.libs.json.{Json, OFormat} +import slick.jdbc.GetResult +import utils.OTLogging + +case class ClinicalIndication( + id: String, + drugId: Option[String], + diseaseId: Option[String], + maxClinicalStage: String, + clinicalReportIds: Seq[String] +) + +object ClinicalIndication extends OTLogging { + + implicit val getClinicalIndicationsFromDB: GetResult[ClinicalIndication] = + GetResult(r => Json.parse(r.<<[String]).as[ClinicalIndication]) + + implicit val clinicalIndicationsF: OFormat[ClinicalIndication] = Json.format[ClinicalIndication] +} diff --git a/app/models/entities/ClinicalIndications.scala b/app/models/entities/ClinicalIndications.scala new file mode 100644 index 00000000..105b9868 --- /dev/null +++ b/app/models/entities/ClinicalIndications.scala @@ -0,0 +1,48 @@ +package models.entities + +import models.Backend +import models.gql.Objects.{clinicalIndicationFromDiseaseImp, clinicalIndicationFromDrugImp} +import sangria.schema.{Field, ListType, LongType, ObjectType, fields} + +case class ClinicalIndications( + count: Long, + rows: IndexedSeq[ClinicalIndication] +) + +object ClinicalIndications { + def empty: ClinicalIndications = ClinicalIndications(0, IndexedSeq.empty) + val clinicalIndicationsFromDrugImp: ObjectType[Backend, ClinicalIndications] = ObjectType( + "clinicalIndicationsFromDrugImp", + "", + fields[Backend, ClinicalIndications]( + Field("count", + LongType, + description = Some("Total number of indications results matching the query filters"), + resolve = _.value.count + ), + Field( + "rows", + ListType(clinicalIndicationFromDrugImp), + description = Some("List of clinical indications results between drug-disease pairs"), + resolve = _.value.rows + ) + ) + ) + val clinicalIndicationsFromDiseaseImp: ObjectType[Backend, ClinicalIndications] = ObjectType( + "clinicalIndicationsFromDiseaseImp", + "", + fields[Backend, ClinicalIndications]( + Field("count", + LongType, + description = Some("Total number of indications results matching the query filters"), + resolve = _.value.count + ), + Field( + "rows", + ListType(clinicalIndicationFromDiseaseImp), + description = Some("List of clinical indications results between drug-disease pairs"), + resolve = _.value.rows + ) + ) + ) +} diff --git a/app/models/entities/ClinicalReport.scala b/app/models/entities/ClinicalReport.scala new file mode 100644 index 00000000..37970877 --- /dev/null +++ b/app/models/entities/ClinicalReport.scala @@ -0,0 +1,62 @@ +package models.entities + +import play.api.libs.json.{JsString, JsValue, Json, OFormat, Writes} +import slick.jdbc.GetResult +import utils.OTLogging + +case class ClinRepDrugListItem(drugFromSource: String, drugId: String) + +case class ClinicalReport( + id: String, + source: String, + clinicalStage: String, + phaseFromSource: Option[String], + `type`: Option[String], + title: Option[String], + trialStudyType: Option[String], + trialDescription: Option[String], + trialNumberOfArms: Option[Int], + trialStartDate: Option[String], + trialLiterature: Seq[String], + trialOverallStatus: Option[String], + trialWhyStopped: Option[String], + trialPrimaryPurpose: Option[String], + trialPhase: Option[String], + trialStopReasonCategories: Seq[String], + diseases: Seq[ClinicalDiseaseListItem], + drugs: Seq[ClinRepDrugListItem], + hasExpertReview: Boolean, + countries: Seq[String], + year: Option[Int], + sideEffects: Seq[ClinicalDiseaseListItem], + trialOfficialTitle: Option[String], + url: Option[String] +) + +object ClinicalReport extends OTLogging { + implicit val getClinicalReportFromDB: GetResult[ClinicalReport] = + GetResult { r => + val raw = r.<<[String] + val escaped = raw + .replaceAll("""\\([*^<>&\[\]_~])""", "$1") + .replace("\\", "\\\\") + .replace("\n", "\\n") + .replaceAll("""\\(nrt)""", "\\\\$1") + val json = Json.parse(escaped) + json.as[ClinicalReport] + } + + implicit val diseaseListItemW: Writes[ClinicalDiseaseListItem] = + Json.writes[ClinicalDiseaseListItem] + + implicit val diseaseListItemF: OFormat[ClinicalDiseaseListItem] = + Json.format[ClinicalDiseaseListItem] + + implicit val drugListItemW: Writes[ClinRepDrugListItem] = Json.writes[ClinRepDrugListItem] + + implicit val drugListItemF: OFormat[ClinRepDrugListItem] = Json.format[ClinRepDrugListItem] + + implicit val clinicalReportW: Writes[ClinicalReport] = Json.writes[ClinicalReport] + + implicit val clinicalReportF: OFormat[ClinicalReport] = Json.format[ClinicalReport] +} diff --git a/app/models/entities/ClinicalTarget.scala b/app/models/entities/ClinicalTarget.scala new file mode 100644 index 00000000..f7ef818a --- /dev/null +++ b/app/models/entities/ClinicalTarget.scala @@ -0,0 +1,25 @@ +package models.entities + +import play.api.libs.json.{Json, OFormat} +import slick.jdbc.GetResult +import utils.OTLogging + +case class ClinicalTarget( + id: String, + drugId: Option[String], + targetId: Option[String], + diseases: Seq[ClinicalDiseaseListItem], + maxClinicalStage: String, + clinicalReportIds: Seq[String] +) + +object ClinicalTarget extends OTLogging { + + implicit val getClinicalTargetsFromDB: GetResult[ClinicalTarget] = + GetResult(r => Json.parse(r.<<[String]).as[ClinicalTarget]) + + implicit val clinicalDiseaseListItemF: OFormat[ClinicalDiseaseListItem] = + Json.format[ClinicalDiseaseListItem] + + implicit val clinicalTargetsF: OFormat[ClinicalTarget] = Json.format[ClinicalTarget] +} diff --git a/app/models/entities/ClinicalTargets.scala b/app/models/entities/ClinicalTargets.scala new file mode 100644 index 00000000..fb583607 --- /dev/null +++ b/app/models/entities/ClinicalTargets.scala @@ -0,0 +1,32 @@ +package models.entities + +import models.Backend +import models.gql.Objects.clinicalTargetImp +import sangria.schema.{Field, ListType, LongType, ObjectType, fields} + +case class ClinicalTargets( + count: Long, + rows: IndexedSeq[ClinicalTarget] +) + +object ClinicalTargets { + def empty: ClinicalTargets = ClinicalTargets(0, IndexedSeq.empty) + val clinicalTargetsImp: ObjectType[Backend, ClinicalTargets] = ObjectType( + "clinicalTargets", + "", + fields[Backend, ClinicalTargets]( + Field("count", + LongType, + description = + Some("Total number of clinical targets results matching the query filters"), + resolve = _.value.count + ), + Field( + "rows", + ListType(clinicalTargetImp), + description = Some("List of clinical clinical targets results between drug-target pairs"), + resolve = _.value.rows + ) + ) + ) +} diff --git a/app/models/entities/Configuration.scala b/app/models/entities/Configuration.scala index 4c3db803..d93e069a 100644 --- a/app/models/entities/Configuration.scala +++ b/app/models/entities/Configuration.scala @@ -55,6 +55,10 @@ object Configuration { case class HarmonicSettings(pExponent: Int, datasources: Seq[DatasourceSettings]) + case class ClinicalIndicationSettings(drugTable: DbTableSettings, diseaseTable: DbTableSettings) + + case class ClinicalTargetSettings(drugTable: DbTableSettings, targetTable: DbTableSettings) + /** ClickHouse settings stores the configuration for the entities it handles. Target Disease and * Harmonic settings used to compute associations on the fly and LUTs for interaction expansions */ @@ -66,7 +70,10 @@ object Configuration { similarities: DbTableSettings, harmonic: HarmonicSettings, literature: DbTableSettings, - literatureIndex: DbTableSettings + literatureIndex: DbTableSettings, + clinicalIndication: ClinicalIndicationSettings, + clinicalReport: DbTableSettings, + clinicalTarget: DbTableSettings ) /** main Open Targets configuration object. It keeps track of meta, elasticsearch and clickhouse @@ -150,6 +157,10 @@ object Configuration { implicit val harmonicSettingsJSONImp: OFormat[HarmonicSettings] = Json.format[HarmonicSettings] implicit val targetSettingsJSONImp: OFormat[TargetSettings] = Json.format[TargetSettings] implicit val diseaseSettingsJSONImp: OFormat[DiseaseSettings] = Json.format[DiseaseSettings] + implicit val clinicalIndicationSettingsJSONImp: OFormat[ClinicalIndicationSettings] = + Json.format[ClinicalIndicationSettings] + implicit val clinicalTargetSettingsJSONImp: OFormat[ClinicalTargetSettings] = + Json.format[ClinicalTargetSettings] implicit val clickhouseSettingsJSONImp: OFormat[ClickhouseSettings] = Json.format[ClickhouseSettings] diff --git a/app/models/entities/Drug.scala b/app/models/entities/Drug.scala index 89a9a137..777c51b1 100644 --- a/app/models/entities/Drug.scala +++ b/app/models/entities/Drug.scala @@ -21,8 +21,6 @@ case class DrugWarning( case class Reference(ids: Option[Seq[String]], source: String, urls: Option[Seq[String]]) -case class IndicationReference(ids: Option[Seq[String]], source: String) - case class MechanismOfActionRow( mechanismOfAction: String, actionType: Option[String], @@ -31,21 +29,6 @@ case class MechanismOfActionRow( references: Option[Seq[Reference]] ) -case class IndicationRow( - maxPhaseForIndication: Double, - disease: String, - references: Option[Seq[IndicationReference]] -) - -case class LinkedIds(count: Int, rows: Seq[String]) - -case class Indications( - id: String, - indications: Seq[IndicationRow], - indicationCount: Long, - approvedIndications: Option[Seq[String]] -) - case class MechanismsOfAction( rows: Seq[MechanismOfActionRow], uniqueActionTypes: Seq[String], @@ -70,21 +53,14 @@ case class Drug( synonyms: Seq[String], tradeNames: Seq[String], childChemblIds: Option[Seq[String]], // Gone? - yearOfFirstApproval: Option[Int], drugType: String, - isApproved: Option[Boolean], crossReferences: Option[Seq[DrugReferences]], parentId: Option[String], - maximumClinicalTrialPhase: Option[Double], - hasBeenWithdrawn: Boolean, - linkedDiseases: Option[LinkedIds], - linkedTargets: Option[LinkedIds], - blackBoxWarning: Boolean, + maximumClinicalStage: String, description: Option[String] ) object Drug { - implicit val linkedIdsImpW: OFormat[LinkedIds] = Json.format[models.entities.LinkedIds] // implicit val drugWarningRefenceImpW = Json.format[models.entities.DrugWarningReference] implicit val drugWarningsReferenceImpR: Reads[models.entities.DrugWarningReference] = ( (JsPath \ "ref_id").read[String] and @@ -112,11 +88,6 @@ object Drug { Json.format[models.entities.MechanismOfActionRow] implicit val mechanismOfActionImpW: OFormat[MechanismsOfAction] = Json.format[models.entities.MechanismsOfAction] - implicit val indicationReferenceImpW: OFormat[IndicationReference] = - Json.format[models.entities.IndicationReference] - implicit val indicationRowImpW: OFormat[IndicationRow] = - Json.format[models.entities.IndicationRow] - implicit val indicationsImpW: OFormat[Indications] = Json.format[models.entities.Indications] implicit val mechanismOfActionRaw: OFormat[MechanismOfActionRaw] = Json.format[models.entities.MechanismOfActionRaw] diff --git a/app/models/entities/Evidence.scala b/app/models/entities/Evidence.scala index e70316f7..9725cfa6 100644 --- a/app/models/entities/Evidence.scala +++ b/app/models/entities/Evidence.scala @@ -77,7 +77,8 @@ case class Evidence( log2FoldChangePercentileRank: Option[Long], biologicalModelAllelicComposition: Option[String], confidence: Option[String], - clinicalPhase: Option[Double], + clinicalStage: Option[String], + clinicalReportId: Option[String], resourceScore: Option[Double], variantFunctionalConsequenceId: Option[String], variantFunctionalConsequenceFromQtlId: Option[String], @@ -91,7 +92,6 @@ case class Evidence( datasourceId: String, datatypeId: String, oddsRatioConfidenceIntervalUpper: Option[Double], - clinicalStatus: Option[String], log2FoldChangeValue: Option[Double], oddsRatio: Option[Double], cohortDescription: Option[String], @@ -115,8 +115,8 @@ case class Evidence( betaConfidenceIntervalLower: Option[Double], betaConfidenceIntervalUpper: Option[Double], studyStartDate: Option[String], - studyStopReason: Option[String], - studyStopReasonCategories: Option[Seq[String]], + trialWhyStopped: Option[String], + trialStopReasonCategories: Option[Seq[String]], targetFromSource: Option[String], cellLineBackground: Option[String], contrast: Option[String], diff --git a/app/models/entities/KnownDrug.scala b/app/models/entities/KnownDrug.scala deleted file mode 100644 index d6efd2e0..00000000 --- a/app/models/entities/KnownDrug.scala +++ /dev/null @@ -1,75 +0,0 @@ -package models.entities - -import play.api.libs.json.* -import play.api.libs.json.Reads.* -import play.api.libs.functional.syntax.* -import utils.OTLogging - -import scala.util.matching.Regex - -case class URL(url: String, name: String) -case class KnownDrugReference(source: String, ids: Seq[String], urls: Seq[String]) - -case class KnownDrug( - approvedSymbol: String, - approvedName: String, - label: String, - prefName: String, - drugType: String, - targetId: String, - diseaseId: String, - drugId: String, - phase: Double, - mechanismOfAction: String, - status: Option[String], - targetClass: Seq[String], - references: Seq[KnownDrugReference], - ctIds: Seq[String], - urls: Seq[URL] -) - -case class KnownDrugs( - uniqueDrugs: Long, - uniqueDiseases: Long, - uniqueTargets: Long, - count: Long, - cursor: Option[String], - rows: Seq[KnownDrug] -) - -object KnownDrug extends OTLogging { - - val ctPattern: Regex = "NCT(\\d{8})".r - - implicit val KnownDrugReferenceImpJSONF: OFormat[KnownDrugReference] = - Json.format[KnownDrugReference] - implicit val URLImpW: OWrites[URL] = Json.writes[URL] - implicit val URLImpR: Reads[URL] = ( - (__ \ "url").read[String] and - (__ \ "niceName").read[String] - )(URL.apply) - - // approvedSymbol: String, label: String, prefName: String, - implicit val knownDrugImpW: OWrites[KnownDrug] = Json.writes[KnownDrug] - implicit val knownDrugImpR: Reads[KnownDrug] = ( - (__ \ "approvedSymbol").read[String] and - (__ \ "approvedName").read[String] and - (__ \ "label").read[String] and - (__ \ "prefName").read[String] and - (__ \ "drugType").read[String] and - (__ \ "targetId").read[String] and - (__ \ "diseaseId").read[String] and - (__ \ "drugId").read[String] and - (__ \ "phase").read[Double] and - (__ \ "mechanismOfAction").read[String] and - (__ \ "status").readNullable[String] and - (__ \ "targetClass").readWithDefault[Seq[String]](Seq.empty) and - (__ \ "references").readWithDefault[Seq[KnownDrugReference]](Seq.empty) and - (__ \ "urls") - .readWithDefault[Seq[Map[String, String]]](Seq.empty) - .map(s => s.flatMap(m => ctPattern findAllIn m("url"))) and - (__ \ "urls").read[Seq[URL]] - )(KnownDrug.apply) - - implicit val knownDrugsImpF: OFormat[KnownDrugs] = Json.format[KnownDrugs] -} diff --git a/app/models/gql/Arguments.scala b/app/models/gql/Arguments.scala index dcbd86ab..8d79165a 100644 --- a/app/models/gql/Arguments.scala +++ b/app/models/gql/Arguments.scala @@ -113,6 +113,13 @@ object Arguments { val ensemblId: Argument[String] = Argument("ensemblId", StringType, description = "Ensembl ID") val ensemblIds: Argument[Seq[String @@ FromInput.CoercedScalaResult]] = Argument("ensemblIds", ListInputType(StringType), description = "List of Ensembl IDs") + val clinicalReportId: Argument[String] = + Argument("clinicalReportId", StringType, description = "Clinical Report ID") + val clinicalReportIds: Argument[Seq[String @@ FromInput.CoercedScalaResult]] = + Argument("clinicalReportsIds", + ListInputType(StringType), + description = "List of Clinical Report IDs" + ) val ensemblIdsOpt: Argument[Option[Seq[String]]] = Argument("ensemblIds", OptionInputType(ListInputType(StringType)), diff --git a/app/models/gql/DeferredResolvers.scala b/app/models/gql/DeferredResolvers.scala index 6069a238..9fc450d6 100644 --- a/app/models/gql/DeferredResolvers.scala +++ b/app/models/gql/DeferredResolvers.scala @@ -196,9 +196,9 @@ object DeferredResolvers extends OTLogging { Fetchers.expressionFetcher, Fetchers.otarProjectsFetcher, Fetchers.soTermsFetcher, - Fetchers.indicationFetcher, Fetchers.goFetcher, Fetchers.variantFetcher, - Fetchers.studyFetcher + Fetchers.studyFetcher, + Fetchers.clinicalReportFetcher ) } diff --git a/app/models/gql/Fetchers.scala b/app/models/gql/Fetchers.scala index 854acedb..173555ef 100644 --- a/app/models/gql/Fetchers.scala +++ b/app/models/gql/Fetchers.scala @@ -2,13 +2,13 @@ package models.gql import models.entities.{ Biosample, + ClinicalReport, CredibleSet, Disease, Drug, Expressions, GeneOntologyTerm, HPO, - Indications, OtarProjects, Reactome, SequenceOntologyTerm, @@ -42,9 +42,10 @@ object Fetchers extends OTLogging { FetcherConfig.maxBatchSize(entities.Configuration.batchSize).caching(targetsFetcherCache), fetch = (ctx: Backend, ids: Seq[String]) => ctx.getTargets(ids) ) - val diseasesFetcherCache = FetcherCache.simple // disease + val diseasesFetcherCache = FetcherCache.simple + implicit val diseaseHasId: HasId[Disease, String] = HasId[Disease, String](_.id) val diseasesFetcher: Fetcher[Backend, Disease, Disease, String] = Fetcher( config = @@ -106,12 +107,6 @@ object Fetchers extends OTLogging { fetch = (ctx: Backend, ids: Seq[String]) => ctx.getDrugs(ids) ) - implicit val indicationHasId: HasId[Indications, String] = HasId[Indications, String](_.id) - val indicationFetcher: Fetcher[Backend, Indications, Indications, String] = Fetcher( - config = FetcherConfig.maxBatchSize(entities.Configuration.batchSize), - fetch = (ctx: Backend, ids: Seq[String]) => ctx.getIndications(ids) - ) - implicit val goFetcherId: HasId[GeneOntologyTerm, String] = HasId[GeneOntologyTerm, String](_.id) val goFetcherCache = FetcherCache.simple val goFetcher: Fetcher[Backend, GeneOntologyTerm, GeneOntologyTerm, String] = Fetcher( @@ -151,6 +146,18 @@ object Fetchers extends OTLogging { ) } + val clinicalReportFetcherCache = FetcherCache.simple + val clinicalReportFetcher: Fetcher[Backend, ClinicalReport, ClinicalReport, String] = { + implicit val clinicalreportFetcherId: HasId[ClinicalReport, String] = + HasId[ClinicalReport, String](js => js.id) + Fetcher( + config = FetcherConfig + .maxBatchSize(entities.Configuration.batchSize) + .caching(clinicalReportFetcherCache), + fetch = (ctx: Backend, ids: Seq[String]) => ctx.getClinicalReports(ids) + ) + } + def resetCache(): Unit = { logger.info("clearing all GraphQL caches") val fetchers: List[SimpleFetcherCache] = List( @@ -166,7 +173,8 @@ object Fetchers extends OTLogging { reactomeFetcherCache, expressionFetcherCache, otarProjectsFetcherCache, - soTermsFetcherCache + soTermsFetcherCache, + clinicalReportFetcherCache ) fetchers.foreach(_.clear()) } diff --git a/app/models/gql/Objects.scala b/app/models/gql/Objects.scala index f745b054..6a29f1ac 100644 --- a/app/models/gql/Objects.scala +++ b/app/models/gql/Objects.scala @@ -8,10 +8,15 @@ import models.entities.Publications.publicationsImp import models.entities.Colocalisations.* import models.entities.* import models.gql.Arguments.* -import models.gql.Fetchers.* +import models.gql.Fetchers.{diseasesFetcher, *} import models.Helpers.ComplexityCalculator.* +import models.entities.ClinicalIndications.{ + clinicalIndicationsFromDiseaseImp, + clinicalIndicationsFromDrugImp +} +import models.entities.ClinicalTargets.clinicalTargetsImp import play.api.libs.json.* -import sangria.macros.derive.{DocumentField, *} +import sangria.macros.derive.* import sangria.schema.* import scala.concurrent.ExecutionContext.Implicits.global @@ -312,23 +317,6 @@ object Objects extends OTLogging { case None => Seq.empty } ), - Field( - "knownDrugs", - OptionType(knownDrugsImp), - description = Some( - "Set of clinical precedence for drugs with investigational or approved indications " + - "targeting this gene product according to their curated mechanism of action" - ), - arguments = freeTextQuery :: pageSize :: cursor :: Nil, - complexity = Some(complexityCalculator(pageSize)), - resolve = ctx => - ctx.ctx.getKnownDrugs( - ctx.arg(freeTextQuery).getOrElse(""), - Map("targetId.raw" -> ctx.value.id), - ctx.arg(pageSize), - ctx.arg(cursor) - ) - ), Field( "associatedDiseases", associatedOTFDiseasesImp, @@ -422,6 +410,13 @@ object Objects extends OTLogging { arguments = pageArg :: Nil, complexity = Some(complexityCalculator(pageArg)), resolve = ctx => ctx.ctx.getProteinCodingCoordinatesByTarget(ctx.value.id, ctx.arg(pageArg)) + ), + Field( + "drugAndClinicalCandidates", + clinicalTargetsImp, + description = Some(""), + arguments = Nil, + resolve = ctx => ctx.ctx.getClinicalTargetsByTarget(ctx.value.id) ) ) ) @@ -615,25 +610,6 @@ object Objects extends OTLogging { case None => Seq.empty } ), - Field( - "knownDrugs", - OptionType(knownDrugsImp), - description = Some( - "Investigational or approved drugs indicated for this disease with curated mechanisms of action" - ), - arguments = freeTextQuery :: pageSize :: cursor :: Nil, - complexity = Some(complexityCalculator(pageSize)), - resolve = ctx => - ctx.ctx.getKnownDrugs( - ctx.arg(freeTextQuery).getOrElse(""), - Map( - "diseaseId.raw" -> ctx.value.id, - "ancestors.raw" -> ctx.value.id - ), - ctx.arg(pageSize), - ctx.arg(cursor) - ) - ), Field( "associatedTargets", associatedOTFTargetsImp, @@ -679,6 +655,15 @@ object Objects extends OTLogging { ), arguments = Nil, resolve = ctx => diseasesFetcher.deferSeq(ctx.value.ancestors) + ), + Field( + "drugAndClinicalCandidates", + clinicalIndicationsFromDiseaseImp, + description = Some( + "Clinical indications for this disease as reported by clinical trial records." + ), + arguments = Nil, + resolve = ctx => ctx.ctx.getClinicalIndicationsByDisease(ctx.value.id) ) ) ) @@ -1238,39 +1223,6 @@ object Objects extends OTLogging { DocumentField("rows", "List of phenotype annotations for the disease") ) - // howto doc https://sangria-graphql.org/learn/#macro-based-graphql-type-derivation - implicit lazy val linkedDiseasesImp: ObjectType[Backend, LinkedIds] = - deriveObjectType[Backend, LinkedIds]( - ObjectTypeName("LinkedDiseases"), - ObjectTypeDescription("Diseases linked via indications"), - DocumentField("count", "Total number of linked diseases"), - ReplaceField( - "rows", - Field( - "rows", - ListType(diseaseImp), - Some("List of linked disease entities"), - resolve = r => diseasesFetcher.deferSeqOpt(r.value.rows) - ) - ) - ) - - implicit lazy val linkedTargetsImp: ObjectType[Backend, LinkedIds] = - deriveObjectType[Backend, LinkedIds]( - ObjectTypeName("LinkedTargets"), - ObjectTypeDescription("Targets linked via curated mechanisms of action"), - DocumentField("count", "Total number of linked targets"), - ReplaceField( - "rows", - Field( - "rows", - ListType(targetImp), - Some("List of linked target entities"), - resolve = r => targetsFetcher.deferSeqOpt(r.value.rows) - ) - ) - ) - implicit lazy val drugReferenceImp: ObjectType[Backend, Reference] = deriveObjectType[Backend, Reference]( ObjectTypeDescription("Reference information supporting the drug mechanisms of action"), @@ -1400,13 +1352,6 @@ object Objects extends OTLogging { ) ) - implicit lazy val indicationReferenceImp: ObjectType[Backend, IndicationReference] = - deriveObjectType[Backend, IndicationReference]( - ObjectTypeDescription("Reference information for drug indications"), - DocumentField("ids", "List of reference identifiers (e.g., PubMed IDs)"), - DocumentField("source", "Source of the reference") - ) - implicit lazy val mechanismOfActionRowImp: ObjectType[Backend, MechanismOfActionRow] = deriveObjectType[Backend, MechanismOfActionRow]( ObjectTypeDescription("Mechanism of action information for a drug"), @@ -1430,38 +1375,6 @@ object Objects extends OTLogging { ) ) - implicit lazy val indicationRowImp: ObjectType[Backend, IndicationRow] = - deriveObjectType[Backend, IndicationRow]( - ObjectTypeDescription( - "Indication information linking a drug or clinical candidate molecule to a disease" - ), - DocumentField( - "maxPhaseForIndication", - "Maximum clinical trial phase for this drug-disease indication. [Values: -1: `Unknown`, 0: `Phase 0`, 0.5: `Phase I (Early)`, 1: `Phase I`, 2: `Phase II`, 3: `Phase III`, 4: `Phase IV`]" - ), - DocumentField("references", "Reference information supporting the indication"), - ReplaceField( - "disease", - Field( - "disease", - diseaseImp, - description = Some("Potential indication disease entity"), - resolve = r => diseasesFetcher.defer(r.value.disease) - ) - ) - ) - - implicit lazy val indicationsImp: ObjectType[Backend, Indications] = - deriveObjectType[Backend, Indications]( - ObjectTypeDescription("Collection of indications for a drug or clinical candidate molecule"), - ExcludeFields("id"), - RenameField("indications", "rows"), - RenameField("indicationCount", "count"), - DocumentField("indications", "List of potential indication entries"), - DocumentField("indicationCount", "Total number of potential indications"), - DocumentField("approvedIndications", "List of approved indication identifiers") - ) - implicit lazy val resourceScoreImp: ObjectType[Backend, ResourceScore] = deriveObjectType[Backend, ResourceScore]( ObjectTypeDescription("Score from a specific datasource"), @@ -1574,20 +1487,14 @@ object Objects extends OTLogging { DocumentField("name", "Generic name of the drug molecule"), DocumentField("synonyms", "List of alternative names for the drug"), DocumentField("tradeNames", "List of brand names for the drug"), - DocumentField("yearOfFirstApproval", "Year when the drug received regulatory approval"), DocumentField( "drugType", "Classification of the molecule's therapeutic category or chemical class (e.g. Antibody)" ), DocumentField( - "maximumClinicalTrialPhase", - "Highest clinical trial phase reached by the drug or clinical candidate molecule. [Values: -1: `Unknown`, 0: `Phase 0`, 0.5: `Phase I (Early)`, 1: `Phase I`, 2: `Phase II`, 3: `Phase III`, 4: `Phase IV`]" + "maximumClinicalStage", + "Highest clinical stage reached by the drug or clinical candidate molecule" ), - DocumentField("isApproved", - "Flag indicating whether the drug has received regulatory approval" - ), - DocumentField("hasBeenWithdrawn", "Flag indicating whether the drug was removed from market"), - DocumentField("blackBoxWarning", "Flag indicating whether the drug has safety warnings"), DocumentField("crossReferences", "Cross-reference information for this molecule from external databases" ), @@ -1611,14 +1518,6 @@ object Objects extends OTLogging { ) ), AddFields( - Field( - "approvedIndications", - OptionType(ListType(StringType)), - description = Some("Indications for which there is a phase IV clinical trial"), - resolve = r => - DeferredValue(indicationFetcher.deferOpt(r.value.id)) - .map(_.flatMap(_.approvedIndications)) - ), Field( "drugWarnings", ListType(drugWarningsImp), @@ -1674,32 +1573,6 @@ object Objects extends OTLogging { ), resolve = ctx => ctx.ctx.getMechanismsOfAction(ctx.value.id) ), - Field( - "indications", - OptionType(indicationsImp), - description = Some( - "Investigational and approved indications curated from clinical trial records and " + - "post-marketing package inserts" - ), - resolve = ctx => DeferredValue(indicationFetcher.deferOpt(ctx.value.id)) - ), - Field( - "knownDrugs", - OptionType(knownDrugsImp), - description = Some( - "Curated Clinical trial records and and post-marketing package inserts " + - "with a known mechanism of action" - ), - arguments = freeTextQuery :: pageSize :: cursor :: Nil, - complexity = Some(complexityCalculator(pageSize)), - resolve = ctx => - ctx.ctx.getKnownDrugs( - ctx.arg(freeTextQuery).getOrElse(""), - Map("drugId.raw" -> ctx.value.id), - ctx.arg(pageSize), - ctx.arg(cursor) - ) - ), Field( "adverseEvents", OptionType(adverseEventsImp), @@ -1719,33 +1592,181 @@ object Objects extends OTLogging { arguments = pageArg :: Nil, complexity = Some(complexityCalculator(pageArg)), resolve = ctx => ctx.ctx.getPharmacogenomicsByDrug(ctx.value.id) - ) - ), - ReplaceField( - "linkedDiseases", - Field( - "linkedDiseases", - OptionType(linkedDiseasesImp), - description = Some("List of molecule potential indications"), - resolve = r => r.value.linkedDiseases - ) - ), - ReplaceField( - "linkedTargets", + ), Field( - "linkedTargets", - OptionType(linkedTargetsImp), - description = Some("List of molecule targets based on molecule mechanism of action"), - resolve = ctx => { - val moa: Future[MechanismsOfAction] = ctx.ctx.getMechanismsOfAction(ctx.value.id) - val targets: Future[Seq[String]] = - moa.map(m => m.rows.flatMap(r => r.targets.getOrElse(Seq.empty))) - targets.map(t => LinkedIds(t.size, t)) - } + "indications", + clinicalIndicationsFromDrugImp, + description = Some( + "Clinical indications for this drug as reported by clinical trial records." + ), + arguments = Nil, + resolve = ctx => ctx.ctx.getClinicalIndicationsByDrug(ctx.value.id) ) ) ) + implicit val clinRepDiseaseListItemImp: ObjectType[Backend, ClinicalDiseaseListItem] = + deriveObjectType[Backend, ClinicalDiseaseListItem]( + ReplaceField( + "diseaseId", + Field( + "disease", + OptionType(diseaseImp), + description = Some(""), + resolve = ctx => + val tId: String = ctx.value.diseaseId + logger.debug(s"finding disease $tId") + + tId match { + case "" => None + case _ => diseasesFetcher.deferOpt(tId) + } + ) + ) + ) + + implicit val clinRepDrugListItemImp: ObjectType[Backend, ClinRepDrugListItem] = + deriveObjectType[Backend, ClinRepDrugListItem]( + ReplaceField( + "drugId", + Field( + "drug", + OptionType(drugImp), + description = Some( + "" + ), + resolve = ctx => { + val id = ctx.value.drugId + logger.debug(s"finding drug $id") + + id match { + case "" => None + case _ => drugsFetcher.deferOpt(id) + } + } + ) + ) + ) + + implicit val clinicalReportImp: ObjectType[Backend, ClinicalReport] = + deriveObjectType[Backend, ClinicalReport]() + + implicit val clinicalIndicationFromDiseaseImp: ObjectType[Backend, ClinicalIndication] = + deriveObjectType[Backend, ClinicalIndication]( + ObjectTypeName("ClinicalIndicationFromDisease"), + ExcludeFields("diseaseId"), + ReplaceField( + "drugId", + Field( + "drug", + OptionType(drugImp), + description = Some( + "" + ), + resolve = ctx => { + val id: Option[String] = ctx.value.drugId + logger.debug(s"finding drug $id") + + id match { + case None => None + case Some(dId) => drugsFetcher.deferOpt(dId) + } + } + ) + ), + ReplaceField( + "clinicalReportIds", + Field( + "clinicalReports", + ListType(clinicalReportImp), + description = Some( + "" + ), + resolve = ctx => { + val ids = ctx.value.clinicalReportIds + logger.debug(s"finding clinical reports for ids ${ids.mkString(",")}") + clinicalReportFetcher.deferSeqOpt(ids) + } + ) + ) + ) + + implicit val clinicalIndicationFromDrugImp: ObjectType[Backend, ClinicalIndication] = + deriveObjectType[Backend, ClinicalIndication]( + ObjectTypeName("ClinicalIndicationFromDrug"), + ExcludeFields("drugId"), + ReplaceField( + "diseaseId", + Field( + "disease", + OptionType(diseaseImp), + description = Some(""), + resolve = ctx => + ctx.value.diseaseId match { + case Some(tId) => + logger.debug(s"finding disease $tId") + diseasesFetcher.deferOpt(tId) + case None => None + } + ) + ), + ReplaceField( + "clinicalReportIds", + Field( + "clinicalReports", + ListType(clinicalReportImp), + description = Some( + "" + ), + resolve = ctx => { + val ids = ctx.value.clinicalReportIds + logger.debug(s"finding clinical reports for ids ${ids.mkString(",")}") + clinicalReportFetcher.deferSeqOpt(ids) + } + ) + ) + ) + + implicit val clinicalTargetImp: ObjectType[Backend, ClinicalTarget] = + deriveObjectType[Backend, ClinicalTarget]( + ObjectTypeName("ClinicalTargetFromTarget"), + ExcludeFields("targetId"), + ReplaceField( + "drugId", + Field( + "drug", + OptionType(drugImp), + description = Some( + "" + ), + resolve = ctx => { + val id: Option[String] = ctx.value.drugId + logger.debug(s"finding drug $id") + + id match { + case None => None + case Some(dId) => drugsFetcher.deferOpt(dId) + } + } + ) + ), + ReplaceField( + "clinicalReportIds", + Field( + "clinicalReports", + ListType(clinicalReportImp), + description = Some( + "" + ), + resolve = ctx => { + val ids = ctx.value.clinicalReportIds + logger.debug(s"finding clinical reports for ids ${ids.mkString(",")}") + clinicalReportFetcher.deferSeqOpt(ids) + } + ) + ) + ) + implicit val datasourceSettingsImp: ObjectType[Backend, DatasourceSettings] = deriveObjectType[Backend, DatasourceSettings]( ObjectTypeDescription( @@ -1789,6 +1810,18 @@ object Objects extends OTLogging { ObjectTypeDescription("Disease-specific database settings configuration"), DocumentField("associations", "Database table settings for disease associations") ) + implicit val clinicalIndicationSettingsImp: ObjectType[Backend, ClinicalIndicationSettings] = + deriveObjectType[Backend, ClinicalIndicationSettings]( + ObjectTypeDescription("Clinical indication database settings configuration"), + DocumentField("drugTable", "Database table settings for drug indications"), + DocumentField("diseaseTable", "Database table settings for disease indications") + ) + implicit val clinicalTargetSettingsImp: ObjectType[Backend, ClinicalTargetSettings] = + deriveObjectType[Backend, ClinicalTargetSettings]( + ObjectTypeDescription("Clinical indication database settings configuration"), + DocumentField("drugTable", "Database table settings for drug indications"), + DocumentField("targetTable", "Database table settings for disease indications") + ) implicit val harmonicSettingsImp: ObjectType[Backend, HarmonicSettings] = deriveObjectType[Backend, HarmonicSettings]( ObjectTypeDescription("Harmonic mean scoring settings for association calculations"), @@ -1805,7 +1838,9 @@ object Objects extends OTLogging { DocumentField("similarities", "Database table settings for entity similarities"), DocumentField("harmonic", "Harmonic mean scoring settings"), DocumentField("literature", "Database table settings for literature data"), - DocumentField("literatureIndex", "Database table settings for literature index") + DocumentField("literatureIndex", "Database table settings for literature index"), + DocumentField("clinicalIndication", "Database table settings for clinical indications"), + DocumentField("clinicalTarget", "Database table settings for clinical target") ) implicit val evidenceSourceImp: ObjectType[Backend, EvidenceSource] = deriveObjectType[Backend, EvidenceSource]( @@ -1870,87 +1905,6 @@ object Objects extends OTLogging { DocumentField("id", "Gene ontology term identifier [bioregistry:go]"), DocumentField("name", "Gene ontology term name") ) - implicit val knownDrugReferenceImp: ObjectType[Backend, KnownDrugReference] = - deriveObjectType[Backend, KnownDrugReference]( - ObjectTypeDescription("Reference information for known drug indications"), - DocumentField("source", "Source of the reference (e.g., PubMed, FDA, package inserts)"), - DocumentField("ids", "List of reference identifiers"), - DocumentField("urls", "List of URLs linking to the reference") - ) - - implicit val URLImp: ObjectType[Backend, URL] = deriveObjectType[Backend, URL]( - ObjectTypeDescription("Source URL for clinical trials, FDA and package inserts"), - DocumentField("url", "List of web addresses that support the drug/indication pair"), - DocumentField("name", "List of human readable names for the reference source") - ) - - implicit val knownDrugImp: ObjectType[Backend, KnownDrug] = deriveObjectType[Backend, KnownDrug]( - ObjectTypeDescription( - "For any approved or clinical candidate drug, includes information on the target gene product and indication. It is derived from the ChEMBL target/disease evidence." - ), - DocumentField( - "approvedSymbol", - "Approved gene symbol of the target modulated by the drug" - ), - DocumentField("approvedName", - "Approved full name of the gene or gene product modulated by the drug" - ), - DocumentField("label", "Disease label for the condition being treated"), - DocumentField("prefName", "Commonly used name for the drug"), - DocumentField("drugType", "Classification of the modality of the drug (e.g. Small molecule)"), - DocumentField("targetId", "Open Targets target identifier"), - DocumentField("diseaseId", "Open Targets disease identifier"), - DocumentField("drugId", "Open Targets molecule identifier"), - DocumentField( - "phase", - "Clinical development stage of the drug. [Values: -1: `Unknown`, 0: `Phase 0`, 0.5: `Phase I (Early)`, 1: `Phase I`, 2: `Phase II`, 3: `Phase III`, 4: `Phase IV`]" - ), - DocumentField("mechanismOfAction", "Drug pharmacological action"), - DocumentField("status", "Clinical trial status for the drug/indication pair"), - DocumentField("targetClass", - "Classification category of the drug's biological target (e.g. Enzyme)" - ), - DocumentField("references", "Source urls for FDA or package inserts"), - DocumentField("ctIds", "Clinicaltrials.gov identifiers on entry trials"), - DocumentField("urls", "List of web addresses that support the drug/indication pair"), - AddFields( - Field( - "disease", - OptionType(diseaseImp), - description = Some("Curated disease indication entity"), - resolve = r => diseasesFetcher.deferOpt(r.value.diseaseId) - ), - Field( - "target", - OptionType(targetImp), - description = Some("Drug target entity based on curated mechanism of action"), - resolve = r => targetsFetcher.deferOpt(r.value.targetId) - ), - Field( - "drug", - OptionType(drugImp), - description = Some("Curated drug entity"), - resolve = r => drugsFetcher.deferOpt(r.value.drugId) - ) - ) - ) - - implicit val knownDrugsImp: ObjectType[Backend, KnownDrugs] = - deriveObjectType[Backend, KnownDrugs]( - ObjectTypeDescription( - "Set of clinical precedence for drugs with investigational or " + - "approved indications targeting gene products according to their curated mechanism of action" - ), - DocumentField("uniqueDrugs", "Total unique drug or clinical candidate molecules"), - DocumentField("uniqueDiseases", "Total unique diseases or phenotypes"), - DocumentField( - "uniqueTargets", - "Total unique known mechanism of action targets" - ), - DocumentField("cursor", "Opaque pagination cursor to request the next page of results"), - DocumentField("count", "Total number of entries"), - DocumentField("rows", "Clinical precedence entries with known mechanism of action") - ) lazy val mUnionType: UnionType[Backend] = UnionType( @@ -2742,10 +2696,10 @@ object Objects extends OTLogging { DocumentField("biologicalModelAllelicComposition", "Allelic composition of the model organism"), DocumentField("confidence", "Confidence qualifier on the reported evidence"), DocumentField( - "clinicalPhase", - "Phase of the clinical trial. [Values: -1: `Unknown`, 0: `Phase 0`, 0.5: `Phase I (Early)`, 1: `Phase I`, 2: `Phase II`, 3: `Phase III`, 4: `Phase IV`]" + "clinicalStage", + "Clinical stage of the drug-disease pair" ), - DocumentField("clinicalStatus", "Current stage of a clinical study"), + DocumentField("clinicalReportId", "Identifier of the clinical report"), DocumentField("clinicalSignificances", "Standard terms to define clinical significance"), DocumentField("resourceScore", "Score provided by datasource indicating strength of target-disease association" @@ -2785,10 +2739,8 @@ object Objects extends OTLogging { DocumentField("betaConfidenceIntervalLower", "Lower value of the confidence interval"), DocumentField("betaConfidenceIntervalUpper", "Upper value of the confidence interval"), DocumentField("studyStartDate", "Start date of study in a YYYY-MM-DD format"), - DocumentField("studyStopReason", "Reason why a study has been stopped"), - DocumentField("studyStopReasonCategories", - "Predicted reason(s) why the study has been stopped based on studyStopReason" - ), + DocumentField("trialWhyStopped", "Reason why the trial was stopped, as reported"), + DocumentField("trialStopReasonCategories", "Categorised reason(s) why the trial was stopped"), DocumentField("cellLineBackground", "Background of the derived cell lines"), DocumentField("contrast", "Experiment contrast"), DocumentField("crisprScreenLibrary", diff --git a/conf/application.conf b/conf/application.conf index bf37bfa8..44c3c21e 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -32,6 +32,24 @@ ot { label = "Literature index ocurrences table" name = "literature_index" } + clinicalReport { + label = "Clinical report table" + name = "clinical_report" + } + clinicalIndication { + drugTable { + label = "Clinical indication drug table" + name = "clinical_indication_drug" + } + diseaseTable { + label = "Clinical indication disease table" + name = "clinical_indication_disease" + } + } + clinicalTarget { + label = "Clinical target table" + name = "clinical_target" + } similarities { label = "Similarity table for the Word2Vec model" name = "ml_w2v" diff --git a/conf/logback.xml b/conf/logback.xml index 463b948d..154a98f3 100644 --- a/conf/logback.xml +++ b/conf/logback.xml @@ -24,12 +24,12 @@ - - - - - - + + + + + + diff --git a/production.xml b/production.xml index 38a077fd..a805757e 100644 --- a/production.xml +++ b/production.xml @@ -32,13 +32,13 @@ - - - - - - - + + + + + + + diff --git a/queries.graphql b/queries.graphql index 75b1e097..625f404f 100644 --- a/queries.graphql +++ b/queries.graphql @@ -34,13 +34,6 @@ query test { } } } - knownDrugs { - rows { - drugType - targetId - diseaseId - } - } } search(queryString: "erectile") { total @@ -168,12 +161,6 @@ query test { topCategory } } - knownDrugs { - uniqueDrugs - uniqueDiseases - uniqueTargets - count - } cancerBiomarkers { uniqueDrugs uniqueDiseases @@ -205,16 +192,6 @@ query test { name disease } - knownDrugs { - uniqueDrugs - uniqueDiseases - uniqueTargets - count - rows { - drugType - targetId - } - } relatedDiseases { maxCountAOrB count @@ -225,17 +202,8 @@ query test { name synonyms tradeNames - yearOfFirstApproval drugType - maximumClinicalTrialPhase - hasBeenWithdrawn - withdrawnNotice { - classes - countries - reasons - year - } - internalCompound + maximumClinicalStage mechanismsOfAction { uniqueActionTypes uniqueTargetTypes @@ -252,7 +220,6 @@ query test { } } } - blackBoxWarning indications { count rows { @@ -277,40 +244,5 @@ query test { criticalValue } } - knownDrugs { - uniqueDrugs - uniqueDiseases - uniqueTargets - count - rows { - drugType - targetId - diseaseId - phase - mechanismOfAction - status - activity - targetClass - ctIds - } - } } - knwonDrugs: target(ensemblId: "ENSG00000105650") { - approvedSymbol - knownDrugs(sortField:"label", sortOrder:Asc) { - count - rows { - drugType - targetId - diseaseId - approvedSymbol - label - prefName - mechanismOfAction - drug { - synonyms - } - } - } - } } diff --git a/test/controllers/GqlTest.scala b/test/controllers/GqlTest.scala index 36c17655..77851aa3 100644 --- a/test/controllers/GqlTest.scala +++ b/test/controllers/GqlTest.scala @@ -138,7 +138,7 @@ class GqlTest "return valid response for Bibliography summary fragment" taggedAs (IntegrationTestTag, ClickhouseTestTag ) in { - testQueryAgainstGqlEndpoint(DiseaseDrugFragment("Bibliography_BibliographySummaryFragment")) + testQueryAgainstGqlEndpoint(DrugFragment("Bibliography_BibliographySummaryFragment")) } "return valid response for Bibliography similar entities summary fragment" taggedAs ( IntegrationTestTag, @@ -330,9 +330,6 @@ class GqlTest "return a valid response for disease profile header" taggedAs IntegrationTestTag in { testQueryAgainstGqlEndpoint(DiseaseFragment("DiseasePage_ProfileHeader")) } - "return a valid response for known drugs" taggedAs IntegrationTestTag in { - testQueryAgainstGqlEndpoint(DiseaseDrugFragment("KnownDrugs_KnownDrugsSummaryFragment")) - } "return a valid response for ontology summary fragment" taggedAs IntegrationTestTag in { testQueryAgainstGqlEndpoint(DiseaseFragment("Ontology_OntologySummaryFragment")) } @@ -428,12 +425,6 @@ class GqlTest } } - "Known Drugs query" must { - "return a valid response" taggedAs IntegrationTestTag in { - testQueryAgainstGqlEndpoint(KnownDrugs("KnownDrugs_KnownDrugsQuery")) - } - } - "MolecularInteractions" must { "return a valid response for interaction stats" taggedAs IntegrationTestTag in { testQueryAgainstGqlEndpoint(Target("MolecularInteractions_InteractionsStats")) @@ -522,9 +513,6 @@ class GqlTest "return valid gene ontology response" taggedAs IntegrationTestTag in { testQueryAgainstGqlEndpoint(TargetFragment("GeneOntology_GeneOntologySummary")) } - "return valid known drugs fragment response" taggedAs IntegrationTestTag in { - testQueryAgainstGqlEndpoint(TargetFragment("KnownDrugs_KnownDrugsSummary")) - } "return valid response for molecular interactions" taggedAs IntegrationTestTag in { testQueryAgainstGqlEndpoint(TargetFragment("MolecularInteractions_InteractionsSummary")) } diff --git a/test/inputs/GqlCase.scala b/test/inputs/GqlCase.scala index 683450b2..99b7fa40 100644 --- a/test/inputs/GqlCase.scala +++ b/test/inputs/GqlCase.scala @@ -151,12 +151,6 @@ case class DiseaseFragment(file: String) extends AbstractDisease with GqlFragmen .replace("xyz", "$efoId") } -case class DiseaseDrugFragment(file: String) extends AbstractDisease with GqlFragment[String] { - def generateFragmentQuery: String = - s"$fragmentQuery query DiseaseFragment(xyz: String!) { disease(efoId: xyz) { knownDrugs { rows { drug { ...$fragmentName } } } } }" - .replace("xyz", "$efoId") -} - /* This is a fragment on disease which takes a gene as an argument. Used on the FE to create summary information. @@ -190,19 +184,6 @@ case class GeneOntology(file: String) extends GqlCase[List[String]] { """ } -case class KnownDrugs(file: String) extends GqlCase[String] { - val inputGenerator = geneGenerator - - def generateVariables(target: String) = - s""" - "variables": { - "ensgId": "$target", - "cursor": null, - "freeTextQuery": null - } - """ -} - case class Search(file: String) extends GqlCase[String] { override val inputGenerator = searchGenerator diff --git a/test/resources/gqlQueries/APIPage_DrugAnnotation.gql b/test/resources/gqlQueries/APIPage_DrugAnnotation.gql index 38a4c675..385c8249 100644 --- a/test/resources/gqlQueries/APIPage_DrugAnnotation.gql +++ b/test/resources/gqlQueries/APIPage_DrugAnnotation.gql @@ -2,9 +2,6 @@ query drugApprovalWithdrawnWarningData { drug(chemblId: "CHEMBL118") { name id - isApproved - hasBeenWithdrawn - blackBoxWarning drugWarnings { warningType description diff --git a/test/resources/gqlQueries/Chembl_ChemblQuery.gql b/test/resources/gqlQueries/Chembl_ChemblQuery.gql index d9c86369..99be3c46 100644 --- a/test/resources/gqlQueries/Chembl_ChemblQuery.gql +++ b/test/resources/gqlQueries/Chembl_ChemblQuery.gql @@ -4,7 +4,7 @@ query ChemblQuery($ensemblId: String!, $efoId: String!, $size: Int!, $cursor: St chembl: evidences( ensemblIds: [$ensemblId] enableIndirect: true - datasourceIds: ["chembl"] + datasourceIds: ["clinical_precedence"] size: $size cursor: $cursor ) { @@ -36,16 +36,11 @@ query ChemblQuery($ensemblId: String!, $efoId: String!, $size: Int!, $cursor: St variantEffect directionOnTrait targetFromSourceId - clinicalPhase - clinicalStatus + clinicalStage + clinicalReportId studyStartDate - studyStopReason - studyStopReasonCategories - cohortPhenotypes - urls { - niceName - url - } + trialWhyStopped + trialStopReasonCategories } } } diff --git a/test/resources/gqlQueries/Chembl_ChemblSummaryFragment.gql b/test/resources/gqlQueries/Chembl_ChemblSummaryFragment.gql index e0a749db..481f8060 100644 --- a/test/resources/gqlQueries/Chembl_ChemblSummaryFragment.gql +++ b/test/resources/gqlQueries/Chembl_ChemblSummaryFragment.gql @@ -2,7 +2,7 @@ fragment ChemblSummaryFragment on Disease { chembl: evidences( ensemblIds: [$ensgId] enableIndirect: true - datasourceIds: ["chembl"] + datasourceIds: ["clinical_precedence"] size: 0 ) { count diff --git a/test/resources/gqlQueries/DrugPage_ProfileHeader.gql b/test/resources/gqlQueries/DrugPage_ProfileHeader.gql index d17bab3c..f5cb3f04 100644 --- a/test/resources/gqlQueries/DrugPage_ProfileHeader.gql +++ b/test/resources/gqlQueries/DrugPage_ProfileHeader.gql @@ -10,10 +10,7 @@ fragment DrugProfileHeaderFragment on Drug { id name } - isApproved - hasBeenWithdrawn - blackBoxWarning - maximumClinicalTrialPhase + maximumClinicalStage tradeNames yearOfFirstApproval } diff --git a/test/resources/gqlQueries/DrugWarnings_DrugWarningsQuery.gql b/test/resources/gqlQueries/DrugWarnings_DrugWarningsQuery.gql index 2f282f0f..ab19b599 100644 --- a/test/resources/gqlQueries/DrugWarnings_DrugWarningsQuery.gql +++ b/test/resources/gqlQueries/DrugWarnings_DrugWarningsQuery.gql @@ -16,7 +16,5 @@ query DrugWarningsQuery($chemblId: String!) { url } } - hasBeenWithdrawn - blackBoxWarning } } diff --git a/test/resources/gqlQueries/DrugWarnings_DrugWarningsSummaryFragment.gql b/test/resources/gqlQueries/DrugWarnings_DrugWarningsSummaryFragment.gql index c66be325..a8ff1f2a 100644 --- a/test/resources/gqlQueries/DrugWarnings_DrugWarningsSummaryFragment.gql +++ b/test/resources/gqlQueries/DrugWarnings_DrugWarningsSummaryFragment.gql @@ -1,4 +1,3 @@ fragment DrugWarningsSummaryFragment on Drug { - hasBeenWithdrawn - blackBoxWarning + id } diff --git a/test/resources/gqlQueries/KnownDrugs_KnownDrugsQuery.gql b/test/resources/gqlQueries/KnownDrugs_KnownDrugsQuery.gql deleted file mode 100644 index 8621c918..00000000 --- a/test/resources/gqlQueries/KnownDrugs_KnownDrugsQuery.gql +++ /dev/null @@ -1,40 +0,0 @@ -query KnownDrugsQuery( - $ensgId: String! - $cursor: String - $freeTextQuery: String - $size: Int = 10 -) { - target(ensemblId: $ensgId) { - id - knownDrugs(cursor: $cursor, freeTextQuery: $freeTextQuery, size: $size) { - count - cursor - rows { - phase - status - urls { - name - url - } - disease { - id - name - } - drug { - id - name - mechanismsOfAction { - rows { - actionType - targets { - id - } - } - } - } - drugType - mechanismOfAction - } - } - } -} diff --git a/test/resources/gqlQueries/KnownDrugs_KnownDrugsSummary.gql b/test/resources/gqlQueries/KnownDrugs_KnownDrugsSummary.gql deleted file mode 100644 index f78e7e48..00000000 --- a/test/resources/gqlQueries/KnownDrugs_KnownDrugsSummary.gql +++ /dev/null @@ -1,7 +0,0 @@ -fragment TargetKnownDrugsSummaryFragment on Target { - knownDrugs { - count - uniqueDrugs - uniqueDiseases - } -} diff --git a/test/resources/gqlQueries/KnownDrugs_KnownDrugsSummaryFragment.gql b/test/resources/gqlQueries/KnownDrugs_KnownDrugsSummaryFragment.gql deleted file mode 100644 index dba9c0b1..00000000 --- a/test/resources/gqlQueries/KnownDrugs_KnownDrugsSummaryFragment.gql +++ /dev/null @@ -1,7 +0,0 @@ -fragment DrugKnownDrugsSummaryFragment on Drug { - knownDrugs { - count - uniqueTargets - uniqueDiseases - } -} diff --git a/test/resources/gqlQueries/SearchPage_SearchPageQuery.gql b/test/resources/gqlQueries/SearchPage_SearchPageQuery.gql index 46d40dc8..80d934ec 100644 --- a/test/resources/gqlQueries/SearchPage_SearchPageQuery.gql +++ b/test/resources/gqlQueries/SearchPage_SearchPageQuery.gql @@ -71,8 +71,7 @@ query SearchPageQuery( description name drugType - maximumClinicalTrialPhase - hasBeenWithdrawn + maximumClinicalStage indications { rows { disease {