Skip to content

Commit

Permalink
feat: enabled basic implementation of search
Browse files Browse the repository at this point in the history
  • Loading branch information
CS76 committed Nov 28, 2024
1 parent d97501d commit 4dbede1
Show file tree
Hide file tree
Showing 11 changed files with 9,446 additions and 83 deletions.
70 changes: 20 additions & 50 deletions app/Actions/Coconut/SearchMolecule.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public function query($query, $size, $type, $sort, $tagType, $page)
$this->query = $query;
$this->size = $size;
$this->type = $type;

$this->sort = $sort;
$this->tagType = $tagType;
$this->page = $page;
Expand All @@ -61,10 +62,15 @@ public function query($query, $size, $type, $sort, $tagType, $page)
}
$queryType = strtolower($queryType);

$filterMap = $this->getFilterMap();
$filterMap = getFilterMap();

if ($queryType == 'tags') {
$results = $this->buildTagsStatement($offset);
} elseif ($queryType == 'filters') {
$statement = $this->buildStatement($queryType, $offset, $filterMap);
if ($statement) {
$results = $this->executeQuery($statement);
}
} else {
$statement = $this->buildStatement($queryType, $offset, $filterMap);
if ($statement) {
Expand Down Expand Up @@ -112,47 +118,6 @@ private function determineQueryType($query)
return 'text';
}

/**
* Return a mapping of filter codes to database columns.
*/
private function getFilterMap()
{
return [
'mf' => 'molecular_formula',
'mw' => 'molecular_weight',
'hac' => 'heavy_atom_count',
'tac' => 'total_atom_count',
'arc' => 'aromatic_ring_count',
'rbc' => 'rotatable_bond_count',
'mrc' => 'minimal_number_of_rings',
'fc' => 'formal_charge',
'cs' => 'contains_sugar',
'crs' => 'contains_ring_sugars',
'cls' => 'contains_linear_sugars',
'npl' => 'np_likeness_score',
'alogp' => 'alogp',
'topopsa' => 'topo_psa',
'fsp3' => 'fsp3',
'hba' => 'h_bond_acceptor_count',
'hbd' => 'h_bond_donor_count',
'ro5v' => 'rule_of_5_violations',
'lhba' => 'lipinski_h_bond_acceptor_count',
'lhbd' => 'lipinski_h_bond_donor_count',
'lro5v' => 'lipinski_rule_of_5_violations',
'ds' => 'found_in_databases',
'class' => 'chemical_class',
'subclass' => 'chemical_sub_class',
'superclass' => 'chemical_super_class',
'parent' => 'direct_parent_classification',
'np_class' => 'np_classifier_class',
'np_superclass' => 'np_classifier_superclass',
'np_pathway' => 'np_classifier_pathway',
'np_glycoside' => 'np_classifier_is_glycoside',
'org' => 'organism',
'cite' => 'ciatation',
];
}

/**
* Build the SQL statement based on the query type.
*/
Expand Down Expand Up @@ -268,19 +233,23 @@ private function buildTagsStatement($offset)
private function buildFiltersStatement($filterMap)
{
$orConditions = explode('OR', $this->query);
$statement = 'SELECT molecule_id as id, COUNT(*) OVER ()
FROM properties WHERE ';

foreach ($orConditions as $index => $orCondition) {
if ($index > 0) {
$statement = 'SELECT properties.molecule_id as id, COUNT(*) OVER ()
FROM properties
INNER JOIN molecules ON properties.molecule_id = molecules.id
WHERE molecules.active = TRUE
AND NOT (molecules.is_parent = TRUE AND molecules.has_variants = TRUE)
AND ';

foreach ($orConditions as $outerIndex => $orCondition) {
if ($outerIndex > 0) {
$statement .= ' OR ';
}

$andConditions = explode(' ', trim($orCondition));
$statement .= '(';

foreach ($andConditions as $index => $andCondition) {
if ($index > 0) {
foreach ($andConditions as $innerIndex => $andCondition) {
if ($innerIndex > 0) {
$statement .= ' AND ';
}

Expand Down Expand Up @@ -362,9 +331,10 @@ private function executeQuery($statement)
$statement = "
SELECT identifier, canonical_smiles, annotation_level, name, iupac_name, organism_count, citation_count, geo_count, collection_count
FROM molecules
WHERE id = ANY (array[{$ids}])
WHERE id = ANY (array[{$ids}]) AND active = TRUE AND NOT (is_parent = TRUE AND has_variants = TRUE)
ORDER BY array_position(array[{$ids}], id);
";

if ($this->sort == 'recent') {
$statement .= ' ORDER BY created_at DESC';
}
Expand Down
143 changes: 143 additions & 0 deletions app/Console/Commands/GeneratePropertiesJson.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

class GeneratePropertiesJson extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'coconut:generate-properties-json';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate a JSON object with column metadata for the "properties" table, including type and other metadata';

/**
* Determine the appropriate type based on database column type
*/
private function determineType($dbType)
{
$numericTypes = ['integer', 'float', 'decimal', 'double precision', 'numeric'];
$stringTypes = ['text', 'jsonb', 'character varying', 'varchar'];

if (in_array($dbType, $numericTypes)) {
return 'range';
}
if ($dbType === 'boolean') {
return 'boolean';
}
if (in_array($dbType, $stringTypes)) {
return 'select';
}

return 'unknown';
}

public function handle()
{
$tableName = 'properties';

// Define columns to skip
$columnsToSkip = [
'id',
'molecule_id',
'molecular_formula',
'murcko_framework',
'created_at',
'updated_at',
];

// Fetch all columns from the table
$columns = DB::getSchemaBuilder()->getColumnListing($tableName);

// Filter out the columns to skip
$columns = array_diff($columns, $columnsToSkip);

$jsonOutput = [];

// Invert the filter map
$filterMap = getFilterMap();
$invertedFilterMap = array_flip($filterMap);

foreach ($columns as $column) {
// Fetch column type
$typeResult = DB::select('SELECT data_type FROM information_schema.columns WHERE table_name = ? AND column_name = ?', [$tableName, $column]);

if (empty($typeResult)) {
continue;
}

$dbType = $typeResult[0]->data_type;
$type = $this->determineType($dbType);

// Prepare the basic structure with type
$columnData = [
'type' => $type,
'label' => ucfirst(str_replace('_', ' ', $column)),
];

if ($type === 'range') {
// For numeric types, calculate min and max
$result = DB::table($tableName)
->selectRaw("MIN($column) as min, MAX($column) as max")
->first();

$columnData['range'] = [
'min' => $result->min,
'max' => $result->max,
];
} elseif ($type === 'boolean') {
// For boolean types, set true and false as possible values
$columnData['values'] = [true, false];
} elseif ($type === 'select') {
// For text or JSONB types, fetch unique values
$uniqueValues = DB::table($tableName)
->select($column)
->distinct()
->pluck($column)
->filter()
->values()
->toArray();

$columnData['unique_values'] = $uniqueValues;
}

// Determine the key for the JSON output
$key = $invertedFilterMap[$column] ?? $column;

// Add the column data using the determined key
$jsonOutput[$key] = $columnData;
}

// Convert the output to JSON
$json = json_encode($jsonOutput, JSON_PRETTY_PRINT);

// Output the JSON to the console
$this->info($json);

// Optionally save the JSON to a file
$filePath = public_path('assets/properties_metadata.json');
if (! file_exists(dirname($filePath))) {
mkdir(dirname($filePath), 0777, true);
}
file_put_contents($filePath, $json);

$this->info('JSON metadata saved to public/assets/properties_metadata.json');

// Provide some statistics
$this->info(sprintf(
"Generated metadata for %d columns:\n%s",
count($jsonOutput),
implode(', ', array_keys($jsonOutput))
));
}
}
38 changes: 38 additions & 0 deletions app/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,41 @@ function remove_italics_notation($text)

return $converted_text;
}

function getFilterMap()
{
return [
'mf' => 'molecular_formula',
'mw' => 'molecular_weight',
'hac' => 'heavy_atom_count',
'tac' => 'total_atom_count',
'arc' => 'aromatic_ring_count',
'rbc' => 'rotatable_bond_count',
'mrc' => 'minimal_number_of_rings',
'fc' => 'formal_charge',
'cs' => 'contains_sugar',
'crs' => 'contains_ring_sugars',
'cls' => 'contains_linear_sugars',
'npl' => 'np_likeness_score',
'alogp' => 'alogp',
'topopsa' => 'topo_psa',
'fsp3' => 'fsp3',
'hba' => 'h_bond_acceptor_count',
'hbd' => 'h_bond_donor_count',
'ro5v' => 'rule_of_5_violations',
'lhba' => 'lipinski_h_bond_acceptor_count',
'lhbd' => 'lipinski_h_bond_donor_count',
'lro5v' => 'lipinski_rule_of_5_violations',
'ds' => 'found_in_databases',
'class' => 'chemical_class',
'subclass' => 'chemical_sub_class',
'superclass' => 'chemical_super_class',
'parent' => 'direct_parent_classification',
'np_class' => 'np_classifier_class',
'np_superclass' => 'np_classifier_superclass',
'np_pathway' => 'np_classifier_pathway',
'np_glycoside' => 'np_classifier_is_glycoside',
'org' => 'organism',
'cite' => 'ciatation',
];
}
71 changes: 71 additions & 0 deletions app/Livewire/AdvancedSearch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace App\Livewire;

use Illuminate\Support\Facades\File;
use Livewire\Component;

class AdvancedSearch extends Component
{
public $schema = [];

public $searchParams = [];

public $isLoading = true;

public function mount()
{
$this->loadSchema();
}

public function loadSchema()
{
$jsonPath = public_path('assets/properties_metadata.json');

if (! File::exists($jsonPath)) {
$this->isLoading = false;

return;
}

$jsonContent = File::get($jsonPath);
$this->schema = json_decode($jsonContent, true) ?? [];

foreach ($this->schema as $key => $field) {
switch ($field['type']) {
case 'range':
// Store both 'min' and 'max' in searchParams for range types
$this->searchParams[$key] = [
'min' => $field['range']['min'],
'max' => $field['range']['max'],
];
break;

case 'boolean':
// Default to false for boolean fields
$this->searchParams[$key] = 'undefined';
break;

case 'select':
// Default to an empty array for select fields
$this->searchParams[$key] = [];
break;
}
}

$this->isLoading = false;
}

public function search()
{
$filteredParams = array_filter($this->searchParams, fn ($value) => ! empty($value));
$this->dispatch('search-performed', ['params' => $filteredParams]);
}

public function updateSearchParam() {}

public function render()
{
return view('livewire.advanced-search');
}
}
Loading

0 comments on commit 4dbede1

Please sign in to comment.