-
Notifications
You must be signed in to change notification settings - Fork 2
EBMS Data Architecture
The EBMS makes extensive use of the Drupal Entity API, as well as the core Drupal Taxonomy module. The software for the site implements 25 custom entity types (incorporating 13 custom field types) and 21 custom vocabularies. This document provides an overview of these data structures.
The EBMS identifies published articles which may contain useful information for the PDQ cancer information summaries. These articles represent the core of the EBMS and the primary reason for its existence.

Paths in the Description column indicate the relative location of the field data under the PubmedArticle/MedlineCitation block in the XML retrieved from NLM.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| title | string | Full title of the published article (Article/ArticleTitle) |
| search_title | string | ASCII version of title (possibly shortened) for searching |
| authors | Author* | Persons or institutions who created the article (Article/AuthorList/Author) |
| source | string | "Pubmed" is the only source we have had so far (and may be all we ever have) |
| source_id | string | The PubMed ID (PMID) |
| source_journal_id | string | Identifier for the journal in which the article was published (MedlineJournalInfo/NlmUniqueID) |
| source_status | string | How close the article is to its final state, according to PubMed (@Status) |
| journal_title | string | Full title of the journal at the time the article was published (Article/Journal/Title) |
| brief_journal_title | string | Short name by which the journal is known (MedlineJournalInfo/MedlineTA) |
| volume | string | Volume of the journal in which the article was published (Article/Journal/JournalIssue/Volume) |
| issue | string | Issue of the journal in which the article was published (Article/Journal/JournalIssue/Issue) |
| pagination | string | Location of the article in the journal's volume/issue (Article/Pagination/MedlinePgn) |
| abstract | AbstractParagraph* | Summary of the article's content (Article/Abstract/AbstractText) |
| pub_date | PublicationDate | Articulated values for date of article's publication (Article/Journal/JournalIssue/PubDate) |
| year | integer | Extracted at import time using custom logic from publication date string values |
| imported_by | User | User who requested the initial import of this article |
| import_date | datetime | When the initial import happened |
| update_date | datetime? | When the most recent refresh of updated information from NLM happened (if ever) |
| data_mod | date? | Date the article information was last modified (as reported by NLM's API) |
| data_checked | date | The last time we asked NLM about data_mod
|
| full_text | FullText | Information about the PDF file containing the article itself |
| topics | ArticleTopic+ | Topics assigned to this article |
| tags | ArticleTag* | Tags which have been assigned to this article (e.g., "High priority") |
| internal_tags | InternalTag* | Tags identifying this article as an article of interest to NCI staff, not for board member review |
| internal_comments | SimpleComment* | Comment with visibility limited to NCI staff |
| types | string* | Assigned by NLM (Article/PublicationTypeList/PublicationType) |
| legacy_id | integer? | Article ID from the original Visual Basic system, if appropriate |
| last_author_name | string | Used for sorting/searching; calculated at save time |
| publication | string | Citation string calculated from other fields on the entity |
The update_date, data_mod, and data_checked fields are used by a nightly cron job which determines which previously imported articles have been modified in PubMed and refreshes the Article entities in the EBMS for those articles.
Custom repeatable field holding the articulated authors' names.
| Property Name | Type | Description |
|---|---|---|
| last_name | string? | Personal surname (Author/LastName) |
| first_name | string? | Personal given name (Author/ForeName) |
| initials | string? | Personal forename initials (Author/Initials) |
| collective_name | string? | Name of non-personal author; e.g., "National Academy of Clinical Biochemistry" (Author/CollectiveName) |
| display_name | string | Assembled version of name for display |
| search_name | string | ASCII-only version of display name, for searching |
The descriptive summary of the article is separated into paragraphs, each of which has a label.
| Property Name | Type | Description |
|---|---|---|
| paragraph_text | string | Text content of the Article/Abstract/AbstractText element |
| paragraph_label | string | From the Label attribute of the AbstractText element (e.g., "EXPERIMENTAL DESIGN") |
The publication date is stored in various ways, depending on the publication cycle of the journal.
| Property Name | Type | Description |
|---|---|---|
| year | string? | From Article/Journal/JournalIssue/PubDate/Year
|
| month | string? | From Article/Journal/JournalIssue/PubDate/Month
|
| day | string? | From Article/Journal/JournalIssue/PubDate/Day
|
| season | string? | From Article/Journal/JournalIssue/PubDate/Season
|
| medline_date | string? | From Article/Journal/JournalIssue/PubDate/MedlineDate
|
This field records the location of the PDF file for the article if we have it, and optionally, information about why we will not be able to obtain the file.
| Property Name | Type | Description |
|---|---|---|
| file | File? | Reference to an instance of the Drupal core File entity type if we have the article's PDF file |
| unavailable | boolean |
TRUE if it has been determined that it's not worth trying to get a copy of the article's full text file |
| flagged_as_unavailable? | datetime | When the determination was made that the file is unavailable (if appropriate) |
| flagged_by | User? | Who recorded the information that the PDF file is unavailable (if appropriate) |
| notes | string? | Optional additional information about the PDF file |
Record of a topic associated with an article, along with the review cycle assigned to the article-topic combination. The link into the ebms_state table provides all of the remaining information about who was responsible for the association, when it happened, what the state of the article was at the time for that topic, etc.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| topic | Topic | One of the topics assigned to this article for review (see the Topic entity below) |
| cycle | date | The month for which review of the article is targeted for this topic |
| states | State+ | Per-topic states for this article (see the State entity below) |
| tags | ArticleTag* | Article tags specific to this topic |
| comments | ModifiableComment* | Optional comments for the assignment of this topic to the article |
Currently, the requirements call for having at most one such comment for each article/topic combination, but we're prepared for a change to that requirement in the future, by having the comments stored in a separate table. See JIRA ticket OCEEBMS-375.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| tag | ArticleTagValue | Reference to a taxonomy vocabulary entity identifying the tag value |
| user | User | User who assigned this tag to the article |
| assigned | datetime | When the tag was assigned |
| comments | SimpleComment* | Optional comments on this tag assignment |
| active | boolean |
FALSE if the tag assignment is no longer applicable |
The ArticleTagValue taxonomy vocabulary identifies the available tags available for assignment to articles. Internally, the active field is called "status" by the taxonomy module, even though it is a boolean field, not an enumerated field holding several different "status" values. This is true for all taxonomy vocabularies described in this document.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID for the tag value |
| text_id | string | Stable known identifier usable in the code to recognize specific tag values for custom processing |
| name | string | Display name for the tag value (e.g., "Fast track import") |
| description | string | Information about the semantics of the tag |
| topic_required | boolean | If TRUE, a Topic must be associated with assignments of this tag value |
| topic_allowed | boolean | If FALSE, a Topic cannot be associated with assignments of this tag value |
| active | boolean | If FALSE, the tag is no longer available for future assignments to articles |
Logically, the topic_required field cannot be TRUE if the topic_allowed field is FALSE. The software enforces this constraint.
The InternalTag field is used for building the queue of internal articles of interest to PDQ staff, but not intended for inclusion in the board member review process. Unlike the regular article tags, some of which can be topic-specific in their assignment to an article, internal tags are always assigned to the article as a whole.
| Property Name | Type | Description |
|---|---|---|
| tag | InternalTagValue | Reference to a taxonomy vocabulary entity identifying the tag value |
| added | datetime | When the tag was assigned |
This is a taxonomy vocabulary used to enumerate the available internal tag values.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID for the tag value |
| name | string | Display name for the tag value (e.g., "SEO" or "PDQ") |
| active | boolean | If FALSE, the tag is no longer available for future assignments to articles |
Article relationships stored in entities of this type are assigned by PDQ staff, and for some conditions they can also be added by the software during an import job based on information contained in the retrieved PubMed records. A relationship between two articles is recorded in one direction only, so in order to identify all of the relationships in which a given article participates it is necessary to construct queries which find the article in either the related field or the related_to field.
| Property Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| related | Article | Entity reference to the related (e.g., duplicate) article |
| related_to | Article | Entity reference to the article to which that article is related (e.g., the original article of which it is a duplicate) |
| type | RelationshipType | One of a set of enumerated valid relationship values, e.g., "Duplicate" |
| recorded | datetime | When the relationship was entered by the user |
| recorded_by | User | Who recorded the relationship |
| comment | string? | Optional notes elaborating on the nature of the relationship |
| inactivated | datetime? | When the relationship was inactivated, if applicable |
| inactivated_by | User? | Who inactivated the relationship, if applicable |
| suppress | boolean | If TRUE, don't display this relationship in the review queues |
This is a taxonomy vocabulary used to enumerate the valid article relationship type values.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID for the relationship type value |
| name | string | Display name for the tag value (e.g., "Duplicate") |
| description | string? | Optional explanation of the semantics for this relationship type |
| active | boolean | If FALSE, the type is no longer available for future relationship assignments |
The Drupal core Comment entity type is much more complex than is needed for these comments, as it supports threading, versioning, translation, rich text, etc.
| Property Name | Type | Description |
|---|---|---|
| user | User | Who entered the comment |
| entered | datetime | When the comment was entered |
| body | string | What the comment says |
Somewhat more complicated than the custom SimpleComment field described above, but still much lighter-weight than the built-in Comment entity type. Used for board manager topic-specific comments, shown to board members on their packet review pages.
| Property Name | Type | Description |
|---|---|---|
| user | User | Who entered the comment |
| entered | datetime | When the comment was entered |
| comment | string | The body of the comment |
| modified | datetime? | When the comment was last modified (if ever) |
| modified_by | User? | Who last modified the comment (if appropriate) |
The EBMS tracks state history separately for each Topic assigned to each article, with exactly one State entity marked as current for that topic, as well as the previous State entities recorded for the article/topic combination.

The ArticleTopic entities in an article's topics field contain entity references to State entities, so it is clear from the Article entity which State entities belong to a given article. For processing efficiency we also store a back-reference from each State entity to the Article entity to which the state belongs.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| value | StateValue | Reference to the taxonomy entity representing which state this entity represents |
| article | Article | Back-reference to the article for which this state has been entered |
| board | Board | PDQ editorial board for which the state has been entered |
| topic | Topic | Topic for which this state has been entered |
| user | User | User responsible for deciding that the article should enter this state for this topic |
| entered | datetime | When the state was entered |
| active | boolean |
FALSE if the article reverted later to a state earlier in the processing cycle for this topic |
| current | boolean |
TRUE if this is the most recent state entered for this article/topic combination |
| comments | SimpleComment* | Optional comments recorded by PDQ staff for this state |
| decisions | BoardDecision* | One of the things the board decided to do with the article for this topic while in this state |
| wg_decisions | WorkingGroupDecision* | Decisions made by working groups rather than boards |
| deciders | User* | Board members participating in these decisions |
| meetings | Meeting* | This article is on the agenda for these meetings for this state (the system doesn't actually provide a means of assigning more than one meeting to a state, even though the data structures support multiple meetings) |
The board is stored separately for this entity, even though it could be derived from the Topic entity, partly for performance, and partly to capture the board to which the topic belonged at the time the state was entered, in the (unlikely) event that a topic migrates from one board to another at some later time.
This is the taxonomy vocabulary used for the possible states into which an article/topic combination can enter. The normal sequence of states for such a combination which makes it all the way through the process without being rejected somewhere along the way looks roughly like this:
- Ready for initial review by a librarian
- Passed the librarian's initial review
- Added to the queue for NCI review from the article's abstract
- Passed NCI review from the abstract
- Passed the board manager's review from the article's full text
- Changes proposed by board member review
- Added to a meeting agenda
- Final board decision
There is a process between states 4 and 5 in which an administrative assistant retrieves the full text PDF and adds it to the system. This action is not represented by an article/topic state since it is performed for the article as a whole, not for a specific topic, and the information about whether we have the full text PDF for the article is recorded in the Article entity itself.
Between states 5 and 6, articles are assigned to review packets for review by board members, and "Assigned for board member review" is sometimes thought of by the users as a separate state. However, that condition is not a simple binary flag, but is instead the answer to a series of questions (is there a packet for this topic? does it contain this article? are there any reviewers assigned to the packet? has the packet been suppressed? has the article been archived for this packet?). So we let the Packet entities track the complexities of this condition. Sometimes the user interface represents the answer to the question ("has the packet been assigned for review for this topic?") as a simple Yes/No flag, but behind the scenes the software is performing lots of work to make that determination.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID for the state value |
| name | string | Display value for the state value |
| text_id | string | Stable machine identifier for the value |
| sequence | integer | Identifies the position in the workflow for this state value relative to other states |
| terminal | boolean | If TRUE, no further states are expected for the article in connection with this topic |
| active | boolean | If FALSE, this value can no longer be assigned for new states |

| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID for the state value |
| text_id | string | Stable known identifier usable in the code to recognize specific state values for custom processing/reports |
| name | string | Display value for the state |
| description | string | Explanation of this state's semantics |
| terminal | boolean | If TRUE, this article/topic combination is not expected to progress to any further states |
| active | boolean | If FALSE, this state value has been retired and is not available for assignment to future states |
| sequence | integer | Indication of which states are expected to follow which other states |
More than one state value can share the same sequence number, for example "Passed initial review" and "Rejected in initial review" which currently both have sequence value 30. The sequence values can change over time, as long as the correct processing order is preserved. Typically the different sequence values have a gap (currently they are multiples of 10) in order to make it possible to insert a new state with a sequence value which falls between two existing values without having to do lots of renumbering of states. Code which needs to find states that occur earlier or later in the normal processing flow than a known state should not use hard-coded sequence numbers but should instead find the known state's entity using its text_id field value, load the StateValue entity, and pull out its sequence field.
The "Published" state value is used for articles which are ready for a board manager to review from the articles' abstracts for the specified topic. This allows the librarians to approve individual article/topic combinations one by one without having them added to the board manager's abstract review queue as the approvals are made, but instead mark a batch for "publication" to that queue all at once. It is an odd name, but the users got used to it when using the original Visual Basic application, and requested that the name be retained for this state.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| name | string | Display name for the topic |
| board | Board | PDQ board which reviews articles for this topic |
| nci_reviewer | User | NCI staff member who usually performs the initial review from abstract for this topic |
| topic_group | TopicGroup? | Optional string for grouping topics in the UI for boards which have a large number of topics |
| active | boolean | If FALSE, this topic is no longer available for assignment to articles/states |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| name | string | Display name for the board |
| manager | User | NCI staff member who manages the activities of this board |
| loe_guidelines | File | Document describing how this board assigns "levels-of-evidence" values |
| auto_imports | boolean | If TRUE the import software can create followup import jobs for articles in "core" journals |
| active | boolean | If FALSE this board has been retired (has never happened, probably never will) |
Board membership is stored in the User entities, not the Board entities. The same is true for topics and groups. See the custom fields added to the User entity type described below.
This is a taxonomy vocabulary used for the available groups to which topics can be assigned for better UI presentation.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID for the topic group value |
| name | string | Display value for the topic group |
| active | boolean | If FALSE, this group can no longer be assigned to topics |
Represents a decision reached for a "Final board decision" State entity.
| Property Name | Type | Description |
|---|---|---|
| decision | BoardDecisionValue | What the decision was |
| meeting_date | date | This field is carried over from the original implementation but it is really a denormalization, duplicating the value of the cycle date assigned to the combination of the article/topic for which this decision is made, and the value is never actually used anywhere (the name of the field is a bit of a misnomer, being the topic's cycle and not the date of an actual meeting; in fact, there may never have been a meeting for this decision) |
| discussed | boolean | If TRUE the article was discussed at the meeting |
This is a taxonomy vocabulary used for the decisions made by a board which can be recorded for an article/state combination.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID for the decision value |
| name | string | Display value for the decision (e.g., "Text approved") |
This is a taxonomy vocabulary used for the decisions made by a working group which can be recorded for an article/state combination. A separate vocabulary is used because the valid value list for board decisions is similar to, but not identical with this list.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID for the decision value |
| name | string | Display value for the decision (e.g., "Text approved") |
Display of meetings was implemented in the original EBMS using the contributed calendar module. That had been one of the weak links in the EBMS because for a long time, the date/time/calendar project was completely abandoned (though without any acknowledgement from the maintainer that she was doing so), leaving behind a trail of annoying ignored bugs. At the time of the Drupal 9 rewrite of the EBMS, the calendar module had only an alpha release available for Drupal 9, with no clear path to a stable release. Therefore, it was decided that we would abandon the dependency on that poorly-maintained third-party module and implement our own custom Meeting entity type.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| user | User | User who entered the meeting |
| entered | datetime | When the meeting was entered |
| name | string | Display name for the meeting |
| dates | daterange | When the meeting was scheduled to take place |
| type | MeetingType | Whether the meeting is in-person or via WebEx |
| category | MeetingCategory | "Board" or "Subgroup" |
| status | MeetingStatus | For example, "On Calendar" |
| boards | Board* | Board(s) invited to this meeting |
| groups | Group* | Group(s) invited to this meeting |
| individuals | User* | Individual users invited to this meeting |
| agenda | string? | Rich-text agenda for the meeting |
| agenda_published | boolean | If TRUE show the agenda to board members |
| published | boolean | If TRUE show this meeting on the calendar |
| documents | File* | Document files attached to this meeting entity |
| notes | string? | Optional rich-text notes for this meeting |
This is a taxonomy vocabulary used for the valid values distinguishing board meetings from meetings of other groups.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID for the meeting category value |
| name | string | Display value for the meeting category (e.g., "Subgroup") |
Whether the meeting is in-person, or remote (e.g., via WebEx).
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID for the meeting type value |
| name | string | Display value for the meeting type (e.g., "In Person") |
This is a taxonomy vocabulary used for the valid values indicating the status of a meeting.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID for the meeting status value |
| name | string | Display value for the meeting status (e.g., "Canceled") |
This entity type represents groups of users who can have their own meetings and made disposition decisions about articles.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| name | string | Display name for the group |
| boards | Board* | PDQ boards associated with this group |
The User entity is part of core Drupal, but the EBMS has added some custom fields to the entity type.
| Field Name | Type | Description |
|---|---|---|
| boards | Boards* | PDQ boards to which this user is assigned |
| board | Board? | PDQ Board to use by default when the user is assigned to more than one board |
| topics | Topic* | Topics for which this board member is a default reviewer |
| groups | Group* | Groups of which this user is a member |
| review_format | string | Whether the user prefers review pages in the "Brief" or "Abstract" format by default |
| review_per_page | integer | Number of articles to display on review queue pages by default for this user |
| review_sort | string | How articles should be sorted by default on review pages for this user (for example, by publication date) |
| review_boards | integer | Whether all topics should be shown on the review queue pages by default for this user ("all"), or just those topics for boards to which the user is assigned ("mine") |
| search_per_page | string | Number of articles to display on each search results page by default for this user; this field is a string rather than an integer because "View All" is one of the available options |
| search_sort | integer | How to sort the articles in search results by default for this user (for example, by PubMed ID) |
| packet_article_sort | integer | How to sort the articles on the packet creation page by default (options are "Author" and "Journal Title") |
Articles are imported into the EBMS in batches. We track the history of these batch requests. The word "import" has more than one meaning in this context, which can be confusing. Sometimes it is used to mean bringing in an article which has never been in the system before. Sometimes it is used to refer to the process of adding a review topic to an article which was previously imported for a different topic. Sometimes it is used to mean refreshing the information for an article we already have in the system, using updated values retrieved from NLM.

| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| topic | Topic | Every import batch is associated for a single topic (see State section) |
| source | string | At this time all of the articles in the system have been imported from PubMed |
| imported | datetime | When the import happened |
| cycle | date | The month for which review of the article in this batch is targeted for this topic |
| user | User | User requesting this import batch |
| not_list | boolean | If TRUE, suppress import of articles published in less desirable journals |
| import_type | ImportType | What type of import request is this |
| article_count | integer | Number of unique articles in this batch |
| comment | string? | Optional notes on this import request |
| messages | string? | System messages from any batch-level failures (article-specific import failures are recorded elsewhere) |
| success | boolean | If TRUE the batch as a whole succeeded, though some articles may have failed import; otherwise the batch as a whole failed, though individual articles may have been imported |
| actions | ImportAction* | What we did for each article in the batch import request |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| name | string | Display value for the import type (e.g., "Regular import" or "Data refresh from source") |
| text_id | string | Single-character code for import type (e.g., "R" for regular imports) |
One instance is present for each disposition of an article in an import batch. A single article might have more than one import action for the batch. For example, an article imported from NLM might already be in the EBMS but with a different topic and review cycle than were assigned for the original import of the article, resulting in a "Summary topic added" disposition. In addition, the XML currently fetched from NLM for the article may have changed information about the article, causing a "Replaced" disposition to be recorded.
| Property Name | Type | Description |
|---|---|---|
| source_id | string | Typically the PubMed ID (PMID) |
| article | Article? | Reference to the imported article if successful (see Article section) |
| disposition | ImportDisposition | What are we recording for the import of this article |
| message | string? | Optional additional information about the action |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| text_id | string | Stable known identifier usable in the code to recognize specific disposition values for custom processing/reports |
| name | string | Display name for the import disposition |
| description | string | Explanation of the semantics for this disposition value |
| active | boolean | If FALSE, this value is no longer available for assignment to newly created ImportAction entities |
This entity captures the response from NLM to a PubMed search request. The response, which is expected to be in the "PubMed" format, is parsed by the import software, which extracts the PubMed IDs and fetches the XML documents for the articles represented by those IDs for import into the EBMS. The captured response is sometimes useful after the import has completed, in the event that a problem is reported and we need to troubleshoot.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| submitted | datetime | When the response was handed off to the import software |
| results | string | Search response received from PubMed |
An ImportRequest entity captures the options specified by the requester for the import job, as well as the detailed results of the job, as reported by the back end. These entities cannot be purged, as they are used for reporting and diagnostic troubleshooting.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| params | string | Parameters captured from the import request form, serialized using json_encode()
|
| report | string | Results of the import job, used for reporting |
| batch | ImportBatch | Import batch created for this request |
Article/topic combinations which have passed the board manager full-text review are eligible to be assigned to board members for review. Review packets are created for this purpose, with each packet containing articles to be reviewed for a specific topic by one or more board member reviewers.

| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| topic | Topic | Entity reference for the Topic for which these articles are to be reviewed |
| created_by | User | Board manager (or other user) who created this review packet |
| created | datetime | When the packet was created |
| title | string | Name of the packet |
| articles | PacketArticle+ | Entity references for the information about the articles in this packet |
| reviewers | User+ | Board members assigned to review the articles in the packet |
| last_seen | datetime | When the manager of the packet's board last viewed it |
| active | boolean | If FALSE don't bother soliciting any more reviews |
| starred | boolean | Used by the board manager to identify packets she wants to come back to |
| summaries | Doc* | Documents (usually summaries) associated with the packet by the board manager |
| reviewer_docs | ReviewerDoc* | Documents attached to the packet by the reviewers |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| article | Article | One of the articles included in the review packet (see the Article section at the top of this document) |
| dropped | boolean | If TRUE the article should no longer be included when the packet is presented to the board member for review |
| archived | datetime | If not NULL the date/time the article was archived for this packet, suppressing it from review display |
| reviews | Review* | The reviews which the board members have submitted for the article |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| reviewer | User | Board member who submitted the review |
| posted | datetime | When the review was submitted |
| comments | string? | Free text general review information |
| loe_info | string? | Level-of-evidence information for the article |
| dispositions | Disposition+ | One or more values indicating what should be done with the information in this article for the packet's topic |
| reasons | RejectionReasons* | Why the reviewer rejected the article, if applicable |
The reviewer is constrained to select either the first Disposition value ("Warrants no changes to the summary") or one or more of the other Disposition values. If the "Warrants no changes ..." value is not chosen, then none of the RejectionReason options may be selected; otherwise at least one RejectionReason option must be selected.

| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| name | string | Display label for the disposition value |
| description | string? | Optional additional explanation for when to use the value |
| weight | integer | Used to control the order of the values, so that the rejection value ("Warrants no changes ...") always appears before the others |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| name | string | Display label for the reason value |
| description | string? | Optional additional explanation for when to use the value |
| active | boolean | If FALSE the value is no longer available for selection in future reviews |
| weight | integer | Used to control the order of the values |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| file | File | Reference to the built-in core File entity type |
| posted | datetime | When the document was uploaded |
| description | string? | Notes on the purpose/usage of the document |
| dropped | boolean | If TRUE the document no longer appears in selection lists |
| tags | DocTag* | Optional tags controlling how the document is used and where it appears |
| boards | Board* | Zero or more boards associated with this document (cf. the State section) |
| topics | Topic* | Zero or more topics associated with this document (cf. the State section) |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| name | string | Display label for the tag value |
| text_id | string | Stable machine name for this tag value |
| description | string? | Optional additional explanation for when to use the value |
| Property Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| file | File | Reference to the built-in core File entity type |
| reviewer | User | Board member who attached the document to the packet |
| posted | datetime | When the document was attached to the packet |
| description | string? | Optional additional information about the document |
| dropped | boolean | If TRUE display of the document with the packet is suppressed |
The desired business outcome for the EBMS is to have the PDQ summaries continuously improved and kept up to date as a result of the literature review process managed by the site. A portion of the site is set aside for the collection and display of links to the current summaries on the live web site as well as links to Microsoft Word versions of those summaries. When navigating to the Summaries section of the site, an EBMS user who is associated with exactly one of the PDQ boards (which is the case for most users) will be taken to set of links to all of the Summaries pages which belong to her board. All other users are asked to select a PDQ board.

An entity of this type represents the list of summary page links for a given board. Below this list are links to documents generally specific to that board, rather than any specific summary page (typically level-of-evidence usage guidelines for that board).
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| board | Board | The board to which this set of pages belongs |
| pages | SummaryPage+ | Information about each of the summary pages for this board |
| docs | DocUsage* | Board-specific documents to be linked below the list of links to the summary pages |
There is generally a one-to-one correspondence between a single Topic entity and a SummaryPage, but any number of topics (or none) can be associated with a summary page (in fact, the Digestive/Gastrointestinal Cancers page has 14 associated topics, and the Head and Neck Cancers page has 9 topics). In addition to the main links to the existing summaries, both NCI staff and the board member reviewers can post Microsoft Word versions of those summaries, and those files are linked at the bottom of the summary page in two separate tables, one for NCI-posted summary documents, and a second table for the summary documents posted by board members (see the illustration of a sample summary page below).
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| name | string | Display name assigned to this page |
| topics | Topic* | Review topics which are associated with this summary page (see Topic entity above) |
| links | Link+ | Links to the summaries on cancer.gov using the built-in core Link class |
| manager_docs | DocUsage* | Microsoft Word versions of summaries posted by board managers |
| member_docs | DocUsage* | Microsoft Word versions of summaries posted by board member reviewers |
| active | boolean | If FALSE display of this page is suppressed, as is the link to it |

The Link entity type is part of Drupal core.
| Field Name | Type | Description |
|---|---|---|
| url | Url | The address used to reach the link's target |
| text | string | Display name used when rendering this link on a page |
The Url entity type is supplied by Drupal core.
| Field Name | Type | Description |
|---|---|---|
| uri | string | The address used to reach the link's target |
This multi-valued field is a wrapper around the Doc entity, with support for notes and inactivation added.
| Property Name | Type | Description |
|---|---|---|
| doc | Doc | Reference to Doc entity (see the Packet section for details on this entity type) |
| notes | string? | Optional notes on this document as used on this summary page |
| active | boolean | If FALSE, display of the link to this document is suppressed |
There are two forms in the system for submitting travel requests, one for requesting a hotel reservation, and the other for reimbursement of meeting expenses. In the original EBMS these forms were implemented using the contributed webforms module. The rewritten EBMS implements these forms directly, eliminating the dependencies on that module, which had been the second most frequent source of upgrade compatibility conflicts over the years in the EBMS. At the time of the rewrite there were two current branches for the webforms module, and they both used a completely new approach from the Drupal 7 versions, so we would have been compelled to rewrite the travel code anyway, whichever decision we made about whether to continue using the third-party module. Only one of those branches supported Drupal 9, and there were some significant bumps in the road in achieving that support. The other branch required the installation of the jQueryUI tabs, tooltip, and date picker modules. A look at the code in the original ebms_webforms custom module provided a revealing window into the added layers of complexities and counterintuitive techniques required to make webforms do what we needed it to do, and the decision was made that we would be better off without that dependency.

| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| user | User | Board member making this hotel reservation request |
| submitted | datetime | When the board member submitted the request |
| remote_address | string | Network address from which the request was submitted |
| meeting | Meeting | The meeting the board member will be attending while staying at the hotel (see the State section) |
| check_in | date | The day the board member plans to check into the hotel |
| check_out | date | The day the board member plans to check out from the hotel |
| preferred_hotel | Hotel | The hotel at which the board member would like to stay |
| comments | string? | Optional addition information about the request |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| text_id | string | Machine name for the value |
| name | string | Name of the hotel |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| user | User | Board member making this reimbursement request |
| meeting | Meeting | The meeting the board member attended when the expenses were incurred (see the State section) |
| submitted | datetime | When the board member submitted the request |
| remote_address | string | Network address from which the request was submitted |
| arrival | date | When the user arrived at the meeting location |
| departure | date | When the user left the meeting location |
| transportation | TransportationExpense* | Separate details for each transporation-related expense |
| parking_and_tolls | ParkingOrTollExpense* | Separate details for each parking or toll expense |
| hotel_payment | HotelPayment | Value specifying who originally payed for the hotel |
| nights_stayed | integer | Number of nights the board member stayed at the hotel |
| hotel_amount | decimal | Value for the hotel bill amount |
| meals_and_incidentals | MealsAndIncidentals | Value specifying whether a per diem has been requested or that the board member is ineligible for a per diem |
| honararium_requested | boolean | Whether the board member has requested to be paid an honararium for attending the meeting |
| reimburse_to | ReimbursementTo | Whether the reimbursement should be sent to the board member's home, work, or other location |
| total_amount | decimal | Value for the total amount requested for reimbursement |
| comments | string? | Optional additional information about the reimubursement request |
| certified | boolean | Confirmation that the requester has certified that the information in the form is correct |
| confirmation_email | Address to which the confirmation that the request has been received should be sent |
| Property Name | Type | Description |
|---|---|---|
| date | date | When the expense was incurred |
| type | TransportationExpenseType | For example, "Taxi" or "Shuttle" |
| amount | decimal | Amount paid for the transportation expense |
| mileage | integer | Number of miles driven in a privately-owned vehicle to attend the meeting |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| text_id | string | Machine name for the value |
| name | string | Display name for the value |
- Taxi
- Metro
- Shuttle
- Privately Owned Vehicle
| Property Name | Type | Description |
|---|---|---|
| date | date | When the expense was incurred |
| type | ParkingOrTollExpenseType | For example, "Airport Parking" |
| amount | decimal | Amount paid for the parking or tolls expense |
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| text_id | string | Machine name for the value |
| name | string | Display name for the value |
- Airport Parking
- Hotel Parking
- Toll
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| text_id | string | Machine name for the value |
| name | string | Display name for the value |
- NCI paid for my hotel
- I paid for my hotel
- I did not stay in a hotel
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| text_id | string | Machine name for the value |
| name | string | Display name for the value |
- Per diem requested
- Per diem declined
- I am not eligible to receive a per diem
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| text_id | string | Machine name for the value |
| name | string | Display name for the values |
- Work
- Home
- Other
We maintain information pulled from NLM for the journals in which our articles are published. We augment this information with flags indicating which journals are considered "core" journals in the EBMS, as well as which journals are considered by each board to have a history of publishing articles which provide no usable information for that board. These latter sets of journals are affectionately referred to by the users as "NOT lists" and they speak of an article as having been "NOT-listed." Import requests can submit a PubMed search result set and set a flag telling the software to reject articles from these "NOT lists" (this action can later be overridden and an article which was originally "NOT-listed" can be pulled out of that rejection state and put into the normal review queues).
Note that instead of simply recording the entity ID for a journal in which an article was published, the EBMS stores the NLM ID, title, and brief title for the journal directly in the Article entity. This is done for two reasons. First, we need to capture this information in the event that there is no Journal entity in the system (because, for example, the journal information set we have from NLM is out of date, or NLM hasn't yet entered the journal in its database, or the journal for an older article is removed from NLM's database). Also, we want to record the IDs and names of the journals as they existed at the time of publication of the article (or at least, as represented by NLM in its XML document for the article).

| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| source | string | Always "Pubmed" so far |
| source_id | string | NLM's unique identifier for this journal |
| title | string | Full title for the journal |
| brief_title | string | Abbreviated journal title |
| core | boolean | If TRUE this journal receives preferential treatment (automatic imports, moved to the front of queues and reports, etc.) |
| not_lists | NotListBoard* | Lists of boards which want to suppress approval of articles from this journal |
| Property Name | Type | Description |
|---|---|---|
| board | Board | PDQ board which has decided to suppress approval of articles from this journal |
| start | datetime | When this decision should take effect |
| user | User | User who made this decision |
The EBMS supports printing packets for board members who are reluctant to switch from paper to electronic literature review. The original requirements for this feature were very elaborate, and the original version of this document captured the various custom data types which would have been needed to support those requirements. However, at least in part because the number of board members for which this alternate method of reviewing articles has decreased over time, it was decided that the business needs did not justify that much complexity, and the printing module which was eventually implemented during the Drupal 9 rewrite of the EBMS only needs the common SavedRequest entity type, described in the next section.
The EBMS has a common standalone entity type used by requests throughout the system, as well as a custom table providing a lightweight alternative to the Drupal configuration system.

A SavedRequest entity captures the parameters specified by a user for article searches, reports, review queues, etc. The parameters are retrieved for processing by the module which processes the request. The captured information is also useful for troubleshooting in the event that an error occurs.
| Field Name | Type | Description |
|---|---|---|
| id | integer | Entity ID (PK) |
| request_type | string | The name of the request type (for example, "journal queue") |
| user | User | Who submitted the request? |
| submitted | datetime | When the request was submitted |
| parameters | string | JSON-encoded parameters for the request |
Drupal's config values are loaded at bootstrap time for every request. This table contains configuration values which we only want loaded when they are explicitly requested, and which we do not want to be controlled by the Drupal cache system. At present, the only row in this table contains the JSON-serialized hierarchy of MeSH article types retrieved from NLM when a new MeSH release is made available (currently that happens annually). See JIRA ticket OCEEBMS-593.
| Column Name | Type | Description |
|---|---|---|
| name | varchar | Name of the value (e.g., 'article-type-ancestors') |
| value | text | Configuration value |