Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions app/Actions/Coconut/SearchMolecule.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ private function determineQueryType($query)
return 'parttialinchikey';
}

// Check for molecular formula (must match pattern and not contain SMILES-specific characters)
// Molecular formulas contain only element symbols and numbers (e.g., C6H12O6, H2O)
if (preg_match('/^([A-Z][a-z]?\d*)+$/', $query) && ! preg_match('/[()@\[\]\/\\\\=#\-+]/', $query)) {
// Molecular formulas use condensed notation (e.g., C6H12O6, H2O), not SMILES chains
// like C1CCCCC1 or CCO where the same element appears in consecutive tokens.
if ($this->looksLikeMolecularFormula($query)) {
return 'molecularformula';
}

Expand All @@ -139,6 +139,32 @@ private function determineQueryType($query)
return 'text';
}

/**
* True when the query looks like a Hill-order molecular formula, not a SMILES string.
*/
private function looksLikeMolecularFormula(string $query): bool
{
if (! preg_match('/^([A-Z][a-z]?\d*)+$/', $query) || preg_match('/[()@\[\]\/\\\\=#\-+]/', $query)) {
return false;
}

preg_match_all('/[A-Z][a-z]?\d*/', $query, $matches);

$previousElement = null;
foreach ($matches[0] as $token) {
preg_match('/^([A-Z][a-z]?)/', $token, $elementMatch);
$element = $elementMatch[1];

if ($element === $previousElement) {
return false;
}

$previousElement = $element;
}

return true;
}

/**
* Apply status filter to SQL query.
* Only adds a filter when status is 'approved' or 'revoked'.
Expand Down
2 changes: 1 addition & 1 deletion resources/views/livewire/welcome.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ class="h-full w-full border-transparent py-2 pl-8 pr-3 text-sm text-gray-900 pla
</div>
<div class="flex items-center md:ml-1">
<button type="submit"
@click="window.location.href = '/search?q=' + encodeURIComponent(query) + '&tagType=citation&type=tags&activeTab=citation'"
@click="window.location.href = '/search?q=' + encodeURIComponent(query) + '&tagType=citations&type=tags&activeTab=citations'"
class="rounded-md bg-secondary-dark px-3.5 py-1.5 text-base font-semibold leading-7 text-white shadow-sm hover:bg-secondary-light focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 mr-3"><svg
xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
Expand Down
35 changes: 35 additions & 0 deletions tests/Unit/SearchMoleculeQueryTypeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Tests\Unit;

use App\Actions\Coconut\SearchMolecule;
use PHPUnit\Framework\TestCase;
use ReflectionMethod;

class SearchMoleculeQueryTypeTest extends TestCase
{
/**
* @dataProvider queryTypeProvider
*/
public function test_determine_query_type(string $query, string $expectedType): void
{
$search = new SearchMolecule;
$method = new ReflectionMethod(SearchMolecule::class, 'determineQueryType');
$method->setAccessible(true);

$this->assertSame($expectedType, $method->invoke($search, $query));
}

public static function queryTypeProvider(): array
{
return [
'cyclohexane smiles' => ['C1CCCCC1', 'smiles'],
'benzene smiles' => ['c1ccccc1', 'smiles'],
'molecular formula' => ['C6H12', 'molecularformula'],
'glucose formula' => ['C6H12O6', 'molecularformula'],
'water formula' => ['H2O', 'molecularformula'],
'cnp identifier' => ['CNP0228556', 'identifier'],
'compound name' => ['caffeine', 'text'],
];
}
}
Loading