diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg index f5ef8bb1d..797b19666 100644 --- a/.github/badges/code_issues.svg +++ b/.github/badges/code_issues.svg @@ -1 +1 @@ -code issuescode issues23322332 \ No newline at end of file +code issuescode issues23232323 \ No newline at end of file diff --git a/src/ndi/+ndi/+ontology/RRID.m b/src/ndi/+ndi/+ontology/RRID.m index 7f3ed78d7..4490ad706 100644 --- a/src/ndi/+ndi/+ontology/RRID.m +++ b/src/ndi/+ndi/+ontology/RRID.m @@ -1,114 +1,92 @@ % Location: +ndi/+ontology/RRID.m -% Final Version: Supports lookup by RRID component only. Removed name search path. - classdef RRID < ndi.ontology % RRID - NDI Ontology object for Research Resource Identifiers (RRID). -% Inherits from ndi.ontology and implements lookupTermOrID for RRIDs -% using the scicrunch.org resolver API's .json endpoint. methods function obj = RRID() - % RRID - Constructor for the RRID ontology object. - end % constructor + % RRID - Constructor + end function [id, name, definition, synonyms] = lookupTermOrID(obj, term_or_id_or_name) - % LOOKUPTERMORID - Looks up an RRID via scicrunch.org resolver endpoint using its ID component. - % - % [ID, NAME, DEFINITION, SYNONYMS] = lookupTermOrID(OBJ, TERM_OR_ID_OR_NAME) - % - % Overrides the base class method. Expects TERM_OR_ID_OR_NAME to be the - % remainder of the RRID after the 'RRID:' prefix (e.g., 'SCR_006472', 'AB_123456'). - % It must resemble a valid RRID component structure. - % - % NOTE: Lookup by name (e.g., 'NCBI') is NOT currently supported for RRIDs - % due to lack of a confirmed reliable public search API endpoint accessible - % via standard web requests from MATLAB. Please provide the specific RRID - % component (e.g., SCR_006472). - % - % Queries the SciCrunch resolver endpoint: https://scicrunch.org/resolver/{RRID}.json - % - % Outputs: - % ID - The full RRID used for the lookup (e.g., 'RRID:SCR_006472'). - % NAME - The name found for the resource (typically from item.name). - % DEFINITION - The description found (typically from item.description). - % SYNONYMS - Cell array of synonyms, if found within the item.synonyms structure. - % - % See also: ndi.ontology.lookup (static dispatcher) - original_input_remainder = strtrim(term_or_id_or_name); - id = ''; name = ''; definition = ''; synonyms = {}; - - % --- Check if input looks like a valid RRID identifier component --- - % Basic regex for common RRID patterns like XXX_YYY or XXX:YYY - isLikelyID = ~isempty(regexp(original_input_remainder, '^[A-Za-z]+[_:]\S+$', 'once')); - - if ~isLikelyID - error('ndi:ontology:RRID:NameLookupUnsupported', ... - 'Input "%s" does not look like a valid RRID component (e.g., SCR_001234 or AB_123456). Lookup by name is not currently supported for RRID. Please provide the specific RRID component after the RRID: prefix.', original_input_remainder); - end - - % --- Proceed with ID Lookup --- full_rrid = ['RRID:' original_input_remainder]; - lookup_type_msg = sprintf('RRID "%s"', full_rrid); % For error context if helper fails + % We ignore the 5th and 6th outputs here for standard ontology compatibility + [id, name, definition, synonyms, ~, ~] = obj.performRridResolverLookup(full_rrid); + end + function [scientificName, commonName] = lookupSpecies(obj, term_or_id_or_name) + % LOOKUPSPECIES - Returns the scientific and common names for an RRID. + % + % [SCIENTIFIC, COMMON] = lookupSpecies(OBJ, RRID_COMPONENT) + % + % Example: [s, c] = obj.lookupSpecies('RGD_13508588') + % Returns: s = 'Rattus norvegicus', c = 'Rat' + + id_remainder = strtrim(term_or_id_or_name); + full_rrid = ['RRID:' id_remainder]; + try - % Call the private static helper using the full RRID - [id, name, definition, synonyms] = ndi.ontology.RRID.performRridResolverLookup(full_rrid); + [~, ~, ~, ~, scientificName, commonName] = obj.performRridResolverLookup(full_rrid); catch ME - % Wrap errors from the helper - baseME = MException('ndi:ontology:RRID:IDLookupFailed', ... - 'Lookup failed for %s.', lookup_type_msg); - baseME = addCause(baseME, ME); throw(baseME); + rethrow(ME); end - - end % function lookupTermOrID - - end % methods + end + end methods (Static, Access = private) - % --- Helper function for the actual RRID Lookup via Resolver --- - function [id, name, definition, synonyms] = performRridResolverLookup(full_rrid) - %PERFORMRRIDRESOLVERLOOKUP Fetches details using /resolver/{RRID}.json - arguments, full_rrid (1,:) char {mustBeNonempty}, end - id = ''; name = ''; definition = ''; synonyms = {}; % Initialize - - apiUrlBase = 'https://scicrunch.org/resolver/'; - try encoded_rrid_path_segment = urlencode(full_rrid); - catch ME_encode, error('ndi:ontology:RRID:EncodingError', 'Failed URL encode for %s: %s', full_rrid, ME_encode.message); - end - apiUrl = [apiUrlBase encoded_rrid_path_segment '.json']; - - ua = 'Mozilla/5.0 (MATLAB NDI Ontology Lookup)'; - options = weboptions('Timeout', 30, 'HeaderFields', {'Accept', 'application/json'; 'User-Agent', ua}); + function [id, name, definition, synonyms, scientificName, commonName] = performRridResolverLookup(full_rrid) + id = ''; name = ''; definition = ''; synonyms = {}; + scientificName = ''; commonName = ''; + + apiUrl = ['https://scicrunch.org/resolver/' urlencode(full_rrid) '.json']; + options = weboptions('Timeout', 30, 'HeaderFields', {'Accept', 'application/json'}); try response = webread(apiUrl, options); - - % Parse the response based on observed structure - if isstruct(response) && isfield(response, 'hits') && isfield(response.hits, 'hits') && ~isempty(response.hits.hits) && isstruct(response.hits.hits(1)) && isfield(response.hits.hits(1), 'x_source') && isstruct(response.hits.hits(1).x_source) && isfield(response.hits.hits(1).x_source, 'item') && isstruct(response.hits.hits(1).x_source.item) - item_data = response.hits.hits(1).x_source.item; + + if isstruct(response) && isfield(response, 'hits') && ~isempty(response.hits.hits) + source = response.hits.hits(1).x_source; + item_data = source.item; id = full_rrid; - - if isfield(item_data, 'name') && ~isempty(item_data.name) && (ischar(item_data.name) || isstring(item_data.name)), name = strtrim(char(item_data.name)); else, name = ''; end - if isfield(item_data, 'description') && ~isempty(item_data.description) && (ischar(item_data.description) || isstring(item_data.description)), definition = strtrim(char(item_data.description)); else, definition = ''; end - synonyms = {}; - if isfield(item_data, 'synonyms') && ~isempty(item_data.synonyms), syn_data = item_data.synonyms; if iscell(syn_data) && all(cellfun(@(c) ischar(c) || isstring(c), syn_data)), synonyms = cellfun(@char, syn_data, 'UniformOutput', false); elseif isstruct(syn_data), if isfield(syn_data, 'literal') && ~isempty(syn_data.literal), literal_data = syn_data.literal; if iscell(literal_data) && all(cellfun(@(c) ischar(c) || isstring(c), literal_data)), synonyms = cellfun(@char, literal_data, 'UniformOutput', false); elseif ischar(literal_data) || isstring(literal_data), synonyms = {char(literal_data)}; end; elseif isfield(syn_data, 'name'), name_data = {syn_data.name}; synonyms = cellfun(@char, name_data, 'UniformOutput', false); end; elseif ischar(syn_data) || isstring(syn_data), synonyms = {char(syn_data)}; end; if ~isempty(synonyms), synonyms = synonyms(~cellfun('isempty', synonyms)); if isempty(synonyms), synonyms = {}; end; end; end % End synonym parsing - if isempty(name) && isempty(definition), warning('ndi:ontology:RRID:LookupDataMissing', 'Queried %s successfully but key metadata (item.name, item.description) seems missing.', full_rrid); elseif isempty(name), warning('ndi:ontology:RRID:MissingName', 'Could not extract item.name for %s.', full_rrid); + + % 1. Metadata + if isfield(item_data, 'name'), name = char(item_data.name); end + if isfield(item_data, 'description'), definition = char(item_data.description); end + + % 2. Organism Data Extraction (Scientific and Common) + if isfield(source, 'organisms') && isfield(source.organisms, 'primary') && ~isempty(source.organisms.primary) + primary_info = source.organisms.primary(1); + + if isfield(primary_info, 'species') && isfield(primary_info.species, 'name') + scientificName = char(primary_info.species.name); + end + + if isfield(primary_info, 'common') && isfield(primary_info.common, 'name') + commonName = char(primary_info.common.name); + end + + elseif isfield(item_data, 'species') && ~isempty(item_data.species) + % Fallback for cases like antibodies where species is a simple field + if iscell(item_data.species) + scientificName = cellfun(@char, item_data.species, 'UniformOutput', false); + else + scientificName = char(item_data.species); + end + end + + % 3. Synonyms + if isfield(item_data, 'synonyms') && ~isempty(item_data.synonyms) + syn_data = item_data.synonyms; + if iscell(syn_data) + synonyms = cellfun(@char, syn_data, 'UniformOutput', false); + elseif isstruct(syn_data) && isfield(syn_data, 'name') + synonyms = {char(syn_data.name)}; + end end - - elseif isstruct(response) && isfield(response, 'success') && ~response.success, err_msg = 'API indicated failure (success=false).'; if isfield(response,'errormsg') && ~isempty(response.errormsg), err_msg = response.errormsg; elseif isfield(response,'message') && ~isempty(response.message), err_msg = response.message; end; error('ndi:ontology:RRID:APIReportedFailure', 'SciCrunch API reported failure for RRID "%s": %s', full_rrid, err_msg); - else, error('ndi:ontology:RRID:InvalidResponse', 'Received unexpected JSON structure from SciCrunch /resolver/*.json API for %s.', full_rrid); end catch ME - isHttpError = contains(ME.identifier, 'MATLAB:webservices:HTTP'); is404Error = isHttpError && (contains(ME.message, '404') || contains(ME.message, 'Not Found')); - if is404Error, error('ndi:ontology:RRID:IDNotFound', 'RRID "%s" lookup failed via SciCrunch endpoint %s (404 Error).', full_rrid, apiUrlBase); - elseif contains(ME.message, 'Timeout'), error('ndi:ontology:RRID:APITimeout', 'SciCrunch API request timed out for RRID "%s".', full_rrid); - elseif isHttpError, error('ndi:ontology:RRID:APIStatusError', 'SciCrunch API returned HTTP error for RRID "%s": %s', full_rrid, ME.message); - else, baseME = MException('ndi:ontology:RRID:APIError', 'SciCrunch /resolver/*.json API lookup failed for RRID "%s".', full_rrid); baseME = addCause(baseME, ME); throw(baseME); - end + rethrow(ME); end - end % function performRridResolverLookup - - end % methods (Static, Access = private) - -end % classdef RRID \ No newline at end of file + end + end +end \ No newline at end of file