chore: deploy initial code base #1

Merged
bermudalamb merged 1 commits from feature/initial-code-base into main 2026-05-12 10:45:30 -05:00
257 changed files with 6137748 additions and 1 deletions
+183
View File
@@ -0,0 +1,183 @@
# Contributing Guidelines
Welcome, brave engineer. Before you type `git commit -m "oops"` and vanish for 3 weeks of PTO, let's align on how commits work in this repo.
We follow [**Conventional Commits**](https://www.conventionalcommits.org/) with **semantic versioning**.\
This gives us:
- Automatic changelogs (no one likes writing them manually)
- Predictable version bumps
- Clear history that future-you (or your replacement) can actually read
---
## 1. Commit Message Format
Every commit **MUST** follow this format:
```
<type>(<scope>): <description>
[optional body]
[optional BREAKING CHANGE: ...]
[optional footer(s)]
```
- \`\` → what you did
- \`\` *(optional, but strongly encouraged)* → where you did it
- \`\` → short explanation (imperative tense)
- **Body** → additional details, rationale, or context (wrap at 72 chars if possible)
- **Footer** → metadata such as references to backlog items or related issues
> ❗ There must be a blank line separating `BREAKING CHANGE:` from any footer lines.
### Examples
```
feat(auth): add token refresh endpoint
Added a new endpoint for refreshing authentication tokens to reduce
full login frequency.
Refs: JIRA-1234
```
```
fix(api): correctly parse null values
Previously, null values in payloads caused 500 errors due to improper
validation. Updated parser to handle null safely.
BREAKING CHANGE: Parser behavior has changed for null payloads.
Refs: JIRA-9876
```
---
## 2. Allowed Commit Types
| Type | When to Use |
| ---------- | ----------------------------------------- |
| `build` | Build system changes (configs, scripts) |
| `feat` | A new feature |
| `fix` | A bug fix |
| `docs` | Documentation updates |
| `style` | Non-functional style changes (formatting) |
| `refactor` | Code change that isnt a bug or feature |
| `test` | Adding/fixing tests |
| `chore` | Maintenance tasks (deps, configs) |
---
## 3. Scopes
Scopes keep commits relevant. Commonly you'll find service, or module names, used as the scope:
```
feat(api): add user endpoint
fix(web): handle expired token
```
> While unrelated areas should be split into separate commits, multiple scopes can be combined in a single commit if they are related:
`refactor(web,api): clean up regex for widget payloads`
If you're touching multiple unrelated areas, split the work.\
If youre touching the **entire repo**, you may omit the scope (e.g. `chore: update prettier config`).
---
## 4. Commit Body
Use the body to:
- Explain **why** the change was made
- Add context or reasoning (if its not obvious from the diff)
- Mention relevant technical details
> ❗ Keep the body wrapped at 72 characters where possible for better readability in CLI tools.
Example:
```
fix(auth): improve token handling
Added stricter validation to prevent expired tokens from being used
in refresh calls. This fixes intermittent 401 errors for long-lived
sessions.
```
---
## 5. Commit Footer
Use the footer for:
- `BREAKING CHANGE:` declarations (with a blank line after it)
- References to backlog items, tickets, or issues
- Co-authorship metadata if needed (`Co-authored-by:`)
Example:
```
feat(ui): add dark mode toggle
Added a dark mode toggle in the user settings page.
BREAKING CHANGE: Removed old theme switcher.
Refs: JIRA-4567
```
---
## 6. Linting & Enforcement
This repo uses:
- [**commitlint**](https://github.com/conventional-changelog/commitlint) to reject bad commit messages
- [**husky**](https://typicode.github.io/husky) to run lint checks pre-commit
After cloning the repository, run `npm install` to ensure husky hooks are installed and initialized locally. If you skip this step, GitHub Actions will run the same commit linting on your pull requests. Any bad commits that bypass local checks will result in a failed PR check.
If you try to commit `do some stuff`, the hook will fail and mock you.\
Run `npm run commit` if you want a guided prompt.
---
## 7. Pull Requests
- Keep PRs small and focused.
- Please use `--amend` commits or `--fixup` commits where appropriate
- Squash `fixup:` commits, and any commits that dont add value (e.g. `chore: typo`).
- Ensure commit messages still follow Conventional Commits after squashing.
---
## 8. Versioning
We use **semantic versioning**:
- `fix:` → Patch (`1.0.1`)
- `feat:` → Minor (`1.1.0`)
- `BREAKING CHANGE:` → Major (`2.0.0`)
Your commit messages drive the versioning calculation.
---
### TL;DR
- Use Conventional Commits.
- Include a scope if it makes sense.
- Write meaningful bodies when needed.
- Use footers for breaking changes or ticket references.
- Leave a blank line between BREAKING CHANGE and footer.
- Your branch's commit history should tell the story of what you did.
- Bad commits will be rejected by hooks, mocked by your peers, and possibly framed on Slack or in some PowerPoint presentation as "what not to do."
---
Happy committing!
+119
View File
@@ -0,0 +1,119 @@
ARG PROJECT=Strata.RxNorm
ARG VERSION=0.0.0
ARG SONARURL=''
ARG SONARLOGIN=''
ARG SONARBRANCH=''
##############
# Base image #
##############
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.aspnet:8.0 AS base
# Add TMZ files for CST
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
###############
# Build image #
###############
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.sdk:8.0 AS build
ARG PROJECT
ARG VERSION
ARG SONARURL
ARG SONARLOGIN
ARG SONARBRANCH
# Add TMZ files for CST
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
WORKDIR /src
# Copy project files and restore
COPY ["${PROJECT}.sln", "${PROJECT}.sln"]
COPY src/**/*.csproj ./
RUN for file in $(ls *.csproj); do mkdir -p src/${file%.*}/ && mv $file src/${file%.*}/; done
COPY tests/**/*.csproj ./
RUN for file in $(ls *.csproj); do mkdir -p tests/${file%.*}/ && mv $file tests/${file%.*}/; done
RUN dotnet restore "${PROJECT}.sln" \
--source https://api.nuget.org/v3/index.json \
--source https://proget.sdt.local/nuget/nuget/v3/index.json
# Copy files and build
COPY . .
RUN if [ "${SONARLOGIN}" != "" ] ; then dotnet sonarscanner begin \
/k:"${PROJECT}" \
/d:sonar.scm.provider=git \
/d:sonar.host.url="${SONARURL}" \
${SONARBRANCH} \
/d:sonar.login="${SONARLOGIN}" \
/d:sonar.cs.opencover.reportsPaths="/test-results/cover.xml" \
/d:sonar.cs.vstest.reportsPaths="/test-results/*.trx" \
/d:sonar.dependencyCheck.jsonReportPath=/src/buildlogs/dependency-check-report.json \
/d:sonar.dependencyCheck.htmlReportPath=/src/buildlogs/dependency-check-report.html \
/v:sonar.projectVersion="${VERSION}"; fi
RUN dotnet build "${PROJECT}.sln" \
--configuration Release \
--no-restore
# Run tests
RUN dotnet test "tests/${PROJECT}.Biz.Test.Unit/${PROJECT}.Biz.Test.Unit.csproj" \
--configuration Release \
--no-restore \
--no-build \
--verbosity=normal \
-p:CollectCoverage=true \
-p:CoverletOutputFormat="opencover" \
-p:CoverletOutput=/test-results/cover.xml
# Publish project
RUN dotnet publish "src/${PROJECT}.Api/${PROJECT}.Api.csproj" \
--configuration Release \
--no-restore \
--no-build \
--output /app
# Pack client project
RUN dotnet pack "src/${PROJECT}.Client/${PROJECT}.Client.csproj" \
--configuration Release \
--no-restore \
--no-build \
--include-symbols \
--include-source \
--output /pack \
-property:PackageVersion=${VERSION}
RUN mkdir -p /src/buildlogs
RUN if [ "${SONARLOGIN}" != "" ] ; then \
/dc/dependency-check/bin/dependency-check.sh -f JSON -f HTML -s . -o ./buildlogs \
--suppression ./sonarsuppressions.xml \
--noupdate --nodeAuditSkipDevDependencies --disableNodeJS --disableRetireJS --ossIndexRemoteErrorWarnOnly true \
--dbDriverName "com.microsoft.sqlserver.jdbc.SQLServerDriver" \
--connectionString "jdbc:sqlserver://ddensqldevops01.sdt.local;Database=DependencyCheck;encrypt=true;trustServerCertificate=true;" \
--dbUser "dcuser" --dbPassword "${SONARLOGIN}" && \
dotnet sonarscanner end /d:sonar.login="${SONARLOGIN}"; \
fi
RUN touch /src/buildlogs/dependency-check-report.html
###############
# Final image #
###############
FROM base AS final
ARG PROJECT
WORKDIR /app
ARG PROJECT
ENV PROJECTDLL=${PROJECT}.Api.dll
ENV DOTNET_ENVIRONMENT=Development
ENV ASPNETCORE_ENVIRONMENT=Development
COPY --from=build /app /app
COPY --from=build /pack /pack
COPY --from=build /test-results /test-results
COPY --from=build /src/buildlogs/dependency-check-report.html /dc/dependency-check-report.html
ENTRYPOINT dotnet ${PROJECTDLL}
+60
View File
@@ -0,0 +1,60 @@
ARG PROJECT=Strata.RxNorm
ARG VERSION=0.0.0
##############
# Base image #
##############
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.aspnet:8.0 AS base
# Add TMZ files for CST
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
###############
# Build image #
###############
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.sdk:8.0 AS build
ARG PROJECT
ARG VERSION
# Add TMZ files for CST
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
WORKDIR /src
# Copy project files and restore
COPY ["${PROJECT}.sln", "${PROJECT}.sln"]
COPY src/**/*.csproj ./
RUN for file in $(ls *.csproj); do mkdir -p src/${file%.*}/ && mv $file src/${file%.*}/; done
COPY tests/**/*.csproj ./
RUN for file in $(ls *.csproj); do mkdir -p tests/${file%.*}/ && mv $file tests/${file%.*}/; done
RUN dotnet restore "${PROJECT}.sln" \
--source https://api.nuget.org/v3/index.json \
--source https://proget.sdt.local/nuget/nuget/v3/index.json
# Copy files and build
COPY . .
RUN dotnet build "${PROJECT}.sln" \
--configuration Release \
--no-restore
# Publish project
RUN dotnet publish "src/${PROJECT}.Hangfire.Dashboard/${PROJECT}.Hangfire.Dashboard.csproj" \
--configuration Release \
--no-restore \
--no-build \
--output /app
###############
# Final image #
###############
FROM base AS final
ARG PROJECT
WORKDIR /app
ARG PROJECT
ENV PROJECTDLL=${PROJECT}.Hangfire.Dashboard.dll
ENV DOTNET_ENVIRONMENT=Development
ENV ASPNETCORE_ENVIRONMENT=Development
COPY --from=build /app /app
ENTRYPOINT dotnet ${PROJECTDLL}
+61
View File
@@ -0,0 +1,61 @@
ARG PROJECT=Strata.RxNorm
ARG VERSION=0.0.0
##############
# Base image #
##############
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.aspnet:8.0 AS base
# Add TMZ files for CST
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
###############
# Build image #
###############
FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.sdk:8.0 as build
ARG PROJECT
ARG VERSION
# Add TMZ files for CST
RUN cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
WORKDIR /src
# Copy project files and restore
COPY ["${PROJECT}.sln", "${PROJECT}.sln"]
COPY src/**/*.csproj ./
RUN for file in $(ls *.csproj); do mkdir -p src/${file%.*}/ && mv $file src/${file%.*}/; done
COPY tests/**/*.csproj ./
RUN for file in $(ls *.csproj); do mkdir -p tests/${file%.*}/ && mv $file tests/${file%.*}/; done
RUN dotnet restore "${PROJECT}.sln" \
--source https://api.nuget.org/v3/index.json \
--source https://proget.sdt.local/nuget/nuget/v3/index.json
# Copy files and build
COPY . .
RUN dotnet build "${PROJECT}.sln" \
--configuration Release \
--no-restore
# Publish project
RUN dotnet publish "src/${PROJECT}.Hangfire/${PROJECT}.Hangfire.csproj" \
--configuration Release \
--no-restore \
--no-build \
--output /app
###############
# Final image #
###############
FROM base AS final
ARG PROJECT
WORKDIR /app
ARG PROJECT
ENV PROJECTDLL=${PROJECT}.Hangfire.dll
ENV DOTNET_ENVIRONMENT=Development
ENV ASPNETCORE_ENVIRONMENT=Development
COPY --from=build /app /app
ENTRYPOINT dotnet ${PROJECTDLL}
+29
View File
@@ -0,0 +1,29 @@
ARG SONARLOGIN=''
ARG VERSION=0.0.0
FROM node:18.16.0 as build
ARG SONARLOGIN
ARG VERSION
ENV CI=true
# Set working directory
WORKDIR /app
# Install dependencies
RUN npm config set @strata:registry http://proget.sdt.local/npm/npm
RUN npm config set strict-ssl false
COPY src/Strata.RxNorm.Web/rxnorm/package*.json ./
RUN npm install --legacy-peer-deps
RUN npm ci --legacy-peer-deps
# Copy in rest of app and build
COPY src/Strata.RxNorm.Web/rxnorm .
RUN npm run build
# Run the UI tests
RUN npm run test-ci
# Build release container
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
COPY src/Strata.RxNorm.Web/nginx.conf /etc/nginx/conf.d/default.conf
+120
View File
@@ -0,0 +1,120 @@
## Routine to transform given NDC code (v\_ndc) to HIPAA 11-digit format (ret\_ndc)
### Define variables
- v\_ndc = value of source NDC to be normalized
- v\_rsab = RxNorm root source abbreviation (SAB)
- ret\_ndc = string(50); 'the normalized NDC
- v\_format = string(50); 'the format of source NDC code
### Find the format of source NDC value to be normalized
### Format can vary from source to source
```
IF LEN(v_ndc) - LEN(REPLACE (v_ndc, '-')) = 2 ## If NDC string contains 2 dashes
THEN ## find the format of the NDC string. Format could be 6-4-2, 6-4-1, 5-4-2, etc.
v_format =
LEN (SUBSTR (v_ndc, 1, INSTR (v_ndc, '-') - 1)) ## find number of digits before first dash
+ '-'
+ LEN (SUBSTR (v_ndc,
INSTR (v_ndc, '-') + 1, ## digit number after first dash
INSTR (v_ndc, '-', 1, 2) - INSTR (v_ndc, '-') -1 ## number of digits between first and second dash
)
) ## find number of digits between the dashes
+ '-'
+ LEN (SUBSTR (v_ndc, INSTR (v_ndc, '-', 1, 2) + 1)) ## number of digits after second dash
END IF ## v_format now contains X-X-X
```
### Format has been identified. Convert into normalized NDC
```
IF v_format IS NOT NULL ## source NDC string contains dashes
THEN
IF v_format = '6-4-2' ## drop first digit of string
THEN
ret_ndc =
SUBSTR (v_ndc, 2, 5) ## last 5 digits of number before first dash
+ SUBSTR (v_ndc, 8, 4) ## all four digits after first dash
+ SUBSTR (v_ndc, 13, 2) ## last 2 digits after second dash
ELSE IF v_format = '6-4-1' ## drop first digit of string
THEN
ret_ndc =
SUBSTR (v_ndc, 2, 5)
+ SUBSTR (v_ndc, 8, 4)
+ '0' ## pad last digit to 2 chars
+ SUBSTR (v_ndc, 13, 1)
ELSE IF v_format = '6-3-2' ## drop first digit of string
THEN
ret_ndc =
SUBSTR (v_ndc, 2, 5)
+ '0' ## pad second digit to 4 chars
+ SUBSTR (v_ndc, 8, 3)
+ SUBSTR (v_ndc, 12, 2)
ELSE IF v_format = '6-3-1' ## drop first digit of string
THEN
ret_ndc =
SUBSTR (v_ndc, 2, 5)
+ '0' ## pad second digit to 4 chars
+ SUBSTR (v_ndc, 8, 3)
+ '0' ## pad third digit to 2 chars
+ SUBSTR (v_ndc, 12, 1)
ELSE IF v_format = '5-4-2' ## keep all digits
THEN
ret_ndc =
SUBSTR (v_ndc, 1, 5)
+ SUBSTR (v_ndc, 7, 4)
+ SUBSTR (v_ndc, 12, 2)
ELSE IF v_format = '5-4-1' ## keep all digits
THEN
ret_ndc =
SUBSTR (v_ndc, 1, 5)
+ SUBSTR (v_ndc, 7, 4)
+ '0' ## pad third digit to 2 chars
+ SUBSTR (v_ndc, 12, 1)
ELSE IF v_format = '5-3-2' ## keep all digits
THEN
ret_ndc =
SUBSTR (v_ndc, 1, 5)
+ '0' ## pad second digit to 4 chars
+ SUBSTR (v_ndc, 7, 3)
+ SUBSTR (v_ndc, 11, 2)
ELSE IF v_format = '4-4-2' ## keep all digits
THEN
ret_ndc =
'0' ## pad first digit to 5 chars
+ SUBSTR (v_ndc, 1, 4)
+ SUBSTR (v_ndc, 6, 4)
+ SUBSTR (v_ndc, 11, 2)
END IF
```
#### If NDC passed has 11 digits without any '-' then return input NDC as Normalized NDC value
#### NDC string is already in normalized format, it does not contain any dashes
```
ELSE IF INSTR (v_ndc, '-') = 0 AND LEN (v_ndc) = 11
THEN
ret_ndc = v_ndc
```
#### If NDC passed has 12 digits and first char is '0' and its from VANDF then trim first char
#### VA sometimes pads '0' to make it 12 digit NDC
```
ELSE IF INSTR (v_ndc, '-') = 0 ## no dashes in string
AND LEN (v_ndc) = 12
AND SUBSTR (v_ndc, 1, 1) = '0' ## first digit is 0
AND v_rsab = 'VANDF'
THEN
ret_ndc = SUBSTR (v_ndc, 2) ## return all digits except first char which is 0
END IF
```
#### Replace '\*' with '0' as some of the NDCs from MTHFDA contain \* instead of 0
```
ret_ndc = REPLACE (ret_ndc, '\*', '0')
```
#### Check to see if NDC value contains any Alphanumeric values, if yes, then its an invalid NDC code
```
IF TRIM (TRANSLATE (ret_ndc, '1234567890', ' ')) IS NOT NULL
THEN
ret_ndc = ''
END IF
RETURN ret_ndc
```
### For any other cases, return NULL. String cannot be normalized
+55 -1
View File
@@ -1,2 +1,56 @@
# rxnorm
# Strata.RxNorm
[![build](https://github.com/stratadecision/rxnorm/actions/workflows/build.yaml/badge.svg)](https://github.com/stratadecision/rxnorm/actions/workflows/build.yaml)
[![Strata.RxNorm.Octopus](https://img.shields.io/badge/octopus-Strata.RxNorm-004880?logo=OctopusDeploy)](https://octopus.sdt.local/app#/Spaces-1/projects/rxnorm)
[![SonarQube](https://img.shields.io/badge/SonarQube-Strata.RxNorm-004880?logo=sonarqube)](https://sonarqube.ops.stratanetwork.net/dashboard?id=Strata.RxNorm)
[![Strata.RxNorm.Client](https://img.shields.io/badge/nuget-Strata.RxNorm.Client-004880?logo=nuget&logoColor=004880)](https://proget.sdt.local/feeds/nuget/Strata.RxNorm.Client/versions?HidePrerelease=True)
[![Strata.RxNorm.Api](https://img.shields.io/badge/api-Strata.RxNorm-004880?logo=swagger&logoColor=00FF00)](https://rxnorm-api.dev.stratanetwork.net)
[![Strata.RxNorm.Web](https://img.shields.io/badge/web-Strata.RxNorm-004880?logo=react&logoColor=61DBFB)](https://rxnorm.dev.stratanetwork.net)
[![Strata.RxNorm.Hangfire](https://img.shields.io/badge/hangfire-Strata.RxNorm-004880?logo=MediaFire&logoColor=FF0000)](https://rxnorm-dashboard.dev.stratanetwork.net)
## General Information
## Credits
This microservice is developed and maintained by The-Outliers
## Branching and Versioning
| branch | version format | example |
| ---------- | --------------------- | --------------- |
| main | #.#.# | 1.2.3 |
| feature/\* | #.#+1.0-featureName.# | 1.3.0-newfeat.1 |
| fix/\* | #.#.#+1-fixName.# | 1.2.4-bug.1 |
See our [confluence page](https://stratadecision.atlassian.net/wiki/spaces/DOP/pages/3119748190/Branching+and+Versioning) for more information
## Usage
### Build and Run
```
docker-compose up -d --build
```
- https://localhost:8443/index (swagger api)
- https://localhost:8443/hangfire (hangfire dashboard)
- http://localhost:8081 (redis commander)
### Extracting nuget package
```
docker create --name throwaway stratarxnorm_rxnorm-api
docker cp throwaway:/pack .
docker rm throwaway
```
### Cleanup
```
docker-compose down
```
+70
View File
@@ -0,0 +1,70 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.12.35707.178
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.RxNorm.Api", "src\Strata.RxNorm.Api\Strata.RxNorm.Api.csproj", "{5AE8C48E-B1C0-4D90-9C39-B4EAEEBF7EE6}"
ProjectSection(ProjectDependencies) = postProject
{F94E02FB-5AE4-4E84-B86C-1C1E24A7C847} = {F94E02FB-5AE4-4E84-B86C-1C1E24A7C847}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.RxNorm.Client", "src\Strata.RxNorm.Client\Strata.RxNorm.Client.csproj", "{7AD42BCE-2210-4763-B4FB-95E0D2BEC4AB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.RxNorm.Biz", "src\Strata.RxNorm.Biz\Strata.RxNorm.Biz.csproj", "{D07DFE32-EC27-4051-B98E-13D7592867D1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.RxNorm.Hangfire", "src\Strata.RxNorm.Hangfire\Strata.RxNorm.Hangfire.csproj", "{2A3CC678-9D4E-4006-BBDF-338AED6875F6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.RxNorm.Hangfire.Dashboard", "src\Strata.RxNorm.Hangfire.Dashboard\Strata.RxNorm.Hangfire.Dashboard.csproj", "{06B5E50A-2630-44B4-873B-D651FC6FF0AC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.RxNorm.Web", "src\Strata.RxNorm.Web\Strata.RxNorm.Web.csproj", "{C3F72F44-11E5-4BC5-AE61-0FCE48217E19}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.RxNorm.Biz.Test.Unit", "tests\Strata.RxNorm.Biz.Test.Unit\Strata.RxNorm.Biz.Test.Unit.csproj", "{4E6FE970-7178-4BAC-A36F-A1D11C5D0B42}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.RxNorm.Shared", "src\Strata.RxNorm.Shared\Strata.RxNorm.Shared.csproj", "{F94E02FB-5AE4-4E84-B86C-1C1E24A7C847}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5AE8C48E-B1C0-4D90-9C39-B4EAEEBF7EE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5AE8C48E-B1C0-4D90-9C39-B4EAEEBF7EE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5AE8C48E-B1C0-4D90-9C39-B4EAEEBF7EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5AE8C48E-B1C0-4D90-9C39-B4EAEEBF7EE6}.Release|Any CPU.Build.0 = Release|Any CPU
{7AD42BCE-2210-4763-B4FB-95E0D2BEC4AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7AD42BCE-2210-4763-B4FB-95E0D2BEC4AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7AD42BCE-2210-4763-B4FB-95E0D2BEC4AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7AD42BCE-2210-4763-B4FB-95E0D2BEC4AB}.Release|Any CPU.Build.0 = Release|Any CPU
{D07DFE32-EC27-4051-B98E-13D7592867D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D07DFE32-EC27-4051-B98E-13D7592867D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D07DFE32-EC27-4051-B98E-13D7592867D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D07DFE32-EC27-4051-B98E-13D7592867D1}.Release|Any CPU.Build.0 = Release|Any CPU
{C3F72F44-11E5-4BC5-AE61-0FCE48217E19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3F72F44-11E5-4BC5-AE61-0FCE48217E19}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3F72F44-11E5-4BC5-AE61-0FCE48217E19}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3F72F44-11E5-4BC5-AE61-0FCE48217E19}.Release|Any CPU.Build.0 = Release|Any CPU
{2A3CC678-9D4E-4006-BBDF-338AED6875F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A3CC678-9D4E-4006-BBDF-338AED6875F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A3CC678-9D4E-4006-BBDF-338AED6875F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A3CC678-9D4E-4006-BBDF-338AED6875F6}.Release|Any CPU.Build.0 = Release|Any CPU
{06B5E50A-2630-44B4-873B-D651FC6FF0AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{06B5E50A-2630-44B4-873B-D651FC6FF0AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{06B5E50A-2630-44B4-873B-D651FC6FF0AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{06B5E50A-2630-44B4-873B-D651FC6FF0AC}.Release|Any CPU.Build.0 = Release|Any CPU
{4E6FE970-7178-4BAC-A36F-A1D11C5D0B42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E6FE970-7178-4BAC-A36F-A1D11C5D0B42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E6FE970-7178-4BAC-A36F-A1D11C5D0B42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E6FE970-7178-4BAC-A36F-A1D11C5D0B42}.Release|Any CPU.Build.0 = Release|Any CPU
{F94E02FB-5AE4-4E84-B86C-1C1E24A7C847}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F94E02FB-5AE4-4E84-B86C-1C1E24A7C847}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F94E02FB-5AE4-4E84-B86C-1C1E24A7C847}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F94E02FB-5AE4-4E84-B86C-1C1E24A7C847}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F727BD4A-B282-430A-B391-AF7B4FF07EA1}
EndGlobalSection
EndGlobal
+48
View File
@@ -0,0 +1,48 @@
version: "3"
services:
rxnorm-api:
build:
context: .
dockerfile: Dockerfile.api
ports:
- "8080:80"
- "8443:443"
environment:
- hangfire__redis__prefix=rxnorm
- hangfire__redis__configOptions=redis:6379
rxnorm-hangfire:
build:
context: .
dockerfile: Dockerfile.hangfire
environment:
- hangfire__redis__prefix=rxnorm
- hangfire__redis__configOptions=redis:6379
rxnorm-hangfire-dashboard:
build:
context: .
dockerfile: Dockerfile.dashboard
ports:
- 8082:80
environment:
- hangfire__redis__prefix=rxnorm
- hangfire__redis__configOptions=redis:6379
redis:
image: redis:alpine
ports:
- 6379:6379
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis-data:/data
redis-commander:
image: rediscommander/redis-commander:latest
environment:
- REDIS_HOSTS=local:redis:6379
- HTTP_USER=root
- HTTP_PASSWORD=pass
ports:
- 8081:8081
depends_on:
- redis
volumes:
redis-data:
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="defaultPushSource" value="https://proget.ops.stratanetwork.net/nuget/nuget" />
</config>
<packageSources>
<add key="NuGet official package source" value="https://api.nuget.org/v3/index.json" />
<add key="Strata package source" value="https://proget.ops.stratanetwork.net/nuget/nuget" />
</packageSources>
</configuration>
+1717
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
{
"scripts": {
"commit": "commit",
"prepare": "husky"
},
"devDependencies": {
"@commitlint/cli": "^19.2.1",
"@commitlint/prompt-cli": "^19.2.1",
"@commitlint/config-conventional": "^19.1.0",
"husky": "^9.0.11"
}
}
+105
View File
@@ -0,0 +1,105 @@
<#
.SYNOPSIS
Get Octopus Deploy release information for a specific project and environments.
.DESCRIPTION
This script retrieves the latest release information from Octopus Deploy for a specified project across multiple environments
and generates a markdown report.
.PARAMETER yourApiKey
The API key for authenticating with the Octopus Deploy server (mandatory).
.PARAMETER pr_projectName
The name of the Octopus Deploy project (default is "strata.rxnorm").
.PARAMETER pr_path
The path to the git repository (default is the parent directory of the script).
.EXAMPLE
``` powershell
.\src\Get_Octo_Information.ps1 -yourApiKey
```
#>
[CmdletBinding()]
param (
[parameter(Mandatory=$true)] [string]$yourApiKey,
[string]$pr_projectName = "strata.rxnorm",
[string]$pr_path = (Join-Path $PSScriptRoot "..")
)
# Set variables
$apiKey = $yourApiKey
$octopusUrl = "https://octopus.sdt.local/"
$projectName = $pr_projectName # "strata.continuousimprovement"
#region Classes
class octoRelease {
[string]$environmentName
[string]$release
[string]$releaseNotes
# Default constructor
octoRelease() { $this.Init(@{}) }
# Convenience constructor
octoRelease([hashtable]$properties) { $this.Init($properties) }
octoRelease([string]$envName, [string]$rels, [string]$notes)
{
$this.Init(@{ environmentName = $envName; release = $rels; releaseNotes = $notes })
}
# Shared initializer method
[void] Init([hashtable]$Properties) {
foreach ($Property in $Properties.Keys) {
$this.$Property = $Properties.$Property
}
#$this.end = $this.range[-1];
}
}
#endregion
# Set headers
$headers = @{
"X-Octopus-ApiKey" = $apiKey
}
$octoReleaseList = @()
@("Dev","QA","Prod") | ForEach-Object {
$environmentName = $_
# Get project ID
$projectResponse = Invoke-RestMethod -Uri "$octopusUrl/api/projects/all" -Headers $headers
$project = $projectResponse | Where-Object { $_.Name -eq $projectName }
$projectId = $project.Id
# Get environment ID
$envResponse = Invoke-RestMethod -Uri "$octopusUrl/api/environments" -Headers $headers
$environment = $envResponse.Items | Where-Object { $_.Name -eq $environmentName }
$environmentId = $environment.Id
# Get latest deployment
$deploymentResponse = Invoke-RestMethod -Uri "$octopusUrl/api/deployments?projects=$projectId&environments=$environmentId&take=1&order=desc" -Headers $headers
$latestDeployment = $deploymentResponse.Items[0]
$releaseId = $latestDeployment.ReleaseId
# Get release details
$releaseDetails = Invoke-RestMethod -Uri "$octopusUrl/api/releases/$releaseId" -Headers $headers
# Output results
$release = $releaseDetails.Version
$releaseNotes = $releaseDetails.ReleaseNotes
$octoReleaseList += [octoRelease]::new($environmentName, $release, $releaseNotes)
#Write-Host $releaseDetails
}
$filePath = Join-Path $pr_path "/Octo_Release$((Get-Date).ToString("yyyyMMdd")).md"
$report = "# " + $projectName +" Octo Releases " + (Get-Date).ToString("yyyy-MM-dd")+ "`n## Details`n"
$report += $octoReleaseList | `
Format-Table @{name="environment"; Expression={$_.environmentName}}, @{Name="| release"; Expression={"| " + $_.release}}, @{Name="| notes"; Expression={"| " + $_.releaseNotes}} -AutoSize | `
Out-String
$report = $report -replace "--\s+--", "-- | --"
Write-Output $report.Replace("- | -", "- + -")
Set-Content -Path $filePath -Value $report
Write-Host -ForegroundColor DarkRed $filePath
+241
View File
@@ -0,0 +1,241 @@
<#
.SYNOPSIS
Get git tags and messages from multiple repositories.
.DESCRIPTION
This script retrieves git tags and their associated messages from multiple repositories and generates a markdown report.
.PARAMETER pr_patch
The patch version number to start from (default is 0).
.PARAMETER pr_minor_start
The starting minor version number (mandatory).
.PARAMETER pr_minor_end
The ending minor version number (mandatory).
.PARAMETER pr_tag
The tag string to filter by (default is "- CI -").
.PARAMETER pr_path
The path to the git repository (default is the parent directory of the script).
.PARAMETER pr_giturl
The base URL for the git repository (default is "https://github.com/stratadecision/rxnorm/pull/").
.PARAMETER pr_codeFreeze
The code freeze version string (optional).
.PARAMETER pr_qaRelease
The QA release version string (optional).
.PARAMETER pr_withPRLinks
Boolean indicating whether to include PR links in the report (default is false).
.EXAMPLE
``` powershell
.\src\Get_Tags_and_messages.ps1 -pr_patch 0 -pr_minor_start 587 -pr_minor_end 630 -pr_codeFreeze "0.30.4" -pr_qaRelease "0.30.4"
```
#>
[CmdletBinding()]
param (
[int]$pr_patch,
[parameter(Mandatory=$true)] [int]$pr_minor_start,
[parameter(Mandatory=$true)] [int]$pr_minor_end,
[string]$pr_tag = "- RxNorm -",
[string]$pr_path = (Join-Path $PSScriptRoot ".."),
[string]$pr_giturl = "https://github.com/stratadecision/rxnorm/pull/",
[string]$pr_codeFreeze,
[string]$pr_qaRelease,
[bool]$pr_withPRLinks = $false
)
if ($pr_minor_start -eq 0) { $pr_minor_start = 587 }
if ($pr_minor_end -eq 0) { $pr_minor_end = 630 }
#region Classes
class gitGroup {
[string]$tag
[string]$path
[int]$idx
[int[]]$range
[string]$codeFreeze
[string]$qaRelease
[string]$docName
[int]$end
# Default constructor
gitGroup() { $this.Init(@{}) }
# Convenience constructor
gitGroup([hashtable]$properties) { $this.Init($properties) }
gitGroup([string]$pth, [int]$id, [int[]]$rng)
{
$this.Init(@{ path = $pth; idx = $id; range = $rng})
}
# Shared initializer method
[void] Init([hashtable]$Properties) {
foreach ($Property in $Properties.Keys) {
$this.$Property = $Properties.$Property
}
$this.end = $this.range[-1];
}
updateEnd([int]$end)
{
$this.end = $end;
}
}
class gitInfo {
[string]$Release
[string]$PR
[string]$Team
[string]$Name
[int]$Feature
[int]$Bugfix
[string]$Pipe
# Default constructor
gitInfo() { $this.Init(@{}) }
# Convenience constructor from hashtable
gitInfo([hashtable]$Properties) { $this.Init($Properties) }
gitInfo([string]$id, [string]$pr, [string]$name, [string]$team)
{
$this.Init(@{ Release = $id; PR = $pr; Name = $name; Team = $team; })
}
gitInfo([string]$vars, [int]$feature, [int]$bugfix, [bool]$withPRLink, [string]$giturl)
{
try{
if ($vars -match '(?s)^([\d.]*)\s{2,}(.*)$')
{
$vars = $vars -creplace '(?s)^([\d.]*)\s{2,}(.*)$', '$1|||$2'
}
$vars += "||||"
Write-Output $vars
$vars
$arr = $vars.Split("|")
$arr | ForEach-Object { Write-Output '{0}' -f $_ }
$arr
$prTag = $arr[1];
if (![string]::IsNullOrEmpty($prTag) -and $withPRLink) {
$prTag = "[{0}]({1}{2})" -f $arr[1], $giturl, $arr[1];
}
$this.Init(@{
Release = $arr[0];
PR = $prTag;
Team = if ($arr[2].Trim() -eq 'snowflake') { 'the-outliers' } else { $arr[2] };
Name = $arr[3];
Feature = $feature;
Bugfix = $bugfix;
Pipe = "|";
})
} catch {
$msg = "`n`nInput ERROR`n`n{0}" -f $_.Exception.Message
Write-Host -ForegroundColor Red $msg
}
}
# Shared initializer method
[void] Init([hashtable]$Properties) {
foreach ($Property in $Properties.Keys) {
if ($Properties.$Property -is [string]) {
$this.$Property = $Properties.$Property.Trim()
} else {
$this.$Property = $Properties.$Property
}
}
}
}
#endregion
$date = Get-Date
$dateTag = $date.ToString('yyyyMMdd')
$gitGroups = @(
[gitGroup]::new(@{
tag = $pr_tag # "- CI -"
path = $pr_path # "C:\Git\rxnorm"
idx = $pr_patch # 1
range = $pr_minor_start..$pr_minor_end # 31..35
codeFreeze = $pr_codeFreeze # "0.30.4"
qaRelease = $pr_qaRelease # "0.30.4"
docName = "PR_Report_"+$dateTag+".md"
})
)
$gitGroups | `
#Sort-Object -Property Team, Release | `
Sort-Object -Property path | `
Format-Table tag, path, range, idx, codeFreeze -Autosize
$regExp = '^([\d.]*)[^#]*#([\d]*).*\/(.*)\/(.*)$'
$currDir = (Get-Location).Path
$gitGroups | ForEach-Object {
Clear-Host
$gitGrp = $_
$_ | `
Sort-Object -Property path | `
Format-Table tag, path, range, idx, codeFreeze -Autosize
$path = $_.path
Set-Location $path
$idx = $_.idx
$range = $_.range
$codeFreeze = if ($_.codeFreeze -ne "") {
"Code Freeze: {0}\`n" -f $_.codeFreeze
} else {""};
Write-Host $codeFreeze.Replace("\`n", "`n")
$qaRelease = $_.qaRelease
$end = $_.end
$gitList = @()
$range | ForEach-Object {
$feature = $_
$idx..9 | ForEach-Object {
$bugfix = $_
$x = (git tag 0.$feature.$bugfix -n)
if ($x -ne $null -and $x -ne "")
{
$vars = $x -replace $regExp,'$1|$2|$3|$4|'
$arr = $vars.Split('|')
if ($arr.Length -lt 5)
{
$vars = $x -replace '^([\d.]*)\s*(JAZZ.*)$','$1|||$2|'
}
if ($vars -ne $null)
{
$gitList += [gitInfo]::new($vars, $feature, $bugfix, $pr_withPRLinks, $pr_giturl)
}
$end = $feature
$gitGrp.updateEnd($end)
}
}
$idx = 0
}
$myDocuments = [Environment]::GetFolderPath("MyDocuments")
$tempText = Join-Path $myDocuments $_.docName
$tempText = Join-Path "./" $_.docName
$report = "# " + $_.tag +" " + (Get-Date).ToString("yyyy-MM-dd")+ "`n## Summary`nFrom: {0} -> {1} ({2}){5}\`n{3}QA Release: {4}`n## Details" -f `
$range[0], `
$end, `
($gitList | Measure-Object -Property Release).Count, `
$codeFreeze, $qaRelease,
($pr_withPRLinks ? "\`n*PR links included*" : "")
$report += $gitList | `
Sort-Object -Property Feature, Bugfix | `
Format-Table Release, @{Name="| PR"; Expression={"| " + $_.PR}}, @{Name="| Name"; Expression={"| " + $_.Name}} -Autosize | Out-String
# Clean up the report formatting for markdown
while ($report -match '--\s+--') {
$report = $report -replace '--\s+--', '-- | --' # Replace spaces with pipe
}
Set-Content -Path $tempText -Value $report
$report = $report.Replace('-- | --','-- + --') # Replace pipe with plus
Write-Output $report
Write-Host -ForegroundColor DarkRed $tempText
}
Set-Location $currDir
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
namespace Strata.RxNorm.Api.Attributes
{
/// <summary>
/// Used to disable model binding to prevent buffering the entire request body before entering the controller action.
/// We need this to stream the file as it is being uploaded to the server.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
}
@@ -0,0 +1,96 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Strata.RxNorm.Api.Attributes;
using Strata.RxNorm.Api.Extensions;
using Strata.RxNorm.Api.Generics;
using Strata.RxNorm.Biz.RxNorm;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Api.Controllers
{
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{api-version:apiVersion}/[controller]")]
public class FilesController(IRxNormService rxNormService) : ControllerBase
{
private readonly IRxNormService _rxNormService = rxNormService;
[HttpPost("{type}/[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status415UnsupportedMediaType)]
[DisableFormValueModelBinding]
[DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = int.MaxValue, ValueLengthLimit = int.MaxValue)]
public async Task<ActionResult> Upload(
[FromRoute] FileType type,
/* ignore this variable - it will be null due to DisableFormValueModelBinding, it's just for swagger */
[BindNever] IFormFile file,
CancellationToken cancellationToken)
{
var fileSection = await HttpContext.Request.GetFileFromMultipartUpload(cancellationToken);
if (fileSection?.FileStream == null)
{
// prob should return something to indicate exactly what went wrong.
return new UnsupportedMediaTypeResult();
}
switch (type)
{
case FileType.Concept:
await _rxNormService.SeedData<RxnConcept>(fileSection.FileStream, fileSection.FileName, cancellationToken);
break;
case FileType.Relationship:
await _rxNormService.SeedData<RxnRelation>(fileSection.FileStream, fileSection.FileName, cancellationToken);
break;
case FileType.Attribute:
await _rxNormService.SeedData<RxnAttribute>(fileSection.FileStream, fileSection.FileName, cancellationToken);
break;
}
return Ok();
}
[HttpGet("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<DownloadDateRange>> GetDownloadDateRange(CancellationToken cancellationToken)
{
var result = await _rxNormService.GetMinAndMaxReleaseDate(cancellationToken);
return Ok(new DownloadDateRange { MinDate = result.Item1, MaxDate = result.Item2 });
}
[HttpGet("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> DownloadAndProcess(CancellationToken cancellationToken)
{
return await DownloadAndProcessSpecific(DateTime.Now, false, cancellationToken);
}
[HttpGet("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> DownloadAndProcessSpecific(DateTime date, bool isCurrent, CancellationToken cancellationToken)
{
await _rxNormService.DownloadRxNormData(date, isCurrent, cancellationToken);
return Ok();
}
}
public enum FileType
{
Concept,
Relationship,
Attribute
}
public class DownloadDateRange
{
public DateTime MinDate { get; set; }
public DateTime MaxDate { get; set; }
public bool IsCurrent { get; set; } = false;
}
}
@@ -0,0 +1,108 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Strata.ApiLib.Standard.Models;
using Strata.RxNorm.Api.Generics;
using Strata.RxNorm.Biz.Jazz;
using Strata.RxNorm.Biz.Jobs;
using Strata.RxNorm.Biz.Pagination;
using Strata.RxNorm.Biz.RxNorm;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Api.Controllers
{
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{api-version:apiVersion}/[controller]")]
public class NdcController(INdcDbService jazzDbService,
IJazzJobs jazzJobs)
: ControllerBase
{
private readonly INdcDbService _ndcDbService = jazzDbService;
private readonly IJazzJobs _jazzJobs = jazzJobs;
[HttpPost("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(typeof(PagedApiResponse<DimNdc>), StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<PagedApiResponse<DimNdc>> GetNdcsAsync(NdcLoadingOptions loadingOptions, CancellationToken cancellationToken)
{
return await _ndcDbService.GetNdcsAsync(loadingOptions.PagingOptions, loadingOptions.FilterOptions, cancellationToken);
}
/// <summary>
/// Updates Jazz NDC data with optional clearing
/// </summary>
/// <param name="withClearNdc">Whether to clear existing NDC data before update</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Job ID for tracking the update process</returns>
[HttpGet("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<ActionResult<string>> UpdateJazzNdcAsync(bool withClearNdc, CancellationToken cancellationToken)
{
var jobId = await _jazzJobs.UpdateJazzNdcJob(withClearNdc, cancellationToken);
return Ok(jobId);
}
[HttpGet("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<ActionResult<string>> UpdateJazzBrandsAsync(CancellationToken cancellationToken)
{
var jobId = await _jazzJobs.UpdateJazzNdcBrandsJob(cancellationToken);
return Ok(jobId);
}
[HttpGet("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<ActionResult<string>> UpdateJazzDoseFormsAsync(CancellationToken cancellationToken)
{
var jobId = await _jazzJobs.UpdateJazzNdcDoseFormsJob(cancellationToken);
return Ok(jobId);
}
[HttpGet("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<ActionResult<string>> UpdateJazzSemanticClinicalDrugsAsync(CancellationToken cancellationToken)
{
var jobId = await _jazzJobs.UpdateJazzNdcSemanticClinicalDrugsJob(cancellationToken);
return Ok(jobId);
}
[HttpGet("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<ActionResult<string>> UpdateJazzBrandedClinicalDrugsAsync(CancellationToken cancellationToken)
{
var jobId = await _jazzJobs.UpdateJazzNdcBrandedClinicalDrugsJob(cancellationToken);
return Ok(jobId);
}
[HttpGet("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<ActionResult<string>> UpdateJazzClearDimNdc(CancellationToken cancellationToken)
{
var jobId = await _jazzJobs.UpdateJazzClearDimNdcJob(cancellationToken);
return Ok(jobId);
}
}
public class NdcLoadingOptions
{
public required PagingOptions PagingOptions { get; set; }
public required BaseFilterOptions FilterOptions { get; set; }
}
}
@@ -0,0 +1,48 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Strata.RxNorm.Api.Generics;
using Strata.RxNorm.Biz.Pharmacy;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Api.Controllers
{
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{api-version:apiVersion}/[controller]")]
public class PharmacyController(IPharmacyUpdateService pharmacyUpdateService) : ControllerBase
{
private readonly IPharmacyUpdateService _pharmacyUpdateService = pharmacyUpdateService;
[HttpPut("{withClearFirst}/[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public ActionResult<string> UpdatePharmacyDataAsync(bool withClearFirst, CancellationToken cancellationToken)
{
var jobId = _pharmacyUpdateService.RunPharmacyUpdateJob(withClearFirst, cancellationToken);
return Ok(jobId);
}
[HttpPut("{withClearFirst}/[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<ActionResult<string>> UpdateClientPharmacyDataAsync(bool withClearFirst, CancellationToken cancellationToken)
{
var jobId = await _pharmacyUpdateService.RunPharmacyUpdateClientJob(withClearFirst, cancellationToken);
return Ok(jobId);
}
[HttpPost("[action]")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<string> ResetNdcPharmacyAttributes(CancellationToken cancellationToken)
{
var jobId = _pharmacyUpdateService.RunResetNdcPharmacyAttributesJob(cancellationToken);
return Ok(jobId);
}
}
}
@@ -0,0 +1,144 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Strata.ApiLib.Standard.Models;
using Strata.RxNorm.Api.Generics;
using Strata.RxNorm.Biz.Pagination;
using Strata.RxNorm.Biz.RxNorm;
using Strata.RxNorm.Biz.RxNorm.Dimensions;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Api.Controllers
{
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{api-version:apiVersion}/[controller]")]
public class RxNormController(IRxNormService rxNormService) : ControllerBase
{
private readonly IRxNormService _rxNormService = rxNormService;
[HttpPost("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<PagedApiResponse<NdcRelationship>> GetRelationshipsAsync(LoadingOptions loadingOptions, CancellationToken cancellationToken)
{
return await _rxNormService.GetNdcRelationshipAsync(loadingOptions.PagingOptions, loadingOptions.FilterOptions, cancellationToken);
}
[HttpGet("{ndc}/[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<FactNdcDetail> GetNdcAsync(string ndc, CancellationToken cancellationToken)
{
return await _rxNormService.GetNdcAsync(ndc, cancellationToken);
}
[HttpPost("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<PagedApiResponse<Biz.RxNorm.RxNorm>> GetRxNormsAsync(RxNormLoadingOptions loadingOptions, CancellationToken cancellationToken)
{
return await _rxNormService.GetRxNormAsync(loadingOptions.PagingOptions, loadingOptions.FilterOptions, cancellationToken);
}
[HttpPost("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<PagedApiResponse<BrandedSemanticDrug>> GetBrandedSemanticDrugsAsync(RxNormLoadingOptions loadingOptions, CancellationToken cancellationToken)
{
return await _rxNormService.GetBrandedSemanticDrugsAsync(loadingOptions.PagingOptions, loadingOptions.FilterOptions, cancellationToken);
}
[HttpPost("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<PagedApiResponse<IRxNormConcept>> GetDimensionDataAsync(TtyLoadingOptions loadingOptions, CancellationToken cancellationToken)
{
return await _rxNormService.GetDimensionDataAsync(loadingOptions.Tty, loadingOptions.PagingOptions, loadingOptions.FilterOptions, cancellationToken);
}
[HttpPost("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<PagedApiResponse<FactNdcDetail>> GetNdcDetailsAsync(RxNormLoadingOptions loadingOptions, CancellationToken cancellationToken)
{
return await _rxNormService.GetNdcDetailsAsync(loadingOptions.PagingOptions, loadingOptions.FilterOptions, cancellationToken);
}
[HttpPut("[action]")]
[Authorize(Policy = Policies.StrataOnly)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<ActionResult> UpdateAllDimensions(CancellationToken cancellationToken)
{
await _rxNormService.UpdateAllDimensions(cancellationToken);
return Ok();
}
[HttpPut("[action]")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> UpdateBrandNameDimension(CancellationToken cancellationToken)
{
await _rxNormService.UpdateBrandNameDimension(cancellationToken);
return Ok();
}
[HttpPut("[action]")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> UpdateDoseFormDimension(CancellationToken cancellationToken)
{
await _rxNormService.UpdateDoseFormDimension(cancellationToken);
return Ok();
}
[HttpPut("[action]")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> UpdateBrandedDrugDimension(CancellationToken cancellationToken)
{
await _rxNormService.UpdateBrandedDrugDimension(cancellationToken);
return Ok();
}
[HttpPut("[action]")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> UpdateSemanticDrugDimension(CancellationToken cancellationToken)
{
await _rxNormService.UpdateSemanticDrugDimension(cancellationToken);
return Ok();
}
[HttpPut("[action]")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> UpdateFactNdcs(CancellationToken cancellationToken)
{
await _rxNormService.UpdateFactNdcs(cancellationToken);
return Ok();
}
}
public class RxNormLoadingOptions
{
public PagingOptions PagingOptions { get; set; }
public BaseFilterOptions FilterOptions { get; set; }
}
public class TtyLoadingOptions : RxNormLoadingOptions
{
public ITty Tty { get; set; }
}
public class LoadingOptions
{
public PagingOptions PagingOptions { get; set; }
public FilterOptions FilterOptions { get; set; }
}
}
@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Api.Extensions
{
internal static class MultipartFileControllerExtensions
{
public static async Task<FileMultipartSection> GetFileFromMultipartUpload(this HttpRequest request, CancellationToken cancellationToken = default)
{
// validation of Content-Type
// 1. first, it must be a form-data request
// 2. a boundary should be found in the Content-Type
if (!request.HasFormContentType ||
!MediaTypeHeaderValue.TryParse(request.ContentType, out var mediaTypeHeader) ||
string.IsNullOrEmpty(mediaTypeHeader.Boundary.Value))
{
return null;
}
var boundary = HeaderUtilities.RemoveQuotes(mediaTypeHeader.Boundary.Value).Value ?? string.Empty;
var reader = new MultipartReader(boundary, request.Body);
var section = await reader.ReadNextSectionAsync(cancellationToken);
var fileSection = section?.AsFileSection();
return fileSection is null
? null
: section.AsFileSection();
}
}
}
@@ -0,0 +1,8 @@
namespace Strata.RxNorm.Api.Generics
{
public class Policies
{
public const string StrataOnly = "StrataOnly";
public const string IsAdmin = "IsAdmin";
}
}
+93
View File
@@ -0,0 +1,93 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.CommandLineUtils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Strata.Configuration.Client.DependencyInjection;
using Strata.healthchecks.Biz;
using Strata.Logging.DependencyInjection;
using Strata.Logging.Serilog.Formatting;
using Strata.RxNorm.Biz.DbContexts;
using System;
using System.Diagnostics.CodeAnalysis;
namespace Strata.RxNorm.Api
{
[ExcludeFromCodeCoverage]
public class Program
{
public static void Main(string[] args)
{
try
{
var commandLineApplication = new CommandLineApplication(false);
var doMigrate = commandLineApplication.Option(
"--ef-migrate",
"Apply entity framework migrations and exit",
CommandOptionType.NoValue);
commandLineApplication.HelpOption("-? | -h | --help");
commandLineApplication.OnExecute(() =>
{
ExecuteApp(args, doMigrate);
return 0;
});
commandLineApplication.Execute(args);
}
catch (Exception ex)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console(new LogStashFormatter())
.CreateLogger();
Log.Fatal(ex, "There was an unhandled exception that caused the application to crash");
//This is for serilog to ensure that all of the cached logs are flushed when the app crashes
//This is useful so we can capture logs when the application crashes during startup:
Log.CloseAndFlush();
Environment.Exit(1);
}
}
public static void ExecuteApp(string[] args, CommandOption doMigrate)
{
var webHost = CreateWebHostBuilder(args).Build();
if (doMigrate.HasValue())
{
Log.Debug("Applying Entity Framework migrations");
using var scope = webHost.Services.CreateScope();
using var context = scope.ServiceProvider.GetService<RxNormDbContext>();
try
{
context.Database.SetCommandTimeout(TimeSpan.FromMinutes(10));
context.Database.Migrate();
Log.Debug("All done, closing app");
Log.CloseAndFlush();
Environment.Exit(Environment.ExitCode);
}
catch (Exception ex)
{
Log.Error(ex, "There was an unhandled exception that caused the program to crash");
Log.CloseAndFlush();
Environment.Exit(1);
}
}
// no flags provided, so just run the webhost
webHost.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStrataConfiguration()
.UseStrataLogging()
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "Strata.healthchecks.Jazz")
.UseStrataHealthChecks()
.UseStartup<Startup>();
}
}
@@ -0,0 +1,15 @@
{
"profiles": {
"Strata.RxNorm.Api": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"Hangfire__RunLocally": "true",
"ASPNETCORE_ENVIRONMENT": "Development",
"AWS_Profile": "sdt-rxnorm-service-role",
"AWS_Region": "us-east-1"
},
"applicationUrl": "https://localhost:8443"
}
}
}
+101
View File
@@ -0,0 +1,101 @@
using Hangfire;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Strata.ApiLib.Core;
using Strata.ApiLib.Core.Cors.Extensions;
using Strata.ApiLib.Core.Transformers;
using Strata.CoreLib.Claims.Extensions;
using Strata.Hangfire.AspNetCore.Filters.Dashboard;
using Strata.RxNorm.Api.Generics;
using Strata.RxNorm.Biz.Configurations;
using Strata.SignalR.Configuration;
using Strata.SwaggerExtensions;
using System.Diagnostics.CodeAnalysis;
using System.Net.Mime;
using System.Text.Json;
namespace Strata.RxNorm.Api
{
[ExcludeFromCodeCoverage]
public class Startup
{
private readonly IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddStrataPlatformServices(_configuration);
services.AddControllers(options =>
{
//Prevent browsers from caching API responses (this addresses an IE11 issue):
options.Filters.Add(
new ResponseCacheAttribute()
{
Location = ResponseCacheLocation.None,
NoStore = false,
Duration = -1
});
options.ReturnHttpNotAcceptable = true;
options.Filters.Add(new ProducesAttribute(MediaTypeNames.Application.Json));
options.Filters.Add(new ConsumesAttribute(MediaTypeNames.Application.Json));
options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()));
})
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
// Uncomment this line if you want enum to be serialized / deserialized as string value instead of int value
//options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
services.AddAuthorizationBuilder()
.AddPolicy(Policies.IsAdmin, policy => policy.RequireAssertion(context => context.User.IsInRole("CI - System Administrator") || context.User.IsSdtEmployee()))
.AddPolicy(Policies.StrataOnly, policy => policy.RequireAssertion(context => context.User.IsSdtEmployee()));
// configure hangfire
services.AddStrataHangfire();
services.AddRxNormServices(_configuration);
services.AddRouting();
services.AddSwagger(_configuration);
services.AddStrataSignalR();
services.AddStrataHealthChecks(_configuration);
services.AddStrataCors(_configuration);
services.AddHealthChecks();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
{
app.UseRouting();
app.UseStrataCorsPolicy(env);
#if DEBUG
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = [new AllowAnyoneDashboardAuthorizationFilter()]
});
#endif
app.UseAccessTokenQueryStringMiddleware();
app.UseStrataPlatformMiddleware();
app.ConfigureSwagger(env, provider);
app.UseEndpoints(endpoints => endpoints.MapControllers().RequireAuthorization());
app.UseEndpoints(endpoints => endpoints.MapNotificationHub());
app.UseStrataHealthChecks(env);
}
}
}
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
<IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.501.25" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
<PackageReference Include="Strata.ApiLib.Core" Version="8.4.0" />
<PackageReference Include="Strata.Configuration.Client" Version="9.15.1" />
<PackageReference Include="Strata.Hangfire.AspNetCore" Version="5.3.0" />
<PackageReference Include="Strata.healthchecks.Biz" Version="3.2.0" />
<PackageReference Include="Strata.healthchecks.Jazz" Version="3.2.0" />
<PackageReference Include="Strata.healthchecks.Postgres" Version="3.2.0" />
<PackageReference Include="Strata.healthchecks.Redis" Version="3.2.0" />
<PackageReference Include="Strata.healthchecks.Snowflake" Version="3.2.0" />
<PackageReference Include="Strata.Logging" Version="4.1.1" />
<PackageReference Include="Strata.SwaggerExtensions" Version="4.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Strata.RxNorm.Biz\Strata.RxNorm.Biz.csproj" />
<ProjectReference Include="..\Strata.RxNorm.Shared\Strata.RxNorm.Shared.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,15 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"hangfire": {
"redis": {
"prefix": "rxnorm-jobs-service",
"configOptions": "ddenredisc01.sdt.local:6379,ddenredisc02.sdt.local:6379,ddenredisc03.sdt.local:6379,ddenredisc04.sdt.local:6379,ddenredisc05.sdt.local:6379,ddenredisc06.sdt.local:6379"
}
}
}
+33
View File
@@ -0,0 +1,33 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"Version": "0.0.0",
"configuration": {
"basicAuthUsername": "BkQsRDdmdjkFNhKVuTI.TpujQJXuKhGDcV.F_hjADMRTaEUWMJnviaesoOrhhfnz",
"basicAuthPassword": "GWPIIWECDqh.hjDNdpxVEfeFe-bYFwPSx-oBGFD_od_SUQVk-QqTIkZY_NXMeqeo"
},
"StrataConfigServerBaseUrl": "https://configuration.dev.stratanetwork.net",
"strataApi": {
"clientSecret": "V_okTrkWYBs-PqMP.inbJShIvMGWcpdqCDq_ZjntVF.ydgRCoYaDVJYEWEIdsjJ_"
},
"HealthCheck": {
"PushToDataDog": true,
"StrataJazz": true,
"JazzDatabaseGuid": "54409f55-5ddf-4748-8222-9ccfb04fb4f5",
"Snowflake": true,
"Redis": true,
"Sqs": false,
"Sns": false,
"PostgresDatabaseName": "rxnorm",
"SqsQueueNames": [],
"SnsTopicArns": []
},
"Snowflake": {
"Url": "https://stratadev.us-east-1.privatelink.snowflakecomputing.com",
"Account": "stratadev"
}
}
@@ -0,0 +1,66 @@
using Microsoft.Extensions.Options;
using Strata.Id.Client;
using Strata.Id.Client.DTO;
using Strata.Licenses.Client;
using Strata.RxNorm.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.ClientLicense
{
public class ClientLicenseService : IClientLicenseService
{
private readonly ILicensesServiceClient _licensesService;
private readonly IIdServiceClient _idServiceClient;
private readonly DatabaseRegexSection _databaseRegex;
// Well-known GUID for CI license
private static readonly Guid _ciLicenseGuid = Guid.Parse("feb1a208-693c-43fc-bdff-306bdfcdaaa4");
public ClientLicenseService(
ILicensesServiceClient licensesService,
IIdServiceClient idServiceClient,
IOptions<DatabaseRegexSection> sectionAccessor)
{
_licensesService = licensesService;
_idServiceClient = idServiceClient;
_databaseRegex = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(sectionAccessor));
}
/// <summary>
/// Gets eligible clients based on license status and other criteria
/// </summary>
/// <param name="isLicensed">True to get licensed clients, false to get non-licensed clients</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of eligible clients</returns>
public async Task<List<StrataIdFullDto>> GetEligibleCIClientsAsync(bool isLicensed, CancellationToken cancellationToken)
{
var licenses = (await _licensesService
.GetLicenseLinksByLicenseGuidAsync(_ciLicenseGuid, cancellationToken))
.Where(license => license.IsLicensed);
var licensedDbGuids = licenses
.Select(l => l.DatabaseGuid)
.ToHashSet();
var allClients = await _idServiceClient.GetStrataIds(cancellationToken);
var eligibleClients = allClients.Where(client =>
client.StrataIdType == Id.Client.Enums.StrataIdType.Live
&& client.OrgPin != "0000"
&& client.AppVersion != "Not Specified"
&& client.AppVersion != "0.0"
&& client.AppVersion.AppVersion() >= 2023.31m
&& (_databaseRegex?.DatabaseRegexExpression?.IsMatch(client.ServerName) ?? true)
&& (isLicensed
? licensedDbGuids.Contains(client.DatabaseGUID)
: !licensedDbGuids.Contains(client.DatabaseGUID))
).ToList();
return eligibleClients;
}
}
}
@@ -0,0 +1,12 @@
using Strata.Id.Client.DTO;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.ClientLicense
{
public interface IClientLicenseService
{
Task<List<StrataIdFullDto>> GetEligibleCIClientsAsync(bool isLicensed, CancellationToken cancellationToken);
}
}
@@ -0,0 +1,9 @@
namespace Strata.RxNorm.Biz.Configurations
{
public static class Constants
{
public static readonly string ApplicationName = "rxnorm";
public static readonly string HangfireQueueDefault = "rxnorm";
public static readonly string HangfireQueueJazz = "rxnorm-jazz";
}
}
@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Strata.RxNorm.Biz.Notification;
namespace Strata.RxNorm.Biz.Configurations
{
public static class HubEndpointRouteBuilderExtensions
{
public static HubEndpointConventionBuilder MapNotificationHub(this IEndpointRouteBuilder endpoints)
{
return endpoints.MapHub<NotificationHub>("/api/NotificationHub", configureOptions: null)
.RequireAuthorization();
}
}
}
@@ -0,0 +1,18 @@
using Microsoft.EntityFrameworkCore;
using Strata.RxNorm.Biz.RxNorm;
using Strata.RxNorm.Biz.RxNorm.Dimensions;
namespace Strata.RxNorm.Biz.DbContexts
{
public interface IRxNormDbContext
{
DbSet<DimBrandedDrug> DimBrandedDrugs { get; set; }
DbSet<DimBrandName> DimBrandNames { get; set; }
DbSet<DimDoseForm> DimDoseForms { get; set; }
DbSet<DimSemanticDrug> DimSemanticDrugs { get; set; }
DbSet<FactNdc> FactNdcs { get; set; }
DbSet<RxnAttribute> RxnAttributes { get; set; }
DbSet<RxnConcept> RxnConcepts { get; set; }
DbSet<RxnRelation> RxnRelations { get; set; }
}
}
@@ -0,0 +1,126 @@
using Microsoft.EntityFrameworkCore;
using Strata.RxNorm.Biz.Jazz;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.DbContexts
{
public class JazzDbContext : DbContext
{
public virtual DbSet<DimNdc> DimNdcs { get; set; }
public virtual DbSet<NdcDetail> NdcDetails { get; set; }
public JazzDbContext(DbContextOptions<JazzDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<DimNdc>()
.ToTable(tb => tb.UseSqlOutputClause(false));
modelBuilder.Entity<NdcDetail>();
}
public async Task<bool> CheckIfColumnExists(string tableName, string columnName)
{
// Execute a SQL query against INFORMATION_SCHEMA.COLUMNS
var query = $@"SELECT Count(*) [Value]
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '{tableName}' AND COLUMN_NAME = '{columnName}'";
var result = await Database.SqlQueryRaw<int>(query).SingleAsync();
return result == 1;
}
/// <summary>
/// Gets the list of columns in the DimNdc table
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<List<string>> GetDimNdcColumns(CancellationToken cancellationToken)
{
var entity = Model.FindEntityType(typeof(DimNdc));
var tableName = entity.GetTableName();
var schema = entity.GetSchema();
var query = $@"SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '{tableName}' AND TABLE_SCHEMA = '{schema}'";
return await Database.SqlQueryRaw<string>(query).ToListAsync(cancellationToken);
}
public async Task<int> DropNdcDetailTable(CancellationToken cancellationToken)
{
return await Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS [tmp].[ndc_detail]", cancellationToken);
}
public async Task<int> CreateNdcDetailTable(CancellationToken cancellationToken)
{
return await Database.ExecuteSqlRawAsync(@"CREATE TABLE [tmp].[ndc_detail](
[NDCId][int] NOT NULL,
[NDCCode][nvarchar](100) NOT NULL,
[SemanticClinicalDrugCode][varchar](50) NOT NULL DEFAULT '0',
[SemanticClinicalDrugDescription][varchar](3000) NOT NULL DEFAULT 'Not Specified',
[BrandedClinicalDrugCode][varchar](50) NOT NULL DEFAULT '0',
[BrandedClinicalDrugDescription][varchar](3000) NOT NULL DEFAULT 'Not Specified',
[BrandCode][varchar](50) NOT NULL DEFAULT '0',
[BrandDescription][varchar](3000) NOT NULL DEFAULT 'Not Specified',
[DoseFormCode][varchar](50) NOT NULL DEFAULT '0',
[DoseFormDescription][varchar](3000) NOT NULL DEFAULT 'Not Specified'
PRIMARY KEY CLUSTERED
(
[NDCId] ASC
)WITH(PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON[PRIMARY]
) ON[PRIMARY]", cancellationToken);
}
public async Task<int> UpdateDimNdcTable(CancellationToken cancellationToken)
{
return await Database.ExecuteSqlRawAsync(@"MERGE dss.DimNDC AS target
USING (SELECT * FROM tmp.ndc_detail) AS source(
NDCId,
NDCCode,
SemanticClinicalDrugCode,
SemanticClinicalDrugDescription,
BrandedClinicalDrugCode,
BrandedClinicalDrugDescription,
BrandCode,
BrandDescription,
DoseFormCode,
DoseFormDescription)
ON (target.NDCId = source.NDCId)
WHEN MATCHED AND
(target.SemanticClinicalDrugCode <> source.SemanticClinicalDrugCode
OR target.SemanticClinicalDrugDescription <> source.SemanticClinicalDrugDescription
OR target.BrandedClinicalDrugCode <> source.BrandedClinicalDrugCode
OR target.BrandedClinicalDrugDescription <> source.BrandedClinicalDrugDescription
OR target.BrandCode <> source.BrandCode
OR target.BrandDescription <> source.BrandDescription
OR target.DoseFormCode <> source.DoseFormCode
OR target.DoseFormDescription <> source.DoseFormDescription) THEN
UPDATE SET target.[SemanticClinicalDrugCode] = source.SemanticClinicalDrugCode,
target.SemanticClinicalDrugDescription = source.SemanticClinicalDrugDescription,
target.BrandedClinicalDrugCode = source.BrandedClinicalDrugCode,
target.BrandedClinicalDrugDescription = source.BrandedClinicalDrugDescription,
target.BrandCode = source.BrandCode,
target.BrandDescription = source.BrandDescription,
target.DoseFormCode = source.DoseFormCode,
target.DoseFormDescription = source.DoseFormDescription;", cancellationToken);
}
public async Task<int> ClearDimNdc(CancellationToken cancellationToken)
{
return await Database.ExecuteSqlRawAsync(@"UPDATE dss.DimNdc
SET SemanticClinicalDrugCode = '0',
SemanticClinicalDrugDescription = 'Not Specified',
BrandedClinicalDrugCode = '0',
BrandedClinicalDrugDescription = 'Not Specified',
DoseFormCode = '0',
DoseFormDescription = 'Not Specified',
BrandCode = '0',
BrandDescription = 'Not Specified'
WHERE 1=1", cancellationToken);
}
}
}
@@ -0,0 +1,265 @@
using Dapper;
using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore;
using Npgsql;
using Strata.RxNorm.Biz.RxNorm;
using Strata.RxNorm.Biz.RxNorm.Dimensions;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.DbContexts
{
public class RxNormDbContext : DbContext
{
public virtual DbSet<RxnConcept> RxnConcepts { get; set; }
public virtual DbSet<RxnRelation> RxnRelations { get; set; }
public virtual DbSet<RxnAttribute> RxnAttributes { get; set; }
public virtual DbSet<FactNdc> FactNdcs { get; set; }
public virtual DbSet<DimBrandedDrug> DimBrandedDrugs { get; set; }
public virtual DbSet<DimBrandName> DimBrandNames { get; set; }
public virtual DbSet<DimDoseForm> DimDoseForms { get; set; }
public virtual DbSet<DimSemanticDrug> DimSemanticDrugs { get; set; }
public virtual DbSet<NdcRelationship> NdcRelationships { get; set; }
public virtual DbSet<RxNorm.RxNorm> RxNorms { get; set; }
public virtual DbSet<BrandedSemanticDrug> Branded_Semantic_Drugs { get; set; }
public virtual DbSet<FactNdcDetail> FactNdcDetails { get; set; }
public RxNormDbContext(DbContextOptions<RxNormDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DimBrandedDrug>().HasAlternateKey(d => d.RxCui);
modelBuilder.Entity<DimBrandName>().HasAlternateKey(d => d.RxCui);
modelBuilder.Entity<DimDoseForm>().HasAlternateKey(d => d.RxCui);
modelBuilder.Entity<DimSemanticDrug>().HasAlternateKey(d => d.RxCui);
modelBuilder.Entity<RxnRelation>()
.HasIndex(r => new { r.Rxcui1, r.Sab });
modelBuilder.Entity<RxnConcept>()
.HasIndex(c => new { c.Rxcui, c.Sab, c.Tty });
modelBuilder.Entity<RxnAttribute>()
.HasIndex(a => new { a.Rxcui, a.Atn, a.Sab });
modelBuilder
.Entity<NdcRelationship>(entity =>
{
entity.HasKey(e => new { e.Rxcui1, e.Rxcui2 });
entity.ToView(nameof(NdcRelationships).ToLower());
entity.HasAlternateKey(r => new { r.Ndc, r.Tty, r.Code });
});
modelBuilder
.Entity<RxNorm.RxNorm>(entity =>
{
entity.HasKey(e => new { e.Rxcui, e.Code });
entity.ToView(nameof(RxNorms).ToLower());
});
modelBuilder
.Entity<BrandedSemanticDrug>(entity =>
{
entity.HasKey(e => new { e.Rxcui1, e.Rxcui2 });
entity.ToView(nameof(Branded_Semantic_Drugs).ToLower());
});
modelBuilder
.Entity<FactNdcDetail>(entity =>
{
entity.HasKey(e => e.NdcCode);
entity.ToView("fact_ndc_details");
});
modelBuilder
.Entity<FactNdc>(entity =>
{
entity
.HasOne(e => e.BrandName)
.WithMany(e => e.FactNdcs);
entity
.HasOne(e => e.DoseForm)
.WithMany(e => e.FactNdcs);
entity
.HasOne(e => e.BrandedDrug)
.WithMany(e => e.FactNdcs);
entity
.HasOne(e => e.SemanticDrug)
.WithMany(e => e.FactNdcs);
});
}
private DbCommand LoadStoredProc(string storedProc)
{
var cmd = Database.GetDbConnection().CreateCommand();
cmd.CommandText = storedProc;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
return cmd;
}
/// <summary>
/// This is the asynchronous version of System.Data.Common.DbCommand.ExecuteNonQuery.
/// Providers should override with an appropriate implementation. The cancellation
/// token may optionally be ignored. The default implementation invokes the synchronous
/// System.Data.Common.DbCommand.ExecuteNonQuery method and returns a completed task,
/// blocking the calling thread. The default implementation will return a cancelled
/// task if passed an already cancelled cancellation token. Exceptions thrown by
/// System.Data.Common.DbCommand.ExecuteNonQuery will be communicated via the returned
/// Task Exception property. Do not invoke other methods and properties of the DbCommand
/// object until the returned Task is complete.
/// </summary>
/// <param name="storedProc"></param>
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <exception cref="System.Data.Common.DbException">An error occurred while executing the command.</exception>
public async Task<int> ExecuteStoredProcAsync(string storedProc, CancellationToken cancellationToken)
{
var cmd = LoadStoredProc(storedProc);
await cmd.Connection.OpenAsync(cancellationToken);
var result = await cmd.ExecuteNonQueryAsync(cancellationToken);
await cmd.Connection.CloseAsync();
return result;
}
public async Task<int> UpdateDimBrandNames(CancellationToken cancellationToken)
=> await ExecuteStoredProcAsync("public.update_dim_brand_names", cancellationToken);
public async Task<int> UpdateDimDoseForms(CancellationToken cancellationToken)
=> await ExecuteStoredProcAsync("public.update_dim_dose_forms", cancellationToken);
public async Task<int> UpdateDimBrandedDrugs(CancellationToken cancellationToken)
=> await ExecuteStoredProcAsync("public.update_dim_branded_drugs", cancellationToken);
public async Task<int> UpdateDimSemanticDrugs(CancellationToken cancellationToken)
=> await ExecuteStoredProcAsync("public.update_dim_semantic_drugs", cancellationToken);
public async Task<int> UpdateAllDimensions(CancellationToken cancellationToken)
{
var cmd = Database.GetDbConnection().CreateCommand();
cmd.CommandType = System.Data.CommandType.StoredProcedure;
await cmd.Connection.OpenAsync(cancellationToken);
cmd.CommandText = "public.update_dim_brand_names";
var result = await cmd.ExecuteNonQueryAsync(cancellationToken);
cmd.CommandText = "public.update_dim_dose_forms";
result += await cmd.ExecuteNonQueryAsync(cancellationToken);
cmd.CommandText = "public.update_dim_branded_drugs";
result += await cmd.ExecuteNonQueryAsync(cancellationToken);
cmd.CommandText = "public.update_dim_semantic_drugs";
result += await cmd.ExecuteNonQueryAsync(cancellationToken);
await cmd.Connection.CloseAsync();
return result;
}
public async Task<int> UpdateFactNdcs(CancellationToken cancellationToken)
{
var timeout = Database.GetCommandTimeout();
Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
var rows = await Database.ExecuteSqlRawAsync(@"
WITH src AS (
SELECT DISTINCT ndc, brand_name_id, dose_form_id, semantic_drug_id, branded_drug_id
FROM public.factndc
)
UPDATE public.fact_ndcs tgt
SET brand_name_id = src.brand_name_id,
dose_form_id = src.dose_form_id,
semantic_drug_id = src.semantic_drug_id,
branded_drug_id = src.branded_drug_id
FROM src
WHERE tgt.ndc = src.ndc;", cancellationToken);
rows += await Database.ExecuteSqlRawAsync(@"
INSERT INTO public.fact_ndcs (ndc, brand_name_id, dose_form_id, semantic_drug_id, branded_drug_id)
SELECT ndc, brand_name_id, dose_form_id, semantic_drug_id, branded_drug_id
FROM (
SELECT DISTINCT ndc, brand_name_id, dose_form_id, semantic_drug_id, branded_drug_id
FROM public.factndc
) AS src
WHERE NOT EXISTS (
SELECT 1 FROM public.fact_ndcs tgt WHERE tgt.ndc = src.ndc
);", cancellationToken);
rows -= await Database.ExecuteSqlRawAsync(@"
DELETE FROM public.fact_ndcs tgt
WHERE NOT EXISTS (
SELECT 1 FROM public.factndc src WHERE src.ndc = tgt.ndc
);", cancellationToken);
Database.SetCommandTimeout(timeout);
return rows;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Intenionally left empty
}
/// <summary>
/// Truncates <typeparamref name="TEntity"/> table and inserts all records in bulk operation.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="records"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task SeedTable<TEntity>(List<TEntity> records, CancellationToken cancellationToken) where TEntity : class
{
await this.TruncateAsync<TEntity>(cancellationToken: cancellationToken);
await this.BulkInsertAsync(records, cancellationToken: cancellationToken);
await this.BulkSaveChangesAsync(cancellationToken: cancellationToken);
}
/// <summary>
/// Uses postgresql COPY command to bulk insert data from STDIN. More efficient than EF Core SaveChanges.
/// https://www.postgresql.org/docs/current/sql-copy.html
/// https://www.npgsql.org/doc/copy.html#text-copy
/// </summary>
public async Task<TextWriter> BeginTextImportAsync<TEntity>(char delimiter = '|', CancellationToken cancellationToken = default)
{
var connection = Database.GetDbConnection();
if (connection is not NpgsqlConnection npgsqlConnection)
{
throw new NotSupportedException("BeginTextImportAsync is only supported for NpgsqlConnections");
}
if (npgsqlConnection.State != System.Data.ConnectionState.Open)
{
await npgsqlConnection.OpenAsync(cancellationToken);
}
// use the EF Core IModel to get the actual table name and schema in the database
var tableMapping = Model.FindEntityType(typeof(TEntity));
if (tableMapping is null)
{
throw new NotSupportedException($"Entity type {typeof(TEntity).Name} is not mapped to a table");
}
var tableName = tableMapping.GetTableName();
var schemaName = tableMapping.GetSchema() ?? Model.GetDefaultSchema() ?? "public";
// selects all the columns in the table that are not auto generated identity columns
const string sql = """
select c.column_name
from information_schema.columns c
where c.table_schema = @schema_name and c.table_name = @table_name and c.is_identity = 'NO'
order by c.ordinal_position
;
""";
var columns = await connection.QueryAsync<string>(sql, new { schema_name = schemaName, table_name = tableName });
var columnsList = string.Join(", ", columns.Select(c => $"\"{c}\""));
// build the COPY command from the columns and table name
var beginTextImportQuery = $"COPY \"{schemaName}\".\"{tableName}\" ({columnsList}) FROM STDIN (DELIMITER '{delimiter}')";
return await npgsqlConnection.BeginTextImportAsync(beginTextImportQuery, cancellationToken);
}
}
}
@@ -0,0 +1,60 @@
using Hangfire.Common;
using Hangfire.Server;
using Strata.CoreLib.Claims;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Threading;
namespace Strata.RxNorm.Biz.Hangfire
{
/// <summary>
/// Captures Job parameters as Claims on the ThreadPrincipal running the job
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method)]
public class CaptureJobParameterAttribute : JobFilterAttribute, IServerFilter
{
public int DatabaseGuidArgumentPosition { get; set; } = -1;
public int StrataIdArgumentPosition { get; set; } = -1;
// server filters
public void OnPerforming(PerformingContext filterContext)
{
var claims = new List<Claim>();
var args = filterContext.BackgroundJob.Job.Args.ToArray();
if (StrataIdArgumentPosition >= 0)
{
var strataIdValue = string.Format(CultureInfo.CurrentCulture, $"{{{StrataIdArgumentPosition}}}", args);
if (int.TryParse(strataIdValue, out var strataId))
{
claims.Add(new Claim(StrataClaims.StrataId, strataId.ToString()));
}
}
if (DatabaseGuidArgumentPosition >= 0)
{
var databaseGuidValue = string.Format(CultureInfo.CurrentCulture, $"{{{DatabaseGuidArgumentPosition}}}", args);
if (Guid.TryParse(databaseGuidValue, out var databaseGuid))
{
claims.Add(new Claim(StrataClaims.DatabaseGuid, databaseGuid.ToString()));
}
}
if (claims.Count == 0)
{
return;
}
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "hangfire_impersonation"));
Thread.CurrentPrincipal = principal;
}
public void OnPerformed(PerformedContext filterContext)
{
Thread.CurrentPrincipal = null;
}
}
}
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="New Rule Set" Description=" " ToolsVersion="10.0">
</RuleSet>
@@ -0,0 +1,87 @@
using Microsoft.AspNetCore.SignalR;
using System;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Hangfire
{
public interface IHangfireHub
{
Task SendNumber(int i);
Task SendFailureMessage(Exception e);
Task SendSuccessMessage(string taskName);
Task SendHangfireStatus(string status, string objectId, string payload);
}
public class HangfireHub : Hub<IHangfireHub>
{
public const string HubURL = "/api/HangfireHub";
public const string ErrorStatus = "error";
public const string SuccessStatus = "success";
public const string InfoStatus = "info";
public async Task Register(string topic, CancellationToken cancellationToken)
{
await Groups.AddToGroupAsync(Context.ConnectionId, topic, cancellationToken);
}
public async Task Remove(string topic, CancellationToken cancellationToken)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, topic, cancellationToken);
}
}
public static class HangfireHubContextExtensions
{
public static Task SendInfo(this IHubContext<HangfireHub, IHangfireHub> context, object objectId, object payload)
{
return context.SendStatus(HangfireHub.InfoStatus, objectId, payload);
}
public static Task SendSuccess(this IHubContext<HangfireHub, IHangfireHub> context, object objectId, object payload)
{
return context.SendStatus(HangfireHub.SuccessStatus, objectId, payload);
}
public static Task SendFailure(this IHubContext<HangfireHub, IHangfireHub> context, object objectId, Exception exception)
{
return context.SendStatus(HangfireHub.ErrorStatus, objectId, exception.Message);
}
public static Task SendFailure(this IHubContext<HangfireHub, IHangfireHub> context, object objectId, object payload)
{
return context.SendStatus(HangfireHub.ErrorStatus, objectId, payload);
}
public static Task SendStatus(this IHubContext<HangfireHub, IHangfireHub> context, string status, object objectId, object payload)
{
return context.Clients.All.SendHangfireStatus(status, JsonSerializer.Serialize(objectId), JsonSerializer.Serialize(payload));
}
public static Task SendInfoToGroup(this IHubContext<HangfireHub, IHangfireHub> context, string topic, object objectId, object payload)
{
return context.SendStatusToGroup(topic, HangfireHub.InfoStatus, objectId, payload);
}
public static Task SendSuccessToGroup(this IHubContext<HangfireHub, IHangfireHub> context, string topic, object objectId, object payload)
{
return context.SendStatusToGroup(topic, HangfireHub.SuccessStatus, objectId, payload);
}
public static Task SendFailureToGroup(this IHubContext<HangfireHub, IHangfireHub> context, string topic, object objectId, Exception exception)
{
return context.SendStatusToGroup(topic, HangfireHub.ErrorStatus, objectId, exception.Message);
}
public static Task SendFailureToGroup(this IHubContext<HangfireHub, IHangfireHub> context, string topic, object objectId, object payload)
{
return context.SendStatusToGroup(topic, HangfireHub.ErrorStatus, objectId, payload);
}
public static Task SendStatusToGroup(this IHubContext<HangfireHub, IHangfireHub> context, string topic, string status, object objectId, object payload)
{
return context.Clients.Group(topic).SendHangfireStatus(status, JsonSerializer.Serialize(objectId), JsonSerializer.Serialize(payload));
}
}
}
@@ -0,0 +1,64 @@
using Hangfire;
using Hangfire.Common;
using Hangfire.States;
using Hangfire.Storage;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Strata.RxNorm.Biz.Hangfire
{
public class NotifyJobFilterAttribute : JobFilterAttribute, IApplyStateFilter
{
private readonly ILogger<NotifyJobFilterAttribute> _logger;
private readonly IHubContext<HangfireHub, IHangfireHub> _hangfireHubContext;
public NotifyJobFilterAttribute([NotNull] ILogger<NotifyJobFilterAttribute> logger, [NotNull] IHubContext<HangfireHub, IHangfireHub> hangfireHubContext)
{
_logger = logger;
_hangfireHubContext = hangfireHubContext;
}
public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
if (context.NewState.Name == SucceededState.StateName)
{
Notify(false, context);
}
if (context.NewState.Name == FailedState.StateName)
{
Notify(true, context);
}
}
private void Notify(bool isError, ApplyStateContext context)
{
var displayName = context.BackgroundJob.Id;
var displayNameAttribute = context.BackgroundJob.Job.Method.GetCustomAttributes(typeof(JobDisplayNameAttribute), false).FirstOrDefault() as JobDisplayNameAttribute;
if (displayNameAttribute != null)
{
displayName = string.Format(displayNameAttribute.DisplayName, context.BackgroundJob.Job.Args.ToArray());
}
var status = isError ? HangfireHub.ErrorStatus : HangfireHub.SuccessStatus;
var statusMessage = isError ? "Finished with Errors" : "Finished successfully";
var message = $"[{displayName}] {statusMessage}";
_logger.LogInformation(message);
_hangfireHubContext.SendStatus(status, context.BackgroundJob.Id, message);
}
public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
}
}
}
+17
View File
@@ -0,0 +1,17 @@
using Strata.RxNorm.Biz.DbContexts;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Jobs
{
public interface IJazzJobs
{
Task<JazzDbContext> GetDbContext(CancellationToken cancellationToken);
Task<string> UpdateJazzNdcJob(bool withClearFirst, CancellationToken cancellationToken);
Task<string> UpdateJazzClearDimNdcJob(CancellationToken cancellationToken);
Task<string> UpdateJazzNdcBrandedClinicalDrugsJob(CancellationToken cancellationToken);
Task<string> UpdateJazzNdcBrandsJob(CancellationToken cancellationToken);
Task<string> UpdateJazzNdcDoseFormsJob(CancellationToken cancellationToken);
Task<string> UpdateJazzNdcSemanticClinicalDrugsJob(CancellationToken cancellationToken);
}
}
@@ -0,0 +1,12 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Jobs
{
public interface IRxNormUpdateJob
{
Task UpdateRxNormJob(DateTime? now = null, int daysLeft = 5, CancellationToken cancellationToken = default);
}
}
+85
View File
@@ -0,0 +1,85 @@
using Hangfire;
using Strata.CoreLib.Claims;
using Strata.CoreLib.Claims.Extensions;
using Strata.Id.Client;
using Strata.Id.Client.DTO;
using Strata.RxNorm.Biz.DbContexts;
using Strata.RxNorm.Biz.Jazz;
using Strata.SqlTools.Configuration.Common.AsyncFactory;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Jobs
{
public class JazzJobs(
IBackgroundJobClient backgroundJobClient,
IClaimsPrincipalAccessor claimsPrincipalAccessor,
IIdServiceClient idServiceClient,
IAsyncDbContextFactory<JazzDbContext> dbContextFactory) : IJazzJobs
{
private readonly IBackgroundJobClient _backgroundJobClient = backgroundJobClient;
private readonly ClaimsPrincipal _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
private readonly IIdServiceClient _idServiceClient = idServiceClient;
protected readonly IAsyncDbContextFactory<JazzDbContext> _dbContextFactory = dbContextFactory;
public async Task<JazzDbContext> GetDbContext(CancellationToken cancellationToken)
{
return await _dbContextFactory.CreateDbContextAsync(cancellationToken);
}
public async Task<string> UpdateJazzNdcJob(bool withClearFirst, CancellationToken cancellationToken)
{
var client = await GetStrataId(cancellationToken);
return _backgroundJobClient.Enqueue<RefreshClientDbDimNdc>(job =>
job.RefreshDimNdcProperties(client.DatabaseGUID, client.StrataId, client.DatabaseName,
false, string.Empty, cancellationToken));
}
public async Task<string> UpdateJazzNdcBrandsJob(CancellationToken cancellationToken)
{
var client = await GetStrataId(cancellationToken);
return _backgroundJobClient.Enqueue<RefreshClientDbDimNdc>(job =>
job.RefreshDimNdcProperties(client.DatabaseGUID, client.StrataId, client.DatabaseName,
false, nameof(DimNdc.BrandCode), cancellationToken));
}
public async Task<string> UpdateJazzNdcDoseFormsJob(CancellationToken cancellationToken)
{
var client = await GetStrataId(cancellationToken);
return _backgroundJobClient.Enqueue<RefreshClientDbDimNdc>(job =>
job.RefreshDimNdcProperties(client.DatabaseGUID, client.StrataId, client.DatabaseName,
false, nameof(DimNdc.DoseFormCode), cancellationToken));
}
public async Task<string> UpdateJazzNdcBrandedClinicalDrugsJob(CancellationToken cancellationToken)
{
var client = await GetStrataId(cancellationToken);
return _backgroundJobClient.Enqueue<RefreshClientDbDimNdc>(job =>
job.RefreshDimNdcProperties(client.DatabaseGUID, client.StrataId, client.DatabaseName,
false, nameof(DimNdc.BrandedClinicalDrugCode), cancellationToken));
}
public async Task<string> UpdateJazzNdcSemanticClinicalDrugsJob(CancellationToken cancellationToken)
{
var client = await GetStrataId(cancellationToken);
return _backgroundJobClient.Enqueue<RefreshClientDbDimNdc>(job =>
job.RefreshDimNdcProperties(client.DatabaseGUID, client.StrataId, client.DatabaseName,
false, nameof(DimNdc.SemanticClinicalDrugCode), cancellationToken));
}
public async Task<string> UpdateJazzClearDimNdcJob(CancellationToken cancellationToken)
{
var client = await GetStrataId(cancellationToken);
return _backgroundJobClient.Enqueue<RefreshClientDbDimNdc>(job =>
job.ClearClientDimNdc(client.DatabaseGUID, client.StrataId, client.DatabaseName, cancellationToken));
}
private async Task<StrataIdFullDto> GetStrataId(CancellationToken cancellationToken)
{
var strataId = _user.GetStrataId();
return await _idServiceClient.GetStrataId(strataId, cancellationToken);
}
}
}
@@ -0,0 +1,95 @@
using Hangfire;
using Hangfire.Throttling;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Strata.Hangfire.Filters.JobFilters;
using Strata.RxNorm.Biz.DbContexts;
using Strata.RxNorm.Biz.Hangfire;
using Strata.RxNorm.Biz.Jazz;
using Strata.RxNorm.Biz.Utilities;
using Strata.SqlTools.Configuration.Common.AsyncFactory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Jobs
{
public class PharmacyUpdateClientJob
{
private readonly INdcDbService _ndcDbService;
private readonly IAsyncDbContextFactory<JazzDbContext> _dbContextFactory;
private readonly ILogger<PharmacyUpdateClientJob> _logger;
public PharmacyUpdateClientJob(
INdcDbService ndcDbService,
IAsyncDbContextFactory<JazzDbContext> dbContextFactory,
ILogger<PharmacyUpdateClientJob> logger)
{
_ndcDbService = ndcDbService;
_dbContextFactory = dbContextFactory;
_logger = logger;
}
public async Task<JazzDbContext> GetDbContext(CancellationToken cancellationToken)
{
return await _dbContextFactory.CreateDbContextAsync(cancellationToken);
}
/// <summary>
/// The required columns in DimNdc table to perform the update
/// </summary>
internal static readonly Lazy<List<string>> RequiredColumns = new(() =>
[
nameof(DimNdc.BrandCode),
nameof(DimNdc.BrandDescription),
nameof(DimNdc.DoseFormCode),
nameof(DimNdc.DoseFormDescription),
nameof(DimNdc.BrandedClinicalDrugCode),
nameof(DimNdc.BrandedClinicalDrugDescription),
nameof(DimNdc.SemanticClinicalDrugCode),
nameof(DimNdc.SemanticClinicalDrugDescription)
]);
/// <summary>
/// Update the Jazz NDC details from DimNdc table
/// </summary>
/// <param name="databaseGuid"></param>
/// <param name="strataId"></param>
/// <param name="databaseName"></param>
/// <param name="withClearFirst"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[DeleteOnSuccess]
[Queue("rxnorm-jazz")]
[JobDisplayName("Refresh Client DimNdc Details ({1} - {2})")]
[Semaphore(nameof(RefreshClientDetails))]
[CaptureJobParameter(DatabaseGuidArgumentPosition = 0, StrataIdArgumentPosition = 1)]
public async Task RefreshClientDetails(Guid databaseGuid, int strataId, string databaseName, bool withClearFirst, CancellationToken cancellationToken = default)
{
await using var dbContext = GetDbContext(cancellationToken).Result;
var columns = await dbContext.GetDimNdcColumns(cancellationToken);
var requiredColumns = RequiredColumns.Value;
if (!requiredColumns.All(rc => columns.Contains(rc))) { return; }
var dimNdcs = await dbContext.DimNdcs.AnyAsync(cancellationToken);
if (!dimNdcs) { return; }
try
{
if (withClearFirst)
{
await _ndcDbService.ClearDimNdcData(cancellationToken);
}
await _ndcDbService.UpdateJazzNdc(false, cancellationToken);
}
catch (Exception ex)
{
_logger.LogServiceError(ex, dbContext);
throw;
}
}
}
}
@@ -0,0 +1,58 @@
using Hangfire;
using Strata.Hangfire.Filters.JobFilters;
using Strata.Id.Client;
using Strata.RxNorm.Biz.ClientLicense;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Jobs
{
public class PharmacyUpdateJob(
IClientLicenseService clientLicenseService,
IIdServiceClient idServiceClient,
IBackgroundJobClient backgroundJobClient)
{
[DeleteOnSuccess]
[Queue("rxnorm-jazz")]
[JobDisplayName("Update Pharmacy Data on client DimNDC table")]
public async Task UpdateDimNdc(bool withClearFirst, CancellationToken cancellationToken)
{
// Get licensed CI clients with valid app versions and org pins
var refreshClients = await clientLicenseService.GetEligibleCIClientsAsync(isLicensed: true, cancellationToken);
foreach (var client in refreshClients.Where(c => c.IsInMaintenanceMode))
{
backgroundJobClient.Schedule<PharmacyUpdateJob>(job
=> job.RefreshClientInMaintenanceMode(client.StrataId, withClearFirst, 1, cancellationToken), TimeSpan.FromMinutes(30));
}
foreach (var client in refreshClients.Where(c => !c.IsInMaintenanceMode))
{
backgroundJobClient.Enqueue<PharmacyUpdateClientJob>(job
=> job.RefreshClientDetails(client.DatabaseGUID, client.StrataId, client.DatabaseName, withClearFirst, cancellationToken));
}
}
[DeleteOnSuccess]
[JobDisplayName("Update Pharmacy Data on client DimNDC table in maintenance mode ({0})")]
public async Task RefreshClientInMaintenanceMode(int strataId, bool withClearFirst, int tries, CancellationToken cancellationToken)
{
if (tries > 5) { return; }
var client = await idServiceClient.GetStrataId(strataId, cancellationToken);
if (client == null) { return; }
if (client.IsInMaintenanceMode)
{
backgroundJobClient.Schedule<PharmacyUpdateJob>(job
=> job.RefreshClientInMaintenanceMode(client.StrataId, withClearFirst, tries + 1, cancellationToken), TimeSpan.FromMinutes(30));
}
else
{
backgroundJobClient.Enqueue<PharmacyUpdateClientJob>(job
=> job.RefreshClientDetails(client.DatabaseGUID, client.StrataId, client.DatabaseName, withClearFirst, cancellationToken));
}
}
}
}
@@ -0,0 +1,94 @@
using Hangfire;
using Hangfire.Throttling;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Strata.Hangfire.Filters.JobFilters;
using Strata.RxNorm.Biz.DbContexts;
using Strata.RxNorm.Biz.Hangfire;
using Strata.RxNorm.Biz.Jazz;
using Strata.RxNorm.Biz.Utilities;
using Strata.SqlTools.Configuration.Common.AsyncFactory;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Jobs
{
public class RefreshClientDbDimNdc(
INdcDbService ndcDbService,
IAsyncDbContextFactory<JazzDbContext> dbContextFactory,
ILogger<RefreshClientDbDimNdc> logger
)
{
private readonly INdcDbService _ndcDbService = ndcDbService;
private readonly IAsyncDbContextFactory<JazzDbContext> _dbContextFactory = dbContextFactory;
private readonly ILogger<RefreshClientDbDimNdc> _logger = logger;
public async Task<JazzDbContext> GetDbContext(CancellationToken cancellationToken)
{
return await _dbContextFactory.CreateDbContextAsync(cancellationToken);
}
[DeleteOnSuccess]
[Queue("rxnorm-jazz")]
[JobDisplayName("Refresh Client DimNdc {4} ({1} - {2})")]
[Semaphore(nameof(RefreshDimNdcProperties))]
[CaptureJobParameter(DatabaseGuidArgumentPosition = 0, StrataIdArgumentPosition = 1)]
public async Task RefreshDimNdcProperties(Guid databaseGuid, int strataId, string databaseName,
bool withNormalization, string columnName, CancellationToken cancellationToken)
{
await using var dbContext = GetDbContext(cancellationToken).Result;
var dimNdcs = await dbContext.DimNdcs.AnyAsync(cancellationToken);
if (!dimNdcs) { return; }
if (!(await dbContext.CheckIfColumnExists("DimNDC", columnName))) { return; }
try
{
switch (columnName)
{
case nameof(DimNdc.BrandCode):
await _ndcDbService.UpdateJazzNdcBrands(withNormalization, cancellationToken);
break;
case nameof(DimNdc.DoseFormCode):
await _ndcDbService.UpdateJazzNdcDoseForms(withNormalization, cancellationToken);
break;
case nameof(DimNdc.BrandedClinicalDrugCode):
await _ndcDbService.UpdateJazzNdcBrandedClinicalDrugs(withNormalization, cancellationToken);
break;
case nameof(DimNdc.SemanticClinicalDrugCode):
await _ndcDbService.UpdateJazzNdcSemanticClinicalDrugs(withNormalization, cancellationToken);
break;
default:
await _ndcDbService.UpdateJazzNdc(withNormalization, cancellationToken);
break;
}
}
catch (Exception ex)
{
_logger.LogServiceError(ex, dbContext);
throw;
}
}
[DeleteOnSuccess]
[Queue("rxnorm-jazz")]
[JobDisplayName("Clear Client DimNdc RxNorm Colummns ({1} - {2})")]
[Semaphore(nameof(ClearClientDimNdc))]
[CaptureJobParameter(DatabaseGuidArgumentPosition = 0, StrataIdArgumentPosition = 1)]
public async Task ClearClientDimNdc(Guid databaseGuid, int strataId, string databaseName, CancellationToken cancellationToken)
{
await using var dbContext = GetDbContext(cancellationToken).Result;
try
{
await dbContext.ClearDimNdc(cancellationToken);
}
catch (Exception ex)
{
_logger.LogServiceError(ex, dbContext);
throw;
}
}
}
}
@@ -0,0 +1,57 @@
using Hangfire;
using Strata.Hangfire.Filters.JobFilters;
using Strata.Id.Client;
using Strata.RxNorm.Biz.ClientLicense;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Jobs
{
public class ResetNdcPharmacyAttributesJob(
IClientLicenseService clientLicenseService,
IIdServiceClient idServiceClient,
IBackgroundJobClient backgroundJobClient)
{
[DeleteOnSuccess]
[Queue("rxnorm-jazz")]
[JobDisplayName("Clear DimNdc Pharmacy Columns in JAZZ for all non-licensed CI clients")]
public async Task ResetNdcPharmacyAttributes(CancellationToken cancellationToken)
{
// Get non-licensed CI clients with valid app versions and org pins
var clearClients = await clientLicenseService.GetEligibleCIClientsAsync(isLicensed: false, cancellationToken);
foreach (var client in clearClients.Where(c => c.IsInMaintenanceMode))
{
backgroundJobClient.Schedule<ResetNdcPharmacyAttributesJob>(job
=> job.ClearClientInMaintenanceMode(client.StrataId, 1, cancellationToken), TimeSpan.FromMinutes(30));
}
foreach (var client in clearClients.Where(c => !c.IsInMaintenanceMode))
{
backgroundJobClient.Enqueue<RefreshClientDbDimNdc>(job
=> job.ClearClientDimNdc(client.DatabaseGUID, client.StrataId, client.DatabaseName, cancellationToken));
}
}
[DeleteOnSuccess]
[Queue("rxnorm-jazz")]
[JobDisplayName("Clear DimNdc Pharmacy Columns in JAZZ for client in maintenance mode ({0})")]
public async Task ClearClientInMaintenanceMode(int strataId, int retries, CancellationToken cancellationToken)
{
if (retries > 5) { return; }
var client = await idServiceClient.GetStrataId(strataId, cancellationToken);
if (client == null) { return; }
if (client.IsInMaintenanceMode)
{
backgroundJobClient.Schedule<ResetNdcPharmacyAttributesJob>(job
=> job.ClearClientInMaintenanceMode(client.StrataId, retries + 1, cancellationToken), TimeSpan.FromMinutes(30));
}
else
{
backgroundJobClient.Enqueue<RefreshClientDbDimNdc>(job
=> job.ClearClientDimNdc(client.DatabaseGUID, client.StrataId, client.DatabaseName, cancellationToken));
}
}
}
}
@@ -0,0 +1,30 @@
using Hangfire;
using Strata.Hangfire.Filters.JobFilters;
using Strata.RxNorm.Biz.RxNorm;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Jobs
{
public class RxNormUpdateJob(IBackgroundJobClient backgroundJobClient,
IRxNormService rxNormService) : IRxNormUpdateJob
{
private readonly IBackgroundJobClient _backgroundJobClient = backgroundJobClient;
private readonly IRxNormService _rxNormService = rxNormService;
[DeleteOnSuccess]
[JobDisplayName("Update RxNorm data in Postgres")]
public async Task UpdateRxNormJob(DateTime? now = null, int daysLeft = 5, CancellationToken cancellationToken = default)
{
now ??= DateTime.Now;
var result = await _rxNormService.DownloadLAndProcessZipFile(now.Value, true, cancellationToken);
if (!result && daysLeft > 0)
{
daysLeft -= 1;
_backgroundJobClient.Schedule<RxNormUpdateJob>(job => job.UpdateRxNormJob(now, daysLeft, CancellationToken.None), TimeSpan.FromDays(1));
}
}
}
}
@@ -0,0 +1,455 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Strata.RxNorm.Biz.DbContexts;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
[DbContext(typeof(RxNormDbContext))]
[Migration("20250220160537_AddRxNormFileTables")]
partial class AddRxNormFileTables
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandName", b =>
{
b.Property<int>("BrandNameId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("brand_name_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandNameId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandNameId")
.HasName("pk_dim_brand_name");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_brand_name_rx_cui");
b.ToTable("dim_brand_name", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandedDrug", b =>
{
b.Property<int>("BrandedDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandedDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandedDrugId")
.HasName("pk_dim_branded_drug");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_branded_drug_rx_cui");
b.ToTable("dim_branded_drug", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimDoseForm", b =>
{
b.Property<int>("DoseFormId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("dose_form_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("DoseFormId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("DoseFormId")
.HasName("pk_dim_dose_form");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_dose_form_rx_cui");
b.ToTable("dim_dose_form", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimSemanticDrug", b =>
{
b.Property<int>("SemanticDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SemanticDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("SemanticDrugId")
.HasName("pk_dim_semantic_drug");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_semantic_drug_rx_cui");
b.ToTable("dim_semantic_drug", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnAttribute", b =>
{
b.Property<int>("RxnAttributeId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_attribute_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnAttributeId"));
b.Property<string>("Atn")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("atn");
b.Property<string>("Atui")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("atui");
b.Property<string>("Atv")
.HasMaxLength(4000)
.HasColumnType("character varying(4000)")
.HasColumnName("atv");
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Satui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("satui");
b.Property<string>("Stype")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("stype");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnAttributeId")
.HasName("pk_rxn_attributes");
b.ToTable("rxn_attributes", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnConcept", b =>
{
b.Property<int>("RxnConceptId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_concept_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnConceptId"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("IsPref")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("is_pref");
b.Property<string>("Lat")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("lat");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Saui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("saui");
b.Property<string>("Scui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("scui");
b.Property<string>("Sdui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("sdui");
b.Property<string>("Srl")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("srl");
b.Property<string>("Str")
.IsRequired()
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("str");
b.Property<string>("Stt")
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("stt");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.Property<string>("Ts")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("ts");
b.Property<string>("Tty")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("tty");
b.HasKey("RxnConceptId")
.HasName("pk_rxn_concepts");
b.ToTable("rxn_concepts", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnRelation", b =>
{
b.Property<int>("RxnRelationId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_relation_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnRelationId"));
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Dir")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("dir");
b.Property<string>("Rel")
.HasMaxLength(4)
.HasColumnType("character varying(4)")
.HasColumnName("rel");
b.Property<string>("Rela")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("rela");
b.Property<string>("Rg")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rg");
b.Property<string>("Rui")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rui");
b.Property<string>("Rxaui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui1");
b.Property<string>("Rxaui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui2");
b.Property<string>("Rxcui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui2");
b.Property<string>("SType1")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type1");
b.Property<string>("SType2")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type2");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Sl")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("sl");
b.Property<string>("Srui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("srui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnRelationId")
.HasName("pk_rxn_relations");
b.ToTable("rxn_relations", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,187 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
/// <inheritdoc />
public partial class AddRxNormFileTables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "dim_brand_name",
columns: table => new
{
brand_name_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
rx_cui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
code = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
description = table.Column<string>(type: "character varying(3000)", maxLength: 3000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_dim_brand_name", x => x.brand_name_id);
table.UniqueConstraint("ak_dim_brand_name_rx_cui", x => x.rx_cui);
});
migrationBuilder.CreateTable(
name: "dim_branded_drug",
columns: table => new
{
branded_drug_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
rx_cui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
code = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
description = table.Column<string>(type: "character varying(3000)", maxLength: 3000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_dim_branded_drug", x => x.branded_drug_id);
table.UniqueConstraint("ak_dim_branded_drug_rx_cui", x => x.rx_cui);
});
migrationBuilder.CreateTable(
name: "dim_dose_form",
columns: table => new
{
dose_form_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
rx_cui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
code = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
description = table.Column<string>(type: "character varying(3000)", maxLength: 3000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_dim_dose_form", x => x.dose_form_id);
table.UniqueConstraint("ak_dim_dose_form_rx_cui", x => x.rx_cui);
});
migrationBuilder.CreateTable(
name: "dim_semantic_drug",
columns: table => new
{
semantic_drug_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
rx_cui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
code = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
description = table.Column<string>(type: "character varying(3000)", maxLength: 3000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_dim_semantic_drug", x => x.semantic_drug_id);
table.UniqueConstraint("ak_dim_semantic_drug_rx_cui", x => x.rx_cui);
});
migrationBuilder.CreateTable(
name: "rxn_attributes",
columns: table => new
{
rxn_attribute_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
rxcui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true),
lui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true),
sui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true),
rxaui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true),
stype = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
code = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
atui = table.Column<string>(type: "character varying(11)", maxLength: 11, nullable: true),
satui = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
atn = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: false),
sab = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
atv = table.Column<string>(type: "character varying(4000)", maxLength: 4000, nullable: true),
suppress = table.Column<string>(type: "character varying(1)", maxLength: 1, nullable: true),
cvf = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_rxn_attributes", x => x.rxn_attribute_id);
});
migrationBuilder.CreateTable(
name: "rxn_concepts",
columns: table => new
{
rxn_concept_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
rxcui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
lat = table.Column<string>(type: "character varying(3)", maxLength: 3, nullable: false),
ts = table.Column<string>(type: "character varying(1)", maxLength: 1, nullable: true),
lui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true),
stt = table.Column<string>(type: "character varying(3)", maxLength: 3, nullable: true),
sui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true),
is_pref = table.Column<string>(type: "character varying(1)", maxLength: 1, nullable: true),
rxaui = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
saui = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
scui = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
sdui = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
sab = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
tty = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
code = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
str = table.Column<string>(type: "character varying(3000)", maxLength: 3000, nullable: false),
srl = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: true),
suppress = table.Column<string>(type: "character varying(1)", maxLength: 1, nullable: true),
cvf = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_rxn_concepts", x => x.rxn_concept_id);
});
migrationBuilder.CreateTable(
name: "rxn_relations",
columns: table => new
{
rxn_relation_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
rxcui1 = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true),
rxaui1 = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true),
s_type1 = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
rel = table.Column<string>(type: "character varying(4)", maxLength: 4, nullable: true),
rxcui2 = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true),
rxaui2 = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true),
s_type2 = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
rela = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
rui = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: true),
srui = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
sab = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
sl = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
dir = table.Column<string>(type: "character varying(1)", maxLength: 1, nullable: true),
rg = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: true),
suppress = table.Column<string>(type: "character varying(1)", maxLength: 1, nullable: true),
cvf = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_rxn_relations", x => x.rxn_relation_id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "dim_brand_name");
migrationBuilder.DropTable(
name: "dim_branded_drug");
migrationBuilder.DropTable(
name: "dim_dose_form");
migrationBuilder.DropTable(
name: "dim_semantic_drug");
migrationBuilder.DropTable(
name: "rxn_attributes");
migrationBuilder.DropTable(
name: "rxn_concepts");
migrationBuilder.DropTable(
name: "rxn_relations");
}
}
}
@@ -0,0 +1,455 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Strata.RxNorm.Biz.DbContexts;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
[DbContext(typeof(RxNormDbContext))]
[Migration("20250220160749_AddRxNormDimensions")]
partial class AddRxNormDimensions
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandName", b =>
{
b.Property<int>("BrandNameId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("brand_name_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandNameId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandNameId")
.HasName("pk_dim_brand_names");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_brand_names_rx_cui");
b.ToTable("dim_brand_names", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandedDrug", b =>
{
b.Property<int>("BrandedDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandedDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandedDrugId")
.HasName("pk_dim_branded_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_branded_drugs_rx_cui");
b.ToTable("dim_branded_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimDoseForm", b =>
{
b.Property<int>("DoseFormId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("dose_form_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("DoseFormId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("DoseFormId")
.HasName("pk_dim_dose_forms");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_dose_forms_rx_cui");
b.ToTable("dim_dose_forms", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimSemanticDrug", b =>
{
b.Property<int>("SemanticDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SemanticDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("SemanticDrugId")
.HasName("pk_dim_semantic_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_semantic_drugs_rx_cui");
b.ToTable("dim_semantic_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnAttribute", b =>
{
b.Property<int>("RxnAttributeId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_attribute_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnAttributeId"));
b.Property<string>("Atn")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("atn");
b.Property<string>("Atui")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("atui");
b.Property<string>("Atv")
.HasMaxLength(4000)
.HasColumnType("character varying(4000)")
.HasColumnName("atv");
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Satui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("satui");
b.Property<string>("Stype")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("stype");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnAttributeId")
.HasName("pk_rxn_attributes");
b.ToTable("rxn_attributes", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnConcept", b =>
{
b.Property<int>("RxnConceptId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_concept_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnConceptId"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("IsPref")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("is_pref");
b.Property<string>("Lat")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("lat");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Saui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("saui");
b.Property<string>("Scui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("scui");
b.Property<string>("Sdui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("sdui");
b.Property<string>("Srl")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("srl");
b.Property<string>("Str")
.IsRequired()
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("str");
b.Property<string>("Stt")
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("stt");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.Property<string>("Ts")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("ts");
b.Property<string>("Tty")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("tty");
b.HasKey("RxnConceptId")
.HasName("pk_rxn_concepts");
b.ToTable("rxn_concepts", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnRelation", b =>
{
b.Property<int>("RxnRelationId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_relation_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnRelationId"));
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Dir")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("dir");
b.Property<string>("Rel")
.HasMaxLength(4)
.HasColumnType("character varying(4)")
.HasColumnName("rel");
b.Property<string>("Rela")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("rela");
b.Property<string>("Rg")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rg");
b.Property<string>("Rui")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rui");
b.Property<string>("Rxaui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui1");
b.Property<string>("Rxaui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui2");
b.Property<string>("Rxcui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui2");
b.Property<string>("SType1")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type1");
b.Property<string>("SType2")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type2");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Sl")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("sl");
b.Property<string>("Srui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("srui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnRelationId")
.HasName("pk_rxn_relations");
b.ToTable("rxn_relations", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,194 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
/// <inheritdoc />
public partial class AddRxNormDimensions : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropUniqueConstraint(
name: "ak_dim_semantic_drug_rx_cui",
table: "dim_semantic_drug");
migrationBuilder.DropPrimaryKey(
name: "pk_dim_semantic_drug",
table: "dim_semantic_drug");
migrationBuilder.DropUniqueConstraint(
name: "ak_dim_dose_form_rx_cui",
table: "dim_dose_form");
migrationBuilder.DropPrimaryKey(
name: "pk_dim_dose_form",
table: "dim_dose_form");
migrationBuilder.DropUniqueConstraint(
name: "ak_dim_branded_drug_rx_cui",
table: "dim_branded_drug");
migrationBuilder.DropPrimaryKey(
name: "pk_dim_branded_drug",
table: "dim_branded_drug");
migrationBuilder.DropUniqueConstraint(
name: "ak_dim_brand_name_rx_cui",
table: "dim_brand_name");
migrationBuilder.DropPrimaryKey(
name: "pk_dim_brand_name",
table: "dim_brand_name");
migrationBuilder.RenameTable(
name: "dim_semantic_drug",
newName: "dim_semantic_drugs");
migrationBuilder.RenameTable(
name: "dim_dose_form",
newName: "dim_dose_forms");
migrationBuilder.RenameTable(
name: "dim_branded_drug",
newName: "dim_branded_drugs");
migrationBuilder.RenameTable(
name: "dim_brand_name",
newName: "dim_brand_names");
migrationBuilder.AddUniqueConstraint(
name: "ak_dim_semantic_drugs_rx_cui",
table: "dim_semantic_drugs",
column: "rx_cui");
migrationBuilder.AddPrimaryKey(
name: "pk_dim_semantic_drugs",
table: "dim_semantic_drugs",
column: "semantic_drug_id");
migrationBuilder.AddUniqueConstraint(
name: "ak_dim_dose_forms_rx_cui",
table: "dim_dose_forms",
column: "rx_cui");
migrationBuilder.AddPrimaryKey(
name: "pk_dim_dose_forms",
table: "dim_dose_forms",
column: "dose_form_id");
migrationBuilder.AddUniqueConstraint(
name: "ak_dim_branded_drugs_rx_cui",
table: "dim_branded_drugs",
column: "rx_cui");
migrationBuilder.AddPrimaryKey(
name: "pk_dim_branded_drugs",
table: "dim_branded_drugs",
column: "branded_drug_id");
migrationBuilder.AddUniqueConstraint(
name: "ak_dim_brand_names_rx_cui",
table: "dim_brand_names",
column: "rx_cui");
migrationBuilder.AddPrimaryKey(
name: "pk_dim_brand_names",
table: "dim_brand_names",
column: "brand_name_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropUniqueConstraint(
name: "ak_dim_semantic_drugs_rx_cui",
table: "dim_semantic_drugs");
migrationBuilder.DropPrimaryKey(
name: "pk_dim_semantic_drugs",
table: "dim_semantic_drugs");
migrationBuilder.DropUniqueConstraint(
name: "ak_dim_dose_forms_rx_cui",
table: "dim_dose_forms");
migrationBuilder.DropPrimaryKey(
name: "pk_dim_dose_forms",
table: "dim_dose_forms");
migrationBuilder.DropUniqueConstraint(
name: "ak_dim_branded_drugs_rx_cui",
table: "dim_branded_drugs");
migrationBuilder.DropPrimaryKey(
name: "pk_dim_branded_drugs",
table: "dim_branded_drugs");
migrationBuilder.DropUniqueConstraint(
name: "ak_dim_brand_names_rx_cui",
table: "dim_brand_names");
migrationBuilder.DropPrimaryKey(
name: "pk_dim_brand_names",
table: "dim_brand_names");
migrationBuilder.RenameTable(
name: "dim_semantic_drugs",
newName: "dim_semantic_drug");
migrationBuilder.RenameTable(
name: "dim_dose_forms",
newName: "dim_dose_form");
migrationBuilder.RenameTable(
name: "dim_branded_drugs",
newName: "dim_branded_drug");
migrationBuilder.RenameTable(
name: "dim_brand_names",
newName: "dim_brand_name");
migrationBuilder.AddUniqueConstraint(
name: "ak_dim_semantic_drug_rx_cui",
table: "dim_semantic_drug",
column: "rx_cui");
migrationBuilder.AddPrimaryKey(
name: "pk_dim_semantic_drug",
table: "dim_semantic_drug",
column: "semantic_drug_id");
migrationBuilder.AddUniqueConstraint(
name: "ak_dim_dose_form_rx_cui",
table: "dim_dose_form",
column: "rx_cui");
migrationBuilder.AddPrimaryKey(
name: "pk_dim_dose_form",
table: "dim_dose_form",
column: "dose_form_id");
migrationBuilder.AddUniqueConstraint(
name: "ak_dim_branded_drug_rx_cui",
table: "dim_branded_drug",
column: "rx_cui");
migrationBuilder.AddPrimaryKey(
name: "pk_dim_branded_drug",
table: "dim_branded_drug",
column: "branded_drug_id");
migrationBuilder.AddUniqueConstraint(
name: "ak_dim_brand_name_rx_cui",
table: "dim_brand_name",
column: "rx_cui");
migrationBuilder.AddPrimaryKey(
name: "pk_dim_brand_name",
table: "dim_brand_name",
column: "brand_name_id");
}
}
}
@@ -0,0 +1,491 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Strata.RxNorm.Biz.DbContexts;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
[DbContext(typeof(RxNormDbContext))]
[Migration("20250220221647_AddFactNDCTable")]
partial class AddFactNDCTable
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandName", b =>
{
b.Property<int>("BrandNameId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("brand_name_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandNameId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandNameId")
.HasName("pk_dim_brand_names");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_brand_names_rx_cui");
b.ToTable("dim_brand_names", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandedDrug", b =>
{
b.Property<int>("BrandedDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandedDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandedDrugId")
.HasName("pk_dim_branded_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_branded_drugs_rx_cui");
b.ToTable("dim_branded_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimDoseForm", b =>
{
b.Property<int>("DoseFormId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("dose_form_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("DoseFormId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("DoseFormId")
.HasName("pk_dim_dose_forms");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_dose_forms_rx_cui");
b.ToTable("dim_dose_forms", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimSemanticDrug", b =>
{
b.Property<int>("SemanticDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SemanticDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("SemanticDrugId")
.HasName("pk_dim_semantic_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_semantic_drugs_rx_cui");
b.ToTable("dim_semantic_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.FactNdc", b =>
{
b.Property<int>("FactNdcId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("fact_ndc_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("FactNdcId"));
b.Property<int>("BrandNameId")
.HasColumnType("integer")
.HasColumnName("brand_name_id");
b.Property<int>("BrandedDrugId")
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
b.Property<int>("DoseFormId")
.HasColumnType("integer")
.HasColumnName("dose_form_id");
b.Property<string>("Ndc")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("ndc");
b.Property<int>("SemanticDrugId")
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
b.HasKey("FactNdcId")
.HasName("pk_fact_ndc");
b.ToTable("fact_ndc", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnAttribute", b =>
{
b.Property<int>("RxnAttributeId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_attribute_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnAttributeId"));
b.Property<string>("Atn")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("atn");
b.Property<string>("Atui")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("atui");
b.Property<string>("Atv")
.HasMaxLength(4000)
.HasColumnType("character varying(4000)")
.HasColumnName("atv");
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Satui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("satui");
b.Property<string>("Stype")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("stype");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnAttributeId")
.HasName("pk_rxn_attributes");
b.ToTable("rxn_attributes", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnConcept", b =>
{
b.Property<int>("RxnConceptId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_concept_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnConceptId"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("IsPref")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("is_pref");
b.Property<string>("Lat")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("lat");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Saui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("saui");
b.Property<string>("Scui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("scui");
b.Property<string>("Sdui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("sdui");
b.Property<string>("Srl")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("srl");
b.Property<string>("Str")
.IsRequired()
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("str");
b.Property<string>("Stt")
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("stt");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.Property<string>("Ts")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("ts");
b.Property<string>("Tty")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("tty");
b.HasKey("RxnConceptId")
.HasName("pk_rxn_concepts");
b.ToTable("rxn_concepts", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnRelation", b =>
{
b.Property<int>("RxnRelationId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_relation_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnRelationId"));
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Dir")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("dir");
b.Property<string>("Rel")
.HasMaxLength(4)
.HasColumnType("character varying(4)")
.HasColumnName("rel");
b.Property<string>("Rela")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("rela");
b.Property<string>("Rg")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rg");
b.Property<string>("Rui")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rui");
b.Property<string>("Rxaui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui1");
b.Property<string>("Rxaui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui2");
b.Property<string>("Rxcui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui2");
b.Property<string>("SType1")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type1");
b.Property<string>("SType2")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type2");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Sl")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("sl");
b.Property<string>("Srui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("srui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnRelationId")
.HasName("pk_rxn_relations");
b.ToTable("rxn_relations", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
/// <inheritdoc />
public partial class AddFactNDCTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "fact_ndc",
columns: table => new
{
fact_ndc_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
ndc = table.Column<string>(type: "character varying(11)", maxLength: 11, nullable: true),
brand_name_id = table.Column<int>(type: "integer", nullable: false),
dose_form_id = table.Column<int>(type: "integer", nullable: false),
semantic_drug_id = table.Column<int>(type: "integer", nullable: false),
branded_drug_id = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_fact_ndc", x => x.fact_ndc_id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "fact_ndc");
}
}
}
@@ -0,0 +1,533 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Strata.RxNorm.Biz.DbContexts;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
[DbContext(typeof(RxNormDbContext))]
[Migration("20250310181957_JAZZ_84127_Add_Dimension_View_Procs")]
partial class JAZZ_84127_Add_Dimension_View_Procs
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandName", b =>
{
b.Property<int>("BrandNameId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("brand_name_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandNameId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandNameId")
.HasName("pk_dim_brand_names");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_brand_names_rx_cui");
b.ToTable("dim_brand_names", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandedDrug", b =>
{
b.Property<int>("BrandedDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandedDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandedDrugId")
.HasName("pk_dim_branded_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_branded_drugs_rx_cui");
b.ToTable("dim_branded_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimDoseForm", b =>
{
b.Property<int>("DoseFormId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("dose_form_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("DoseFormId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("DoseFormId")
.HasName("pk_dim_dose_forms");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_dose_forms_rx_cui");
b.ToTable("dim_dose_forms", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimSemanticDrug", b =>
{
b.Property<int>("SemanticDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SemanticDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("SemanticDrugId")
.HasName("pk_dim_semantic_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_semantic_drugs_rx_cui");
b.ToTable("dim_semantic_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.FactNdc", b =>
{
b.Property<int>("FactNdcId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("fact_ndc_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("FactNdcId"));
b.Property<int>("BrandNameId")
.HasColumnType("integer")
.HasColumnName("brand_name_id");
b.Property<int>("BrandedDrugId")
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
b.Property<int>("DoseFormId")
.HasColumnType("integer")
.HasColumnName("dose_form_id");
b.Property<string>("Ndc")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("ndc");
b.Property<int>("SemanticDrugId")
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
b.HasKey("FactNdcId")
.HasName("pk_fact_ndc");
b.ToTable("fact_ndc", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.NdcRelationship", b =>
{
b.Property<string>("Rxcui1")
.HasColumnType("text")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasColumnType("text")
.HasColumnName("rxcui2");
b.Property<string>("Atn")
.HasColumnType("text")
.HasColumnName("atn");
b.Property<int>("Code")
.HasColumnType("integer")
.HasColumnName("code");
b.Property<string>("Description")
.HasColumnType("text")
.HasColumnName("description");
b.Property<string>("Ndc")
.IsRequired()
.HasColumnType("text")
.HasColumnName("ndc");
b.Property<string>("Tty")
.IsRequired()
.HasColumnType("text")
.HasColumnName("tty");
b.HasKey("Rxcui1", "Rxcui2")
.HasName("pk_ndc_relationships");
b.HasAlternateKey("Ndc", "Tty", "Code");
b.ToTable((string)null);
b.ToView("ndcrelationships", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnAttribute", b =>
{
b.Property<int>("RxnAttributeId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_attribute_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnAttributeId"));
b.Property<string>("Atn")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("atn");
b.Property<string>("Atui")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("atui");
b.Property<string>("Atv")
.HasMaxLength(4000)
.HasColumnType("character varying(4000)")
.HasColumnName("atv");
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Satui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("satui");
b.Property<string>("Stype")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("stype");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnAttributeId")
.HasName("pk_rxn_attributes");
b.ToTable("rxn_attributes", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnConcept", b =>
{
b.Property<int>("RxnConceptId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_concept_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnConceptId"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("IsPref")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("is_pref");
b.Property<string>("Lat")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("lat");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Saui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("saui");
b.Property<string>("Scui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("scui");
b.Property<string>("Sdui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("sdui");
b.Property<string>("Srl")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("srl");
b.Property<string>("Str")
.IsRequired()
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("str");
b.Property<string>("Stt")
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("stt");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.Property<string>("Ts")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("ts");
b.Property<string>("Tty")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("tty");
b.HasKey("RxnConceptId")
.HasName("pk_rxn_concepts");
b.ToTable("rxn_concepts", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnRelation", b =>
{
b.Property<int>("RxnRelationId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_relation_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnRelationId"));
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Dir")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("dir");
b.Property<string>("Rel")
.HasMaxLength(4)
.HasColumnType("character varying(4)")
.HasColumnName("rel");
b.Property<string>("Rela")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("rela");
b.Property<string>("Rg")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rg");
b.Property<string>("Rui")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rui");
b.Property<string>("Rxaui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui1");
b.Property<string>("Rxaui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui2");
b.Property<string>("Rxcui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui2");
b.Property<string>("SType1")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type1");
b.Property<string>("SType2")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type2");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Sl")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("sl");
b.Property<string>("Srui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("srui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnRelationId")
.HasName("pk_rxn_relations");
b.ToTable("rxn_relations", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,95 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
/// <inheritdoc />
public partial class JAZZ_84127_Add_Dimension_View_Procs : Migration
{
private static readonly List<Tuple<string, string>> _tables = new List<Tuple<string, string>>
{
new("dim_brand_names", "BN" ),
new("dim_dose_forms", "DF" ),
new("dim_branded_drugs", "SBD" ),
new("dim_semantic_drugs", "SCDC")
};
private const string rxnorms = nameof(rxnorms);
private static readonly string[] _relationsIndexColumns = ["rxcui1", "sab"];
private static readonly string[] _conceptsIndexColumns = ["rxcui", "sab", "tty"];
private static readonly string[] _attributesIndexColumns = ["rxcui", "atn", "sab"];
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "ix_rxn_relations_rxcui1_sab",
table: "rxn_relations",
columns: _relationsIndexColumns);
migrationBuilder.CreateIndex(
name: "ix_rxn_concepts_rxcui_sab",
table: "rxn_concepts",
columns: _conceptsIndexColumns);
migrationBuilder.CreateIndex(
name: "ix_rxn_attributes_rxcui_atn_sab",
table: "rxn_attributes",
columns: _attributesIndexColumns);
migrationBuilder.Sql($@"CREATE OR REPLACE VIEW public.{nameof(rxnorms)}
AS
select
c.rxcui, c.tty, c.code, c.str Description
from rxn_relations r
inner join rxn_attributes a on r.rxcui1 = a.rxcui
inner join rxn_concepts c on r.rxcui2 = c.rxcui and a.sab = c.sab
where a.atn = 'NDC' and a.sab = 'RXNORM'
and c.tty in ('BN','DF','SBD','SCDC')");
_tables.ForEach(table => CreateUpdateDimProc(migrationBuilder, table.Item1, table.Item2));
}
private static void CreateUpdateDimProc(MigrationBuilder migrationBuilder, string table, string tty)
{
migrationBuilder.Sql($@"CREATE OR REPLACE PROCEDURE public.update_{table}()
LANGUAGE SQL
AS $$
TRUNCATE TABLE public.{table} RESTART IDENTITY;
INSERT INTO public.{table} (rx_cui, code, description)
SELECT DISTINCT d.rxcui, d.code, d.description
FROM public.{nameof(rxnorms)} d
WHERE d.tty = '{tty}'
$$;");
}
private static void DropUpdateDimProc(MigrationBuilder migrationBuilder, string table)
{
migrationBuilder.Sql($@"DROP PROCEDURE IF EXISTS public.update_{table}");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "ix_rxn_relations_rxcui1_sab",
table: "rxn_relations");
migrationBuilder.DropIndex(
name: "ix_rxn_concepts_rxcui_sab",
table: "rxn_concepts");
migrationBuilder.DropIndex(
name: "ix_rxn_attributes_rxcui_atn_sab",
table: "rxn_attributes");
migrationBuilder.Sql($@"DROP VIEW IF EXISTS public.{nameof(rxnorms)}");
_tables.ForEach(table => DropUpdateDimProc(migrationBuilder, table.Item1));
}
}
}
@@ -0,0 +1,522 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Strata.RxNorm.Biz.DbContexts;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
[DbContext(typeof(RxNormDbContext))]
[Migration("20250317135618_JAZZ_84150_Pharmacy_rollups_populate_central_fact_table")]
partial class JAZZ_84150_Pharmacy_rollups_populate_central_fact_table
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandName", b =>
{
b.Property<int>("BrandNameId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("brand_name_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandNameId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandNameId")
.HasName("pk_dim_brand_names");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_brand_names_rx_cui");
b.ToTable("dim_brand_names", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandedDrug", b =>
{
b.Property<int>("BrandedDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandedDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandedDrugId")
.HasName("pk_dim_branded_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_branded_drugs_rx_cui");
b.ToTable("dim_branded_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimDoseForm", b =>
{
b.Property<int>("DoseFormId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("dose_form_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("DoseFormId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("DoseFormId")
.HasName("pk_dim_dose_forms");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_dose_forms_rx_cui");
b.ToTable("dim_dose_forms", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimSemanticDrug", b =>
{
b.Property<int>("SemanticDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SemanticDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("SemanticDrugId")
.HasName("pk_dim_semantic_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_semantic_drugs_rx_cui");
b.ToTable("dim_semantic_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.FactNdc", b =>
{
b.Property<int>("FactNdcId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("fact_ndc_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("FactNdcId"));
b.Property<int>("BrandNameId")
.HasColumnType("integer")
.HasColumnName("brand_name_id");
b.Property<int>("BrandedDrugId")
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
b.Property<int>("DoseFormId")
.HasColumnType("integer")
.HasColumnName("dose_form_id");
b.Property<string>("Ndc")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("ndc");
b.Property<int>("SemanticDrugId")
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
b.HasKey("FactNdcId")
.HasName("pk_fact_ndc");
b.ToTable("fact_ndc", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxNorm", b =>
{
b.Property<string>("Rxcui")
.HasColumnType("text")
.HasColumnName("rxcui");
b.Property<string>("Code")
.HasColumnType("text")
.HasColumnName("code");
b.Property<string>("Description")
.HasColumnType("text")
.HasColumnName("description");
b.HasKey("Rxcui", "Code")
.HasName("pk_rx_norms");
b.ToTable((string)null);
b.ToView("rxnorms", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnAttribute", b =>
{
b.Property<int>("RxnAttributeId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_attribute_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnAttributeId"));
b.Property<string>("Atn")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("atn");
b.Property<string>("Atui")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("atui");
b.Property<string>("Atv")
.HasMaxLength(4000)
.HasColumnType("character varying(4000)")
.HasColumnName("atv");
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Satui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("satui");
b.Property<string>("Stype")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("stype");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnAttributeId")
.HasName("pk_rxn_attributes");
b.HasIndex("Rxcui", "Atn", "Sab")
.HasDatabaseName("ix_rxn_attributes_rxcui_atn_sab");
b.ToTable("rxn_attributes", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnConcept", b =>
{
b.Property<int>("RxnConceptId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_concept_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnConceptId"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("IsPref")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("is_pref");
b.Property<string>("Lat")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("lat");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Saui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("saui");
b.Property<string>("Scui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("scui");
b.Property<string>("Sdui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("sdui");
b.Property<string>("Srl")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("srl");
b.Property<string>("Str")
.IsRequired()
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("str");
b.Property<string>("Stt")
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("stt");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.Property<string>("Ts")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("ts");
b.Property<string>("Tty")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("tty");
b.HasKey("RxnConceptId")
.HasName("pk_rxn_concepts");
b.HasIndex("Rxcui", "Sab", "Tty")
.HasDatabaseName("ix_rxn_concepts_rxcui_sab_tty");
b.ToTable("rxn_concepts", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnRelation", b =>
{
b.Property<int>("RxnRelationId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_relation_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnRelationId"));
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Dir")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("dir");
b.Property<string>("Rel")
.HasMaxLength(4)
.HasColumnType("character varying(4)")
.HasColumnName("rel");
b.Property<string>("Rela")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("rela");
b.Property<string>("Rg")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rg");
b.Property<string>("Rui")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rui");
b.Property<string>("Rxaui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui1");
b.Property<string>("Rxaui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui2");
b.Property<string>("Rxcui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui2");
b.Property<string>("SType1")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type1");
b.Property<string>("SType2")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type2");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Sl")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("sl");
b.Property<string>("Srui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("srui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnRelationId")
.HasName("pk_rxn_relations");
b.HasIndex("Rxcui1", "Sab")
.HasDatabaseName("ix_rxn_relations_rxcui1_sab");
b.ToTable("rxn_relations", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,144 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
/// <inheritdoc />
public partial class JAZZ_84150_Pharmacy_rollups_populate_central_fact_table : Migration
{
private static readonly List<Tuple<string, string>> _tables = new List<Tuple<string, string>>
{
new("dim_brand_names", "BN" ),
new("dim_dose_forms", "DF" ),
new("dim_branded_drugs", "SBD" ),
new("dim_semantic_drugs", "SCD")
};
private const string rxnorms = nameof(rxnorms);
private const string ndcrelationships = nameof(ndcrelationships);
private const string branded_semantic_drugs = nameof(branded_semantic_drugs);
private const string factndc = nameof(factndc);
private const string update_fact_ndc = nameof(update_fact_ndc);
private static string[] _views = [rxnorms, ndcrelationships, branded_semantic_drugs, factndc];
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql($@"CREATE OR REPLACE VIEW public.{nameof(rxnorms)}
AS
select
c.rxcui, c.tty, c.code, c.str Description
from rxn_relations r
inner join rxn_attributes a on r.rxcui1 = a.rxcui
inner join rxn_concepts c on r.rxcui2 = c.rxcui and a.sab = c.sab
where a.atn = 'NDC' and a.sab = 'RXNORM'
and c.tty in ('BN','DF','SBD','SCD')");
migrationBuilder.Sql($@"CREATE OR REPLACE VIEW public.{nameof(ndcrelationships)}
AS
select
a.rxcui Rxcui1, a.atn, a.atv Ndc
,c.rxcui Rxcui2, c.tty, c.code::int Code, c.str Description
from rxn_relations r
inner join rxn_attributes a on r.rxcui1 = a.rxcui
inner join rxn_concepts c on r.rxcui2 = c.rxcui
where a.atn in ('NDC','RXN_STRENGTH')
and c.sab = 'RXNORM'
and c.tty in ('BN','DF','SBD','SCD')");
migrationBuilder.Sql($@"DROP VIEW IF EXISTS public.{nameof(branded_semantic_drugs)}");
migrationBuilder.Sql($@"CREATE OR REPLACE VIEW public.{nameof(branded_semantic_drugs)}
AS
select
r.rxcui1, c1.tty sbd, c1.code sbd_code, c1.str sbd_description,
r.rxcui2, c.tty scd, c.code scd_code, c.str scd_description
from rxn_relations r
inner join rxn_concepts c1 on r.rxcui1 = c1.rxcui
inner join rxn_concepts c on r.rxcui2 = c.rxcui and c1.sab = c.sab
where c1.tty = 'SBD' and c1.sab = 'RXNORM'
and c.tty = 'SCD'");
migrationBuilder.Sql($@"CREATE OR REPLACE VIEW public.{nameof(factndc)}
AS
SELECT ndc,
max(brand_name_id) brand_name_id,
max(dose_form_id) dose_form_id,
max(semantic_drug_id) semantic_drug_id,
max(branded_drug_id) branded_drug_id
FROM (
select
a.atv NDC,
case when c.tty = 'BN' THEN COALESCE(b.brand_name_id, 0) ELSE 0 END brand_name_id,
case when c.tty = 'DF' THEN COALESCE(df.dose_form_id, 0) ELSE 0 END dose_form_id,
case when c.tty = 'SCD' THEN COALESCE(scdc.semantic_drug_id, 0) ELSE 0 END semantic_drug_id,
case when c.tty = 'SBD' THEN COALESCE(sbd.branded_drug_id, 0) ELSE 0 END branded_drug_id
from rxn_relations r
inner join rxn_attributes a on r.rxcui1 = a.rxcui
inner join rxn_concepts c on r.rxcui2 = c.rxcui
LEFT OUTER join dim_brand_names b on b.rx_cui = c.rxcui and c.tty = 'BN'
LEFT OUTER join dim_dose_forms df on df.rx_cui = c.rxcui and c.tty = 'DF'
LEFT OUTER join dim_semantic_drugs scdc on scdc.rx_cui = c.rxcui and c.tty = 'SCD'
LEFT OUTER join dim_branded_drugs sbd on sbd.rx_cui = c.rxcui and c.tty = 'SBD'
where a.atn = 'NDC' and a.sab = 'RXNORM'
and c.sab = 'RXNORM' and c.tty in ('BN', 'DF', 'SCD', 'SBD')
)
group by ndc");
migrationBuilder.Sql(@"CREATE OR REPLACE PROCEDURE public.update_fact_ndc()
LANGUAGE SQL
AS $$
TRUNCATE TABLE public.fact_ndc RESTART IDENTITY;
INSERT INTO public.fact_ndc (ndc, brand_name_id, dose_form_id, semantic_drug_id, branded_drug_id)
SELECT DISTINCT d.ndc, d.brand_name_id, d.dose_form_id, d.semantic_drug_id, d.branded_drug_id
FROM public.factndc d
$$;");
_tables.ForEach(table => CreateUpdateDimProc(migrationBuilder, table.Item1, table.Item2));
}
private static void CreateUpdateDimProc(MigrationBuilder migrationBuilder, string tableName, string tty)
{
migrationBuilder.Sql($@"CREATE OR REPLACE PROCEDURE public.update_{tableName}()
LANGUAGE SQL
AS $$
TRUNCATE TABLE public.{tableName} RESTART IDENTITY;
INSERT INTO public.{tableName} (rx_cui, code, description)
SELECT DISTINCT
C.RXCUI,
C.CODE::INT CODE,
C.STR DESCRIPTION
FROM
RXN_RELATIONS R
INNER JOIN RXN_ATTRIBUTES A ON R.RXCUI1 = A.RXCUI
AND R.SAB = A.SAB
INNER JOIN RXN_CONCEPTS C ON R.RXCUI2 = C.RXCUI
AND R.SAB = C.SAB
WHERE
R.SAB = 'RXNORM'
AND A.ATN = 'NDC'
AND C.TTY = '{tty}'
$$;");
}
private static void DropUpdateDimProc(MigrationBuilder migrationBuilder, string table)
{
migrationBuilder.Sql($@"DROP PROCEDURE IF EXISTS public.update_{table}");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
_tables.ForEach(table => DropUpdateDimProc(migrationBuilder, table.Item1));
foreach (var view in _views)
{
migrationBuilder.Sql($"DROP VIEW IF EXISTS public.{view}");
}
migrationBuilder.Sql($"DROP PROCEDURE IF EXISTS public.{nameof(update_fact_ndc)}");
}
}
}
@@ -0,0 +1,606 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Strata.RxNorm.Biz.DbContexts;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
[DbContext(typeof(RxNormDbContext))]
[Migration("20250319154449_JAZZ_78250_Update_RxNorm_NDC_Columns")]
partial class JAZZ_78250_Update_RxNorm_NDC_Columns
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.BrandedSemanticDrug", b =>
{
b.Property<string>("Rxcui1")
.HasColumnType("text")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasColumnType("text")
.HasColumnName("rxcui2");
b.Property<string>("Sbd")
.HasColumnType("text")
.HasColumnName("sbd");
b.Property<string>("Sbd_Code")
.HasColumnType("text")
.HasColumnName("sbd_code");
b.Property<string>("Sbd_Description")
.HasColumnType("text")
.HasColumnName("sbd_description");
b.Property<string>("Scdc")
.HasColumnType("text")
.HasColumnName("scdc");
b.Property<string>("Scdc_Code")
.HasColumnType("text")
.HasColumnName("scdc_code");
b.Property<string>("Scdc_Description")
.HasColumnType("text")
.HasColumnName("scdc_description");
b.HasKey("Rxcui1", "Rxcui2")
.HasName("pk_branded_semantic_drugs");
b.ToTable((string)null);
b.ToView("branded_semantic_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandName", b =>
{
b.Property<int>("BrandNameId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("brand_name_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandNameId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandNameId")
.HasName("pk_dim_brand_names");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_brand_names_rx_cui");
b.ToTable("dim_brand_names", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandedDrug", b =>
{
b.Property<int>("BrandedDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandedDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandedDrugId")
.HasName("pk_dim_branded_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_branded_drugs_rx_cui");
b.ToTable("dim_branded_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimDoseForm", b =>
{
b.Property<int>("DoseFormId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("dose_form_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("DoseFormId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("DoseFormId")
.HasName("pk_dim_dose_forms");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_dose_forms_rx_cui");
b.ToTable("dim_dose_forms", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimSemanticDrug", b =>
{
b.Property<int>("SemanticDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SemanticDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("SemanticDrugId")
.HasName("pk_dim_semantic_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_semantic_drugs_rx_cui");
b.ToTable("dim_semantic_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.FactNdc", b =>
{
b.Property<int>("FactNdcId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("fact_ndc_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("FactNdcId"));
b.Property<int>("BrandNameId")
.HasColumnType("integer")
.HasColumnName("brand_name_id");
b.Property<int>("BrandedDrugId")
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
b.Property<int>("DoseFormId")
.HasColumnType("integer")
.HasColumnName("dose_form_id");
b.Property<string>("Ndc")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("ndc");
b.Property<int>("SemanticDrugId")
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
b.HasKey("FactNdcId")
.HasName("pk_fact_ndc");
b.ToTable("fact_ndc", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.NdcRelationship", b =>
{
b.Property<string>("Rxcui1")
.HasColumnType("text")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasColumnType("text")
.HasColumnName("rxcui2");
b.Property<string>("Atn")
.HasColumnType("text")
.HasColumnName("atn");
b.Property<int>("Code")
.HasColumnType("integer")
.HasColumnName("code");
b.Property<string>("Description")
.HasColumnType("text")
.HasColumnName("description");
b.Property<string>("Ndc")
.IsRequired()
.HasColumnType("text")
.HasColumnName("ndc");
b.Property<string>("Tty")
.IsRequired()
.HasColumnType("text")
.HasColumnName("tty");
b.HasKey("Rxcui1", "Rxcui2")
.HasName("pk_ndc_relationships");
b.HasAlternateKey("Ndc", "Tty", "Code");
b.ToTable((string)null);
b.ToView("ndcrelationships", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxNorm", b =>
{
b.Property<string>("Rxcui")
.HasColumnType("text")
.HasColumnName("rxcui");
b.Property<string>("Code")
.HasColumnType("text")
.HasColumnName("code");
b.Property<string>("Description")
.HasColumnType("text")
.HasColumnName("description");
b.HasKey("Rxcui", "Code")
.HasName("pk_rx_norms");
b.ToTable((string)null);
b.ToView("rxnorms", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnAttribute", b =>
{
b.Property<int>("RxnAttributeId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_attribute_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnAttributeId"));
b.Property<string>("Atn")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("atn");
b.Property<string>("Atui")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("atui");
b.Property<string>("Atv")
.HasMaxLength(4000)
.HasColumnType("character varying(4000)")
.HasColumnName("atv");
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Satui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("satui");
b.Property<string>("Stype")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("stype");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnAttributeId")
.HasName("pk_rxn_attributes");
b.HasIndex("Rxcui", "Atn", "Sab")
.HasDatabaseName("ix_rxn_attributes_rxcui_atn_sab");
b.ToTable("rxn_attributes", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnConcept", b =>
{
b.Property<int>("RxnConceptId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_concept_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnConceptId"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("IsPref")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("is_pref");
b.Property<string>("Lat")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("lat");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Saui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("saui");
b.Property<string>("Scui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("scui");
b.Property<string>("Sdui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("sdui");
b.Property<string>("Srl")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("srl");
b.Property<string>("Str")
.IsRequired()
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("str");
b.Property<string>("Stt")
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("stt");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.Property<string>("Ts")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("ts");
b.Property<string>("Tty")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("tty");
b.HasKey("RxnConceptId")
.HasName("pk_rxn_concepts");
b.HasIndex("Rxcui", "Sab", "Tty")
.HasDatabaseName("ix_rxn_concepts_rxcui_sab_tty");
b.ToTable("rxn_concepts", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnRelation", b =>
{
b.Property<int>("RxnRelationId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_relation_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnRelationId"));
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Dir")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("dir");
b.Property<string>("Rel")
.HasMaxLength(4)
.HasColumnType("character varying(4)")
.HasColumnName("rel");
b.Property<string>("Rela")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("rela");
b.Property<string>("Rg")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rg");
b.Property<string>("Rui")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rui");
b.Property<string>("Rxaui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui1");
b.Property<string>("Rxaui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui2");
b.Property<string>("Rxcui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui2");
b.Property<string>("SType1")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type1");
b.Property<string>("SType2")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type2");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Sl")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("sl");
b.Property<string>("Srui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("srui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnRelationId")
.HasName("pk_rxn_relations");
b.HasIndex("Rxcui1", "Sab")
.HasDatabaseName("ix_rxn_relations_rxcui1_sab");
b.ToTable("rxn_relations", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,242 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
/// <inheritdoc />
/// <inheritdoc />
public partial class JAZZ_78250_Update_RxNorm_NDC_Columns : Migration
{
private const string rxnorms = nameof(rxnorms);
private const string ndcrelationships = nameof(ndcrelationships);
private const string branded_semantic_drugs = nameof(branded_semantic_drugs);
private const string factndc = nameof(factndc);
private static string[] _views = [ndcrelationships, branded_semantic_drugs, factndc];
private static List<Tuple<string, string, string>> _relationships =
[
new("dim_brand_names", "brand_name_id", "BN"),
new("dim_dose_forms", "dose_form_id", "DF"),
new("dim_branded_drugs", "branded_drug_id", "SBD"),
new("dim_semantic_drugs", "semantic_drug_id", "SCD"),
];
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "pk_fact_ndc",
table: "fact_ndc");
migrationBuilder.RenameTable(
name: "fact_ndc",
newName: "fact_ndcs");
migrationBuilder.AddPrimaryKey(
name: "pk_fact_ndcs",
table: "fact_ndcs",
column: "fact_ndc_id");
migrationBuilder.Sql($@"CREATE OR REPLACE VIEW public.{nameof(ndcrelationships)}
AS
select
a.rxcui Rxcui1, a.atn, a.atv Ndc
,c.rxcui Rxcui2, c.tty, c.code::int Code, c.str Description
from rxn_relations r
inner join rxn_attributes a on r.rxcui1 = a.rxcui and r.sab = a.sab
inner join rxn_concepts c on r.rxcui2 = c.rxcui and r.sab = c.sab
where r.sab = 'RXNORM'
and a.atn = 'NDC'
and c.tty in ('BN','DF','SBD','SCD')");
migrationBuilder.Sql($@"DROP VIEW IF EXISTS public.{nameof(branded_semantic_drugs)}");
migrationBuilder.Sql($@"CREATE OR REPLACE VIEW public.{nameof(branded_semantic_drugs)}
AS
select
r.rxcui1, c1.tty sbd, c1.code sbd_code, c1.str sbd_description,
r.rxcui2, c.tty scd, c.code scd_code, c.str scd_description
from rxn_relations r
inner join rxn_concepts c1 on r.rxcui1 = c1.rxcui
inner join rxn_concepts c on r.rxcui2 = c.rxcui and c1.sab = c.sab
where c1.tty = 'SBD' and c1.sab = 'RXNORM'
and c.tty = 'SCD'");
migrationBuilder.Sql($@"CREATE OR REPLACE VIEW public.{nameof(factndc)}
AS
SELECT ndc,
max(brand_name_id) brand_name_id,
max(dose_form_id) dose_form_id,
max(semantic_drug_id) semantic_drug_id,
max(branded_drug_id) branded_drug_id
FROM (
select
a.atv NDC,
case when c.tty = 'BN' THEN COALESCE(b.brand_name_id, 0) ELSE 0 END brand_name_id,
case when c.tty = 'DF' THEN COALESCE(df.dose_form_id, 0) ELSE 0 END dose_form_id,
case when c.tty = 'SCD' THEN COALESCE(scd.semantic_drug_id, 0) ELSE 0 END semantic_drug_id,
case when c.tty = 'SBD' THEN COALESCE(sbd.branded_drug_id, 0) ELSE 0 END branded_drug_id
from rxn_relations r
inner join rxn_attributes a on r.rxcui1 = a.rxcui
inner join rxn_concepts c on r.rxcui2 = c.rxcui
LEFT OUTER join dim_brand_names b on b.rx_cui = c.rxcui and c.tty = 'BN'
LEFT OUTER join dim_dose_forms df on df.rx_cui = c.rxcui and c.tty = 'DF'
LEFT OUTER join dim_semantic_drugs scd on scd.rx_cui = c.rxcui and c.tty = 'SCD'
LEFT OUTER join dim_branded_drugs sbd on sbd.rx_cui = c.rxcui and c.tty = 'SBD'
where a.atn = 'NDC' and a.sab = 'RXNORM'
and c.sab = 'RXNORM'
and c.tty in ('BN', 'DF', 'SCD', 'SBD')
)
group by ndc");
migrationBuilder.Sql($@"CREATE OR REPLACE PROCEDURE public.update_dim_semantic_drugs()
LANGUAGE SQL
AS $$
TRUNCATE TABLE public.dim_semantic_drugs RESTART IDENTITY;
INSERT INTO public.dim_semantic_drugs (rx_cui, code, description)
SELECT DISTINCT d.rxcui, d.code, d.description
FROM public.{nameof(rxnorms)} d
WHERE d.tty = 'SCD'
$$;");
migrationBuilder.Sql(@"DROP PROCEDURE IF EXISTS public.update_fact_ndc");
migrationBuilder.Sql(@"CREATE OR REPLACE PROCEDURE public.update_fact_ndcs()
LANGUAGE SQL
AS $$
TRUNCATE TABLE public.fact_ndcs RESTART IDENTITY;
INSERT INTO public.fact_ndcs (ndc, brand_name_id, dose_form_id, semantic_drug_id, branded_drug_id)
SELECT DISTINCT d.ndc, d.brand_name_id, d.dose_form_id, d.semantic_drug_id, d.branded_drug_id
FROM public.factndc d
$$;");
migrationBuilder.Sql(@"CREATE OR REPLACE VIEW public.fact_ndc_details
AS
SELECT
FN.NDC ndc_code,
COALESCE(BN.CODE, '0') brand_code,
COALESCE(BN.DESCRIPTION, 'Not Specified') brand_description,
COALESCE(DF.CODE, '0') dose_form_code,
COALESCE(DF.DESCRIPTION, 'Not Specified') dose_form_description,
COALESCE(SBD.CODE, '0') branded_clinical_drug_code,
COALESCE(SBD.DESCRIPTION, 'Not Specified') branded_clinical_drug_description,
COALESCE(SCD.CODE, '0') semantic_clinical_drug_code,
COALESCE(SCD.DESCRIPTION, 'Not Specified') semantic_clinical_drug_description
FROM
FACT_NDCS FN
LEFT OUTER JOIN DIM_BRAND_NAMES BN ON FN.BRAND_NAME_ID = BN.BRAND_NAME_ID
LEFT OUTER JOIN DIM_DOSE_FORMS DF ON FN.DOSE_FORM_ID = DF.DOSE_FORM_ID
LEFT OUTER JOIN DIM_BRANDED_DRUGS SBD ON FN.BRANDED_DRUG_ID = SBD.BRANDED_DRUG_ID
LEFT OUTER JOIN DIM_SEMANTIC_DRUGS SCD ON FN.SEMANTIC_DRUG_ID = SCD.SEMANTIC_DRUG_ID");
migrationBuilder.Sql("TRUNCATE TABLE public.fact_ndcs RESTART IDENTITY;");
_relationships.ForEach(r =>
{
migrationBuilder.Sql($"TRUNCATE TABLE public.{r.Item1} RESTART IDENTITY;");
migrationBuilder.CreateIndex(
name: $"ix_fact_ndcs_{r.Item2}",
table: "fact_ndcs",
column: r.Item2);
migrationBuilder.AddForeignKey(
name: $"fk_fact_ndcs_{r.Item1}_{r.Item2}",
table: "fact_ndcs",
column: r.Item2,
principalTable: r.Item1,
principalColumn: r.Item2,
onDelete: ReferentialAction.Cascade);
});
_relationships.ForEach(table => CreateUpdateDimProc(migrationBuilder, table.Item1, table.Item2, table.Item3));
}
private static void CreateUpdateDimProc(MigrationBuilder migrationBuilder, string tableName, string key, string tty)
{
migrationBuilder.Sql($@"CREATE OR REPLACE PROCEDURE public.update_{tableName}()
LANGUAGE SQL
AS $$
TRUNCATE TABLE public.{tableName} RESTART IDENTITY CASCADE;
INSERT INTO public.{tableName} ({key}, rx_cui, code, description)
VALUES (0, '', 0, 'Not Specified');
INSERT INTO public.{tableName} (rx_cui, code, description)
SELECT DISTINCT
C.RXCUI,
C.CODE::INT CODE,
C.STR DESCRIPTION
FROM
RXN_RELATIONS R
INNER JOIN RXN_ATTRIBUTES A ON R.RXCUI1 = A.RXCUI
AND R.SAB = A.SAB
INNER JOIN RXN_CONCEPTS C ON R.RXCUI2 = C.RXCUI
AND R.SAB = C.SAB
WHERE
R.SAB = 'RXNORM'
AND A.ATN = 'NDC'
AND C.TTY = '{tty}'
$$;");
}
private static void DropUpdateDimProc(MigrationBuilder migrationBuilder, string table)
{
migrationBuilder.Sql($@"DROP PROCEDURE IF EXISTS public.update_{table}");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
_relationships.ForEach(table => DropUpdateDimProc(migrationBuilder, table.Item1));
migrationBuilder.Sql("DROP PROCEDURE IF EXISTS public.update_fact_ndcs");
migrationBuilder.Sql("DROP VIEW IF EXISTS public.fact_ndc_details");
migrationBuilder.Sql(@"CREATE OR REPLACE PROCEDURE public.update_fact_ndc()
LANGUAGE SQL
AS $$
TRUNCATE TABLE public.fact_ndcs RESTART IDENTITY;
INSERT INTO public.fact_ndcs (ndc, brand_name_id, dose_form_id, semantic_drug_id, branded_drug_id)
SELECT DISTINCT d.ndc, d.brand_name_id, d.dose_form_id, d.semantic_drug_id, d.branded_drug_id
FROM public.factndc d
$$;");
foreach (var view in _views)
{
migrationBuilder.Sql($"DROP VIEW IF EXISTS public.{view}");
}
migrationBuilder.Sql(@"DROP PROCEDURE IF EXISTS public.update_dim_semantic_drugs");
_relationships.ForEach(r =>
{
migrationBuilder.DropForeignKey(
name: $"fk_fact_ndcs_{r.Item1}_{r.Item2}",
table: "fact_ndcs");
migrationBuilder.DropIndex(
name: $"ix_fact_ndcs_{r.Item2}",
table: "fact_ndcs");
});
migrationBuilder.DropPrimaryKey(
name: "pk_fact_ndcs",
table: "fact_ndcs");
migrationBuilder.RenameTable(
name: "fact_ndcs",
newName: "fact_ndc");
migrationBuilder.AddPrimaryKey(
name: "pk_fact_ndc",
table: "fact_ndc",
column: "fact_ndc_id");
}
}
}
@@ -0,0 +1,603 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Strata.RxNorm.Biz.DbContexts;
#nullable disable
namespace Strata.RxNorm.Biz.Migrations
{
[DbContext(typeof(RxNormDbContext))]
partial class RxNormDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.BrandedSemanticDrug", b =>
{
b.Property<string>("Rxcui1")
.HasColumnType("text")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasColumnType("text")
.HasColumnName("rxcui2");
b.Property<string>("Sbd")
.HasColumnType("text")
.HasColumnName("sbd");
b.Property<string>("Sbd_Code")
.HasColumnType("text")
.HasColumnName("sbd_code");
b.Property<string>("Sbd_Description")
.HasColumnType("text")
.HasColumnName("sbd_description");
b.Property<string>("Scdc")
.HasColumnType("text")
.HasColumnName("scdc");
b.Property<string>("Scdc_Code")
.HasColumnType("text")
.HasColumnName("scdc_code");
b.Property<string>("Scdc_Description")
.HasColumnType("text")
.HasColumnName("scdc_description");
b.HasKey("Rxcui1", "Rxcui2")
.HasName("pk_branded_semantic_drugs");
b.ToTable((string)null);
b.ToView("branded_semantic_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandName", b =>
{
b.Property<int>("BrandNameId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("brand_name_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandNameId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandNameId")
.HasName("pk_dim_brand_names");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_brand_names_rx_cui");
b.ToTable("dim_brand_names", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimBrandedDrug", b =>
{
b.Property<int>("BrandedDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandedDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("BrandedDrugId")
.HasName("pk_dim_branded_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_branded_drugs_rx_cui");
b.ToTable("dim_branded_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimDoseForm", b =>
{
b.Property<int>("DoseFormId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("dose_form_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("DoseFormId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("DoseFormId")
.HasName("pk_dim_dose_forms");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_dose_forms_rx_cui");
b.ToTable("dim_dose_forms", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.Dimensions.DimSemanticDrug", b =>
{
b.Property<int>("SemanticDrugId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SemanticDrugId"));
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Description")
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("description");
b.Property<string>("RxCui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rx_cui");
b.HasKey("SemanticDrugId")
.HasName("pk_dim_semantic_drugs");
b.HasAlternateKey("RxCui")
.HasName("ak_dim_semantic_drugs_rx_cui");
b.ToTable("dim_semantic_drugs", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.FactNdc", b =>
{
b.Property<int>("FactNdcId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("fact_ndc_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("FactNdcId"));
b.Property<int>("BrandNameId")
.HasColumnType("integer")
.HasColumnName("brand_name_id");
b.Property<int>("BrandedDrugId")
.HasColumnType("integer")
.HasColumnName("branded_drug_id");
b.Property<int>("DoseFormId")
.HasColumnType("integer")
.HasColumnName("dose_form_id");
b.Property<string>("Ndc")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("ndc");
b.Property<int>("SemanticDrugId")
.HasColumnType("integer")
.HasColumnName("semantic_drug_id");
b.HasKey("FactNdcId")
.HasName("pk_fact_ndc");
b.ToTable("fact_ndc", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.NdcRelationship", b =>
{
b.Property<string>("Rxcui1")
.HasColumnType("text")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasColumnType("text")
.HasColumnName("rxcui2");
b.Property<string>("Atn")
.HasColumnType("text")
.HasColumnName("atn");
b.Property<int>("Code")
.HasColumnType("integer")
.HasColumnName("code");
b.Property<string>("Description")
.HasColumnType("text")
.HasColumnName("description");
b.Property<string>("Ndc")
.IsRequired()
.HasColumnType("text")
.HasColumnName("ndc");
b.Property<string>("Tty")
.IsRequired()
.HasColumnType("text")
.HasColumnName("tty");
b.HasKey("Rxcui1", "Rxcui2")
.HasName("pk_ndc_relationships");
b.HasAlternateKey("Ndc", "Tty", "Code");
b.ToTable((string)null);
b.ToView("ndcrelationships", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxNorm", b =>
{
b.Property<string>("Rxcui")
.HasColumnType("text")
.HasColumnName("rxcui");
b.Property<string>("Code")
.HasColumnType("text")
.HasColumnName("code");
b.Property<string>("Description")
.HasColumnType("text")
.HasColumnName("description");
b.HasKey("Rxcui", "Code")
.HasName("pk_rx_norms");
b.ToTable((string)null);
b.ToView("rxnorms", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnAttribute", b =>
{
b.Property<int>("RxnAttributeId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_attribute_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnAttributeId"));
b.Property<string>("Atn")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("atn");
b.Property<string>("Atui")
.HasMaxLength(11)
.HasColumnType("character varying(11)")
.HasColumnName("atui");
b.Property<string>("Atv")
.HasMaxLength(4000)
.HasColumnType("character varying(4000)")
.HasColumnName("atv");
b.Property<string>("Code")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Satui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("satui");
b.Property<string>("Stype")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("stype");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnAttributeId")
.HasName("pk_rxn_attributes");
b.HasIndex("Rxcui", "Atn", "Sab")
.HasDatabaseName("ix_rxn_attributes_rxcui_atn_sab");
b.ToTable("rxn_attributes", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnConcept", b =>
{
b.Property<int>("RxnConceptId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_concept_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnConceptId"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("code");
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("IsPref")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("is_pref");
b.Property<string>("Lat")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("lat");
b.Property<string>("Lui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("lui");
b.Property<string>("Rxaui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui");
b.Property<string>("Rxcui")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Saui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("saui");
b.Property<string>("Scui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("scui");
b.Property<string>("Sdui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("sdui");
b.Property<string>("Srl")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("srl");
b.Property<string>("Str")
.IsRequired()
.HasMaxLength(3000)
.HasColumnType("character varying(3000)")
.HasColumnName("str");
b.Property<string>("Stt")
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("stt");
b.Property<string>("Sui")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("sui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.Property<string>("Ts")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("ts");
b.Property<string>("Tty")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("tty");
b.HasKey("RxnConceptId")
.HasName("pk_rxn_concepts");
b.HasIndex("Rxcui", "Sab", "Tty")
.HasDatabaseName("ix_rxn_concepts_rxcui_sab_tty");
b.ToTable("rxn_concepts", (string)null);
});
modelBuilder.Entity("Strata.RxNorm.Biz.RxNorm.RxnRelation", b =>
{
b.Property<int>("RxnRelationId")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("rxn_relation_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RxnRelationId"));
b.Property<string>("Cvf")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("cvf");
b.Property<string>("Dir")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("dir");
b.Property<string>("Rel")
.HasMaxLength(4)
.HasColumnType("character varying(4)")
.HasColumnName("rel");
b.Property<string>("Rela")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("rela");
b.Property<string>("Rg")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rg");
b.Property<string>("Rui")
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("rui");
b.Property<string>("Rxaui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui1");
b.Property<string>("Rxaui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxaui2");
b.Property<string>("Rxcui1")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui1");
b.Property<string>("Rxcui2")
.HasMaxLength(8)
.HasColumnType("character varying(8)")
.HasColumnName("rxcui2");
b.Property<string>("SType1")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type1");
b.Property<string>("SType2")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("s_type2");
b.Property<string>("Sab")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("sab");
b.Property<string>("Sl")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("sl");
b.Property<string>("Srui")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("srui");
b.Property<string>("Suppress")
.HasMaxLength(1)
.HasColumnType("character varying(1)")
.HasColumnName("suppress");
b.HasKey("RxnRelationId")
.HasName("pk_rxn_relations");
b.HasIndex("Rxcui1", "Sab")
.HasDatabaseName("ix_rxn_relations_rxcui1_sab");
b.ToTable("rxn_relations", (string)null);
});
#pragma warning restore 612, 618
}
}
}
+29
View File
@@ -0,0 +1,29 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Strata.RxNorm.Biz.Jazz
{
[Table("DimNdc", Schema = "dss")]
public class DimNdc
{
[Key]
public int NdcId { get; set; }
public string NdcCode { get; set; }
public string Description { get; set; }
public string SemanticClinicalDrugCode { get; set; }
public string SemanticClinicalDrugDescription { get; set; }
public string BrandedClinicalDrugCode { get; set; }
public string BrandedClinicalDrugDescription { get; set; }
public string BrandCode { get; set; }
public string BrandDescription { get; set; }
public string DoseFormCode { get; set; }
public string DoseFormDescription { get; set; }
[NotMapped]
public string NormalizedNdcCode { get; set; }
}
}
@@ -0,0 +1,21 @@
using Strata.ApiLib.Standard.Models;
using Strata.RxNorm.Biz.Pagination;
using Strata.RxNorm.Biz.RxNorm;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Jazz
{
public interface INdcDbService
{
Task ClearDimNdcData(CancellationToken cancellationToken);
Task<PagedApiResponse<DimNdc>> GetNdcsAsync(PagingOptions pagingOptions, IBaseFilterOptions filterOptions, CancellationToken cancellationToken);
Task UpdateJazzNdc(bool withNormalization, CancellationToken cancellationToken);
Task UpdateJazzNdcBrands(bool withNormalization, CancellationToken cancellationToken);
Task UpdateJazzNdcDoseForms(bool withNormalization, CancellationToken cancellationToken);
Task UpdateJazzNdcBrandedClinicalDrugs(bool withNormalization, CancellationToken cancellationToken);
Task UpdateJazzNdcSemanticClinicalDrugs(bool withNormalization, CancellationToken cancellationToken);
}
}
@@ -0,0 +1,23 @@
using Strata.RxNorm.Biz.RxNorm;
using System;
namespace Strata.RxNorm.Biz.Jazz
{
internal class JazzNdcUpdate
{
public string Tty { get; set; }
public Func<DimNdc, FactNdcDetail, bool> IsUpdateNdc { get; set; }
public Action<DimNdc, FactNdcDetail> UpdateData { get; set; }
public JazzNdcUpdate() { }
public JazzNdcUpdate(string tty,
Func<DimNdc, FactNdcDetail, bool> isUpdateNdc,
Action<DimNdc, FactNdcDetail> updateData)
{
Tty = tty;
IsUpdateNdc = isUpdateNdc;
UpdateData = updateData;
}
}
}
+269
View File
@@ -0,0 +1,269 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Strata.ApiLib.Standard.Models;
using Strata.RxNorm.Biz.DbContexts;
using Strata.RxNorm.Biz.Jazz;
using Strata.RxNorm.Biz.Pagination;
using Strata.RxNorm.Biz.RxNorm;
using Strata.RxNorm.Biz.Utilities;
using Strata.SqlTools.Configuration.Common.AsyncFactory;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Ndc
{
public class NdcDbService(
RxNormDbContext rxNormDbContext,
IAsyncDbContextFactory<JazzDbContext> dbContextFactory,
ILogger<NdcDbService> logger) : INdcDbService
{
private readonly RxNormDbContext _rxNormDbContext = rxNormDbContext;
protected readonly IAsyncDbContextFactory<JazzDbContext> _dbContextFactory = dbContextFactory;
private readonly ILogger<NdcDbService> _logger = logger;
public async Task<JazzDbContext> GetDbContext(CancellationToken cancellationToken)
{
return await _dbContextFactory.CreateDbContextAsync(cancellationToken);
}
public async Task<PagedApiResponse<DimNdc>> GetNdcsAsync(PagingOptions pagingOptions, IBaseFilterOptions filterOptions, CancellationToken cancellationToken)
{
var jazzDbContext = await GetDbContext(cancellationToken);
try
{
var query = jazzDbContext.DimNdcs.AsQueryable().AsNoTracking();
var filters = GetNdcFilters(filterOptions);
var result = await query.Where(filters).ToPagedAsync(pagingOptions, cancellationToken);
return result;
}
catch (Exception ex)
{
_logger.LogServiceError(ex, jazzDbContext);
throw;
}
}
private static List<Expression<Func<DimNdc, bool>>> GetNdcFilters(IBaseFilterOptions filterOptions)
{
return filterOptions.Search.CreateEntityFilters<DimNdc>(
m => m.NdcCode,
m => m.Description,
m => m.BrandCode,
m => m.BrandDescription,
m => m.DoseFormCode,
m => m.DoseFormDescription,
m => m.BrandedClinicalDrugCode,
m => m.BrandedClinicalDrugDescription,
m => m.SemanticClinicalDrugCode,
m => m.SemanticClinicalDrugDescription
);
}
internal Dictionary<string, JazzNdcUpdate> JazzNdcUpdates = new List<JazzNdcUpdate>{
new ("",
(ndc, ndcDetail) =>
ndc.BrandCode != ndcDetail.BrandCode
|| ndc.BrandDescription != ndcDetail.BrandDescription
|| ndc.BrandedClinicalDrugCode != ndcDetail.BrandedClinicalDrugCode
|| ndc.BrandedClinicalDrugDescription != ndcDetail.BrandedClinicalDrugDescription
|| ndc.SemanticClinicalDrugCode != ndcDetail.SemanticClinicalDrugCode
|| ndc.SemanticClinicalDrugDescription != ndcDetail.SemanticClinicalDrugDescription
|| ndc.DoseFormCode != ndcDetail.DoseFormCode
|| ndc.DoseFormDescription != ndcDetail.DoseFormDescription,
(ndc, ndcDetail) =>
{
ndc.BrandCode = ndcDetail.BrandCode;
ndc.BrandDescription = ndcDetail.BrandDescription;
ndc.BrandedClinicalDrugCode = ndcDetail.BrandedClinicalDrugCode;
ndc.BrandedClinicalDrugDescription = ndcDetail.BrandedClinicalDrugDescription;
ndc.SemanticClinicalDrugCode = ndcDetail.SemanticClinicalDrugCode;
ndc.SemanticClinicalDrugDescription = ndcDetail.SemanticClinicalDrugDescription;
ndc.DoseFormCode = ndcDetail.DoseFormCode;
ndc.DoseFormDescription = ndcDetail.DoseFormDescription;
}),
new ("DF",
(ndc, ndcDetail) => ndc.DoseFormCode != ndcDetail.DoseFormCode
|| ndc.DoseFormDescription != ndcDetail.DoseFormDescription,
(ndc, ndcDetail) =>
{
ndc.DoseFormCode = ndcDetail.DoseFormCode;
ndc.DoseFormDescription = ndcDetail.DoseFormDescription;
}),
new ("BN",
(ndc, ndcDetail) =>
ndc.BrandCode != ndcDetail.BrandCode
|| ndc.BrandDescription != ndcDetail.BrandDescription,
(ndc, ndcDetail) =>
{
ndc.BrandCode = ndcDetail.BrandCode;
ndc.BrandDescription = ndcDetail.BrandDescription;
}),
new ("SCD",
(ndc, ndcDetail) => ndc.SemanticClinicalDrugCode != ndcDetail.SemanticClinicalDrugCode
|| ndc.SemanticClinicalDrugDescription != ndcDetail.SemanticClinicalDrugDescription,
(ndc, ndcDetail) =>
{
ndc.SemanticClinicalDrugCode = ndcDetail.SemanticClinicalDrugCode;
ndc.SemanticClinicalDrugDescription = ndcDetail.SemanticClinicalDrugDescription;
}),
new("SBD",
(ndc, ndcDetail) => ndc.BrandedClinicalDrugCode != ndcDetail.BrandedClinicalDrugCode
|| ndc.BrandedClinicalDrugDescription != ndcDetail.BrandedClinicalDrugDescription,
(ndc, ndcDetail) =>
{
ndc.BrandedClinicalDrugCode = ndcDetail.BrandedClinicalDrugCode;
ndc.BrandedClinicalDrugDescription = ndcDetail.BrandedClinicalDrugDescription;
})
}.ToDictionary(k => k.Tty, v => v);
public async Task UpdateJazzNdc(bool withNormalization, CancellationToken cancellationToken)
{
var hasDetails = await _rxNormDbContext.FactNdcDetails.AnyAsync(cancellationToken);
if (!hasDetails) { return; }
var jazzDbContext = await GetDbContext(cancellationToken);
await jazzDbContext.DropNdcDetailTable(cancellationToken);
await jazzDbContext.CreateNdcDetailTable(cancellationToken);
await UpdateJazzNdc(JazzNdcUpdates.TryGetValue("", out var result) ? result : null, withNormalization, cancellationToken);
await jazzDbContext.UpdateDimNdcTable(cancellationToken);
}
public async Task UpdateJazzNdcBrands(bool withNormalization, CancellationToken cancellationToken)
{
var hasDetails = await _rxNormDbContext.FactNdcDetails.AnyAsync(cancellationToken);
if (!hasDetails) { return; }
await UpdateJazzNdc(JazzNdcUpdates.TryGetValue("BN", out var result) ? result : null, withNormalization, cancellationToken);
}
public async Task UpdateJazzNdcDoseForms(bool withNormalization, CancellationToken cancellationToken)
{
var hasDetails = await _rxNormDbContext.FactNdcDetails.AnyAsync(cancellationToken);
if (!hasDetails) { return; }
await UpdateJazzNdc(JazzNdcUpdates.TryGetValue("DF", out var result) ? result : null, withNormalization, cancellationToken);
}
public async Task UpdateJazzNdcSemanticClinicalDrugs(bool withNormalization, CancellationToken cancellationToken)
{
var hasDetails = await _rxNormDbContext.FactNdcDetails.AnyAsync(cancellationToken);
if (!hasDetails) { return; }
await UpdateJazzNdc(JazzNdcUpdates.TryGetValue("SCD", out var result) ? result : null, withNormalization, cancellationToken);
}
public async Task UpdateJazzNdcBrandedClinicalDrugs(bool withNormalization, CancellationToken cancellationToken)
{
var hasDetails = await _rxNormDbContext.FactNdcDetails.AnyAsync(cancellationToken);
if (!hasDetails) { return; }
await UpdateJazzNdc(JazzNdcUpdates.TryGetValue("SBD", out var result) ? result : null, withNormalization, cancellationToken);
}
public async Task ClearDimNdcData(CancellationToken cancellationToken)
{
var jazzDbContext = await GetDbContext(cancellationToken);
await jazzDbContext.ClearDimNdc(cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="jazzNdcUpdate"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task UpdateJazzNdc(JazzNdcUpdate jazzNdcUpdate, bool withNormalization, CancellationToken cancellationToken)
{
await UpdateJazzNdc([jazzNdcUpdate], withNormalization, cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="jazzNdcUpdates"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task UpdateJazzNdc(IEnumerable<JazzNdcUpdate> jazzNdcUpdates, bool withNormalization, CancellationToken cancellationToken)
{
var jazzDbContext = await GetDbContext(cancellationToken);
foreach (var jazzNdcUpdate in jazzNdcUpdates)
{
await UpdateJazzNdc(jazzDbContext, jazzNdcUpdate, withNormalization, cancellationToken);
}
}
/// <summary>
///
/// </summary>
/// <param name="jazzNdcUpdate"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task UpdateJazzNdc(JazzDbContext jazzDbContext, JazzNdcUpdate jazzNdcUpdate,
bool withNormalization,
CancellationToken cancellationToken)
{
var start = Stopwatch.StartNew();
var skip = 0;
var take = 10000;
var totalUpdates = 0;
var query = jazzDbContext.DimNdcs.AsQueryable();
while (true)
{
var batch = await query.Skip(skip).Take(take).ToListAsync(cancellationToken);
if (batch.Count == 0) { break; }
var batchNdcs = batch.Select(ndc => withNormalization ? ndc.NdcCode.NormalizeNdc() : ndc.NdcCode).Distinct().ToList();
var ndcDetails = await _rxNormDbContext.FactNdcDetails
.Where(r => batchNdcs.Contains(r.NdcCode)).ToListAsync(cancellationToken);
totalUpdates += await UpdateDetails(jazzDbContext, jazzNdcUpdate,
withNormalization,
batch, ndcDetails, cancellationToken);
skip += take;
}
var elapsed = start.Elapsed.TotalSeconds;
_logger.LogServiceInfo(jazzDbContext, nameof(UpdateJazzNdc),
new Dictionary<string, object> {
{ nameof(elapsed), elapsed },
{ nameof(totalUpdates), totalUpdates }
});
}
private async Task<int> UpdateDetails(JazzDbContext jazzDbContext, JazzNdcUpdate jazzNdcUpdate,
bool withNormalization,
List<DimNdc> batch, List<FactNdcDetail> ndcDetails,
CancellationToken cancellationToken)
{
if (ndcDetails.Count == 0) { return 0; }
var updates =
(from ndc in batch
join ndcDetail in ndcDetails on withNormalization ? ndc.NdcCode.NormalizeNdc() : ndc.NdcCode equals ndcDetail.NdcCode
where jazzNdcUpdate.IsUpdateNdc(ndc, ndcDetail)
select new { ndc, ndcDetail }).ToList();
if (updates.Count == 0) { return 0; }
if (string.IsNullOrEmpty(jazzNdcUpdate.Tty))
{
var ndcDetailInserts = updates.Select(update => new NdcDetail(update.ndc, update.ndcDetail));
await jazzDbContext.NdcDetails.AddRangeAsync(ndcDetailInserts, cancellationToken);
return await jazzDbContext.SaveChangesAsync(cancellationToken);
}
try
{
updates.ForEach(update => jazzNdcUpdate.UpdateData(update.ndc, update.ndcDetail));
return await jazzDbContext.SaveChangesAsync(cancellationToken);
}
catch (Exception ex)
{
_logger.LogServiceError(ex);
throw;
}
}
}
}
+41
View File
@@ -0,0 +1,41 @@
using Strata.RxNorm.Biz.RxNorm;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Strata.RxNorm.Biz.Jazz
{
[Table("ndc_detail", Schema = "tmp")]
public class NdcDetail
{
[Key]
public int NdcId { get; set; }
public string NdcCode { get; set; }
public string SemanticClinicalDrugCode { get; set; }
public string SemanticClinicalDrugDescription { get; set; }
public string BrandedClinicalDrugCode { get; set; }
public string BrandedClinicalDrugDescription { get; set; }
public string BrandCode { get; set; }
public string BrandDescription { get; set; }
public string DoseFormCode { get; set; }
public string DoseFormDescription { get; set; }
public NdcDetail() { }
public NdcDetail(DimNdc ndc, FactNdcDetail ndcDetail)
{
NdcId = ndc.NdcId;
NdcCode = ndc.NdcCode;
DoseFormCode = ndcDetail.DoseFormCode;
DoseFormDescription = ndcDetail.DoseFormDescription;
BrandCode = ndcDetail.BrandCode;
BrandDescription = ndcDetail.BrandDescription;
SemanticClinicalDrugCode = ndcDetail.SemanticClinicalDrugCode;
SemanticClinicalDrugDescription = ndcDetail.SemanticClinicalDrugDescription;
BrandedClinicalDrugCode = ndcDetail.BrandedClinicalDrugCode;
BrandedClinicalDrugDescription = ndcDetail.BrandedClinicalDrugDescription;
}
}
}
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Strata.RxNorm.Biz.Jazz
{
public static partial class NdcNormalizer
{
private static readonly Dictionary<string, Func<Match, string>> FormatTransformations = new()
{
// Format: 6-4-2
[@"^(\d{6})-(\d{4})-(\d{2})$"] = m => m.Groups[1].Value[1..] + m.Groups[2].Value + m.Groups[3].Value,
// Format: 6-4-1
[@"^(\d{6})-(\d{4})-(\d{1})$"] = m => m.Groups[1].Value[1..] + m.Groups[2].Value + "0" + m.Groups[3].Value,
// Format: 6-3-2
[@"^(\d{6})-(\d{3})-(\d{2})$"] = m => m.Groups[1].Value[1..] + "0" + m.Groups[2].Value + m.Groups[3].Value,
// Format: 6-3-1
[@"^(\d{6})-(\d{3})-(\d{1})$"] = m => m.Groups[1].Value[1..] + "0" + m.Groups[2].Value + "0" + m.Groups[3].Value,
// Format: 5-4-2
[@"^(\d{5})-(\d{4})-(\d{2})$"] = m => m.Groups[1].Value + m.Groups[2].Value + m.Groups[3].Value,
// Format: 5-4-1
[@"^(\d{5})-(\d{4})-(\d{1})$"] = m => m.Groups[1].Value + m.Groups[2].Value + "0" + m.Groups[3].Value,
// Format: 5-3-2
[@"^(\d{5})-(\d{3})-(\d{2})$"] = m => m.Groups[1].Value + "0" + m.Groups[2].Value + m.Groups[3].Value,
// Format: 4-4-2
[@"^(\d{4})-(\d{4})-(\d{2})$"] = m => "0" + m.Groups[1].Value + m.Groups[2].Value + m.Groups[3].Value
};
private static readonly char Asterisk = '*';
private static readonly char Dash = '-';
private static readonly char Zero = '0';
[GeneratedRegex(@"^\d{11}$")]
private static partial Regex ElevenDigitRegex();
private static bool IsValidNdc(string ndc) => ElevenDigitRegex().IsMatch(ndc);
/// <summary>
/// <see href="https://www.nlm.nih.gov/research/umls/rxnorm/docs/techdoc.html#s6_0">Normalizing NDC codes in RxNorm</see><br />
/// <seealso href="http://www.nlm.nih.gov/research/umls/rxnorm/NDC_Normalization_Code.rtf">Algorithm used for normalizing NDC Codes</seealso>
/// </summary>
/// <param name="ndc"></param>
/// <param name="source"></param>
/// <returns></returns>
public static string NormalizeNdc(this string ndc, string source = "VANDF")
{
if (string.IsNullOrWhiteSpace(ndc))
return string.Empty;
ndc = ndc.Trim().Replace(Asterisk, Zero);
// Try regex-based format matching
foreach (var kvp in FormatTransformations)
{
var match = Regex.Match(ndc, kvp.Key);
if (match.Success)
{
var result = kvp.Value(match);
return IsValidNdc(result) ? result : string.Empty;
}
}
// Handle already normalized 11-digit NDC
if (!ndc.Contains(Dash) && ndc.Length == 11 && IsValidNdc(ndc))
{
return ndc;
}
// Handle 12-digit padded NDC from VANDF
if (!ndc.Contains(Dash) && ndc.Length == 12 && ndc.StartsWith(Zero) && source == "VANDF")
{
var trimmed = ndc[1..];
return IsValidNdc(trimmed) ? trimmed : string.Empty;
}
return string.Empty;
}
}
}
@@ -0,0 +1,18 @@
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Notification
{
/// <summary>
/// These are the SignalR notifications sent to the react client
/// </summary>
public interface INotificationHub
{
Task SendUploadFileStart(string toastKey, string filename);
Task SendUploadFileError(string toastKey, string filename);
Task SendUploadFileFinish(string toastKey, string filename);
Task SendDownloadComplete(string toastKey);
Task SendDownloadCurrentZipStart(string toastKey);
Task SendCurrentMonthFileMissing(string toastKey);
Task SendUpdateFactNdc(string toastKey);
}
}
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Notification
{
public class NotificationHub : Hub<INotificationHub>
{
public async Task Register(string topic)
{
await Groups.AddToGroupAsync(Context.ConnectionId, topic);
}
}
}
@@ -0,0 +1,50 @@
using Strata.RxNorm.Biz.Pagination;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Strata.RxNorm.Biz.Pagination
{
public static class IQueryableExtensions
{
public static IQueryable<T> Where<T>(this IQueryable<T> query, IEnumerable<Expression<Func<T, bool>>> filters)
{
foreach (var e in filters)
{
query = query.Where(e);
}
return query;
}
public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, SortOptions sortOptions)
{
if (sortOptions == null || string.IsNullOrWhiteSpace(sortOptions.SortField))
{
return query;
}
if (sortOptions.SortOrder == SortOrder.Asc)
{
return query.OrderBy(sortOptions.SortField);
}
else
{
return query.OrderByDescending(sortOptions.SortField);
}
}
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName) =>
query.OrderBy(ToLambda<T>(propertyName));
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName) =>
query.OrderByDescending(ToLambda<T>(propertyName));
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName);
var propAsObject = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(propAsObject, parameter);
}
}
}
@@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore;
using Strata.ApiLib.Standard.Models;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Pagination
{
public static class PagingExtensions
{
public static async Task<PagedApiResponse<T>> ToPagedAsync<T>(this IQueryable<T> query, PagingOptions pagingOptions, CancellationToken cancellationToken)
{
var count = await query.CountAsync(cancellationToken);
query = query.OrderBy(pagingOptions);
query = query
.Skip(pagingOptions.First)
.Take(pagingOptions.Rows);
#if DEBUG
var sql = query.ToQueryString();
#endif
var items = await query.ToListAsync(cancellationToken);
var pagingInfo = new PagingInfo()
{
PageNumber = (pagingOptions.Rows == 0 ? 0 : pagingOptions.First / pagingOptions.Rows) + 1,
ResultsPerPage = pagingOptions.Rows,
TotalNumberOfResults = count
};
return new PagedApiResponse<T>() { Data = items, PagingInfo = pagingInfo };
}
}
}
@@ -0,0 +1,20 @@
namespace Strata.RxNorm.Biz.Pagination
{
public class PagingOptions : SortOptions
{
public int First { get; set; }
public int Rows { get; set; }
public PagingOptions() : base()
{
First = 0;
Rows = 25;
}
public PagingOptions(string sortField, SortOrder sortOrder) : base(sortField, sortOrder)
{
First = 0;
Rows = 25;
}
}
}
@@ -0,0 +1,20 @@
namespace Strata.RxNorm.Biz.Pagination
{
public class SortOptions
{
public string SortField { get; set; }
public SortOrder SortOrder { get; set; }
public SortOptions()
{
SortField = "";
SortOrder = SortOrder.Asc;
}
public SortOptions(string sortField, SortOrder sortOrder) : this()
{
SortField = sortField;
SortOrder = sortOrder;
}
}
}
@@ -0,0 +1,8 @@
namespace Strata.RxNorm.Biz.Pagination
{
public enum SortOrder
{
Asc = 1,
Desc = -1
}
}
@@ -0,0 +1,12 @@
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Pharmacy
{
public interface IPharmacyUpdateService
{
string RunPharmacyUpdateJob(bool withClearFirst, CancellationToken cancellationToken);
Task<string> RunPharmacyUpdateClientJob(bool withClearFirst, CancellationToken cancellationToken);
string RunResetNdcPharmacyAttributesJob(CancellationToken cancellationToken);
}
}
@@ -0,0 +1,89 @@
using Hangfire;
using Microsoft.Extensions.Logging;
using Strata.CoreLib.Claims;
using Strata.CoreLib.Claims.Extensions;
using Strata.Id.Client;
using Strata.RxNorm.Biz.Jobs;
using Strata.RxNorm.Biz.Utilities;
using System;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.Pharmacy
{
public class PharmacyUpdateService : IPharmacyUpdateService
{
private readonly IBackgroundJobClient _backgroundJobClient;
private readonly ClaimsPrincipal _user;
private readonly IIdServiceClient _idServiceClient;
private readonly ILogger<PharmacyUpdateService> _logger;
public PharmacyUpdateService(IBackgroundJobClient backgroundJobClient,
IClaimsPrincipalAccessor claimsPrincipalAccessor,
IIdServiceClient idServiceClient,
ILogger<PharmacyUpdateService> logger)
{
_backgroundJobClient = backgroundJobClient;
_user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
_idServiceClient = idServiceClient;
_logger = logger;
}
public string RunPharmacyUpdateJob(bool withClearFirst, CancellationToken cancellationToken)
{
var jobId = string.Empty;
try
{
jobId = _backgroundJobClient.Enqueue<PharmacyUpdateJob>(
job => job.UpdateDimNdc(withClearFirst, cancellationToken));
}
catch (Exception ex)
{
_logger.LogServiceError(ex);
}
return jobId;
}
public async Task<string> RunPharmacyUpdateClientJob(bool withClearFirst, CancellationToken cancellationToken)
{
var jobId = string.Empty;
try
{
var strataId = _user.GetStrataId();
var strataDto = await _idServiceClient.GetStrataId(strataId, cancellationToken);
if (strataDto.IsInMaintenanceMode)
{
_backgroundJobClient.Schedule<PharmacyUpdateJob>(job
=> job.RefreshClientInMaintenanceMode(strataDto.StrataId, withClearFirst, 1, cancellationToken), TimeSpan.FromMinutes(30));
}
else
{
jobId = _backgroundJobClient.Enqueue<PharmacyUpdateClientJob>(
job => job.RefreshClientDetails(strataDto.DatabaseGUID, strataDto.StrataId, strataDto.DatabaseName, withClearFirst, cancellationToken));
}
}
catch (Exception ex)
{
_logger.LogServiceError(ex);
}
return jobId;
}
public string RunResetNdcPharmacyAttributesJob(CancellationToken cancellationToken)
{
var jobId = string.Empty;
try
{
jobId = _backgroundJobClient.Enqueue<ResetNdcPharmacyAttributesJob>(
job => job.ResetNdcPharmacyAttributes(cancellationToken));
}
catch (Exception ex)
{
_logger.LogError(ex, nameof(RunResetNdcPharmacyAttributesJob));
throw;
}
return jobId;
}
}
}
@@ -0,0 +1,14 @@
namespace Strata.RxNorm.Biz.RxNorm
{
public class BrandedSemanticDrug
{
public string Rxcui1 { get; set; }
public string Sbd { get; set; }
public string Sbd_Code { get; set; }
public string Sbd_Description { get; set; }
public string Rxcui2 { get; set; }
public string Scd { get; set; }
public string Scd_Code { get; set; }
public string Scd_Description { get; set; }
}
}
@@ -0,0 +1,15 @@
using System;
namespace Strata.RxNorm.Biz.RxNorm
{
public partial class RxnConcept
{
public class ConvertLineException : Exception
{
public ConvertLineException(string message) : base(message)
{
}
}
}
}
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Strata.RxNorm.Biz.RxNorm.Dimensions
{
/// <summary>
/// <seealso cref="RxnConcept"/> TTY == 'BN'
/// </summary>
public class DimBrandName : RxNormConcept
{
[Key]
public int BrandNameId { get; set; }
public DimBrandName(RxnConcept rxnConcept)
: base(rxnConcept) { }
public DimBrandName() : base() { }
public ICollection<FactNdc> FactNdcs { get; set; }
}
}
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Strata.RxNorm.Biz.RxNorm.Dimensions
{
/// <summary>
/// <seealso cref="RxnConcept"/> TTY == 'SBD'
/// </summary>
public class DimBrandedDrug : RxNormConcept
{
[Key]
public int BrandedDrugId { get; set; }
public DimBrandedDrug(RxnConcept rxnConcept)
: base(rxnConcept) { }
public DimBrandedDrug() : base() { }
public ICollection<FactNdc> FactNdcs { get; set; }
}
}
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Strata.RxNorm.Biz.RxNorm.Dimensions
{
/// <summary>
/// <seealso cref="RxnConcept"/> TTY == 'DF'
/// </summary>
public class DimDoseForm : RxNormConcept
{
[Key]
public int DoseFormId { get; set; }
public DimDoseForm(RxnConcept rxnConcept)
: base(rxnConcept) { }
public DimDoseForm() : base() { }
public ICollection<FactNdc> FactNdcs { get; set; }
}
}
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Strata.RxNorm.Biz.RxNorm.Dimensions
{
/// <summary>
/// <seealso cref="RxnConcept"/> TTY == 'SCD'
/// </summary>
public class DimSemanticDrug : RxNormConcept
{
[Key]
public int SemanticDrugId { get; set; }
public DimSemanticDrug(RxnConcept rxnConcept)
: base(rxnConcept) { }
public DimSemanticDrug() : base() { }
public ICollection<FactNdc> FactNdcs { get; set; }
}
}
@@ -0,0 +1,9 @@
namespace Strata.RxNorm.Biz.RxNorm.Dimensions
{
public interface IRxNormConcept
{
string Code { get; set; }
string Description { get; set; }
string RxCui { get; set; }
}
}
@@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
namespace Strata.RxNorm.Biz.RxNorm.Dimensions
{
public class RxNormConcept : IRxNormConcept
{
[StringLength(8)]
public string RxCui { get; set; }
[StringLength(50)]
public string Code { get; set; }
[StringLength(3000)]
public string Description { get; set; }
public RxNormConcept(RxnConcept rxnConcept)
{
Code = rxnConcept.Code;
Description = rxnConcept.Str;
}
public RxNormConcept()
{
}
}
}
+43
View File
@@ -0,0 +1,43 @@
using Strata.RxNorm.Biz.RxNorm.Dimensions;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Strata.RxNorm.Biz.RxNorm
{
/// <summary>
/// <seealso cref="RxnAttribute"/> ATN == "NDC"
/// </summary>
public class FactNdc
{
[Key]
public int FactNdcId { get; set; }
[StringLength(11)]
public string Ndc { get; set; }
public int BrandNameId { get; set; } = 0;
public int DoseFormId { get; set; } = 0;
public int SemanticDrugId { get; set; } = 0;
public int BrandedDrugId { get; set; } = 0;
public DimBrandName BrandName { get; set; }
public DimDoseForm DoseForm { get; set; }
public DimSemanticDrug SemanticDrug { get; set; }
public DimBrandedDrug BrandedDrug { get; set; }
[NotMapped]
internal string BrandCode => BrandName == null ? "0" : BrandName.Code;
[NotMapped]
internal string BrandDescription => BrandName == null ? "Not Specified" : BrandName.Description;
[NotMapped]
internal string DoseFormCode => DoseForm == null ? "0" : DoseForm.Code;
[NotMapped]
internal string DoseFormDescription => DoseForm == null ? "Not Specified" : DoseForm.Description;
[NotMapped]
internal string SemanticDrugCode => SemanticDrug == null ? "0" : SemanticDrug.Code;
[NotMapped]
internal string SemanticDrugDescription => SemanticDrug == null ? "Not Specified" : SemanticDrug.Description;
[NotMapped]
internal string BrandedDrugCode => BrandedDrug == null ? "0" : BrandedDrug.Code;
[NotMapped]
internal string BrandedDrugDescription => BrandedDrug == null ? "Not Specified" : BrandedDrug.Description;
}
}
@@ -0,0 +1,15 @@
namespace Strata.RxNorm.Biz.RxNorm
{
public class FactNdcDetail
{
public string NdcCode { get; set; }
public string BrandCode { get; set; }
public string BrandDescription { get; set; }
public string DoseFormCode { get; set; }
public string DoseFormDescription { get; set; }
public string BrandedClinicalDrugCode { get; set; }
public string BrandedClinicalDrugDescription { get; set; }
public string SemanticClinicalDrugCode { get; set; }
public string SemanticClinicalDrugDescription { get; set; }
}
}
@@ -0,0 +1,34 @@
namespace Strata.RxNorm.Biz.RxNorm
{
public interface IBaseFilterOptions
{
string Search { get; set; }
}
public class BaseFilterOptions : IBaseFilterOptions
{
public string Search { get; set; }
}
public class FilterOptions : BaseFilterOptions
{
#nullable enable
public Ttys[]? Ttys { get; set; }
public Atns[]? Atns { get; set; }
#nullable disable
}
public enum Ttys
{
Bn = 1,
Df,
Sbd,
Scd
}
public enum Atns
{
Ndc = 1
}
}
@@ -0,0 +1,31 @@
using Strata.ApiLib.Standard.Models;
using Strata.RxNorm.Biz.Pagination;
using Strata.RxNorm.Biz.RxNorm.Dimensions;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz.RxNorm
{
public interface IRxNormService
{
Task<PagedApiResponse<NdcRelationship>> GetNdcRelationshipAsync(PagingOptions pagingOptions, FilterOptions filterOptions, CancellationToken cancellationToken);
Task<PagedApiResponse<RxNorm>> GetRxNormAsync(PagingOptions pagingOptions, BaseFilterOptions filterOptions, CancellationToken cancellationToken);
Task<PagedApiResponse<BrandedSemanticDrug>> GetBrandedSemanticDrugsAsync(PagingOptions pagingOptions, BaseFilterOptions filterOptions, CancellationToken cancellationToken);
Task<PagedApiResponse<IRxNormConcept>> GetDimensionDataAsync(ITty Tty, PagingOptions pagingOptions, BaseFilterOptions filterOptions, CancellationToken cancellationToken);
Task<FactNdcDetail> GetNdcAsync(string ndc, CancellationToken cancellationToken);
Task<PagedApiResponse<FactNdcDetail>> GetNdcDetailsAsync(PagingOptions pagingOptions, BaseFilterOptions filterOptions, CancellationToken cancellationToken);
Task SeedData<TEntity>(Stream stream, string fileName, CancellationToken cancellationToken) where TEntity : class;
Task UpdateAllDimensions(CancellationToken cancellationToken);
Task UpdateBrandNameDimension(CancellationToken cancellationToken);
Task UpdateDoseFormDimension(CancellationToken cancellationToken);
Task UpdateBrandedDrugDimension(CancellationToken cancellationToken);
Task UpdateSemanticDrugDimension(CancellationToken cancellationToken);
Task UpdateFactNdcs(CancellationToken cancellationToken);
Task<Tuple<DateTime, DateTime>> GetMinAndMaxReleaseDate(CancellationToken cancellationToken);
Task DownloadRxNormData(DateTime? now, bool isCurrent, CancellationToken cancellationToken);
Task<bool> DownloadLAndProcessZipFile(DateTime now, bool isCurrent, CancellationToken cancellationToken);
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace Strata.RxNorm.Biz.RxNorm
{
public enum ITty
{
Bn,
Df,
Sbd,
Scd
}
}
@@ -0,0 +1,13 @@
namespace Strata.RxNorm.Biz.RxNorm
{
public class NdcRelationship
{
public string Rxcui1 { get; set; }
public string Rxcui2 { get; set; }
public string Atn { get; set; }
public string Ndc { get; set; }
public string Tty { get; set; }
public int Code { get; set; }
public string Description { get; set; }
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Strata.RxNorm.Biz.RxNorm
{
public class RxNorm
{
public string Rxcui { get; set; }
public string Code { get; set; }
public string Description { get; set; }
}
}
@@ -0,0 +1,488 @@
using EFCore.BulkExtensions;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Strata.ApiLib.Standard.Models;
using Strata.CoreLib.Claims;
using Strata.RxNorm.Biz.DbContexts;
using Strata.RxNorm.Biz.Notification;
using Strata.RxNorm.Biz.Pagination;
using Strata.RxNorm.Biz.Pharmacy;
using Strata.RxNorm.Biz.RxNorm.Dimensions;
using Strata.RxNorm.Biz.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Net.Http.Json;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using static Strata.RxNorm.Biz.Utilities.SearchFilterExpressions;
namespace Strata.RxNorm.Biz.RxNorm
{
public class RxNormService(RxNormDbContext dbContext,
IPharmacyUpdateService pharmacyUpdateService,
ILogger<RxNormService> logger,
IHubContext<NotificationHub, INotificationHub> notificationHubContext,
IClaimsPrincipalAccessor claimsPrincipalAccessor
) : IRxNormService
{
private readonly ClaimsPrincipal _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
private const string RxNormPrescribeMonthlyContentUrl = "https://uts-ws.nlm.nih.gov/releases";
private const string RxNormPrescribeMonthlyContentQuery = "releaseType=rxnorm-prescribable-content-monthly-release";
public async Task<PagedApiResponse<NdcRelationship>> GetNdcRelationshipAsync(PagingOptions pagingOptions, FilterOptions filterOptions, CancellationToken cancellationToken)
{
try
{
var query = dbContext.NdcRelationships.AsQueryable().AsNoTracking();
var filters = GetNdcRelationshipFilters(filterOptions);
var result = await query.Where(filters).ToPagedAsync(pagingOptions, cancellationToken);
return result;
}
catch (Exception ex)
{
logger.LogError(ex, nameof(GetNdcRelationshipAsync));
return new PagedApiResponse<NdcRelationship>();
}
}
public async Task<PagedApiResponse<RxNorm>> GetRxNormAsync(PagingOptions pagingOptions, BaseFilterOptions filterOptions, CancellationToken cancellationToken)
{
try
{
var query = dbContext.RxNorms.AsQueryable().AsNoTracking();
var filters = GetRxNormFilters(filterOptions);
var result = await query.Where(filters).ToPagedAsync(pagingOptions, cancellationToken);
return result;
}
catch (Exception ex)
{
logger.LogError(ex, nameof(GetRxNormAsync));
return new PagedApiResponse<RxNorm>();
}
}
public async Task<PagedApiResponse<IRxNormConcept>> GetDimensionDataAsync(ITty Tty, PagingOptions pagingOptions, BaseFilterOptions filterOptions, CancellationToken cancellationToken)
{
try
{
var filters = GetDimensionDataFilters(filterOptions);
PagedApiResponse<IRxNormConcept> result = default;
switch (Tty)
{
case ITty.Bn:
var queryBn = dbContext.DimBrandNames.AsQueryable().AsNoTracking();
result = await queryBn.Where(filters).ToPagedAsync(pagingOptions, cancellationToken);
break;
case ITty.Df:
var queryDf = dbContext.DimDoseForms.AsQueryable().AsNoTracking();
result = await queryDf.Where(filters).ToPagedAsync(pagingOptions, cancellationToken);
break;
case ITty.Sbd:
var querySbd = dbContext.DimBrandedDrugs.AsQueryable().AsNoTracking();
result = await querySbd.Where(filters).ToPagedAsync(pagingOptions, cancellationToken);
break;
case ITty.Scd:
var queryScd = dbContext.DimSemanticDrugs.AsQueryable().AsNoTracking();
result = await queryScd.Where(filters).ToPagedAsync(pagingOptions, cancellationToken);
break;
default:
throw new NotImplementedException();
}
return result;
}
catch (Exception ex)
{
logger.LogError(ex, nameof(GetBrandedSemanticDrugsAsync));
return new PagedApiResponse<IRxNormConcept>();
}
}
public async Task<PagedApiResponse<BrandedSemanticDrug>> GetBrandedSemanticDrugsAsync(PagingOptions pagingOptions, BaseFilterOptions filterOptions, CancellationToken cancellationToken)
{
try
{
var query = dbContext.Branded_Semantic_Drugs.AsQueryable().AsNoTracking();
var filters = GetBrandedSemanticDrugFilters(filterOptions);
var result = await query.Where(filters).ToPagedAsync(pagingOptions, cancellationToken);
return result;
}
catch (Exception ex)
{
logger.LogError(ex, nameof(GetBrandedSemanticDrugsAsync));
return new PagedApiResponse<BrandedSemanticDrug>();
}
}
public async Task<FactNdcDetail> GetNdcAsync(string ndc, CancellationToken cancellationToken)
{
try
{
var query = GetFactNdcDetailsQuery();
var filters = GetFactNdcDetailFilters(ndc);
return await query.Where(filters).FirstOrDefaultAsync(cancellationToken);
}
catch (Exception ex)
{
logger.LogServiceError(ex);
return new FactNdcDetail();
}
}
public async Task<PagedApiResponse<FactNdcDetail>> GetNdcDetailsAsync(PagingOptions pagingOptions, BaseFilterOptions filterOptions, CancellationToken cancellationToken)
{
try
{
var filters = GetNdcDetailFilters(filterOptions);
var query = GetFactNdcDetailsQuery().Where(filters);
return await query.ToPagedAsync(pagingOptions, cancellationToken);
}
catch (Exception ex)
{
logger.LogServiceError(ex);
return new PagedApiResponse<FactNdcDetail>
{
PagingInfo = new PagingInfo
{
TotalNumberOfResults = 0,
PageNumber = 0,
ResultsPerPage = 0,
TotalPages = 0
},
Data = []
};
}
}
private IQueryable<FactNdcDetail> GetFactNdcDetailsQuery()
=> dbContext.FactNdcDetails.AsQueryable<FactNdcDetail>().AsNoTracking();
#region Filters Helpers
private static List<Expression<Func<BrandedSemanticDrug, bool>>> GetBrandedSemanticDrugFilters(BaseFilterOptions filterOptions)
=> filterOptions.Search
.CreateEntityFilters<BrandedSemanticDrug>(
m => m.Sbd_Code,
m => m.Scd_Code,
m => m.Rxcui1,
m => m.Rxcui2,
m => m.Sbd_Description,
m => m.Scd_Description);
private static List<Expression<Func<NdcRelationship, bool>>> GetNdcRelationshipFilters(FilterOptions filterOptions)
=> filterOptions.Search.CreateEntityFilters<NdcRelationship>(
enumFilters: new[]
{
new EnumFilter<NdcRelationship>(filterOptions.Ttys, m => m.Tty),
new EnumFilter<NdcRelationship>(filterOptions.Atns, m => m.Atn)
},
propertySelectors: new Expression<Func<NdcRelationship, string>>[]
{
m => m.Atn,
m => m.Tty,
m => m.Ndc,
m => m.Description
}
);
private static List<Expression<Func<RxNorm, bool>>> GetRxNormFilters(BaseFilterOptions filterOptions)
=> filterOptions.Search
.CreateEntityFilters<RxNorm>(
m => m.Rxcui,
m => m.Code,
m => m.Description);
private static List<Expression<Func<IRxNormConcept, bool>>> GetDimensionDataFilters(BaseFilterOptions filterOptions)
=> filterOptions.Search
.CreateEntityFilters<IRxNormConcept>(
m => m.Code,
m => m.RxCui,
m => m.Description);
private static List<Expression<Func<FactNdcDetail, bool>>> GetFactNdcDetailFilters(string ndc)
=> ndc.CreateEntityFilters<FactNdcDetail>(m => m.NdcCode);
private static List<Expression<Func<FactNdcDetail, bool>>> GetNdcDetailFilters(BaseFilterOptions filterOptions)
=> filterOptions.Search
.CreateEntityFilters<FactNdcDetail>(
m => m.NdcCode,
m => m.BrandDescription,
m => m.DoseFormDescription,
m => m.BrandedClinicalDrugDescription,
m => m.SemanticClinicalDrugDescription);
private static List<Expression<Func<T, bool>>> GetFilters<T>() where T : class
{
var filters = new List<Expression<Func<T, bool>>>();
return filters;
}
#endregion
public async Task UpdateAllDimensions(CancellationToken cancellationToken)
{
try
{
await dbContext.UpdateAllDimensions(cancellationToken);
}
catch (Exception ex)
{
logger.LogServiceError(ex);
throw;
}
}
public async Task UpdateBrandNameDimension(CancellationToken cancellationToken)
{
try
{
await dbContext.UpdateDimBrandNames(cancellationToken);
}
catch (Exception ex)
{
logger.LogServiceError(ex);
throw;
}
}
public async Task UpdateDoseFormDimension(CancellationToken cancellationToken)
{
try
{
await dbContext.UpdateDimDoseForms(cancellationToken);
}
catch (Exception ex)
{
logger.LogServiceError(ex);
throw;
}
}
public async Task UpdateBrandedDrugDimension(CancellationToken cancellationToken)
{
try
{
await dbContext.UpdateDimBrandedDrugs(cancellationToken);
}
catch (Exception ex)
{
logger.LogServiceError(ex);
throw;
}
}
public async Task UpdateSemanticDrugDimension(CancellationToken cancellationToken)
{
try
{
await dbContext.UpdateDimSemanticDrugs(cancellationToken);
}
catch (Exception ex)
{
logger.LogServiceError(ex);
throw;
}
}
public async Task UpdateFactNdcs(CancellationToken cancellationToken)
{
try
{
await dbContext.UpdateFactNdcs(cancellationToken);
}
catch (Exception ex)
{
logger.LogServiceError(ex);
throw;
}
}
public async Task SeedData<TEntity>(Stream stream, string fileName, CancellationToken cancellationToken) where TEntity : class
{
var toastKey = _user.Identity?.Name + fileName;
var hub = notificationHubContext.Clients.Group(toastKey);
try
{
await hub.SendUploadFileStart(toastKey, fileName);
await SeedTable<TEntity>(stream, cancellationToken);
await hub.SendUploadFileFinish(toastKey, fileName);
}
catch (Exception ex)
{
logger.LogServiceError(ex);
await hub.SendUploadFileError(toastKey, fileName);
throw;
}
}
/// <summary>
/// Truncates and repopulates table from input file. Uses postgresql COPY command to bulk insert data into the database.
/// https://www.postgresql.org/docs/current/sql-copy.html
/// </summary>
public async Task SeedTable<TEntity>(Stream stream, CancellationToken cancellationToken) where TEntity : class
{
var name = typeof(TEntity).Name;
if (_user?.Identity != null)
{
var toastKey = _user.Identity?.Name + "rxnorm";
var hub = notificationHubContext.Clients.Group(toastKey);
await hub.SendUploadFileStart(toastKey, name);
}
await dbContext.TruncateAsync<TEntity>(cancellationToken: cancellationToken);
await using var importer = await dbContext.BeginTextImportAsync<TEntity>(cancellationToken: cancellationToken);
using var reader = new StreamReader(stream, bufferSize: 1024 * 64);
string line;
do
{
line = await reader.ReadLineAsync(cancellationToken);
if (line?.Length > 0)
{
// Trimming the last character because it seems all file types have a trailing '|' character that does not map to a column.
// Also, note we are not using the async version of this method because we are calling this MANY times and don't really need to release
// the thread. There is some overhead to the async method and that overhead is evident when calling this method many times.
// ReSharper disable once MethodHasAsyncOverload
importer.WriteLine(line[..^1]);
}
} while (line != null);
}
/// <summary>
/// Function used for manual process of getting the data.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task DownloadRxNormData(DateTime? now, bool isCurrent, CancellationToken cancellationToken = default)
{
now ??= DateTime.Now;
var toastKey = _user.Identity?.Name + "rxnorm";
var hub = notificationHubContext.Clients.Group(toastKey);
try
{
await hub.SendDownloadCurrentZipStart(toastKey);
bool result = await DownloadLAndProcessZipFile(now.Value, isCurrent, cancellationToken);
if (!result)
await hub.SendCurrentMonthFileMissing(toastKey);
else await hub.SendDownloadComplete(toastKey);
}
catch (Exception ex)
{
logger.LogError(ex, nameof(SeedData));
await hub.SendUploadFileError(toastKey, "DownloadFail");
throw;
}
}
/// <summary>
/// Get the min and max release date from the available releases.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns>
/// A tuple containing the minimum and maximum release dates.
/// </returns>
public async Task<Tuple<DateTime, DateTime>> GetMinAndMaxReleaseDate(CancellationToken cancellationToken)
{
try
{
var uriBuilder = new UriBuilder(RxNormPrescribeMonthlyContentUrl);
uriBuilder.Query += RxNormPrescribeMonthlyContentQuery;
using var client = new HttpClient();
var messages = await client.GetFromJsonAsync<List<RxNormPrescribeMonthlyContent>>(uriBuilder.Uri, cancellationToken);
var minDate = messages.Min(m => m.Released);
var maxDate = messages.Max(m => m.Released);
return new Tuple<DateTime, DateTime>(minDate, maxDate);
}
catch (Exception ex)
{
logger.LogServiceError(ex);
return new Tuple<DateTime, DateTime>(DateTime.Now, DateTime.Now);
}
}
/// <summary>
/// Downloads the current month zipfile and populates the Postgres tables with the data.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<bool> DownloadLAndProcessZipFile(DateTime now, bool isCurrent, CancellationToken cancellationToken)
{
var uriBuilder = new UriBuilder(RxNormPrescribeMonthlyContentUrl);
uriBuilder.Query += RxNormPrescribeMonthlyContentQuery;
using var client = new HttpClient();
var messages = await client.GetFromJsonAsync<List<RxNormPrescribeMonthlyContent>>(uriBuilder.Uri, cancellationToken);
var fileToDownload = isCurrent
? messages.SingleOrDefault(m => m.Current && m.Released.Month == now.Month && m.Released.Year == now.Year)
: messages.SingleOrDefault(m => m.Released.Month == now.Month && m.Released.Year == now.Year);
if (fileToDownload == null) { return false; }
string downloadUrl = fileToDownload.DownloadUrl;
var stream = await client.GetStreamAsync(downloadUrl, cancellationToken);
var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream, cancellationToken);
using (var zip = new ZipArchive(memoryStream, ZipArchiveMode.Read))
{
var rrfFiles = zip.Entries.Where(entry => entry.FullName.StartsWith("rrf/"));
// Make certain that there are all 3 files
if (rrfFiles.Count() != 3
|| !rrfFiles.Any(entry => entry.FullName == "rrf/RXNCONSO.RRF")
|| !rrfFiles.Any(entry => entry.FullName == "rrf/RXNREL.RRF")
|| !rrfFiles.Any(entry => entry.FullName == "rrf/RXNSAT.RRF")) { return false; }
foreach (var entry in rrfFiles)
{
switch (entry.FullName)
{
case "rrf/RXNCONSO.RRF":
var msConcept = entry.Open();
await SeedTable<RxnConcept>(msConcept, cancellationToken);
break;
case "rrf/RXNREL.RRF":
var msRelative = entry.Open();
await SeedTable<RxnRelation>(msRelative, cancellationToken);
break;
case "rrf/RXNSAT.RRF":
var msSat = entry.Open();
await SeedTable<RxnAttribute>(msSat, cancellationToken);
break;
}
}
}
if (_user?.Identity != null)
{
var toastKey = _user.Identity?.Name + "rxnorm";
var hub = notificationHubContext.Clients.Group(toastKey);
await hub.SendUpdateFactNdc(toastKey);
}
await dbContext.UpdateFactNdcs(cancellationToken);
if (!isCurrent) { return true; }
pharmacyUpdateService.RunPharmacyUpdateJob(false, cancellationToken);
return true;
}
}
internal class RxNormPrescribeMonthlyContent
{
public string FileName { get; set; }
public string ReleaseVersion { get; set; }
public string ReleaseDate { get; set; }
public DateTime Released => DateTime.Parse(ReleaseDate);
public string DownloadUrl { get; set; }
public string ReleaseType { get; set; }
public string Product { get; set; }
public bool Current { get; set; }
}
}
@@ -0,0 +1,50 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace Strata.RxNorm.Biz.RxNorm
{
public class RxnAttribute
{
public int RxnAttributeId { get; set; }
[StringLength(8)]
public string Rxcui { get; set; }
[StringLength(8)]
public string Lui { get; set; }
[StringLength(8)]
public string Sui { get; set; }
[StringLength(8)]
public string Rxaui { get; set; }
[StringLength(50)]
public string Stype { get; set; }
[StringLength(50)]
public string Code { get; set; }
[StringLength(11)]
public string Atui { get; set; }
[StringLength(50)]
public string Satui { get; set; }
[StringLength(1000), NotNull]
public string Atn { get; set; }
[StringLength(20), NotNull]
public string Sab { get; set; }
[StringLength(4000)]
public string Atv { get; set; }
[StringLength(1)]
public string Suppress { get; set; }
[StringLength(50)]
public string Cvf { get; set; }
}
}
@@ -0,0 +1,65 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace Strata.RxNorm.Biz.RxNorm
{
public partial class RxnConcept
{
public int RxnConceptId { get; set; }
[StringLength(8), NotNull]
public string Rxcui { get; set; }
[StringLength(3), NotNull, DefaultValue("ENG")]
public string Lat { get; set; }
[StringLength(1)]
public string Ts { get; set; }
[StringLength(8)]
public string Lui { get; set; }
[StringLength(3)]
public string Stt { get; set; }
[StringLength(8)]
public string Sui { get; set; }
[StringLength(1)]
public string IsPref { get; set; }
[StringLength(8), NotNull]
public string Rxaui { get; set; }
[StringLength(50)]
public string Saui { get; set; }
[StringLength(50)]
public string Scui { get; set; }
[StringLength(50)]
public string Sdui { get; set; }
[StringLength(20), NotNull]
public string Sab { get; set; }
[StringLength(20), NotNull]
public string Tty { get; set; }
[StringLength(50), NotNull]
public string Code { get; set; }
[StringLength(3000), NotNull]
public string Str { get; set; }
[StringLength(10)]
public string Srl { get; set; }
[StringLength(1)]
public string Suppress { get; set; }
[StringLength(50)]
public string Cvf { get; set; }
}
}
@@ -0,0 +1,58 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace Strata.RxNorm.Biz.RxNorm
{
public class RxnRelation
{
public int RxnRelationId { get; set; }
[StringLength(8)]
public string Rxcui1 { get; set; }
[StringLength(8)]
public string Rxaui1 { get; set; }
[StringLength(50)]
public string SType1 { get; set; }
[StringLength(4)]
public string Rel { get; set; }
[StringLength(8)]
public string Rxcui2 { get; set; }
[StringLength(8)]
public string Rxaui2 { get; set; }
[StringLength(50)]
public string SType2 { get; set; }
[StringLength(100)]
public string Rela { get; set; }
[StringLength(10)]
public string Rui { get; set; }
[StringLength(50)]
public string Srui { get; set; }
[StringLength(20), NotNull]
public string Sab { get; set; }
[StringLength(1000)]
public string Sl { get; set; }
[StringLength(1)]
public string Dir { get; set; }
[StringLength(10)]
public string Rg { get; set; }
[StringLength(1)]
public string Suppress { get; set; }
[StringLength(50)]
public string Cvf { get; set; }
}
}
@@ -0,0 +1,77 @@
using Hangfire;
using Hangfire.Throttling;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Strata.RxNorm.Biz.Jobs;
using Strata.RxNorm.Biz.Utilities;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Biz
{
public class RxNormBootstrapperService(
ILogger<RxNormBootstrapperService> logger,
IRecurringJobManager recurringJobManager,
IThrottlingManager throttlingManager) : BackgroundService
{
private readonly ILogger<RxNormBootstrapperService> _logger = logger;
private readonly IRecurringJobManager _recurringJobManager = recurringJobManager;
private readonly IThrottlingManager _throttlingManager = throttlingManager;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var tzi = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
var cronOnDemand = "0 0 31 2 0";
var cronMonthly = Cron.Monthly(15); // Run process on the 15th of the month.
try
{
_throttlingManager.AddOrUpdateSemaphore(nameof(PharmacyUpdateJob.UpdateDimNdc), new SemaphoreOptions(maxCount: 1));
_throttlingManager.AddOrUpdateSemaphore(nameof(PharmacyUpdateJob.RefreshClientInMaintenanceMode), new SemaphoreOptions(maxCount: 10));
_throttlingManager.AddOrUpdateSemaphore(nameof(PharmacyUpdateClientJob.RefreshClientDetails), new SemaphoreOptions(maxCount: 10));
_throttlingManager.AddOrUpdateSemaphore(nameof(RefreshClientDbDimNdc.RefreshDimNdcProperties), new SemaphoreOptions(maxCount: 10));
_throttlingManager.AddOrUpdateSemaphore(nameof(RefreshClientDbDimNdc.ClearClientDimNdc), new SemaphoreOptions(maxCount: 1));
_throttlingManager.AddOrUpdateSemaphore(nameof(ResetNdcPharmacyAttributesJob.ClearClientInMaintenanceMode), new SemaphoreOptions(maxCount: 10));
_throttlingManager.AddOrUpdateSemaphore(nameof(ResetNdcPharmacyAttributesJob.ResetNdcPharmacyAttributes), new SemaphoreOptions(maxCount: 10));
}
catch (Exception ex)
{
_logger.LogServiceError(ex);
}
try
{
// This job will run at midnight on the 15th of the month
var RxNormUpdateJobId = nameof(RxNormUpdateJob.UpdateRxNormJob);
#pragma warning disable CS0618 // Type or member is obsolete
_recurringJobManager.AddOrUpdate<RxNormUpdateJob>(RxNormUpdateJobId,
job => job.UpdateRxNormJob(null, 5, CancellationToken.None), cronMonthly, tzi,
Configurations.Constants.HangfireQueueDefault);
#pragma warning restore CS0618 // Type or member is obsolete
// On Demand Jobs
var pharmacyUpdateJobId = nameof(PharmacyUpdateJob.UpdateDimNdc);
#pragma warning disable CS0618 // Type or member is obsolete
_recurringJobManager.AddOrUpdate<PharmacyUpdateJob>(pharmacyUpdateJobId,
job => job.UpdateDimNdc(true, CancellationToken.None), cronOnDemand, tzi,
Configurations.Constants.HangfireQueueJazz);
#pragma warning restore CS0618 // Type or member is obsolete
var resetNdcPharmacyAttributesJob = nameof(ResetNdcPharmacyAttributesJob.ResetNdcPharmacyAttributes);
#pragma warning disable CS0618 // Type or member is obsolete
_recurringJobManager.AddOrUpdate<ResetNdcPharmacyAttributesJob>(resetNdcPharmacyAttributesJob,
job => job.ResetNdcPharmacyAttributes(CancellationToken.None), cronOnDemand, tzi,
Configurations.Constants.HangfireQueueJazz);
#pragma warning restore CS0618 // Type or member is obsolete
}
catch (Exception ex)
{
_logger.LogServiceError(ex);
}
await Task.CompletedTask;
}
}
}
@@ -0,0 +1,71 @@
using Hangfire;
using Hangfire.Throttling;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Strata.Hangfire.Configuration;
using Strata.Id.Client;
using Strata.RxNorm.Biz.ClientLicense;
using Strata.RxNorm.Biz.Configurations;
using Strata.RxNorm.Biz.DbContexts;
using Strata.RxNorm.Biz.Jazz;
using Strata.RxNorm.Biz.Jobs;
using Strata.RxNorm.Biz.Ndc;
using Strata.RxNorm.Biz.Pharmacy;
using Strata.RxNorm.Biz.RxNorm;
using Strata.RxNorm.Shared;
using Strata.SqlTools.Configuration.Postgres;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
public static class RxNormServiceCollectionExtensions
{
const int commandTimeoutInSeconds = 300;
public static IServiceCollection AddRxNormServices(this IServiceCollection services, IConfiguration configuration)
{
var connectionString = ConnectionStringBuilder.Build(configuration, "rxnorm");
services.AddPostgres<RxNormDbContext>((options, builder) => { options.ConnectionString = connectionString; });
//hangfire
services.AddTransient<IBatchJobClient, BatchJobClient>();
services.AddTransient<IThrottlingManager, ThrottlingManager>();
services.ConfigureHangfireOptionsFromAws(options =>
{
options.Schema = Constants.ApplicationName;
});
services.AddIdServiceClient();
services.AddLicensesServiceClient();
services.AddCachedSmcServiceClient();
services.AddHttpContextAccessor();
services.AddClaimsPrincipalAccessor();
services.ConfigureHangfireOptionsFromAws(options =>
{
options.Schema = "rxnorm";
});
services.AddScoped<IRxNormService, RxNormService>();
services.AddScoped<INdcDbService, NdcDbService>();
services.AddScoped<IPharmacyUpdateService, PharmacyUpdateService>();
services.AddScoped<IJazzJobs, JazzJobs>();
services.AddScoped<IClientLicenseService, ClientLicenseService>();
services.AddScoped<IRxNormUpdateJob, RxNormUpdateJob>();
services.AddOptions<DatabaseRegexSection>().BindConfiguration("Databases").ValidateDataAnnotations();
#region Jazz
services.AddAsyncDbContextFactory<JazzDbContext>(options =>
{
options
.WithConnectionString((provider, token) => provider.GetConnectionStringFromSmc(token))
.WithDbContextOptions((connectionString, provider, builder) =>
builder.UseSqlServer(connectionString,
options => { options.CommandTimeout(commandTimeoutInSeconds); }));
});
#endregion
return services;
}
}
}
@@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Jobs\JazzClearDimNdcJob.cs" />
<Compile Remove="Jobs\PharmacyUpdateBrandedClinicalDrugsJob.cs" />
<Compile Remove="Jobs\PharmacyUpdateBrandsJob.cs" />
<Compile Remove="Jobs\PharmacyUpdateClientNdcs.cs" />
<Compile Remove="Jobs\PharmacyUpdateDoseFormsJob.cs" />
<Compile Remove="Jobs\PharmacyUpdateSemanticClinicalDrugsJob.cs" />
<Compile Remove="RxNorm\StringExtensions.cs" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="EFCore.BulkExtensions.MIT.PostgreSql" Version="8.20.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Strata.Hangfire" Version="5.3.0" />
<PackageReference Include="Strata.Id.Client" Version="4.1.4" />
<PackageReference Include="Strata.Licenses.Client" Version="1.2.0" />
<PackageReference Include="Strata.SignalR" Version="2.0.0" />
<PackageReference Include="Strata.SqlTools.Configuration.Postgres" Version="6.0.3" />
<PackageReference Include="Strata.SqlTools.Configuration.SqlServer" Version="6.0.3" />
<PackageReference Include="Strata.SMC.Client" Version="3.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Strata.RxNorm.Shared\Strata.RxNorm.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Strata.RxNorm.Biz.Test.Unit</_Parameter1>
<!-- The name of the project that you want the Internal class to be visible To it -->
</AssemblyAttribute>
</ItemGroup>
</Project>
@@ -0,0 +1,110 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Logging;
using Strata.Id.Client.DTO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
namespace Strata.RxNorm.Biz.Utilities
{
public static class LogUtilsExtensions
{
internal static readonly string Microservice = "strata-rxnorm-";
internal static readonly string[] strings = ["password"];
public static Dictionary<string, TValue> ToDictionary<TValue>(this object obj)
{
try
{
var json = JsonSerializer.Serialize(obj);
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
MaxDepth = 0
};
return JsonSerializer.Deserialize<Dictionary<string, TValue>>(json, options);
}
catch
{
return new Dictionary<string, TValue> { { "object", (TValue)obj } };
}
}
public static Dictionary<string, object> DatabaseDictionary(this StrataIdFullDto database, string service)
{
var dictionary = new Dictionary<string, object> { { "service", $"{Microservice}{service}" } };
if (database != null)
{
dictionary.Add("db", new Dictionary<string, object> {
{ "guid", database.DatabaseGUID },
{ "instance", database.PhysicalName },
{ "server", database.ServerName },
{ "strata_id", database.StrataId },
{ "org_pin", database.OrgPin },
{ "name", database.DatabaseName }
});
}
return dictionary;
}
/// <summary>
/// <para>
/// This turns the dbContext connction string into a dictionary of elements.
/// </para>
/// <para>
/// It splits the string by semi-colons, then splits each of those by the equal symbol.
/// </para>
/// <para>
/// The left side is the dictionary key, the right side is the dictionary value, ignoring the Password (if present).
/// </para>
/// </summary>
/// <param name="dbContext"></param>
/// <returns></returns>
public static Dictionary<string, object> DbContextDictionary(this DatabaseFacade dbContext, string service)
{
var dictionary = dbContext?.GetConnectionString().Split(';')
.Select(x => x.Split('='))
.Select(y => new { key = y[0].Replace(" ", ""), value = y[1] })
.Where(x => !strings.Contains(x.key.ToLowerInvariant()))
.ToDictionary(x => x.key, x => x.value) ?? [];
return new Dictionary<string, object> { { "service", $"{Microservice}{service}" }, { "dbContext", dictionary } };
}
public static void LogServiceError<T>(this ILogger<T> logger, Exception exception, DbContext dbContext = null, string message = null, Dictionary<string, object> info = null)
{
if (logger == null) { return; }
if (exception.InnerException != null)
{
logger.LogServiceError(exception.InnerException, dbContext, message, info);
return;
}
var dictionary = dbContext?.Database.DbContextDictionary(typeof(T).Name)
?? new Dictionary<string, object> { { "service", $"{Microservice}{typeof(T).Name}" } };
if (info != null)
{
dictionary.Add("information", info);
}
var msg = !string.IsNullOrEmpty(message)
? message
: exception.InnerException?.Message ?? exception.Message;
using (logger.BeginScope(dictionary))
logger.LogError(exception.InnerException ?? exception, "{msg}", msg);
}
public static void LogServiceInfo<T>(this ILogger<T> logger, DbContext dbContext = null, string message = null, Dictionary<string, object> info = null)
{
if (logger == null) { return; }
var dictionary = dbContext?.Database.DbContextDictionary(typeof(T).Name)
?? new Dictionary<string, object> { { "service", $"{Microservice}{typeof(T).Name}" } };
if (info != null)
{
dictionary.Add("information", info);
}
var msg = !string.IsNullOrEmpty(message) ? message : typeof(T).Name;
using (logger.BeginScope(dictionary))
logger.LogInformation("{msg}", msg);
}
}
}
@@ -0,0 +1,357 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Strata.RxNorm.Biz.Utilities
{
/// <summary>
/// Provides extension methods and utilities for creating and combining expression-based search filters
/// for Entity Framework queries. Supports text-based searching, enum filtering, and negation operations.
/// </summary>
public static class SearchFilterExpressions
{
/// <summary>
/// Adds search filters to an existing list by splitting search terms and combining them with OR/AND logic.
/// Supports negation with '!' prefix which uses AND logic instead of OR.
/// </summary>
/// <typeparam name="T">The entity type to filter</typeparam>
/// <param name="filters">The existing list of filter expressions to add to</param>
/// <param name="search">Comma-separated search terms. Terms starting with '!' are treated as negated</param>
/// <param name="expressionBuilder">Function that takes a search term and returns a filter expression</param>
/// <returns>The updated list of filter expressions</returns>
public static List<Expression<Func<T, bool>>> AddSearchFilters<T>(this List<Expression<Func<T, bool>>> filters,
string search,
Func<string, Expression<Func<T, bool>>> expressionBuilder)
where T : class
{
if (string.IsNullOrEmpty(search)) return filters;
var searches = search.ToLower()
.Split([','], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
// Create a single OR expression that combines all search terms
Expression<Func<T, bool>>? combinedSearchExpression = null;
foreach (var searchTerm in searches)
{
var searchExpression = expressionBuilder(searchTerm);
if (combinedSearchExpression == null)
{ combinedSearchExpression = searchExpression; }
else
{
var parameter = Expression.Parameter(typeof(T), "m");
var left = Expression.Invoke(combinedSearchExpression, parameter);
var right = Expression.Invoke(searchExpression, parameter);
var expression = searchTerm.StartsWith('!')
? Expression.AndAlso(left, right) // Combine expressions with AND logic
: Expression.OrElse(left, right); // Combine expressions with OR logic
combinedSearchExpression = Expression.Lambda<Func<T, bool>>(expression, parameter);
}
}
if (combinedSearchExpression != null)
{
filters.Add(combinedSearchExpression);
}
return filters;
}
/// <summary>
/// Adds entity-based search filters by creating text search expressions across multiple properties.
/// Automatically handles negation for terms starting with '!' and creates appropriate OR/AND combinations.
/// </summary>
/// <typeparam name="T">The entity type to filter</typeparam>
/// <param name="filters">The existing list of filter expressions to add to</param>
/// <param name="search">Search string containing terms to search for across the specified properties</param>
/// <param name="propertySelectors">Expression selectors for the string properties to search within</param>
/// <returns>The updated list of filter expressions</returns>
public static List<Expression<Func<T, bool>>> AddEntityFilters<T>(
this List<Expression<Func<T, bool>>> filters,
string search,
params Expression<Func<T, string>>[] propertySelectors)
where T : class
{
if (string.IsNullOrEmpty(search) || propertySelectors?.Length == 0)
return filters;
return filters.AddSearchFilters(search, searchTerm =>
{
if (searchTerm.StartsWith('!'))
{
searchTerm = searchTerm[1..];
if (string.IsNullOrWhiteSpace(searchTerm))
{
// No valid search term after '!', return a no-op filter (matches everything)
return m => true;
}
return CreateNegationExpression(searchTerm, propertySelectors);
}
return CreatePositiveExpression(searchTerm, propertySelectors);
});
}
/// <summary>
/// Creates a new list of entity filters for text-based searching across specified properties.
/// This is a convenience method that starts with an empty filter list.
/// </summary>
/// <typeparam name="T">The entity type to filter</typeparam>
/// <param name="search">Search string containing terms to search for</param>
/// <param name="propertySelectors">Expression selectors for the string properties to search within</param>
/// <returns>A new list containing the generated filter expressions</returns>
public static List<Expression<Func<T, bool>>> CreateEntityFilters<T>(
this string search,
params Expression<Func<T, string>>[] propertySelectors)
where T : class
{
var filters = new List<Expression<Func<T, bool>>>();
return filters.AddEntityFilters(search, propertySelectors);
}
/// <summary>
/// Creates entity filters using a flexible parameter array approach that supports mixed enum and property selectors.
/// Processes parameters in pairs where enum arrays are followed by their corresponding property selectors.
/// </summary>
/// <typeparam name="T">The entity type to filter</typeparam>
/// <param name="search">Search string for text-based filtering</param>
/// <param name="parameters">Mixed array of enum arrays, property selectors for enums, and property selectors for text search</param>
/// <returns>A new list containing the generated filter expressions</returns>
public static List<Expression<Func<T, bool>>> CreateEntityFilters<T>(
this string search,
params object[] parameters)
where T : class
{
var filters = new List<Expression<Func<T, bool>>>();
// Add search filters first
if (!string.IsNullOrEmpty(search))
{
// Extract property selectors from the end of the parameters array
var propertySelectors = parameters
.OfType<Expression<Func<T, string>>>()
.ToArray();
if (propertySelectors.Length > 0)
{
filters = filters.AddEntityFilters(search, propertySelectors);
}
}
// Process enum filters
for (int i = 0; i < parameters.Length - 1; i += 2)
{
if (parameters[i] is Array enumArray &&
parameters[i + 1] is Expression<Func<T, string>> propertySelector)
{
filters = filters.AddEnumFilter(enumArray, propertySelector);
}
}
return filters;
}
/// <summary>
/// Record type that encapsulates an enum filter configuration, pairing enum values with their target property.
/// Used for strongly-typed enum filtering operations.
/// </summary>
/// <typeparam name="T">The entity type being filtered</typeparam>
/// <param name="EnumValues">Array of enum values to filter by (nullable)</param>
/// <param name="PropertySelector">Expression that selects the string property to match against enum names</param>
public record EnumFilter<T>(Array? EnumValues, Expression<Func<T, string>> PropertySelector) where T : class;
/// <summary>
/// Creates entity filters with support for both text search and structured enum filtering.
/// Combines multiple filter types into a single operation for complex query scenarios.
/// </summary>
/// <typeparam name="T">The entity type to filter</typeparam>
/// <param name="search">Search string for text-based filtering across properties</param>
/// <param name="enumFilters">Array of structured enum filter configurations (optional)</param>
/// <param name="propertySelectors">Expression selectors for properties to include in text search</param>
/// <returns>A new list containing all generated filter expressions</returns>
public static List<Expression<Func<T, bool>>> CreateEntityFilters<T>(
this string search,
EnumFilter<T>[]? enumFilters = null,
params Expression<Func<T, string>>[] propertySelectors)
where T : class
{
var filters = new List<Expression<Func<T, bool>>>();
// Add search filters
if (!string.IsNullOrEmpty(search) && propertySelectors?.Length > 0)
{
filters = filters.AddEntityFilters(search, propertySelectors);
}
// Add enum filters
if (enumFilters != null)
{
foreach (var enumFilter in enumFilters)
{
filters = filters.AddEnumFilter(enumFilter.EnumValues, enumFilter.PropertySelector);
}
}
return filters;
}
/// <summary>
/// Adds an enum-based filter to the existing filter list. Converts enum values to uppercase strings
/// and creates an expression that checks if the target property contains any of those values.
/// Uses expression trees for Entity Framework compatibility.
/// </summary>
/// <typeparam name="T">The entity type to filter</typeparam>
/// <param name="filters">The existing list of filter expressions to add to</param>
/// <param name="enumArray">Array of enum values to filter by (null values are ignored)</param>
/// <param name="propertySelector">Expression that selects the string property to match against enum names</param>
/// <returns>The updated list of filter expressions</returns>
public static List<Expression<Func<T, bool>>> AddEnumFilter<T>(
this List<Expression<Func<T, bool>>> filters,
Array? enumArray,
Expression<Func<T, string>> propertySelector)
where T : class
{
if (enumArray?.Length > 0)
{
var names = enumArray.Cast<Enum>()
.Select(enumValue => Enum.GetName(enumValue.GetType(), enumValue))
.Where(name => name != null)
.Select(name => name!.ToUpper())
.ToList(); // Convert to list to avoid multiple enumerations
if (names.Count > 0)
{
// Build the expression tree properly
var parameter = Expression.Parameter(typeof(T), "m");
var property = Expression.Invoke(propertySelector, parameter);
var nullCheck = Expression.NotEqual(property, Expression.Constant(null, typeof(string)));
// Create contains check using expression trees
var namesConstant = Expression.Constant(names);
var containsMethod = typeof(List<string>).GetMethod("Contains", new[] { typeof(string) });
var containsCall = Expression.Call(namesConstant, containsMethod!, property);
// Combine null check and contains check
var finalExpression = Expression.AndAlso(nullCheck, containsCall);
var lambda = Expression.Lambda<Func<T, bool>>(finalExpression, parameter);
filters.Add(lambda);
}
}
return filters;
}
/// <summary>
/// Creates a positive search expression that uses OR logic to match the search term against any of the specified properties.
/// Each property is converted to lowercase before checking if it contains the search term.
/// </summary>
/// <typeparam name="T">The entity type to filter</typeparam>
/// <param name="searchTerm">The term to search for (should be lowercase)</param>
/// <param name="propertySelectors">Expression selectors for the string properties to search within</param>
/// <returns>An expression that returns true if any property contains the search term</returns>
private static Expression<Func<T, bool>>? CreatePositiveExpression<T>(
string searchTerm,
Expression<Func<T, string>>[] propertySelectors)
where T : class
{
Expression<Func<T, bool>>? positiveExpression = null;
foreach (var propertySelector in propertySelectors)
{
var parameter = Expression.Parameter(typeof(T), "m");
var property = Expression.Invoke(propertySelector, parameter);
var toLowerCall = Expression.Call(property, typeof(string).GetMethod("ToLower", Type.EmptyTypes)!);
var containsCall = Expression.Call(toLowerCall, typeof(string).GetMethod("Contains", new[] { typeof(string) })!, Expression.Constant(searchTerm));
if (positiveExpression == null)
{
positiveExpression = Expression.Lambda<Func<T, bool>>(containsCall, parameter);
}
else
{
var newParam = Expression.Parameter(typeof(T), "m");
var left = new ReplaceParameterVisitor(positiveExpression.Parameters[0], newParam).Visit(positiveExpression.Body);
var right = new ReplaceParameterVisitor(parameter, newParam).Visit(containsCall);
var orExpression = Expression.OrElse(left, right);
positiveExpression = Expression.Lambda<Func<T, bool>>(orExpression, newParam);
}
}
return positiveExpression;
}
/// <summary>
/// Creates a negation search expression that uses AND logic to ensure the search term is NOT found in any of the specified properties.
/// Each property is converted to lowercase before checking that it does not contain the search term.
/// </summary>
/// <typeparam name="T">The entity type to filter</typeparam>
/// <param name="searchTerm">The term to exclude from search results (should be lowercase)</param>
/// <param name="propertySelectors">Expression selectors for the string properties to search within</param>
/// <returns>An expression that returns true if no property contains the search term</returns>
private static Expression<Func<T, bool>>? CreateNegationExpression<T>(
string searchTerm,
Expression<Func<T, string>>[] propertySelectors)
where T : class
{
Expression<Func<T, bool>>? negationExpression = null;
foreach (var propertySelector in propertySelectors)
{
var parameter = Expression.Parameter(typeof(T), "m");
var property = Expression.Invoke(propertySelector, parameter);
var toLowerCall = Expression.Call(property, typeof(string).GetMethod("ToLower", Type.EmptyTypes)!);
var containsCall = Expression.Call(toLowerCall, typeof(string).GetMethod("Contains", new[] { typeof(string) })!, Expression.Constant(searchTerm));
// Add null check: if property is null, treat as "does not contain" (i.e., true)
var notContainsWithNullCheck = Expression.OrElse(
Expression.Equal(property, Expression.Constant(null)),
Expression.Not(containsCall)
);
if (negationExpression == null)
{
negationExpression = Expression.Lambda<Func<T, bool>>(notContainsWithNullCheck, parameter);
}
else
{
var newParam = Expression.Parameter(typeof(T), "m");
var left = new ReplaceParameterVisitor(negationExpression.Parameters[0], newParam).Visit(negationExpression.Body);
var right = new ReplaceParameterVisitor(parameter, newParam).Visit(notContainsWithNullCheck);
var andExpression = Expression.AndAlso(left, right);
negationExpression = Expression.Lambda<Func<T, bool>>(andExpression, newParam);
}
}
return negationExpression;
}
/// <summary>
/// Helper class that visits expression trees and replaces parameter references.
/// Used to combine multiple expressions that use different parameter instances but represent the same entity.
/// Essential for properly merging expression trees without parameter conflicts.
/// </summary>
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldParameter;
private readonly ParameterExpression _newParameter;
/// <summary>
/// Initializes a new instance of the ReplaceParameterVisitor class.
/// </summary>
/// <param name="oldParameter">The parameter expression to replace</param>
/// <param name="newParameter">The parameter expression to replace it with</param>
public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
/// <summary>
/// Visits parameter nodes in the expression tree and replaces matching parameters.
/// </summary>
/// <param name="node">The parameter expression node being visited</param>
/// <returns>The replacement parameter if it matches the target, otherwise the original node</returns>
protected override Expression VisitParameter(ParameterExpression node)
{
return node == _oldParameter ? _newParameter : base.VisitParameter(node);
}
}
}
}
@@ -0,0 +1,15 @@
namespace Strata.RxNorm.Client.FactNdcDeatils
{
public class FactNdcDetail
{
public string NdcCode { get; set; }
public string BrandCode { get; set; }
public string BrandDescription { get; set; }
public string DoseFormCode { get; set; }
public string DoseFormDescription { get; set; }
public string BrandedClinicalDrugCode { get; set; }
public string BrandedClinicalDrugDescription { get; set; }
public string SemanticClinicalDrugCode { get; set; }
public string SemanticClinicalDrugDescription { get; set; }
}
}
@@ -0,0 +1,11 @@
using Strata.RxNorm.Client.FactNdcDeatils;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.RxNorm.Client
{
public interface IRxNormService
{
Task<FactNdcDetail> GetAsync(string ndc, CancellationToken cancellationToken = default);
}
}

Some files were not shown because too many files have changed in this diff Show More